diff --git a/zephyr/common/CMakeLists.txt b/zephyr/common/CMakeLists.txt index b254d63..45b3e0e 100644 --- a/zephyr/common/CMakeLists.txt +++ b/zephyr/common/CMakeLists.txt @@ -9,6 +9,7 @@ set(HEADERS set(HEADERS_PUBLIC include/zephyr/bit.hpp + include/zephyr/event.hpp include/zephyr/float.hpp include/zephyr/integer.hpp include/zephyr/literal.hpp diff --git a/zephyr/common/include/zephyr/event.hpp b/zephyr/common/include/zephyr/event.hpp new file mode 100644 index 0000000..63b431a --- /dev/null +++ b/zephyr/common/include/zephyr/event.hpp @@ -0,0 +1,62 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace zephyr { + + template + class Event { + public: + using SubID = u64; + using Handler = std::function; + + struct Subscription { + Subscription(SubID id, Handler handler) : id{id}, handler{handler} {} + SubID id; + Handler handler; + }; + + Event() = default; + Event(const Event& other) = delete; + + Event& operator=(Event const& other) = delete; + + void Emit(Args&&... args) const { + for(const Subscription& subscription : m_subscription_list) { + subscription.handler(std::forward(args)...); + } + } + + SubID Subscribe(Handler handler) { + return m_subscription_list.emplace_back(NextID(), std::move(handler)).id; + } + + void Unsubscribe(SubID id) { + const auto match = std::ranges::find_if(m_subscription_list, [id](const Subscription& subscription) { return subscription.id == id; }); + if(match != m_subscription_list.end()) { + m_subscription_list.erase(match); + } + } + + private: + SubID NextID() { + if(m_next_id == std::numeric_limits::max()) [[unlikely]] { + ZEPHYR_PANIC("Reached the maximum number of event subscriptions. What?"); + } + return m_next_id++; + } + + SubID m_next_id{0u}; + std::vector m_subscription_list{}; + }; + + typedef Event<> VoidEvent; + +} // namespace zephyr \ No newline at end of file diff --git a/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp b/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp index 9983a29..f03248f 100644 --- a/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp +++ b/zephyr/renderer/include/zephyr/renderer/engine/geometry_cache.hpp @@ -13,6 +13,8 @@ namespace zephyr { public: explicit GeometryCache(std::shared_ptr render_backend) : m_render_backend{std::move(render_backend)} {} + ~GeometryCache(); + // Game Thread API: void CommitPendingDeleteTaskList(); void UpdateGeometry(const Geometry* geometry); @@ -25,6 +27,7 @@ namespace zephyr { struct GeometryState { bool uploaded{false}; u64 current_version{}; + VoidEvent::SubID destruct_event_subscription; }; struct UploadTask { diff --git a/zephyr/renderer/include/zephyr/renderer/resource/resource.hpp b/zephyr/renderer/include/zephyr/renderer/resource/resource.hpp index 5052237..4d2424d 100644 --- a/zephyr/renderer/include/zephyr/renderer/resource/resource.hpp +++ b/zephyr/renderer/include/zephyr/renderer/resource/resource.hpp @@ -1,12 +1,12 @@ #pragma once +#include #include #include #include #include #include -#include #include namespace zephyr { @@ -19,10 +19,8 @@ namespace zephyr { */ class Resource : NonCopyable, NonMoveable { public: - using DeleteCallback = std::function; - virtual ~Resource() { - for(const auto& delete_callback : m_delete_cbs) delete_callback(this); + OnBeforeDestruct().Emit(); } /// @returns the current version of the resource @@ -32,7 +30,7 @@ namespace zephyr { /// Mark the resource as dirty by bumping the version number. virtual void MarkAsDirty() { - if(m_version == std::numeric_limits::max()) { + if(m_version == std::numeric_limits::max()) [[unlikely]] { // If this happens something is very messed up and m_version most likely was corrupted. ZEPHYR_PANIC("The 64-bit version counter overflowed. What?") } @@ -40,14 +38,14 @@ namespace zephyr { m_version++; } - /// Register a callback to run when this resource is deleted. - void RegisterDeleteCallback(DeleteCallback callback) const { - m_delete_cbs.push_back(std::move(callback)); + /// @returns an event that is fired right before the resource is destroyed. + VoidEvent& OnBeforeDestruct() const { + return m_on_before_destruct; } private: u64 m_version{}; ///< current 64-bit version number of the resource - mutable std::vector m_delete_cbs{}; ///< list of callbacks to run when this resource is deleted + mutable VoidEvent m_on_before_destruct{}; ///< An event that is fired right before the resource is destroyed. }; } // namespace zephyr diff --git a/zephyr/renderer/src/engine/geometry_cache.cpp b/zephyr/renderer/src/engine/geometry_cache.cpp index 53938dd..12172d4 100644 --- a/zephyr/renderer/src/engine/geometry_cache.cpp +++ b/zephyr/renderer/src/engine/geometry_cache.cpp @@ -6,6 +6,13 @@ namespace zephyr { + GeometryCache::~GeometryCache() { + // Ensure that we do not receive any destruction callbacks from geometries that outlive this geometry cache. + for(const auto& [geometry, state] : m_geometry_state_table) { + geometry->OnBeforeDestruct().Unsubscribe(state.destruct_event_subscription); + } + } + void GeometryCache::CommitPendingDeleteTaskList() { std::swap(m_delete_tasks[0], m_delete_tasks[1]); } @@ -31,7 +38,7 @@ namespace zephyr { }); if(!state.uploaded) { - geometry->RegisterDeleteCallback([this](const Resource* geometry) { + state.destruct_event_subscription = geometry->OnBeforeDestruct().Subscribe([this, geometry]() { /** * This callback is called from the game thread and may be called outside the frame submission phase. * To avoid deleting geometries too early, we have to push the delete task to an intermediate list,