Files
Renderive/Kernel/src/renderive/real_time_data/Double_Buffer_Strategy.hpp
T
2026-08-17 14:28:19 +08:00

193 lines
7.0 KiB
C++

#pragma once
#include <array>
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <mutex>
#include <tuple>
#include <type_traits>
#include <utility>
#include "renderive/base/Atomic_Mutex.hpp"
#include "renderive/base/Concepts.hpp"
#include "renderive/base/observer/Observer.hpp"
// 多域双缓冲,只锁交换
template <class Tag_Type, class Data_Type>
struct Buffered_Data {
using Tag = Tag_Type;
using Data = Data_Type;
};
template <class Entry>
concept Buffered_Data_Entry = requires {
typename Entry::Tag;
typename Entry::Data;
};
template <Buffered_Data_Entry... Entries>
struct Double_Buffer_Layout {};
namespace double_buffer_detail {
template <class Data>
struct Slot {
Data buffers[2]{};
std::uint8_t render_index{};
std::uint8_t cache_index{1};
std::uint64_t cache_update_count{};
std::uint64_t publish_count{};
bool dirty{};
};
template <class... Entries>
struct Unique_Tags;
template <>
struct Unique_Tags<> : std::true_type {};
template <class First, class... Rest>
struct Unique_Tags<First, Rest...> : std::bool_constant<((!std::same_as < typename First::Tag, typename Rest::Tag >) && ...) && Unique_Tags<Rest...>::value> {};
template <class Tag, class... Entries>
struct Entry_Index {
static constexpr std::size_t match_count = (static_cast<std::size_t>(std::same_as<Tag, typename Entries::Tag>) + ...);
static_assert(match_count == 1);
static constexpr std::size_t value = []() consteval {
constexpr bool matches[] = {std::same_as<Tag, typename Entries::Tag>...};
for (std::size_t i = 0; i < sizeof...(Entries); ++i) {
if (matches[i]) {
return i;
}
}
return sizeof...(Entries);
}();
};
template <class Tag, class... Entries>
using Entry_Of = std::tuple_element_t<Entry_Index<Tag, Entries...>::value, std::tuple<Entries...>>;
struct No_Publish_Effect {
void operator()() const noexcept {}
};
}
template <class Layout, Mutex_Type Mutex = Atomic_Spin_Mutex, class Observer = Observer_State<>>
struct Multi_Double_Buffer_Strategy;
template <Mutex_Type Mutex, class Observer, Buffered_Data_Entry... Entries>
struct Multi_Double_Buffer_Strategy<Double_Buffer_Layout<Entries...>, Mutex, Observer> {
using Self = Multi_Double_Buffer_Strategy;
enum class Observation_Event {
cache_updated,
published
};
struct Observation {
using Event = Observation_Event;
Observation_Event event{};
std::size_t buffer_index{};
std::uint64_t time_ns{};
std::uint64_t cache_update_count{};
std::uint64_t publish_count{};
};
static_assert(sizeof...(Entries) > 0);
static_assert(double_buffer_detail::Unique_Tags<Entries...>::value);
static_assert((std::default_initializable < typename Entries::Data > &&
...
)
);
static_assert(Timed_Struct_Observer<Observer, Observation>);
Multi_Double_Buffer_Strategy() = default;
explicit Multi_Double_Buffer_Strategy(With_Observer<Observer> option) : observer(std::move(option.observer)) {}
template <class Tag>
using Data = typename double_buffer_detail::Entry_Of<Tag, Entries...>::Data;
template <class Tag>
static consteval std::size_t buffer_index() {
return double_buffer_detail::Entry_Index<Tag, Entries...>::value;
}
template <class Tag>
Self& write(Data<Tag> value) {
auto& current = slot<Tag>();
current.buffers[current.cache_index] = std::move(value);
current.dirty = true;
++current.cache_update_count;
observer.observe(Observation{Observation_Event::cache_updated, buffer_index<Tag>(), observer.now_ns(), current.cache_update_count, current.publish_count});
return *this;
}
template <class Target> requires requires(Observer& value, Target& target) {
value.bind(target);
}
decltype(auto) bind(Target& target) {
return observer.bind(target);
}
template <class Side_Effect = double_buffer_detail::No_Publish_Effect> requires std::invocable<Side_Effect>
bool publish(Side_Effect&& side_effect = {}) {
std::array<Observation, sizeof...(Entries)> observations{};
std::size_t observation_count{};
bool published{};
{
std::lock_guard lock(mtx);
(publish_entry<Entries>(observations, observation_count, published), ...);
if (published) {
++publish_revision;
}
}
for (std::size_t i = 0; i < observation_count; ++i) {
observer.observe(observations[i]);
}
if (!published) {
return false;
}
std::invoke(std::forward<Side_Effect>(side_effect));
return true;
}
std::uint64_t revision() const noexcept {
return publish_revision;
}
template <class Tag>
std::uint64_t buffer_revision() const noexcept {
return slot<Tag>().publish_count;
}
template <class Tag>
std::uint64_t cache_revision() const noexcept {
return slot<Tag>().cache_update_count;
}
template <class Tag>
const Data<Tag>& cache_buffer_value() const noexcept {
const auto& current = slot<Tag>();
return current.buffers[current.cache_index];
}
template <class Tag> requires std::copy_constructible<Data<Tag>>
[[nodiscard]] Data<Tag> snapshot() const {
std::lock_guard lock(mtx);
return render_buffer_value<Tag>();
}
template <class Tag> requires std::copy_constructible<Data<Tag>>
[[nodiscard]] Data<Tag> latest_snapshot() const {
std::lock_guard lock(mtx);
const auto& current = slot<Tag>();
const auto index = current.dirty ? current.cache_index
: current.render_index;
return current.buffers[index];
}
protected:
template <class Tag>
const Data<Tag>& render_buffer_value() const noexcept {
const auto& current = slot<Tag>();
return current.buffers[current.render_index];
}
private:
template <class Tag>
auto& slot() noexcept {
return std::get < buffer_index<Tag>() > (slots);
}
template <class Tag>
const auto& slot() const noexcept {
return std::get < buffer_index<Tag>() > (slots);
}
template <class Entry>
void publish_entry(std::array<Observation, sizeof...(Entries)>& observations, std::size_t& observation_count, bool& published) {
using Tag = typename Entry::Tag;
auto& current = slot<Tag>();
if (!current.dirty) {
return;
}
std::swap(current.render_index, current.cache_index);
current.dirty = false;
++current.publish_count;
observations[observation_count++] = Observation{Observation_Event::published, buffer_index<Tag>(), observer.now_ns(), current.cache_update_count, current.publish_count};
published = true;
}
Observer observer;
std::tuple<double_buffer_detail::Slot<typename Entries::Data>...> slots;
std::uint64_t publish_revision{};
mutable Mutex mtx;
};