193 lines
7.0 KiB
C++
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;
|
|
};
|