Timeout and Interval events

This commit is contained in:
2023-11-25 23:57:07 -06:00
parent 687c5c0257
commit eda0b8d639
15 changed files with 418 additions and 10 deletions

View File

@ -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<Texture> AssetManager::get<Texture>(
const std::string filename
) {
// TODO: Check for existing loader
auto existing = this->getExisting<TextureLoader>(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<TextureLoader> loader = std::make_shared<TextureLoader>(
filename
);

View File

@ -13,6 +13,42 @@ namespace Dawn {
std::vector<std::shared_ptr<AssetLoader>> pendingAssetLoaders;
std::vector<std::shared_ptr<AssetLoader>> 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<class T>
std::shared_ptr<T> 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<T>(*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.

View File

@ -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<InternalListenerData, std::function<void(InvokeArgs...)>>
> 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<void()> listen(
const ListenerArgument &data,
const std::function<void(InvokeArgs...)> 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();
}
};
}

View File

@ -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<void()> listen(const std::function<void(A...)> listener) {
std::function<void()> listen(
const std::function<void(A...)> 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();

View File

@ -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<SceneItem> Scene::createSceneItem() {
return item;
}
void Scene::removeItem(const std::shared_ptr<SceneItem> item) {
sceneItemsToRemove.push_back(item);
}
Scene::~Scene() {
}

View File

@ -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<Scene> {
private:
std::weak_ptr<Game> game;
const std::function<void(Scene&)> sceneInitializer;
std::vector<std::shared_ptr<SceneItem>> sceneItems;
std::vector<std::shared_ptr<SceneItem>> sceneItemsToRemove;
bool_t paused = false;
bool_t hasInitialized = false;
public:
Event<float_t> onUnpausedUpdate;
Event<float_t> onPausedUpdate;
TimeoutEvent onTimeout;
IntervalEvent onInterval;
/**
* Constructs a scene object.
@ -57,6 +68,14 @@ namespace Dawn {
*/
std::shared_ptr<SceneItem> 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<SceneItem> item);
/**
* Returns a list of scene components that match the given type.
*

View File

@ -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(),

View File

@ -42,6 +42,12 @@ namespace Dawn {
*/
std::shared_ptr<Scene> 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.
*

View File

@ -6,4 +6,7 @@
target_sources(${DAWN_TARGET_NAME}
PRIVATE
ITimeManager.cpp
)
)
# Subdirs
add_subdirectory(event)

View File

@ -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
)

View File

@ -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();
}

View File

@ -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);
};
}

View File

@ -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();
}

View File

@ -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<float_t, float_t, float_t> {
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);
};
}

View File

@ -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>("texture_border");
auto cubeItem = s.createSceneItem();
auto cubeMeshRenderer = cubeItem->addComponent<MeshRenderer>();
cubeMeshRenderer->mesh = cubeMesh;
auto cubeMaterial = cubeItem->addComponent<SimpleTexturedMaterial>();
cubeMaterial->setTexture(texture);
cubeMaterial->setTexture(s.getGame()->assetManager.get<Texture>("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));
}));
});
}