Skip to content

Commit

Permalink
🐛 use vector of delegates to guarantee order of execution
Browse files Browse the repository at this point in the history
  • Loading branch information
linkdd committed Jan 20, 2024
1 parent c22a292 commit c4cb900
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 54 deletions.
102 changes: 58 additions & 44 deletions include/trollworks/game-loop.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <optional>
#include <chrono>
#include <thread>
#include <ranges>
#include <vector>

#include <type_traits>
#include <concepts>
Expand All @@ -25,41 +27,29 @@ namespace tw {

class game_loop {
private:
using sigh_setup_type = entt::sigh<void(controlflow&)>;
using sigh_teardown_type = entt::sigh<void()>;
using cb_setup_type = entt::delegate<void(controlflow&)>;
using cb_teardown_type = entt::delegate<void()>;

using sigh_frame_begin_type = entt::sigh<void(controlflow&)>;
using sigh_frame_end_type = entt::sigh<void(controlflow&)>;
using cb_frame_begin_type = entt::delegate<void(controlflow&)>;
using cb_frame_end_type = entt::delegate<void(controlflow&)>;

using sigh_fixed_update_type = entt::sigh<void(float, controlflow&)>;
using sigh_update_type = entt::sigh<void(float, controlflow&)>;
using sigh_late_update_type = entt::sigh<void(float, controlflow&)>;
using cb_fixed_update_type = entt::delegate<void(float, controlflow&)>;
using cb_update_type = entt::delegate<void(float, controlflow&)>;
using cb_late_update_type = entt::delegate<void(float, controlflow&)>;

using sigh_render_type = entt::sigh<void()>;
using cb_render_type = entt::delegate<void()>;

sigh_setup_type m_sig_setup;
sigh_teardown_type m_sig_teardown;
std::vector<cb_setup_type> m_sig_setup;
std::vector<cb_teardown_type> m_sig_teardown;

sigh_frame_begin_type m_sig_frame_begin;
sigh_frame_end_type m_sig_frame_end;
std::vector<cb_frame_begin_type> m_sig_frame_begin;
std::vector<cb_frame_end_type> m_sig_frame_end;

sigh_fixed_update_type m_sig_fixed_update;
sigh_update_type m_sig_update;
sigh_late_update_type m_sig_late_update;
std::vector<cb_fixed_update_type> m_sig_fixed_update;
std::vector<cb_update_type> m_sig_update;
std::vector<cb_late_update_type> m_sig_late_update;

sigh_render_type m_sig_render;

entt::sink<sigh_setup_type> m_sink_setup{m_sig_setup};
entt::sink<sigh_teardown_type> m_sink_teardown{m_sig_teardown};

entt::sink<sigh_frame_begin_type> m_sink_frame_begin{m_sig_frame_begin};
entt::sink<sigh_frame_end_type> m_sink_frame_end{m_sig_frame_end};

entt::sink<sigh_fixed_update_type> m_sink_fixed_update{m_sig_fixed_update};
entt::sink<sigh_update_type> m_sink_update{m_sig_update};
entt::sink<sigh_late_update_type> m_sink_late_update{m_sig_late_update};

entt::sink<sigh_render_type> m_sink_render{m_sig_render};
std::vector<cb_render_type> m_sig_render;

public:
game_loop() = default;
Expand Down Expand Up @@ -93,56 +83,72 @@ namespace tw {

template <auto Candidate, typename... Type>
game_loop& on_setup(Type&&... args) {
m_sink_setup.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_setup_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_setup.push_back(delegate);
return *this;
}

template <auto Candidate, typename... Type>
game_loop& on_teardown(Type&&... args) {
m_sink_teardown.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_teardown_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_teardown.push_back(delegate);
return *this;
}

template <auto Candidate, typename... Type>
game_loop& on_frame_begin(Type&&... args) {
m_sink_frame_begin.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_frame_begin_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_frame_begin.push_back(delegate);
return *this;
}

template <auto Candidate, typename... Type>
game_loop& on_frame_end(Type&&... args) {
m_sink_frame_end.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_frame_end_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_frame_end.push_back(delegate);
return *this;
}

template <auto Candidate, typename... Type>
game_loop& on_fixed_update(Type&&... args) {
m_sink_fixed_update.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_fixed_update_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_fixed_update.push_back(delegate);
return *this;
}

template <auto Candidate, typename... Type>
game_loop& on_update(Type&&... args) {
m_sink_update.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_update_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_update.push_back(delegate);
return *this;
}

template <auto Candidate, typename... Type>
game_loop& on_late_update(Type&&... args) {
m_sink_late_update.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_late_update_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_late_update.push_back(delegate);
return *this;
}

template <auto Candidate, typename... Type>
game_loop& on_render(Type&&... args) {
m_sink_render.template connect<Candidate>(std::forward<Type>(args)...);
auto delegate = cb_render_type{};
delegate.template connect<Candidate>(std::forward<Type>(args)...);
m_sig_render.push_back(delegate);
return *this;
}

void run() {
auto cf = controlflow::running;

m_sig_setup.publish(cf);
publish(m_sig_setup, cf);

auto last_time = std::chrono::high_resolution_clock::now();
auto lag = 0.0f;
Expand All @@ -155,23 +161,23 @@ namespace tw {
lag += delta_time;
last_time = current_time;

m_sig_frame_begin.publish(cf);
publish(m_sig_frame_begin, cf);

auto fixed_delta_time = 1.0f / m_ups;
while (lag >= fixed_delta_time) {
m_sig_fixed_update.publish(fixed_delta_time, cf);
publish(m_sig_fixed_update, fixed_delta_time, cf);
lag -= fixed_delta_time;
}

m_sig_update.publish(delta_time, cf);
publish(m_sig_update, delta_time, cf);
coroutine_manager::main().update();
m_sig_late_update.publish(delta_time, cf);
publish(m_sig_late_update, delta_time, cf);
job_manager::main().update(delta_time, &cf);
message_bus::main().update();

m_sig_render.publish();
publish(m_sig_render);

m_sig_frame_end.publish(cf);
publish(m_sig_frame_end, cf);

auto frame_end = std::chrono::high_resolution_clock::now();
auto frame_duration = frame_end - current_time;
Expand All @@ -187,7 +193,15 @@ namespace tw {
}
}

m_sig_teardown.publish();
publish(std::ranges::reverse_view{m_sig_teardown});
}

private:
template <typename Signal, typename... Args>
void publish(Signal s, Args&&... args) {
for (auto& delegate : s) {
delegate(std::forward<Args>(args)...);
}
}

private:
Expand Down
24 changes: 14 additions & 10 deletions tests/game-loop.spec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
#include "../include/trollworks.hpp"

struct game_state {
bool setup{false};
bool teardown{false};
int setup{0};
int teardown{0};
};

struct listener {
game_state& gs;
int val{0};

void on_setup(tw::controlflow& cf) {
gs.setup = true;
gs.setup = val;
}

void on_teardown() {
gs.teardown = true;
gs.teardown = val;
}

void on_update(float dt, tw::controlflow& cf) {
Expand All @@ -25,14 +26,17 @@ struct listener {

TEST_CASE("game_loop") {
auto gs = game_state{};
auto l = listener{.gs = gs};
auto l1 = listener{.gs = gs, .val = 1};
auto l2 = listener{.gs = gs, .val = 2};

auto loop = tw::game_loop{}
.on_setup<&listener::on_setup>(l)
.on_teardown<&listener::on_teardown>(l)
.on_update<&listener::on_update>(l);
.on_setup<&listener::on_setup>(l1)
.on_teardown<&listener::on_teardown>(l1)
.on_update<&listener::on_update>(l1)
.on_setup<&listener::on_setup>(l2)
.on_teardown<&listener::on_teardown>(l2);

loop.run();
CHECK(gs.setup);
CHECK(gs.teardown);
CHECK(gs.setup == 2);
CHECK(gs.teardown == 1);
}

0 comments on commit c4cb900

Please # to comment.