]> git.localhorst.tv Git - blank.git/commitdiff
centralize entity controllers
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 23 Oct 2015 13:00:43 +0000 (15:00 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Fri, 23 Oct 2015 13:05:28 +0000 (15:05 +0200)
30 files changed:
src/ai/AIController.hpp [new file with mode: 0644]
src/ai/Chaser.hpp [deleted file]
src/ai/Controller.hpp [deleted file]
src/ai/RandomWalk.hpp [deleted file]
src/ai/Spawner.cpp
src/ai/Spawner.hpp
src/ai/ai.cpp
src/app/IntervalTimer.hpp
src/audio/Audio.hpp
src/audio/audio.cpp
src/client/ChunkReceiver.hpp
src/client/InteractiveState.hpp
src/client/NetworkedInput.hpp
src/client/client.cpp
src/client/net.cpp
src/net/Connection.hpp
src/rand/GaloisLFSR.hpp
src/server/ClientConnection.hpp
src/server/ServerState.hpp
src/server/net.cpp
src/standalone/MasterState.cpp
src/ui/DirectInput.hpp
src/ui/HUD.hpp
src/ui/PlayerController.hpp
src/ui/ui.cpp
src/world/Entity.hpp
src/world/EntityController.hpp [new file with mode: 0644]
src/world/world.cpp
tst/app/TimerTest.cpp
tst/app/TimerTest.hpp

diff --git a/src/ai/AIController.hpp b/src/ai/AIController.hpp
new file mode 100644 (file)
index 0000000..53bdc82
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef BLANK_AI_AICONTROLLER_HPP_
+#define BLANK_AI_AICONTROLLER_HPP_
+
+#include "../world/EntityController.hpp"
+
+#include <glm/glm.hpp>
+
+
+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 (file)
index 43667c0..0000000
+++ /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 (file)
index d2d6b1c..0000000
+++ /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 (file)
index 339e77d..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef BLANK_AI_RANDOMWALK_HPP_
-#define BLANK_AI_RANDOMWALK_HPP_
-
-#include "Controller.hpp"
-
-#include "../rand/GaloisLFSR.hpp"
-
-#include <glm/glm.hpp>
-
-
-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
index 7b6e4b2e8b730cb31ca91f133d9010e5e3caeca8..94220d4af0f608b7b761cabf4c75c7cae608c0a7 100644 (file)
@@ -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<std::uint64_t>());
-               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 {
index be2ff31b7307e99f484c5b183c788e329d260e82..f99029e28f7fdec7b1f08f34d4ef6ff9ef28d269 100644 (file)
@@ -9,7 +9,6 @@
 
 namespace blank {
 
-class Controller;
 class Entity;
 class GaloisLFSR;
 class Model;
@@ -36,11 +35,11 @@ private:
 private:
        World &world;
        ModelRegistry &models;
-       std::vector<Controller *> controllers;
+       std::vector<Entity *> entities;
 
        GaloisLFSR &random;
 
-       IntervalTimer timer;
+       CoarseTimer timer;
        float despawn_range;
        float spawn_distance;
        unsigned int max_entities;
index 9ec9fecad2ab0542e72df3e82b199ab7bc7ce01a..1d424cc218c73c9224f3d0abe6eea37a41bae06f 100644 (file)
-#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 <cmath>
+#include <limits>
 #include <glm/glm.hpp>
 
 
 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<float>::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<float>::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<unsigned short>() % 5000);
-               lerp_max = 1500 + (random.Next<unsigned short>() % 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<float>::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<short>() % 1024);
-       target_vel.y = base * (random.Next<short>() % 1024);
-       target_vel.z = base * (random.Next<short>() % 1024);
-}
-
 }
index 5240f5d3f60a419e8593b5e883dab9fd5c8e8d1c..ee84c65e1b02c068c2de7ae1c5de9ec237e52c9e 100644 (file)
@@ -1,46 +1,49 @@
 #ifndef BLANK_APP_INTERVALTIMER_HPP
 #define BLANK_APP_INTERVALTIMER_HPP
 
