Skip to content

Commit 24e2537

Browse files
feat: implement LifecycleManager for managing application lifecycle events
1 parent 9c2c6b9 commit 24e2537

3 files changed

Lines changed: 115 additions & 53 deletions

File tree

src/engine/engine.cpp

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,20 @@ namespace Hooray {
2424
InitAudioDevice();
2525
SetTargetFPS(60);
2626

27-
if (on_init_) {
28-
on_init_();
29-
}
27+
lifecycle_manager_.notify_init();
3028

3129
while (!WindowShouldClose()) {
3230
if (clear_buffer_after_frame) command_buffer_.clear();
3331

34-
// Process Input Listeners
35-
input_manager_.process_frame();
32+
const float delta_time = GetFrameTime();
3633

37-
if (on_update_) {
38-
on_update_(GetFrameTime());
39-
}
34+
// Update
35+
input_manager_.process_frame();
36+
lifecycle_manager_.notify_update(delta_time);
4037

38+
// Draw
4139
BeginDrawing();
42-
43-
if (on_draw_) {
44-
on_draw_();
45-
}
46-
40+
lifecycle_manager_.notify_draw();
4741
execute_commands();
4842

4943
EndDrawing();

src/engine/engine.hpp

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,41 @@ namespace Hooray {
1212
class LifecycleListener {
1313
public:
1414
virtual ~LifecycleListener() = default;
15-
virtual void on_init() {}
16-
virtual void on_update(float delta_time) {}
17-
virtual void on_draw() {}
15+
virtual bool on_init() { return false; }
16+
virtual bool on_update(float delta_time) { return false; }
17+
virtual bool on_draw() { return false; }
18+
};
19+
20+
class LifecycleManager {
21+
public:
22+
void add_listener(const std::shared_ptr<LifecycleListener>& listener) {
23+
listeners_.push_back(listener);
24+
}
25+
26+
void remove_listener(const std::shared_ptr<LifecycleListener>& listener) {
27+
std::erase(listeners_, listener);
28+
}
29+
30+
void notify_init() {
31+
for (auto& listener : listeners_) {
32+
listener->on_init();
33+
}
34+
}
35+
36+
void notify_update(float delta_time) {
37+
for (auto& listener : listeners_) {
38+
listener->on_update(delta_time);
39+
}
40+
}
41+
42+
void notify_draw() {
43+
for (auto& listener : listeners_) {
44+
listener->on_draw();
45+
}
46+
}
47+
48+
private:
49+
std::vector<std::shared_ptr<LifecycleListener>> listeners_;
1850
};
1951

2052
class InputListener {
@@ -28,12 +60,12 @@ namespace Hooray {
2860

2961
class InputManager {
3062
public:
31-
void add_listener(std::shared_ptr<InputListener> listener) {
63+
void add_listener(const std::shared_ptr<InputListener>& listener) {
3264
listeners_.push_back(listener);
3365
}
3466

3567
void remove_listener(const std::shared_ptr<InputListener>& listener) {
36-
listeners_.erase(std::remove(listeners_.begin(), listeners_.end(), listener), listeners_.end());
68+
std::erase(listeners_, listener);
3769
}
3870

3971
// --- State Polling API ---
@@ -73,25 +105,25 @@ namespace Hooray {
73105
std::vector<std::shared_ptr<InputListener>> listeners_;
74106

75107
void dispatch_key_pressed(int key) {
76-
for (auto & listener : std::views::reverse(listeners_)) {
108+
for (auto& listener : std::views::reverse(listeners_)) {
77109
if (listener->on_key_pressed(key)) break;
78110
}
79111
}
80112

81113
void dispatch_mouse_pressed(int button, Vector2 pos) {
82-
for (auto & listener : std::views::reverse(listeners_)) {
114+
for (auto& listener : std::views::reverse(listeners_)) {
83115
if (listener->on_mouse_pressed(button, pos)) break;
84116
}
85117
}
86118

87119
void dispatch_mouse_released(int button, Vector2 pos) {
88-
for (auto & listener : std::views::reverse(listeners_)) {
120+
for (auto& listener : std::views::reverse(listeners_)) {
89121
if (listener->on_mouse_released(button, pos)) break;
90122
}
91123
}
92124

93125
void dispatch_mouse_moved(Vector2 pos) {
94-
for (auto & listener : std::views::reverse(listeners_)) {
126+
for (auto& listener : std::views::reverse(listeners_)) {
95127
if (listener->on_mouse_moved(pos)) break;
96128
}
97129
}
@@ -123,6 +155,9 @@ namespace Hooray {
123155
InputManager& get_input_manager() { return input_manager_; }
124156
[[nodiscard]] const InputManager& get_input_manager() const { return input_manager_; }
125157

158+
LifecycleManager& get_lifecycle_manager() { return lifecycle_manager_; }
159+
[[nodiscard]] const LifecycleManager& get_lifecycle_manager() const { return lifecycle_manager_; }
160+
126161
void run();
127162

128163
void display();
@@ -137,6 +172,7 @@ namespace Hooray {
137172
std::string title_;
138173
CommandBufferBuilder command_buffer_{ 1024 };
139174

175+
LifecycleManager lifecycle_manager_;
140176
InputManager input_manager_;
141177

142178
Callback on_init_;

src/modules/vectorjs.cpp

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,61 @@ namespace App::Modules {
342342
return render_obj;
343343
}
344344

345+
class JSLifecycleListener : public Hooray::LifecycleListener {
346+
public:
347+
JSLifecycleListener(qjspp::Engine& engine, JSApplication& app, qjspp::Value user_app): app(app), engine(engine), user_app_(std::move(user_app)) {
348+
349+
cached_update_obj_ = create_update_context_object(engine);
350+
cached_render_obj_ = create_draw_render_object(engine, app.rengine.get_buffer());
351+
352+
if (auto fn = std::make_unique<qjspp::Value>(user_app_.get("onInit")); fn->is_function()) {
353+
on_init_fn_ = std::move(fn);
354+
}
355+
356+
if (auto fn = std::make_unique<qjspp::Value>(user_app_.get("onUpdate")); fn->is_function()) {
357+
on_update_fn_ = std::move(fn);
358+
}
359+
360+
if (auto fn = std::make_unique<qjspp::Value>(user_app_.get("onDraw")); fn->is_function()) {
361+
on_draw_fn_ = std::move(fn);
362+
}
363+
}
364+
365+
bool on_init() override {
366+
if (!on_init_fn_) return false;
367+
std::ignore = on_init_fn_->call_method(user_app_, {});
368+
return false;
369+
}
370+
371+
bool on_update(float delta_time) override {
372+
if (!on_update_fn_) return false;
373+
std::ignore = on_update_fn_->call_method(user_app_, { cached_update_obj_.clone() });
374+
return false;
375+
}
376+
377+
bool on_draw() override {
378+
if (!on_draw_fn_) return false;
379+
std::ignore = on_draw_fn_->call_method(user_app_, { cached_render_obj_.clone() });
380+
return false;
381+
}
382+
383+
private:
384+
JSApplication& app;
385+
qjspp::Value user_app_;
386+
qjspp::Engine& engine;
387+
qjspp::Value cached_update_obj_;
388+
qjspp::Value cached_render_obj_;
389+
std::unique_ptr<qjspp::Value> on_init_fn_;
390+
std::unique_ptr<qjspp::Value> on_update_fn_;
391+
std::unique_ptr<qjspp::Value> on_draw_fn_;
392+
};
393+
345394
class JSInputListener : public Hooray::InputListener {
346395
public:
347-
JSInputListener(qjspp::Engine& engine, qjspp::Value user_app) : engine(engine), user_app_(std::move(user_app)) {
348-
// Cache JavaScript callback functions if they exist on the JS object
396+
JSInputListener(qjspp::Engine& engine, JSApplication& app, qjspp::Value user_app) : app(app), engine(engine), user_app_(std::move(user_app)) {
349397
if (auto fn = std::make_unique<qjspp::Value>(user_app_.get("onKeyPressed")); fn->is_function()) {
350398
on_key_pressed_fn_ = std::move(fn);
351399
}
352-
353400
if (auto fn = std::make_unique<qjspp::Value>(user_app_.get("onMousePressed")); fn->is_function()) {
354401
on_mouse_pressed_fn_ = std::move(fn);
355402
}
@@ -372,6 +419,7 @@ namespace App::Modules {
372419
}
373420

374421
private:
422+
JSApplication& app;
375423
qjspp::Value user_app_;
376424
qjspp::Engine& engine;
377425
std::unique_ptr<qjspp::Value> on_key_pressed_fn_;
@@ -391,41 +439,25 @@ namespace App::Modules {
391439
});
392440

393441
cls.instance_method("run", [&engine](JSApplication* app, const qjspp::ArgList& args) -> qjspp::Value {
394-
if (!app) return {};
442+
if (!app || args.empty()) return {};
395443

396444
const qjspp::Value user_app = args[0].clone();
397445

398-
const auto js_listener = std::make_shared<JSInputListener>(engine, user_app.clone());
399-
app->rengine.get_input_manager().add_listener(js_listener);
400-
401-
if (const qjspp::Value on_init_func = user_app.get("onInit"); on_init_func.is_function()) {
402-
auto func_ptr = std::make_shared<qjspp::Value>(on_init_func.clone());
403-
auto app_ptr = std::make_shared<qjspp::Value>(user_app.clone());
404-
app->rengine.set_on_init([func_ptr, app_ptr]() {
405-
std::ignore = func_ptr->call_method(*app_ptr, {});
406-
});
407-
}
446+
// Instantiate listeners once for this application run session
447+
auto input_listener = std::make_shared<JSInputListener>(engine, *app, user_app.clone());
448+
auto lifecycle_listener = std::make_shared<JSLifecycleListener>(engine, *app, user_app.clone());
408449

409-
if (const qjspp::Value on_update_func = user_app.get("onUpdate"); on_update_func.is_function()) {
410-
auto func_ptr = std::make_shared<qjspp::Value>(on_update_func.clone());
411-
auto app_ptr = std::make_shared<qjspp::Value>(user_app.clone());
412-
auto update_obj_ptr = std::make_shared<qjspp::Value>(create_update_context_object(engine));
413-
app->rengine.set_on_update([func_ptr, app_ptr, update_obj_ptr](float delta) {
414-
std::ignore = func_ptr->call_method(*app_ptr, { update_obj_ptr->clone() });
415-
});
416-
}
417-
418-
if (const qjspp::Value on_draw_func = user_app.get("onDraw"); on_draw_func.is_function()) {
419-
auto func_ptr = std::make_shared<qjspp::Value>(on_draw_func.clone());
420-
auto app_ptr = std::make_shared<qjspp::Value>(user_app.clone());
421-
auto render_obj_ptr = std::make_shared<qjspp::Value>(create_draw_render_object(engine, app->rengine.get_buffer()));
422-
app->rengine.set_on_draw([func_ptr, app_ptr, render_obj_ptr]() {
423-
std::ignore = func_ptr->call_method(*app_ptr, { render_obj_ptr->clone() });
424-
});
425-
}
450+
// Register listeners with the engine managers
451+
app->rengine.get_input_manager().add_listener(input_listener);
452+
app->rengine.get_lifecycle_manager().add_listener(lifecycle_listener);
426453

454+
// Run the main game/render loop
427455
app->rengine.run();
428456

457+
// Cleanup listeners upon exiting the main loop to prevent leaks or dangling references
458+
app->rengine.get_input_manager().remove_listener(input_listener);
459+
app->rengine.get_lifecycle_manager().remove_listener(lifecycle_listener);
460+
429461
return {};
430462
});
431463

0 commit comments

Comments
 (0)