182 lines
7.1 KiB
C++
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);
|
|
}
|