+#include <cmath>
+
 
 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 Time = int>
 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<int>;
+using FineTimer = IntervalTimer<float>;
+
+template<>
+inline float IntervalTimer<float>::mod(float val, float m) noexcept {
+       return std::fmod(val, m);
+}
+
+template<>
+inline int IntervalTimer<float>::Iteration() const noexcept {
+       return std::floor(value / intv);
+}
+
 }
 
 #endif
index bfd6d47594f4e81b175aa7a860269d51fb0439bd..800e247c010ef770a1a998aae8abd969880dcbec 100644 (file)
@@ -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;
 
 };
index a223e05a496d7e4889be4f02cb73ce626fb764b4..c7ed554ee8304e880fb8ce2f2587437b76d27b1d 100644 (file)
@@ -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();
 }
 
index d304a85384f739e60fe05a55ad7b881e858f8683..57c1cebbc1255118e1f39f3ad266d60bef8936eb 100644 (file)
@@ -43,7 +43,7 @@ private:
        ChunkStore &store;
        const WorldSave &save;
        std::list<ChunkTransmission> transmissions;
-       IntervalTimer timer;
+       CoarseTimer timer;
 
 };
 
index 55e03d029f630ddf8481870476546b2677aecd89..ab6513813e49f01c3049dfa45f336ea758ff2268 100644 (file)
@@ -88,7 +88,7 @@ private:
        Interface interface;
        ChunkReceiver chunk_receiver;
        ChunkRenderer chunk_renderer;
-       IntervalTimer loop_timer;
+       CoarseTimer loop_timer;
 
        SkyBox sky;
 
index a6702228a149e6c1b80f78812a0dcd510e8e9ec3..b557b1b0b3eb289f92aa8e152d8398ea68ca5644 100644 (file)
@@ -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<PlayerHistory> player_hist;
 
index f0a3f35c7b984825d2dcffbc4c529a8c0d9815cb..ae1bf7378f59f6ed12494a08de8bd692e3b49aa6 100644 (file)
@@ -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));
index b1076ba56c41a98d97cb882168dcecff5118aab3..6677f5143d3647d9bd3bc7e24ad1a6ec4beca9b3 100644 (file)
@@ -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<WorldCollision> 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<float>::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() {
index ed7a946aab01d2296123f9f68229d46a2b9b4b48..bb0d791a9c5ce898eca36b35d297ec937a3b7510 100644 (file)
@@ -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;
index 2ee476b6e321cc5d0298993b75e134b0727aeaad..f5de7ad0bb650f15d9d6870d82e3bd6f3ee97b70 100644 (file)
@@ -48,6 +48,14 @@ public:
                return (*this)(next);
        }
 
+       float SNorm() noexcept {
+               return float(Next<std::uint32_t>()) * (1.0f / 2147483647.5f) - 1.0f;
+       }
+
+       float UNorm() noexcept {
+               return float(Next<std::uint32_t>()) * (1.0f / 4294967295.0f);
+       }
+
        template<class Container>
        typename Container::reference From(Container &c) {
                return c[Next<typename Container::size_type>() % c.size()];
index 1a781c5dd6ad5dea7f88b033e4648bed6aee4550..0a5dcd034db106e0b868ae8fe82b9a9505636a5d 100644 (file)
@@ -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;
index fbf28e5768f71c7003fa5436b2e6aa48a0da9892..4ad6bd72497bfd8ffae5da30edd4c5d90ad56ed8 100644 (file)
@@ -44,7 +44,7 @@ private:
        ChunkLoader chunk_loader;
        Spawner spawner;
        Server server;
-       IntervalTimer loop_timer;
+       CoarseTimer loop_timer;
 
 };
 
