统一 2D/3D Renderable 继承链与实时数据策略

This commit is contained in:
2026-08-14 18:47:36 +08:00
parent bb64801679
commit cb32f72bc9
43 changed files with 1010 additions and 528 deletions
@@ -64,13 +64,12 @@ struct Real_Time_Data_Test_Renderable : Renderable_Test_Harness {
std::atomic<int> render_count{};
};
using Real_Time_Data_Test_Bridge = Observer_State<Frame_Strategy_Real_Time_Data_Observer, Real_Time_Data_Test_Time_Source>;
using Real_Time_Data_Test_Latest = Latest_Real_Time_Data<int, std::mutex, Real_Time_Data_Test_Bridge>;
using Real_Time_Data_Test_History = History_Real_Time_Data<int, std::vector<int>, std::mutex, Real_Time_Data_Test_Bridge>;
using Real_Time_Data_Test_Attachment = Attach_Real_Time_Data<Real_Time_Data_Test_Renderable, Real_Time_Data_Test_Latest, Real_Time_Data_Test_History>;
using Real_Time_Data_Test_Attachment = Attach_Real_Time_Data<Real_Time_Data_Test_Renderable, Real_Time_Data_Test_History>;
class Real_Time_Data_Render_Probe : public Renderable_Test_Harness {
class Bound_Latest : public Latest_Real_Time_Data<int> {
class Bound_History : public History_Real_Time_Data<int> {
public:
explicit Bound_Latest(Renderable_Base& owner)
explicit Bound_History(Renderable_Base& owner)
: binding_(bind_renderable(owner)) {}
private:
Real_Time_Data_Binding binding_;
@@ -79,9 +78,9 @@ public:
explicit Real_Time_Data_Render_Probe() : data(*this) {}
void prepare_for_test(const Prepare_Render_Context& context) override {
const auto& value = context.frame.render_state.get(data);
rendered_value = value.value_or(-1);
rendered_value = value.empty() ? -1 : value.back();
}
Bound_Latest data;
Bound_History data;
int rendered_value{-1};
};
TEST(real_time_data_render_state_test, publishes_once_at_frame_boundary_and_reads_without_snapshot) {
@@ -102,15 +101,6 @@ TEST(real_time_data_render_state_test, publishes_once_at_frame_boundary_and_read
scene.wait_for_render();
EXPECT_EQ(renderable->rendered_value, 2);
}
TEST(latest_real_time_data_test, retains_only_latest_value) {
Latest_Real_Time_Data<int> data;
data.update(1);
data.update(2);
ASSERT_TRUE(data.snapshot());
EXPECT_EQ(*data.snapshot(), 2);
EXPECT_EQ(data.update_state().retained_value_count, 1);
EXPECT_EQ(data.revision(), 2);
}
TEST(history_real_time_data_test, retains_all_values_until_explicit_discard) {
using Observer = Observer_State<Disabled_Observer, Real_Time_Data_Test_Time_Source>;
History_Real_Time_Data<int, std::vector<int>, std::mutex, Observer> data;
@@ -135,10 +125,8 @@ TEST(history_real_time_data_test, retains_latest_requested_value_count) {
}
TEST(real_time_data_attachment_test, binds_updates_to_renderable_frame_strategy) {
Scene2D_Context<> scene;
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(latest, history));
renderable->discard_stale_frame_on_latest_data_update(true);
auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(history));
{
auto attach = scene.attach_builder();
attach.attach(renderable);
@@ -147,17 +135,15 @@ TEST(real_time_data_attachment_test, binds_updates_to_renderable_frame_strategy)
auto frame = scene.frame_control.acquire_painter();
ASSERT_TRUE(frame);
}
latest->update(1);
EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1);
auto frame = scene.frame_control.acquire_renderer();
EXPECT_FALSE(frame);
history->update(1);
EXPECT_EQ(renderable->real_time_data_attachment_state().total_update_count,
1U);
}
TEST(real_time_data_attachment_test, discards_history_older_than_one_frame_interval) {
using Strategy = Low_Latency_Strategy<Scene2D_Frame_Data>;
Scene2D_Context<Strategy> scene(Observer_State<>{}, Strategy::Configuration{100'000'000.0});
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(latest, history));
auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(history));
renderable->real_time_data_discard_mode = Real_Time_Data_Discard_Mode::retain_frame_interval;
history->update(1);
history->update(2);
@@ -172,22 +158,21 @@ TEST(real_time_data_attachment_test, discards_history_older_than_one_frame_inter
EXPECT_EQ(renderable->render_count.load(), 1);
}
TEST(real_time_data_attachment_test, rejects_null_real_time_data_source) {
std::shared_ptr<Real_Time_Data_Test_Latest> latest;
EXPECT_THROW((With_Real_Time_Data(latest)), std::invalid_argument);
std::shared_ptr<Real_Time_Data_Test_History> history;
EXPECT_THROW((With_Real_Time_Data(history)), std::invalid_argument);
}
using Real_Time_Data_Test_Latest_Attachment = Attach_Real_Time_Data<Real_Time_Data_Test_Renderable, Real_Time_Data_Test_Latest>;
using Real_Time_Data_Test_History_Attachment = Attach_Real_Time_Data<Real_Time_Data_Test_Renderable, Real_Time_Data_Test_History>;
TEST(real_time_data_attachment_test, rejects_binding_one_source_to_multiple_renderables) {
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto first = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
EXPECT_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))), std::logic_error);
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto first = renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history));
EXPECT_THROW((renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history))), std::logic_error);
first.reset();
EXPECT_NO_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))));
EXPECT_NO_THROW((renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history))));
}
TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_strategy) {
Scene2D_Context<> scene;
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
renderable->discard_stale_frame_on_latest_data_update(true);
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto renderable = renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history));
{
auto attach = scene.attach_builder();
attach.attach(renderable);
@@ -196,8 +181,8 @@ TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_
auto frame = scene.frame_control.acquire_painter();
ASSERT_TRUE(frame);
}
latest->update(1);
EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1);
history->update(1);
const auto before_detach = scene.frame_control.state().real_time_data_update_sequence;
wait_renderable_edit(scene, [renderable](auto& editor) {
editor.detach(renderable);
});
@@ -205,19 +190,20 @@ TEST(real_time_data_attachment_test, detached_renderable_no_longer_drives_frame_
auto frame = scene.frame_control.acquire_painter();
ASSERT_TRUE(frame);
}
latest->update(2);
EXPECT_EQ(scene.frame_control.counter_statistics().manually_discarded_frame_count, 1);
history->update(2);
EXPECT_EQ(scene.frame_control.state().real_time_data_update_sequence,
before_detach);
}
TEST(real_time_data_attachment_test, unbind_is_synchronized_with_concurrent_updates) {
Scene2D_Context<> scene;
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto renderable = renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history));
std::atomic<bool> running{true};
std::atomic<bool> updated{};
std::thread updater([&] {
int value{};
while (running.load(std::memory_order_acquire)) {
latest->update(++value);
history->update(++value);
updated.store(true, std::memory_order_release);
}
});
@@ -227,20 +213,19 @@ TEST(real_time_data_attachment_test, unbind_is_synchronized_with_concurrent_upda
renderable.reset();
running.store(false, std::memory_order_release);
updater.join();
EXPECT_TRUE(latest->revision() > 0);
EXPECT_TRUE(history->revision() > 0);
}
TEST(real_time_data_attachment_test, runtime_discard_configuration_is_safe_during_updates_and_rendering) {
Scene2D_Context<> scene;
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(latest, history));
auto renderable = renderive_Owner<Real_Time_Data_Test_Attachment>::make(With_Real_Time_Data(history));
{
auto attach = scene.attach_builder();
attach.attach(renderable);
}
std::thread updater([&] {
for (int value = 0; value < 1000; ++value) {
latest->update(value);
history->update(value, 1);
}
});
std::thread configurator([&] {
@@ -256,40 +241,40 @@ TEST(real_time_data_attachment_test, runtime_discard_configuration_is_safe_durin
}
updater.join();
configurator.join();
EXPECT_EQ(latest->revision(), 1000);
EXPECT_EQ(history->revision(), 1000);
}
TEST(real_time_data_attachment_test, ignores_updates_after_bound_scene_is_destroyed) {
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
renderive_Owner<Real_Time_Data_Test_Latest_Attachment> renderable;
auto history = std::make_shared<Real_Time_Data_Test_History>();
renderive_Owner<Real_Time_Data_Test_History_Attachment> renderable;
{
Scene2D_Context<> scene;
renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
renderable = renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history));
{
auto attach = scene.attach_builder();
attach.attach(renderable);
}
latest->update(1);
history->update(1);
EXPECT_EQ(scene.frame_control.state().real_time_data_update_sequence, 1);
}
EXPECT_NO_THROW(latest->update(2));
EXPECT_NO_THROW(history->update(2));
EXPECT_THROW(renderable->scene_for_test(), std::logic_error);
renderable.reset();
}
TEST(real_time_data_attachment_test, source_binding_is_owned_by_renderable_lifetime) {
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
EXPECT_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))), std::logic_error);
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto renderable = renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history));
EXPECT_THROW((renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history))), std::logic_error);
renderable.reset();
EXPECT_NO_THROW((renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest))));
EXPECT_NO_THROW((renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history))));
}
TEST(real_time_data_attachment_test, attachment_owns_real_time_data_sources) {
Scene2D_Context<> scene;
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
std::weak_ptr<Real_Time_Data_Test_Latest> weak = latest;
auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
latest.reset();
auto history = std::make_shared<Real_Time_Data_Test_History>();
std::weak_ptr<Real_Time_Data_Test_History> weak = history;
auto renderable = renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history));
history.reset();
EXPECT_FALSE(weak.expired());
renderable->real_time_data<Real_Time_Data_Test_Latest>().update(1);
renderable->real_time_data<Real_Time_Data_Test_History>().update(1);
renderable.reset();
EXPECT_TRUE(weak.expired());
}
@@ -314,8 +299,8 @@ TEST(real_time_data_attachment_test, frame_strategy_observer_can_reacquire_scene
Real_Time_Data_Reentrant_Frame_Observer_State observer_state(observer, Real_Time_Data_Test_Time_Source{});
Real_Time_Data_Reentrant_Strategy::Configuration configuration{60.0};
Scene2D_Context<Real_Time_Data_Reentrant_Strategy> scene(std::move(observer_state), configuration);
auto latest = std::make_shared<Real_Time_Data_Test_Latest>();
auto renderable = renderive_Owner<Real_Time_Data_Test_Latest_Attachment>::make(With_Real_Time_Data(latest));
auto history = std::make_shared<Real_Time_Data_Test_History>();
auto renderable = renderive_Owner<Real_Time_Data_Test_History_Attachment>::make(With_Real_Time_Data(history));
{
auto attach = scene.attach_builder();
attach.attach(renderable);
@@ -325,7 +310,7 @@ TEST(real_time_data_attachment_test, frame_strategy_observer_can_reacquire_scene
reacquired.store(&renderable->scene_for_test() == &scene,
std::memory_order_release);
};
latest->update(1);
history->update(1);
EXPECT_TRUE(reacquired.load(std::memory_order_acquire));
}
class Real_Time_Data_Failing_Memory_Resource : public std::pmr::memory_resource {
@@ -351,5 +336,8 @@ TEST(history_real_time_data_test, failed_timestamp_allocation_rolls_back_value_a
EXPECT_EQ(state.retained_value_count, 0);
EXPECT_EQ(state.update_time_ns, 0);
}
static_assert(Unique_Real_Time_Data_Types<Real_Time_Data_Test_Latest, Real_Time_Data_Test_History>);
static_assert(!Unique_Real_Time_Data_Types<Real_Time_Data_Test_Latest, Real_Time_Data_Test_Latest>);
using Real_Time_Data_Test_Other_History = History_Real_Time_Data<long>;
static_assert(Unique_Real_Time_Data_Types<Real_Time_Data_Test_History,
Real_Time_Data_Test_Other_History>);
static_assert(!Unique_Real_Time_Data_Types<Real_Time_Data_Test_History,
Real_Time_Data_Test_History>);
@@ -1,12 +1,8 @@
#include <gtest/gtest.h>
#include "renderive/real_time_data/Real_Time_Data.hpp"
static_assert(std::derived_from<Latest_Real_Time_Data<int>, Real_Time_Data_Base>);
static_assert(std::derived_from<History_Real_Time_Data<int>, Real_Time_Data_Base>);
TEST(real_time_data_base_test, exposes_runtime_retention) {
Latest_Real_Time_Data<int> latest;
History_Real_Time_Data<int> history;
Real_Time_Data_Base* latest_base = &latest;
Real_Time_Data_Base* history_base = &history;
EXPECT_EQ(latest_base->retention(), Real_Time_Data_Retention::latest);
EXPECT_EQ(history_base->retention(), Real_Time_Data_Retention::history);
}