summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2024-02-29 21:29:56 +0100
committerStanislaw Halik <sthalik@misaki.pl>2024-03-01 06:06:53 +0100
commit486e591169af724107ffaf6f24cd1e81b24529a9 (patch)
tree012ec6c07c474d4695eb5c22b47dd35cfce2767c
parent5afe42d6a81eeb81db08d4bd2107ac4c71f6671e (diff)
time wip
It works now, but some functionality is lost in main/draw.cpp
-rw-r--r--editor/app.cpp1
-rw-r--r--editor/app.hpp9
-rw-r--r--editor/camera.cpp5
-rw-r--r--editor/events.cpp1
-rw-r--r--editor/keys.hpp1
-rw-r--r--editor/update.cpp20
-rw-r--r--floormat/app.hpp6
-rw-r--r--include/mg/Time.h2
-rw-r--r--include/mg/TimeStl.h2
-rw-r--r--main/ctor.cpp2
-rw-r--r--main/draw.cpp43
-rw-r--r--main/main-impl.hpp14
-rw-r--r--main/setup.cpp2
-rw-r--r--src/anim.hpp2
-rw-r--r--src/critter.cpp62
-rw-r--r--src/critter.hpp8
-rw-r--r--src/light.cpp3
-rw-r--r--src/light.hpp2
-rw-r--r--src/object.cpp36
-rw-r--r--src/object.hpp6
-rw-r--r--src/scenery.cpp16
-rw-r--r--src/scenery.hpp6
-rw-r--r--src/timer-fwd.hpp27
-rw-r--r--src/timer.cpp76
-rw-r--r--src/timer.hpp13
-rw-r--r--test/app.cpp1
-rw-r--r--test/app.hpp1
-rw-r--r--test/serializer.cpp7
-rw-r--r--test/time.cpp28
29 files changed, 304 insertions, 98 deletions
diff --git a/editor/app.cpp b/editor/app.cpp
index db395b98..10f4b25d 100644
--- a/editor/app.cpp
+++ b/editor/app.cpp
@@ -5,6 +5,7 @@
#include "editor.hpp"
#include "src/anim-atlas.hpp"
#include "src/critter.hpp"
+#include "src/timer-fwd.hpp"
#include "src/world.hpp"
#include "floormat/main.hpp"
#include "floormat/settings.hpp"
diff --git a/editor/app.hpp b/editor/app.hpp
index ea618bac..32193fe6 100644
--- a/editor/app.hpp
+++ b/editor/app.hpp
@@ -93,14 +93,14 @@ private:
int exec();
- void update(float dt) override;
- void update_world(float dt);
+ void update(Ns dt) override;
+ void update_world(Ns dt);
void update_cursor_tile(const Optional<Vector2i>& pixel);
z_bounds get_z_bounds() override;
void set_cursor();
void maybe_initialize_chunk(const chunk_coords_& pos, chunk& c) override;
void maybe_initialize_chunk_(const chunk_coords_& pos, chunk& c);
- void update_character(float dt);
+ void update_character(Ns dt);
void reset_world();
void reset_world(class world&& w);
@@ -157,6 +157,7 @@ private:
void do_key(key k);
void do_set_mode(editor_mode mode);
void do_rotate(bool backward);
+ static void do_emit_timestamp();
void apply_commands(const key_set& k);
int get_key_modifiers();
void clear_keys(key min_inclusive, key max_exclusive);
@@ -164,7 +165,7 @@ private:
void clear_non_global_keys();
void clear_non_repeated_keys();
- void do_camera(float dt, const key_set& cmds, int mods);
+ void do_camera(Ns dt, const key_set& cmds, int mods);
void reset_camera_offset();
[[nodiscard]] bool tests_handle_key(const key_event& e, bool is_down);
diff --git a/editor/camera.cpp b/editor/camera.cpp
index e103c844..e71be739 100644
--- a/editor/camera.cpp
+++ b/editor/camera.cpp
@@ -7,13 +7,14 @@
#include "src/object.hpp"
#include "src/world.hpp"
#include "src/camera-offset.hpp"
+#include "src/timer.hpp"
#include "compat/enum-bitset.hpp"
#include <bit>
#include <Magnum/Math/Functions.h>
namespace floormat {
-void app::do_camera(float dt, const key_set& cmds, int mods)
+void app::do_camera(Ns dt, const key_set& cmds, int mods)
{
if (cmds[key_camera_reset])
{
@@ -44,7 +45,7 @@ void app::do_camera(float dt, const key_set& cmds, int mods)
auto camera_offset = shader.camera_offset();
const auto max_camera_offset = Vector2d(sz * 10);
- camera_offset -= dir.normalized() * (double)dt * pixels_per_second;
+ camera_offset -= dir.normalized() * (double)Time::to_seconds(dt) * pixels_per_second;
camera_offset = Math::clamp(camera_offset, -max_camera_offset, max_camera_offset);
shader.set_camera_offset(camera_offset, shader.depth_offset());
diff --git a/editor/events.cpp b/editor/events.cpp
index cc1dbb3d..2ba39ff5 100644
--- a/editor/events.cpp
+++ b/editor/events.cpp
@@ -137,6 +137,7 @@ auto app::resolve_keybinding(int k_, int mods_) -> std::tuple<key, int>
case SDLK_d: return key_camera_right;
case SDLK_HOME: return key_camera_reset;
case SDLK_r: return key_rotate_tile;
+ case SDLK_F2: return key_emit_timestamp;
case SDLK_1: return key_mode_none;
case SDLK_2: return key_mode_floor;
case SDLK_3: return key_mode_walls;
diff --git a/editor/keys.hpp b/editor/keys.hpp
index a7f042d6..407d1f63 100644
--- a/editor/keys.hpp
+++ b/editor/keys.hpp
@@ -19,6 +19,7 @@ enum key : unsigned {
key_rotate_tile,
key_mode_none, key_mode_floor, key_mode_walls, key_mode_scenery, key_mode_vobj, key_mode_tests,
key_render_collision_boxes, key_render_clickables, key_render_vobjs, key_render_all_z_levels,
+ key_emit_timestamp,
key_GLOBAL,
key_new_file,
key_quit,
diff --git a/editor/update.cpp b/editor/update.cpp
index 05b12e51..dffc1fdd 100644
--- a/editor/update.cpp
+++ b/editor/update.cpp
@@ -12,6 +12,7 @@
#include "floormat/main.hpp"
#include "src/critter.hpp"
#include "src/tile-iterator.hpp"
+#include "src/timer.hpp"
#include "keys.hpp"
#include "loader/loader.hpp"
#include "compat/enum-bitset.hpp"
@@ -121,6 +122,17 @@ void app::do_rotate(bool backward)
}
}
+void app::do_emit_timestamp()
+{
+ static struct
+ {
+ Time time = Time::now();
+ unsigned ctr = 0;
+ } s;
+ char buf[fm_DATETIME_BUF_SIZE];
+ fm_debug("%s -- MARK -- 0x%08X", format_datetime_to_string(buf), ++s.ctr & 0xffff'ffffU);
+}
+
void app::do_set_mode(editor_mode mode)
{
if (mode != _editor->mode())
@@ -152,6 +164,8 @@ void app::do_key(key k, int mods)
return;
case key_rotate_tile:
return do_rotate(false);
+ case key_emit_timestamp:
+ return do_emit_timestamp();
case key_mode_none:
return do_set_mode(editor_mode::none);
case key_mode_floor:
@@ -193,7 +207,7 @@ void app::apply_commands(const key_set& keys)
do_key(k, key_modifiers[i]);
}
-void app::update_world(float dt)
+void app::update_world(Ns dt)
{
auto& world = M->world();
world.increment_frame_no();
@@ -214,7 +228,7 @@ void app::update_world(float dt)
}
}
-void app::update_character([[maybe_unused]] float dt)
+void app::update_character([[maybe_unused]] Ns dt)
{
auto& keys = *keys_;
if (_character_id)
@@ -244,7 +258,7 @@ auto app::get_z_bounds() -> z_bounds
return { chunk_z_min, chunk_z_max, _z_level, !_render_all_z_levels };
}
-void app::update(float dt)
+void app::update(Ns dt)
{
//M->world().collect();
M->world().collect(true);
diff --git a/floormat/app.hpp b/floormat/app.hpp
index c07c48e1..01f94b7f 100644
--- a/floormat/app.hpp
+++ b/floormat/app.hpp
@@ -1,9 +1,11 @@
#pragma once
-namespace Magnum::Math { template<typename T> class Vector2; }
+namespace Magnum::Math { template<typename T> class Vector2; template<class T> class Nanoseconds; }
namespace floormat {
+using Ns = Math::Nanoseconds<int64_t>;
+
struct mouse_move_event;
struct mouse_button_event;
struct mouse_scroll_event;
@@ -28,7 +30,7 @@ struct floormat_app
[[deprecated]] floormat_app(floormat_app&&) = default;
[[deprecated]] floormat_app& operator=(floormat_app&&) = default;
- virtual void update(float dt) = 0;
+ virtual void update(Ns t) = 0;
virtual void maybe_initialize_chunk(const chunk_coords_& pos, chunk& c) = 0;
virtual void draw() = 0;
virtual z_bounds get_z_bounds() = 0;
diff --git a/include/mg/Time.h b/include/mg/Time.h
new file mode 100644
index 00000000..cd7f8228
--- /dev/null
+++ b/include/mg/Time.h
@@ -0,0 +1,2 @@
+#pragma once
+#include <Magnum/Math/Time.h>
diff --git a/include/mg/TimeStl.h b/include/mg/TimeStl.h
new file mode 100644
index 00000000..31f958ff
--- /dev/null
+++ b/include/mg/TimeStl.h
@@ -0,0 +1,2 @@
+#pragma once
+#include <Magnum/Math/TimeStl.h>
diff --git a/main/ctor.cpp b/main/ctor.cpp
index da38fb18..19a4546a 100644
--- a/main/ctor.cpp
+++ b/main/ctor.cpp
@@ -23,7 +23,7 @@ main_impl::main_impl(floormat_app& app, fm_settings&& se, int& argc, char** argv
(void)setSwapInterval(0);
set_fp_mask();
arrayReserve(_clickable_scenery, 128);
- timeline.start();
+ tm.timeline = Time::now();
}
class world& main_impl::reset_world(class world&& w) noexcept
diff --git a/main/draw.cpp b/main/draw.cpp
index b0f97fab..79e9ccfe 100644
--- a/main/draw.cpp
+++ b/main/draw.cpp
@@ -6,6 +6,7 @@
#include "main/clickable.hpp"
#include "src/light.hpp"
#include "src/log.hpp"
+#include "src/timer.hpp"
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/ArrayView.h>
#include <Magnum/GL/DefaultFramebuffer.h>
@@ -71,8 +72,7 @@ void main_impl::recalc_viewport(Vector2i fb_size, Vector2i win_size) noexcept
// -- user--
app.on_viewport_event(fb_size);
-
- fps_sample_timeline.start();
+ tm.timeline = Time::now();
}
global_coords main_impl::pixel_to_tile(Vector2d position) const noexcept
@@ -216,34 +216,33 @@ bool floormat_main::check_chunk_visible(const Vector2d& offset, const Vector2i&
return X + W > 0 && X < size[x] && Y + H > 0 && Y < size[y];
}
+#if 0
#ifndef FM_NO_DEBUG
static size_t good_frames, bad_frames; // NOLINT
#endif
+#endif
-void main_impl::do_update()
+void main_impl::do_update() // todo! move to separate file
{
-
- // todo switch to using microseconds
- float dt = timeline.previousFrameDuration();
- if (dt > 0)
+ constexpr auto eps = 1e-5f;
+ const auto dt = tm.timeline.update();
+ if (auto secs = Time::to_seconds(dt); secs > eps)
{
- if (fps_sample_timeline.currentFrameDuration() > 1.f/15)
- {
- fps_sample_timeline.nextFrame();
- constexpr float RC = 10;
- constexpr float alpha = 1/(1 + RC);
- _frame_time = _frame_time*(1-alpha) + alpha*dt;
- }
+#if 1
+ constexpr float RC = 60;
+ constexpr auto alpha = 1 / (1 + RC);
+ _frame_time = _frame_time * (1 - alpha) + alpha * secs;
+#else
+ _frame_time = secs;
+#endif
static size_t ctr = 0;
- if (dt >= 1.f/55 && !dt_expected.do_sleep)
- fm_debug("%zu frame took %.1f milliseconds", ctr++, dt*1e3f);
+ if (secs > 35e-3f /* && !dt_expected.do_sleep */)
+ fm_debug("%zu frame took %.2f milliseconds", ctr++, (double)secs*1e3);
}
else
- {
swapBuffers();
- timeline.nextFrame();
- }
+#if 0
#ifndef FM_NO_DEBUG
if (dt_expected.value == 0 || !is_log_verbose()) [[likely]]
void();
@@ -286,6 +285,7 @@ void main_impl::do_update()
t = {};
}
}
+#endif
app.update(dt);
}
@@ -295,7 +295,7 @@ void main_impl::bind() noexcept
framebuffer.fb.bind();
}
-void main_impl::drawEvent()
+void main_impl::drawEvent() // todo! move to separate file (with `do_update')
{
_shader.set_tint({1, 1, 1, 1});
@@ -318,8 +318,8 @@ void main_impl::drawEvent()
swapBuffers();
redraw();
- timeline.nextFrame();
+#if 0
if (dt_expected.do_sleep && (false))
{
constexpr float ε = 1e-3f;
@@ -338,6 +338,7 @@ void main_impl::drawEvent()
dt_expected.jitter = 0;
dt_expected.value = 0;
}
+#endif
}
ArrayView<const clickable> main_impl::clickable_scenery() const noexcept
diff --git a/main/main-impl.hpp b/main/main-impl.hpp
index 0f1bae35..01be6187 100644
--- a/main/main-impl.hpp
+++ b/main/main-impl.hpp
@@ -2,6 +2,8 @@
#include "floormat/main.hpp"
#include "floormat/settings.hpp"
#include "src/world.hpp"
+#include "src/timer-fwd.hpp"
+#include "src/timer.hpp"
#include "draw/ground.hpp"
#include "draw/wall.hpp"
#include "draw/anim.hpp"
@@ -11,7 +13,6 @@
#include "main/clickable.hpp"
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/String.h>
-#include <Magnum/Timeline.h>
#include <Magnum/Math/Range.h>
#include <Magnum/GL/DebugOutput.h>
#include <Magnum/Platform/Sdl2Application.h>
@@ -102,6 +103,12 @@ struct main_impl final : Platform::Sdl2Application, floormat_main
class astar& astar() override;
private:
+ struct {
+ Ns last_frame_duration{0};
+ Time frame_start;
+ Time timeline; // todo rename
+ } tm;
+
struct texture_unit_cache _tuc;
fm_settings s;
[[maybe_unused]] char _dummy = (register_debug_callback(), '\0');
@@ -110,7 +117,6 @@ private:
struct lightmap_shader _lightmap_shader{_tuc};
Array<clickable> _clickable_scenery;
class world _world{};
- Magnum::Timeline timeline;
uint32_t _mouse_cursor = (uint32_t)-1;
ground_mesh _ground_mesh;
wall_mesh _wall_mesh;
@@ -121,14 +127,14 @@ private:
safe_ptr<path_search> _search;
safe_ptr<class astar> _astar;
+#if 0
struct {
float value = 0;
float jitter = 0;
bool do_sleep = false;
bool has_focus = true;
} dt_expected;
-
- Timeline fps_sample_timeline;
+#endif
void recalc_viewport(Vector2i fb_size, Vector2i win_size) noexcept;
void draw_world() noexcept;
diff --git a/main/setup.cpp b/main/setup.cpp
index a16165ba..7014cc0d 100644
--- a/main/setup.cpp
+++ b/main/setup.cpp
@@ -59,6 +59,7 @@ void main_impl::update_window_state()
{
const auto flags = (SDL_WindowFlags)SDL_GetWindowFlags(window());
+#if 0
dt_expected.do_sleep = true;
dt_expected.jitter = 0;
dt_expected.has_focus = true;
@@ -83,6 +84,7 @@ void main_impl::update_window_state()
dt_expected.do_sleep = false;
dt_expected.value = 1e-1f;
}
+#endif
}
auto main_impl::meshes() noexcept -> struct meshes
diff --git a/src/anim.hpp b/src/anim.hpp
index 3c25f51c..e22d1e6b 100644
--- a/src/anim.hpp
+++ b/src/anim.hpp
@@ -57,7 +57,7 @@ struct anim_def final
Array<anim_group> groups{};
Vector2ui pixel_size{};
anim_scale scale = anim_scale::ratio{1};
- size_t nframes = 0, fps = 0, action_frame = 0, action_frame2 = 0;
+ uint32_t nframes = 0, fps = 0, action_frame = 0, action_frame2 = 0;
};
} // namespace floormat
diff --git a/src/critter.cpp b/src/critter.cpp
index 1a4f77a1..fb3d7e52 100644
--- a/src/critter.cpp
+++ b/src/critter.cpp
@@ -4,13 +4,13 @@
#include "loader/loader.hpp"
#include "src/world.hpp"
#include "src/object.hpp"
+#include "src/timer.hpp"
#include "shaders/shader.hpp"
#include "compat/exception.hpp"
#include <cmath>
#include <utility>
#include <algorithm>
#include <mg/Functions.h>
-#include <mg/Timeline.h>
namespace floormat {
@@ -110,17 +110,6 @@ bool critter_proto::operator==(const object_proto& e0) const
return name == s0.name && playable == s0.playable;
}
-int critter::allocate_frame_time(float dt)
-{
- auto d = (double)delta / 65535. + (double)dt;
- d = std::min(1., d);
- auto ret = (int)(d / frame_time);
- d -= ret;
- d = Math::clamp(d, 0., 1.);
- delta = (uint16_t)(d * 65535);
- return ret;
-}
-
void critter::set_keys(bool L, bool R, bool U, bool D)
{
b_L = L;
@@ -140,10 +129,30 @@ Vector2 critter::ordinal_offset(Vector2b offset) const
return Vector2(offset);
}
-void critter::update(size_t i, float dt)
+void critter::update(size_t i, Ns dt)
{
if (playable)
{
+#if 0
+ static auto TL = Time::now();
+ static Ns TIME{0};
+ static unsigned FRAMES;
+
+ if (++FRAMES == 0)
+ TL = Time::now();
+ else
+ TIME += dt;
+
+ if (++FRAMES > 240)
+ {
+ auto t = TL.update();
+ Debug{} << "player time" << Time::to_milliseconds(TIME) << Time::to_milliseconds(t);
+ Debug{} << Time::to_milliseconds(TIME) / Time::to_milliseconds(t);
+ TIME = Ns{0};
+ FRAMES = 0;
+ }
+#endif
+
const auto new_r = arrows_to_dir(b_L, b_R, b_U, b_D);
if (new_r == rotation_COUNT)
{
@@ -157,21 +166,21 @@ void critter::update(size_t i, float dt)
update_nonplayable(i, dt);
}
-void critter::update_nonplayable(size_t i, float dt)
+void critter::update_nonplayable(size_t i, Ns dt)
{
(void)i; (void)dt; (void)playable;
}
-void critter::update_movement(size_t i, float dt, rotation new_r)
+void critter::update_movement(size_t i, Ns dt, rotation new_r)
{
fm_assert(new_r < rotation_COUNT);
fm_assert(is_dynamic());
- int nframes = allocate_frame_time(dt * speed);
+ auto nframes = allocate_frame_time(dt * speed);
if (nframes == 0)
{
static unsigned foo;
- Debug{} << ++foo << "stopped";
+ //Debug{} << ++foo << "stopped";
return;
}
@@ -184,24 +193,7 @@ void critter::update_movement(size_t i, float dt, rotation new_r)
c->ensure_passability();
- static Timeline TL{};
- static double TIME;
- static unsigned FRAMES;
-
- if (++FRAMES == 0)
- TL.start();
- else
- TIME += (double)dt;
-
- if (++FRAMES > 30)
- {
- Debug{} << "player time" << TIME;
- TL.stop();
- TIME = 0;
- FRAMES = 0;
- }
-
- for (int k = 0; k < nframes; k++)
+ for (auto k = 0u; k < nframes; k++)
{
for (unsigned j = 0; j < nvecs; j++)
{
diff --git a/src/critter.hpp b/src/critter.hpp
index a6e9e4cc..01159710 100644
--- a/src/critter.hpp
+++ b/src/critter.hpp
@@ -26,9 +26,9 @@ struct critter final : object
object_type type() const noexcept override;
explicit operator critter_proto() const;
- void update(size_t i, float dt) override;
- void update_movement(size_t i, float dt, rotation r);
- void update_nonplayable(size_t i, float dt);
+ void update(size_t i, Ns dt) override;
+ void update_movement(size_t i, Ns dt, rotation r);
+ void update_nonplayable(size_t i, Ns dt);
void set_keys(bool L, bool R, bool U, bool D);
Vector2 ordinal_offset(Vector2b offset) const override;
float depth_offset() const override;
@@ -40,8 +40,6 @@ struct critter final : object
bool playable : 1 = false;
private:
- int allocate_frame_time(float dt);
-
friend class world;
critter(object_id id, class chunk& c, const critter_proto& proto);
};
diff --git a/src/light.cpp b/src/light.cpp
index 3de02885..ead434a2 100644
--- a/src/light.cpp
+++ b/src/light.cpp
@@ -1,4 +1,5 @@
#include "light.hpp"
+#include "timer.hpp"
#include "tile-constants.hpp"
#include "shaders/shader.hpp"
#include "loader/loader.hpp"
@@ -52,7 +53,7 @@ light::operator light_proto() const
}
object_type light::type() const noexcept { return object_type::light; }
-void light::update(size_t, float) {}
+void light::update(size_t, Ns) {}
bool light::is_dynamic() const { return true; }
bool light::is_virtual() const { return true; }
diff --git a/src/light.hpp b/src/light.hpp
index 92da2a19..91709b1b 100644
--- a/src/light.hpp
+++ b/src/light.hpp
@@ -33,7 +33,7 @@ struct light final : object
Vector2 ordinal_offset(Vector2b offset) const override;
float depth_offset() const override;
object_type type() const noexcept override;
- void update(size_t i, float dt) override;
+ void update(size_t i, Ns dt) override;
bool is_dynamic() const override;
bool is_virtual() const override;
diff --git a/src/object.cpp b/src/object.cpp
index 550f8a6b..9aca4705 100644
--- a/src/object.cpp
+++ b/src/object.cpp
@@ -4,8 +4,9 @@
#include "rotation.inl"
#include "anim-atlas.hpp"
#include "src/RTree-search.hpp"
+#include "src/timer.hpp"
+#include "compat/debug.hpp"
#include "compat/exception.hpp"
-#include "shaders/shader.hpp"
#include <cmath>
#include <algorithm>
#include <Corrade/Containers/GrowableArray.h>
@@ -268,6 +269,39 @@ void object::move_to(Magnum::Vector2i delta)
move_to(i, delta, r);
}
+uint32_t object::allocate_frame_time(Ns dt, uint16_t& accum, uint32_t hz)
+{
+ fm_assert(hz > 0);
+ fm_assert(dt >= Ns{0});
+ constexpr auto ns_in_sec = Ns::Type{Ns(1e9)};
+ //const auto count = Ns::Type{ns_in_sec / hz} + accum};
+ const auto nsecs = Ns::Type{dt} + accum * ns_in_sec / Ns::Type{65535};
+ const auto frame_duration = ns_in_sec / hz;
+ const auto frames = (uint32_t)(nsecs / frame_duration);
+ const auto rem = nsecs % frame_duration;
+ const auto new_accum_ = rem * Ns::Type{65535} / ns_in_sec;
+ const auto new_accum = (uint16_t)Math::clamp(new_accum_, Ns::Type{0}, Ns::Type{65535});
+ [[maybe_unused]] const auto old_accum = accum;
+ accum = new_accum;
+#if 0
+ DBG_nospace << "alloc-frame-time: "
+ << "dt:" << fraction(Time::to_milliseconds(dt)) << "ms"
+ << ", secs:" << fraction(Time::to_milliseconds(Ns{nsecs}), 1) << " ms"
+ << ", frames:" << frames
+ << ", acc:" << new_accum_
+ << ", rem:" << rem;
+#endif
+ return frames;
+}
+
+uint32_t object::allocate_frame_time(Ns dt)
+{
+ fm_assert(atlas);
+ auto hz = atlas->info().fps;
+ fm_assert(hz > 0);
+ return allocate_frame_time(dt, delta, hz);
+}
+
void object::set_bbox_(Vector2b offset_, Vector2b bb_offset_, Vector2ub bb_size_, pass_mode pass_)
{
const_cast<Vector2b&>(offset) = offset_;
diff --git a/src/object.hpp b/src/object.hpp
index 2d33885d..e462c8db 100644
--- a/src/object.hpp
+++ b/src/object.hpp
@@ -6,6 +6,7 @@
#include "src/object-type.hpp"
#include "src/object-id.hpp"
#include "src/point.hpp"
+#include "src/timer-fwd.hpp"
#include <memory>
namespace floormat {
@@ -68,7 +69,7 @@ struct object
virtual object_type type() const noexcept = 0;
virtual bool can_activate(size_t i) const;
virtual bool activate(size_t i);
- virtual void update(size_t i, float dt) = 0;
+ virtual void update(size_t i, Ns dt) = 0;
virtual void rotate(size_t i, rotation r);
virtual bool can_rotate(global_coords coord, rotation new_r, rotation old_r, Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size);
virtual bool can_move_to(Vector2i delta, global_coords coord, Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_aize);
@@ -84,6 +85,9 @@ struct object
void move_to(size_t& i, Vector2i delta, rotation new_r);
void move_to(Vector2i delta);
+ static uint32_t allocate_frame_time(Ns dt, uint16_t& accum, uint32_t hz);
+ uint32_t allocate_frame_time(Ns dt);
+
protected:
object(object_id id, class chunk& c, const object_proto& proto);
void set_bbox_(Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size, pass_mode pass);
diff --git a/src/scenery.cpp b/src/scenery.cpp
index ed5b643f..b5fd3b59 100644
--- a/src/scenery.cpp
+++ b/src/scenery.cpp
@@ -7,6 +7,7 @@
#include "shaders/shader.hpp"
#include "src/rotation.inl"
#include "compat/exception.hpp"
+#include "src/timer.hpp"
#include <algorithm>
namespace floormat {
@@ -50,7 +51,7 @@ scenery_proto::operator bool() const { return atlas != nullptr; }
bool generic_scenery_proto::operator==(const generic_scenery_proto& p) const = default;
enum scenery_type generic_scenery_proto::scenery_type() const { return scenery_type::generic; }
-void generic_scenery::update(scenery&, size_t, float) {}
+void generic_scenery::update(scenery&, size_t, Ns) {}
Vector2 generic_scenery::ordinal_offset(const scenery&, Vector2b offset) const { return Vector2(offset); }
bool generic_scenery::can_activate(const scenery&, size_t) const { return interactive; }
bool generic_scenery::activate(floormat::scenery&, size_t) { return false; }
@@ -80,23 +81,16 @@ door_scenery::door_scenery(object_id, class chunk&, const door_scenery_proto& p)
closing{p.closing}, active{p.active}, interactive{p.interactive}
{}
-void door_scenery::update(scenery& s, size_t, float dt)
+void door_scenery::update(scenery& s, size_t, Ns dt)
{
if (!s.atlas || !active)
return;
fm_assert(s.atlas);
auto& anim = *s.atlas;
- const auto hz = uint8_t(s.atlas->info().fps);
const auto nframes = (int)anim.info().nframes;
fm_debug_assert(anim.info().fps > 0 && anim.info().fps <= 0xff);
-
- auto delta_ = int(s.delta) + int(65535u * dt);
- delta_ = std::min(65535, delta_);
- const auto frame_time = int(1.f/hz * 65535);
- const auto n = (uint8_t)std::clamp(delta_ / frame_time, 0, 255);
- s.delta = (uint16_t)std::clamp(delta_ - frame_time*n, 0, 65535);
- fm_debug_assert(s.delta >= 0);
+ const auto n = (int)s.allocate_frame_time(dt);
if (n == 0)
return;
const int8_t dir = closing ? 1 : -1;
@@ -154,7 +148,7 @@ bool scenery::can_activate(size_t i) const
);
}
-void scenery::update(size_t i, float dt)
+void scenery::update(size_t i, Ns dt)
{
return std::visit(
[&]<typename T>(T& sc) { sc.update(*this, i, dt); },
diff --git a/src/scenery.hpp b/src/scenery.hpp
index 8b003e73..25d40889 100644
--- a/src/scenery.hpp
+++ b/src/scenery.hpp
@@ -63,7 +63,7 @@ struct generic_scenery
unsigned char active : 1 = false;
unsigned char interactive : 1 = false;
- void update(scenery& sc, size_t i, float dt);
+ void update(scenery& sc, size_t i, Ns dt);
Vector2 ordinal_offset(const scenery& sc, Vector2b offset) const;
bool can_activate(const scenery& sc, size_t i) const;
bool activate(scenery& sc, size_t i);
@@ -80,7 +80,7 @@ struct door_scenery
unsigned char active : 1 = false;
unsigned char interactive : 1 = false;
- void update(scenery& sc, size_t i, float dt);
+ void update(scenery& sc, size_t i, Ns dt);
Vector2 ordinal_offset(const scenery& sc, Vector2b offset) const;
bool can_activate(const scenery& sc, size_t i) const;
bool activate(scenery& sc, size_t i);
@@ -97,7 +97,7 @@ struct scenery final : object
{
scenery_variants subtype;
- void update(size_t i, float dt) override;
+ void update(size_t i, Ns dt) override;
Vector2 ordinal_offset(Vector2b offset) const override;
float depth_offset() const override;
bool can_activate(size_t i) const override;
diff --git a/src/timer-fwd.hpp b/src/timer-fwd.hpp
new file mode 100644
index 00000000..5e11ce7b
--- /dev/null
+++ b/src/timer-fwd.hpp
@@ -0,0 +1,27 @@
+#pragma once
+#include <compare>
+
+namespace Magnum::Math { template<class T> class Nanoseconds; }
+
+namespace floormat {
+
+using Ns = Math::Nanoseconds<int64_t>;
+
+struct Time final
+{
+ static Time now() noexcept;
+ bool operator==(const Time&) const noexcept;
+ std::strong_ordering operator<=>(const Time&) const noexcept;
+ friend Ns operator-(const Time& a, const Time& b) noexcept;
+ [[nodiscard]] Ns update(const Time& ts = now()) & noexcept;
+
+ static float to_seconds(const Ns& ts) noexcept;
+ static float to_milliseconds(const Ns& ts) noexcept;
+
+ uint64_t stamp = init();
+
+private:
+ static uint64_t init() noexcept;
+};
+
+} // namespace floormat
diff --git a/src/timer.cpp b/src/timer.cpp
new file mode 100644
index 00000000..0b87ac06
--- /dev/null
+++ b/src/timer.cpp
@@ -0,0 +1,76 @@
+#include "timer.hpp"
+#include "compat/assert.hpp"
+#include <ctime>
+#include <cstdio>
+#include <chrono>
+#include <mg/Time.h>
+
+namespace floormat {
+
+using std::chrono::duration_cast;
+using std::chrono::duration;
+
+using Clock = std::chrono::high_resolution_clock;
+using SystemClock = std::chrono::system_clock;
+using Nsecs = duration<uint64_t, std::nano>;
+using Millis = duration<unsigned, std::milli>;
+
+namespace {
+
+template<typename T> struct array_size_;
+template<typename T, size_t N> struct array_size_<T(&)[N]> : std::integral_constant<size_t, N> {};
+template<typename T, size_t N> struct array_size_<std::array<T, N>> : std::integral_constant<size_t, N> {};
+template<typename T> constexpr inline auto array_size = array_size_<T>::value;
+
+uint64_t get_time() noexcept { return duration_cast<Nsecs>(Clock::now().time_since_epoch()).count(); }
+
+const uint64_t Epoch = get_time();
+
+} // namespace
+
+Time Time::now() noexcept
+{
+ auto val = get_time();
+ auto ret = val - Epoch;
+ return {ret};
+}
+
+Ns operator-(const Time& a, const Time& b) noexcept
+{
+ fm_assert(a.stamp >= b.stamp);
+ auto ret = a.stamp - b.stamp;
+ fm_assert(ret < uint64_t{1} << 63);
+ return Ns{ int64_t(ret) };
+}
+
+Ns Time::update(const Time& ts) & noexcept
+{
+ auto ret = ts - *this;
+ stamp = ts.stamp;
+ return ret;
+}
+
+uint64_t Time::init() noexcept { return get_time(); }
+bool Time::operator==(const Time&) const noexcept = default;
+std::strong_ordering Time::operator<=>(const Time&) const noexcept = default;
+
+float Time::to_seconds(const Ns& ts) noexcept { return float(Ns::Type{ts} * 1e-9L); }
+float Time::to_milliseconds(const Ns& ts) noexcept { return float(Ns::Type{ts} * 1e-6L); }
+
+const char* format_datetime_to_string(char (&buf)[fm_DATETIME_BUF_SIZE])
+{
+ constexpr const char* fmt = "%a, %d %b %Y %H:%M:%S.";
+ constexpr size_t fmtsize = std::size("Thu 01 Mon 197000 00:00:00.");
+ static_assert(array_size<decltype(buf)> - fmtsize == 4);
+ const auto t = SystemClock::now();
+ const auto ms = duration_cast<Millis>(t.time_since_epoch()) % 1000;
+ const auto time = SystemClock::to_time_t(t);
+ const auto* tm = std::localtime(&time);
+ auto len = std::strftime(buf, std::size(buf), fmt, tm);
+ fm_assert(len > 0 && len <= fmtsize);
+ auto len2 = std::sprintf(buf + len, "%03u", unsigned{ms.count()});
+ fm_assert(len2 > 0 && len + (size_t)len2 < std::size(buf));
+ return buf;
+}
+
+} // namespace floormat
diff --git a/src/timer.hpp b/src/timer.hpp
new file mode 100644
index 00000000..24554363
--- /dev/null
+++ b/src/timer.hpp
@@ -0,0 +1,13 @@
+#pragma once
+#include "timer-fwd.hpp"
+#include <mg/Time.h>
+
+namespace floormat {
+
+long double operator/(Ns a, Ns b) noexcept;
+using namespace Magnum::Math::Literals::TimeLiterals;
+
+constexpr inline size_t fm_DATETIME_BUF_SIZE = 32;
+const char* format_datetime_to_string(char(&buf)[fm_DATETIME_BUF_SIZE]);
+
+} // namespace floormat
diff --git a/test/app.cpp b/test/app.cpp
index af5cc1f9..4677de39 100644
--- a/test/app.cpp
+++ b/test/app.cpp
@@ -53,6 +53,7 @@ int test_app::exec()
FM_TEST(test_hash),
FM_TEST(test_raycast),
FM_TEST(test_json),
+ FM_TEST(test_time),
FM_TEST(test_loader),
FM_TEST(test_region),
FM_TEST(test_wall_atlas),
diff --git a/test/app.hpp b/test/app.hpp
index e4b77614..d100ab13 100644
--- a/test/app.hpp
+++ b/test/app.hpp
@@ -42,6 +42,7 @@ struct test_app final : private FM_APPLICATION
static void test_saves();
static void test_serializer1();
static void test_tile_iter();
+ static void test_time();
static void test_wall_atlas();
static void test_wall_atlas2();
diff --git a/test/serializer.cpp b/test/serializer.cpp
index 44746449..cc9405ef 100644
--- a/test/serializer.cpp
+++ b/test/serializer.cpp
@@ -7,6 +7,7 @@
#include "src/ground-atlas.hpp"
#include "src/anim-atlas.hpp"
#include "src/tile-iterator.hpp"
+#include "src/timer.hpp"
#include <Corrade/Utility/Path.h>
namespace floormat {
@@ -52,15 +53,17 @@ chunk& test_app::make_test_chunk(world& w, chunk_coords_ ch)
auto& e = *w.make_object<scenery>(w.make_id(), {ch, {K+3, K+1}}, door);
const auto index = e.index();
const auto end = e.atlas->info().nframes-1;
+ constexpr auto second = Ns(1e9);
+ constexpr auto dt = second / 60;
fm_assert(e.frame == end);
{ auto& x = std::get<door_scenery>(e.subtype);
fm_assert(!x.active);
e.activate(e.index());
fm_assert(x.active);
- e.update(index, 1.f/60);
+ e.update(index, dt);
fm_assert(e.frame != end);
for (int i = 0; i < 60*3; i++)
- e.update(index, 1.f/60);
+ e.update(index, dt);
fm_assert(e.frame == 0);
fm_assert(!x.active);
}
diff --git a/test/time.cpp b/test/time.cpp
new file mode 100644
index 00000000..dac8a2e4
--- /dev/null
+++ b/test/time.cpp
@@ -0,0 +1,28 @@
+#include "app.hpp"
+#include "src/timer.hpp"
+#include <cstdio>
+#include <thread>
+#include <mg/TimeStl.h>
+
+namespace floormat {
+
+using namespace std::chrono_literals;
+
+void test_app::test_time()
+{
+#if 0
+ constexpr auto to_ms = [](Ns dt) { return Time::to_seconds(dt); };
+ Timer::maybe_start();
+
+ Debug{} << "";
+ auto t1 = Time::now();
+ std::this_thread::sleep_for(8ms);
+ auto t2 = Time::now();
+ Debug{} << "- foo1" << to_ms(t2 - t1);
+ Debug{} << "- foo2" << to_ms(Time::now() - t1);
+ Debug{} << "- foo3" << to_ms(Timer::since_epoch());
+ std::fputc('\t', stdout);
+#endif
+}
+
+} // namespace floormat