From 4883a0b98bb12def0d9c26f0f2fdf8b0719186b9 Mon Sep 17 00:00:00 2001 From: SilverSupplier Date: Tue, 7 Apr 2026 22:56:20 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[Engine]=20EngineRuntime=20=EC=B5=9C?= =?UTF-8?q?=EC=86=8C=20=EC=98=A4=EC=BC=80=EC=8A=A4=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - EngineWorld에 WorldQuery/WorldCommands 노출 추가 - EngineRuntime에 EcsCore, CommandBuffer, systems_ 소유권 추가 - addSystem, initialize, stepFrame 고정 스텝 루프 및 CommandBuffer flush 구현 - EngineRuntimeTests: 시스템 update 호출 및 CommandBuffer flush 검증 테스트 추가 --- src/engine/EngineRuntime.cpp | 23 +++++++++++++++ src/engine/EngineRuntime.h | 9 ++++++ src/engine/EngineSystem.h | 14 ++++++++++ tests/EngineRuntimeTests.cpp | 54 ++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+) diff --git a/src/engine/EngineRuntime.cpp b/src/engine/EngineRuntime.cpp index 1dfc923..8615c61 100644 --- a/src/engine/EngineRuntime.cpp +++ b/src/engine/EngineRuntime.cpp @@ -23,14 +23,23 @@ EngineConfig normalizeConfig(EngineConfig config) { EngineRuntime::EngineRuntime(EngineConfig config) : config_(normalizeConfig(config)), + world_(core_, buffer_), frameClock_(config_) { } +void EngineRuntime::addSystem(std::unique_ptr system) { + systems_.push_back(std::move(system)); +} + void EngineRuntime::initialize() { frameClock_.reset(); stats_ = {}; stats_.state = EngineState::Ready; ++runIndex_; + + for (auto& system : systems_) { + system->configure(world_); + } } void EngineRuntime::play() { @@ -67,6 +76,20 @@ void EngineRuntime::stepFrame(double deltaSeconds) { frameClock_.consumeFixedStep(); ++stats_.fixedStepIndex; ++stats_.fixedStepsThisFrame; + + const EngineStepContext ctx{ + .frameIndex = stats_.frameIndex, + .fixedStepIndex = stats_.fixedStepIndex, + .alpha = frameClock_.alpha(), + .runIndex = runIndex_, + .derivedSeed = 0, + }; + + for (auto& system : systems_) { + system->update(world_, ctx); + } + + buffer_.flush(core_); } stats_.alpha = frameClock_.alpha(); diff --git a/src/engine/EngineRuntime.h b/src/engine/EngineRuntime.h index 0da63c9..05c5336 100644 --- a/src/engine/EngineRuntime.h +++ b/src/engine/EngineRuntime.h @@ -1,7 +1,11 @@ #pragma once #include +#include +#include +#include "engine/CommandBuffer.h" +#include "engine/EcsCore.h" #include "engine/EngineConfig.h" #include "engine/EngineStats.h" #include "engine/EngineSystem.h" @@ -13,6 +17,8 @@ class EngineRuntime { public: explicit EngineRuntime(EngineConfig config = {}); + void addSystem(std::unique_ptr system); + void initialize(); void play(); void pause(); @@ -29,9 +35,12 @@ class EngineRuntime { private: EngineConfig config_; EngineStats stats_; + EcsCore core_; + CommandBuffer buffer_; EngineWorld world_; FrameClock frameClock_; std::uint64_t runIndex_{0}; + std::vector> systems_; }; } // namespace safecrowd::engine diff --git a/src/engine/EngineSystem.h b/src/engine/EngineSystem.h index 943b151..c8d09d6 100644 --- a/src/engine/EngineSystem.h +++ b/src/engine/EngineSystem.h @@ -1,10 +1,24 @@ #pragma once +#include "engine/CommandBuffer.h" #include "engine/EngineStepContext.h" +#include "engine/WorldQuery.h" namespace safecrowd::engine { class EngineWorld { +public: + EngineWorld() = delete; + explicit EngineWorld(EcsCore& core, CommandBuffer& buffer) + : query_(core), commands_(buffer) {} + + [[nodiscard]] WorldQuery& query() noexcept { return query_; } + [[nodiscard]] const WorldQuery& query() const noexcept { return query_; } + [[nodiscard]] WorldCommands& commands() noexcept { return commands_; } + +private: + WorldQuery query_; + WorldCommands commands_; }; class EngineSystem { diff --git a/tests/EngineRuntimeTests.cpp b/tests/EngineRuntimeTests.cpp index b40db19..178a4b9 100644 --- a/tests/EngineRuntimeTests.cpp +++ b/tests/EngineRuntimeTests.cpp @@ -1,7 +1,31 @@ #include "TestSupport.h" +#include + #include "engine/EngineRuntime.h" +namespace { + +struct Marker {}; + +class UpdateCounterSystem : public safecrowd::engine::EngineSystem { +public: + int& count; + explicit UpdateCounterSystem(int& c) : count(c) {} + void update(safecrowd::engine::EngineWorld&, const safecrowd::engine::EngineStepContext&) override { + ++count; + } +}; + +class SpawnMarkerSystem : public safecrowd::engine::EngineSystem { +public: + void update(safecrowd::engine::EngineWorld& world, const safecrowd::engine::EngineStepContext&) override { + world.commands().spawnEntity(Marker{}); + } +}; + +} // namespace + SC_TEST(EngineRuntimePlayAndStepUpdatesStats) { safecrowd::engine::EngineRuntime runtime({ .fixedDeltaTime = 0.25, @@ -20,6 +44,36 @@ SC_TEST(EngineRuntimePlayAndStepUpdatesStats) { SC_EXPECT_NEAR(stats.alpha, 0.0, 1e-9); } +SC_TEST(EngineRuntime_RegisteredSystem_UpdateCalledOnFixedStep) { + int count = 0; + safecrowd::engine::EngineRuntime runtime({ + .fixedDeltaTime = 0.25, + .maxCatchUpSteps = 4, + .baseSeed = 1, + }); + + runtime.addSystem(std::make_unique(count)); + runtime.play(); + runtime.stepFrame(0.50); // 2 fixed steps + + SC_EXPECT_EQ(count, 2); +} + +SC_TEST(EngineRuntime_WorldCommands_FlushedAfterEachFixedStep) { + safecrowd::engine::EngineRuntime runtime({ + .fixedDeltaTime = 0.25, + .maxCatchUpSteps = 4, + .baseSeed = 1, + }); + + runtime.addSystem(std::make_unique()); + runtime.play(); + runtime.stepFrame(0.25); // 1 fixed step + + const auto entities = runtime.world().query().view(); + SC_EXPECT_EQ(entities.size(), std::size_t{1}); +} + SC_TEST(EngineRuntimePauseAndStopResetLifecycleState) { safecrowd::engine::EngineRuntime runtime({ .fixedDeltaTime = 0.25, From df951326a2f459ad201ee4d44655a3afd8b7d5fd Mon Sep 17 00:00:00 2001 From: learncold Date: Wed, 8 Apr 2026 01:20:00 +0900 Subject: [PATCH 2/2] [Engine] Fix runtime lifecycle regressions --- src/engine/EngineRuntime.cpp | 10 ++++ tests/EngineRuntimeTests.cpp | 92 ++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/src/engine/EngineRuntime.cpp b/src/engine/EngineRuntime.cpp index 8615c61..2cc7e0f 100644 --- a/src/engine/EngineRuntime.cpp +++ b/src/engine/EngineRuntime.cpp @@ -33,12 +33,15 @@ void EngineRuntime::addSystem(std::unique_ptr system) { void EngineRuntime::initialize() { frameClock_.reset(); + core_ = EcsCore{}; + buffer_ = CommandBuffer{}; stats_ = {}; stats_.state = EngineState::Ready; ++runIndex_; for (auto& system : systems_) { system->configure(world_); + buffer_.flush(core_); } } @@ -58,6 +61,8 @@ void EngineRuntime::pause() { void EngineRuntime::stop() { frameClock_.reset(); + core_ = EcsCore{}; + buffer_ = CommandBuffer{}; stats_ = {}; stats_.state = EngineState::Stopped; } @@ -67,6 +72,11 @@ void EngineRuntime::stepFrame(double deltaSeconds) { initialize(); } + if (stats_.state == EngineState::Paused) { + stats_.fixedStepsThisFrame = 0; + return; + } + frameClock_.beginFrame(deltaSeconds); ++stats_.frameIndex; diff --git a/tests/EngineRuntimeTests.cpp b/tests/EngineRuntimeTests.cpp index 178a4b9..f3ac57f 100644 --- a/tests/EngineRuntimeTests.cpp +++ b/tests/EngineRuntimeTests.cpp @@ -1,5 +1,6 @@ #include "TestSupport.h" +#include #include #include "engine/EngineRuntime.h" @@ -24,6 +25,30 @@ class SpawnMarkerSystem : public safecrowd::engine::EngineSystem { } }; +class ConfigureSpawnMarkerSystem : public safecrowd::engine::EngineSystem { +public: + void configure(safecrowd::engine::EngineWorld& world) override { + world.commands().spawnEntity(Marker{}); + } + + void update(safecrowd::engine::EngineWorld&, const safecrowd::engine::EngineStepContext&) override { + } +}; + +class ConfigureObserveMarkerSystem : public safecrowd::engine::EngineSystem { +public: + std::size_t& count; + + explicit ConfigureObserveMarkerSystem(std::size_t& c) : count(c) {} + + void configure(safecrowd::engine::EngineWorld& world) override { + count = world.query().view().size(); + } + + void update(safecrowd::engine::EngineWorld&, const safecrowd::engine::EngineStepContext&) override { + } +}; + } // namespace SC_TEST(EngineRuntimePlayAndStepUpdatesStats) { @@ -74,6 +99,73 @@ SC_TEST(EngineRuntime_WorldCommands_FlushedAfterEachFixedStep) { SC_EXPECT_EQ(entities.size(), std::size_t{1}); } +SC_TEST(EngineRuntime_ConfigureCommands_AreVisibleToLaterSystems) { + std::size_t configuredMarkerCount = 0; + + safecrowd::engine::EngineRuntime runtime({ + .fixedDeltaTime = 0.25, + .maxCatchUpSteps = 4, + .baseSeed = 1, + }); + + runtime.addSystem(std::make_unique()); + runtime.addSystem(std::make_unique(configuredMarkerCount)); + + runtime.play(); + + SC_EXPECT_EQ(configuredMarkerCount, std::size_t{1}); + SC_EXPECT_EQ(runtime.world().query().view().size(), std::size_t{1}); +} + +SC_TEST(EngineRuntime_Stop_ClearsWorldAndPendingCommandsBeforeNextRun) { + safecrowd::engine::EngineRuntime runtime({ + .fixedDeltaTime = 0.25, + .maxCatchUpSteps = 4, + .baseSeed = 1, + }); + + runtime.addSystem(std::make_unique()); + runtime.play(); + runtime.stepFrame(0.25); + SC_EXPECT_EQ(runtime.world().query().view().size(), std::size_t{1}); + + runtime.world().commands().spawnEntity(Marker{}); + runtime.stop(); + + SC_EXPECT_EQ(runtime.world().query().view().size(), std::size_t{0}); + + runtime.play(); + runtime.stepFrame(0.25); + + SC_EXPECT_EQ(runtime.world().query().view().size(), std::size_t{1}); +} + +SC_TEST(EngineRuntime_PausedRuntime_DoesNotAdvanceSimulation) { + int count = 0; + + safecrowd::engine::EngineRuntime runtime({ + .fixedDeltaTime = 0.25, + .maxCatchUpSteps = 4, + .baseSeed = 1, + }); + + runtime.addSystem(std::make_unique(count)); + runtime.play(); + runtime.stepFrame(0.25); + runtime.pause(); + runtime.stepFrame(1.00); + + const auto& pausedStats = runtime.stats(); + SC_EXPECT_EQ(count, 1); + SC_EXPECT_EQ(pausedStats.frameIndex, 1ULL); + SC_EXPECT_EQ(pausedStats.fixedStepIndex, 1ULL); + SC_EXPECT_EQ(pausedStats.fixedStepsThisFrame, 0U); + + runtime.play(); + runtime.stepFrame(0.25); + SC_EXPECT_EQ(count, 2); +} + SC_TEST(EngineRuntimePauseAndStopResetLifecycleState) { safecrowd::engine::EngineRuntime runtime({ .fixedDeltaTime = 0.25,