接口优化

This commit is contained in:
2026-08-13 09:16:54 +08:00
parent b964dcdcd9
commit 9e598a0318
12 changed files with 182 additions and 229 deletions
+72 -104
View File
@@ -336,44 +336,40 @@ bool Scene_Base::Mutation::ready() const {
std::lock_guard lock(state_->mutex);
return state_->completed;
}
void Scene_Base::Mutation::then(Mutation::Callback callback) const {
if (!callback)
return;
if (!state_) {
try {
callback({});
} catch (...) {
}
return;
}
std::exception_ptr exception;
{
std::lock_guard lock(state_->mutex);
if (!state_->completed) {
state_->callbacks.push_back(std::move(callback));
return;
}
exception = state_->exception;
}
try {
callback(exception);
} catch (...) {
}
}
Scene_Base::Attach_Builder::Attach_Builder(Scene_Base& scene)
: scene_(&scene), task_lock_(scene.task_mutex_) {
if (scene.runtime_started_)
throw std::logic_error("scene attach builder is only available before runtime starts");
}
void Scene_Base::Attach_Builder::attach_renderable(Renderable renderable,
Attach_Relationships relationships) {
void Scene_Base::Attach_Builder::attach(Renderable renderable,
Attach_Relationships relationships) {
std::lock_guard lock(scene_->model_mutex_);
scene_->apply_attach_renderable_locked(std::move(renderable), std::move(relationships));
}
void Scene_Base::Attach_Builder::set_display_parent(const Renderable& child,
const Renderable& parent) {
std::lock_guard lock(scene_->model_mutex_);
scene_->apply_set_display_parent_locked(child, parent);
}
void Scene_Base::Attach_Builder::add_display_parent(const Renderable& child,
const Renderable& parent) {
std::lock_guard lock(scene_->model_mutex_);
scene_->apply_add_display_parent_locked(child, parent);
}
void Scene_Base::Attach_Builder::clear_display_parent(const Renderable& child) {
std::lock_guard lock(scene_->model_mutex_);
scene_->apply_clear_display_parent_locked(child);
}
void Scene_Base::Attach_Builder::set_dependency_parent(const Renderable& child,
const Renderable& parent) {
std::lock_guard lock(scene_->model_mutex_);
scene_->apply_set_dependency_parent_locked(child, parent);
}
void Scene_Base::Attach_Builder::add_dependency_parent(const Renderable& child,
const Renderable& parent) {
std::lock_guard lock(scene_->model_mutex_);
scene_->apply_add_dependency_parent_locked(child, parent);
}
void Scene_Base::Attach_Builder::clear_dependency_parent(const Renderable& child) {
std::lock_guard lock(scene_->model_mutex_);
scene_->apply_clear_dependency_parent_locked(child);
}
Scene_Base::Attach_Builder Scene_Base::attach_builder() {
return Attach_Builder(*this);
}
@@ -468,15 +464,6 @@ void Scene_Base::wait_for_render() {
if (exception)
std::rethrow_exception(exception);
}
void Scene_Base::wait_for_mutations() {
if (is_render_worker_thread())
throw std::logic_error("scene mutation wait is not allowed from render execution");
std::unique_lock<std::recursive_mutex> lock(task_mutex_);
render_completed_.wait(lock, [this] {
return mutation_queue_.empty() && !mutating_;
});
}
std::vector<Renderable_Id> Scene_Base::relationship_ids_locked(
const std::vector<Renderable>& renderables) const {
std::vector<Renderable_Id> ids;
@@ -490,28 +477,28 @@ std::vector<Renderable_Id> Scene_Base::relationship_ids_locked(
}
return ids;
}
Scene_Base::Mutation Scene_Base::enqueue_mutation(std::function<void()> operation,
Mutation_Callback callback) {
Scene_Base::Mutation Scene_Base::enqueue_mutation(std::function<void()> operation) {
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
return enqueue_mutation_locked(std::move(operation), std::move(callback));
return enqueue_mutation_locked(std::move(operation));
}
Scene_Base::Mutation Scene_Base::enqueue_mutation_locked(std::function<void()> operation,
Mutation_Callback callback) {
Scene_Base::Mutation Scene_Base::enqueue_mutation_locked(std::function<void()> operation) {
if (stop_)
throw std::logic_error("scene is shutting down");
runtime_started_ = true;
auto completion = std::make_shared<Mutation::State>(*this);
mutation_queue_.push_back({std::move(operation), std::move(callback), completion});
mutation_queue_.push_back({std::move(operation), completion});
task_ready_.notify_one();
return Mutation(std::move(completion));
}
void Scene_Base::complete_mutation(Mutation_Command& command,
std::exception_ptr exception) noexcept {
std::exception_ptr exception,
std::vector<Mutation::Callback>& callbacks) noexcept {
{
std::lock_guard lock(command.completion->mutex);
command.completion->exception = exception;
command.completion->completed = true;
command.completion->scene = nullptr;
callbacks = std::move(command.completion->callbacks);
}
command.completion->condition.notify_all();
}
@@ -639,80 +626,60 @@ void Scene_Base::apply_clear_dependency_parent_locked(const Renderable& child) {
notify_model_dirty();
}
Scene_Base::Mutation Scene_Base::attach_renderable(Renderable renderable,
Attach_Relationships relationships,
Mutation_Callback callback) {
Attach_Relationships relationships) {
if (!renderable)
throw std::invalid_argument("renderable is null");
validate_renderable_scene(*renderable);
return enqueue_mutation(
[this, renderable = std::move(renderable), relationships = std::move(relationships)]() mutable {
apply_attach_renderable_locked(std::move(renderable), std::move(relationships));
},
std::move(callback));
});
}
Scene_Base::Mutation Scene_Base::detach_renderable(const Renderable& renderable,
Mutation_Callback callback) {
Scene_Base::Mutation Scene_Base::detach_renderable(const Renderable& renderable) {
if (!renderable)
throw std::invalid_argument("renderable is null");
validate_renderable_scene(*renderable);
return enqueue_mutation([this, renderable] { apply_detach_renderable_locked(renderable); },
std::move(callback));
return enqueue_mutation([this, renderable] { apply_detach_renderable_locked(renderable); });
}
Scene_Base::Mutation Scene_Base::set_display_parent(const Renderable& child,
const Renderable& parent,
Mutation_Callback callback) {
Scene_Base::Mutation Scene_Base::set_parent(Relationship relationship,
const Renderable& child,
const Renderable& parent) {
if (!child || !parent)
throw std::invalid_argument("display relationship endpoint is null");
throw std::invalid_argument("topology relationship endpoint is null");
validate_renderable_scene(*child);
validate_renderable_scene(*parent);
return enqueue_mutation([this, child, parent] { apply_set_display_parent_locked(child, parent); },
std::move(callback));
return enqueue_mutation([this, relationship, child, parent] {
if (relationship == Relationship::display)
apply_set_display_parent_locked(child, parent);
else
apply_set_dependency_parent_locked(child, parent);
});
}
Scene_Base::Mutation Scene_Base::add_display_parent(const Renderable& child,
const Renderable& parent,
Mutation_Callback callback) {
Scene_Base::Mutation Scene_Base::add_parent(Relationship relationship,
const Renderable& child,
const Renderable& parent) {
if (!child || !parent)
throw std::invalid_argument("display relationship endpoint is null");
throw std::invalid_argument("topology relationship endpoint is null");
validate_renderable_scene(*child);
validate_renderable_scene(*parent);
return enqueue_mutation([this, child, parent] { apply_add_display_parent_locked(child, parent); },
std::move(callback));
return enqueue_mutation([this, relationship, child, parent] {
if (relationship == Relationship::display)
apply_add_display_parent_locked(child, parent);
else
apply_add_dependency_parent_locked(child, parent);
});
}
Scene_Base::Mutation Scene_Base::clear_display_parent(const Renderable& child,
Mutation_Callback callback) {
Scene_Base::Mutation Scene_Base::clear_parents(Relationship relationship,
const Renderable& child) {
if (!child)
throw std::invalid_argument("display child is null");
throw std::invalid_argument("topology child is null");
validate_renderable_scene(*child);
return enqueue_mutation([this, child] { apply_clear_display_parent_locked(child); },
std::move(callback));
}
Scene_Base::Mutation Scene_Base::set_dependency_parent(const Renderable& child,
const Renderable& parent,
Mutation_Callback callback) {
if (!child || !parent)
throw std::invalid_argument("dependency relationship endpoint is null");
validate_renderable_scene(*child);
validate_renderable_scene(*parent);
return enqueue_mutation([this, child, parent] { apply_set_dependency_parent_locked(child, parent); },
std::move(callback));
}
Scene_Base::Mutation Scene_Base::add_dependency_parent(const Renderable& child,
const Renderable& parent,
Mutation_Callback callback) {
if (!child || !parent)
throw std::invalid_argument("dependency relationship endpoint is null");
validate_renderable_scene(*child);
validate_renderable_scene(*parent);
return enqueue_mutation([this, child, parent] { apply_add_dependency_parent_locked(child, parent); },
std::move(callback));
}
Scene_Base::Mutation Scene_Base::clear_dependency_parent(const Renderable& child,
Mutation_Callback callback) {
if (!child)
throw std::invalid_argument("dependency child is null");
validate_renderable_scene(*child);
return enqueue_mutation([this, child] { apply_clear_dependency_parent_locked(child); },
std::move(callback));
return enqueue_mutation([this, relationship, child] {
if (relationship == Relationship::display)
apply_clear_display_parent_locked(child);
else
apply_clear_dependency_parent_locked(child);
});
}
void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) {
validate_renderable_scene(renderable);
@@ -726,7 +693,7 @@ void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) {
static_cast<void>(enqueue_mutation_locked([this, owner = std::move(owner)] {
owner->reset_render_graph();
notify_model_dirty();
}, {}));
}));
}
void Scene_Base::publish_frame_state() {
auto task_lock = lock_render_idle();
@@ -1055,6 +1022,7 @@ void Scene_Base::render_loop() {
}
if (!mutations.empty()) {
std::vector<std::exception_ptr> exceptions(mutations.size());
std::vector<std::vector<Mutation::Callback>> callbacks(mutations.size());
{
std::lock_guard lock(model_mutex_);
for (std::size_t index = 0; index < mutations.size(); ++index) {
@@ -1066,16 +1034,16 @@ void Scene_Base::render_loop() {
}
}
for (std::size_t index = 0; index < mutations.size(); ++index)
complete_mutation(mutations[index], exceptions[index]);
complete_mutation(mutations[index], exceptions[index], callbacks[index]);
{
std::lock_guard<std::recursive_mutex> lock(task_mutex_);
mutating_ = false;
}
render_completed_.notify_all();
for (std::size_t index = 0; index < mutations.size(); ++index) {
if (mutations[index].callback) {
for (std::size_t index = 0; index < callbacks.size(); ++index) {
for (auto& callback : callbacks[index]) {
try {
mutations[index].callback(exceptions[index]);
callback(exceptions[index]);
} catch (...) {
}
}
+22 -35
View File
@@ -44,7 +44,11 @@ private:
public:
using Renderable = std::shared_ptr<Renderable_Base>;
using Const_Renderable = std::shared_ptr<const Renderable_Base>;
using Mutation_Callback = std::function<void(std::exception_ptr)>;
enum class Relationship {
display,
dependency
};
struct Attach_Relationships {
std::vector<Renderable> display_parents;
@@ -54,15 +58,18 @@ public:
class Mutation {
public:
using Callback = std::function<void(std::exception_ptr)>;
Mutation() = default;
void wait() const;
[[nodiscard]] bool ready() const;
void then(Callback callback) const;
private:
struct State {
explicit State(Scene_Base& scene) : scene(&scene) {}
Scene_Base* scene{};
mutable std::mutex mutex;
std::condition_variable condition;
std::vector<Callback> callbacks;
std::exception_ptr exception;
bool completed{};
};
@@ -75,16 +82,9 @@ public:
public:
Attach_Builder(const Attach_Builder&) = delete;
Attach_Builder& operator=(const Attach_Builder&) = delete;
Attach_Builder(Attach_Builder&&) noexcept = default;
Attach_Builder& operator=(Attach_Builder&&) noexcept = default;
void attach_renderable(Renderable renderable,
Attach_Relationships relationships = {});
void set_display_parent(const Renderable& child, const Renderable& parent);
void add_display_parent(const Renderable& child, const Renderable& parent);
void clear_display_parent(const Renderable& child);
void set_dependency_parent(const Renderable& child, const Renderable& parent);
void add_dependency_parent(const Renderable& child, const Renderable& parent);
void clear_dependency_parent(const Renderable& child);
Attach_Builder(Attach_Builder&&) = delete;
Attach_Builder& operator=(Attach_Builder&&) = delete;
void attach(Renderable renderable, Attach_Relationships relationships = {});
private:
explicit Attach_Builder(Scene_Base& scene);
Scene_Base* scene_{};
@@ -129,28 +129,18 @@ public:
void render();
void render(Abstract_Frame& frame);
void wait_for_render();
void wait_for_mutations();
void publish_frame_state();
void notify_model_dirty() noexcept;
Attach_Builder attach_builder();
Mutation attach_renderable(Renderable renderable,
Attach_Relationships relationships = {},
Mutation_Callback callback = {});
Mutation detach_renderable(const Renderable& renderable,
Mutation_Callback callback = {});
Mutation set_display_parent(const Renderable& child, const Renderable& parent,
Mutation_Callback callback = {});
Mutation add_display_parent(const Renderable& child, const Renderable& parent,
Mutation_Callback callback = {});
Mutation clear_display_parent(const Renderable& child,
Mutation_Callback callback = {});
Mutation set_dependency_parent(const Renderable& child, const Renderable& parent,
Mutation_Callback callback = {});
Mutation add_dependency_parent(const Renderable& child, const Renderable& parent,
Mutation_Callback callback = {});
Mutation clear_dependency_parent(const Renderable& child,
Mutation_Callback callback = {});
Attach_Relationships relationships = {});
Mutation detach_renderable(const Renderable& renderable);
Mutation set_parent(Relationship relationship, const Renderable& child,
const Renderable& parent);
Mutation add_parent(Relationship relationship, const Renderable& child,
const Renderable& parent);
Mutation clear_parents(Relationship relationship, const Renderable& child);
void set_renderable_configuration(const Renderable& renderable,
Renderable_Configuration configuration);
@@ -265,7 +255,6 @@ private:
struct Mutation_Command {
std::function<void()> operation;
Mutation_Callback callback;
std::shared_ptr<Mutation::State> completion;
};
@@ -279,12 +268,10 @@ private:
};
void submit_render(Abstract_Frame* frame);
[[nodiscard]] Mutation enqueue_mutation(std::function<void()> operation,
Mutation_Callback callback);
[[nodiscard]] Mutation enqueue_mutation_locked(std::function<void()> operation,
Mutation_Callback callback);
void complete_mutation(Mutation_Command& command,
std::exception_ptr exception) noexcept;
[[nodiscard]] Mutation enqueue_mutation(std::function<void()> operation);
[[nodiscard]] Mutation enqueue_mutation_locked(std::function<void()> operation);
void complete_mutation(Mutation_Command& command, std::exception_ptr exception,
std::vector<Mutation::Callback>& callbacks) noexcept;
void apply_attach_renderable_locked(Renderable renderable,
Attach_Relationships relationships);
void apply_detach_renderable_locked(const Renderable& renderable);
@@ -104,7 +104,8 @@ TEST(dynamic_renderable_lifecycle_test,
scene.render();
gate->wait_until_arrived();
std::atomic<bool> callback_called{};
auto mutation = scene.attach_renderable(attached, {}, [&](std::exception_ptr exception) {
auto mutation = scene.attach_renderable(attached);
mutation.then([&](std::exception_ptr exception) {
EXPECT_FALSE(exception);
callback_called.store(true, std::memory_order_release);
});
@@ -154,13 +155,13 @@ TEST(dynamic_renderable_lifecycle_test,
auto child = std::make_shared<Counting_Renderable>(scene);
scene.attach_renderable(first_parent);
scene.attach_renderable(second_parent);
scene.attach_renderable(child, {
auto attach = scene.attach_renderable(child, {
.dependency_parents = {first_parent}
});
scene.wait_for_mutations();
attach.wait();
scene.render();
first_gate->wait_until_arrived();
auto reparent = scene.set_dependency_parent(child, second_parent);
auto reparent = scene.set_parent(Scene_Base::Relationship::dependency, child, second_parent);
EXPECT_FALSE(reparent.ready());
EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 0);
first_gate->open();
@@ -252,7 +253,7 @@ TEST(dynamic_renderable_lifecycle_test,
scene.render();
scene.wait_for_render();
scene.wait_for_mutations();
EXPECT_TRUE(wait_until([&] { return scene.renderable_count() == 0; }));
EXPECT_EQ(renderable->prepare_count.load(std::memory_order_acquire), 1);
EXPECT_EQ(scene.renderable_count(), 0u);
scene.render();
@@ -428,11 +429,11 @@ TEST(dynamic_renderable_lifecycle_test,
.display_parents = {parent},
.dependency_parents = {parent}
});
scene.set_display_parent(child, alternate_parent);
scene.set_dependency_parent(child, alternate_parent);
scene.set_parent(Scene_Base::Relationship::display, child, alternate_parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, alternate_parent);
child->invalidate_graph();
scene.set_display_parent(child, parent);
scene.set_dependency_parent(child, parent);
scene.set_parent(Scene_Base::Relationship::display, child, parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.detach_renderable(child);
} catch (...) {
failures.fetch_add(1, std::memory_order_relaxed);
@@ -444,7 +445,8 @@ TEST(dynamic_renderable_lifecycle_test,
controller.join();
controls_done.store(true, std::memory_order_release);
renderer.join();
scene.wait_for_mutations();
scene.render();
scene.wait_for_render();
EXPECT_EQ(failures.load(std::memory_order_acquire), 0);
EXPECT_EQ(violations.load(std::memory_order_acquire), 0);
EXPECT_EQ(scene.renderable_count(), 2u);
@@ -95,8 +95,8 @@ TEST(render_plan_execution_test, dependency_edges_connect_prepare_only_and_layer
auto child = std::make_shared<Pipeline_Renderable>(scene);
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.set_dependency_parent(child, parent);
scene.set_display_parent(child, parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.set_parent(Scene_Base::Relationship::display, child, parent);
scene.render();
scene.wait_for_render();
@@ -166,7 +166,7 @@ TEST(render_plan_execution_test, dependent_prepare_does_not_wait_for_dependency_
child->prepare_action = [&] { child_prepared.store(true, std::memory_order_release); };
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.set_dependency_parent(child, parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.render();
{
std::unique_lock lock(mutex);
@@ -248,21 +248,24 @@ TEST(render_plan_execution_test, logical_node_ids_are_stable_and_retired_ids_are
const Render_Node_Id retired_chunk = first->graph.nodes[2].node_id;
renderable->set_chunk_count(2);
scene.wait_for_mutations();
scene.render();
scene.wait_for_render();
const auto unchanged = renderable->graph_snapshot();
EXPECT_EQ(unchanged->graph.nodes[0].node_id, root);
EXPECT_EQ(unchanged->graph.nodes[1].node_id, first_chunk);
EXPECT_EQ(unchanged->graph.nodes[2].node_id, retired_chunk);
renderable->set_chunk_count(1);
scene.wait_for_mutations();
scene.render();
scene.wait_for_render();
const auto reduced = renderable->graph_snapshot();
ASSERT_EQ(reduced->graph.nodes.size(), 2u);
EXPECT_EQ(reduced->graph.nodes[0].node_id, root);
EXPECT_EQ(reduced->graph.nodes[1].node_id, first_chunk);
renderable->set_chunk_count(2);
scene.wait_for_mutations();
scene.render();
scene.wait_for_render();
const auto expanded = renderable->graph_snapshot();
ASSERT_EQ(expanded->graph.nodes.size(), 3u);
EXPECT_EQ(expanded->graph.nodes[0].node_id, root);
@@ -110,13 +110,13 @@ TEST(scene2d_context_test, dependency_reparent_invalidates_cached_child) {
scene.attach_renderable(first_parent);
scene.attach_renderable(second_parent);
scene.attach_renderable(child);
scene.set_dependency_parent(child, first_parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, first_parent);
scene.render();
scene.wait_for_render();
EXPECT_EQ(first_parent->render_count, 1);
EXPECT_EQ(second_parent->render_count, 1);
EXPECT_EQ(child->render_count, 1);
scene.set_dependency_parent(child, second_parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, second_parent);
scene.render();
scene.wait_for_render();
EXPECT_EQ(first_parent->render_count, 1);
@@ -131,8 +131,8 @@ TEST(scene2d_context_test, multiple_dependency_parents_invalidate_cached_child)
scene.attach_renderable(first_parent);
scene.attach_renderable(second_parent);
scene.attach_renderable(child);
scene.set_dependency_parent(child, first_parent);
scene.add_dependency_parent(child, second_parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, first_parent);
scene.add_parent(Scene_Base::Relationship::dependency, child, second_parent);
scene.render();
scene.wait_for_render();
EXPECT_EQ(child->render_count, 1);
@@ -171,8 +171,8 @@ TEST(scene2d_context_test, detach_dependency_parent_invalidates_promoted_cached_
scene.attach_renderable(grandparent);
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.set_dependency_parent(parent, grandparent);
scene.set_dependency_parent(child, parent);
scene.set_parent(Scene_Base::Relationship::dependency, parent, grandparent);
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.render();
scene.wait_for_render();
EXPECT_EQ(grandparent->render_count, 1);
@@ -39,8 +39,8 @@ TEST(scene2d_render_order_test, separates_dependency_order_from_display_order) {
auto axis = std::make_shared<Scene2D_Render_Order_Renderable>(scene, state, 1);
scene.attach_renderable(spectrum);
scene.attach_renderable(axis);
scene.set_dependency_parent(spectrum, axis);
scene.set_display_parent(axis, spectrum);
scene.set_parent(Scene_Base::Relationship::dependency, spectrum, axis);
scene.set_parent(Scene_Base::Relationship::display, axis, spectrum);
scene.render();
scene.wait_for_render();
EXPECT_EQ(state.snapshot(), (std::vector<std::uint64_t>{1, 2}));
@@ -61,10 +61,10 @@ TEST(scene2d_render_order_test, shared_overlay_waits_for_every_display_parent) {
scene.attach_renderable(first_plot);
scene.attach_renderable(axis);
scene.attach_renderable(second_plot);
scene.add_dependency_parent(first_plot, axis);
scene.add_dependency_parent(second_plot, axis);
scene.add_display_parent(axis, first_plot);
scene.add_display_parent(axis, second_plot);
scene.add_parent(Scene_Base::Relationship::dependency, first_plot, axis);
scene.add_parent(Scene_Base::Relationship::dependency, second_plot, axis);
scene.add_parent(Scene_Base::Relationship::display, axis, first_plot);
scene.add_parent(Scene_Base::Relationship::display, axis, second_plot);
scene.render();
scene.wait_for_render();
const auto render_order = state.snapshot();
@@ -36,8 +36,8 @@ TEST(scene3d_context_test, propagates_cache_invalidation_through_unsorted_depend
scene.attach_renderable(leaf);
scene.attach_renderable(middle);
scene.attach_renderable(root);
scene.set_dependency_parent(leaf, middle);
scene.set_dependency_parent(middle, root);
scene.set_parent(Scene_Base::Relationship::dependency, leaf, middle);
scene.set_parent(Scene_Base::Relationship::dependency, middle, root);
scene.render();
scene.wait_for_render();
EXPECT_EQ(root->render_count, 1);
@@ -58,10 +58,10 @@ TEST(scene3d_context_test, detach_parent_promotes_children_and_invalidates_depen
scene.attach_renderable(grandparent);
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.set_display_parent(parent, grandparent);
scene.set_display_parent(child, parent);
scene.set_dependency_parent(parent, grandparent);
scene.set_dependency_parent(child, parent);
scene.set_parent(Scene_Base::Relationship::display, parent, grandparent);
scene.set_parent(Scene_Base::Relationship::display, child, parent);
scene.set_parent(Scene_Base::Relationship::dependency, parent, grandparent);
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.render();
scene.wait_for_render();
EXPECT_EQ(child->render_count, 1);
@@ -124,11 +124,11 @@ TEST(scene_base_test, concurrent_render_and_topology_updates_remain_serialized)
std::thread mutator([&] {
for (int index = 0; index < 100; ++index) {
if (index % 2 == 0) {
scene.set_dependency_parent(child, parent);
scene.set_display_parent(child, parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.set_parent(Scene_Base::Relationship::display, child, parent);
} else {
scene.clear_dependency_parent(child);
scene.clear_display_parent(child);
scene.clear_parents(Scene_Base::Relationship::dependency, child);
scene.clear_parents(Scene_Base::Relationship::display, child);
}
scene.set_renderable_configuration(child, {.cache_enabled = index % 2 == 0});
}
@@ -158,11 +158,11 @@ TEST(scene_base_test, topology_rejects_unattached_renderables) {
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(child);
EXPECT_THROW(scene.set_dependency_parent(child, parent).wait(), std::invalid_argument);
EXPECT_THROW(scene.set_display_parent(child, parent).wait(), std::invalid_argument);
EXPECT_THROW(scene.set_parent(Scene_Base::Relationship::dependency, child, parent).wait(), std::invalid_argument);
EXPECT_THROW(scene.set_parent(Scene_Base::Relationship::display, child, parent).wait(), std::invalid_argument);
scene.attach_renderable(parent);
auto detached_child = std::make_shared<Scene_Base_Test_Renderable>(scene);
EXPECT_THROW(scene.set_dependency_parent(detached_child, parent).wait(), std::invalid_argument);
EXPECT_THROW(scene.set_parent(Scene_Base::Relationship::dependency, detached_child, parent).wait(), std::invalid_argument);
}
TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
Scene2D_Context<> scene;
@@ -170,9 +170,9 @@ TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.set_dependency_parent(child, parent).wait();
scene.set_parent(Scene_Base::Relationship::dependency, child, parent).wait();
const auto revision = child->prepare_revision();
EXPECT_NO_THROW(scene.set_dependency_parent(child, parent).wait());
EXPECT_NO_THROW(scene.set_parent(Scene_Base::Relationship::dependency, child, parent).wait());
EXPECT_EQ(child->prepare_revision(), revision);
const auto topology = scene.topology_snapshot();
EXPECT_EQ(topology.dependency.size(), 2);
@@ -193,9 +193,9 @@ TEST(scene_base_test, display_graph_rejects_cycles) {
scene.attach_renderable(first);
scene.attach_renderable(second);
scene.attach_renderable(third);
scene.set_display_parent(second, first);
scene.add_display_parent(third, second);
EXPECT_THROW(scene.add_display_parent(first, third).wait(), std::invalid_argument);
scene.set_parent(Scene_Base::Relationship::display, second, first);
scene.add_parent(Scene_Base::Relationship::display, third, second);
EXPECT_THROW(scene.add_parent(Scene_Base::Relationship::display, first, third).wait(), std::invalid_argument);
}
TEST(scene_base_test, mutation_callback_receives_apply_failure) {
Scene2D_Context<> scene;
@@ -203,10 +203,11 @@ TEST(scene_base_test, mutation_callback_receives_apply_failure) {
auto second = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(first).wait();
scene.attach_renderable(second).wait();
scene.set_display_parent(second, first).wait();
scene.set_parent(Scene_Base::Relationship::display, second, first).wait();
std::atomic<bool> callback_called{};
std::atomic<bool> callback_failed{};
auto mutation = scene.set_display_parent(first, second, [&](std::exception_ptr exception) {
auto mutation = scene.set_parent(Scene_Base::Relationship::display, first, second);
mutation.then([&](std::exception_ptr exception) {
callback_failed.store(static_cast<bool>(exception), std::memory_order_release);
callback_called.store(true, std::memory_order_release);
});
@@ -221,12 +222,12 @@ TEST(scene_base_test, atomic_attach_can_place_existing_display_children) {
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent);
scene.attach_renderable(axis);
scene.attach_renderable(renderable, {
auto mutation = scene.attach_renderable(renderable, {
.display_parents = {parent},
.display_children = {axis},
.dependency_parents = {parent, axis}
});
scene.wait_for_mutations();
mutation.wait();
const auto topology = scene.topology_snapshot();
const auto contains = [](const auto& relationships, const auto& child,
const auto& parent_value) {
@@ -275,16 +276,15 @@ TEST(scene_base_test, topology_snapshot_is_safe_during_topology_updates) {
auto parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent);
scene.attach_renderable(child);
scene.wait_for_mutations();
scene.attach_renderable(child).wait();
std::thread writer([&] {
for (int index = 0; index < 500; ++index) {
if (index % 2 == 0) {
scene.set_dependency_parent(child, parent);
scene.set_display_parent(child, parent);
scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.set_parent(Scene_Base::Relationship::display, child, parent);
} else {
scene.clear_dependency_parent(child);
scene.clear_display_parent(child);
scene.clear_parents(Scene_Base::Relationship::dependency, child);
scene.clear_parents(Scene_Base::Relationship::display, child);
}
}
});
@@ -363,7 +363,7 @@ TEST(scene_base_test, attach_builder_is_synchronous_before_runtime) {
auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
{
auto builder = scene.attach_builder();
builder.attach_renderable(renderable);
builder.attach(renderable);
EXPECT_EQ(scene.renderable_count(), 1u);
}
scene.render();
@@ -556,8 +556,9 @@ TEST(threading_contract_test, scene_render_and_render_graph_rebuild_are_serializ
start.store(true, std::memory_order_release);
renderer.join();
rebuilder.join();
scene.wait_for_mutations();
EXPECT_EQ(executed.load(std::memory_order_acquire), render_count);
scene.render();
scene.wait_for_render();
EXPECT_EQ(executed.load(std::memory_order_acquire), render_count + 1);
}
TEST(threading_contract_test, real_time_data_updates_can_race_with_scene_destruction) {
using Data = Latest_Real_Time_Data<int, std::mutex, Observer_State<Frame_Strategy_Real_Time_Data_Observer>>;
+3 -5
View File
@@ -210,7 +210,7 @@ public:
auto root = detail::make_renderable_group(*this, true);
root->set_object_name("root");
auto builder = this->attach_builder();
builder.attach_renderable(root);
builder.attach(root);
}
[[nodiscard]] std::shared_ptr<Renderable> root_renderable() const {
const auto topology = this->topology_snapshot();
@@ -236,12 +236,10 @@ public:
attach_renderable_node(renderable, parent);
return renderable;
}
::Scene_Base::Mutation remove_renderable(
const std::shared_ptr<Renderable>& renderable,
::Scene_Base::Mutation_Callback callback = {}) {
::Scene_Base::Mutation remove_renderable(const std::shared_ptr<Renderable>& renderable) {
if (!renderable || renderable == root_renderable())
return {};
return this->detach_renderable(renderable, std::move(callback));
return this->detach_renderable(renderable);
}
void set_background_color(Color color) {
if (background_color() == color)
+16 -22
View File
@@ -1,22 +1,17 @@
#include "Web_Server.h"
#include "Gallery_WebSocket_Controller.h"
#include "Renderive_WebSocket_Controller.h"
#include "Web_Performance_Log.h"
#include <drogon/drogon.h>
#include <algorithm>
#include <filesystem>
#include <functional>
#include <iostream>
#include <memory>
#include <thread>
namespace renderive::web {
int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root) {
const std::filesystem::path gallery_root = asset_root / "webapp_gallery";
const std::filesystem::path gallery_root = asset_root / "webapp_gallery" / "dist";
const std::filesystem::path gallery_index = gallery_root / "index.html";
if (!std::filesystem::is_regular_file(gallery_index)) {
std::cerr << "Renderive Gallery assets not found: " << gallery_index << '\n';
@@ -27,12 +22,12 @@ int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root)
const auto gallery_controller = std::make_shared<Gallery_WebSocket_Controller>();
const auto hardware_threads = std::max(2U, std::thread::hardware_concurrency());
std::cout << "Renderive WebSocket backend: ws://127.0.0.1:" << port
<< "/renderive\n"
<< "Renderive control gallery: ws://127.0.0.1:" << port
<< "/renderive/gallery\n"
<< "Renderive hosted gallery: http://127.0.0.1:" << port << "/\n"
<< "Renderive performance log: " << web_performance_log_path().string() << "\n"
<< "HTTP only serves static HTML/CSS/JS; events and pixels stay on WebSocket.\n";
<< "/renderive\n"
<< "Renderive control gallery: ws://127.0.0.1:" << port
<< "/renderive/gallery\n"
<< "Renderive hosted gallery: http://127.0.0.1:" << port << "/\n"
<< "Renderive performance log: " << web_performance_log_path().string() << "\n"
<< "HTTP only serves static HTML/CSS/JS; events and pixels stay on WebSocket.\n";
auto& app = drogon::app();
const auto redirect_to_gallery = [](
const drogon::HttpRequestPtr&,
@@ -42,16 +37,15 @@ int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root)
app.registerHandler("/gallery", redirect_to_gallery, {drogon::Get});
app.registerHandler("/gallery/", redirect_to_gallery, {drogon::Get});
app
.registerController(controller)
.registerController(gallery_controller)
.setDocumentRoot(gallery_root.string())
.setHomePage("index.html")
.setStaticFileHeaders({{"Cache-Control", "no-store"}})
.addListener("127.0.0.1", port)
.setThreadNum(std::min(8U, hardware_threads))
.setIdleConnectionTimeout(90)
.run();
.registerController(controller)
.registerController(gallery_controller)
.setDocumentRoot(gallery_root.string())
.setHomePage("index.html")
.setStaticFileHeaders({{"Cache-Control", "no-store"}})
.addListener("127.0.0.1", port)
.setThreadNum(std::min(8U, hardware_threads))
.setIdleConnectionTimeout(90)
.run();
return 0;
}
} // namespace renderive::web
+4 -4
View File
@@ -7,12 +7,12 @@
namespace {
std::uint16_t parse_port(int argc, char** argv) {
constexpr std::uint16_t default_port = 8848;
if(argc != 3 || std::string_view(argv[1]) != "--port")
if (argc != 3 || std::string_view(argv[1]) != "--port")
return default_port;
unsigned value{};
const std::string_view text(argv[2]);
const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value);
if(error != std::errc{} || end != text.data() + text.size() || value == 0 || value > 65535) {
if (error != std::errc{} || end != text.data() + text.size() || value == 0 || value > 65535) {
std::cerr << "Invalid port: " << text << '\n';
return 0;
}
@@ -21,9 +21,9 @@ std::uint16_t parse_port(int argc, char** argv) {
}
int main(int argc, char** argv) {
const std::uint16_t port = parse_port(argc, argv);
if(port == 0)
if (port == 0)
return 2;
std::cout << "http://127.0.0.1:8848" << std::endl;
const std::filesystem::path executable = std::filesystem::absolute(argv[0]);
return renderive::web::run_web_server(port, executable.parent_path());
return renderive::web::run_web_server(port, executable.parent_path().parent_path().parent_path());
}