Skip to content

Commit

Permalink
Zephyr: Renderer: ensure that delete tasks cannot be scheduled past t…
Browse files Browse the repository at this point in the history
…he geometry cache's lifetime
  • Loading branch information
fleroviux committed May 4, 2024
1 parent 7fb841e commit f4b9e25
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 10 deletions.
1 change: 1 addition & 0 deletions zephyr/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
62 changes: 62 additions & 0 deletions zephyr/common/include/zephyr/event.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@

#pragma once

#include <zephyr/integer.hpp>
#include <zephyr/panic.hpp>
#include <algorithm>
#include <functional>
#include <limits>
#include <utility>
#include <vector>

namespace zephyr {

template<typename... Args>
class Event {
public:
using SubID = u64;
using Handler = std::function<void(Args...)>;

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>(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<decltype(m_next_id)>::max()) [[unlikely]] {
ZEPHYR_PANIC("Reached the maximum number of event subscriptions. What?");
}
return m_next_id++;
}

SubID m_next_id{0u};
std::vector<Subscription> m_subscription_list{};
};

typedef Event<> VoidEvent;

} // namespace zephyr
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ namespace zephyr {
public:
explicit GeometryCache(std::shared_ptr<RenderBackend> render_backend) : m_render_backend{std::move(render_backend)} {}

~GeometryCache();

// Game Thread API:
void CommitPendingDeleteTaskList();
void UpdateGeometry(const Geometry* geometry);
Expand All @@ -25,6 +27,7 @@ namespace zephyr {
struct GeometryState {
bool uploaded{false};
u64 current_version{};
VoidEvent::SubID destruct_event_subscription;
};

struct UploadTask {
Expand Down
16 changes: 7 additions & 9 deletions zephyr/renderer/include/zephyr/renderer/resource/resource.hpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

#pragma once

#include <zephyr/event.hpp>
#include <zephyr/integer.hpp>
#include <zephyr/panic.hpp>
#include <zephyr/non_copyable.hpp>
#include <zephyr/non_moveable.hpp>
#include <limits>
#include <functional>
#include <vector>

namespace zephyr {
Expand All @@ -19,10 +19,8 @@ namespace zephyr {
*/
class Resource : NonCopyable, NonMoveable {
public:
using DeleteCallback = std::function<void (const Resource*)>;

virtual ~Resource() {
for(const auto& delete_callback : m_delete_cbs) delete_callback(this);
OnBeforeDestruct().Emit();
}

/// @returns the current version of the resource
Expand All @@ -32,22 +30,22 @@ namespace zephyr {

/// Mark the resource as dirty by bumping the version number.
virtual void MarkAsDirty() {
if(m_version == std::numeric_limits<u64>::max()) {
if(m_version == std::numeric_limits<u64>::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?")
}

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<DeleteCallback> 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
9 changes: 8 additions & 1 deletion zephyr/renderer/src/engine/geometry_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
Expand All @@ -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,
Expand Down

0 comments on commit f4b9e25

Please sign in to comment.