接口优化

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); std::lock_guard lock(state_->mutex);
return state_->completed; 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_Base::Attach_Builder::Attach_Builder(Scene_Base& scene)
: scene_(&scene), task_lock_(scene.task_mutex_) { : scene_(&scene), task_lock_(scene.task_mutex_) {
if (scene.runtime_started_) if (scene.runtime_started_)
throw std::logic_error("scene attach builder is only available before runtime starts"); throw std::logic_error("scene attach builder is only available before runtime starts");
} }
void Scene_Base::Attach_Builder::attach_renderable(Renderable renderable, void Scene_Base::Attach_Builder::attach(Renderable renderable,
Attach_Relationships relationships) { Attach_Relationships relationships) {
std::lock_guard lock(scene_->model_mutex_); std::lock_guard lock(scene_->model_mutex_);
scene_->apply_attach_renderable_locked(std::move(renderable), std::move(relationships)); 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() { Scene_Base::Attach_Builder Scene_Base::attach_builder() {
return Attach_Builder(*this); return Attach_Builder(*this);
} }
@@ -468,15 +464,6 @@ void Scene_Base::wait_for_render() {
if (exception) if (exception)
std::rethrow_exception(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( std::vector<Renderable_Id> Scene_Base::relationship_ids_locked(
const std::vector<Renderable>& renderables) const { const std::vector<Renderable>& renderables) const {
std::vector<Renderable_Id> ids; std::vector<Renderable_Id> ids;
@@ -490,28 +477,28 @@ std::vector<Renderable_Id> Scene_Base::relationship_ids_locked(
} }
return ids; return ids;
} }
Scene_Base::Mutation Scene_Base::enqueue_mutation(std::function<void()> operation, Scene_Base::Mutation Scene_Base::enqueue_mutation(std::function<void()> operation) {
Mutation_Callback callback) {
std::lock_guard<std::recursive_mutex> lock(task_mutex_); 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, Scene_Base::Mutation Scene_Base::enqueue_mutation_locked(std::function<void()> operation) {
Mutation_Callback callback) {
if (stop_) if (stop_)
throw std::logic_error("scene is shutting down"); throw std::logic_error("scene is shutting down");
runtime_started_ = true; runtime_started_ = true;
auto completion = std::make_shared<Mutation::State>(*this); 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(); task_ready_.notify_one();
return Mutation(std::move(completion)); return Mutation(std::move(completion));
} }
void Scene_Base::complete_mutation(Mutation_Command& command, 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); std::lock_guard lock(command.completion->mutex);
command.completion->exception = exception; command.completion->exception = exception;
command.completion->completed = true; command.completion->completed = true;
command.completion->scene = nullptr; command.completion->scene = nullptr;
callbacks = std::move(command.completion->callbacks);
} }
command.completion->condition.notify_all(); command.completion->condition.notify_all();
} }
@@ -639,80 +626,60 @@ void Scene_Base::apply_clear_dependency_parent_locked(const Renderable& child) {
notify_model_dirty(); notify_model_dirty();
} }
Scene_Base::Mutation Scene_Base::attach_renderable(Renderable renderable, Scene_Base::Mutation Scene_Base::attach_renderable(Renderable renderable,
Attach_Relationships relationships, Attach_Relationships relationships) {
Mutation_Callback callback) {
if (!renderable) if (!renderable)
throw std::invalid_argument("renderable is null"); throw std::invalid_argument("renderable is null");
validate_renderable_scene(*renderable); validate_renderable_scene(*renderable);
return enqueue_mutation( return enqueue_mutation(
[this, renderable = std::move(renderable), relationships = std::move(relationships)]() mutable { [this, renderable = std::move(renderable), relationships = std::move(relationships)]() mutable {
apply_attach_renderable_locked(std::move(renderable), std::move(relationships)); apply_attach_renderable_locked(std::move(renderable), std::move(relationships));
}, });
std::move(callback));
} }
Scene_Base::Mutation Scene_Base::detach_renderable(const Renderable& renderable, Scene_Base::Mutation Scene_Base::detach_renderable(const Renderable& renderable) {
Mutation_Callback callback) {
if (!renderable) if (!renderable)
throw std::invalid_argument("renderable is null"); throw std::invalid_argument("renderable is null");
validate_renderable_scene(*renderable); validate_renderable_scene(*renderable);
return enqueue_mutation([this, renderable] { apply_detach_renderable_locked(renderable); }, return enqueue_mutation([this, renderable] { apply_detach_renderable_locked(renderable); });
std::move(callback));
} }
Scene_Base::Mutation Scene_Base::set_display_parent(const Renderable& child, Scene_Base::Mutation Scene_Base::set_parent(Relationship relationship,
const Renderable& parent, const Renderable& child,
Mutation_Callback callback) { const Renderable& parent) {
if (!child || !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(*child);
validate_renderable_scene(*parent); validate_renderable_scene(*parent);
return enqueue_mutation([this, child, parent] { apply_set_display_parent_locked(child, parent); }, return enqueue_mutation([this, relationship, child, parent] {
std::move(callback)); 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, Scene_Base::Mutation Scene_Base::add_parent(Relationship relationship,
const Renderable& parent, const Renderable& child,
Mutation_Callback callback) { const Renderable& parent) {
if (!child || !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(*child);
validate_renderable_scene(*parent); validate_renderable_scene(*parent);
return enqueue_mutation([this, child, parent] { apply_add_display_parent_locked(child, parent); }, return enqueue_mutation([this, relationship, child, parent] {
std::move(callback)); 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, Scene_Base::Mutation Scene_Base::clear_parents(Relationship relationship,
Mutation_Callback callback) { const Renderable& child) {
if (!child) if (!child)
throw std::invalid_argument("display child is null"); throw std::invalid_argument("topology child is null");
validate_renderable_scene(*child); validate_renderable_scene(*child);
return enqueue_mutation([this, child] { apply_clear_display_parent_locked(child); }, return enqueue_mutation([this, relationship, child] {
std::move(callback)); if (relationship == Relationship::display)
} apply_clear_display_parent_locked(child);
Scene_Base::Mutation Scene_Base::set_dependency_parent(const Renderable& child, else
const Renderable& parent, apply_clear_dependency_parent_locked(child);
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));
} }
void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) { void Scene_Base::request_render_graph_rebuild(Renderable_Base& renderable) {
validate_renderable_scene(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)] { static_cast<void>(enqueue_mutation_locked([this, owner = std::move(owner)] {
owner->reset_render_graph(); owner->reset_render_graph();
notify_model_dirty(); notify_model_dirty();
}, {})); }));
} }
void Scene_Base::publish_frame_state() { void Scene_Base::publish_frame_state() {
auto task_lock = lock_render_idle(); auto task_lock = lock_render_idle();
@@ -1055,6 +1022,7 @@ void Scene_Base::render_loop() {
} }
if (!mutations.empty()) { if (!mutations.empty()) {
std::vector<std::exception_ptr> exceptions(mutations.size()); std::vector<std::exception_ptr> exceptions(mutations.size());
std::vector<std::vector<Mutation::Callback>> callbacks(mutations.size());
{ {
std::lock_guard lock(model_mutex_); std::lock_guard lock(model_mutex_);
for (std::size_t index = 0; index < mutations.size(); ++index) { 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) 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_); std::lock_guard<std::recursive_mutex> lock(task_mutex_);
mutating_ = false; mutating_ = false;
} }
render_completed_.notify_all(); render_completed_.notify_all();
for (std::size_t index = 0; index < mutations.size(); ++index) { for (std::size_t index = 0; index < callbacks.size(); ++index) {
if (mutations[index].callback) { for (auto& callback : callbacks[index]) {
try { try {
mutations[index].callback(exceptions[index]); callback(exceptions[index]);
} catch (...) { } catch (...) {
} }
} }
+22 -35
View File
@@ -44,7 +44,11 @@ private:
public: public:
using Renderable = std::shared_ptr<Renderable_Base>; using Renderable = std::shared_ptr<Renderable_Base>;
using Const_Renderable = std::shared_ptr<const 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 { struct Attach_Relationships {
std::vector<Renderable> display_parents; std::vector<Renderable> display_parents;
@@ -54,15 +58,18 @@ public:
class Mutation { class Mutation {
public: public:
using Callback = std::function<void(std::exception_ptr)>;
Mutation() = default; Mutation() = default;
void wait() const; void wait() const;
[[nodiscard]] bool ready() const; [[nodiscard]] bool ready() const;
void then(Callback callback) const;
private: private:
struct State { struct State {
explicit State(Scene_Base& scene) : scene(&scene) {} explicit State(Scene_Base& scene) : scene(&scene) {}
Scene_Base* scene{}; Scene_Base* scene{};
mutable std::mutex mutex; mutable std::mutex mutex;
std::condition_variable condition; std::condition_variable condition;
std::vector<Callback> callbacks;
std::exception_ptr exception; std::exception_ptr exception;
bool completed{}; bool completed{};
}; };
@@ -75,16 +82,9 @@ public:
public: public:
Attach_Builder(const Attach_Builder&) = delete; Attach_Builder(const Attach_Builder&) = delete;
Attach_Builder& operator=(const Attach_Builder&) = delete; Attach_Builder& operator=(const Attach_Builder&) = delete;
Attach_Builder(Attach_Builder&&) noexcept = default; Attach_Builder(Attach_Builder&&) = delete;
Attach_Builder& operator=(Attach_Builder&&) noexcept = default; Attach_Builder& operator=(Attach_Builder&&) = delete;
void attach_renderable(Renderable renderable, void attach(Renderable renderable, Attach_Relationships relationships = {});
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);
private: private:
explicit Attach_Builder(Scene_Base& scene); explicit Attach_Builder(Scene_Base& scene);
Scene_Base* scene_{}; Scene_Base* scene_{};
@@ -129,28 +129,18 @@ public:
void render(); void render();
void render(Abstract_Frame& frame); void render(Abstract_Frame& frame);
void wait_for_render(); void wait_for_render();
void wait_for_mutations();
void publish_frame_state(); void publish_frame_state();
void notify_model_dirty() noexcept; void notify_model_dirty() noexcept;
Attach_Builder attach_builder(); Attach_Builder attach_builder();
Mutation attach_renderable(Renderable renderable, Mutation attach_renderable(Renderable renderable,
Attach_Relationships relationships = {}, Attach_Relationships relationships = {});
Mutation_Callback callback = {}); Mutation detach_renderable(const Renderable& renderable);
Mutation detach_renderable(const Renderable& renderable, Mutation set_parent(Relationship relationship, const Renderable& child,
Mutation_Callback callback = {}); const Renderable& parent);
Mutation set_display_parent(const Renderable& child, const Renderable& parent, Mutation add_parent(Relationship relationship, const Renderable& child,
Mutation_Callback callback = {}); const Renderable& parent);
Mutation add_display_parent(const Renderable& child, const Renderable& parent, Mutation clear_parents(Relationship relationship, const Renderable& child);
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 = {});
void set_renderable_configuration(const Renderable& renderable, void set_renderable_configuration(const Renderable& renderable,
Renderable_Configuration configuration); Renderable_Configuration configuration);
@@ -265,7 +255,6 @@ private:
struct Mutation_Command { struct Mutation_Command {
std::function<void()> operation; std::function<void()> operation;
Mutation_Callback callback;
std::shared_ptr<Mutation::State> completion; std::shared_ptr<Mutation::State> completion;
}; };
@@ -279,12 +268,10 @@ private:
}; };
void submit_render(Abstract_Frame* frame); void submit_render(Abstract_Frame* frame);
[[nodiscard]] Mutation enqueue_mutation(std::function<void()> operation, [[nodiscard]] Mutation enqueue_mutation(std::function<void()> operation);
Mutation_Callback callback); [[nodiscard]] Mutation enqueue_mutation_locked(std::function<void()> operation);
[[nodiscard]] Mutation enqueue_mutation_locked(std::function<void()> operation, void complete_mutation(Mutation_Command& command, std::exception_ptr exception,
Mutation_Callback callback); std::vector<Mutation::Callback>& callbacks) noexcept;
void complete_mutation(Mutation_Command& command,
std::exception_ptr exception) noexcept;
void apply_attach_renderable_locked(Renderable renderable, void apply_attach_renderable_locked(Renderable renderable,
Attach_Relationships relationships); Attach_Relationships relationships);
void apply_detach_renderable_locked(const Renderable& renderable); void apply_detach_renderable_locked(const Renderable& renderable);
@@ -104,7 +104,8 @@ TEST(dynamic_renderable_lifecycle_test,
scene.render(); scene.render();
gate->wait_until_arrived(); gate->wait_until_arrived();
std::atomic<bool> callback_called{}; 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); EXPECT_FALSE(exception);
callback_called.store(true, std::memory_order_release); 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); auto child = std::make_shared<Counting_Renderable>(scene);
scene.attach_renderable(first_parent); scene.attach_renderable(first_parent);
scene.attach_renderable(second_parent); scene.attach_renderable(second_parent);
scene.attach_renderable(child, { auto attach = scene.attach_renderable(child, {
.dependency_parents = {first_parent} .dependency_parents = {first_parent}
}); });
scene.wait_for_mutations(); attach.wait();
scene.render(); scene.render();
first_gate->wait_until_arrived(); 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_FALSE(reparent.ready());
EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 0); EXPECT_EQ(child->prepare_count.load(std::memory_order_acquire), 0);
first_gate->open(); first_gate->open();
@@ -252,7 +253,7 @@ TEST(dynamic_renderable_lifecycle_test,
scene.render(); scene.render();
scene.wait_for_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(renderable->prepare_count.load(std::memory_order_acquire), 1);
EXPECT_EQ(scene.renderable_count(), 0u); EXPECT_EQ(scene.renderable_count(), 0u);
scene.render(); scene.render();
@@ -428,11 +429,11 @@ TEST(dynamic_renderable_lifecycle_test,
.display_parents = {parent}, .display_parents = {parent},
.dependency_parents = {parent} .dependency_parents = {parent}
}); });
scene.set_display_parent(child, alternate_parent); scene.set_parent(Scene_Base::Relationship::display, child, alternate_parent);
scene.set_dependency_parent(child, alternate_parent); scene.set_parent(Scene_Base::Relationship::dependency, child, alternate_parent);
child->invalidate_graph(); child->invalidate_graph();
scene.set_display_parent(child, parent); scene.set_parent(Scene_Base::Relationship::display, child, parent);
scene.set_dependency_parent(child, parent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.detach_renderable(child); scene.detach_renderable(child);
} catch (...) { } catch (...) {
failures.fetch_add(1, std::memory_order_relaxed); failures.fetch_add(1, std::memory_order_relaxed);
@@ -444,7 +445,8 @@ TEST(dynamic_renderable_lifecycle_test,
controller.join(); controller.join();
controls_done.store(true, std::memory_order_release); controls_done.store(true, std::memory_order_release);
renderer.join(); renderer.join();
scene.wait_for_mutations(); scene.render();
scene.wait_for_render();
EXPECT_EQ(failures.load(std::memory_order_acquire), 0); EXPECT_EQ(failures.load(std::memory_order_acquire), 0);
EXPECT_EQ(violations.load(std::memory_order_acquire), 0); EXPECT_EQ(violations.load(std::memory_order_acquire), 0);
EXPECT_EQ(scene.renderable_count(), 2u); 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); auto child = std::make_shared<Pipeline_Renderable>(scene);
scene.attach_renderable(parent); scene.attach_renderable(parent);
scene.attach_renderable(child); scene.attach_renderable(child);
scene.set_dependency_parent(child, parent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.set_display_parent(child, parent); scene.set_parent(Scene_Base::Relationship::display, child, parent);
scene.render(); scene.render();
scene.wait_for_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); }; child->prepare_action = [&] { child_prepared.store(true, std::memory_order_release); };
scene.attach_renderable(parent); scene.attach_renderable(parent);
scene.attach_renderable(child); scene.attach_renderable(child);
scene.set_dependency_parent(child, parent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.render(); scene.render();
{ {
std::unique_lock lock(mutex); 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; const Render_Node_Id retired_chunk = first->graph.nodes[2].node_id;
renderable->set_chunk_count(2); renderable->set_chunk_count(2);
scene.wait_for_mutations(); scene.render();
scene.wait_for_render();
const auto unchanged = renderable->graph_snapshot(); const auto unchanged = renderable->graph_snapshot();
EXPECT_EQ(unchanged->graph.nodes[0].node_id, root); EXPECT_EQ(unchanged->graph.nodes[0].node_id, root);
EXPECT_EQ(unchanged->graph.nodes[1].node_id, first_chunk); EXPECT_EQ(unchanged->graph.nodes[1].node_id, first_chunk);
EXPECT_EQ(unchanged->graph.nodes[2].node_id, retired_chunk); EXPECT_EQ(unchanged->graph.nodes[2].node_id, retired_chunk);
renderable->set_chunk_count(1); renderable->set_chunk_count(1);
scene.wait_for_mutations(); scene.render();
scene.wait_for_render();
const auto reduced = renderable->graph_snapshot(); const auto reduced = renderable->graph_snapshot();
ASSERT_EQ(reduced->graph.nodes.size(), 2u); ASSERT_EQ(reduced->graph.nodes.size(), 2u);
EXPECT_EQ(reduced->graph.nodes[0].node_id, root); EXPECT_EQ(reduced->graph.nodes[0].node_id, root);
EXPECT_EQ(reduced->graph.nodes[1].node_id, first_chunk); EXPECT_EQ(reduced->graph.nodes[1].node_id, first_chunk);
renderable->set_chunk_count(2); renderable->set_chunk_count(2);
scene.wait_for_mutations(); scene.render();
scene.wait_for_render();
const auto expanded = renderable->graph_snapshot(); const auto expanded = renderable->graph_snapshot();
ASSERT_EQ(expanded->graph.nodes.size(), 3u); ASSERT_EQ(expanded->graph.nodes.size(), 3u);
EXPECT_EQ(expanded->graph.nodes[0].node_id, root); 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(first_parent);
scene.attach_renderable(second_parent); scene.attach_renderable(second_parent);
scene.attach_renderable(child); scene.attach_renderable(child);
scene.set_dependency_parent(child, first_parent); scene.set_parent(Scene_Base::Relationship::dependency, child, first_parent);
scene.render(); scene.render();
scene.wait_for_render(); scene.wait_for_render();
EXPECT_EQ(first_parent->render_count, 1); EXPECT_EQ(first_parent->render_count, 1);
EXPECT_EQ(second_parent->render_count, 1); EXPECT_EQ(second_parent->render_count, 1);
EXPECT_EQ(child->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.render();
scene.wait_for_render(); scene.wait_for_render();
EXPECT_EQ(first_parent->render_count, 1); 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(first_parent);
scene.attach_renderable(second_parent); scene.attach_renderable(second_parent);
scene.attach_renderable(child); scene.attach_renderable(child);
scene.set_dependency_parent(child, first_parent); scene.set_parent(Scene_Base::Relationship::dependency, child, first_parent);
scene.add_dependency_parent(child, second_parent); scene.add_parent(Scene_Base::Relationship::dependency, child, second_parent);
scene.render(); scene.render();
scene.wait_for_render(); scene.wait_for_render();
EXPECT_EQ(child->render_count, 1); 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(grandparent);
scene.attach_renderable(parent); scene.attach_renderable(parent);
scene.attach_renderable(child); scene.attach_renderable(child);
scene.set_dependency_parent(parent, grandparent); scene.set_parent(Scene_Base::Relationship::dependency, parent, grandparent);
scene.set_dependency_parent(child, parent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.render(); scene.render();
scene.wait_for_render(); scene.wait_for_render();
EXPECT_EQ(grandparent->render_count, 1); 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); auto axis = std::make_shared<Scene2D_Render_Order_Renderable>(scene, state, 1);
scene.attach_renderable(spectrum); scene.attach_renderable(spectrum);
scene.attach_renderable(axis); scene.attach_renderable(axis);
scene.set_dependency_parent(spectrum, axis); scene.set_parent(Scene_Base::Relationship::dependency, spectrum, axis);
scene.set_display_parent(axis, spectrum); scene.set_parent(Scene_Base::Relationship::display, axis, spectrum);
scene.render(); scene.render();
scene.wait_for_render(); scene.wait_for_render();
EXPECT_EQ(state.snapshot(), (std::vector<std::uint64_t>{1, 2})); 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(first_plot);
scene.attach_renderable(axis); scene.attach_renderable(axis);
scene.attach_renderable(second_plot); scene.attach_renderable(second_plot);
scene.add_dependency_parent(first_plot, axis); scene.add_parent(Scene_Base::Relationship::dependency, first_plot, axis);
scene.add_dependency_parent(second_plot, axis); scene.add_parent(Scene_Base::Relationship::dependency, second_plot, axis);
scene.add_display_parent(axis, first_plot); scene.add_parent(Scene_Base::Relationship::display, axis, first_plot);
scene.add_display_parent(axis, second_plot); scene.add_parent(Scene_Base::Relationship::display, axis, second_plot);
scene.render(); scene.render();
scene.wait_for_render(); scene.wait_for_render();
const auto render_order = state.snapshot(); 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(leaf);
scene.attach_renderable(middle); scene.attach_renderable(middle);
scene.attach_renderable(root); scene.attach_renderable(root);
scene.set_dependency_parent(leaf, middle); scene.set_parent(Scene_Base::Relationship::dependency, leaf, middle);
scene.set_dependency_parent(middle, root); scene.set_parent(Scene_Base::Relationship::dependency, middle, root);
scene.render(); scene.render();
scene.wait_for_render(); scene.wait_for_render();
EXPECT_EQ(root->render_count, 1); 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(grandparent);
scene.attach_renderable(parent); scene.attach_renderable(parent);
scene.attach_renderable(child); scene.attach_renderable(child);
scene.set_display_parent(parent, grandparent); scene.set_parent(Scene_Base::Relationship::display, parent, grandparent);
scene.set_display_parent(child, parent); scene.set_parent(Scene_Base::Relationship::display, child, parent);
scene.set_dependency_parent(parent, grandparent); scene.set_parent(Scene_Base::Relationship::dependency, parent, grandparent);
scene.set_dependency_parent(child, parent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.render(); scene.render();
scene.wait_for_render(); scene.wait_for_render();
EXPECT_EQ(child->render_count, 1); EXPECT_EQ(child->render_count, 1);
@@ -124,11 +124,11 @@ TEST(scene_base_test, concurrent_render_and_topology_updates_remain_serialized)
std::thread mutator([&] { std::thread mutator([&] {
for (int index = 0; index < 100; ++index) { for (int index = 0; index < 100; ++index) {
if (index % 2 == 0) { if (index % 2 == 0) {
scene.set_dependency_parent(child, parent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.set_display_parent(child, parent); scene.set_parent(Scene_Base::Relationship::display, child, parent);
} else { } else {
scene.clear_dependency_parent(child); scene.clear_parents(Scene_Base::Relationship::dependency, child);
scene.clear_display_parent(child); scene.clear_parents(Scene_Base::Relationship::display, child);
} }
scene.set_renderable_configuration(child, {.cache_enabled = index % 2 == 0}); 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 parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = std::make_shared<Scene_Base_Test_Renderable>(scene); auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(child); scene.attach_renderable(child);
EXPECT_THROW(scene.set_dependency_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_display_parent(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); scene.attach_renderable(parent);
auto detached_child = std::make_shared<Scene_Base_Test_Renderable>(scene); 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) { TEST(scene_base_test, setting_same_topology_parent_is_a_noop) {
Scene2D_Context<> scene; 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); auto child = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent); scene.attach_renderable(parent);
scene.attach_renderable(child); 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(); 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); EXPECT_EQ(child->prepare_revision(), revision);
const auto topology = scene.topology_snapshot(); const auto topology = scene.topology_snapshot();
EXPECT_EQ(topology.dependency.size(), 2); 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(first);
scene.attach_renderable(second); scene.attach_renderable(second);
scene.attach_renderable(third); scene.attach_renderable(third);
scene.set_display_parent(second, first); scene.set_parent(Scene_Base::Relationship::display, second, first);
scene.add_display_parent(third, second); scene.add_parent(Scene_Base::Relationship::display, third, second);
EXPECT_THROW(scene.add_display_parent(first, third).wait(), std::invalid_argument); EXPECT_THROW(scene.add_parent(Scene_Base::Relationship::display, first, third).wait(), std::invalid_argument);
} }
TEST(scene_base_test, mutation_callback_receives_apply_failure) { TEST(scene_base_test, mutation_callback_receives_apply_failure) {
Scene2D_Context<> scene; 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); auto second = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(first).wait(); scene.attach_renderable(first).wait();
scene.attach_renderable(second).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_called{};
std::atomic<bool> callback_failed{}; 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_failed.store(static_cast<bool>(exception), std::memory_order_release);
callback_called.store(true, 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); auto renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
scene.attach_renderable(parent); scene.attach_renderable(parent);
scene.attach_renderable(axis); scene.attach_renderable(axis);
scene.attach_renderable(renderable, { auto mutation = scene.attach_renderable(renderable, {
.display_parents = {parent}, .display_parents = {parent},
.display_children = {axis}, .display_children = {axis},
.dependency_parents = {parent, axis} .dependency_parents = {parent, axis}
}); });
scene.wait_for_mutations(); mutation.wait();
const auto topology = scene.topology_snapshot(); const auto topology = scene.topology_snapshot();
const auto contains = [](const auto& relationships, const auto& child, const auto contains = [](const auto& relationships, const auto& child,
const auto& parent_value) { 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 parent = std::make_shared<Scene_Base_Test_Renderable>(scene);
auto child = 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(parent);
scene.attach_renderable(child); scene.attach_renderable(child).wait();
scene.wait_for_mutations();
std::thread writer([&] { std::thread writer([&] {
for (int index = 0; index < 500; ++index) { for (int index = 0; index < 500; ++index) {
if (index % 2 == 0) { if (index % 2 == 0) {
scene.set_dependency_parent(child, parent); scene.set_parent(Scene_Base::Relationship::dependency, child, parent);
scene.set_display_parent(child, parent); scene.set_parent(Scene_Base::Relationship::display, child, parent);
} else { } else {
scene.clear_dependency_parent(child); scene.clear_parents(Scene_Base::Relationship::dependency, child);
scene.clear_display_parent(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 renderable = std::make_shared<Scene_Base_Test_Renderable>(scene);
{ {
auto builder = scene.attach_builder(); auto builder = scene.attach_builder();
builder.attach_renderable(renderable); builder.attach(renderable);
EXPECT_EQ(scene.renderable_count(), 1u); EXPECT_EQ(scene.renderable_count(), 1u);
} }
scene.render(); 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); start.store(true, std::memory_order_release);
renderer.join(); renderer.join();
rebuilder.join(); rebuilder.join();
scene.wait_for_mutations(); scene.render();
EXPECT_EQ(executed.load(std::memory_order_acquire), render_count); 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) { 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>>; 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); auto root = detail::make_renderable_group(*this, true);
root->set_object_name("root"); root->set_object_name("root");
auto builder = this->attach_builder(); auto builder = this->attach_builder();
builder.attach_renderable(root); builder.attach(root);
} }
[[nodiscard]] std::shared_ptr<Renderable> root_renderable() const { [[nodiscard]] std::shared_ptr<Renderable> root_renderable() const {
const auto topology = this->topology_snapshot(); const auto topology = this->topology_snapshot();
@@ -236,12 +236,10 @@ public:
attach_renderable_node(renderable, parent); attach_renderable_node(renderable, parent);
return renderable; return renderable;
} }
::Scene_Base::Mutation remove_renderable( ::Scene_Base::Mutation remove_renderable(const std::shared_ptr<Renderable>& renderable) {
const std::shared_ptr<Renderable>& renderable,
::Scene_Base::Mutation_Callback callback = {}) {
if (!renderable || renderable == root_renderable()) if (!renderable || renderable == root_renderable())
return {}; return {};
return this->detach_renderable(renderable, std::move(callback)); return this->detach_renderable(renderable);
} }
void set_background_color(Color color) { void set_background_color(Color color) {
if (background_color() == color) if (background_color() == color)
+16 -22
View File
@@ -1,22 +1,17 @@
#include "Web_Server.h" #include "Web_Server.h"
#include "Gallery_WebSocket_Controller.h" #include "Gallery_WebSocket_Controller.h"
#include "Renderive_WebSocket_Controller.h" #include "Renderive_WebSocket_Controller.h"
#include "Web_Performance_Log.h" #include "Web_Performance_Log.h"
#include <drogon/drogon.h> #include <drogon/drogon.h>
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <thread> #include <thread>
namespace renderive::web { namespace renderive::web {
int run_web_server(std::uint16_t port, const std::filesystem::path& asset_root) { 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"; const std::filesystem::path gallery_index = gallery_root / "index.html";
if (!std::filesystem::is_regular_file(gallery_index)) { if (!std::filesystem::is_regular_file(gallery_index)) {
std::cerr << "Renderive Gallery assets not found: " << gallery_index << '\n'; 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 gallery_controller = std::make_shared<Gallery_WebSocket_Controller>();
const auto hardware_threads = std::max(2U, std::thread::hardware_concurrency()); const auto hardware_threads = std::max(2U, std::thread::hardware_concurrency());
std::cout << "Renderive WebSocket backend: ws://127.0.0.1:" << port std::cout << "Renderive WebSocket backend: ws://127.0.0.1:" << port
<< "/renderive\n" << "/renderive\n"
<< "Renderive control gallery: ws://127.0.0.1:" << port << "Renderive control gallery: ws://127.0.0.1:" << port
<< "/renderive/gallery\n" << "/renderive/gallery\n"
<< "Renderive hosted gallery: http://127.0.0.1:" << port << "/\n" << "Renderive hosted gallery: http://127.0.0.1:" << port << "/\n"
<< "Renderive performance log: " << web_performance_log_path().string() << "\n" << "Renderive performance log: " << web_performance_log_path().string() << "\n"
<< "HTTP only serves static HTML/CSS/JS; events and pixels stay on WebSocket.\n"; << "HTTP only serves static HTML/CSS/JS; events and pixels stay on WebSocket.\n";
auto& app = drogon::app(); auto& app = drogon::app();
const auto redirect_to_gallery = []( const auto redirect_to_gallery = [](
const drogon::HttpRequestPtr&, 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.registerHandler("/gallery/", redirect_to_gallery, {drogon::Get}); app.registerHandler("/gallery/", redirect_to_gallery, {drogon::Get});
app app
.registerController(controller) .registerController(controller)
.registerController(gallery_controller) .registerController(gallery_controller)
.setDocumentRoot(gallery_root.string()) .setDocumentRoot(gallery_root.string())
.setHomePage("index.html") .setHomePage("index.html")
.setStaticFileHeaders({{"Cache-Control", "no-store"}}) .setStaticFileHeaders({{"Cache-Control", "no-store"}})
.addListener("127.0.0.1", port) .addListener("127.0.0.1", port)
.setThreadNum(std::min(8U, hardware_threads)) .setThreadNum(std::min(8U, hardware_threads))
.setIdleConnectionTimeout(90) .setIdleConnectionTimeout(90)
.run(); .run();
return 0; return 0;
} }
} // namespace renderive::web } // namespace renderive::web
+4 -4
View File
@@ -7,12 +7,12 @@
namespace { namespace {
std::uint16_t parse_port(int argc, char** argv) { std::uint16_t parse_port(int argc, char** argv) {
constexpr std::uint16_t default_port = 8848; 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; return default_port;
unsigned value{}; unsigned value{};
const std::string_view text(argv[2]); const std::string_view text(argv[2]);
const auto [end, error] = std::from_chars(text.data(), text.data() + text.size(), value); 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'; std::cerr << "Invalid port: " << text << '\n';
return 0; return 0;
} }
@@ -21,9 +21,9 @@ std::uint16_t parse_port(int argc, char** argv) {
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
const std::uint16_t port = parse_port(argc, argv); const std::uint16_t port = parse_port(argc, argv);
if(port == 0) if (port == 0)
return 2; return 2;
std::cout << "http://127.0.0.1:8848" << std::endl; std::cout << "http://127.0.0.1:8848" << std::endl;
const std::filesystem::path executable = std::filesystem::absolute(argv[0]); 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());
} }