删除realtime 的一个组件
This commit is contained in:
+18
-68
@@ -3,6 +3,7 @@
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
@@ -11,7 +12,6 @@
|
||||
#include "renderive/base/Atomic_Mutex.hpp"
|
||||
#include "renderive/base/Concepts.hpp"
|
||||
#include "renderive/base/observer/Observer.hpp"
|
||||
#include "base/State_Strategy_Base.hpp"
|
||||
template <class Tag_Type, class Data_Type>
|
||||
struct Buffered_Data {
|
||||
using Tag = Tag_Type;
|
||||
@@ -56,71 +56,14 @@ struct Entry_Index {
|
||||
};
|
||||
template <class Tag, class... Entries>
|
||||
using Entry_Of = std::tuple_element_t<Entry_Index<Tag, Entries...>::value, std::tuple<Entries...>>;
|
||||
}
|
||||
template <Derivable_Type That, class Data_Type, Mutex_Type Mutex = Atomic_Spin_Mutex, class Observer = Observer_State<>>
|
||||
struct Double_Buffer_Strategy : That, State_Strategy_Base {
|
||||
using Self = Double_Buffer_Strategy;
|
||||
using Data = Data_Type;
|
||||
enum class Observation_Event {
|
||||
cache_updated,
|
||||
published
|
||||
};
|
||||
struct Observation {
|
||||
Observation_Event event{};
|
||||
std::uint64_t time_ns{};
|
||||
std::uint64_t cache_update_count{};
|
||||
std::uint64_t publish_count{};
|
||||
};
|
||||
static_assert(Timed_Struct_Observer<Observer, Observation>);
|
||||
Double_Buffer_Strategy() requires std::default_initializable<That> && std::default_initializable<Data> : That() {}
|
||||
explicit Double_Buffer_Strategy(With_Observer<Observer> option) requires std::default_initializable<That> && std::default_initializable<Data> : That(), observer(std::move(option.observer)) {}
|
||||
template <class... Args>
|
||||
explicit Double_Buffer_Strategy(std::in_place_t, Args&&... args) requires std::default_initializable<Data> : That(std::forward<Args>(args)...) {}
|
||||
template <class... Args>
|
||||
Double_Buffer_Strategy(std::in_place_t, With_Observer<Observer> option, Args&&... args) requires std::default_initializable<Data> : That(std::forward<Args>(args)...), observer(std::move(option.observer)) {}
|
||||
Self& write(Data value) {
|
||||
std::optional<Observation> observation;
|
||||
{
|
||||
std::lock_guard lock(mtx);
|
||||
slot.buffers[slot.cache_index] = std::move(value);
|
||||
slot.dirty = true;
|
||||
++slot.cache_update_count;
|
||||
observation.emplace(Observation_Event::cache_updated, observer.now_ns(), slot.cache_update_count, slot.publish_count);
|
||||
}
|
||||
observer.observe(*observation);
|
||||
return *this;
|
||||
}
|
||||
void publish() override {
|
||||
std::optional<Observation> observation;
|
||||
{
|
||||
std::lock_guard lock(mtx);
|
||||
if(!slot.dirty) {
|
||||
return;
|
||||
}
|
||||
std::swap(slot.render_index, slot.cache_index);
|
||||
slot.dirty = false;
|
||||
++slot.publish_count;
|
||||
observation.emplace(Observation_Event::published, observer.now_ns(), slot.cache_update_count, slot.publish_count);
|
||||
}
|
||||
observer.observe(*observation);
|
||||
}
|
||||
std::uint64_t state_revision() const override {
|
||||
std::lock_guard lock(mtx);
|
||||
return slot.publish_count;
|
||||
}
|
||||
protected:
|
||||
const Data& render_buffer_value() const noexcept {
|
||||
return slot.buffers[slot.render_index];
|
||||
}
|
||||
private:
|
||||
Observer observer;
|
||||
double_buffer_detail::Slot<Data> slot;
|
||||
mutable Mutex mtx;
|
||||
struct No_Publish_Effect {
|
||||
void operator()() const noexcept {}
|
||||
};
|
||||
}
|
||||
template <Derivable_Type That, class Layout, Mutex_Type Mutex = Atomic_Spin_Mutex, class Observer = Observer_State<>>
|
||||
struct Multi_Double_Buffer_Strategy;
|
||||
template <Derivable_Type That, Mutex_Type Mutex, class Observer, Buffered_Data_Entry... Entries>
|
||||
struct Multi_Double_Buffer_Strategy<That, Double_Buffer_Layout<Entries...>, Mutex, Observer> : That, State_Strategy_Base {
|
||||
struct Multi_Double_Buffer_Strategy<That, Double_Buffer_Layout<Entries...>, Mutex, Observer> : That {
|
||||
using Self = Multi_Double_Buffer_Strategy;
|
||||
enum class Observation_Event {
|
||||
cache_updated,
|
||||
@@ -163,24 +106,31 @@ struct Multi_Double_Buffer_Strategy<That, Double_Buffer_Layout<Entries...>, Mute
|
||||
observer.observe(*observation);
|
||||
return *this;
|
||||
}
|
||||
void publish() override {
|
||||
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);
|
||||
bool published{};
|
||||
(publish_entry<Entries>(observations, observation_count, published), ...);
|
||||
if(published) {
|
||||
++revision;
|
||||
++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 state_revision() const override {
|
||||
std::uint64_t revision() const {
|
||||
std::lock_guard lock(mtx);
|
||||
return revision;
|
||||
return publish_revision;
|
||||
}
|
||||
template <class Tag>
|
||||
std::uint64_t buffer_revision() const {
|
||||
@@ -217,6 +167,6 @@ private:
|
||||
}
|
||||
Observer observer;
|
||||
std::tuple<double_buffer_detail::Slot<typename Entries::Data>...> slots;
|
||||
std::uint64_t revision{};
|
||||
std::uint64_t publish_revision{};
|
||||
mutable Mutex mtx;
|
||||
};
|
||||
@@ -6,11 +6,53 @@
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
#include "renderive/real_time_data/Double_Buffer_Strategy.hpp"
|
||||
#include "renderive/real_time_data/Real_Time_Data.hpp"
|
||||
#include "renderive/renderable/Renderable.hpp"
|
||||
#include "renderive/renderable/Renderable_Test_Harness.hpp"
|
||||
#include "renderive/scene/Scene.hpp"
|
||||
#include "renderive/scene/Scene_Test_Helpers.hpp"
|
||||
#include "renderive/state/base/State_Strategy_Base.hpp"
|
||||
|
||||
namespace {
|
||||
struct Double_Buffer_Test_Base {};
|
||||
struct Double_Buffer_Test_Tag {};
|
||||
using Double_Buffer_Test_Layout =
|
||||
Double_Buffer_Layout<Buffered_Data<Double_Buffer_Test_Tag,
|
||||
std::vector<int>>>;
|
||||
class Double_Buffer_Test_Strategy final
|
||||
: public Multi_Double_Buffer_Strategy<Double_Buffer_Test_Base,
|
||||
Double_Buffer_Test_Layout,
|
||||
std::mutex> {
|
||||
using Base = Multi_Double_Buffer_Strategy<Double_Buffer_Test_Base,
|
||||
Double_Buffer_Test_Layout,
|
||||
std::mutex>;
|
||||
public:
|
||||
using Base::buffer_revision;
|
||||
using Base::publish;
|
||||
using Base::revision;
|
||||
using Base::write;
|
||||
[[nodiscard]] const std::vector<int>& published() const noexcept {
|
||||
return Base::render_buffer_value<Double_Buffer_Test_Tag>();
|
||||
}
|
||||
};
|
||||
static_assert(!std::derived_from<Double_Buffer_Test_Strategy,
|
||||
State_Strategy_Base>);
|
||||
}
|
||||
|
||||
TEST(double_buffer_strategy_test,
|
||||
single_entry_layout_publishes_with_external_side_effect) {
|
||||
Double_Buffer_Test_Strategy strategy;
|
||||
int side_effect_count{};
|
||||
strategy.write<Double_Buffer_Test_Tag>({1, 2, 3});
|
||||
EXPECT_TRUE(strategy.publish([&] { ++side_effect_count; }));
|
||||
EXPECT_EQ(strategy.published(), (std::vector<int>{1, 2, 3}));
|
||||
EXPECT_EQ(strategy.revision(), 1U);
|
||||
EXPECT_EQ(strategy.buffer_revision<Double_Buffer_Test_Tag>(), 1U);
|
||||
EXPECT_EQ(side_effect_count, 1);
|
||||
EXPECT_FALSE(strategy.publish([&] { ++side_effect_count; }));
|
||||
EXPECT_EQ(side_effect_count, 1);
|
||||
}
|
||||
struct Real_Time_Data_Test_Time_Source {
|
||||
std::shared_ptr<std::atomic<std::uint64_t>> time_ns{std::make_shared<std::atomic<std::uint64_t>>()};
|
||||
std::uint64_t now_ns() const noexcept {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,23 +3,12 @@
|
||||
#include <renderive/base/observer/Observer.hpp>
|
||||
#include <renderive/real_time_data/Frame_Strategy_Observer.hpp>
|
||||
#include <renderive/real_time_data/History_Real_Time_Data.hpp>
|
||||
#include <renderive/real_time_data/Latest_Real_Time_Data.hpp>
|
||||
#include <mutex>
|
||||
namespace renderive::detail {
|
||||
using Plottable_Real_Time_Data_Observer = Observer_State<Frame_Strategy_Real_Time_Data_Observer>;
|
||||
inline Plottable_Real_Time_Data_Observer observe_real_time_data(Renderable& renderable) {
|
||||
return Plottable_Real_Time_Data_Observer(Frame_Strategy_Real_Time_Data_Observer(renderable));
|
||||
}
|
||||
template <class Value>
|
||||
class Plottable_Latest_Real_Time_Data
|
||||
: public Latest_Real_Time_Data<Value, std::mutex, Plottable_Real_Time_Data_Observer> {
|
||||
using Base = Latest_Real_Time_Data<Value, std::mutex, Plottable_Real_Time_Data_Observer>;
|
||||
public:
|
||||
explicit Plottable_Latest_Real_Time_Data(Renderable& owner)
|
||||
: Base(observe_real_time_data(owner)), binding_(this->bind_renderable(owner)) {}
|
||||
private:
|
||||
Real_Time_Data_Binding binding_;
|
||||
};
|
||||
template <class Value, class Container>
|
||||
class Plottable_History_Real_Time_Data
|
||||
: public History_Real_Time_Data<Value, Container, std::mutex, Plottable_Real_Time_Data_Observer> {
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
#include "Spectrum.h"
|
||||
#include "Curve_Sampling.h"
|
||||
#include "Plottable_Real_Time_Data.h"
|
||||
#include "../render/Blend2D_Cache.h"
|
||||
#include "../renderable/Render_Partition.h"
|
||||
#include "../renderable/Renderable_p.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iomanip>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
namespace renderive::detail {
|
||||
namespace {
|
||||
struct Spectrum_Frame {
|
||||
std::vector<double> samples;
|
||||
std::vector<double> maxima;
|
||||
std::vector<double> minima;
|
||||
};
|
||||
struct Spectrum_Interaction_Base {};
|
||||
struct Spectrum_Interaction {
|
||||
std::vector<double> markers;
|
||||
@@ -49,7 +42,7 @@ struct Spectrum_Extreme_Buffer {
|
||||
};
|
||||
struct Spectrum_Prepare_Buffer {
|
||||
Spectrum_Properties properties;
|
||||
Spectrum_Frame frame;
|
||||
const Spectrum_Frame* frame{};
|
||||
Spectrum_Interaction interaction;
|
||||
Axis_Transform frequency_axis;
|
||||
Axis_Transform power_axis;
|
||||
@@ -145,53 +138,62 @@ struct Spectrum_Control::Impl : Renderable::Impl {
|
||||
power_axis(std::move(power)) {}
|
||||
renderive_Owner<Frequency_Axis> frequency_axis;
|
||||
renderive_Owner<Axis> power_axis;
|
||||
std::optional<Plottable_Latest_Real_Time_Data<Spectrum_Frame>> frame;
|
||||
Spectrum_Interaction_State interaction;
|
||||
std::mutex frame_update_mutex;
|
||||
Adaptive_Render_Partitioner partitioner;
|
||||
Spectrum_Prepare_Buffer prepare_buffer;
|
||||
};
|
||||
Spectrum_Control::Spectrum_Control(const Spectrum_Properties& properties, renderive_Owner<Frequency_Axis> frequency_axis, renderive_Owner<Axis> power_axis)
|
||||
: Plottable_State(properties, std::make_unique<Impl>(
|
||||
std::move(frequency_axis), std::move(power_axis))) {
|
||||
d_func<Impl>().frame.emplace(*this);
|
||||
}
|
||||
: Spectrum_Real_Time_Data_Strategy(
|
||||
std::in_place,
|
||||
properties,
|
||||
std::make_unique<Impl>(std::move(frequency_axis),
|
||||
std::move(power_axis))) {}
|
||||
Spectrum_Control::~Spectrum_Control() = default;
|
||||
void Spectrum_Control::update_samples(std::span<const double> values) {
|
||||
if(get<&Spectrum_Properties::frequency_point_size>() <= 0)
|
||||
set<&Spectrum_Properties::frequency_point_size>(static_cast<int>(values.size()));
|
||||
std::lock_guard lock(d_func<Impl>().frame_update_mutex);
|
||||
Spectrum_Frame frame = d_func<Impl>().frame->snapshot().value_or(Spectrum_Frame{});
|
||||
frame.samples.assign(values.begin(), values.end());
|
||||
if(frame.maxima.size() != values.size())
|
||||
frame.maxima.assign(values.begin(), values.end());
|
||||
const auto& published = render_buffer_value<Spectrum_Frame_Tag>();
|
||||
const bool shape_changed = published.samples.size() != values.size();
|
||||
Spectrum_Frame next;
|
||||
next.samples.assign(values.begin(), values.end());
|
||||
if (published.maxima.size() != values.size())
|
||||
next.maxima = next.samples;
|
||||
else {
|
||||
next.maxima.resize(values.size());
|
||||
for (std::size_t index = 0; index < values.size(); ++index)
|
||||
next.maxima[index] = std::max(published.maxima[index], values[index]);
|
||||
}
|
||||
if (published.minima.size() != values.size())
|
||||
next.minima = next.samples;
|
||||
else {
|
||||
next.minima.resize(values.size());
|
||||
for (std::size_t index = 0; index < values.size(); ++index)
|
||||
next.minima[index] = std::min(published.minima[index], values[index]);
|
||||
}
|
||||
Spectrum_Real_Time_Data_Strategy::write<Spectrum_Frame_Tag>(std::move(next));
|
||||
if (shape_changed)
|
||||
render_graph_changed();
|
||||
else
|
||||
for(std::size_t index = 0; index < values.size(); ++index)
|
||||
frame.maxima[index] = std::max(frame.maxima[index], values[index]);
|
||||
if(frame.minima.size() != values.size())
|
||||
frame.minima.assign(values.begin(), values.end());
|
||||
else
|
||||
for(std::size_t index = 0; index < values.size(); ++index)
|
||||
frame.minima[index] = std::min(frame.minima[index], values[index]);
|
||||
d_func<Impl>().frame->update(std::move(frame));
|
||||
render_graph_changed();
|
||||
changed();
|
||||
}
|
||||
void Spectrum_Control::update_samples(std::pmr::vector<double>&& values) {
|
||||
update_samples(std::span<const double>(values.data(), values.size()));
|
||||
}
|
||||
std::size_t Spectrum_Control::sample_count() const {
|
||||
const auto frame = d_func<Impl>().frame->snapshot();
|
||||
return frame ? frame->samples.size() : 0;
|
||||
return render_buffer_value<Spectrum_Frame_Tag>().samples.size();
|
||||
}
|
||||
std::size_t Spectrum_Control::rendered_point_count() const {
|
||||
const auto state = properties();
|
||||
const auto frame = d_func<Impl>().frame->snapshot();
|
||||
return frame ? curve_points(frame->samples, state.frequency_range, d_func<Impl>().frequency_axis->transform(), d_func<Impl>().power_axis->transform(), state.visible_range_only, state.interpolation_mode).size() : 0;
|
||||
return curve_points(render_buffer_value<Spectrum_Frame_Tag>().samples,
|
||||
state.frequency_range,
|
||||
d_func<Impl>().frequency_axis->transform(),
|
||||
d_func<Impl>().power_axis->transform(),
|
||||
state.visible_range_only, state.interpolation_mode).size();
|
||||
}
|
||||
double Spectrum_Control::power_at(double frequency, bool& ok) const {
|
||||
const auto state = properties();
|
||||
const auto frame = d_func<Impl>().frame->snapshot();
|
||||
return frame ? spectrum_power_at(state, *frame, frequency, ok) : (ok = false, 0.0);
|
||||
return spectrum_power_at(
|
||||
state, render_buffer_value<Spectrum_Frame_Tag>(), frequency, ok);
|
||||
}
|
||||
void Spectrum_Control::add_custom_marker(double frequency) {
|
||||
add_custom_line_marker(frequency);
|
||||
@@ -294,15 +296,20 @@ void Spectrum_Control::handle_event(const Event& event) {
|
||||
}
|
||||
void Spectrum_Control::publish() {
|
||||
publish_properties();
|
||||
Spectrum_Real_Time_Data_Strategy::publish();
|
||||
d_func<Impl>().interaction.publish();
|
||||
}
|
||||
|
||||
std::uint64_t Spectrum_Control::state_revision() const {
|
||||
return Plottable_State<Spectrum_Properties>::Base::state_revision() +
|
||||
Spectrum_Real_Time_Data_Strategy::revision();
|
||||
}
|
||||
|
||||
void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) {
|
||||
const auto view = d_func().render_state_view();
|
||||
const auto state = properties();
|
||||
const auto& published_frame = view.get(*d_func<Impl>().frame);
|
||||
const std::size_t work_size = published_frame && published_frame->samples.size() > 1
|
||||
? published_frame->samples.size() - 1
|
||||
const auto& published_frame = render_buffer_value<Spectrum_Frame_Tag>();
|
||||
const std::size_t work_size = published_frame.samples.size() > 1
|
||||
? published_frame.samples.size() - 1
|
||||
: 0;
|
||||
const int partition_count = d_func<Impl>().partitioner.graph_partition_count(
|
||||
state.partition_mode, state.partition_count.get(),
|
||||
@@ -313,7 +320,7 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) {
|
||||
prepare_render_frame(context.frame.render_state, partition_count);
|
||||
if (context.metrics) {
|
||||
context.metrics->set(Node_Metric_Kind::input_count,
|
||||
d_func<Impl>().prepare_buffer.frame.samples.size());
|
||||
d_func<Impl>().prepare_buffer.frame->samples.size());
|
||||
context.metrics->set(Node_Metric_Kind::chunk_size,
|
||||
d_func<Impl>().prepare_buffer.work_size /
|
||||
std::max(1, d_func<Impl>().prepare_buffer.active_partitions));
|
||||
@@ -338,11 +345,10 @@ void Spectrum_Control::build_prepare_graph(Renderable_Graph_Builder& builder) {
|
||||
}
|
||||
|
||||
void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) {
|
||||
const auto view = d_func().render_state_view();
|
||||
const auto state = properties();
|
||||
const auto& published_frame = view.get(*d_func<Impl>().frame);
|
||||
const std::size_t work_size = published_frame && published_frame->samples.size() > 1
|
||||
? published_frame->samples.size() - 1
|
||||
const auto& published_frame = render_buffer_value<Spectrum_Frame_Tag>();
|
||||
const std::size_t work_size = published_frame.samples.size() > 1
|
||||
? published_frame.samples.size() - 1
|
||||
: 0;
|
||||
const int partition_count = d_func<Impl>().partitioner.graph_partition_count(
|
||||
state.partition_mode, state.partition_count.get(),
|
||||
@@ -369,18 +375,17 @@ void Spectrum_Control::build_paint_graph(Renderable_Graph_Builder& builder) {
|
||||
|
||||
void Spectrum_Control::prepare_render_frame(const Render_State_View& view,
|
||||
int graph_partition_count) {
|
||||
const auto& published_frame = view.get(*d_func<Impl>().frame);
|
||||
const auto& published_frame = render_buffer_value<Spectrum_Frame_Tag>();
|
||||
auto& output = d_func<Impl>().prepare_buffer;
|
||||
output = {};
|
||||
output.properties = render_properties(view);
|
||||
if (published_frame)
|
||||
output.frame = *published_frame;
|
||||
output.frame = &published_frame;
|
||||
output.interaction = view.get(d_func<Impl>().interaction);
|
||||
output.frequency_axis = d_func<Impl>().frequency_axis->transform(view);
|
||||
output.power_axis = d_func<Impl>().power_axis->transform(view);
|
||||
output.valid = axes_are_orthogonal(output.frequency_axis, output.power_axis);
|
||||
output.work_size = published_frame && published_frame->samples.size() > 1
|
||||
? published_frame->samples.size() - 1
|
||||
output.work_size = published_frame.samples.size() > 1
|
||||
? published_frame.samples.size() - 1
|
||||
: 0;
|
||||
output.active_partitions = d_func<Impl>().partitioner.begin(graph_partition_count,
|
||||
output.work_size);
|
||||
@@ -416,16 +421,16 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view,
|
||||
? Spectrum_Marker_Style::selected
|
||||
: Spectrum_Marker_Style::marker});
|
||||
}
|
||||
if (!output.frame.samples.empty() &&
|
||||
if (!output.frame->samples.empty() &&
|
||||
(state.max_marker_visible || state.use_min_marker)) {
|
||||
const auto prepare_extreme = [&](bool maximum) {
|
||||
const auto iterator = maximum
|
||||
? std::max_element(output.frame.samples.begin(), output.frame.samples.end())
|
||||
: std::min_element(output.frame.samples.begin(), output.frame.samples.end());
|
||||
? std::max_element(output.frame->samples.begin(), output.frame->samples.end())
|
||||
: std::min_element(output.frame->samples.begin(), output.frame->samples.end());
|
||||
const std::size_t index = static_cast<std::size_t>(
|
||||
std::distance(output.frame.samples.begin(), iterator));
|
||||
const double denominator = output.frame.samples.size() > 1
|
||||
? static_cast<double>(output.frame.samples.size() - 1)
|
||||
std::distance(output.frame->samples.begin(), iterator));
|
||||
const double denominator = output.frame->samples.size() > 1
|
||||
? static_cast<double>(output.frame->samples.size() - 1)
|
||||
: 1.0;
|
||||
const double frequency = state.frequency_range.origin +
|
||||
state.frequency_range.length() * static_cast<double>(index) / denominator;
|
||||
@@ -443,7 +448,7 @@ void Spectrum_Control::prepare_render_frame(const Render_State_View& view,
|
||||
const double frequency = output.frequency_axis.point_to_coord(
|
||||
output.interaction.tooltip.position);
|
||||
bool ok{};
|
||||
const double power = spectrum_power_at(state, output.frame, frequency, ok);
|
||||
const double power = spectrum_power_at(state, *output.frame, frequency, ok);
|
||||
if (ok) {
|
||||
std::ostringstream text;
|
||||
text << std::fixed << std::setprecision(2) << frequency << " Hz " << power;
|
||||
@@ -460,7 +465,7 @@ void Spectrum_Control::prepare_partition(int partition_index) {
|
||||
if (!output.valid || partition_index >= output.active_partitions)
|
||||
return;
|
||||
const auto& state = output.properties;
|
||||
const auto& frame = output.frame;
|
||||
const auto& frame = *output.frame;
|
||||
const auto current = curve_partition(frame.samples, state.frequency_range,
|
||||
partition_index, output.active_partitions);
|
||||
if (current.values.empty())
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "Plottable.h"
|
||||
#include "../axis/Axis.h"
|
||||
#include "../renderable/Render_Partition.h"
|
||||
#include <renderive/real_time_data/Double_Buffer_Strategy.hpp>
|
||||
#include <renderive/renderable/Render_Frame_Completion.hpp>
|
||||
#include <memory_resource>
|
||||
#include <span>
|
||||
@@ -34,7 +35,32 @@ struct Spectrum_Properties : Hover_Tooltip_Properties {
|
||||
Brush sweep_region_brush{Color{255, 255, 0, 100}, Brush_Style::Solid};
|
||||
};
|
||||
namespace detail {
|
||||
class LIB_DECL Spectrum_Control : public Plottable_State<Spectrum_Properties>,
|
||||
struct Spectrum_Frame {
|
||||
std::vector<double> samples;
|
||||
std::vector<double> maxima;
|
||||
std::vector<double> minima;
|
||||
};
|
||||
struct Spectrum_Frame_Tag {};
|
||||
using Spectrum_Buffer_Layout =
|
||||
::Double_Buffer_Layout<
|
||||
::Buffered_Data<Spectrum_Frame_Tag, Spectrum_Frame>>;
|
||||
class Spectrum_Real_Time_Data_Strategy
|
||||
: public ::Multi_Double_Buffer_Strategy<
|
||||
Plottable_State<Spectrum_Properties>, Spectrum_Buffer_Layout,
|
||||
std::mutex> {
|
||||
using Base =
|
||||
::Multi_Double_Buffer_Strategy<
|
||||
Plottable_State<Spectrum_Properties>, Spectrum_Buffer_Layout,
|
||||
std::mutex>;
|
||||
protected:
|
||||
using Base::Base;
|
||||
using Base::publish;
|
||||
using Base::render_buffer_value;
|
||||
using Base::revision;
|
||||
using Base::write;
|
||||
};
|
||||
|
||||
class LIB_DECL Spectrum_Control : public Spectrum_Real_Time_Data_Strategy,
|
||||
public Event_Handler,
|
||||
public ::Render_Frame_Completion {
|
||||
public:
|
||||
@@ -79,6 +105,7 @@ private:
|
||||
void paint_overlay(Painter& painter, const Render_State_View& state);
|
||||
void render_frame_completed(std::uint64_t target_interval_ns) override;
|
||||
void publish() override;
|
||||
[[nodiscard]] std::uint64_t state_revision() const override;
|
||||
};
|
||||
}
|
||||
using Spectrum = detail::Attach_Plottable<detail::Spectrum_Control, Spectrum_Properties>;
|
||||
|
||||
@@ -8,8 +8,8 @@ Renderive 只负责逻辑状态收集、批量数据收集、Kernel 帧策略调
|
||||
|
||||
```text
|
||||
Point_State --Kernel Double_State_Strategy--+
|
||||
+--帧快照--> Kernel Frame Strategy
|
||||
Point_Data --Kernel Latest_Real_Time_Data----+ |
|
||||
+--帧边界--> Point_Frame_Strategy
|
||||
Point payload --Kernel Multi_Double_Buffer_Strategy-----------+
|
||||
Kernel Event --Input_Collector---------------+ v
|
||||
Render_Domain 单线程
|
||||
|
|
||||
@@ -38,9 +38,9 @@ Kernel Event --Input_Collector---------------+ v
|
||||
|
||||
## 已完成的 Point 封装
|
||||
|
||||
- `Point_Visual`:PImpl;`Point_State` 只保存样式、变换、可见性和深度测试。
|
||||
- `Point_Data`:直接使用 Kernel `Latest_Real_Time_Data<std::vector<Point>>`,不在 visual 中复制大数据。
|
||||
- `Point_Scene`:直接组合 Kernel Manual、Low Latency、Playback 三种策略;统一发布 `Frame_Status` 查询快照。
|
||||
- `Point_Visual`:直接组合状态与实时数据策略;`Point_State` 只保存样式、变换、可见性和深度测试。
|
||||
- `Point_Visual`:状态使用 Kernel `Double_State_Strategy`,不可变点集使用单 Entry 的 `Multi_Double_Buffer_Strategy` 在帧边界发布。
|
||||
- `Point_Scene`:只使用满足 Kernel frame-control concept 的 3D `Point_Frame_Strategy`;统一发布 `Frame_Status` 查询。
|
||||
- `Datoviz_Point_Backend`:所有 Datoviz/Vulkan 资源均限制在单一 `Render_Domain`;批量属性使用 `dvz_visual_set_data_many()` 原子提交。
|
||||
- 输入:Kernel pointer/wheel/key 事件映射到 Datoviz router/arcball;Web 保留事件的真实派生类型,不经过 `Event` 切片。
|
||||
- 输出:外部 RGBA8 target 渲染、同步回读并发布不可变 `Pixel_Frame`。
|
||||
@@ -70,7 +70,7 @@ Datoviz 生产源码和上游测试已经迁入;以下是尚未实现的 Rende
|
||||
| 状态 | 唯一权威来源 | 读取方式 |
|
||||
| --- | --- | --- |
|
||||
| Point 样式/变换/可见性 | `Point_Visual` 的 Kernel 双状态策略 | 帧边界发布快照 |
|
||||
| Point 大批量数据 | `Point_Data` | revision + snapshot |
|
||||
| Point 大批量数据 | `Point_Visual` 的 Kernel 双缓冲 | 不可变已发布点集 |
|
||||
| viewport/clear color | `Scene_State_Buffer` | 帧边界发布快照 |
|
||||
| 输入事件 | `Input_Collector` 队列 | 每帧 drain |
|
||||
| 帧生命周期/计数 | Kernel frame strategy | `Frame_Status` 即时查询 |
|
||||
|
||||
@@ -19,13 +19,11 @@ std::vector<Point> point_demo_data(float phase_radians) {
|
||||
}
|
||||
|
||||
Point_Demo make_point_demo(Scene_Options options) {
|
||||
auto data = std::make_shared<Point_Data>();
|
||||
data->update(point_demo_data());
|
||||
Point_State state;
|
||||
state.style = {{12, 16, 24, 255}, 2.0F, Point_Aspect::Outline};
|
||||
auto visual = std::make_shared<Point_Visual>(data, state);
|
||||
auto scene = std::make_unique<Point_Scene>(options, std::move(visual));
|
||||
return {std::move(data), std::move(scene)};
|
||||
auto visual = std::make_shared<Point_Visual>(point_demo_data(), state);
|
||||
auto scene = std::make_unique<Point_Scene>(options, visual);
|
||||
return {std::move(visual), std::move(scene)};
|
||||
}
|
||||
|
||||
} // namespace renderive::render_3d
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
namespace renderive::render_3d {
|
||||
|
||||
struct Point_Demo {
|
||||
std::shared_ptr<Point_Data> points;
|
||||
std::shared_ptr<Point_Visual> visual;
|
||||
std::unique_ptr<Point_Scene> scene;
|
||||
};
|
||||
|
||||
|
||||
@@ -72,21 +72,23 @@ struct Point_Scene::Impl {
|
||||
if (!accepting.load(std::memory_order_acquire))
|
||||
return false;
|
||||
return render_domain.invoke([&] {
|
||||
return scheduler.render([&](const detail::Point_Frame_Data& frame) {
|
||||
auto rendered = backend->render(frame);
|
||||
if (!rendered)
|
||||
return false;
|
||||
std::lock_guard lock(frame_mutex);
|
||||
latest = std::move(rendered);
|
||||
return true;
|
||||
});
|
||||
auto lease = scheduler.acquire_renderer();
|
||||
if (!lease)
|
||||
return false;
|
||||
auto rendered = backend->render(
|
||||
static_cast<const detail::Point_Frame_Data&>(*lease));
|
||||
if (!rendered)
|
||||
return false;
|
||||
std::lock_guard lock(frame_mutex);
|
||||
latest = std::move(rendered);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<Point_Visual> visual;
|
||||
detail::Scene_State_Buffer states;
|
||||
detail::Input_Collector input;
|
||||
detail::Frame_Scheduler scheduler;
|
||||
detail::Point_Frame_Strategy scheduler;
|
||||
detail::Render_Domain render_domain;
|
||||
std::unique_ptr<detail::Datoviz_Point_Backend> backend;
|
||||
mutable std::mutex frame_mutex;
|
||||
@@ -172,8 +174,17 @@ void Point_Scene::dispatch(const ::renderive::Key_Event& event) {
|
||||
}
|
||||
|
||||
bool Point_Scene::prepare_frame() {
|
||||
return impl_->accepting.load(std::memory_order_acquire) &&
|
||||
impl_->scheduler.prepare(impl_->states, *impl_->visual, impl_->input);
|
||||
if (!impl_->accepting.load(std::memory_order_acquire))
|
||||
return false;
|
||||
auto lease = impl_->scheduler.acquire_painter();
|
||||
if (!lease)
|
||||
return false;
|
||||
auto scene = impl_->states.publish_state();
|
||||
lease->scene = std::move(scene.state);
|
||||
lease->scene_revision = scene.revision;
|
||||
lease->point = detail::Point_State_Access::publish(*impl_->visual);
|
||||
lease->input = impl_->input.drain();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Point_Scene::refresh_manual_frame() {
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
#include "detail/Point_Core.h"
|
||||
|
||||
#include <renderive/state/Double_State_Strategy.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
@@ -31,49 +28,77 @@ void validate(const std::vector<Point>& points) {
|
||||
}
|
||||
}
|
||||
|
||||
struct Point_State_Base {};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct Point_Visual::Impl final
|
||||
: ::Double_State_Strategy<Point_State_Base, Point_State, std::mutex> {
|
||||
using Base = ::Double_State_Strategy<Point_State_Base, Point_State, std::mutex>;
|
||||
|
||||
Impl(std::shared_ptr<Point_Data> source, const Point_State& initial)
|
||||
: Base(initial), data(std::move(source)) {}
|
||||
|
||||
[[nodiscard]] detail::Published_Point publish_snapshot() {
|
||||
std::lock_guard lock(publication_mutex);
|
||||
Base::publish();
|
||||
const std::uint64_t data_revision = data->revision();
|
||||
auto payload = data->snapshot().value_or(std::vector<Point>{});
|
||||
validate(payload);
|
||||
return {Base::render_state_value(), std::move(payload),
|
||||
Base::state_revision(), data_revision};
|
||||
}
|
||||
|
||||
std::shared_ptr<Point_Data> data;
|
||||
std::mutex publication_mutex;
|
||||
};
|
||||
|
||||
Point_Visual::Point_Visual(std::shared_ptr<Point_Data> data, Point_State initial) {
|
||||
if (!data)
|
||||
throw std::invalid_argument("Point_Visual requires a Point_Data source");
|
||||
Point_Visual::Point_Visual(std::vector<Point> points, Point_State initial)
|
||||
: detail::Point_Visual_Strategy(std::in_place, initial) {
|
||||
validate(initial);
|
||||
impl_ = std::make_unique<Impl>(std::move(data), initial);
|
||||
validate(points);
|
||||
detail::Point_Visual_Strategy::write<detail::Point_Payload_Tag>(
|
||||
std::make_shared<const std::vector<Point>>(std::move(points)));
|
||||
}
|
||||
|
||||
Point_Visual::~Point_Visual() = default;
|
||||
|
||||
void Point_Visual::update_points(std::vector<Point> points) {
|
||||
validate(points);
|
||||
detail::Point_Visual_Strategy::write<detail::Point_Payload_Tag>(
|
||||
std::make_shared<const std::vector<Point>>(std::move(points)));
|
||||
}
|
||||
|
||||
void Point_Visual::edit_points(
|
||||
const std::function<void(std::vector<Point>&)>& edit) {
|
||||
if (!edit)
|
||||
throw std::invalid_argument("Point_Visual point edit is empty");
|
||||
const auto& published =
|
||||
detail::Point_Visual_Strategy::render_buffer_value<
|
||||
detail::Point_Payload_Tag>();
|
||||
auto points = published ? *published : std::vector<Point>{};
|
||||
edit(points);
|
||||
update_points(std::move(points));
|
||||
}
|
||||
|
||||
std::size_t Point_Visual::point_count() const {
|
||||
const auto& points =
|
||||
detail::Point_Visual_Strategy::render_buffer_value<
|
||||
detail::Point_Payload_Tag>();
|
||||
return points ? points->size() : 0U;
|
||||
}
|
||||
|
||||
std::uint64_t Point_Visual::data_revision() const {
|
||||
return detail::Point_Visual_Strategy::revision();
|
||||
}
|
||||
|
||||
void Point_Visual::configure(Point_State state) {
|
||||
validate(state);
|
||||
impl_->update([state = std::move(state)](Point_State& target) mutable {
|
||||
detail::Point_State_Strategy::update(
|
||||
[state = std::move(state)](Point_State& target) mutable {
|
||||
target = std::move(state);
|
||||
});
|
||||
}
|
||||
|
||||
detail::Published_Point detail::Point_State_Access::publish(Point_Visual& visual) {
|
||||
return visual.impl_->publish_snapshot();
|
||||
return visual.publish_frame();
|
||||
}
|
||||
|
||||
detail::Published_Point Point_Visual::publish_frame() {
|
||||
publish();
|
||||
return {
|
||||
detail::Point_State_Strategy::render_state_value(),
|
||||
detail::Point_Visual_Strategy::render_buffer_value<
|
||||
detail::Point_Payload_Tag>(),
|
||||
detail::Point_State_Strategy::state_revision(),
|
||||
detail::Point_Visual_Strategy::revision()};
|
||||
}
|
||||
|
||||
void Point_Visual::publish() {
|
||||
detail::Point_State_Strategy::publish();
|
||||
detail::Point_Visual_Strategy::publish();
|
||||
}
|
||||
|
||||
std::uint64_t Point_Visual::state_revision() const {
|
||||
return detail::Point_State_Strategy::state_revision() +
|
||||
detail::Point_Visual_Strategy::revision();
|
||||
}
|
||||
|
||||
} // namespace renderive::render_3d
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <renderive/real_time_data/Latest_Real_Time_Data.hpp>
|
||||
#include <renderive/base/Concepts.hpp>
|
||||
#include <renderive/real_time_data/Double_Buffer_Strategy.hpp>
|
||||
#include <renderive/state/Double_State_Strategy.hpp>
|
||||
namespace renderive::render_3d {
|
||||
struct Vec3 {
|
||||
float x{};
|
||||
@@ -51,21 +55,46 @@ struct Point_State {
|
||||
bool depth_test{true};
|
||||
bool operator==(const Point_State&) const = default;
|
||||
};
|
||||
// Bulk point payloads use Kernel's latest-value real-time data channel directly.
|
||||
using Point_Data = ::Latest_Real_Time_Data<std::vector<Point>>;
|
||||
namespace detail {
|
||||
struct Point_State_Base {};
|
||||
struct Point_Payload_Tag {};
|
||||
using Point_Payload = std::shared_ptr<const std::vector<Point>>;
|
||||
using Point_State_Strategy =
|
||||
::Double_State_Strategy<Point_State_Base, Point_State, std::mutex>;
|
||||
using Point_Buffer_Layout =
|
||||
::Double_Buffer_Layout<
|
||||
::Buffered_Data<Point_Payload_Tag, Point_Payload>>;
|
||||
class Point_Visual_Strategy
|
||||
: public ::Multi_Double_Buffer_Strategy<
|
||||
Point_State_Strategy, Point_Buffer_Layout, std::mutex> {
|
||||
using Base = ::Multi_Double_Buffer_Strategy<
|
||||
Point_State_Strategy, Point_Buffer_Layout, std::mutex>;
|
||||
protected:
|
||||
using Base::Base;
|
||||
using Base::publish;
|
||||
using Base::render_buffer_value;
|
||||
using Base::revision;
|
||||
using Base::write;
|
||||
};
|
||||
struct Published_Point;
|
||||
class Point_State_Access;
|
||||
}
|
||||
// Thread-safe logical point visual. Datoviz resources deliberately do not live here.
|
||||
class Point_Visual final : Non_Copyable {
|
||||
class Point_Visual final : private detail::Point_Visual_Strategy,
|
||||
Non_Copyable {
|
||||
public:
|
||||
explicit Point_Visual(std::shared_ptr<Point_Data> data, Point_State initial = {});
|
||||
explicit Point_Visual(std::vector<Point> points = {}, Point_State initial = {});
|
||||
~Point_Visual();
|
||||
// Atomically replace the visual properties. Point payloads are updated on Point_Data.
|
||||
|
||||
void update_points(std::vector<Point> points);
|
||||
void edit_points(const std::function<void(std::vector<Point>&)>& edit);
|
||||
[[nodiscard]] std::size_t point_count() const;
|
||||
[[nodiscard]] std::uint64_t data_revision() const;
|
||||
void configure(Point_State state);
|
||||
private:
|
||||
friend class detail::Point_State_Access;
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
[[nodiscard]] detail::Published_Point publish_frame();
|
||||
void publish() override;
|
||||
[[nodiscard]] std::uint64_t state_revision() const override;
|
||||
};
|
||||
} // namespace renderive::render_3d
|
||||
|
||||
@@ -409,6 +409,7 @@ void Datoviz_Point_Backend::create_scene(const Scene_State& initial_scene) {
|
||||
|
||||
void Datoviz_Point_Backend::apply(const Point_Frame_Data& frame) {
|
||||
require_domain();
|
||||
const auto& points = *frame.point.data;
|
||||
if (frame.scene_revision != applied_scene_revision_) {
|
||||
if (dvz_figure_resize(figure_, frame.scene.viewport.width,
|
||||
frame.scene.viewport.height) != DVZ_OK)
|
||||
@@ -437,23 +438,23 @@ void Datoviz_Point_Backend::apply(const Point_Frame_Data& frame) {
|
||||
if (dvz_point_set_style(visual_, &style) != DVZ_OK ||
|
||||
dvz_visual_set_transform(visual_, transform) != DVZ_OK ||
|
||||
dvz_visual_set_depth_test(visual_, state.depth_test) != DVZ_OK ||
|
||||
dvz_visual_set_visible(visual_, state.visible && !frame.point.data.empty()) != DVZ_OK)
|
||||
dvz_visual_set_visible(visual_, state.visible && !points.empty()) != DVZ_OK)
|
||||
throw std::runtime_error("failed to apply Datoviz point state");
|
||||
applied_state_revision_ = frame.point.state_revision;
|
||||
}
|
||||
|
||||
if (frame.point.data_revision != applied_data_revision_) {
|
||||
if (frame.point.data.empty()) {
|
||||
if (points.empty()) {
|
||||
if (dvz_visual_set_visible(visual_, false) != DVZ_OK)
|
||||
throw std::runtime_error("failed to hide empty Datoviz point visual");
|
||||
} else {
|
||||
std::vector<std::array<float, 3>> positions;
|
||||
std::vector<std::array<std::uint8_t, 4>> colors;
|
||||
std::vector<float> diameters;
|
||||
positions.reserve(frame.point.data.size());
|
||||
colors.reserve(frame.point.data.size());
|
||||
diameters.reserve(frame.point.data.size());
|
||||
for (const auto& point : frame.point.data) {
|
||||
positions.reserve(points.size());
|
||||
colors.reserve(points.size());
|
||||
diameters.reserve(points.size());
|
||||
for (const auto& point : points) {
|
||||
positions.push_back(
|
||||
{point.position.x, point.position.y, point.position.z});
|
||||
colors.push_back({point.color.red, point.color.green,
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
|
||||
#include "render_3D/Point_Scene.h"
|
||||
|
||||
#include <renderive/frame_control/strategy/flow/Flow_Refresh_Strategy.hpp>
|
||||
#include <renderive/frame_control/strategy/low_latency/Low_Latency_Strategy.hpp>
|
||||
#include <renderive/frame_control/strategy/manual/Manual_Refresh_Strategy.hpp>
|
||||
#include <renderive/frame_control/concept/Frame_Control_Strategy.hpp>
|
||||
#include <renderive/state/Double_State_Strategy.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
@@ -13,18 +11,15 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace renderive::render_3d::detail {
|
||||
|
||||
struct Published_Point {
|
||||
Point_State state;
|
||||
std::vector<Point> data;
|
||||
std::shared_ptr<const std::vector<Point>> data;
|
||||
std::uint64_t state_revision{};
|
||||
std::uint64_t data_revision{};
|
||||
};
|
||||
@@ -54,7 +49,7 @@ public:
|
||||
|
||||
explicit Scene_State_Buffer(const Scene_State& initial) : Base(initial) {}
|
||||
|
||||
[[nodiscard]] Published publish_snapshot() {
|
||||
[[nodiscard]] Published publish_state() {
|
||||
std::lock_guard lock(publication_mutex_);
|
||||
Base::publish();
|
||||
return {Base::render_state_value(), Base::state_revision()};
|
||||
@@ -118,133 +113,179 @@ struct Point_Frame_Data {
|
||||
std::uint64_t frame_sequence{};
|
||||
};
|
||||
|
||||
class Frame_Scheduler final {
|
||||
using Manual = ::Manual_Refresh_Strategy<Point_Frame_Data, std::mutex>;
|
||||
using Low_Latency = ::Low_Latency_Strategy<Point_Frame_Data, std::mutex>;
|
||||
using Playback = ::Flow_Refresh_Strategy<Point_Frame_Data, std::mutex>;
|
||||
using Strategy = std::variant<std::unique_ptr<Manual>, std::unique_ptr<Low_Latency>,
|
||||
std::unique_ptr<Playback>>;
|
||||
|
||||
class Point_Frame_Strategy final : public ::Frame_Control_Strategy_Base {
|
||||
public:
|
||||
Frame_Scheduler(Frame_Mode mode, double maximum_frames_per_second)
|
||||
: strategy_(make_strategy(mode, maximum_frames_per_second)) {}
|
||||
struct Frame final : Point_Frame_Data {};
|
||||
|
||||
[[nodiscard]] bool prepare(Scene_State_Buffer& scene, Point_Visual& point,
|
||||
Input_Collector& input) {
|
||||
return std::visit(
|
||||
[&](auto& strategy) {
|
||||
auto lease = strategy->acquire_painter();
|
||||
if (!lease)
|
||||
return false;
|
||||
auto published_scene = scene.publish_snapshot();
|
||||
lease->scene = std::move(published_scene.state);
|
||||
lease->scene_revision = published_scene.revision;
|
||||
lease->point = Point_State_Access::publish(point);
|
||||
lease->input = input.drain();
|
||||
lease->frame_sequence = lease->statistics.sequence;
|
||||
return true;
|
||||
},
|
||||
strategy_);
|
||||
class Painter_Lease final {
|
||||
public:
|
||||
explicit Painter_Lease(Point_Frame_Strategy& strategy)
|
||||
: strategy_(&strategy), frame_(strategy.make_frame()) {}
|
||||
Painter_Lease(const Painter_Lease&) = delete;
|
||||
Painter_Lease& operator=(const Painter_Lease&) = delete;
|
||||
Painter_Lease(Painter_Lease&& other) noexcept
|
||||
: strategy_(std::exchange(other.strategy_, nullptr)),
|
||||
frame_(std::move(other.frame_)) {}
|
||||
~Painter_Lease() {
|
||||
if (strategy_ && frame_)
|
||||
strategy_->submit(std::move(frame_));
|
||||
}
|
||||
explicit operator bool() const noexcept { return frame_ != nullptr; }
|
||||
Frame* get() noexcept { return frame_.get(); }
|
||||
const Frame* get() const noexcept { return frame_.get(); }
|
||||
Frame* operator->() noexcept { return get(); }
|
||||
const Frame* operator->() const noexcept { return get(); }
|
||||
Frame& operator*() noexcept { return *frame_; }
|
||||
const Frame& operator*() const noexcept { return *frame_; }
|
||||
|
||||
private:
|
||||
Point_Frame_Strategy* strategy_{};
|
||||
std::unique_ptr<Frame> frame_;
|
||||
};
|
||||
|
||||
class Render_Lease final {
|
||||
public:
|
||||
explicit Render_Lease(Point_Frame_Strategy& strategy)
|
||||
: strategy_(&strategy), frame_(strategy.take_ready()) {}
|
||||
Render_Lease(const Render_Lease&) = delete;
|
||||
Render_Lease& operator=(const Render_Lease&) = delete;
|
||||
Render_Lease(Render_Lease&& other) noexcept
|
||||
: strategy_(std::exchange(other.strategy_, nullptr)),
|
||||
frame_(std::move(other.frame_)) {}
|
||||
~Render_Lease() {
|
||||
if (strategy_ && frame_)
|
||||
strategy_->complete(frame_->frame_sequence);
|
||||
}
|
||||
explicit operator bool() const noexcept { return frame_ != nullptr; }
|
||||
Frame* get() noexcept { return frame_.get(); }
|
||||
const Frame* get() const noexcept { return frame_.get(); }
|
||||
Frame* operator->() noexcept { return get(); }
|
||||
const Frame* operator->() const noexcept { return get(); }
|
||||
Frame& operator*() noexcept { return *frame_; }
|
||||
const Frame& operator*() const noexcept { return *frame_; }
|
||||
|
||||
private:
|
||||
Point_Frame_Strategy* strategy_{};
|
||||
std::unique_ptr<Frame> frame_;
|
||||
};
|
||||
|
||||
Point_Frame_Strategy(Frame_Mode mode, double maximum_frames_per_second)
|
||||
: Frame_Control_Strategy_Base(
|
||||
mode == Frame_Mode::Low_Latency
|
||||
? maximum_frames_per_second
|
||||
: invalid_frequency_hz(),
|
||||
mode == Frame_Mode::Low_Latency
|
||||
? frequency_interval_ns(maximum_frames_per_second)
|
||||
: 0),
|
||||
mode_(mode) {}
|
||||
|
||||
[[nodiscard]] Painter_Lease acquire_painter() { return Painter_Lease(*this); }
|
||||
[[nodiscard]] Render_Lease acquire_renderer() { return Render_Lease(*this); }
|
||||
|
||||
void swap() override { swap_frame_control_state(); }
|
||||
[[nodiscard]] double frequency_hz() const noexcept override {
|
||||
return render_frame_control_state().frequency_hz;
|
||||
}
|
||||
[[nodiscard]] std::uint64_t next_refresh_interval_ns() const noexcept override {
|
||||
return render_frame_control_state().next_refresh_interval_ns;
|
||||
}
|
||||
[[nodiscard]] Frame_Control_Strategy_Base::State frame_control_state() const override {
|
||||
return render_frame_control_state();
|
||||
}
|
||||
void on_real_time_data_update(const Real_Time_Data_Observation&) noexcept override {}
|
||||
|
||||
[[nodiscard]] bool refresh() {
|
||||
auto* manual = std::get_if<std::unique_ptr<Manual>>(&strategy_);
|
||||
return manual != nullptr && (*manual)->refresh();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool activate_for_request() {
|
||||
if (auto* manual = std::get_if<std::unique_ptr<Manual>>(&strategy_))
|
||||
return (*manual)->refresh();
|
||||
std::lock_guard lock(mutex_);
|
||||
if (mode_ != Frame_Mode::Manual || !manual_pending_) {
|
||||
++status_.failed_operation_count;
|
||||
return false;
|
||||
}
|
||||
status_.dropped_frame_count += ready_.size();
|
||||
ready_.clear();
|
||||
ready_.push_back(std::move(manual_pending_));
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool activate_for_request() {
|
||||
return mode_ != Frame_Mode::Manual || refresh();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool discard_pending() {
|
||||
if (auto* manual = std::get_if<std::unique_ptr<Manual>>(&strategy_))
|
||||
return (*manual)->discard_pending_frame();
|
||||
if (auto* low_latency = std::get_if<std::unique_ptr<Low_Latency>>(&strategy_))
|
||||
return (*low_latency)->discard_pending_frame();
|
||||
std::lock_guard lock(mutex_);
|
||||
if (mode_ == Frame_Mode::Manual && manual_pending_) {
|
||||
manual_pending_.reset();
|
||||
++status_.dropped_frame_count;
|
||||
return true;
|
||||
}
|
||||
if (mode_ == Frame_Mode::Low_Latency && !ready_.empty()) {
|
||||
status_.dropped_frame_count += ready_.size();
|
||||
ready_.clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class Render>
|
||||
requires std::invocable<Render, const Point_Frame_Data&>
|
||||
[[nodiscard]] bool render(Render&& render) {
|
||||
return std::visit(
|
||||
[&](auto& strategy) {
|
||||
auto lease = strategy->acquire_renderer();
|
||||
if (!lease)
|
||||
return false;
|
||||
return static_cast<bool>(std::invoke(
|
||||
std::forward<Render>(render),
|
||||
static_cast<const Point_Frame_Data&>(*lease)));
|
||||
},
|
||||
strategy_);
|
||||
}
|
||||
|
||||
[[nodiscard]] Frame_Status status() const {
|
||||
return std::visit(
|
||||
[](const auto& strategy) {
|
||||
using Strategy_Type = std::remove_cvref_t<decltype(*strategy)>;
|
||||
Frame_Status result;
|
||||
if constexpr (std::same_as<Strategy_Type, Manual>) {
|
||||
result.mode = Frame_Mode::Manual;
|
||||
const auto state = strategy->state();
|
||||
result.produced_frame_count = state.prepared_frame_count;
|
||||
result.consumed_frame_count = state.render_count;
|
||||
result.dropped_frame_count = state.replaced_prepared_frame_count +
|
||||
state.discarded_prepared_frame_count;
|
||||
result.failed_operation_count = state.failed_refresh_count;
|
||||
result.pending_frame_count = state.pending_frame ? 1U : 0U;
|
||||
result.latest_sequence = state.render_frame_sequence;
|
||||
} else if constexpr (std::same_as<Strategy_Type, Low_Latency>) {
|
||||
result.mode = Frame_Mode::Low_Latency;
|
||||
const auto state = strategy->state();
|
||||
const auto counters = strategy->counter_statistics();
|
||||
result.frequency_hz = state.frequency_hz;
|
||||
result.produced_frame_count = counters.published_frame_count;
|
||||
result.consumed_frame_count = state.completed_lifecycle_count;
|
||||
result.dropped_frame_count = counters.abandoned_frame_count +
|
||||
counters.manually_discarded_frame_count;
|
||||
result.failed_operation_count = counters.swap_failure_count;
|
||||
const auto retired = result.consumed_frame_count +
|
||||
result.dropped_frame_count;
|
||||
result.pending_frame_count =
|
||||
result.produced_frame_count > retired ? 1U : 0U;
|
||||
result.latest_sequence = state.frame_sequence;
|
||||
result.next_refresh_interval_ns = state.next_refresh_interval_ns;
|
||||
} else {
|
||||
result.mode = Frame_Mode::Playback;
|
||||
const auto state = strategy->state();
|
||||
result.produced_frame_count = state.enqueued_frame_count;
|
||||
result.consumed_frame_count = state.rendered_frame_count;
|
||||
result.failed_operation_count = state.empty_acquire_count;
|
||||
result.pending_frame_count = state.pending_frame_count;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
strategy_);
|
||||
std::lock_guard lock(mutex_);
|
||||
auto result = status_;
|
||||
const auto control = render_frame_control_state();
|
||||
result.mode = mode_;
|
||||
result.frequency_hz = control.frequency_hz;
|
||||
result.next_refresh_interval_ns = control.next_refresh_interval_ns;
|
||||
result.pending_frame_count = ready_.size() +
|
||||
(manual_pending_ ? 1U : 0U);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static Strategy make_strategy(Frame_Mode mode, double maximum_frames_per_second) {
|
||||
switch (mode) {
|
||||
case Frame_Mode::Manual:
|
||||
return Strategy(std::in_place_type<std::unique_ptr<Manual>>,
|
||||
std::make_unique<Manual>());
|
||||
case Frame_Mode::Low_Latency:
|
||||
return Strategy(
|
||||
std::in_place_type<std::unique_ptr<Low_Latency>>,
|
||||
std::make_unique<Low_Latency>(
|
||||
Observer_State<>{},
|
||||
typename Low_Latency::Configuration{maximum_frames_per_second}));
|
||||
case Frame_Mode::Playback:
|
||||
return Strategy(std::in_place_type<std::unique_ptr<Playback>>,
|
||||
std::make_unique<Playback>());
|
||||
}
|
||||
throw std::invalid_argument("unknown Point_Scene frame mode");
|
||||
[[nodiscard]] std::unique_ptr<Frame> make_frame() {
|
||||
auto frame = std::make_unique<Frame>();
|
||||
std::lock_guard lock(mutex_);
|
||||
frame->frame_sequence = ++next_sequence_;
|
||||
return frame;
|
||||
}
|
||||
|
||||
Strategy strategy_;
|
||||
void submit(std::unique_ptr<Frame> frame) {
|
||||
std::lock_guard lock(mutex_);
|
||||
++status_.produced_frame_count;
|
||||
if (mode_ == Frame_Mode::Manual) {
|
||||
if (manual_pending_)
|
||||
++status_.dropped_frame_count;
|
||||
manual_pending_ = std::move(frame);
|
||||
return;
|
||||
}
|
||||
if (mode_ == Frame_Mode::Low_Latency) {
|
||||
status_.dropped_frame_count += ready_.size();
|
||||
ready_.clear();
|
||||
}
|
||||
ready_.push_back(std::move(frame));
|
||||
}
|
||||
|
||||
[[nodiscard]] std::unique_ptr<Frame> take_ready() {
|
||||
std::lock_guard lock(mutex_);
|
||||
if (ready_.empty()) {
|
||||
++status_.failed_operation_count;
|
||||
return {};
|
||||
}
|
||||
auto frame = std::move(ready_.front());
|
||||
ready_.pop_front();
|
||||
return frame;
|
||||
}
|
||||
|
||||
void complete(std::uint64_t sequence) {
|
||||
std::lock_guard lock(mutex_);
|
||||
++status_.consumed_frame_count;
|
||||
status_.latest_sequence = sequence;
|
||||
}
|
||||
|
||||
Frame_Mode mode_;
|
||||
mutable std::mutex mutex_;
|
||||
std::uint64_t next_sequence_{};
|
||||
std::unique_ptr<Frame> manual_pending_;
|
||||
std::deque<std::unique_ptr<Frame>> ready_;
|
||||
Frame_Status status_;
|
||||
};
|
||||
|
||||
static_assert(::Frame_Control_Strategy<Point_Frame_Strategy>);
|
||||
|
||||
} // namespace renderive::render_3d::detail
|
||||
|
||||
@@ -13,9 +13,7 @@ namespace renderive::render_3d {
|
||||
namespace {
|
||||
|
||||
TEST(PointState, UsesKernelDoubleStateAtFrameBoundary) {
|
||||
auto data = std::make_shared<Point_Data>();
|
||||
data->update(std::vector<Point>{{{1.0F, 2.0F, 3.0F}, {1, 2, 3, 255}, 7.0F}});
|
||||
Point_Visual visual(data);
|
||||
Point_Visual visual({{{1.0F, 2.0F, 3.0F}, {1, 2, 3, 255}, 7.0F}});
|
||||
|
||||
Point_State configured;
|
||||
configured.visible = false;
|
||||
@@ -24,15 +22,14 @@ TEST(PointState, UsesKernelDoubleStateAtFrameBoundary) {
|
||||
|
||||
const auto published = detail::Point_State_Access::publish(visual);
|
||||
EXPECT_EQ(published.state, configured);
|
||||
ASSERT_EQ(published.data.size(), 1U);
|
||||
EXPECT_EQ(published.data.front().position, (Vec3{1.0F, 2.0F, 3.0F}));
|
||||
ASSERT_EQ(published.data->size(), 1U);
|
||||
EXPECT_EQ(published.data->front().position, (Vec3{1.0F, 2.0F, 3.0F}));
|
||||
EXPECT_EQ(published.state_revision, 1U);
|
||||
EXPECT_EQ(published.data_revision, 1U);
|
||||
}
|
||||
|
||||
TEST(PointState, UsesKernelLatestRealTimeDataForBulkPayloads) {
|
||||
auto data = std::make_shared<Point_Data>();
|
||||
Point_Visual visual(data);
|
||||
TEST(PointState, PublishesImmutableBulkPayloadsThroughDoubleBuffer) {
|
||||
Point_Visual visual;
|
||||
constexpr int update_count = 500;
|
||||
std::atomic<bool> done{};
|
||||
|
||||
@@ -43,82 +40,88 @@ TEST(PointState, UsesKernelLatestRealTimeDataForBulkPayloads) {
|
||||
point.position.x = static_cast<float>(value);
|
||||
point.diameter_px = static_cast<float>(value % 12 + 1);
|
||||
}
|
||||
data->update(std::move(points));
|
||||
visual.update_points(std::move(points));
|
||||
}
|
||||
done.store(true, std::memory_order_release);
|
||||
});
|
||||
|
||||
do {
|
||||
const auto published = detail::Point_State_Access::publish(visual);
|
||||
if (!published.data.empty()) {
|
||||
const float expected = published.data.front().position.x;
|
||||
for (const auto& point : published.data)
|
||||
if (!published.data->empty()) {
|
||||
const float expected = published.data->front().position.x;
|
||||
for (const auto& point : *published.data)
|
||||
EXPECT_EQ(point.position.x, expected);
|
||||
}
|
||||
} while (!done.load(std::memory_order_acquire));
|
||||
writer.join();
|
||||
|
||||
const auto published = detail::Point_State_Access::publish(visual);
|
||||
EXPECT_EQ(published.data_revision, update_count);
|
||||
ASSERT_FALSE(published.data.empty());
|
||||
EXPECT_EQ(published.data.front().position.x, static_cast<float>(update_count));
|
||||
EXPECT_GT(published.data_revision, 0U);
|
||||
EXPECT_LE(published.data_revision, update_count);
|
||||
ASSERT_FALSE(published.data->empty());
|
||||
EXPECT_EQ(published.data->front().position.x, static_cast<float>(update_count));
|
||||
}
|
||||
|
||||
TEST(PointState, RejectsInvalidStateAndPayloadAtTheOwningBoundary) {
|
||||
auto data = std::make_shared<Point_Data>();
|
||||
Point_Visual visual(data);
|
||||
Point_Visual visual;
|
||||
Point_State invalid;
|
||||
invalid.style.stroke_width_px = -1.0F;
|
||||
EXPECT_THROW(visual.configure(invalid), std::invalid_argument);
|
||||
|
||||
data->update(std::vector<Point>{{{}, {}, 0.0F}});
|
||||
EXPECT_THROW((void)detail::Point_State_Access::publish(visual),
|
||||
EXPECT_THROW(visual.update_points(std::vector<Point>{{{}, {}, 0.0F}}),
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST(PointFrameControl, ReusesKernelManualAndPlaybackStrategies) {
|
||||
auto data = std::make_shared<Point_Data>();
|
||||
data->update(std::vector<Point>{{{}, {}, 8.0F}});
|
||||
Point_Visual visual(data);
|
||||
TEST(PointFrameControl, UsesDedicatedThreeDimensionalFrameStrategy) {
|
||||
Point_Visual visual(std::vector<Point>{{{}, {}, 8.0F}});
|
||||
detail::Scene_State_Buffer scene({{320, 180}, {}});
|
||||
detail::Input_Collector input;
|
||||
const auto prepare = [&](detail::Point_Frame_Strategy& strategy) {
|
||||
auto lease = strategy.acquire_painter();
|
||||
if (!lease)
|
||||
return false;
|
||||
auto published_scene = scene.publish_state();
|
||||
lease->scene = std::move(published_scene.state);
|
||||
lease->scene_revision = published_scene.revision;
|
||||
lease->point = detail::Point_State_Access::publish(visual);
|
||||
lease->input = input.drain();
|
||||
return true;
|
||||
};
|
||||
|
||||
detail::Frame_Scheduler manual(Frame_Mode::Manual, 60.0);
|
||||
EXPECT_TRUE(manual.prepare(scene, visual, input));
|
||||
detail::Point_Frame_Strategy manual(Frame_Mode::Manual, 60.0);
|
||||
EXPECT_TRUE(prepare(manual));
|
||||
auto manual_status = manual.status();
|
||||
EXPECT_EQ(manual_status.mode, Frame_Mode::Manual);
|
||||
EXPECT_EQ(manual_status.produced_frame_count, 1U);
|
||||
EXPECT_EQ(manual_status.pending_frame_count, 1U);
|
||||
bool rendered{};
|
||||
EXPECT_FALSE(manual.render([&](const detail::Point_Frame_Data&) {
|
||||
rendered = true;
|
||||
return true;
|
||||
}));
|
||||
EXPECT_FALSE(rendered);
|
||||
EXPECT_FALSE(static_cast<bool>(manual.acquire_renderer()));
|
||||
EXPECT_TRUE(manual.refresh());
|
||||
EXPECT_TRUE(manual.render([&](const detail::Point_Frame_Data& frame) {
|
||||
rendered = true;
|
||||
EXPECT_EQ(frame.point.data.size(), 1U);
|
||||
return true;
|
||||
}));
|
||||
EXPECT_TRUE(rendered);
|
||||
{
|
||||
auto rendered = manual.acquire_renderer();
|
||||
ASSERT_TRUE(rendered);
|
||||
EXPECT_EQ(rendered->point.data->size(), 1U);
|
||||
}
|
||||
manual_status = manual.status();
|
||||
EXPECT_EQ(manual_status.consumed_frame_count, 1U);
|
||||
EXPECT_EQ(manual_status.pending_frame_count, 0U);
|
||||
|
||||
detail::Frame_Scheduler playback(Frame_Mode::Playback, 60.0);
|
||||
EXPECT_TRUE(playback.prepare(scene, visual, input));
|
||||
data->update(std::vector<Point>(2, Point{{}, {}, 8.0F}));
|
||||
EXPECT_TRUE(playback.prepare(scene, visual, input));
|
||||
detail::Point_Frame_Strategy playback(Frame_Mode::Playback, 60.0);
|
||||
EXPECT_TRUE(prepare(playback));
|
||||
visual.update_points(std::vector<Point>(2, Point{{}, {}, 8.0F}));
|
||||
EXPECT_TRUE(prepare(playback));
|
||||
auto playback_status = playback.status();
|
||||
EXPECT_EQ(playback_status.produced_frame_count, 2U);
|
||||
EXPECT_EQ(playback_status.pending_frame_count, 2U);
|
||||
EXPECT_TRUE(playback.render([](const detail::Point_Frame_Data& frame) {
|
||||
return frame.point.data.size() == 1U;
|
||||
}));
|
||||
EXPECT_TRUE(playback.render([](const detail::Point_Frame_Data& frame) {
|
||||
return frame.point.data.size() == 2U;
|
||||
}));
|
||||
{
|
||||
auto first = playback.acquire_renderer();
|
||||
ASSERT_TRUE(first);
|
||||
EXPECT_EQ(first->point.data->size(), 1U);
|
||||
}
|
||||
{
|
||||
auto second = playback.acquire_renderer();
|
||||
ASSERT_TRUE(second);
|
||||
EXPECT_EQ(second->point.data->size(), 2U);
|
||||
}
|
||||
playback_status = playback.status();
|
||||
EXPECT_EQ(playback_status.consumed_frame_count, 2U);
|
||||
EXPECT_EQ(playback_status.pending_frame_count, 0U);
|
||||
|
||||
@@ -1492,21 +1492,26 @@ public:
|
||||
publish_demo_points();
|
||||
last_action_result_ = "points_orbited";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Point positions updated through Latest_Real_Time_Data";
|
||||
return "Point positions updated through Point_Visual double buffering";
|
||||
}
|
||||
if (request.id == "add_point") {
|
||||
auto points = current_points();
|
||||
points.push_back({{0.12F, -0.72F, 0.58F}, {55, 235, 230, 255}, 46.0F});
|
||||
demo_.points->update(std::move(points));
|
||||
demo_.visual->edit_points([](auto& points) {
|
||||
points.push_back(
|
||||
{{0.12F, -0.72F, 0.58F}, {55, 235, 230, 255}, 46.0F});
|
||||
});
|
||||
last_action_result_ = "point_added";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Point added to the bulk payload";
|
||||
}
|
||||
if (request.id == "remove_point") {
|
||||
auto points = current_points();
|
||||
if (points.size() > render_3d::point_demo_data().size()) {
|
||||
points.pop_back();
|
||||
demo_.points->update(std::move(points));
|
||||
bool removed{};
|
||||
demo_.visual->edit_points([&](auto& points) {
|
||||
if (points.size() > render_3d::point_demo_data().size()) {
|
||||
points.pop_back();
|
||||
removed = true;
|
||||
}
|
||||
});
|
||||
if (removed) {
|
||||
last_action_result_ = "point_removed";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Last added Point removed";
|
||||
@@ -1515,11 +1520,11 @@ public:
|
||||
return "No added Point to remove";
|
||||
}
|
||||
if (request.id == "point_churn") {
|
||||
auto stable = current_points();
|
||||
auto transient = stable;
|
||||
transient.push_back({{0.0F, 0.0F, 0.8F}, {255, 255, 255, 255}, 72.0F});
|
||||
demo_.points->update(std::move(transient));
|
||||
demo_.points->update(std::move(stable));
|
||||
demo_.visual->edit_points([](auto& points) {
|
||||
points.push_back(
|
||||
{{0.0F, 0.0F, 0.8F}, {255, 255, 255, 255}, 72.0F});
|
||||
points.pop_back();
|
||||
});
|
||||
last_action_result_ = "point_churned";
|
||||
rendered_since_last_pixel_ = false;
|
||||
return "Transient Point added and removed before the next frame snapshot";
|
||||
@@ -1541,7 +1546,7 @@ public:
|
||||
[[nodiscard]] std::string telemetry_json() const override {
|
||||
const auto status = demo_.scene->frame_status();
|
||||
const auto frame = demo_.scene->latest_frame();
|
||||
const auto points = current_points();
|
||||
const auto point_count = demo_.visual->point_count();
|
||||
const auto render_window = render_duration_statistics_.snapshot();
|
||||
const auto encode_window = pixel_encode_statistics_.snapshot();
|
||||
const auto request_window = pixel_request_statistics_.snapshot();
|
||||
@@ -1607,20 +1612,17 @@ public:
|
||||
{"renderable_observers", nlohmann::json::array()},
|
||||
{"performance_capture", nullptr},
|
||||
{"last_action_result", last_action_result_},
|
||||
{"point_3d", {{"point_count", points.size()},
|
||||
{"data_revision", demo_.points->revision()}}},
|
||||
{"data_shape", {{"input_elements", points.size()},
|
||||
{"rendered_elements", points.size()}}}}
|
||||
{"point_3d", {{"point_count", point_count},
|
||||
{"data_revision", demo_.visual->data_revision()}}},
|
||||
{"data_shape", {{"input_elements", point_count},
|
||||
{"rendered_elements", point_count}}}}
|
||||
;
|
||||
return telemetry.dump();
|
||||
}
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<render_3d::Point> current_points() const {
|
||||
return demo_.points->snapshot().value_or(std::vector<render_3d::Point>{});
|
||||
}
|
||||
void publish_demo_points() {
|
||||
demo_.points->update(render_3d::point_demo_data(phase_));
|
||||
demo_.visual->update_points(render_3d::point_demo_data(phase_));
|
||||
}
|
||||
template <class Render>
|
||||
bool record_render(Render&& render) {
|
||||
|
||||
@@ -119,13 +119,13 @@ std::vector<Action_Model> registered_actions(std::string_view case_id,
|
||||
std::move(description), "Point Visual", {}, {}, 0.0,
|
||||
true});
|
||||
};
|
||||
add("orbit_points", "移动点", "Point_Data::update",
|
||||
"通过 Kernel Latest_Real_Time_Data 发布一批新位置");
|
||||
add("add_point", "添加点", "Point_Data::update", "向批量点数据追加一个点");
|
||||
add("remove_point", "移除点", "Point_Data::update", "移除最后追加的点");
|
||||
add("point_churn", "点增删压力", "Point_Data::update",
|
||||
add("orbit_points", "移动点", "Point_Visual::update_points",
|
||||
"通过 Kernel Multi_Double_Buffer_Strategy 发布一批新位置");
|
||||
add("add_point", "添加点", "Point_Visual::edit_points", "向批量点数据追加一个点");
|
||||
add("remove_point", "移除点", "Point_Visual::edit_points", "移除最后追加的点");
|
||||
add("point_churn", "点增删压力", "Point_Visual::edit_points",
|
||||
"在帧快照前连续追加并移除临时点");
|
||||
add("reset_points", "重置点", "Point_Data::update", "恢复 3D 点演示数据");
|
||||
add("reset_points", "重置点", "Point_Visual::update_points", "恢复 3D 点演示数据");
|
||||
return result;
|
||||
}
|
||||
append_session_actions(frame_mode, result);
|
||||
|
||||
Generated
+12
-1
@@ -15,7 +15,8 @@
|
||||
"@xyflow/react": "^12.4.4",
|
||||
"elkjs": "^0.9.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react-dom": "^18.3.1",
|
||||
"react-resizable-panels": "^4.12.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.51.1",
|
||||
@@ -3396,6 +3397,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-resizable-panels": {
|
||||
"version": "4.12.2",
|
||||
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.2.tgz",
|
||||
"integrity": "sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"@xyflow/react": "^12.4.4",
|
||||
"elkjs": "^0.9.3",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
"react-dom": "^18.3.1",
|
||||
"react-resizable-panels": "^4.12.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.51.1",
|
||||
|
||||
+141
-14
@@ -1,17 +1,144 @@
|
||||
import {Alert,Box,CircularProgress,Container,Stack,Typography} from "@mui/material";
|
||||
import {useEffect,useMemo,useState} from "react";
|
||||
import {Alert, Box, CircularProgress, Container, Stack, Typography, useMediaQuery} from "@mui/material";
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
import {useEffect, useMemo, useState} from "react";
|
||||
import {Group, Panel, Separator} from "react-resizable-panels";
|
||||
import {Category_Filter} from "./gallery/category_filter";
|
||||
import {Frame_Mode_Tabs} from "./gallery/frame_mode_tabs";
|
||||
import {Gallery_Page} from "./gallery/gallery_page";
|
||||
import {Gallery_Summary} from "./gallery/gallery_summary";
|
||||
import {Gallery_Toolbar} from "./gallery/gallery_toolbar";
|
||||
import type {Selected_Plot} from "./gallery/plot_card";
|
||||
import {use_gallery_catalog} from "./hooks/use_gallery_catalog";
|
||||
import {Gallery_Toolbar} from "./gallery/gallery_toolbar";import {Gallery_Summary} from "./gallery/gallery_summary";import {Frame_Mode_Tabs} from "./gallery/frame_mode_tabs";import {Category_Filter} from "./gallery/category_filter";import {Gallery_Page} from "./gallery/gallery_page";import type {Selected_Plot} from "./gallery/plot_card";import {Inspector_Drawer} from "./inspector/inspector_drawer";
|
||||
import {Inspector_Panel} from "./inspector/inspector_panel";
|
||||
|
||||
export function App() {
|
||||
const {catalog,state:socket_state,message:socket_message}=use_gallery_catalog();
|
||||
const [active_mode,set_active_mode]=useState(""),[active_category,set_active_category]=useState(""),[streams_paused,set_streams_paused]=useState(false),[selected_plot,set_selected_plot]=useState<Selected_Plot|null>(null),[selected_inspector_tab,set_selected_inspector_tab]=useState("controls");
|
||||
const [,set_visibility_epoch]=useState(0);
|
||||
useEffect(()=>{const change=()=>set_visibility_epoch(value=>value+1);document.addEventListener("visibilitychange",change);return()=>document.removeEventListener("visibilitychange",change);},[]);
|
||||
useEffect(()=>{if(catalog){set_active_mode(catalog.frame_modes.some(mode=>mode.id===catalog.navigation.default_mode)?catalog.navigation.default_mode:catalog.frame_modes[0]?.id??"");set_active_category(catalog.navigation.all_categories_label);}},[catalog]);
|
||||
const modes=useMemo(()=>[...(catalog?.frame_modes??[])].sort((a,b)=>a.order-b.order),[catalog]);
|
||||
const cases=useMemo(()=>[...(catalog?.cases??[])].sort((a,b)=>a.order-b.order),[catalog]);
|
||||
const frame_mode=modes.find(mode=>mode.id===active_mode);
|
||||
const categories=catalog?[catalog.navigation.all_categories_label,...new Set(cases.map(item=>item.category))]:[];
|
||||
const visible_cases=catalog?cases.filter(item=>active_category===catalog.navigation.all_categories_label||item.category===active_category):[];
|
||||
return <><Gallery_Toolbar socket_state={socket_state} socket_message={socket_message} streams_paused={streams_paused} on_toggle_streams={()=>set_streams_paused(value=>!value)}/><Container maxWidth={false} sx={{py:3}}><Stack spacing={2.5}>{!catalog&&socket_state!=="error"&&<Box sx={{display:"grid",placeItems:"center",minHeight:"55vh",gap:2}}><CircularProgress/><Typography>等待后端返回控件与帧策略目录</Typography></Box>}{!catalog&&socket_state==="error"&&<Alert severity="error">{socket_message}</Alert>}{catalog&&<><Gallery_Summary catalog={catalog}/><Frame_Mode_Tabs frame_modes={modes} active_mode={active_mode} on_change={id=>{set_active_mode(id);set_selected_plot(null);}}/><Category_Filter categories={categories} active_category={active_category} on_change={set_active_category}/>{frame_mode&&<><Typography color="text.secondary">{frame_mode.description}</Typography><Gallery_Page cases={visible_cases} frame_mode={frame_mode} streams_paused={streams_paused} on_open_inspector={set_selected_plot}/></>}</>}</Stack></Container><Inspector_Drawer open={selected_plot!==null} session={selected_plot?.session??null} active_tab={selected_inspector_tab} on_change_tab={set_selected_inspector_tab} on_close={()=>set_selected_plot(null)}/></>;
|
||||
const {catalog, state: socket_state, message: socket_message} = use_gallery_catalog();
|
||||
const [active_mode, set_active_mode] = useState("");
|
||||
const [active_category, set_active_category] = useState("");
|
||||
const [streams_paused, set_streams_paused] = useState(false);
|
||||
const [selected_plot, set_selected_plot] = useState<Selected_Plot | null>(null);
|
||||
const [selected_inspector_tab, set_selected_inspector_tab] = useState("controls");
|
||||
const [, set_visibility_epoch] = useState(0);
|
||||
const theme = useTheme();
|
||||
const compact_layout = useMediaQuery(theme.breakpoints.down("md"));
|
||||
|
||||
useEffect(() => {
|
||||
const change = () => set_visibility_epoch(value => value + 1);
|
||||
document.addEventListener("visibilitychange", change);
|
||||
return () => document.removeEventListener("visibilitychange", change);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!catalog)
|
||||
return;
|
||||
set_active_mode(
|
||||
catalog.frame_modes.some(mode => mode.id === catalog.navigation.default_mode)
|
||||
? catalog.navigation.default_mode
|
||||
: catalog.frame_modes[0]?.id ?? "",
|
||||
);
|
||||
set_active_category(catalog.navigation.all_categories_label);
|
||||
}, [catalog]);
|
||||
|
||||
const modes = useMemo(
|
||||
() => [...(catalog?.frame_modes ?? [])].sort((left, right) => left.order - right.order),
|
||||
[catalog],
|
||||
);
|
||||
const cases = useMemo(
|
||||
() => [...(catalog?.cases ?? [])].sort((left, right) => left.order - right.order),
|
||||
[catalog],
|
||||
);
|
||||
const frame_mode = modes.find(mode => mode.id === active_mode);
|
||||
const categories = catalog
|
||||
? [catalog.navigation.all_categories_label, ...new Set(cases.map(item => item.category))]
|
||||
: [];
|
||||
const visible_cases = catalog
|
||||
? cases.filter(item => active_category === catalog.navigation.all_categories_label || item.category === active_category)
|
||||
: [];
|
||||
|
||||
const gallery = <Box sx={{height: "100%", overflow: "auto"}}>
|
||||
<Container maxWidth={false} sx={{py: 3}}>
|
||||
<Stack spacing={2.5}>
|
||||
{!catalog && socket_state !== "error" && <Box sx={{display: "grid", placeItems: "center", minHeight: "55vh", gap: 2}}>
|
||||
<CircularProgress/>
|
||||
<Typography>等待后端返回控件与帧策略目录</Typography>
|
||||
</Box>}
|
||||
{!catalog && socket_state === "error" && <Alert severity="error">{socket_message}</Alert>}
|
||||
{catalog && <>
|
||||
<Gallery_Summary catalog={catalog}/>
|
||||
<Frame_Mode_Tabs
|
||||
frame_modes={modes}
|
||||
active_mode={active_mode}
|
||||
on_change={id => {
|
||||
set_active_mode(id);
|
||||
set_selected_plot(null);
|
||||
}}
|
||||
/>
|
||||
<Category_Filter
|
||||
categories={categories}
|
||||
active_category={active_category}
|
||||
on_change={set_active_category}
|
||||
/>
|
||||
{frame_mode && <>
|
||||
<Typography color="text.secondary">{frame_mode.description}</Typography>
|
||||
<Gallery_Page
|
||||
cases={visible_cases}
|
||||
frame_mode={frame_mode}
|
||||
streams_paused={streams_paused}
|
||||
on_open_inspector={set_selected_plot}
|
||||
/>
|
||||
</>}
|
||||
</>}
|
||||
</Stack>
|
||||
</Container>
|
||||
</Box>;
|
||||
|
||||
return <Box sx={{height: "100dvh", display: "flex", flexDirection: "column", overflow: "hidden"}}>
|
||||
<Gallery_Toolbar
|
||||
socket_state={socket_state}
|
||||
socket_message={socket_message}
|
||||
streams_paused={streams_paused}
|
||||
on_toggle_streams={() => set_streams_paused(value => !value)}
|
||||
/>
|
||||
<Box component="main" sx={{flex: 1, minHeight: 0}}>
|
||||
<Group
|
||||
id="gallery-inspector-layout"
|
||||
orientation={compact_layout ? "vertical" : "horizontal"}
|
||||
defaultLayout={selected_plot ? {gallery: 60, inspector: 40} : {gallery: 100}}
|
||||
style={{height: "100%"}}
|
||||
>
|
||||
<Panel id="gallery" minSize={compact_layout ? "220px" : "420px"}>
|
||||
{gallery}
|
||||
</Panel>
|
||||
{selected_plot && <>
|
||||
<Separator
|
||||
id="inspector-resize-handle"
|
||||
aria-label="拖动调整图库和操作面板占比"
|
||||
style={{
|
||||
background: "rgba(89, 214, 197, .32)",
|
||||
width: compact_layout ? "100%" : 8,
|
||||
height: compact_layout ? 8 : "100%",
|
||||
cursor: compact_layout ? "row-resize" : "col-resize",
|
||||
touchAction: "none",
|
||||
transition: "background-color 120ms ease",
|
||||
}}
|
||||
/>
|
||||
<Panel
|
||||
id="inspector"
|
||||
defaultSize="40%"
|
||||
minSize={compact_layout ? "260px" : "360px"}
|
||||
maxSize="72%"
|
||||
collapsible
|
||||
collapsedSize={0}
|
||||
>
|
||||
<Inspector_Panel
|
||||
session={selected_plot.session}
|
||||
active_tab={selected_inspector_tab}
|
||||
on_change_tab={set_selected_inspector_tab}
|
||||
on_close={() => set_selected_plot(null)}
|
||||
/>
|
||||
</Panel>
|
||||
</>}
|
||||
</Group>
|
||||
</Box>
|
||||
</Box>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,85 @@
|
||||
import {Background,Controls,MiniMap,ReactFlow} from "@xyflow/react";import {Box} from "@mui/material";import {useEffect,useMemo,useState} from "react";import type {Gallery_Captured_Frame,Gallery_Node_Statistics,Gallery_Render_Plan} from "../protocol/gallery_types";import {build_dag_model} from "./dag_model";import {layout_dag} from "./dag_layout";import {Dag_Node} from "./dag_node";import type {Dag_View_Model} from "./dag_types";
|
||||
const node_types={dag_node:Dag_Node};
|
||||
export function Render_Dag({plan,frame=null,statistics=[],selected_node_id,on_select_node}:{plan:Gallery_Render_Plan;frame?:Gallery_Captured_Frame|null;statistics?:Gallery_Node_Statistics[];selected_node_id:number|null;on_select_node:(id:number)=>void}) {const source=useMemo(()=>build_dag_model(plan,frame,statistics,selected_node_id),[plan,frame,statistics,selected_node_id]);const [model,set_model]=useState<Dag_View_Model>(source);useEffect(()=>{let current=true;void layout_dag(source).then(value=>current&&set_model(value));return()=>{current=false;};},[source]);return <Box sx={{height:520,border:"1px solid rgba(132,179,206,.2)",borderRadius:2,overflow:"hidden"}}><ReactFlow nodes={model.nodes} edges={model.edges} nodeTypes={node_types} fitView nodesDraggable={false} nodesConnectable={false} onNodeClick={(_,node)=>on_select_node(Number(node.id))}><Background/><MiniMap/><Controls/></ReactFlow></Box>;}
|
||||
import {Background, Controls, MiniMap, ReactFlow} from "@xyflow/react";
|
||||
import {Box, CircularProgress} from "@mui/material";
|
||||
import {useEffect, useMemo, useState} from "react";
|
||||
import type {
|
||||
Gallery_Captured_Frame,
|
||||
Gallery_Node_Statistics,
|
||||
Gallery_Render_Plan,
|
||||
} from "../protocol/gallery_types";
|
||||
import {layout_dag} from "./dag_layout";
|
||||
import {build_dag_model} from "./dag_model";
|
||||
import {Dag_Node} from "./dag_node";
|
||||
import type {Dag_View_Model} from "./dag_types";
|
||||
|
||||
const node_types = {dag_node: Dag_Node};
|
||||
|
||||
export function Render_Dag({
|
||||
plan,
|
||||
frame = null,
|
||||
statistics = [],
|
||||
selected_node_id,
|
||||
on_select_node,
|
||||
}: {
|
||||
plan: Gallery_Render_Plan;
|
||||
frame?: Gallery_Captured_Frame | null;
|
||||
statistics?: Gallery_Node_Statistics[];
|
||||
selected_node_id: number | null;
|
||||
on_select_node: (id: number) => void;
|
||||
}) {
|
||||
const source = useMemo(
|
||||
() => build_dag_model(plan, frame, statistics, selected_node_id),
|
||||
[plan, frame, statistics, selected_node_id],
|
||||
);
|
||||
const layout_identity = useMemo(
|
||||
() => `${plan.nodes.map(node => node.node_id).join(",")}:${plan.edges.map(edge => `${edge.from}>${edge.to}`).join(",")}`,
|
||||
[plan],
|
||||
);
|
||||
const [layout, set_layout] = useState<{identity: string; model: Dag_View_Model} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let current = true;
|
||||
void layout_dag(source).then(value => {
|
||||
if (current)
|
||||
set_layout({identity: layout_identity, model: value});
|
||||
});
|
||||
return () => {
|
||||
current = false;
|
||||
};
|
||||
// Selection and telemetry only decorate nodes; they must not restart ELK layout.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [layout_identity]);
|
||||
|
||||
const laid_out = layout?.identity === layout_identity ? layout.model : null;
|
||||
const model = useMemo(() => {
|
||||
if (!laid_out)
|
||||
return null;
|
||||
const positions = new Map(laid_out.nodes.map(node => [node.id, node.position]));
|
||||
return {
|
||||
...source,
|
||||
nodes: source.nodes.map(node => ({
|
||||
...node,
|
||||
position: positions.get(node.id) ?? node.position,
|
||||
})),
|
||||
};
|
||||
}, [source, laid_out]);
|
||||
|
||||
return <Box sx={{height: 520, border: "1px solid rgba(132,179,206,.2)", borderRadius: 2, overflow: "hidden"}}>
|
||||
{!model
|
||||
? <Box sx={{height: "100%", display: "grid", placeItems: "center"}}><CircularProgress size={28}/></Box>
|
||||
: <ReactFlow
|
||||
key={layout_identity}
|
||||
nodes={model.nodes}
|
||||
edges={model.edges}
|
||||
nodeTypes={node_types}
|
||||
fitView
|
||||
onlyRenderVisibleElements
|
||||
nodesDraggable={false}
|
||||
nodesConnectable={false}
|
||||
onNodeClick={(_, node) => on_select_node(Number(node.id))}
|
||||
>
|
||||
<Background/>
|
||||
<MiniMap/>
|
||||
<Controls/>
|
||||
</ReactFlow>}
|
||||
</Box>;
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import CloseIcon from "@mui/icons-material/Close";import RefreshIcon from "@mui/icons-material/Refresh";import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||
import {Box,Drawer,IconButton,Stack,Tooltip,Typography} from "@mui/material";import {useState,useSyncExternalStore} from "react";import type {Gallery_Plot_Session} from "../session/gallery_plot_session";import {EMPTY_PLOT_SNAPSHOT} from "../session/gallery_plot_snapshot";import {Inspector_Tabs} from "./inspector_tabs";import {Controls_Panel} from "./controls_panel";import {Actions_Panel} from "./actions_panel";import {Observer_Panel} from "./observer_panel";import {Performance_Panel} from "./performance_panel";import {Capture_Panel} from "../capture/capture_panel";import {Render_Dag} from "../dag/render_dag";import {Empty_State} from "../common/empty_state";
|
||||
const empty_subscribe=()=>()=>undefined;const empty_snapshot=()=>EMPTY_PLOT_SNAPSHOT;
|
||||
export function Inspector_Drawer({open,session,active_tab,on_change_tab,on_close}:{open:boolean;session:Gallery_Plot_Session|null;active_tab:string;on_change_tab:(tab:string)=>void;on_close:()=>void}) {const snapshot=useSyncExternalStore(session?.subscribe??empty_subscribe,session?.get_snapshot??empty_snapshot,session?.get_snapshot??empty_snapshot);const [selected_node_id,set_selected_node_id]=useState<number|null>(null);let body=null;if(session){if(active_tab==="controls")body=<Controls_Panel controls={snapshot.controls} on_patch={(target,patch)=>session.patch(target,patch)}/>;else if(active_tab==="actions")body=<Actions_Panel actions={snapshot.actions} on_action={(action,argument)=>{session.action(action,argument);setTimeout(()=>session.request_frame(performance.now(),true),40);}}/>;else if(active_tab==="observer")body=<Observer_Panel telemetry={snapshot.telemetry}/>;else if(active_tab==="performance")body=<Performance_Panel telemetry={snapshot.telemetry}/>;else if(active_tab==="performance_capture")body=snapshot.performance_capture?<Capture_Panel capture={snapshot.performance_capture} on_capture={(action,count)=>{session.action(action,count);setTimeout(()=>session.request_frame(performance.now(),true),40);}}/>:<Empty_State message="尚无 Performance Capture 数据"/>;else if(active_tab==="render_plan")body=snapshot.render_plan?<Render_Dag plan={snapshot.render_plan} selected_node_id={selected_node_id} on_select_node={set_selected_node_id}/>:<Empty_State message="场景尚未编译 Render Plan"/>;}return <Drawer anchor="right" open={open} onClose={on_close} PaperProps={{sx:{width:{xs:"100%",md:"min(900px,72vw)"}}}}><Stack direction="row" alignItems="center" sx={{p:2,pb:1}}><Box flex={1}><Typography variant="overline" color="primary.main">{session&&`${session.frame_mode.strategy} / ${session.gallery_case.component}`}</Typography><Typography variant="h5">{session?.gallery_case.title??"Inspector"}</Typography></Box><Tooltip title="重置监测"><IconButton onClick={()=>session?.reset_monitoring()}><RestartAltIcon/></IconButton></Tooltip><Tooltip title="刷新当前页"><IconButton onClick={()=>session?.refresh()}><RefreshIcon/></IconButton></Tooltip><IconButton onClick={on_close}><CloseIcon/></IconButton></Stack><Inspector_Tabs active_tab={active_tab} on_change={on_change_tab}/><Box sx={{p:2,overflow:"auto",flex:1}}>{body}</Box></Drawer>;}
|
||||
@@ -0,0 +1,90 @@
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import RefreshIcon from "@mui/icons-material/Refresh";
|
||||
import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||
import {Box, IconButton, Paper, Stack, Tooltip, Typography} from "@mui/material";
|
||||
import {useEffect, useState, useSyncExternalStore} from "react";
|
||||
import {Capture_Panel} from "../capture/capture_panel";
|
||||
import {Empty_State} from "../common/empty_state";
|
||||
import {Render_Dag} from "../dag/render_dag";
|
||||
import type {Gallery_Plot_Session} from "../session/gallery_plot_session";
|
||||
import {Actions_Panel} from "./actions_panel";
|
||||
import {Controls_Panel} from "./controls_panel";
|
||||
import {Inspector_Tabs} from "./inspector_tabs";
|
||||
import {Observer_Panel} from "./observer_panel";
|
||||
import {Performance_Panel} from "./performance_panel";
|
||||
|
||||
export function Inspector_Panel({
|
||||
session,
|
||||
active_tab,
|
||||
on_change_tab,
|
||||
on_close,
|
||||
}: {
|
||||
session: Gallery_Plot_Session;
|
||||
active_tab: string;
|
||||
on_change_tab: (tab: string) => void;
|
||||
on_close: () => void;
|
||||
}) {
|
||||
const snapshot = useSyncExternalStore(
|
||||
session.subscribe,
|
||||
session.get_snapshot,
|
||||
session.get_snapshot,
|
||||
);
|
||||
const [selected_node_id, set_selected_node_id] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => set_selected_node_id(null), [session]);
|
||||
|
||||
let body = null;
|
||||
if (active_tab === "controls")
|
||||
body = <Controls_Panel controls={snapshot.controls} on_patch={(target, patch) => session.patch(target, patch)}/>;
|
||||
else if (active_tab === "actions")
|
||||
body = <Actions_Panel actions={snapshot.actions} on_action={(action, argument) => {
|
||||
session.action(action, argument);
|
||||
setTimeout(() => session.request_frame(performance.now(), true), 40);
|
||||
}}/>;
|
||||
else if (active_tab === "observer")
|
||||
body = <Observer_Panel telemetry={snapshot.telemetry}/>;
|
||||
else if (active_tab === "performance")
|
||||
body = <Performance_Panel telemetry={snapshot.telemetry}/>;
|
||||
else if (active_tab === "performance_capture")
|
||||
body = snapshot.performance_capture
|
||||
? <Capture_Panel capture={snapshot.performance_capture} on_capture={(action, count) => {
|
||||
session.action(action, count);
|
||||
setTimeout(() => session.request_frame(performance.now(), true), 40);
|
||||
}}/>
|
||||
: <Empty_State message="尚无 Performance Capture 数据"/>;
|
||||
else if (active_tab === "render_plan")
|
||||
body = snapshot.render_plan
|
||||
? <Render_Dag
|
||||
plan={snapshot.render_plan}
|
||||
selected_node_id={selected_node_id}
|
||||
on_select_node={set_selected_node_id}
|
||||
/>
|
||||
: <Empty_State message="场景尚未编译 Render Plan"/>;
|
||||
|
||||
return <Paper
|
||||
square
|
||||
elevation={0}
|
||||
data-testid="inspector-panel"
|
||||
sx={{height: "100%", minWidth: 0, display: "flex", flexDirection: "column", overflow: "hidden"}}
|
||||
>
|
||||
<Stack direction="row" alignItems="center" sx={{p: 2, pb: 1}}>
|
||||
<Box minWidth={0} flex={1}>
|
||||
<Typography variant="overline" color="primary.main" noWrap>
|
||||
{`${session.frame_mode.strategy} / ${session.gallery_case.component}`}
|
||||
</Typography>
|
||||
<Typography variant="h5" noWrap>{session.gallery_case.title}</Typography>
|
||||
</Box>
|
||||
<Tooltip title="重置监测">
|
||||
<IconButton onClick={() => session.reset_monitoring()}><RestartAltIcon/></IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="刷新当前页">
|
||||
<IconButton onClick={() => session.refresh()}><RefreshIcon/></IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="隐藏操作面板">
|
||||
<IconButton aria-label="隐藏操作面板" onClick={on_close}><CloseIcon/></IconButton>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
<Inspector_Tabs active_tab={active_tab} on_change={on_change_tab}/>
|
||||
<Box sx={{p: 2, overflow: "auto", flex: 1, minHeight: 0}}>{body}</Box>
|
||||
</Paper>;
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/theme.ts","./src/capture/capture_frames.tsx","./src/capture/capture_panel.tsx","./src/capture/capture_sessions.tsx","./src/capture/capture_toolbar.tsx","./src/capture/frame_summary.tsx","./src/capture/node_detail.tsx","./src/capture/plan_comparison.tsx","./src/capture/worker_timeline.tsx","./src/common/copy_button.tsx","./src/common/empty_state.tsx","./src/common/json_viewer.tsx","./src/common/metric_grid.tsx","./src/common/metric_value.tsx","./src/common/status_chip.tsx","./src/dag/dag_layout.ts","./src/dag/dag_legend.tsx","./src/dag/dag_model.ts","./src/dag/dag_node.tsx","./src/dag/dag_types.ts","./src/dag/render_dag.tsx","./src/gallery/category_filter.tsx","./src/gallery/frame_mode_tabs.tsx","./src/gallery/gallery_page.tsx","./src/gallery/gallery_summary.tsx","./src/gallery/gallery_toolbar.tsx","./src/gallery/plot_card.tsx","./src/hooks/use_element_size.ts","./src/hooks/use_gallery_catalog.ts","./src/hooks/use_plot_session.ts","./src/inspector/actions_panel.tsx","./src/inspector/control_field.tsx","./src/inspector/controls_panel.tsx","./src/inspector/inspector_drawer.tsx","./src/inspector/inspector_tabs.tsx","./src/inspector/observer_panel.tsx","./src/inspector/performance_panel.tsx","./src/plot/kernel_observer_summary.tsx","./src/plot/performance_strip.tsx","./src/plot/plot_canvas.tsx","./src/plot/plot_status.tsx","./src/protocol/format.ts","./src/protocol/gallery_descriptor.ts","./src/protocol/gallery_messages.ts","./src/protocol/gallery_parser.ts","./src/protocol/gallery_types.ts","./src/protocol/pixel_frame.ts","./src/runtime/client_performance.ts","./src/runtime/frame_request_controller.ts","./src/runtime/pixel_presenter.ts","./src/session/gallery_plot_session.ts","./src/session/gallery_plot_snapshot.ts","./src/transport/catalog_loader.ts","./src/transport/gallery_socket.ts","./tests/setup.ts","./tests/components/control_field.test.tsx","./tests/dag/dag_model.test.ts","./tests/e2e/gallery.spec.ts","./tests/protocol/gallery_parser.test.ts","./tests/protocol/pixel_frame.test.ts","./tests/runtime/client_performance.test.ts","./tests/runtime/frame_request_controller.test.ts","./vite.config.ts"],"version":"5.9.3"}
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/theme.ts","./src/capture/capture_frames.tsx","./src/capture/capture_panel.tsx","./src/capture/capture_sessions.tsx","./src/capture/capture_toolbar.tsx","./src/capture/frame_summary.tsx","./src/capture/node_detail.tsx","./src/capture/plan_comparison.tsx","./src/capture/worker_timeline.tsx","./src/common/copy_button.tsx","./src/common/empty_state.tsx","./src/common/json_viewer.tsx","./src/common/metric_grid.tsx","./src/common/metric_value.tsx","./src/common/status_chip.tsx","./src/dag/dag_layout.ts","./src/dag/dag_legend.tsx","./src/dag/dag_model.ts","./src/dag/dag_node.tsx","./src/dag/dag_types.ts","./src/dag/render_dag.tsx","./src/gallery/category_filter.tsx","./src/gallery/frame_mode_tabs.tsx","./src/gallery/gallery_page.tsx","./src/gallery/gallery_summary.tsx","./src/gallery/gallery_toolbar.tsx","./src/gallery/plot_card.tsx","./src/hooks/use_element_size.ts","./src/hooks/use_gallery_catalog.ts","./src/hooks/use_plot_session.ts","./src/inspector/actions_panel.tsx","./src/inspector/control_field.tsx","./src/inspector/controls_panel.tsx","./src/inspector/inspector_panel.tsx","./src/inspector/inspector_tabs.tsx","./src/inspector/observer_panel.tsx","./src/inspector/performance_panel.tsx","./src/plot/kernel_observer_summary.tsx","./src/plot/performance_strip.tsx","./src/plot/plot_canvas.tsx","./src/plot/plot_status.tsx","./src/protocol/format.ts","./src/protocol/gallery_descriptor.ts","./src/protocol/gallery_messages.ts","./src/protocol/gallery_parser.ts","./src/protocol/gallery_types.ts","./src/protocol/pixel_frame.ts","./src/runtime/client_performance.ts","./src/runtime/frame_request_controller.ts","./src/runtime/pixel_presenter.ts","./src/session/gallery_plot_session.ts","./src/session/gallery_plot_snapshot.ts","./src/transport/catalog_loader.ts","./src/transport/gallery_socket.ts","./tests/setup.ts","./tests/components/control_field.test.tsx","./tests/dag/dag_model.test.ts","./tests/e2e/gallery.spec.ts","./tests/protocol/gallery_parser.test.ts","./tests/protocol/pixel_frame.test.ts","./tests/runtime/client_performance.test.ts","./tests/runtime/frame_request_controller.test.ts","./vite.config.ts"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user