From 150d065f431d665326fd8028748c48a74ad956bb Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Fri, 23 Oct 2015 15:00:43 +0200 Subject: [PATCH] centralize entity controllers --- src/ai/AIController.hpp | 44 +++++++++ src/ai/Chaser.hpp | 35 ------- src/ai/Controller.hpp | 27 ----- src/ai/RandomWalk.hpp | 40 -------- src/ai/Spawner.cpp | 44 ++++----- src/ai/Spawner.hpp | 5 +- src/ai/ai.cpp | 121 +++++++---------------- src/app/IntervalTimer.hpp | 56 +++++++---- src/audio/Audio.hpp | 2 +- src/audio/audio.cpp | 4 +- src/client/ChunkReceiver.hpp | 2 +- src/client/InteractiveState.hpp | 2 +- src/client/NetworkedInput.hpp | 8 +- src/client/client.cpp | 19 ++-- src/client/net.cpp | 12 ++- src/net/Connection.hpp | 4 +- src/rand/GaloisLFSR.hpp | 8 ++ src/server/ClientConnection.hpp | 2 +- src/server/ServerState.hpp | 2 +- src/server/net.cpp | 1 - src/standalone/MasterState.cpp | 5 +- src/ui/DirectInput.hpp | 6 +- src/ui/HUD.hpp | 2 +- src/ui/PlayerController.hpp | 7 +- src/ui/ui.cpp | 16 +-- src/world/Entity.hpp | 40 +++++++- src/world/EntityController.hpp | 24 +++++ src/world/world.cpp | 77 +++++++++++++-- tst/app/TimerTest.cpp | 170 +++++++++++++++++++++++++++----- tst/app/TimerTest.hpp | 6 +- 30 files changed, 475 insertions(+), 316 deletions(-) create mode 100644 src/ai/AIController.hpp delete mode 100644 src/ai/Chaser.hpp delete mode 100644 src/ai/Controller.hpp delete mode 100644 src/ai/RandomWalk.hpp create mode 100644 src/world/EntityController.hpp diff --git a/src/ai/AIController.hpp b/src/ai/AIController.hpp new file mode 100644 index 0000000..53bdc82 --- /dev/null +++ b/src/ai/AIController.hpp @@ -0,0 +1,44 @@ +#ifndef BLANK_AI_AICONTROLLER_HPP_ +#define BLANK_AI_AICONTROLLER_HPP_ + +#include "../world/EntityController.hpp" + +#include + + +namespace blank { + +class GaloisLFSR; + +class AIController +: public EntityController { + +public: + explicit AIController(GaloisLFSR &); + ~AIController(); + + void Update(Entity &, float dt) override; + + glm::vec3 ControlForce(const EntityState &) const override; + + static glm::vec3 Heading(const EntityState &) noexcept; + +private: + GaloisLFSR &random; + + float chase_speed; + float flee_speed; + float stop_dist; + float flee_dist; + + glm::vec3 wander_pos; + float wander_dist; + float wander_radius; + float wander_disp; + float wander_speed; + +}; + +} + +#endif diff --git a/src/ai/Chaser.hpp b/src/ai/Chaser.hpp deleted file mode 100644 index 43667c0..0000000 --- a/src/ai/Chaser.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef BLANK_AI_CHASER_HPP_ -#define BLANK_AI_CHASER_HPP_ - -#include "Controller.hpp" - - -namespace blank { - -class World; - -class Chaser -: public Controller { - -public: - Chaser(World &, Entity &ctrl, Entity &tgt) noexcept; - ~Chaser(); - - Entity &Target() noexcept { return tgt; } - const Entity &Target() const noexcept { return tgt; } - - void Update(int dt) override; - -private: - World &world; - Entity &tgt; - float chase_speed; - float flee_speed; - float stop_dist; - float flee_dist; - -}; - -} - -#endif diff --git a/src/ai/Controller.hpp b/src/ai/Controller.hpp deleted file mode 100644 index d2d6b1c..0000000 --- a/src/ai/Controller.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef BLANK_AI_CONTROLLER_HPP_ -#define BLANK_AI_CONTROLLER_HPP_ - - -namespace blank { - -class Entity; - -class Controller { - -public: - explicit Controller(Entity &e) noexcept; - virtual ~Controller(); - - Entity &Controlled() noexcept { return entity; } - const Entity &Controlled() const noexcept { return entity; } - - virtual void Update(int dt) = 0; - -private: - Entity &entity; - -}; - -} - -#endif diff --git a/src/ai/RandomWalk.hpp b/src/ai/RandomWalk.hpp deleted file mode 100644 index 339e77d..0000000 --- a/src/ai/RandomWalk.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef BLANK_AI_RANDOMWALK_HPP_ -#define BLANK_AI_RANDOMWALK_HPP_ - -#include "Controller.hpp" - -#include "../rand/GaloisLFSR.hpp" - -#include - - -namespace blank { - -/// Randomly start or stop moving in axis directions every now and then. -class RandomWalk -: public Controller { - -public: - RandomWalk(Entity &, std::uint64_t seed) noexcept; - ~RandomWalk(); - - void Update(int dt) override; - -private: - void Change() noexcept; - -private: - GaloisLFSR random; - - glm::vec3 start_vel; - glm::vec3 target_vel; - - int switch_time; - float lerp_max; - float lerp_time; - -}; - -} - -#endif diff --git a/src/ai/Spawner.cpp b/src/ai/Spawner.cpp index 7b6e4b2..94220d4 100644 --- a/src/ai/Spawner.cpp +++ b/src/ai/Spawner.cpp @@ -1,7 +1,7 @@ #include "Spawner.hpp" -#include "Chaser.hpp" -#include "RandomWalk.hpp" +#include "AIController.hpp" + #include "../model/Model.hpp" #include "../model/ModelRegistry.hpp" #include "../rand/GaloisLFSR.hpp" @@ -21,7 +21,7 @@ namespace blank { Spawner::Spawner(World &world, ModelRegistry &models, GaloisLFSR &rand) : world(world) , models(models) -, controllers() +, entities() , random(rand) , timer(64) , despawn_range(128 * 128) @@ -34,8 +34,8 @@ Spawner::Spawner(World &world, ModelRegistry &models, GaloisLFSR &rand) } Spawner::~Spawner() { - for (auto &ctrl : controllers) { - delete ctrl; + for (Entity *e : entities) { + e->UnRef(); } } @@ -55,20 +55,17 @@ void Spawner::Update(int dt) { if (timer.Hit()) { TrySpawn(); } - for (auto &ctrl : controllers) { - ctrl->Update(dt); - } } void Spawner::CheckDespawn() noexcept { const auto &refs = world.Players(); - for (auto iter = controllers.begin(), end = controllers.end(); iter != end;) { - Entity &e = (*iter)->Controlled(); + for (auto iter = entities.begin(), end = entities.end(); iter != end;) { + Entity &e = (**iter); if (e.Dead()) { - delete *iter; - iter = controllers.erase(iter); - end = controllers.end(); + e.UnRef(); + iter = entities.erase(iter); + end = entities.end(); continue; } bool safe = false; @@ -81,9 +78,9 @@ void Spawner::CheckDespawn() noexcept { } if (!safe) { e.Kill(); - delete *iter; - iter = controllers.erase(iter); - end = controllers.end(); + e.UnRef(); + iter = entities.erase(iter); + end = entities.end(); } else { ++iter; } @@ -91,7 +88,7 @@ void Spawner::CheckDespawn() noexcept { } void Spawner::TrySpawn() { - if (controllers.size() >= max_entities || model_length == 0) return; + if (entities.size() >= max_entities || model_length == 0) return; // select random player to punish auto &players = world.Players(); @@ -130,15 +127,10 @@ void Spawner::Spawn(Entity &reference, const glm::ivec3 &chunk, const glm::vec3 e.Bounds({ { -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f } }); e.WorldCollidable(true); RandomModel().Instantiate(e.GetModel()); - Controller *ctrl; - if (random()) { - ctrl = new RandomWalk(e, random.Next()); - e.Name("walker"); - } else { - ctrl = new Chaser(world, e, reference); - e.Name("chaser"); - } - controllers.emplace_back(ctrl); + e.SetController(new AIController(random)); + e.Name("spawned"); + e.Ref(); + entities.emplace_back(&e); } Model &Spawner::RandomModel() noexcept { diff --git a/src/ai/Spawner.hpp b/src/ai/Spawner.hpp index be2ff31..f99029e 100644 --- a/src/ai/Spawner.hpp +++ b/src/ai/Spawner.hpp @@ -9,7 +9,6 @@ namespace blank { -class Controller; class Entity; class GaloisLFSR; class Model; @@ -36,11 +35,11 @@ private: private: World &world; ModelRegistry ⊧ - std::vector controllers; + std::vector entities; GaloisLFSR &random; - IntervalTimer timer; + CoarseTimer timer; float despawn_range; float spawn_distance; unsigned int max_entities; diff --git a/src/ai/ai.cpp b/src/ai/ai.cpp index 9ec9fec..1d424cc 100644 --- a/src/ai/ai.cpp +++ b/src/ai/ai.cpp @@ -1,114 +1,67 @@ -#include "Chaser.hpp" -#include "Controller.hpp" -#include "RandomWalk.hpp" +#include "AIController.hpp" #include "../model/geometry.hpp" +#include "../rand/GaloisLFSR.hpp" #include "../world/Entity.hpp" #include "../world/World.hpp" #include "../world/WorldCollision.hpp" +#include +#include #include namespace blank { -Chaser::Chaser(World &world, Entity &ctrl, Entity &tgt) noexcept -: Controller(ctrl) -, world(world) -, tgt(tgt) +AIController::AIController(GaloisLFSR &rand) +: random(rand) , chase_speed(2.0f) , flee_speed(-5.0f) -, stop_dist(10) -, flee_dist(5) { - tgt.Ref(); -} +, stop_dist(10.0f) +, flee_dist(5.0f) +, wander_pos(1.0f, 0.0f, 0.0f) +, wander_dist(2.0f) +, wander_radius(1.0f) +, wander_disp(1.0f) +, wander_speed(1.0f) { -Chaser::~Chaser() { - tgt.UnRef(); } -void Chaser::Update(int dt) { - if (Target().Dead()) { - Controlled().Kill(); - return; - } +AIController::~AIController() { - glm::vec3 diff(Target().AbsoluteDifference(Controlled())); - float dist = length(diff); - if (dist < std::numeric_limits::epsilon()) { - Controlled().TargetVelocity(glm::vec3(0.0f)); - return; - } - glm::vec3 norm_diff(diff / dist); +} - bool line_of_sight = true; - Ray aim{Target().Position() - diff, norm_diff}; - WorldCollision coll; - if (world.Intersection(aim, glm::mat4(1.0f), Target().ChunkCoords(), coll)) { - line_of_sight = coll.depth > dist; +void AIController::Update(Entity &e, float dt) { + // movement: for now, wander only + glm::vec3 displacement( + random.SNorm() * wander_disp, + random.SNorm() * wander_disp, + random.SNorm() * wander_disp + ); + if (dot(displacement, displacement) > std::numeric_limits::epsilon()) { + wander_pos = normalize(wander_pos + displacement * dt) * wander_radius; } - if (!line_of_sight) { - Controlled().TargetVelocity(glm::vec3(0.0f)); - } else if (dist > stop_dist) { - Controlled().TargetVelocity(norm_diff * chase_speed); - } else if (dist < flee_dist) { - Controlled().TargetVelocity(norm_diff * flee_speed); - } else { - Controlled().TargetVelocity(glm::vec3(0.0f)); + if (e.Moving()) { + // orient head towards heading + glm::vec3 heading(Heading(e.GetState())); + float tgt_pitch = std::atan(heading.y / length(glm::vec2(heading.x, heading.z))); + float tgt_yaw = std::atan2(-heading.x, -heading.z); + e.SetHead(tgt_pitch, tgt_yaw); } } - -Controller::Controller(Entity &e) noexcept -: entity(e) { - entity.Ref(); -} - -Controller::~Controller() { - entity.UnRef(); +glm::vec3 AIController::ControlForce(const EntityState &state) const { + return (Heading(state) * wander_dist + wander_pos) * wander_speed; } - -RandomWalk::RandomWalk(Entity &e, std::uint64_t seed) noexcept -: Controller(e) -, random(seed) -, start_vel(e.Velocity()) -, target_vel(start_vel) -, switch_time(0) -, lerp_max(1.0f) -, lerp_time(0.0f) { - -} - -RandomWalk::~RandomWalk() { - -} - -void RandomWalk::Update(int dt) { - switch_time -= dt; - lerp_time -= dt; - if (switch_time < 0) { - switch_time += 2500 + (random.Next() % 5000); - lerp_max = 1500 + (random.Next() % 1000); - lerp_time = lerp_max; - Change(); - } else if (lerp_time > 0) { - float a = std::min(lerp_time / lerp_max, 1.0f); - Controlled().TargetVelocity(mix(target_vel, start_vel, a)); +glm::vec3 AIController::Heading(const EntityState &state) noexcept { + if (dot(state.velocity, state.velocity) > std::numeric_limits::epsilon()) { + return normalize(state.velocity); } else { - Controlled().TargetVelocity(target_vel); + float cp = std::cos(state.pitch); + return glm::vec3(std::cos(state.yaw) * cp, std::sin(state.yaw) * cp, std::sin(state.pitch)); } } -void RandomWalk::Change() noexcept { - start_vel = target_vel; - - constexpr float base = 0.001f; - - target_vel.x = base * (random.Next() % 1024); - target_vel.y = base * (random.Next() % 1024); - target_vel.z = base * (random.Next() % 1024); -} - } diff --git a/src/app/IntervalTimer.hpp b/src/app/IntervalTimer.hpp index 5240f5d..ee84c65 100644 --- a/src/app/IntervalTimer.hpp +++ b/src/app/IntervalTimer.hpp @@ -1,46 +1,49 @@ #ifndef BLANK_APP_INTERVALTIMER_HPP #define BLANK_APP_INTERVALTIMER_HPP +#include + namespace blank { -/// Timer that hits every n milliseconds. Resolution is that of the -/// delta values passed to Update(), minimum 1ms. -/// Also tracks the number of iterations as well as milliseconds +/// Timer that hits every n Time units. Resolution is that of the +/// delta values passed to Update(). +/// Also tracks the number of iterations as well as Time units /// passed. +template class IntervalTimer { public: - /// Create a timer that hits every interval_ms milliseconds. + /// Create a timer that hits every interval Time units. /// Initial state is stopped. - explicit IntervalTimer(int interval_ms = 0) noexcept + explicit IntervalTimer(Time interval_ms = Time(0)) noexcept : intv(interval_ms) { } void Start() noexcept { - speed = 1; + speed = Time(1); } void Stop() noexcept { - value = 0; - speed = 0; + value = Time(0); + speed = Time(0); } void Reset() noexcept { - value = 0; + value = Time(0); } bool Running() const noexcept { - return speed != 0; + return speed != Time(0); } /// true if an interval boundary was passed by the last call to Update() bool Hit() const noexcept { - return Running() && value % intv < last_dt; + return Running() && mod(value, intv) < last_dt; } bool HitOnce() const noexcept { return Running() && value >= intv; } - int Elapsed() const noexcept { + Time Elapsed() const noexcept { return value; } - int Interval() const noexcept { + Time Interval() const noexcept { return intv; } int Iteration() const noexcept { @@ -50,19 +53,36 @@ public: value -= intv; } - void Update(int dt) noexcept { + void Update(Time dt) noexcept { value += dt * speed; last_dt = dt; } + static Time mod(Time val, Time m) noexcept { + return val % m; + } + private: - int intv; - int value = 0; - int speed = 0; - int last_dt = 0; + Time intv; + Time value = Time(0); + Time speed = Time(0); + Time last_dt = Time(0); }; +using CoarseTimer = IntervalTimer; +using FineTimer = IntervalTimer; + +template<> +inline float IntervalTimer::mod(float val, float m) noexcept { + return std::fmod(val, m); +} + +template<> +inline int IntervalTimer::Iteration() const noexcept { + return std::floor(value / intv); +} + } #endif diff --git a/src/audio/Audio.hpp b/src/audio/Audio.hpp index bfd6d47..800e247 100644 --- a/src/audio/Audio.hpp +++ b/src/audio/Audio.hpp @@ -42,7 +42,7 @@ private: private: static constexpr std::size_t NUM_SRC = 16; ALuint source[NUM_SRC]; - IntervalTimer timer[NUM_SRC]; + CoarseTimer timer[NUM_SRC]; int last_free; }; diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index a223e05..c7ed554 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -104,7 +104,7 @@ void Audio::Play( } ALuint src = source[i]; - IntervalTimer &t = timer[i]; + CoarseTimer &t = timer[i]; sound.Bind(src); alSourcefv(src, AL_POSITION, glm::value_ptr(pos)); @@ -112,7 +112,7 @@ void Audio::Play( alSourcefv(src, AL_DIRECTION, glm::value_ptr(dir)); alSourcePlay(src); - t = IntervalTimer(sound.Duration()); + t = CoarseTimer(sound.Duration()); t.Start(); } diff --git a/src/client/ChunkReceiver.hpp b/src/client/ChunkReceiver.hpp index d304a85..57c1ceb 100644 --- a/src/client/ChunkReceiver.hpp +++ b/src/client/ChunkReceiver.hpp @@ -43,7 +43,7 @@ private: ChunkStore &store; const WorldSave &save; std::list transmissions; - IntervalTimer timer; + CoarseTimer timer; }; diff --git a/src/client/InteractiveState.hpp b/src/client/InteractiveState.hpp index 55e03d0..ab65138 100644 --- a/src/client/InteractiveState.hpp +++ b/src/client/InteractiveState.hpp @@ -88,7 +88,7 @@ private: Interface interface; ChunkReceiver chunk_receiver; ChunkRenderer chunk_renderer; - IntervalTimer loop_timer; + CoarseTimer loop_timer; SkyBox sky; diff --git a/src/client/NetworkedInput.hpp b/src/client/NetworkedInput.hpp index a670222..b557b1b 100644 --- a/src/client/NetworkedInput.hpp +++ b/src/client/NetworkedInput.hpp @@ -20,7 +20,7 @@ class NetworkedInput public: explicit NetworkedInput(World &, Player &, Client &); - void Update(int dt); + void Update(Entity &, float dt) override; void PushPlayerUpdate(int dt); void MergePlayerCorrection(std::uint16_t, const EntityState &); @@ -36,11 +36,11 @@ private: struct PlayerHistory { EntityState state; - glm::vec3 tgt_vel; + glm::vec3 movement; float delta_t; std::uint16_t packet; - PlayerHistory(EntityState s, const glm::vec3 &tv, float dt, std::uint16_t p) - : state(s), tgt_vel(tv), delta_t(dt), packet(p) { } + PlayerHistory(EntityState s, const glm::vec3 &mv, float dt, std::uint16_t p) + : state(s), movement(mv), delta_t(dt), packet(p) { } }; std::list player_hist; diff --git a/src/client/client.cpp b/src/client/client.cpp index f0a3f35..ae1bf73 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -135,20 +135,10 @@ void InteractiveState::Handle(const SDL_Event &event) { } void InteractiveState::Update(int dt) { - input.Update(dt); - if (input.BlockFocus()) { - hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block); - } else if (input.EntityFocus()) { - hud.FocusEntity(*input.EntityFocus().entity); - } else { - hud.FocusNone(); - } - hud.Display(res.block_types[player.GetInventorySlot() + 1]); loop_timer.Update(dt); master.Update(dt); chunk_receiver.Update(dt); - hud.Update(dt); int world_dt = 0; while (loop_timer.HitOnce()) { world.Update(loop_timer.Interval()); @@ -157,9 +147,18 @@ void InteractiveState::Update(int dt) { } chunk_renderer.Update(dt); + if (input.BlockFocus()) { + hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block); + } else if (input.EntityFocus()) { + hud.FocusEntity(*input.EntityFocus().entity); + } else { + hud.FocusNone(); + } if (world_dt > 0) { input.PushPlayerUpdate(world_dt); } + hud.Display(res.block_types[player.GetInventorySlot() + 1]); + hud.Update(dt); glm::mat4 trans = player.GetEntity().Transform(player.GetEntity().ChunkCoords()); glm::vec3 dir(trans * glm::vec4(0.0f, 0.0f, -1.0f, 0.0f)); diff --git a/src/client/net.cpp b/src/client/net.cpp index b1076ba..6677f51 100644 --- a/src/client/net.cpp +++ b/src/client/net.cpp @@ -325,7 +325,7 @@ NetworkedInput::NetworkedInput(World &world, Player &player, Client &client) } -void NetworkedInput::Update(int dt) { +void NetworkedInput::Update(Entity &, float dt) { Invalidate(); UpdatePlayer(); } @@ -342,11 +342,11 @@ void NetworkedInput::PushPlayerUpdate(int dt) { InventorySlot() ); if (player_hist.size() < 16) { - player_hist.emplace_back(state, GetPlayer().GetEntity().TargetVelocity(), dt * 0.001f, packet); + player_hist.emplace_back(state, GetMovement(), dt * 0.001f, packet); } else { auto entry = player_hist.begin(); entry->state = state; - entry->tgt_vel = GetPlayer().GetEntity().TargetVelocity(); + entry->movement = GetMovement(); entry->delta_t = dt * 0.001f; entry->packet = packet; player_hist.splice(player_hist.end(), player_hist, entry); @@ -376,6 +376,8 @@ void NetworkedInput::MergePlayerCorrection(uint16_t seq, const EntityState &corr } } + glm::vec3 restore_movement(GetMovement()); + EntityState player_state = GetPlayer().GetEntity().GetState(); Entity replay(GetPlayer().GetEntity()); replay.SetState(corrected_state); @@ -389,7 +391,7 @@ void NetworkedInput::MergePlayerCorrection(uint16_t seq, const EntityState &corr vector col; while (entry != end) { replay.Velocity(entry->state.velocity); - replay.TargetVelocity(entry->tgt_vel); + SetMovement(entry->movement); GetWorld().Update(replay, entry->delta_t); entry->state.chunk_pos = replay.GetState().chunk_pos; entry->state.block_pos = replay.GetState().block_pos; @@ -400,6 +402,7 @@ void NetworkedInput::MergePlayerCorrection(uint16_t seq, const EntityState &corr const float disp_squared = dot(displacement, displacement); if (disp_squared < 16.0f * numeric_limits::epsilon()) { + SetMovement(restore_movement); return; } @@ -419,6 +422,7 @@ void NetworkedInput::MergePlayerCorrection(uint16_t seq, const EntityState &corr player_state.block_pos += displacement; } GetPlayer().GetEntity().SetState(player_state); + SetMovement(restore_movement); } void NetworkedInput::StartPrimaryAction() { diff --git a/src/net/Connection.hpp b/src/net/Connection.hpp index ed7a946..bb0d791 100644 --- a/src/net/Connection.hpp +++ b/src/net/Connection.hpp @@ -46,8 +46,8 @@ private: private: ConnectionHandler *handler; IPaddress addr; - IntervalTimer send_timer; - IntervalTimer recv_timer; + CoarseTimer send_timer; + CoarseTimer recv_timer; Packet::TControl ctrl_out; Packet::TControl ctrl_in; diff --git a/src/rand/GaloisLFSR.hpp b/src/rand/GaloisLFSR.hpp index 2ee476b..f5de7ad 100644 --- a/src/rand/GaloisLFSR.hpp +++ b/src/rand/GaloisLFSR.hpp @@ -48,6 +48,14 @@ public: return (*this)(next); } + float SNorm() noexcept { + return float(Next()) * (1.0f / 2147483647.5f) - 1.0f; + } + + float UNorm() noexcept { + return float(Next()) * (1.0f / 4294967295.0f); + } + template typename Container::reference From(Container &c) { return c[Next() % c.size()]; diff --git a/src/server/ClientConnection.hpp b/src/server/ClientConnection.hpp index 1a781c5..0a5dcd0 100644 --- a/src/server/ClientConnection.hpp +++ b/src/server/ClientConnection.hpp @@ -109,7 +109,7 @@ private: EntityState player_update_state; std::uint16_t player_update_pack; - IntervalTimer player_update_timer; + CoarseTimer player_update_timer; std::uint8_t old_actions; ChunkTransmitter transmitter; diff --git a/src/server/ServerState.hpp b/src/server/ServerState.hpp index fbf28e5..4ad6bd7 100644 --- a/src/server/ServerState.hpp +++ b/src/server/ServerState.hpp @@ -44,7 +44,7 @@ private: ChunkLoader chunk_loader; Spawner spawner; Server server; - IntervalTimer loop_timer; + CoarseTimer loop_timer; }; diff --git a/src/server/net.cpp b/src/server/net.cpp index cdd1a0a..38224ec 100644 --- a/src/server/net.cpp +++ b/src/server/net.cpp @@ -247,7 +247,6 @@ void ClientConnection::Update(int dt) { } SendUpdates(); - input->Update(dt); CheckPlayerFix(); CheckChunkQueue(); } diff --git a/src/standalone/MasterState.cpp b/src/standalone/MasterState.cpp index 9cc1543..81017f7 100644 --- a/src/standalone/MasterState.cpp +++ b/src/standalone/MasterState.cpp @@ -129,7 +129,8 @@ void MasterState::Handle(const SDL_Event &event) { } void MasterState::Update(int dt) { - input.Update(dt); + spawner.Update(dt); + world.Update(dt); if (input.BlockFocus()) { hud.FocusBlock(input.BlockFocus().GetChunk(), input.BlockFocus().block); } else if (input.EntityFocus()) { @@ -139,8 +140,6 @@ void MasterState::Update(int dt) { } hud.Display(res.block_types[player.GetInventorySlot() + 1]); hud.Update(dt); - spawner.Update(dt); - world.Update(dt); chunk_loader.Update(dt); chunk_renderer.Update(dt); diff --git a/src/ui/DirectInput.hpp b/src/ui/DirectInput.hpp index 142da66..7d7cd51 100644 --- a/src/ui/DirectInput.hpp +++ b/src/ui/DirectInput.hpp @@ -18,7 +18,7 @@ class DirectInput public: DirectInput(World &, Player &, WorldManipulator &); - void Update(int dt); + void Update(Entity &, float dt) override; void StartPrimaryAction() override; void StopPrimaryAction() override; @@ -35,8 +35,8 @@ private: private: WorldManipulator &manip; - IntervalTimer place_timer; - IntervalTimer remove_timer; + FineTimer place_timer; + FineTimer remove_timer; }; diff --git a/src/ui/HUD.hpp b/src/ui/HUD.hpp index 7db0258..2302fd2 100644 --- a/src/ui/HUD.hpp +++ b/src/ui/HUD.hpp @@ -81,7 +81,7 @@ private: // message box MessageBox messages; - IntervalTimer msg_timer; + CoarseTimer msg_timer; bool msg_keep; // crosshair diff --git a/src/ui/PlayerController.hpp b/src/ui/PlayerController.hpp index b9d3b2b..2245413 100644 --- a/src/ui/PlayerController.hpp +++ b/src/ui/PlayerController.hpp @@ -4,6 +4,7 @@ #include #include "../world/EntityCollision.hpp" +#include "../world/EntityController.hpp" #include "../world/WorldCollision.hpp" @@ -12,7 +13,8 @@ namespace blank { class Player; class World; -class PlayerController { +class PlayerController +: public EntityController { public: PlayerController(World &, Player &); @@ -31,6 +33,9 @@ public: /// the magnitude (clamped to [0..1]) can be used to attenuate target velocity void SetMovement(const glm::vec3 &) noexcept; const glm::vec3 &GetMovement() const noexcept { return move_dir; } + + glm::vec3 ControlForce(const EntityState &) const override; + /// turn the controlled entity's head by given pitch and yaw deltas void TurnHead(float pitch, float yaw) noexcept; diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index 5a39fa8..1af9e87 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -40,7 +40,7 @@ PlayerController::PlayerController(World &world, Player &player) , dirty(true) , aim_world() , aim_entity() { - + player.GetEntity().SetController(*this); } void PlayerController::SetMovement(const glm::vec3 &m) noexcept { @@ -52,6 +52,11 @@ void PlayerController::SetMovement(const glm::vec3 &m) noexcept { Invalidate(); } +glm::vec3 PlayerController::ControlForce(const EntityState &s) const { + glm::vec3 target(rotateY(move_dir * player.GetEntity().MaxVelocity(), s.yaw) - s.velocity); + return target * player.GetEntity().MaxControlForce(); +} + void PlayerController::TurnHead(float dp, float dy) noexcept { player.GetEntity().TurnHead(dp, dy); } @@ -77,10 +82,7 @@ void PlayerController::Invalidate() noexcept { } void PlayerController::UpdatePlayer() noexcept { - constexpr float max_vel = 5.0f; // in m/s if (dirty) { - player.GetEntity().TargetVelocity(glm::rotateY(move_dir * max_vel, player.GetEntity().Yaw())); - Ray aim = player.Aim(); if (!world.Intersection(aim, glm::mat4(1.0f), player.GetEntity().ChunkCoords(), aim_world)) { aim_world = WorldCollision(); @@ -104,12 +106,12 @@ void PlayerController::UpdatePlayer() noexcept { DirectInput::DirectInput(World &world, Player &player, WorldManipulator &manip) : PlayerController(world, player) , manip(manip) -, place_timer(256) -, remove_timer(256) { +, place_timer(0.25f) +, remove_timer(0.25f) { } -void DirectInput::Update(int dt) { +void DirectInput::Update(Entity &, float dt) { Invalidate(); // world has changed in the meantime UpdatePlayer(); diff --git a/src/world/Entity.hpp b/src/world/Entity.hpp index 6affc21..33eeb7d 100644 --- a/src/world/Entity.hpp +++ b/src/world/Entity.hpp @@ -15,12 +15,29 @@ namespace blank { class DirectionalLighting; +class EntityController; class Shape; class Entity { public: Entity() noexcept; + ~Entity() noexcept; + + // note that when copying an entity which owns its controller, the + // original must outlive the copy, otherwise the copy ends up with + // an invalid controller pointer + Entity(const Entity &) noexcept; + Entity &operator =(const Entity &) = delete; + + bool HasController() const noexcept { return ctrl; } + // entity takes over ownership of controller + void SetController(EntityController *c) noexcept; + // entity uses shared controller + void SetController(EntityController &c) noexcept; + void UnsetController() noexcept; + EntityController &GetController() noexcept { return *ctrl; } + const EntityController &GetController() const noexcept { return *ctrl; } Instance &GetModel() noexcept { return model; } const Instance &GetModel() const noexcept { return model; } @@ -37,13 +54,20 @@ public: bool WorldCollidable() const noexcept { return world_collision; } void WorldCollidable(bool b) noexcept { world_collision = b; } - /// desired velocity in local coordinate system - const glm::vec3 &TargetVelocity() const noexcept { return tgt_vel; } - void TargetVelocity(const glm::vec3 &v) noexcept { tgt_vel = v; } + float MaxVelocity() const noexcept { return max_vel; } + void MaxVelocity(float v) noexcept { max_vel = v; } + float MaxControlForce() const noexcept { return max_force; } + void MaxControlForce(float f) noexcept { max_force = f; } + + glm::vec3 ControlForce(const EntityState &) const noexcept; const glm::vec3 &Velocity() const noexcept { return state.velocity; } void Velocity(const glm::vec3 &v) noexcept { state.velocity = v; } + bool Moving() const noexcept { + return dot(Velocity(), Velocity()) > std::numeric_limits::epsilon(); + } + const glm::vec3 &Position() const noexcept { return state.block_pos; } void Position(const glm::ivec3 &, const glm::vec3 &) noexcept; void Position(const glm::vec3 &) noexcept; @@ -84,6 +108,8 @@ public: bool Dead() const noexcept { return dead; } bool CanRemove() const noexcept { return dead && ref_count <= 0; } + void Update(float dt); + void Render(const glm::mat4 &M, DirectionalLighting &prog) noexcept { if (model) model.Render(M, prog); } @@ -92,6 +118,7 @@ private: void UpdateModel() noexcept; private: + EntityController *ctrl; Instance model; std::uint32_t id; @@ -99,13 +126,18 @@ private: AABB bounds; EntityState state; - glm::vec3 tgt_vel; + + // TODO: I'd prefer a drag solution + float max_vel; + float max_force; int ref_count; bool world_collision; bool dead; + bool owns_controller; + }; } diff --git a/src/world/EntityController.hpp b/src/world/EntityController.hpp new file mode 100644 index 0000000..8b14ca8 --- /dev/null +++ b/src/world/EntityController.hpp @@ -0,0 +1,24 @@ +#ifndef BLANK_WORLD_ENTITYCONTROLLER_HPP_ +#define BLANK_WORLD_ENTITYCONTROLLER_HPP_ + +#include + + +namespace blank { + +class Entity; +class EntityState; + +struct EntityController { + + virtual ~EntityController(); + + virtual void Update(Entity &, float dt) = 0; + + virtual glm::vec3 ControlForce(const EntityState &) const = 0; + +}; + +} + +#endif diff --git a/src/world/world.cpp b/src/world/world.cpp index a852038..e77192b 100644 --- a/src/world/world.cpp +++ b/src/world/world.cpp @@ -1,4 +1,5 @@ #include "Entity.hpp" +#include "EntityController.hpp" #include "EntityDerivative.hpp" #include "EntityState.hpp" #include "Player.hpp" @@ -23,18 +24,68 @@ namespace blank { Entity::Entity() noexcept -: model() +: ctrl(nullptr) +, model() , id(-1) , name("anonymous") , bounds() , state() -, tgt_vel(0.0f) +, max_vel(5.0f) +, max_force(25.0f) , ref_count(0) , world_collision(false) -, dead(false) { +, dead(false) +, owns_controller(false) { } +Entity::~Entity() noexcept { + UnsetController(); +} + +Entity::Entity(const Entity &other) noexcept +: ctrl(other.ctrl) +, model(other.model) +, id(-1) +, name(other.name) +, bounds(other.bounds) +, state(other.state) +, max_vel(other.max_vel) +, max_force(other.max_force) +, ref_count(0) +, world_collision(other.world_collision) +, dead(other.dead) +, owns_controller(false) { + +} + +void Entity::SetController(EntityController *c) noexcept { + UnsetController(); + ctrl = c; + owns_controller = true; +} + +void Entity::SetController(EntityController &c) noexcept { + UnsetController(); + ctrl = &c; + owns_controller = false; +} + +void Entity::UnsetController() noexcept { + if (ctrl && owns_controller) { + delete ctrl; + } + ctrl = nullptr; +} + +glm::vec3 Entity::ControlForce(const EntityState &s) const noexcept { + if (HasController()) { + return GetController().ControlForce(s); + } else { + return -s.velocity; + } +} + void Entity::Position(const glm::ivec3 &c, const glm::vec3 &b) noexcept { state.chunk_pos = c; state.block_pos = b; @@ -91,6 +142,17 @@ void Entity::UpdateModel() noexcept { } } +void Entity::Update(float dt) { + if (HasController()) { + GetController().Update(*this, dt); + } +} + + +EntityController::~EntityController() { + +} + EntityState::EntityState() : chunk_pos(0) @@ -396,6 +458,9 @@ bool World::Intersection(const Entity &e, const EntityState &s, std::vector + CPPUNIT_TEST_SUITE_REGISTRATION(blank::test::TimerTest); @@ -15,96 +17,96 @@ void TimerTest::tearDown() { } -void TimerTest::testIntervalTimer() { - IntervalTimer timer(50); +void TimerTest::testCoarseTimer() { + CoarseTimer timer(50); CPPUNIT_ASSERT_MESSAGE( - "fresh timer is running", + "fresh coarse timer is running", !timer.Running() ); CPPUNIT_ASSERT_MESSAGE( - "fresh timer hit", + "fresh coarse timer hit", !timer.Hit() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "fresh timer with non-zero elapsed time", + "fresh coarse timer with non-zero elapsed time", 0, timer.Elapsed() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "fresh timer at non-zero iteration", + "fresh coarse timer at non-zero iteration", 0, timer.Iteration() ); timer.Start(); CPPUNIT_ASSERT_MESSAGE( - "startet timer is not running", + "startet coarse timer is not running", timer.Running() ); CPPUNIT_ASSERT_MESSAGE( - "started timer hit without update", + "started coarse timer hit without update", !timer.Hit() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "started, but not updated timer with non-zero elapsed time", + "started, but not updated coarse timer with non-zero elapsed time", 0, timer.Elapsed() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "started, but not updated timer at non-zero iteration", + "started, but not updated coarse timer at non-zero iteration", 0, timer.Iteration() ); timer.Update(25); CPPUNIT_ASSERT_MESSAGE( - "updated timer is not running", + "updated coarse timer is not running", timer.Running() ); CPPUNIT_ASSERT_MESSAGE( - "timer hit after update, but before it should", + "coarse timer hit after update, but before it should", !timer.Hit() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "wrong elapsed time on updated timer", + "wrong elapsed time on updated coarse timer", 25, timer.Elapsed() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "wrong iteration on updated timer", + "wrong iteration on updated coarse timer", 0, timer.Iteration() ); timer.Update(25); CPPUNIT_ASSERT_MESSAGE( - "timer not hit after updating to its exact interval time", + "coarse timer not hit after updating to its exact interval time", timer.Hit() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "wrong elapsed time on updated timer", + "wrong elapsed time on updated coarse timer", 50, timer.Elapsed() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "wrong iteration on updated timer at exact interval time", + "wrong iteration on updated coarse timer at exact interval time", 1, timer.Iteration() ); timer.Update(49); CPPUNIT_ASSERT_MESSAGE( - "timer hit after updating from exact interval time to just before the next", + "coarse timer hit after updating from exact interval time to just before the next", !timer.Hit() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "wrong elapsed time on updated timer", + "wrong elapsed time on updated coarse timer", 99, timer.Elapsed() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "wrong iteration after updating timer from exact interval time to just before the next", + "wrong iteration after updating coarse timer from exact interval time to just before the next", 1, timer.Iteration() ); timer.Update(2); CPPUNIT_ASSERT_MESSAGE( - "timer not hit after updating across interval time boundary", + "coarse timer not hit after updating across interval time boundary", timer.Hit() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "wrong elapsed time on updated timer", + "wrong elapsed time on updated coarse timer", 101, timer.Elapsed() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( @@ -114,19 +116,135 @@ void TimerTest::testIntervalTimer() { timer.Stop(); CPPUNIT_ASSERT_MESSAGE( - "stopped timer is running", + "stopped coarse timer is running", !timer.Running() ); CPPUNIT_ASSERT_MESSAGE( - "stopped timer hit", + "stopped coarse timer hit", !timer.Hit() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "stopped timer has non-zero elapsed time", + "stopped coarse timer has non-zero elapsed time", 0, timer.Elapsed() ); CPPUNIT_ASSERT_EQUAL_MESSAGE( - "stopped timer at non-zero iteration", + "stopped coarse timer at non-zero iteration", + 0, timer.Iteration() + ); +} + +void TimerTest::testFineTimer() { + FineTimer timer(0.5f); + CPPUNIT_ASSERT_MESSAGE( + "fresh fine timer is running", + !timer.Running() + ); + CPPUNIT_ASSERT_MESSAGE( + "fresh fine timer hit", + !timer.Hit() + ); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "fresh fine timer with non-zero elapsed time", + 0.0f, timer.Elapsed(), std::numeric_limits::epsilon() + ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "fresh fine timer at non-zero iteration", + 0, timer.Iteration() + ); + + timer.Start(); + CPPUNIT_ASSERT_MESSAGE( + "startet fine timer is not running", + timer.Running() + ); + CPPUNIT_ASSERT_MESSAGE( + "started fine timer hit without update", + !timer.Hit() + ); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "started, but not updated fine timer with non-zero elapsed time", + 0.0f, timer.Elapsed(), std::numeric_limits::epsilon() + ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "started, but not updated fine timer at non-zero iteration", + 0, timer.Iteration() + ); + + timer.Update(0.25f); + CPPUNIT_ASSERT_MESSAGE( + "updated fine timer is not running", + timer.Running() + ); + CPPUNIT_ASSERT_MESSAGE( + "fine timer hit after update, but before it should", + !timer.Hit() + ); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "wrong elapsed time on updated fine timer", + 0.25f, timer.Elapsed(), std::numeric_limits::epsilon() + ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "wrong iteration on updated fine timer", + 0, timer.Iteration() + ); + + timer.Update(0.25f); + CPPUNIT_ASSERT_MESSAGE( + "fine timer not hit after updating to its exact interval time", + timer.Hit() + ); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "wrong elapsed time on updated fine timer", + 0.5f, timer.Elapsed(), std::numeric_limits::epsilon() + ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "wrong iteration on updated fine timer at exact interval time", + 1, timer.Iteration() + ); + + timer.Update(0.49f); + CPPUNIT_ASSERT_MESSAGE( + "fine timer hit after updating from exact interval time to just before the next", + !timer.Hit() + ); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "wrong elapsed time on updated fine timer", + 0.99f, timer.Elapsed(), std::numeric_limits::epsilon() + ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "wrong iteration after updating fine timer from exact interval time to just before the next", + 1, timer.Iteration() + ); + + timer.Update(0.02f); + CPPUNIT_ASSERT_MESSAGE( + "fine timer not hit after updating across interval time boundary", + timer.Hit() + ); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "wrong elapsed time on updated fine timer", + 1.01f, timer.Elapsed(), std::numeric_limits::epsilon() + ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "wrong iteration after updating across interval time boundary", + 2, timer.Iteration() + ); + + timer.Stop(); + CPPUNIT_ASSERT_MESSAGE( + "stopped fine timer is running", + !timer.Running() + ); + CPPUNIT_ASSERT_MESSAGE( + "stopped fine timer hit", + !timer.Hit() + ); + CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( + "stopped fine timer has non-zero elapsed time", + 0.0f, timer.Elapsed(), std::numeric_limits::epsilon() + ); + CPPUNIT_ASSERT_EQUAL_MESSAGE( + "stopped fine timer at non-zero iteration", 0, timer.Iteration() ); } diff --git a/tst/app/TimerTest.hpp b/tst/app/TimerTest.hpp index ead7d43..b99ce70 100644 --- a/tst/app/TimerTest.hpp +++ b/tst/app/TimerTest.hpp @@ -12,7 +12,8 @@ class TimerTest CPPUNIT_TEST_SUITE(TimerTest); -CPPUNIT_TEST(testIntervalTimer); +CPPUNIT_TEST(testCoarseTimer); +CPPUNIT_TEST(testFineTimer); CPPUNIT_TEST_SUITE_END(); @@ -20,7 +21,8 @@ public: void setUp(); void tearDown(); - void testIntervalTimer(); + void testCoarseTimer(); + void testFineTimer(); }; -- 2.39.2