Add entity freeing and queued scene management

Works like Godot's queue_free
master
John Montagu, the 4th Earl of Sandvich 2026-03-15 20:44:45 -07:00
parent 3bf8200d0f
commit 773329605b
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
3 changed files with 56 additions and 4 deletions

View File

@ -33,6 +33,7 @@ class Component {
struct Entity { struct Entity {
std::vector<std::shared_ptr<Component>> components; std::vector<std::shared_ptr<Component>> components;
GameContext *context = nullptr; GameContext *context = nullptr;
bool queuedForFree = false;
/** /**
* Injects the global game context/state so the entity and its components can * Injects the global game context/state so the entity and its components can
@ -93,4 +94,20 @@ struct Entity {
c->Update(dt); c->Update(dt);
} }
} }
/**
* Marks the entity for deferred destruction at the end of a systems tick.
* Inspired by Godot!
*/
void QueueFree() { queuedForFree = true; }
/**
* Propagates cleanup call to all owned components so they can release any
* resources or references before the entity is destroyed.
*/
void Cleanup() {
for (auto &c : components) {
c->Cleanup();
}
}
}; };

View File

@ -39,6 +39,7 @@ class SceneManager {
* @param args Arguments forwarded to the constructor of the new scene. * @param args Arguments forwarded to the constructor of the new scene.
*/ */
template <typename T, typename... Args> void ChangeScene(Args &&...args); template <typename T, typename... Args> void ChangeScene(Args &&...args);
template <typename T, typename... Args> void QueueSceneChange(Args &&...args);
/** /**
* @brief Changes to the provided next scene, invoking `Exit` on the old scene and * @brief Changes to the provided next scene, invoking `Exit` on the old scene and
@ -47,6 +48,7 @@ class SceneManager {
* @param nextScene The new scene to switch to. * @param nextScene The new scene to switch to.
*/ */
void ChangeScene(std::unique_ptr<Scene> nextScene); void ChangeScene(std::unique_ptr<Scene> nextScene);
void QueueSceneChange(std::unique_ptr<Scene> nextScene);
void Update(float dt); void Update(float dt);
void Draw(); void Draw();
@ -54,6 +56,7 @@ class SceneManager {
// use unique_ptr to enforce single ownership and ensure proper cleanup on // use unique_ptr to enforce single ownership and ensure proper cleanup on
// scene change // scene change
std::unique_ptr<Scene> current; std::unique_ptr<Scene> current;
std::unique_ptr<Scene> queued;
}; };
class StartMenuScene; class StartMenuScene;
@ -100,10 +103,22 @@ inline void SceneManager::ChangeScene(std::unique_ptr<Scene> nextScene) {
} }
} }
inline void SceneManager::QueueSceneChange(std::unique_ptr<Scene> nextScene) {
queued = std::move(nextScene);
}
inline void SceneManager::Update(float dt) { inline void SceneManager::Update(float dt) {
if (queued) {
ChangeScene(std::move(queued));
}
if (current) { if (current) {
current->Update(dt); current->Update(dt);
} }
if (queued) {
ChangeScene(std::move(queued));
}
} }
inline void SceneManager::Draw() { inline void SceneManager::Draw() {
@ -114,7 +129,7 @@ inline void SceneManager::Draw() {
inline void StartMenuScene::Update(float) { inline void StartMenuScene::Update(float) {
if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
manager.ChangeScene<GameplayScene>(); manager.QueueSceneChange<GameplayScene>();
} }
} }
@ -154,7 +169,7 @@ inline void GameplayScene::Enter() {
inline void GameplayScene::Update(float dt) { inline void GameplayScene::Update(float dt) {
UpdateAllSystems(entities, context, dt); UpdateAllSystems(entities, context, dt);
if (wantsDeathScene) { if (wantsDeathScene) {
manager.ChangeScene<DeathScene>(); manager.QueueSceneChange<DeathScene>();
} }
} }
@ -198,12 +213,12 @@ inline void GameplayScene::Draw() {
inline void DeathScene::Update(float) { inline void DeathScene::Update(float) {
if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { if (IsKeyPressed(KEY_ENTER) || IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
manager.ChangeScene<GameplayScene>(); manager.QueueSceneChange<GameplayScene>();
return; return;
} }
if (IsKeyPressed(KEY_ESCAPE)) { if (IsKeyPressed(KEY_ESCAPE)) {
manager.ChangeScene<StartMenuScene>(); manager.QueueSceneChange<StartMenuScene>();
} }
} }
@ -217,3 +232,7 @@ inline void DeathScene::Draw() {
template <typename T, typename... Args> inline void SceneManager::ChangeScene(Args &&...args) { template <typename T, typename... Args> inline void SceneManager::ChangeScene(Args &&...args) {
ChangeScene(std::make_unique<T>(*this, std::forward<Args>(args)...)); ChangeScene(std::make_unique<T>(*this, std::forward<Args>(args)...));
} }
template <typename T, typename... Args> inline void SceneManager::QueueSceneChange(Args &&...args) {
QueueSceneChange(std::make_unique<T>(*this, std::forward<Args>(args)...));
}

View File

@ -4,6 +4,7 @@
#include "Entity.hpp" #include "Entity.hpp"
#include "GameContext.hpp" #include "GameContext.hpp"
#include <algorithm>
#include <memory> #include <memory>
#include <vector> #include <vector>
@ -60,4 +61,19 @@ void UpdateAllSystems(std::vector<std::shared_ptr<Entity>> &entities, GameContex
} }
} }
} }
auto remover = [](const std::shared_ptr<Entity> &entity) {
if (!entity) {
return true;
}
if (entity->queuedForFree) {
entity->Cleanup();
return true;
}
return false;
};
entities.erase(std::remove_if(entities.begin(), entities.end(), remover), entities.end());
} }