From 3920dd34a3eeb40d63b3268539b17d2835f9a113 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 25 Nov 2023 23:57:07 -0600 Subject: [PATCH] Timeout and Interval events --- src/dawn/asset/AssetManager.cpp | 36 ++++- src/dawn/asset/AssetManager.hpp | 36 +++++ src/dawn/event/CustomEvent.hpp | 141 +++++++++++++++++++ src/dawn/event/Event.hpp | 7 +- src/dawn/scene/Scene.cpp | 20 ++- src/dawn/scene/Scene.hpp | 19 +++ src/dawn/scene/SceneItem.cpp | 6 + src/dawn/scene/SceneItem.hpp | 6 + src/dawn/time/CMakeLists.txt | 5 +- src/dawn/time/event/CMakeLists.txt | 10 ++ src/dawn/time/event/IntervalEvent.cpp | 41 ++++++ src/dawn/time/event/IntervalEvent.hpp | 38 +++++ src/dawn/time/event/TimeoutEvent.cpp | 26 ++++ src/dawn/time/event/TimeoutEvent.hpp | 26 ++++ src/dawnhelloworld/scene/HelloWorldScene.cpp | 11 +- 15 files changed, 418 insertions(+), 10 deletions(-) create mode 100644 src/dawn/event/CustomEvent.hpp create mode 100644 src/dawn/time/event/CMakeLists.txt create mode 100644 src/dawn/time/event/IntervalEvent.cpp create mode 100644 src/dawn/time/event/IntervalEvent.hpp create mode 100644 src/dawn/time/event/TimeoutEvent.cpp create mode 100644 src/dawn/time/event/TimeoutEvent.hpp diff --git a/src/dawn/asset/AssetManager.cpp b/src/dawn/asset/AssetManager.cpp index 97d07c9b..922434c5 100644 --- a/src/dawn/asset/AssetManager.cpp +++ b/src/dawn/asset/AssetManager.cpp @@ -27,13 +27,45 @@ void AssetManager::update() { } } +void AssetManager::removeExisting(const std::string filename) { + auto existing = std::find_if( + pendingAssetLoaders.begin(), pendingAssetLoaders.end(), + [&](auto &loader) { + return loader->name == filename; + } + ); + if(existing != pendingAssetLoaders.end()) { + pendingAssetLoaders.erase(existing); + } + + existing = std::find_if( + finishedAssetLoaders.begin(), finishedAssetLoaders.end(), + [&](auto &loader) { + return loader->name == filename; + } + ); + if(existing != finishedAssetLoaders.end()) { + finishedAssetLoaders.erase(existing); + } +} + template<> std::shared_ptr AssetManager::get( const std::string filename ) { - // TODO: Check for existing loader + + auto existing = this->getExisting(filename); + if(existing) { + std::cout << "Found existing texture loader for " << filename << std::endl; + + // Check pointer hasn't gone stale, if it has remove it and create new. + auto texture = existing->weakTexture.lock(); + if(texture) return texture; + + std::cout << "Texture loader for " << filename << " has gone stale." << std::endl; + this->removeExisting(filename); + } - // Create new loader std::shared_ptr loader = std::make_shared( filename ); diff --git a/src/dawn/asset/AssetManager.hpp b/src/dawn/asset/AssetManager.hpp index 3a878b1b..a71ccd00 100644 --- a/src/dawn/asset/AssetManager.hpp +++ b/src/dawn/asset/AssetManager.hpp @@ -13,6 +13,42 @@ namespace Dawn { std::vector> pendingAssetLoaders; std::vector> finishedAssetLoaders; + /** + * Returns an existing asset loader if it exists. + * + * @param filename The filename of the asset to get. + * @return The asset loader if it exists, otherwise nullptr. + */ + template + std::shared_ptr getExisting(const std::string filename) { + auto existing = std::find_if( + pendingAssetLoaders.begin(), pendingAssetLoaders.end(), + [&](auto &loader) { + return loader->name == filename; + } + ); + + if(existing == finishedAssetLoaders.end()) { + existing = std::find_if( + finishedAssetLoaders.begin(), finishedAssetLoaders.end(), + [&](auto &loader) { + return loader->name == filename; + } + ); + + if(existing == finishedAssetLoaders.end()) return nullptr; + } + + return std::static_pointer_cast(*existing); + } + + /** + * Removes an existing asset loader if it exists. + * + * @param filename The filename of the asset to remove. + */ + void removeExisting(const std::string filename); + public: /** * Initializes this asset manager so it can begin accepting assets. diff --git a/src/dawn/event/CustomEvent.hpp b/src/dawn/event/CustomEvent.hpp new file mode 100644 index 00000000..a2622dbb --- /dev/null +++ b/src/dawn/event/CustomEvent.hpp @@ -0,0 +1,141 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "dawnlibs.hpp" + +namespace Dawn { + enum class CustomEventResult { + NOTHING, + REMOVE, + INVOKE, + INVOKE_AND_REMOVE + }; + + template< + typename InternalData, + typename InternalListenerData, + typename ListenerArgument, + typename ...InvokeArgs + > + class CustomEvent { + private: + int32_t nextId = 0; + std::unordered_map< + int32_t, + std::pair> + > listeners; + + protected: + InternalData internalData; + + /** + * Custom event filter. Decides whether or not the event should be emitted + * to the listener. + * + * @param listenerData Data for this listener. + * @param args The arguments to pass to the listeners. + * @return The result of the filter. + */ + virtual enum CustomEventResult shouldEmit( + const InternalListenerData &listenerData, + const InvokeArgs... args + ) = 0; + + /** + * Transform the arguments for listener data when the listener is first + * subscribed. + * + * @param argument The argument to transform. + * @return The transformed argument into an internal data format. + */ + virtual InternalListenerData transformData( + const ListenerArgument &argument + ) = 0; + + /** + * Transform the data for listener data after the event has been emitted. + * + * @param internalData The internal data to transform. + * @return Updated/Transformed internal data. + */ + virtual InternalListenerData transformDataAfterEmit( + const InternalListenerData &internalData + ) { + return internalData; + } + + public: + /** + * Emits the event. + * @param args The arguments to pass to the listeners. + */ + void emit(InvokeArgs... args) { + auto copy = listeners; + for(auto &pair : copy) { + // Check emit test. + auto result = this->shouldEmit( + pair.second.first, + args... + ); + if( + result == CustomEventResult::INVOKE || + result == CustomEventResult::INVOKE_AND_REMOVE + ) { + pair.second.second(args...); + } + + if( + result == CustomEventResult::REMOVE || + result == CustomEventResult::INVOKE_AND_REMOVE + ) { + listeners.erase(pair.first); + continue; + } + + if( + result == CustomEventResult::INVOKE || + result == CustomEventResult::INVOKE_AND_REMOVE + ) { + // Update the internal data. + listeners[pair.first].first = transformDataAfterEmit( + pair.second.first + ); + } + } + } + + /** + * Listens to the event. + * + * @param data Listener data to use. + * @param listener The listener to add. + * @returns A function that can be called to remove the listener. + */ + std::function listen( + const ListenerArgument &data, + const std::function listener + ) { + int32_t id = nextId++; + + auto pair = std::make_pair( + transformData(data), + listener + ); + + listeners[id] = pair; + return [this, id]() { + listeners.erase(id); + }; + } + + /** + * Destroys the custom event. + */ + virtual ~CustomEvent() { + listeners.clear(); + } + }; +} \ No newline at end of file diff --git a/src/dawn/event/Event.hpp b/src/dawn/event/Event.hpp index 638da515..5204a029 100644 --- a/src/dawn/event/Event.hpp +++ b/src/dawn/event/Event.hpp @@ -37,7 +37,9 @@ namespace Dawn { * @param listener The listener to add. * @returns A function that can be called to remove the listener. */ - std::function listen(const std::function listener) { + std::function listen( + const std::function listener + ) { int32_t id = nextId++; listeners[id] = listener; return [this, id]() { @@ -46,8 +48,7 @@ namespace Dawn { } /** - * Removes a listener from the event. - * @param id The id of the listener to remove. + * Destroys the event. */ virtual ~Event() { listeners.clear(); diff --git a/src/dawn/scene/Scene.cpp b/src/dawn/scene/Scene.cpp index 50f177e9..735baf95 100644 --- a/src/dawn/scene/Scene.cpp +++ b/src/dawn/scene/Scene.cpp @@ -14,7 +14,6 @@ Scene::Scene( game(game), sceneInitializer(sceneInitializer) { - } void Scene::stage() { @@ -23,18 +22,33 @@ void Scene::stage() { } void Scene::update() { + // Initialize new scene items if(!hasInitialized) { hasInitialized = true; for(auto &item : sceneItems) { item->init(); } } + + // Remove stale scene items. + auto itRemove = sceneItemsToRemove.begin(); + while(itRemove != sceneItemsToRemove.end()) { + auto item = *itRemove; + auto it = std::find(sceneItems.begin(), sceneItems.end(), item); + if(it != sceneItems.end()) sceneItems.erase(it); + itRemove++; + } + sceneItemsToRemove.clear(); + // Tick scene items. float_t delta = getGame()->timeManager.delta; if(paused) { onPausedUpdate.emit(delta); } else { onUnpausedUpdate.emit(delta); + + onTimeout.tick(delta); + onInterval.tick(delta); } } @@ -48,5 +62,9 @@ std::shared_ptr Scene::createSceneItem() { return item; } +void Scene::removeItem(const std::shared_ptr item) { + sceneItemsToRemove.push_back(item); +} + Scene::~Scene() { } \ No newline at end of file diff --git a/src/dawn/scene/Scene.hpp b/src/dawn/scene/Scene.hpp index 71d07f91..095a866f 100644 --- a/src/dawn/scene/Scene.hpp +++ b/src/dawn/scene/Scene.hpp @@ -6,19 +6,30 @@ #pragma once #include "game/Game.hpp" #include "scene/SceneItem.hpp" +#include "event/Event.hpp" +#include "time/event/IntervalEvent.hpp" +#include "time/event/TimeoutEvent.hpp" namespace Dawn { + struct IntervalData { + float_t frequency; + float_t nextInvoke; + }; + class Scene final : public std::enable_shared_from_this { private: std::weak_ptr game; const std::function sceneInitializer; std::vector> sceneItems; + std::vector> sceneItemsToRemove; bool_t paused = false; bool_t hasInitialized = false; public: Event onUnpausedUpdate; Event onPausedUpdate; + TimeoutEvent onTimeout; + IntervalEvent onInterval; /** * Constructs a scene object. @@ -57,6 +68,14 @@ namespace Dawn { */ std::shared_ptr createSceneItem(); + /** + * Removes a scene item from the scene. This remove will happen at the + * start of the next scene update. + * + * @param item Scene item to remove. + */ + void removeItem(const std::shared_ptr item); + /** * Returns a list of scene components that match the given type. * diff --git a/src/dawn/scene/SceneItem.cpp b/src/dawn/scene/SceneItem.cpp index 0ca1d838..33ef3669 100644 --- a/src/dawn/scene/SceneItem.cpp +++ b/src/dawn/scene/SceneItem.cpp @@ -30,6 +30,12 @@ void SceneItem::init() { } } +void SceneItem::remove() { + auto scene = getScene(); + if(!scene) return; + scene->removeItem(shared_from_this()); +} + SceneItem::~SceneItem() { std::for_each( components.begin(), diff --git a/src/dawn/scene/SceneItem.hpp b/src/dawn/scene/SceneItem.hpp index 1c2b464b..32106923 100644 --- a/src/dawn/scene/SceneItem.hpp +++ b/src/dawn/scene/SceneItem.hpp @@ -42,6 +42,12 @@ namespace Dawn { */ std::shared_ptr getScene(); + /** + * Queues this scene item to be removed from the scene (on the next scene + * update). + */ + void remove(); + /** * Returns the scene that this item belongs to. * diff --git a/src/dawn/time/CMakeLists.txt b/src/dawn/time/CMakeLists.txt index 20bae16f..07371285 100644 --- a/src/dawn/time/CMakeLists.txt +++ b/src/dawn/time/CMakeLists.txt @@ -6,4 +6,7 @@ target_sources(${DAWN_TARGET_NAME} PRIVATE ITimeManager.cpp -) \ No newline at end of file +) + +# Subdirs +add_subdirectory(event) \ No newline at end of file diff --git a/src/dawn/time/event/CMakeLists.txt b/src/dawn/time/event/CMakeLists.txt new file mode 100644 index 00000000..514f06a7 --- /dev/null +++ b/src/dawn/time/event/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2023 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DAWN_TARGET_NAME} + PRIVATE + IntervalEvent.cpp + TimeoutEvent.cpp +) \ No newline at end of file diff --git a/src/dawn/time/event/IntervalEvent.cpp b/src/dawn/time/event/IntervalEvent.cpp new file mode 100644 index 00000000..a7025788 --- /dev/null +++ b/src/dawn/time/event/IntervalEvent.cpp @@ -0,0 +1,41 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "IntervalEvent.hpp" + +using namespace Dawn; + +IntervalEvent::IntervalEvent() : CustomEvent() { + internalData = 0.0f; +} + +enum CustomEventResult IntervalEvent::shouldEmit( + const struct IntervalEventData &listener +) { + if(listener.nextInvoke > internalData) return CustomEventResult::NOTHING; + return CustomEventResult::INVOKE; +} + +struct IntervalEventData IntervalEvent::transformData(const float_t &interval) { + + return { + .interval = interval, + .nextInvoke = internalData + interval + }; +} + +struct IntervalEventData IntervalEvent::transformDataAfterEmit( + const struct IntervalEventData &listenerData +) { + return { + .interval = listenerData.interval, + .nextInvoke = listenerData.nextInvoke + listenerData.interval + }; +} + +void IntervalEvent::tick(const float_t delta) { + internalData += delta; + emit(); +} \ No newline at end of file diff --git a/src/dawn/time/event/IntervalEvent.hpp b/src/dawn/time/event/IntervalEvent.hpp new file mode 100644 index 00000000..a404efe8 --- /dev/null +++ b/src/dawn/time/event/IntervalEvent.hpp @@ -0,0 +1,38 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "event/CustomEvent.hpp" + +namespace Dawn { + struct IntervalEventData { + float_t interval; + float_t nextInvoke; + }; + + class IntervalEvent : public CustomEvent< + float_t, struct IntervalEventData, float_t + > { + protected: + enum CustomEventResult shouldEmit( + const struct IntervalEventData &listener + ) override; + struct IntervalEventData transformData(const float_t &interval) override; + struct IntervalEventData transformDataAfterEmit( + const struct IntervalEventData &listenerData + ) override; + + public: + IntervalEvent(); + + /** + * Ticks this interval event by a time delta amount. Will also emit all + * events that are ready to be emitted. + * + * @param delta Delta time. + */ + void tick(const float_t delta); + }; +} \ No newline at end of file diff --git a/src/dawn/time/event/TimeoutEvent.cpp b/src/dawn/time/event/TimeoutEvent.cpp new file mode 100644 index 00000000..72ce8955 --- /dev/null +++ b/src/dawn/time/event/TimeoutEvent.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "TimeoutEvent.hpp" + +using namespace Dawn; + +TimeoutEvent::TimeoutEvent() : CustomEvent() { + internalData = 0.0f; +} + +enum CustomEventResult TimeoutEvent::shouldEmit(const float_t &listener) { + if(listener > internalData) return CustomEventResult::NOTHING; + return CustomEventResult::INVOKE_AND_REMOVE; +} + +float_t TimeoutEvent::transformData(const float_t &interval) { + return interval; +} + +void TimeoutEvent::tick(const float_t delta) { + internalData += delta; + emit(); +} \ No newline at end of file diff --git a/src/dawn/time/event/TimeoutEvent.hpp b/src/dawn/time/event/TimeoutEvent.hpp new file mode 100644 index 00000000..8386dde5 --- /dev/null +++ b/src/dawn/time/event/TimeoutEvent.hpp @@ -0,0 +1,26 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "event/CustomEvent.hpp" + +namespace Dawn { + class TimeoutEvent : public CustomEvent { + protected: + enum CustomEventResult shouldEmit(const float_t &listener) override; + float_t transformData(const float_t &timeout) override; + + public: + TimeoutEvent(); + + /** + * Ticks this timeout event by a time delta amount. Will also emit all + * events that are ready to be emitted. + * + * @param delta Delta time. + */ + void tick(const float_t delta); + }; +} \ No newline at end of file diff --git a/src/dawnhelloworld/scene/HelloWorldScene.cpp b/src/dawnhelloworld/scene/HelloWorldScene.cpp index 65c87596..3c2a799e 100644 --- a/src/dawnhelloworld/scene/HelloWorldScene.cpp +++ b/src/dawnhelloworld/scene/HelloWorldScene.cpp @@ -23,13 +23,11 @@ void Dawn::helloWorldScene(Scene &s) { cubeMesh->createBuffers(CUBE_VERTICE_COUNT, CUBE_INDICE_COUNT); CubeMesh::buffer(cubeMesh, glm::vec3(-1, -1, -1), glm::vec3(1, 1, 1), 0, 0); - auto texture = s.getGame()->assetManager.get("texture_border"); - auto cubeItem = s.createSceneItem(); auto cubeMeshRenderer = cubeItem->addComponent(); cubeMeshRenderer->mesh = cubeMesh; auto cubeMaterial = cubeItem->addComponent(); - cubeMaterial->setTexture(texture); + cubeMaterial->setTexture(s.getGame()->assetManager.get("texture_border")); addSimpleComponent(cubeItem, [](auto &cmp, auto &events) { events.push_back(cmp.getScene()->onUnpausedUpdate.listen([&](float_t delta) { @@ -37,4 +35,11 @@ void Dawn::helloWorldScene(Scene &s) { item->setLocalRotation(item->getLocalRotation() * glm::quat(glm::vec3(1, 1, 0) * delta)); })); }); + + addSimpleComponent(cubeItem, [](auto &cmp, auto &events) { + events.push_back(cmp.getScene()->onTimeout.listen(5.0f, [&]{ + auto item = cmp.getItem(); + item->setLocalPosition(item->getLocalPosition() + glm::vec3(0, 1, 0)); + })); + }); } \ No newline at end of file