Loading...
Searching...
No Matches
SceneManager.h
Go to the documentation of this file.
1// Copyright (c) 2025 Dave R. Smith.
2// Malena Framework — Licensed under PolyForm Noncommercial 1.0.0; commercial use requires a paid license. See LICENSE.
3
4//
5// SceneManager.h
6//
7
8#ifndef MALENA_SCENEMANAGER_H
9#define MALENA_SCENEMANAGER_H
10
11#pragma once
12
14#include <Malena/Core/Core.h>
16#include <unordered_map>
17#include <functional>
18#include <vector>
19#include <memory>
20#include <type_traits>
21
22namespace ml
23{
151 template<typename StateEnum>
153 {
154 // ── Internal scene entry ────────────────────────────────────────────
155
157 struct SceneEntry
158 {
159 Core* scene;
160 std::function<Core*()> factory;
161 std::unique_ptr<Core> owned;
162
163 bool isLazy() const { return static_cast<bool>(factory); }
164 bool isActive() const { return scene != nullptr; }
165
167 Core* get() const { return scene; }
168 };
170
171 // ── Per-instance storage ────────────────────────────────────────────
172
173 std::unordered_map<StateEnum, SceneEntry, EnumClassHash> _bindings;
174 std::vector<StateEnum> _history;
175 StateEnum _current{};
176 bool _started = false;
177 bool _attached = false;
178
180 std::function<void(Core&)> _add;
181
183 std::function<void(Core&)> _remove;
184
186 std::function<void(StateEnum)> _setter;
187
189 std::function<void(StateEnum)> _sync;
190
191 public:
192
193 // ── Lifetime ──────────────────────────────────────────────────────────
194
195 SceneManager() = default;
196
197 // Non-copyable, non-movable: attach() captures `this` into the owner's
198 // callbacks, so the instance must keep a stable address while attached.
199 SceneManager(const SceneManager&) = delete;
203
204 // ── Bind ────────────────────────────────────────────────────────────
205
216 void bind(StateEnum state, Core& scene)
217 {
218 scene.setEnabled(false);
219 _bindings[state] = SceneEntry{ &scene, nullptr, nullptr };
220 }
221
239 template<typename SceneType, typename... Args>
240 void bindLazy(StateEnum state, Args&&... args)
241 {
242 static_assert(std::is_base_of_v<Core, SceneType>,
243 "[SceneManager] bindLazy: SceneType must derive from ml::Core.");
244
245 // Capture args by value so the factory is self-contained
246 _bindings[state] = SceneEntry
247 {
248 nullptr,
249 [capturedArgs = std::make_tuple(std::forward<Args>(args)...)]() mutable -> Core*
250 {
251 return std::apply([](auto&&... a) -> Core*
252 {
253 return new SceneType(std::forward<decltype(a)>(a)...);
254 }, capturedArgs);
255 },
256 nullptr
257 };
258 }
259
260 // ── Attach ──────────────────────────────────────────────────────────
261
285 template<typename Owner>
286 void attach(Owner& owner)
287 {
288 _attached = true;
289
290 // Store navigation primitives
291 _setter = [&owner](StateEnum s) { owner.setState(s); };
292 _sync = [&owner](StateEnum s) { owner.syncState(s); };
293 _add = [&owner](Core& s) { owner.addComponent(s); };
294 _remove = [&owner](Core& s) { owner.removeComponent(s); };
295
296 // Hook into the owner's state machine — SceneManager reacts to transitions
297 owner.onStateExit([this](StateEnum leaving)
298 {
299 deactivate(leaving);
300 });
301
302 owner.onStateEnter([this](StateEnum entering)
303 {
304 activate(entering);
305 });
306 }
307
308 // ── Start ───────────────────────────────────────────────────────────
309
319 void start(StateEnum state)
320 {
321 _current = state;
322 _started = true;
323 if (_sync) _sync(state); // sync owner state machine without triggering callbacks
324 activate(state); // show first scene — no history push
325 }
326
327 // ── Navigation ──────────────────────────────────────────────────────
328
339 void back()
340 {
341 if (_history.empty()) return;
342
343 StateEnum previous = _history.back();
344 _history.pop_back();
345
346 // Call setState without re-pushing to history
347 // We set _current ahead of time so activate() knows not to push
348 _current = previous;
349
350 if (_setter)
351 _setter(previous);
352 }
353
354 // ── Query ───────────────────────────────────────────────────────────
355
360 StateEnum current() const { return _current; }
361
366 bool isActive(StateEnum state) const { return _started && _current == state; }
367
372 bool has(StateEnum state) const { return _bindings.count(state) > 0; }
373
380 void clear()
381 {
382 _bindings.clear();
383 _history.clear();
384 _started = false;
385 _attached = false;
386 _add = nullptr;
387 _remove = nullptr;
388 _setter = nullptr;
389 _sync = nullptr;
390 }
391
392 private:
393
394 // ── Internal transition helpers ─────────────────────────────────────
395
403 void activate(StateEnum state)
404 {
405 auto it = _bindings.find(state);
406 if (it == _bindings.end()) return;
407
408 SceneEntry& entry = it->second;
409
410 if (entry.isLazy())
411 {
412 // Construct on demand if not already alive
413 if (!entry.owned)
414 {
415 entry.owned.reset(entry.factory());
416 entry.scene = entry.owned.get();
417 }
418 }
419
420 if (entry.scene && _add)
421 {
422 entry.scene->setEnabled(true);
423 _add(*entry.scene);
424 }
425 }
426
435 void deactivate(StateEnum state)
436 {
437 auto it = _bindings.find(state);
438 if (it == _bindings.end()) return;
439
440 SceneEntry& entry = it->second;
441
442 if (entry.scene && _remove)
443 {
444 _remove(*entry.scene);
445 entry.scene->setEnabled(false);
446 }
447
448 // Push to history so back() can return
449 _history.push_back(state);
450 _current = state; // will be overwritten by activate() shortly
451
452 if (entry.isLazy())
453 {
454 // Free the lazy instance — next visit will reconstruct
455 entry.scene = nullptr;
456 entry.owned.reset();
457 }
458 }
459 };
460
461} // namespace ml
462
463#endif // MALENA_SCENEMANAGER_H
Virtual base class for all Malena framework objects.
Definition Core.h:97
void setEnabled(bool enabled)
bool has(StateEnum state) const
Return true if a scene has been bound to state.
SceneManager()=default
void bindLazy(StateEnum state, Args &&... args)
Bind a lazily-constructed scene type to a state.
void attach(Owner &owner)
Wire SceneManager into the owner's state machine.
SceneManager(SceneManager &&)=delete
void back()
Navigate to the previously visited state.
void bind(StateEnum state, Core &scene)
Bind an existing scene instance to a state (eager).
SceneManager(const SceneManager &)=delete
bool isActive(StateEnum state) const
Return true if state is currently the active scene.
SceneManager & operator=(SceneManager &&)=delete
void start(StateEnum state)
Activate the initial scene without recording a history entry.
StateEnum current() const
Return the currently active state.
SceneManager & operator=(const SceneManager &)=delete
void clear()
Clear all bindings and history.
Definition Component.h:22