Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions src/engine/EngineRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ void EngineRuntime::initialize() {
core_ = EcsCore{};
resources_ = ResourceStore{};
buffer_ = CommandBuffer{};
scheduler_.resetCadenceState();
stats_ = {};
stats_.state = EngineState::Ready;
++runIndex_;
Expand Down Expand Up @@ -73,6 +74,7 @@ void EngineRuntime::stop() {
core_ = EcsCore{};
resources_ = ResourceStore{};
buffer_ = CommandBuffer{};
scheduler_.resetCadenceState();
stats_ = {};
stats_.state = EngineState::Stopped;
}
Expand Down Expand Up @@ -100,8 +102,7 @@ void EngineRuntime::stepFrame(double deltaSeconds) {
.derivedSeed = 0,
};

scheduler_.executePhase(UpdatePhase::PreSimulation, TriggerPolicy::EveryFrame,
world_, ctx);
scheduler_.executePhase(UpdatePhase::PreSimulation, world_, ctx);

while (frameClock_.shouldRunFixedStep()) {
frameClock_.consumeFixedStep();
Expand All @@ -116,19 +117,16 @@ void EngineRuntime::stepFrame(double deltaSeconds) {
.derivedSeed = 0,
};

scheduler_.executePhase(UpdatePhase::FixedSimulation, TriggerPolicy::FixedStep,
world_, ctx);
scheduler_.executePhase(UpdatePhase::FixedSimulation, world_, ctx);
}

ctx.alpha = frameClock_.alpha();
scheduler_.executePhase(UpdatePhase::PostSimulation, TriggerPolicy::EveryFrame,
world_, ctx);
scheduler_.executePhase(UpdatePhase::PostSimulation, world_, ctx);

stats_.alpha = frameClock_.alpha();

ctx.alpha = stats_.alpha;
scheduler_.executePhase(UpdatePhase::RenderSync, TriggerPolicy::EveryFrame,
world_, ctx);
scheduler_.executePhase(UpdatePhase::RenderSync, world_, ctx);
}

