Files
Renderive/Core/architecture/Update_Completion.h
T
2026-08-01 20:09:45 +08:00

182 lines
7.1 KiB
C++

#pragma once
#include <atomic>
#include <cstdint>
#include <memory>
#include <memory_resource>
#include <mutex>
#include <system_error>
#include <type_traits>
#include <utility>
#include <vector>
#include "../base/Memory.h"
#include "../base/Object_Semantics.h"
#include "../base/Task.h"
#include "asio.hpp"
namespace renderive {
/// @brief Lifecycle stage that an asynchronous update ticket can wait for.
enum class Update_Stage : std::uint8_t {
/// @brief Input storage no longer references caller-owned data.
Input_Released,
/// @brief The update has been committed to a version visible to the render pipeline.
Committed,
/// @brief The update has participated in a completed CPU render pass.
Rendered,
/// @brief A frame containing the update has been presented by the frontend.
Presented
};
/// @brief Final result reported when an update wait operation completes.
enum class Update_Outcome : std::uint8_t {
/// @brief The requested lifecycle stage completed normally.
Completed,
/// @brief The update was replaced by newer data before completing all stages.
Superseded,
/// @brief The update was stopped by cancellation, destruction, or shutdown.
Cancelled
};
using Update_Id = std::uint64_t;
class Update_Waiter : Move_Only {
public:
Update_Waiter() noexcept = default;
template <typename Handler>
explicit Update_Waiter(Handler&& handler) {
using Model = Waiter_Model<std::decay_t<Handler>>;
resource = memory_resource(Memory_Domain::Update_Completion);
std::pmr::polymorphic_allocator<Model> allocator(resource);
object = allocator.allocate(1);
try {
std::construct_at(static_cast<Model*>(object), std::forward<Handler>(handler));
}
catch (...) {
allocator.deallocate(static_cast<Model*>(object), 1);
object = nullptr;
resource = nullptr;
throw;
}
complete_function = &complete_model<Model>;
destroy_function = &destroy_model<Model>;
}
Update_Waiter(Update_Waiter&& other) noexcept {
move_from(std::move(other));
}
Update_Waiter& operator=(Update_Waiter&& other) noexcept {
if (this == &other)
return *this;
reset();
move_from(std::move(other));
return *this;
}
~Update_Waiter() {
reset();
}
void complete(std::error_code error, Update_Outcome outcome) {
if (complete_function)
complete_function(object, error, outcome);
}
explicit operator bool() const noexcept {
return object != nullptr;
}
private:
template <typename Handler>
struct Waiter_Model {
using Executor = asio::associated_executor_t<Handler, asio::system_executor>;
template <typename Source>
explicit Waiter_Model(Source&& value) : handler(std::forward<Source>(value)), executor(asio::get_associated_executor(handler, asio::system_executor())) {}
Handler handler;
Executor executor;
};
template <typename Model>
static void complete_model(void* object, std::error_code error, Update_Outcome outcome) {
auto& model = *static_cast<Model*>(object);
auto handler = std::move(model.handler);
auto executor = model.executor;
asio::post(executor, [handler = std::move(handler), error, outcome]() mutable {
handler(error, outcome);
});
}
template <typename Model>
static void destroy_model(void* object, std::pmr::memory_resource* resource) {
std::pmr::polymorphic_allocator<Model> allocator(resource);
std::destroy_at(static_cast<Model*>(object));
allocator.deallocate(static_cast<Model*>(object), 1);
}
void move_from(Update_Waiter&& other) noexcept {
object = std::exchange(other.object, nullptr);
resource = std::exchange(other.resource, nullptr);
complete_function = std::exchange(other.complete_function, nullptr);
destroy_function = std::exchange(other.destroy_function, nullptr);
}
void reset() noexcept {
if (!object)
return;
destroy_function(object, resource);
object = nullptr;
resource = nullptr;
complete_function = nullptr;
destroy_function = nullptr;
}
void* object{};
std::pmr::memory_resource* resource{};
void (*complete_function)(void*, std::error_code, Update_Outcome){};
void (*destroy_function)(void*, std::pmr::memory_resource*){};
};
struct LIB_DECL Update_State {
explicit Update_State(Update_Id id);
Update_Id id() const noexcept;
bool is_complete() const noexcept;
bool stage_complete(Update_Stage stage) const noexcept;
void register_waiter(Update_Stage stage, Update_Waiter waiter);
void complete_until(Update_Stage stage, std::error_code error = {}, Update_Outcome outcome = Update_Outcome::Completed);
void complete_all(std::error_code error = {}, Update_Outcome outcome = Update_Outcome::Completed);
private:
struct Stage_Record {
explicit Stage_Record(std::pmr::memory_resource* resource);
bool completed = false;
std::error_code error;
Update_Outcome outcome = Update_Outcome::Cancelled;
std::pmr::vector<Update_Waiter> waiters;
};
Stage_Record& slot(Update_Stage stage);
const Stage_Record& slot(Update_Stage stage) const;
void drain_completed_waiters(Stage_Record& record, std::pmr::vector<Update_Waiter>& waiters, std::error_code error, Update_Outcome outcome);
Update_Id update_id{};
mutable std::mutex mutex;
Stage_Record input_released;
Stage_Record committed;
Stage_Record rendered;
Stage_Record presented;
std::atomic_bool terminal{};
};
class Update_Ticket {
public:
Update_Ticket() noexcept = default;
explicit Update_Ticket(std::shared_ptr<Update_State> state) : state_(std::move(state)) {}
Update_Id id() const noexcept {
return state_ ? state_->id() : 0;
}
explicit operator bool() const noexcept {
return static_cast<bool>(state_);
}
bool is_complete() const noexcept {
return state_ && state_->is_complete();
}
template <class CompletionToken>
auto async_wait(Update_Stage stage, CompletionToken&& token) {
return asio::async_initiate<CompletionToken, void(std::error_code, Update_Outcome)>(
[state = state_, stage](auto handler) mutable {
if (!state) {
Update_Waiter waiter(std::move(handler));
waiter.complete(std::make_error_code(std::errc::operation_canceled), Update_Outcome::Cancelled);
return;
}
state->register_waiter(stage, Update_Waiter(std::move(handler)));
}, token);
}
private:
std::shared_ptr<Update_State> state_;
};
LIB_DECL std::shared_ptr<Update_State> make_update_state();
LIB_DECL void complete_update_states(std::pmr::vector<std::shared_ptr<Update_State>>& states, Update_Stage stage, std::error_code error = {}, Update_Outcome outcome = Update_Outcome::Completed);
LIB_DECL void cancel_update_states(std::pmr::vector<std::shared_ptr<Update_State>>& states);
LIB_DECL void supersede_update_states(std::pmr::vector<std::shared_ptr<Update_State>>& states);
}