index cdd1a0a6092c47d9e9dab5b83f1474245c74ac3a..38224ec9a57e879e2a84357a77456b30253f3f1c 100644 (file)
@@ -247,7 +247,6 @@ void ClientConnection::Update(int dt) {
                }
                SendUpdates();
 
-               input->Update(dt);
                CheckPlayerFix();
                CheckChunkQueue();
        }
index 9cc154387e3f6becb55bf756b5df29606514f2fd..81017f78603cea89bca967d440089e7c26876e05 100644 (file)
@@ -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);
 
index 142da66495b9d3493375643ec249c5d417ab5a67..7d7cd5129e4eab36d6af5b1bfc15017c0f2c5d37 100644 (file)
@@ -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;
 
 };
 
index 7db025865c329c92cddde9916ee889fd85857832..2302fd2991d2ec954b54a77e41929f00bb5a0d5e 100644 (file)
@@ -81,7 +81,7 @@ private:
 
        // message box
        MessageBox messages;
-       IntervalTimer msg_timer;
+       CoarseTimer msg_timer;
        bool msg_keep;
 
        // crosshair
index b9d3b2bd6cf6854f53cf5586b07fa49dcc72cc12..22454133bd3c6971bec4f7ff9f3d65786653df13 100644 (file)
@@ -4,6 +4,7 @@
 #include <glm/glm.hpp>
 
 #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;
 
index 5a39fa8109ebfc810ed1a333c0d089d407db7706..1af9e8722f75cbc60c503b7882b6c187abde12a5 100644 (file)
@@ -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();
 
index 6affc21762a2bae9354d223c2203e5133d4317fa..33eeb7db20540e5e354f1398d7105894d7b3f6ea 100644 (file)
 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<float>::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 (file)
index 0000000..8b14ca8
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef BLANK_WORLD_ENTITYCONTROLLER_HPP_
+#define BLANK_WORLD_ENTITYCONTROLLER_HPP_
+
+#include <glm/glm.hpp>
+
+
+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
index a852038664a05410292a2e7bdd58ecfb0183f2de..e77192b277b46c8a592c9049f00601c59be1a08f 100644 (file)
@@ -1,4 +1,5 @@
 #include "Entity.hpp"
+#include "EntityController.hpp"
 #include "EntityDerivative.hpp"
 #include "EntityState.hpp"
 #include "Player.hpp"
 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<Worl
 
 void World::Update(int dt) {
        float fdt(dt * 0.001f);
+       for (Entity &entity : entities) {
+               entity.Update(fdt);
+       }
        for (Entity &entity : entities) {
                Update(entity, fdt);
        }
@@ -459,11 +524,7 @@ glm::vec3 World::ControlForce(
        const Entity &entity,
        const EntityState &state
 ) {
-       constexpr float k = 10.0f; // spring constant
-       constexpr float b = 10.0f; // damper constant
-       const glm::vec3 x(-entity.TargetVelocity()); // endpoint displacement from equilibrium, by 1s, in m
-       const glm::vec3 v(state.velocity); // relative velocity between endpoints in m/s
-       return ((-k) * x) - (b * v); // times 1kg/s, in kg*m/s²
+       return entity.ControlForce(state);
 }
 
 namespace {
index 6e20882c44100d8eb435773ad5353537895bd082..6eafe6fb73246d486e72bd634492d31aeef7328f 100644 (file)
@@ -2,6 +2,8 @@
 
 #include "app/IntervalTimer.hpp"
 
+#include <limits>
+
 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<float>::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<float>::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<float>::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<float>::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<float>::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<float>::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<float>::epsilon()
+       );
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "stopped fine timer at non-zero iteration",
                0, timer.Iteration()
        );
 }
index ead7d43e63c76913aa0a2bcd364ae85fa45a7309..b99ce70c35ceea26c0ffc4514a13235d4c97354f 100644 (file)
@@ -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();
 
 };