EngineWorld& EngineRuntime::world() noexcept {
Expand Down
3 changes: 3 additions & 0 deletions src/engine/SystemDescriptor.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include <cstdint>

#include "engine/TriggerPolicy.h"
#include "engine/UpdatePhase.h"

Expand All @@ -9,6 +11,7 @@ struct SystemDescriptor {
UpdatePhase phase{UpdatePhase::FixedSimulation};
int order{0};
TriggerPolicy triggerPolicy{TriggerPolicy::FixedStep};
std::uint32_t intervalTicks{1};
};

} // namespace safecrowd::engine
79 changes: 63 additions & 16 deletions src/engine/SystemScheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,37 @@ namespace safecrowd::engine {
namespace {

void validateDescriptor(const SystemDescriptor& descriptor) {
if (descriptor.triggerPolicy == TriggerPolicy::Interval) {
throw std::invalid_argument("TriggerPolicy::Interval is not supported yet.");
}

if (descriptor.phase == UpdatePhase::FixedSimulation &&
descriptor.triggerPolicy != TriggerPolicy::FixedStep) {
if (descriptor.triggerPolicy == TriggerPolicy::Interval &&
descriptor.intervalTicks == 0) {
throw std::invalid_argument(
"FixedSimulation systems must use TriggerPolicy::FixedStep.");
"TriggerPolicy::Interval requires intervalTicks > 0.");
}

if (descriptor.phase != UpdatePhase::FixedSimulation &&
descriptor.phase != UpdatePhase::Startup &&
descriptor.triggerPolicy != TriggerPolicy::EveryFrame) {
throw std::invalid_argument(
"Frame phases must use TriggerPolicy::EveryFrame.");
switch (descriptor.phase) {
case UpdatePhase::Startup:
if (descriptor.triggerPolicy != TriggerPolicy::EveryFrame) {
throw std::invalid_argument(
"Startup systems must use TriggerPolicy::EveryFrame.");
}
break;

case UpdatePhase::FixedSimulation:
if (descriptor.triggerPolicy == TriggerPolicy::EveryFrame) {
throw std::invalid_argument(
"FixedSimulation systems must use TriggerPolicy::FixedStep or "
"TriggerPolicy::Interval.");
}
break;

case UpdatePhase::PreSimulation:
case UpdatePhase::PostSimulation:
case UpdatePhase::RenderSync:
if (descriptor.triggerPolicy == TriggerPolicy::FixedStep) {
throw std::invalid_argument(
"Frame phases must use TriggerPolicy::EveryFrame or "
"TriggerPolicy::Interval.");
}
break;
}
}

Expand Down Expand Up @@ -55,15 +71,46 @@ void SystemScheduler::executeStartup(EngineWorld& world, const EngineStepContext
buffer_.flush(core_);
}

void SystemScheduler::executePhase(UpdatePhase phase, TriggerPolicy triggerPolicy,
EngineWorld& world, const EngineStepContext& ctx) {
void SystemScheduler::executePhase(UpdatePhase phase, EngineWorld& world,
const EngineStepContext& ctx) {
auto shouldExecuteInterval = [](Entry& entry) {
if (entry.intervalCountdown == 0) {
entry.intervalCountdown = entry.descriptor.intervalTicks - 1;
return true;
}

--entry.intervalCountdown;
return false;
};

for (auto& e : entries_) {
if (e.descriptor.phase == phase &&
e.descriptor.triggerPolicy == triggerPolicy) {
if (e.descriptor.phase != phase) {
continue;
}

bool shouldExecute = false;
switch (e.descriptor.triggerPolicy) {
case TriggerPolicy::EveryFrame:
case TriggerPolicy::FixedStep:
shouldExecute = true;
break;

case TriggerPolicy::Interval:
shouldExecute = shouldExecuteInterval(e);
break;
}

if (shouldExecute) {
e.system->update(world, ctx);
}
}
buffer_.flush(core_);
}

void SystemScheduler::resetCadenceState() {
for (auto& e : entries_) {
e.intervalCountdown = 0;
}
}

} // namespace safecrowd::engine
6 changes: 4 additions & 2 deletions src/engine/SystemScheduler.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <cstdint>
#include <memory>
#include <vector>

Expand All @@ -18,13 +19,14 @@ class SystemScheduler {
void registerSystem(std::unique_ptr<EngineSystem> system, SystemDescriptor descriptor);
void configure(EngineWorld& world);
void executeStartup(EngineWorld& world, const EngineStepContext& ctx);
void executePhase(UpdatePhase phase, TriggerPolicy triggerPolicy,
EngineWorld& world, const EngineStepContext& ctx);
void executePhase(UpdatePhase phase, EngineWorld& world, const EngineStepContext& ctx);
void resetCadenceState();

private:
struct Entry {
std::unique_ptr<EngineSystem> system;
SystemDescriptor descriptor;
std::uint32_t intervalCountdown{0};
};

EcsCore& core_;
Expand Down
71 changes: 69 additions & 2 deletions tests/EngineRuntimeTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,43 @@ SC_TEST(EngineRuntime_PausedRuntime_DoesNotAdvanceSimulation) {
SC_EXPECT_EQ(count, 2);
}

SC_TEST(EngineRuntime_AddSystem_RejectsUnsupportedIntervalTriggerPolicy) {
SC_TEST(EngineRuntime_IntervalSystemsFollowTheirPhaseCadence) {
int frameCadenceCount = 0;
int fixedCadenceCount = 0;

safecrowd::engine::EngineRuntime runtime({
.fixedDeltaTime = 0.25,
.maxCatchUpSteps = 4,
.baseSeed = 1,
});

runtime.addSystem(
std::make_unique<UpdateCounterSystem>(frameCadenceCount),
{.phase = safecrowd::engine::UpdatePhase::PreSimulation,
.triggerPolicy = safecrowd::engine::TriggerPolicy::Interval,
.intervalTicks = 2});
runtime.addSystem(
std::make_unique<UpdateCounterSystem>(fixedCadenceCount),
{.phase = safecrowd::engine::UpdatePhase::FixedSimulation,
.triggerPolicy = safecrowd::engine::TriggerPolicy::Interval,
.intervalTicks = 2});

runtime.play();

runtime.stepFrame(0.50);
SC_EXPECT_EQ(frameCadenceCount, 1);
SC_EXPECT_EQ(fixedCadenceCount, 1);

runtime.stepFrame(0.25);
SC_EXPECT_EQ(frameCadenceCount, 1);
SC_EXPECT_EQ(fixedCadenceCount, 2);

runtime.stepFrame(0.25);
SC_EXPECT_EQ(frameCadenceCount, 2);
SC_EXPECT_EQ(fixedCadenceCount, 2);
}

SC_TEST(EngineRuntime_AddSystem_RejectsZeroIntervalTicks) {
int count = 0;
safecrowd::engine::EngineRuntime runtime;

Expand All @@ -245,14 +281,45 @@ SC_TEST(EngineRuntime_AddSystem_RejectsUnsupportedIntervalTriggerPolicy) {
runtime.addSystem(
std::make_unique<UpdateCounterSystem>(count),
{.phase = safecrowd::engine::UpdatePhase::PreSimulation,
.triggerPolicy = safecrowd::engine::TriggerPolicy::Interval});
.triggerPolicy = safecrowd::engine::TriggerPolicy::Interval,
.intervalTicks = 0});
} catch (const std::exception&) {
threw = true;
}

SC_EXPECT_TRUE(threw);
}

SC_TEST(EngineRuntime_InitializeAndStop_ResetIntervalCadenceState) {
int count = 0;

safecrowd::engine::EngineRuntime runtime({
.fixedDeltaTime = 0.25,
.maxCatchUpSteps = 4,
.baseSeed = 1,
});

runtime.addSystem(
std::make_unique<UpdateCounterSystem>(count),
{.phase = safecrowd::engine::UpdatePhase::FixedSimulation,
.triggerPolicy = safecrowd::engine::TriggerPolicy::Interval,
.intervalTicks = 3});

runtime.play();
runtime.stepFrame(0.25);
runtime.stepFrame(0.25);
SC_EXPECT_EQ(count, 1);

runtime.initialize();
runtime.stepFrame(0.25);
SC_EXPECT_EQ(count, 2);

runtime.stop();
runtime.play();
runtime.stepFrame(0.25);
SC_EXPECT_EQ(count, 3);
}

SC_TEST(EngineRuntimePauseAndStopResetLifecycleState) {
safecrowd::engine::EngineRuntime runtime({
.fixedDeltaTime = 0.25,
Expand Down
Loading
Loading