From de5e7bd8a825b51806374b873b9e1c2402c2b3b9 Mon Sep 17 00:00:00 2001 From: SirLynix Date: Sun, 23 Apr 2023 19:45:33 +0200 Subject: [PATCH] Core/ApplicationBase: Add support for updaters with intervals --- examples/DeferredShading/main.cpp | 2 +- examples/DopplerEffect/main.cpp | 2 +- examples/PhysicallyBasedRendering/main.cpp | 2 +- examples/Physics2DDemo/main.cpp | 2 +- examples/PhysicsDemo/main.cpp | 2 +- examples/PhysicsPlayground/main.cpp | 4 +- examples/PlayMusic/main.cpp | 2 +- examples/Showcase/main.cpp | 4 +- include/Nazara/Core/ApplicationBase.hpp | 20 ++++- include/Nazara/Core/ApplicationBase.inl | 67 +++++++++++----- include/Nazara/Core/ApplicationUpdater.hpp | 21 ++++- include/Nazara/Core/ApplicationUpdater.inl | 54 ++++++++++++- include/Nazara/Core/Modules.inl | 1 + src/Nazara/Core/ApplicationBase.cpp | 29 ++++++- tests/GraphicsTest/main.cpp | 2 +- tests/RenderTest/main.cpp | 2 +- .../UnitTests/Engine/Core/ApplicationTest.cpp | 76 +++++++++++++++++++ 17 files changed, 253 insertions(+), 39 deletions(-) create mode 100644 tests/UnitTests/Engine/Core/ApplicationTest.cpp diff --git a/examples/DeferredShading/main.cpp b/examples/DeferredShading/main.cpp index 522c9186b..07a86cff3 100644 --- a/examples/DeferredShading/main.cpp +++ b/examples/DeferredShading/main.cpp @@ -1140,7 +1140,7 @@ int main() } }); - app.AddUpdater([&](Nz::Time deltaTime) + app.AddUpdaterFunc([&](Nz::Time deltaTime) { if (lightAnimation) elapsedTime += deltaTime; diff --git a/examples/DopplerEffect/main.cpp b/examples/DopplerEffect/main.cpp index 0a2a63dda..cceaf07f6 100644 --- a/examples/DopplerEffect/main.cpp +++ b/examples/DopplerEffect/main.cpp @@ -55,7 +55,7 @@ int main() sound.Play(); Nz::MillisecondClock clock; - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { std::this_thread::sleep_for(std::chrono::milliseconds(1000 / 30)); diff --git a/examples/PhysicallyBasedRendering/main.cpp b/examples/PhysicallyBasedRendering/main.cpp index 954f0f1ef..2667f6025 100644 --- a/examples/PhysicallyBasedRendering/main.cpp +++ b/examples/PhysicallyBasedRendering/main.cpp @@ -141,7 +141,7 @@ int main() } }); - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { if (std::optional deltaTime = updateClock.RestartIfOver(Nz::Time::TickDuration(60))) { diff --git a/examples/Physics2DDemo/main.cpp b/examples/Physics2DDemo/main.cpp index d52fe274c..8ed0227ac 100644 --- a/examples/Physics2DDemo/main.cpp +++ b/examples/Physics2DDemo/main.cpp @@ -118,7 +118,7 @@ int main() Nz::PidController headingController(0.5f, 0.f, 0.05f); Nz::PidController upController(1.f, 0.f, 0.1f); - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { fps++; diff --git a/examples/PhysicsDemo/main.cpp b/examples/PhysicsDemo/main.cpp index 4450cca87..76ca17a4a 100644 --- a/examples/PhysicsDemo/main.cpp +++ b/examples/PhysicsDemo/main.cpp @@ -256,7 +256,7 @@ int main() headingEntity.get().SetRotation(camQuat); }); - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { if (std::optional deltaTime = updateClock.RestartIfOver(Nz::Time::TickDuration(60))) { diff --git a/examples/PhysicsPlayground/main.cpp b/examples/PhysicsPlayground/main.cpp index 090a5c103..33b961609 100644 --- a/examples/PhysicsPlayground/main.cpp +++ b/examples/PhysicsPlayground/main.cpp @@ -509,7 +509,7 @@ int main() }); Nz::DegreeAnglef rotation = 0.f; - app.AddUpdater([&](Nz::Time elapsedTime) + app.AddUpdaterFunc([&](Nz::Time elapsedTime) { rotation += elapsedTime.AsSeconds() * 45.f; //physSystem.GetPhysWorld().SetGravity(Nz::Quaternionf(Nz::EulerAnglesf(0.f, rotation, 0.f)) * Nz::Vector3f::Forward() * 10.f); @@ -517,7 +517,7 @@ int main() Nz::MillisecondClock fpsClock; unsigned int fps = 0; - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { fps++; if (fpsClock.RestartIfOver(Nz::Time::Second())) diff --git a/examples/PlayMusic/main.cpp b/examples/PlayMusic/main.cpp index 4cef04759..5bee8ab77 100644 --- a/examples/PlayMusic/main.cpp +++ b/examples/PlayMusic/main.cpp @@ -35,7 +35,7 @@ int main() std::cout << "Playing sound..." << std::endl; - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { if (!music.IsPlaying()) app.Quit(); diff --git a/examples/Showcase/main.cpp b/examples/Showcase/main.cpp index 5217d0be2..a26931eed 100644 --- a/examples/Showcase/main.cpp +++ b/examples/Showcase/main.cpp @@ -68,7 +68,7 @@ int main() character.emplace(physSytem.GetPhysWorld(), playerCollider, Nz::Vector3f::Up() * 5.f); - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { auto [position, rotation] = character->GetPositionAndRotation(); @@ -417,7 +417,7 @@ int main() playerRotNode.SetRotation(camAngles); }); - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { if (std::optional deltaTime = updateClock.RestartIfOver(Nz::Time::TickDuration(60))) { diff --git a/include/Nazara/Core/ApplicationBase.hpp b/include/Nazara/Core/ApplicationBase.hpp index 290224b7c..9f43446ff 100644 --- a/include/Nazara/Core/ApplicationBase.hpp +++ b/include/Nazara/Core/ApplicationBase.hpp @@ -19,6 +19,9 @@ namespace Nz class NAZARA_CORE_API ApplicationBase { public: + struct FixedInterval { Time interval; }; + struct Interval { Time interval; }; + inline ApplicationBase(); inline ApplicationBase(int argc, char** argv); ApplicationBase(int argc, const Pointer* argv); @@ -26,7 +29,10 @@ namespace Nz ApplicationBase(ApplicationBase&&) = delete; ~ApplicationBase() = default; - template void AddUpdater(F&& functor); + inline void AddUpdater(std::unique_ptr&& functor); + template void AddUpdaterFunc(F&& functor); + template void AddUpdaterFunc(FixedInterval fixedInterval, F&& functor); + template void AddUpdaterFunc(Interval interval, F&& functor); inline void ClearComponents(); @@ -46,10 +52,20 @@ namespace Nz template T& AddComponent(Args&&... args); private: + template void AddUpdaterFunc(Time interval, F&& functor); + + struct Updater + { + std::unique_ptr updater; + Time lastUpdate; + Time nextUpdate; + }; + std::atomic_bool m_running; std::vector> m_components; - std::vector> m_updaters; + std::vector m_updaters; HighPrecisionClock m_clock; + Time m_currentTime; }; } diff --git a/include/Nazara/Core/ApplicationBase.inl b/include/Nazara/Core/ApplicationBase.inl index 2d022cdb9..36ba093e9 100644 --- a/include/Nazara/Core/ApplicationBase.inl +++ b/include/Nazara/Core/ApplicationBase.inl @@ -18,28 +18,31 @@ namespace Nz { } - template - T& ApplicationBase::AddComponent(Args&&... args) + inline void ApplicationBase::AddUpdater(std::unique_ptr&& functor) { - std::size_t componentIndex = ApplicationComponentRegistry::GetComponentId(); - - std::unique_ptr component = std::make_unique(*this, std::forward(args)...); - T& componentRef = *component; - - if (componentIndex >= m_components.size()) - m_components.resize(componentIndex + 1); - else if (m_components[componentIndex] != nullptr) - throw std::runtime_error("component was added multiple times"); - - m_components[componentIndex] = std::move(component); - - return componentRef; + auto& updaterEntry = m_updaters.emplace_back(); + updaterEntry.lastUpdate = Time::Zero(); + updaterEntry.nextUpdate = Time::Zero(); + updaterEntry.updater = std::move(functor); } template - void ApplicationBase::AddUpdater(F&& functor) + void ApplicationBase::AddUpdaterFunc(F&& functor) { - m_updaters.emplace_back(std::make_unique>>(std::forward(functor))); + static_assert(std::is_invocable_v || std::is_invocable_v, "functor must be callable with either a Time parameter or no parameter"); + return AddUpdater(std::make_unique>>(std::forward(functor))); + } + + template + void ApplicationBase::AddUpdaterFunc(FixedInterval fixedInterval, F&& functor) + { + return AddUpdaterFunc(fixedInterval.interval, std::forward(functor)); + } + + template + void ApplicationBase::AddUpdaterFunc(Interval interval, F&& functor) + { + return AddUpdaterFunc(interval.interval, std::forward(functor)); } inline void ApplicationBase::ClearComponents() @@ -71,6 +74,36 @@ namespace Nz { m_running = false; } + + template + T& ApplicationBase::AddComponent(Args&&... args) + { + std::size_t componentIndex = ApplicationComponentRegistry::GetComponentId(); + + std::unique_ptr component = std::make_unique(*this, std::forward(args)...); + T& componentRef = *component; + + if (componentIndex >= m_components.size()) + m_components.resize(componentIndex + 1); + else if (m_components[componentIndex] != nullptr) + throw std::runtime_error("component was added multiple times"); + + m_components[componentIndex] = std::move(component); + + return componentRef; + } + + template + void ApplicationBase::AddUpdaterFunc(Time interval, F&& functor) + { + if constexpr (std::is_invocable_r_v || std::is_invocable_r_v) + return AddUpdater(std::make_unique, FixedInterval>>(std::forward(functor), interval)); + else if constexpr (std::is_invocable_r_v || std::is_invocable_r_v) + return AddUpdater(std::make_unique>>(std::forward(functor))); + else + static_assert(AlwaysFalse(), "functor must be callable with either elapsed time or nothing and return void or next update time"); + } + } #include diff --git a/include/Nazara/Core/ApplicationUpdater.hpp b/include/Nazara/Core/ApplicationUpdater.hpp index bfda3866b..1eb70e12c 100644 --- a/include/Nazara/Core/ApplicationUpdater.hpp +++ b/include/Nazara/Core/ApplicationUpdater.hpp @@ -21,21 +21,36 @@ namespace Nz ApplicationUpdater(ApplicationUpdater&&) = delete; virtual ~ApplicationUpdater(); - virtual void Update(Time elapsedTime) = 0; + virtual Time Update(Time elapsedTime) = 0; ApplicationUpdater& operator=(const ApplicationUpdater&) = delete; ApplicationUpdater& operator=(ApplicationUpdater&&) = delete; }; - + template class ApplicationUpdaterFunctor : public ApplicationUpdater { public: ApplicationUpdaterFunctor(F functor); - void Update(Time elapsedTime) override; + Time Update(Time elapsedTime) override; private: + template Time TriggerFunctor(Args&&... args); + + F m_functor; + }; + + template + class ApplicationUpdaterFunctorWithInterval : public ApplicationUpdater + { + public: + ApplicationUpdaterFunctorWithInterval(F functor, Time interval); + + Time Update(Time elapsedTime) override; + + private: + Time m_interval; F m_functor; }; } diff --git a/include/Nazara/Core/ApplicationUpdater.inl b/include/Nazara/Core/ApplicationUpdater.inl index 41166bbc8..4803fd58e 100644 --- a/include/Nazara/Core/ApplicationUpdater.inl +++ b/include/Nazara/Core/ApplicationUpdater.inl @@ -13,9 +13,59 @@ namespace Nz } template - void ApplicationUpdaterFunctor::Update(Time elapsedTime) + Time ApplicationUpdaterFunctor::Update(Time elapsedTime) { - m_functor(elapsedTime); + if constexpr (std::is_invocable_v) + return TriggerFunctor(elapsedTime); + else if constexpr (std::is_invocable_v) + return TriggerFunctor(); + else + static_assert(AlwaysFalse(), "updater functor must be callable with a Time or nothing"); + } + + template + template + Time ApplicationUpdaterFunctor::TriggerFunctor(Args&&... args) + { + if constexpr (std::is_same_v, Time>) + return m_functor(std::forward(args)...); + else if constexpr (std::is_same_v, void>) + { + m_functor(std::forward(args)...); + return Time::Zero(); + } + else + static_assert(AlwaysFalse(), "updater functor must either return Time or void"); + } + + + template + ApplicationUpdaterFunctorWithInterval::ApplicationUpdaterFunctorWithInterval(F functor, Time interval) : + m_interval(interval), + m_functor(std::move(functor)) + { + } + + template + Time ApplicationUpdaterFunctorWithInterval::Update(Time elapsedTime) + { + if constexpr (std::is_invocable_v) + { + if constexpr (FixedInterval) + m_functor(m_interval); + else + m_functor(elapsedTime); + } + else + { + static_assert(std::is_invocable_v, "updater functor must be callable with a Time or nothing"); + m_functor(); + } + + if constexpr (FixedInterval) + return -m_interval; + else + return m_interval; } } diff --git a/include/Nazara/Core/Modules.inl b/include/Nazara/Core/Modules.inl index 1e070a746..34736d5fe 100644 --- a/include/Nazara/Core/Modules.inl +++ b/include/Nazara/Core/Modules.inl @@ -48,6 +48,7 @@ namespace Nz return ModuleTuple::template Get(); } + template template ModuleTuple::ModuleTuple(ModuleConfig&&... configs) : diff --git a/src/Nazara/Core/ApplicationBase.cpp b/src/Nazara/Core/ApplicationBase.cpp index e62121ddc..60dd50b4f 100644 --- a/src/Nazara/Core/ApplicationBase.cpp +++ b/src/Nazara/Core/ApplicationBase.cpp @@ -12,7 +12,8 @@ namespace Nz { ApplicationBase::ApplicationBase(int argc, const Pointer* argv) : - m_running(true) + m_running(true), + m_currentTime(Time::Zero()) { } @@ -55,8 +56,30 @@ namespace Nz bool ApplicationBase::Update(Time elapsedTime) { - for (auto& updater : m_updaters) - updater->Update(elapsedTime); + m_currentTime += elapsedTime; + + for (auto& updaterEntry : m_updaters) + { + if (updaterEntry.nextUpdate > m_currentTime) + continue; + + Time timeSinceLastUpdate = m_currentTime - updaterEntry.lastUpdate; + + if NAZARA_UNLIKELY(updaterEntry.nextUpdate == updaterEntry.lastUpdate) + { + // First call + timeSinceLastUpdate = Time::Zero(); + updaterEntry.lastUpdate = m_currentTime; + } + + Time interval = updaterEntry.updater->Update(timeSinceLastUpdate); + if (interval >= Time::Zero()) + updaterEntry.nextUpdate = m_currentTime + interval; + else + updaterEntry.nextUpdate = updaterEntry.lastUpdate + (-interval); + + updaterEntry.nextUpdate = std::max(updaterEntry.nextUpdate, m_currentTime); + } for (auto& componentPtr : m_components) { diff --git a/tests/GraphicsTest/main.cpp b/tests/GraphicsTest/main.cpp index 5241e5e3e..2bf40d807 100644 --- a/tests/GraphicsTest/main.cpp +++ b/tests/GraphicsTest/main.cpp @@ -174,7 +174,7 @@ int main() } }); - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { if (std::optional deltaTime = updateClock.RestartIfOver(Nz::Time::TickDuration(60))) { diff --git a/tests/RenderTest/main.cpp b/tests/RenderTest/main.cpp index 9bbc16bbd..93afe4624 100644 --- a/tests/RenderTest/main.cpp +++ b/tests/RenderTest/main.cpp @@ -284,7 +284,7 @@ int main() uboUpdate = true; }); - app.AddUpdater([&](Nz::Time /*elapsedTime*/) + app.AddUpdaterFunc([&] { if (std::optional deltaTime = updateClock.RestartIfOver(Nz::Time::TickDuration(60))) { diff --git a/tests/UnitTests/Engine/Core/ApplicationTest.cpp b/tests/UnitTests/Engine/Core/ApplicationTest.cpp new file mode 100644 index 000000000..08c552f7a --- /dev/null +++ b/tests/UnitTests/Engine/Core/ApplicationTest.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +SCENARIO("Application", "[CORE][ABSTRACTHASH]") +{ + WHEN("Updating the application multiple times") + { + Nz::ApplicationBase app; + + std::size_t triggerCount = 0; + app.AddUpdaterFunc([&](Nz::Time elapsedTime) + { + if (triggerCount == 0) + { + INFO("First update should have elapsed time as zero"); + CHECK(elapsedTime == Nz::Time::Zero()); + } + + triggerCount++; + }); + + app.Update(Nz::Time::Milliseconds(10)); + app.Update(Nz::Time::Milliseconds(10)); + app.Update(Nz::Time::Milliseconds(10)); + + CHECK(triggerCount == 3); + } + + WHEN("Using interval") + { + Nz::ApplicationBase app; + + std::size_t triggerCount = 0; + app.AddUpdaterFunc(Nz::ApplicationBase::Interval{ Nz::Time::Milliseconds(100) }, [&](Nz::Time elapsedTime) + { + if (triggerCount == 0) + { + INFO("First update should have elapsed time as zero"); + CHECK(elapsedTime == Nz::Time::Zero()); + } + + triggerCount++; + }); + + app.Update(Nz::Time::Milliseconds(100)); + CHECK(triggerCount == 1); + app.Update(Nz::Time::Milliseconds(10)); + CHECK(triggerCount == 1); + app.Update(Nz::Time::Milliseconds(100)); + CHECK(triggerCount == 2); + app.Update(Nz::Time::Milliseconds(90)); + CHECK(triggerCount == 2); // this does not trigger since 100ms have not elapsed since last update + } + + WHEN("Using fixed-time interval") + { + Nz::ApplicationBase app; + + std::size_t triggerCount = 0; + app.AddUpdaterFunc(Nz::ApplicationBase::FixedInterval{ Nz::Time::Milliseconds(100) }, [&](Nz::Time elapsedTime) + { + CHECK(elapsedTime == Nz::Time::Milliseconds(100)); + triggerCount++; + }); + + app.Update(Nz::Time::Milliseconds(100)); + CHECK(triggerCount == 1); + app.Update(Nz::Time::Milliseconds(10)); + CHECK(triggerCount == 1); + app.Update(Nz::Time::Milliseconds(100)); + CHECK(triggerCount == 2); + app.Update(Nz::Time::Milliseconds(90)); + CHECK(triggerCount == 3); // lost time is caught up + } +}