This commit is contained in:
2026-09-02 11:43:48 +08:00
parent e7fdbb93c1
commit d30839597f
52 changed files with 402 additions and 372 deletions
@@ -1,14 +0,0 @@
#pragma once
#include "global.hpp"
#include "../scene/rely_facade.h"
#include <cstdint>
#include <expected>
#include <functional>
#include <proxy/proxy.h>
#include <string>
namespace aethera::renderable {
enum struct Add_Task_Graph_Result : std::uint8_t { work_missing };
struct Task_Node : facade_builder::add_skill<pro::skills::rtti>::build {};
struct Task_Graph : facade_builder::add_convention<_add, std::expected<proxy<Task_Node>, Add_Task_Graph_Result>(std::string, std::function<void()>)>::add_convention<_clear, void()>::add_convention<_empty, bool() const noexcept>::add_convention<_taskflow, proxy<scene::Task_Graph>()>::support_relocation<pro::constraint_level::nothrow>::build {};
proxy<Task_Graph> make_task_graph(std::string name);
} // namespace aethera::renderable
@@ -1,19 +0,0 @@
#pragma once
#include "global.hpp"
#include <cstdint>
#include <exception>
#include <expected>
#include <functional>
#include <proxy/proxy.h>
#include <string>
namespace aethera::scene {
enum struct Compose_Task_Graph_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed };
enum struct Precede_Task_Graph_Result : std::uint8_t { preceded, node_unavailable, dependency_cycle };
enum struct Run_Task_Graph_Result : std::uint8_t { submitted, runtime_not_initialized, completion_missing };
using Taskflow_Completion = std::function<void(std::exception_ptr)>;
struct Task_Node : facade_builder::add_skill<pro::skills::rtti>::build {};
struct Task_Graph_Component : facade_builder::add_skill<pro::skills::rtti>::build {};
struct Task_Graph : facade_builder::add_convention<_component, proxy<Task_Graph_Component>()>::add_convention<_compose, std::expected<proxy<Task_Node>, Compose_Task_Graph_Result>(std::string, proxy<Task_Graph_Component>&)>::add_convention<_precede, Precede_Task_Graph_Result(proxy<Task_Node>&, proxy<Task_Node>&)>::add_convention<_empty, bool() const noexcept>::add_convention<_run, Run_Task_Graph_Result(Taskflow_Completion)>::support_relocation<pro::constraint_level::nothrow>::build {};
proxy<Task_Graph> make_task_graph(std::string name);
struct Renderable : facade_builder::add_convention<_advance, void()>::add_convention<_taskflow, proxy<Task_Graph>()>::support_relocation<pro::constraint_level::nothrow>::build {};
} // namespace aethera::scene
@@ -1,10 +0,0 @@
#pragma once
#include <cstdint>
namespace aethera {
enum struct Add_Task_Graph_Result : std::uint8_t { work_missing };
enum struct Add_Condition_Task_Graph_Result : std::uint8_t { condition_missing };
enum struct Compose_Task_Graph_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed };
enum struct Precede_Task_Graph_Result : std::uint8_t { preceded, node_unavailable, node_stale, different_graph };
enum struct Describe_Task_Graph_Result : std::uint8_t { described, key_missing };
enum struct Describe_Task_Graph_Node_Result : std::uint8_t { described, node_unavailable, node_stale, key_missing };
} // namespace aethera
@@ -1,19 +0,0 @@
#pragma once
#include <cstdint>
#include <exception>
#include <functional>
namespace aethera {
enum struct Run_Taskflow_Result : std::uint8_t {
submitted,
runtime_not_initialized,
completion_missing,
observation_unavailable
};
enum struct Taskflow_Unknown_Failure_Policy : std::uint8_t {
fast_fail,
exception
};
using Taskflow_Completion = std::function<void(std::exception_ptr)>;
/* Runtime owns one immutable copy for its process lifetime; borrowed captures must outlive the runtime. */
using Taskflow_Exception_Boundary = std::function<void(std::exception_ptr)>;
} // namespace aethera
@@ -1,90 +0,0 @@
#include "Task_Graph.hpp"
#include "Task_Runtime.hpp"
#include "function/renderable/rely_facade.h"
#include "function/scene/rely_facade.h"
#include "model/Model.hpp"
#include <memory>
#include <utility>
namespace aethera {
namespace detail {
struct Scene_Task_Node { explicit Scene_Task_Node(Task_Node value) : value(std::move(value)) {} Task_Node value; };
struct Scene_Task_Component { explicit Scene_Task_Component(std::shared_ptr<Task_Graph> value) : value(std::move(value)) {} std::shared_ptr<Task_Graph> value; };
struct Renderable_Task_Node { explicit Renderable_Task_Node(Task_Node value) : value(std::move(value)) {} Task_Node value; };
struct Scene_Task_Graph_Model : Def<Scene_Task_Graph_Model, Root> {
struct Private;
explicit Scene_Task_Graph_Model(std::shared_ptr<Task_Graph> graph) : Def(std::move(graph)) {}
};
struct Renderable_Task_Graph_Model : Def<Renderable_Task_Graph_Model, Root> {
struct Private;
explicit Renderable_Task_Graph_Model(std::shared_ptr<Task_Graph> graph) : Def(std::move(graph)) {}
};
struct Scene_Task_Graph_Model::Private : Prev_Private {
explicit Private(std::shared_ptr<Task_Graph> graph) : graph(std::move(graph)) {}
proxy<scene::Task_Graph_Component> component() { return pro::make_proxy<scene::Task_Graph_Component, Scene_Task_Component>(graph); }
auto compose(std::string name, proxy<scene::Task_Graph_Component>& child) -> std::expected<proxy<scene::Task_Node>, scene::Compose_Task_Graph_Result>;
scene::Precede_Task_Graph_Result precede(proxy<scene::Task_Node>& before, proxy<scene::Task_Node>& after);
bool empty() const noexcept { return graph->empty(); }
scene::Run_Task_Graph_Result run(scene::Taskflow_Completion completion);
std::shared_ptr<Task_Graph> graph; /* 共享同一权威任务图,仅用于为不同消费模块生成视图。 */
};
struct Renderable_Task_Graph_Model::Private : Prev_Private {
explicit Private(std::shared_ptr<Task_Graph> graph) : graph(std::move(graph)) {}
auto add(std::string name, std::function<void()> work) -> std::expected<proxy<renderable::Task_Node>, renderable::Add_Task_Graph_Result>;
void clear() { graph->clear(); }
bool empty() const noexcept { return graph->empty(); }
proxy<scene::Task_Graph> taskflow() { return make_model_proxy<scene::Task_Graph, Scene_Task_Graph_Model>(graph); }
std::shared_ptr<Task_Graph> graph; /* 共享同一权威任务图,仅用于为不同消费模块生成视图。 */
};
} // namespace detail
auto detail::Scene_Task_Graph_Model::Private::compose(std::string name, proxy<scene::Task_Graph_Component>& child) -> std::expected<proxy<scene::Task_Node>, scene::Compose_Task_Graph_Result> {
auto* component = child ? proxy_cast<Scene_Task_Component>(&*child) : nullptr;
if (component == nullptr) return std::unexpected(scene::Compose_Task_Graph_Result::component_unavailable);
auto result = graph->compose(std::move(name), *component->value);
if (!result) {
switch (result.error()) {
case aethera::Compose_Task_Graph_Result::component_unavailable: return std::unexpected(scene::Compose_Task_Graph_Result::component_unavailable);
case aethera::Compose_Task_Graph_Result::dependency_cycle: return std::unexpected(scene::Compose_Task_Graph_Result::dependency_cycle);
case aethera::Compose_Task_Graph_Result::already_composed: return std::unexpected(scene::Compose_Task_Graph_Result::already_composed);
}
}
return pro::make_proxy<scene::Task_Node, Scene_Task_Node>(std::move(*result));
}
scene::Precede_Task_Graph_Result detail::Scene_Task_Graph_Model::Private::precede(proxy<scene::Task_Node>& before, proxy<scene::Task_Node>& after) {
auto* before_node = before ? proxy_cast<Scene_Task_Node>(&*before) : nullptr;
auto* after_node = after ? proxy_cast<Scene_Task_Node>(&*after) : nullptr;
if (before_node == nullptr || after_node == nullptr) return scene::Precede_Task_Graph_Result::node_unavailable;
switch (before_node->value.precede(after_node->value)) {
case aethera::Precede_Task_Graph_Result::preceded: return scene::Precede_Task_Graph_Result::preceded;
case aethera::Precede_Task_Graph_Result::node_unavailable: return scene::Precede_Task_Graph_Result::node_unavailable;
case aethera::Precede_Task_Graph_Result::node_stale:
case aethera::Precede_Task_Graph_Result::different_graph: return scene::Precede_Task_Graph_Result::node_unavailable;
}
std::terminate();
}
scene::Run_Task_Graph_Result detail::Scene_Task_Graph_Model::Private::run(scene::Taskflow_Completion completion) {
switch (run_taskflow(*graph, std::move(completion))) {
case Run_Taskflow_Result::submitted: return scene::Run_Task_Graph_Result::submitted;
case Run_Taskflow_Result::runtime_not_initialized: return scene::Run_Task_Graph_Result::runtime_not_initialized;
case Run_Taskflow_Result::completion_missing: return scene::Run_Task_Graph_Result::completion_missing;
case Run_Taskflow_Result::observation_unavailable: std::terminate();
}
std::terminate();
}
auto detail::Renderable_Task_Graph_Model::Private::add(std::string name, std::function<void()> work) -> std::expected<proxy<renderable::Task_Node>, renderable::Add_Task_Graph_Result> {
auto result = graph->add(std::move(name), std::move(work));
if (!result) return std::unexpected(renderable::Add_Task_Graph_Result::work_missing);
return pro::make_proxy<renderable::Task_Node, Renderable_Task_Node>(std::move(*result));
}
proxy<scene::Task_Graph> scene::make_task_graph(std::string name) { return make_model_proxy<scene::Task_Graph, detail::Scene_Task_Graph_Model>(std::make_shared<aethera::Task_Graph>(std::move(name))); }
proxy<renderable::Task_Graph> renderable::make_task_graph(std::string name) { return make_model_proxy<renderable::Task_Graph, detail::Renderable_Task_Graph_Model>(std::make_shared<aethera::Task_Graph>(std::move(name))); }
} // namespace aethera
@@ -0,0 +1,12 @@
#pragma once
#include <cstdint>
namespace aethera {
enum struct Frame_Policy_Configuration_Result : std::uint8_t { invalid_user_frame_rate, interval_out_of_range, invalid_statistics_window };
enum struct Frame_Policy_Render_Submission_Result : std::uint8_t { runtime_not_initialized, task_graph_invalid };
enum struct Throttled_Latest_Only_Start_Result : std::uint8_t { already_running, start_in_progress, stop_in_progress, dependency_unavailable, frame_unavailable, invalid_user_frame_rate, interval_out_of_range, invalid_statistics_window };
enum struct Throttled_Latest_Only_Stop_Result : std::uint8_t { already_stopped, already_stopping, start_in_progress, completion_missing };
namespace frame_policy {
enum struct Render_Scene_Result : std::uint8_t { submitted, runtime_not_initialized, task_graph_invalid, frame_missing, completion_missing };
} // namespace frame_policy
} // namespace aethera
@@ -1,10 +1,13 @@
#include "Frame_Policy.hpp"
#include "rely_facade.h"
#include "time_thread/src/Timer_Service_Model.hpp"
#include <algorithm>
#include <cmath>
#include <limits>
namespace aethera {
proxy<frame_policy::Timer_Service> frame_policy::make_timer_service() { return time_thread::make_timer_service_proxy<frame_policy::Timer_Service>(make_steady_timer_time_source()); }
namespace {
auto capacity_fps(const Sliding_Statistics& statistics) noexcept -> std::optional<double> {
const double average = statistics.summary().average;
@@ -1,4 +1,5 @@
#pragma once
#include "../export/export.h"
#include "concurrent/base/Concurrent_Struct.hpp"
#include "model/Model.hpp"
#include "statistics/Sliding_Statistics.hpp"
@@ -8,15 +9,6 @@
#include <expected>
#include <optional>
namespace aethera {
enum struct Frame_Policy_Configuration_Result : std::uint8_t {
invalid_user_frame_rate,
interval_out_of_range,
invalid_statistics_window
};
enum struct Frame_Policy_Render_Submission_Result : std::uint8_t {
runtime_not_initialized,
task_graph_invalid
};
struct Frame_Policy_Prop {
std::optional<double> user_frames_per_second; /* 有值时作为用户帧率上限;无值时不参与限速。 */
bool render_rate_limit_enabled{}; /* true 时用渲染平均耗时推导理论生成上限。 */
@@ -1,5 +1,5 @@
#pragma once
#include "function/frame_policy/Frame_Policy.hpp"
#include "Frame_Policy.hpp"
#include "rely_facade.h"
#include <cstdint>
#include <expected>
@@ -10,22 +10,8 @@ namespace aethera {
* tickstop completion Scene/Sink
*/
struct Throttled_Latest_only : Def<Throttled_Latest_only, Frame_Policy> {
enum struct Start_Result : std::uint8_t {
already_running,
start_in_progress,
stop_in_progress,
dependency_unavailable,
frame_unavailable,
invalid_user_frame_rate,
interval_out_of_range,
invalid_statistics_window
};
enum struct Stop_Result : std::uint8_t {
already_stopped,
already_stopping,
start_in_progress,
completion_missing
};
using Start_Result = Throttled_Latest_Only_Start_Result;
using Stop_Result = Throttled_Latest_Only_Stop_Result;
using Stop_Completion = std::function<void()>;
struct Private;
Throttled_Latest_only(proxy<frame_policy::Timer_Service> timer_service, proxy<frame_policy::Scene> scene, proxy<frame_policy::Sink> sink);
@@ -1,4 +1,5 @@
#pragma once
#include "../export/export.h"
#include "global.hpp"
#include <chrono>
#include <cstdint>
@@ -12,7 +13,6 @@ using Timer_Callback = std::function<void()>;
struct Frame : facade_builder::build {};
using Frame_Completion = std::function<void(proxy<Frame>&)>;
using Render_Completion = std::function<void(proxy<Frame>&, std::exception_ptr)>;
enum struct Render_Scene_Result : std::uint8_t { submitted, runtime_not_initialized, task_graph_invalid, frame_missing, completion_missing };
struct Scene : facade_builder::add_convention<_create_frame, proxy<Frame>()>::add_convention<_render, Render_Scene_Result(proxy<Frame>&, Render_Completion)>::support_relocation<pro::constraint_level::nothrow>::build {};
struct Sink : facade_builder::add_convention<_send, void(proxy<Frame>&, Frame_Completion)>::support_relocation<pro::constraint_level::nothrow>::build {};
struct Timer_Service : facade_builder::add_convention<_schedule_every, Timer_Id(std::chrono::nanoseconds, Timer_Callback)>::add_convention<_cancel, void(Timer_Id, Timer_Callback)>::add_convention<_reschedule, void(Timer_Id, std::chrono::nanoseconds)>::support_relocation<pro::constraint_level::nothrow>::build {};
@@ -1,4 +1,4 @@
#include "function/frame_policy/Throttled_Latest_Only.hpp"
#include "frame_policy/src/Throttled_Latest_Only.hpp"
#include "statistics/Sliding_Statistics.hpp"
#include <gtest/gtest.h>
+26
View File
@@ -0,0 +1,26 @@
function(aethera_kernel_add_module target module_name)
set(module_dir "${Aethera_Kernel_module_dir}/${module_name}")
append_glob_source(module_sources "${module_dir}/src" "${module_dir}/export")
add_library(${target} STATIC ${module_sources})
target_include_directories(${target} PUBLIC "$<BUILD_INTERFACE:${Aethera_Kernel_module_dir}>")
target_link_libraries(${target} PUBLIC Aethera_Kernel_Core ${ARGN})
append_glob_source(Aethera_Kernel_module_test_sources "${module_dir}/test")
set(Aethera_Kernel_module_test_sources "${Aethera_Kernel_module_test_sources}" PARENT_SCOPE)
endfunction()
aethera_kernel_add_module(Aethera_Kernel_Task_Flow task_flow Taskflow::Taskflow)
add_library(Aethera_Kernel::Task_Flow ALIAS Aethera_Kernel_Task_Flow)
target_link_libraries(Aethera_Kernel_Task_Flow PRIVATE Threads::Threads)
aethera_kernel_add_module(Aethera_Kernel_Time_Thread time_thread)
add_library(Aethera_Kernel::Time_Thread ALIAS Aethera_Kernel_Time_Thread)
target_link_libraries(Aethera_Kernel_Time_Thread PRIVATE Threads::Threads)
aethera_kernel_add_module(Aethera_Kernel_Frame_Policy frame_policy Aethera_Kernel_Time_Thread)
add_library(Aethera_Kernel::Frame_Policy ALIAS Aethera_Kernel_Frame_Policy)
aethera_kernel_add_module(Aethera_Kernel_Renderable renderable Aethera_Kernel_Task_Flow)
add_library(Aethera_Kernel::Renderable ALIAS Aethera_Kernel_Renderable)
aethera_kernel_add_module(Aethera_Kernel_Scene scene Aethera_Kernel_Task_Flow Aethera_Kernel_Renderable Aethera_Kernel_Frame_Policy)
add_library(Aethera_Kernel::Scene ALIAS Aethera_Kernel_Scene)
@@ -0,0 +1 @@
#pragma once
@@ -0,0 +1,12 @@
#pragma once
#include "global.hpp"
#include "task_flow/export/export.h"
#include <expected>
#include <functional>
#include <proxy/proxy.h>
#include <string>
namespace aethera::renderable {
struct Task_Graph : facade_builder::add_convention<_add, std::expected<proxy<task_flow::Task_Node>, aethera::Add_Task_Graph_Result>(std::string, std::function<void()>)>::add_convention<_component, proxy<task_flow::Task_Graph_Component>()>::add_convention<_clear, void()>::add_convention<_empty, bool() const noexcept>::support_relocation<pro::constraint_level::nothrow>::build {};
proxy<Task_Graph> make_task_graph(std::string name);
} // namespace aethera::renderable
@@ -0,0 +1,7 @@
#include "renderable.hpp"
#include "task_flow/src/Task_Graph_Model.hpp"
#include <utility>
namespace aethera {
proxy<renderable::Task_Graph> renderable::make_task_graph(std::string name) { return task_flow::make_task_graph<renderable::Task_Graph>(std::move(name)); }
} // namespace aethera
@@ -2,7 +2,7 @@
namespace aethera {
template <typename Self> struct Renderable<Self>::Private : Prev_Private {
void advance();
proxy<scene::Task_Graph> taskflow();
proxy<task_flow::Task_Graph_Component> taskflow();
proxy<renderable::Task_Graph>& internal_taskflow() noexcept { return graph; }
private:
proxy<renderable::Task_Graph> graph{renderable::make_task_graph("renderable")}; /* 唯一拥有本 Renderable 的任务图代理。 */
@@ -11,5 +11,5 @@ template <typename Self> void Renderable<Self>::Private::advance() {
auto& private_data = static_cast<typename Self::Private&>(*this);
if constexpr (requires { private_data.advance_renderable(); }) private_data.advance_renderable();
}
template <typename Self> proxy<scene::Task_Graph> Renderable<Self>::Private::taskflow() { return graph->taskflow(); }
template <typename Self> proxy<task_flow::Task_Graph_Component> Renderable<Self>::Private::taskflow() { return graph->component(); }
} // namespace aethera
@@ -0,0 +1,19 @@
#include "renderable/src/renderable.hpp"
#include "scene/src/rely_facade.h"
#include <gtest/gtest.h>
namespace {
struct Test_Renderable : aethera::Def<Test_Renderable, aethera::Renderable<Test_Renderable>> {
struct Private : Prev_Private {
void advance_renderable() { ++advance_count; auto& graph = internal_taskflow(); graph->clear(); (void)graph->add("test.prepare", [] {}); }
int advance_count{};
};
};
static_assert(pro::proxiable<aethera::detail::Model_Private_Pointer<Test_Renderable*>, aethera::scene::Renderable>);
TEST(Renderable, Facade_Uses_Private_Task_Graph_Capabilities) {
auto renderable = aethera::make_model_proxy<aethera::scene::Renderable, Test_Renderable>();
renderable->advance();
EXPECT_TRUE(renderable->taskflow());
}
} // namespace
@@ -0,0 +1,6 @@
#pragma once
#include <cstdint>
namespace aethera {
enum struct Scene_Create_Taskflow_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed, dependency_unavailable };
} // namespace aethera
@@ -1,4 +1,5 @@
#include "Scene.hpp"
#include "task_flow/src/Task_Graph_Model.hpp"
#include <string>
#include <unordered_map>
#include <utility>
@@ -6,6 +7,7 @@ namespace aethera {
namespace detail {
struct Scene_Frame {};
}
proxy<scene::Task_Graph> scene::make_task_graph(std::string name) { return task_flow::make_task_graph<scene::Task_Graph>(std::move(name)); }
Scene::Private::Private() = default;
proxy<frame_policy::Frame> Scene::Private::create_frame() {
return pro::make_proxy<frame_policy::Frame, detail::Scene_Frame>();
@@ -15,21 +17,20 @@ proxy<scene::Task_Graph>& Scene::Private::taskflow() {
}
auto Scene::Private::create_taskflow() -> std::expected<proxy<scene::Task_Graph>, Create_Taskflow_Result> {
auto result = scene::make_task_graph("scene.renderables");
std::unordered_map<std::uint64_t, proxy<scene::Task_Node>> nodes;
std::unordered_map<std::uint64_t, proxy<task_flow::Task_Node>> nodes;
auto& renderables = get<Scene_Renderable_Dag>().internal.use();
std::optional<Create_Taskflow_Result> error;
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
if (error) return;
auto taskflow = renderable->taskflow();
auto component = taskflow->component();
auto component = renderable->taskflow();
auto composed = result->compose("renderable." + std::to_string(id.value), component);
if (!composed) {
switch (composed.error()) {
case scene::Compose_Task_Graph_Result::component_unavailable: error = Create_Taskflow_Result::component_unavailable;
case Compose_Task_Graph_Result::component_unavailable: error = Create_Taskflow_Result::component_unavailable;
break;
case scene::Compose_Task_Graph_Result::dependency_cycle: error = Create_Taskflow_Result::dependency_cycle;
case Compose_Task_Graph_Result::dependency_cycle: error = Create_Taskflow_Result::dependency_cycle;
break;
case scene::Compose_Task_Graph_Result::already_composed: error = Create_Taskflow_Result::already_composed;
case Compose_Task_Graph_Result::already_composed: error = Create_Taskflow_Result::already_composed;
break;
}
return;
@@ -38,7 +39,7 @@ auto Scene::Private::create_taskflow() -> std::expected<proxy<scene::Task_Graph>
});
if (error) return std::unexpected(*error);
renderables.for_each_dependency([&](Dag_Node_Id target, Dag_Node_Id source) {
if (result->precede(nodes.at(source.value), nodes.at(target.value)) != scene::Precede_Task_Graph_Result::preceded) error = Create_Taskflow_Result::dependency_unavailable;
if (result->precede(nodes.at(source.value), nodes.at(target.value)) != Precede_Task_Graph_Result::preceded) error = Create_Taskflow_Result::dependency_unavailable;
});
if (error) return std::unexpected(*error);
return result;
@@ -62,9 +63,10 @@ frame_policy::Render_Scene_Result Scene::Private::render(proxy<frame_policy::Fra
completion(frame.get(), std::move(failure));
});
switch (result) {
case scene::Run_Task_Graph_Result::submitted: return frame_policy::Render_Scene_Result::submitted;
case scene::Run_Task_Graph_Result::runtime_not_initialized: return frame_policy::Render_Scene_Result::runtime_not_initialized;
case scene::Run_Task_Graph_Result::completion_missing: return frame_policy::Render_Scene_Result::completion_missing;
case Run_Taskflow_Result::submitted: return frame_policy::Render_Scene_Result::submitted;
case Run_Taskflow_Result::runtime_not_initialized: return frame_policy::Render_Scene_Result::runtime_not_initialized;
case Run_Taskflow_Result::completion_missing: return frame_policy::Render_Scene_Result::completion_missing;
case Run_Taskflow_Result::observation_unavailable: std::terminate();
}
std::terminate();
}
@@ -1,5 +1,6 @@
#pragma once
#include "../frame_policy/rely_facade.h"
#include "../export/export.h"
#include "frame_policy/src/rely_facade.h"
#include "model/Model.hpp"
#include "relation/Dag.hpp"
#include <functional>
@@ -1,7 +1,7 @@
#pragma once
namespace aethera {
struct Scene::Private : Prev_Private {
enum struct Create_Taskflow_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed, dependency_unavailable };
using Create_Taskflow_Result = Scene_Create_Taskflow_Result;
Private();
proxy<frame_policy::Frame> create_frame();
frame_policy::Render_Scene_Result render(proxy<frame_policy::Frame>& frame, frame_policy::Render_Completion completion);
@@ -0,0 +1,12 @@
#pragma once
#include "global.hpp"
#include "task_flow/export/export.h"
#include <expected>
#include <proxy/proxy.h>
#include <string>
namespace aethera::scene {
struct Task_Graph : facade_builder::add_convention<_component, proxy<task_flow::Task_Graph_Component>()>::add_convention<_compose, std::expected<proxy<task_flow::Task_Node>, aethera::Compose_Task_Graph_Result>(std::string, proxy<task_flow::Task_Graph_Component>&)>::add_convention<_precede, aethera::Precede_Task_Graph_Result(proxy<task_flow::Task_Node>&, proxy<task_flow::Task_Node>&)>::add_convention<_empty, bool() const noexcept>::add_convention<_run, aethera::Run_Taskflow_Result(aethera::Taskflow_Completion)>::support_relocation<pro::constraint_level::nothrow>::build {};
proxy<Task_Graph> make_task_graph(std::string name);
struct Renderable : facade_builder::add_convention<_advance, void()>::add_convention<_taskflow, proxy<task_flow::Task_Graph_Component>()>::support_relocation<pro::constraint_level::nothrow>::build {};
} // namespace aethera::scene
@@ -0,0 +1,103 @@
#include "renderable/src/renderable.hpp"
#include "scene/src/Scene.hpp"
#include <gtest/gtest.h>
#include <exception>
#include <memory>
#include <optional>
#include <vector>
namespace {
struct Test_Renderable : aethera::Def<Test_Renderable, aethera::Renderable<Test_Renderable>> {
struct Private : Prev_Private { void advance_renderable() { auto& graph = internal_taskflow(); graph->clear(); (void)graph->add("test.prepare", [] {}); } };
};
struct Test_Dag_Renderable : aethera::Def<Test_Dag_Renderable, aethera::Renderable<Test_Dag_Renderable>> {
Test_Dag_Renderable(std::shared_ptr<std::vector<int>> advance_order, int id) : Def(std::move(advance_order), id) {}
struct Private : Prev_Private {
Private(std::shared_ptr<std::vector<int>> advance_order, int id) : advance_order(std::move(advance_order)), id(id) {}
void advance_renderable() { advance_order->push_back(id); auto& graph = internal_taskflow(); graph->clear(); (void)graph->add("prepare", [] {}); }
std::shared_ptr<std::vector<int>> advance_order;
int id{};
};
};
static_assert(pro::proxiable<aethera::detail::Model_Private_Pointer<aethera::Scene*>, aethera::frame_policy::Scene>);
TEST(Scene, Advances_Renderables_And_Composes_Task_Graph_In_Dag_Order) {
auto advance_order = std::make_shared<std::vector<int>>();
aethera::Dag<aethera::scene::Renderable>::Builder builder;
builder.add({2}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 2));
builder.add({1}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 1));
builder.add({3}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 3));
builder.add_dependency({2}, {1});
builder.add_dependency({3}, {1});
auto graph = builder.build();
ASSERT_TRUE(graph.has_value());
aethera::Scene scene;
EXPECT_TRUE(scene.d->create_frame());
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
EXPECT_TRUE(scene.d->advance().has_value());
EXPECT_EQ(*advance_order, (std::vector<int>{1, 2, 3}));
EXPECT_FALSE(scene.d->taskflow()->empty());
advance_order->clear();
std::optional<aethera::Edit_Dag_Result> edit_error;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>([](aethera::Dag<aethera::scene::Renderable>::Editor& editor) { editor.remove({99}); }, [&](std::expected<void, aethera::Edit_Dag_Result> result) { if (!result) edit_error = result.error(); });
EXPECT_TRUE(scene.d->advance().has_value());
ASSERT_TRUE(edit_error.has_value());
EXPECT_EQ(*edit_error, aethera::Edit_Dag_Result::node_not_found);
EXPECT_EQ(*advance_order, (std::vector<int>{1, 2, 3}));
}
TEST(Scene, Dag_Rejects_Unallocated_Id_And_Missing_Object) {
aethera::Dag<aethera::scene::Renderable>::Builder invalid_id_builder;
invalid_id_builder.add({0}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Renderable>());
const auto invalid_id = invalid_id_builder.build();
ASSERT_FALSE(invalid_id.has_value());
EXPECT_EQ(invalid_id.error(), aethera::Build_Dag_Result::invalid_node_id);
aethera::Dag<aethera::scene::Renderable>::Builder missing_object_builder;
missing_object_builder.add({1}, {});
const auto missing_object = missing_object_builder.build();
ASSERT_FALSE(missing_object.has_value());
EXPECT_EQ(missing_object.error(), aethera::Build_Dag_Result::node_object_missing);
}
TEST(Scene, Owned_Dag_Rolls_Back_The_Whole_Invalid_Edit) {
auto advance_order = std::make_shared<std::vector<int>>();
aethera::Dag<aethera::scene::Renderable>::Builder builder;
builder.add({1}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 1));
auto graph = builder.build();
ASSERT_TRUE(graph.has_value());
aethera::Scene scene;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
ASSERT_TRUE(scene.d->advance().has_value());
advance_order->clear();
std::optional<aethera::Edit_Dag_Result> edit_error;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>([advance_order](aethera::Dag<aethera::scene::Renderable>::Editor& editor) { editor.add({2}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 2)); editor.add_dependency({2}, {99}); }, [&](std::expected<void, aethera::Edit_Dag_Result> result) { if (!result) edit_error = result.error(); });
ASSERT_TRUE(scene.d->advance().has_value());
ASSERT_TRUE(edit_error.has_value());
EXPECT_EQ(*edit_error, aethera::Edit_Dag_Result::node_not_found);
EXPECT_EQ(*advance_order, (std::vector<int>{1}));
}
TEST(Scene, Render_Returns_Known_Submission_Result) {
aethera::Scene scene;
auto frame = scene.d->create_frame();
bool completed{};
EXPECT_EQ(scene.d->render(frame, [&](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) { completed = true; }), aethera::frame_policy::Render_Scene_Result::runtime_not_initialized);
EXPECT_FALSE(completed);
aethera::proxy<aethera::frame_policy::Frame> empty_frame;
EXPECT_EQ(scene.d->render(empty_frame, [&](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) {}), aethera::frame_policy::Render_Scene_Result::frame_missing);
EXPECT_EQ(scene.d->render(frame, {}), aethera::frame_policy::Render_Scene_Result::completion_missing);
}
TEST(Scene, Rejects_The_Same_Renderable_Task_Graph_Twice) {
Test_Renderable renderable;
aethera::Dag<aethera::scene::Renderable>::Builder builder;
builder.add({1}, aethera::model_proxy_view<aethera::scene::Renderable>(renderable));
builder.add({2}, aethera::model_proxy_view<aethera::scene::Renderable>(renderable));
auto graph = builder.build();
ASSERT_TRUE(graph.has_value());
aethera::Scene scene;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
auto frame = scene.d->create_frame();
EXPECT_EQ(scene.d->render(frame, [](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) {}), aethera::frame_policy::Render_Scene_Result::task_graph_invalid);
}
} // namespace
@@ -0,0 +1,25 @@
#pragma once
#include "global.hpp"
#include <cstdint>
#include <exception>
#include <functional>
#include <proxy/proxy.h>
namespace aethera {
enum struct Add_Task_Graph_Result : std::uint8_t { work_missing };
enum struct Add_Condition_Task_Graph_Result : std::uint8_t { condition_missing };
enum struct Compose_Task_Graph_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed };
enum struct Precede_Task_Graph_Result : std::uint8_t { preceded, node_unavailable, node_stale, different_graph };
enum struct Describe_Task_Graph_Result : std::uint8_t { described, key_missing };
enum struct Describe_Task_Graph_Node_Result : std::uint8_t { described, node_unavailable, node_stale, key_missing };
enum struct Run_Taskflow_Result : std::uint8_t { submitted, runtime_not_initialized, completion_missing, observation_unavailable };
enum struct Taskflow_Unknown_Failure_Policy : std::uint8_t { fast_fail, exception };
enum struct Initialize_Task_Runtime_Result : std::uint8_t { initialized, invalid_worker_count, exception_boundary_missing, exception_boundary_unexpected, already_initialized, initialization_in_progress };
enum struct Take_Taskflow_Observation_Result : std::uint8_t { not_recorded, recording, already_taken };
using Taskflow_Completion = std::function<void(std::exception_ptr)>;
using Taskflow_Exception_Boundary = std::function<void(std::exception_ptr)>;
namespace task_flow {
struct Task_Node : facade_builder::add_skill<pro::skills::rtti>::build {};
struct Task_Graph_Component : facade_builder::add_skill<pro::skills::rtti>::build {};
} // namespace task_flow
} // namespace aethera
@@ -1,5 +1,5 @@
#pragma once
#include "Task_Graph_Types.hpp"
#include "../export/export.h"
#include <expected>
#include <functional>
#include <memory>
@@ -0,0 +1,30 @@
#pragma once
#include "Task_Graph.hpp"
#include "../export/export.h"
#include "model/Model.hpp"
#include <expected>
#include <functional>
#include <memory>
#include <string>
namespace aethera::task_flow {
struct Task_Graph_Model : Def<Task_Graph_Model, Root> {
struct Private;
explicit Task_Graph_Model(std::shared_ptr<aethera::Task_Graph> graph) : Def(std::move(graph)) {}
};
struct Task_Graph_Model::Private : Prev_Private {
explicit Private(std::shared_ptr<aethera::Task_Graph> graph) : graph(std::move(graph)) {}
auto add(std::string name, std::function<void()> work) -> std::expected<proxy<Task_Node>, Add_Task_Graph_Result>;
proxy<Task_Graph_Component> component();
auto compose(std::string name, proxy<Task_Graph_Component>& child) -> std::expected<proxy<Task_Node>, Compose_Task_Graph_Result>;
Precede_Task_Graph_Result precede(proxy<Task_Node>& before, proxy<Task_Node>& after);
void clear();
bool empty() const noexcept;
Run_Taskflow_Result run(Taskflow_Completion completion);
private:
std::shared_ptr<aethera::Task_Graph> graph; /* 所有消费 facade 共享的唯一权威任务图。 */
};
template <typename Facade> proxy<Facade> make_task_graph(std::string name) { return make_model_proxy<Facade, Task_Graph_Model>(std::make_shared<aethera::Task_Graph>(std::move(name))); }
} // namespace aethera::task_flow
@@ -1,6 +1,6 @@
#pragma once
#include "Task_Graph.hpp"
#include "Task_Runtime_Types.hpp"
#include "../export/export.h"
#include "Taskflow_Observation.hpp"
#include <cstddef>
#include <cstdint>
@@ -8,14 +8,6 @@
#include <functional>
#include <thread>
namespace aethera {
enum struct Initialize_Task_Runtime_Result : std::uint8_t {
initialized,
invalid_worker_count,
exception_boundary_missing,
exception_boundary_unexpected,
already_initialized,
initialization_in_progress
};
Initialize_Task_Runtime_Result initialize_task_runtime(std::size_t workers = std::thread::hardware_concurrency(), Taskflow_Unknown_Failure_Policy failure_policy = Taskflow_Unknown_Failure_Policy::fast_fail, Taskflow_Exception_Boundary exception_boundary = {});
/* 完成回调接收业务任务传播出的 Unknown Failure;本次执行不记录观察数据。 */
Run_Taskflow_Result run_taskflow(
@@ -1,4 +1,5 @@
#pragma once
#include "../export/export.h"
#include "Taskflow_Trace.hpp"
#include <cstdint>
#include <expected>
@@ -8,11 +9,6 @@ namespace aethera {
namespace detail {
struct Taskflow_Observation_Execution;
}
enum struct Take_Taskflow_Observation_Result : std::uint8_t {
not_recorded,
recording,
already_taken
};
/*
* Taskflow N
*
@@ -1,6 +1,6 @@
#pragma once
#include "../Task_Graph.hpp"
#include "../Task_Runtime_Types.hpp"
#include "../../export/export.h"
#include "../Taskflow_Observation.hpp"
#include "../Taskflow_Trace.hpp"
#include <chrono>
@@ -0,0 +1,37 @@
#include "Task_Graph_Model.hpp"
#include "Task_Runtime.hpp"
#include <utility>
namespace aethera::task_flow {
namespace detail {
struct Proxied_Task_Node { explicit Proxied_Task_Node(aethera::Task_Node value) : value(std::move(value)) {} aethera::Task_Node value; };
struct Proxied_Task_Graph_Component { explicit Proxied_Task_Graph_Component(std::shared_ptr<aethera::Task_Graph> value) : value(std::move(value)) {} std::shared_ptr<aethera::Task_Graph> value; };
} // namespace detail
auto Task_Graph_Model::Private::add(std::string name, std::function<void()> work) -> std::expected<proxy<Task_Node>, Add_Task_Graph_Result> {
auto result = graph->add(std::move(name), std::move(work));
if (!result) return std::unexpected(result.error());
return pro::make_proxy<Task_Node, detail::Proxied_Task_Node>(std::move(*result));
}
proxy<Task_Graph_Component> Task_Graph_Model::Private::component() { return pro::make_proxy<Task_Graph_Component, detail::Proxied_Task_Graph_Component>(graph); }
auto Task_Graph_Model::Private::compose(std::string name, proxy<Task_Graph_Component>& child) -> std::expected<proxy<Task_Node>, Compose_Task_Graph_Result> {
auto* component = child ? proxy_cast<detail::Proxied_Task_Graph_Component>(&*child) : nullptr;
if (component == nullptr) return std::unexpected(Compose_Task_Graph_Result::component_unavailable);
auto result = graph->compose(std::move(name), *component->value);
if (!result) return std::unexpected(result.error());
return pro::make_proxy<Task_Node, detail::Proxied_Task_Node>(std::move(*result));
}
Precede_Task_Graph_Result Task_Graph_Model::Private::precede(proxy<Task_Node>& before, proxy<Task_Node>& after) {
auto* before_node = before ? proxy_cast<detail::Proxied_Task_Node>(&*before) : nullptr;
auto* after_node = after ? proxy_cast<detail::Proxied_Task_Node>(&*after) : nullptr;
if (before_node == nullptr || after_node == nullptr) return Precede_Task_Graph_Result::node_unavailable;
return before_node->value.precede(after_node->value);
}
void Task_Graph_Model::Private::clear() { graph->clear(); }
bool Task_Graph_Model::Private::empty() const noexcept { return graph->empty(); }
Run_Taskflow_Result Task_Graph_Model::Private::run(Taskflow_Completion completion) { return run_taskflow(*graph, std::move(completion)); }
} // namespace aethera::task_flow
@@ -1,5 +1,5 @@
#include "task_flow/Task_Runtime.hpp"
#include "task_flow/detail/Taskflow_Execution.ipp"
#include "task_flow/src/Task_Runtime.hpp"
#include "../src/detail/Taskflow_Execution.ipp"
#include <gtest/gtest.h>
@@ -0,0 +1,11 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <functional>
namespace aethera {
using Timer_Id = std::uint64_t;
using Timer_Callback = std::function<void()>;
using Timer_Time_Point = std::chrono::steady_clock::time_point;
enum struct Make_Timer_Service_Result : std::uint8_t { time_source_unavailable };
} // namespace aethera
@@ -1,8 +1,7 @@
#include "Timer_Service.hpp"
#include "Timer_Service_Model.hpp"
#include "detail/Timer_Scheduler.hpp"
#include "function/frame_policy/rely_facade.h"
#include "model/Model.hpp"
#include <atomic>
#include <condition_variable>
@@ -22,7 +21,10 @@ struct Steady_Timer_Time_Source : Def<Steady_Timer_Time_Source, Root> {
struct Steady_Timer_Time_Source::Private : Prev_Private {
Timer_Time_Point now() const noexcept { return std::chrono::steady_clock::now(); }
};
} // namespace
namespace time_thread {
namespace detail = aethera::detail;
struct Thread_Timer_Service_State : Immovable {
struct Impl {
static constexpr std::chrono::seconds Maximum_Wait{1};
@@ -189,19 +191,13 @@ struct Thread_Timer_Service_State : Immovable {
std::shared_ptr<Impl> impl; /* proxy 与计时线程共享的运行状态所有权。 */
};
struct Thread_Timer_Service : Def<Thread_Timer_Service, Root> {
struct Private;
explicit Thread_Timer_Service(proxy<Timer_Time_Source> time_source) : Def(std::move(time_source)) {}
};
struct Thread_Timer_Service::Private : Prev_Private {
explicit Private(proxy<Timer_Time_Source> time_source) : state(std::move(time_source)) {}
Timer_Id schedule_after(std::chrono::nanoseconds delay, Timer_Callback callback) { return state.schedule_after(delay, std::move(callback)); }
Timer_Id schedule_every(std::chrono::nanoseconds interval, Timer_Callback callback) { return state.schedule_every(interval, std::move(callback)); }
void reschedule(Timer_Id id, std::chrono::nanoseconds delay) { state.reschedule(id, delay); }
void cancel(Timer_Id id, Timer_Callback completion) { state.cancel(id, std::move(completion)); }
Thread_Timer_Service_State state;
};
}
} // namespace time_thread
Timer_Id time_thread::Timer_Service_Model::Private::schedule_after(std::chrono::nanoseconds delay, Timer_Callback callback) { return state->schedule_after(delay, std::move(callback)); }
Timer_Id time_thread::Timer_Service_Model::Private::schedule_every(std::chrono::nanoseconds interval, Timer_Callback callback) { return state->schedule_every(interval, std::move(callback)); }
void time_thread::Timer_Service_Model::Private::reschedule(Timer_Id id, std::chrono::nanoseconds delay) { state->reschedule(id, delay); }
void time_thread::Timer_Service_Model::Private::cancel(Timer_Id id, Timer_Callback completion) { state->cancel(id, std::move(completion)); }
std::shared_ptr<time_thread::Thread_Timer_Service_State> time_thread::make_timer_service_state(proxy<Timer_Time_Source> time_source) { return std::make_shared<Thread_Timer_Service_State>(std::move(time_source)); }
proxy<Timer_Time_Source> make_steady_timer_time_source() { return make_model_proxy<Timer_Time_Source, Steady_Timer_Time_Source>(); }
@@ -211,9 +207,6 @@ make_timer_service(proxy<Timer_Time_Source> time_source) {
return std::unexpected(
Make_Timer_Service_Result::time_source_unavailable);
}
return make_model_proxy_shared<Timer_Service, Thread_Timer_Service>(std::move(time_source));
}
proxy<frame_policy::Timer_Service> frame_policy::make_timer_service() {
return make_model_proxy_shared<frame_policy::Timer_Service, Thread_Timer_Service>(make_steady_timer_time_source());
return time_thread::make_timer_service_proxy<Timer_Service>(std::move(time_source));
}
}
@@ -1,5 +1,6 @@
#pragma once
#include "../export/export.h"
#include "Timer_Time_Source.hpp"
#include <chrono>
@@ -9,10 +10,6 @@
#include <proxy/proxy.h>
namespace aethera {
using Timer_Id = std::uint64_t;
using Timer_Callback = std::function<void()>;
struct Timer_Service : facade_builder
::add_convention<
_schedule_every,
@@ -29,10 +26,6 @@ struct Timer_Service : facade_builder
::support_relocation<pro::constraint_level::nothrow>
::build {};
enum struct Make_Timer_Service_Result : std::uint8_t {
time_source_unavailable
};
[[nodiscard]] std::expected<proxy<Timer_Service>, Make_Timer_Service_Result>
make_timer_service(proxy<Timer_Time_Source> time_source);
}
@@ -0,0 +1,27 @@
#pragma once
#include "Timer_Service.hpp"
#include "model/Model.hpp"
#include <memory>
#include <utility>
namespace aethera::time_thread {
struct Thread_Timer_Service_State;
struct Timer_Service_Model : Def<Timer_Service_Model, Root> {
struct Private;
explicit Timer_Service_Model(std::shared_ptr<Thread_Timer_Service_State> state) : Def(std::move(state)) {}
};
struct Timer_Service_Model::Private : Prev_Private {
explicit Private(std::shared_ptr<Thread_Timer_Service_State> state) : state(std::move(state)) {}
Timer_Id schedule_after(std::chrono::nanoseconds delay, Timer_Callback callback);
Timer_Id schedule_every(std::chrono::nanoseconds interval, Timer_Callback callback);
void reschedule(Timer_Id id, std::chrono::nanoseconds delay);
void cancel(Timer_Id id, Timer_Callback completion);
private:
std::shared_ptr<Thread_Timer_Service_State> state; /* facade 与计时线程共享同一运行状态所有权。 */
};
std::shared_ptr<Thread_Timer_Service_State> make_timer_service_state(proxy<Timer_Time_Source> time_source);
template <typename Facade> proxy<Facade> make_timer_service_proxy(proxy<Timer_Time_Source> time_source) { return make_model_proxy_shared<Facade, Timer_Service_Model>(make_timer_service_state(std::move(time_source))); }
} // namespace aethera::time_thread
@@ -1,15 +1,12 @@
#pragma once
#include "../export/export.h"
#include "global.hpp"
#include <chrono>
#include <proxy/proxy.h>
namespace aethera {
using Timer_Time_Point = std::chrono::steady_clock::time_point;
struct Timer_Time_Source : facade_builder::add_convention<_now, Timer_Time_Point() const noexcept>::support_relocation<pro::constraint_level::nothrow>::build {};
[[nodiscard]] proxy<Timer_Time_Source> make_steady_timer_time_source();
@@ -1,7 +1,7 @@
#pragma once
#include "time_thread/Timer_Service.hpp"
#include "time_thread/Timer_Time_Source.hpp"
#include "../Timer_Service.hpp"
#include "../Timer_Time_Source.hpp"
#include <chrono>
#include <memory>
@@ -1,6 +1,6 @@
#include "time_thread/Timer_Service.hpp"
#include "time_thread/Timer_Time_Source.hpp"
#include "time_thread/detail/Timer_Scheduler.hpp"
#include "time_thread/src/Timer_Service.hpp"
#include "time_thread/src/Timer_Time_Source.hpp"
#include "../src/detail/Timer_Scheduler.hpp"
#include "model/Model.hpp"
#include <gtest/gtest.h>
+6 -112
View File
@@ -1,22 +1,14 @@
#include "concurrent/base/Concurrent_List.hpp"
#include "../../include/function/renderable/renderable.hpp"
#include "../../include/function/scene/Scene.hpp"
#include "model/Model.hpp"
#include <gtest/gtest.h>
#include <exception>
#include <memory>
#include <optional>
#include <vector>
namespace {
PRO_DEF_MEM_DISPATCH(Test_Model_Private_Read, private_value);
struct Test_Model_Private_Facade : aethera::facade_builder::add_convention<Test_Model_Private_Read, int() const>::support_relocation<pro::constraint_level::nothrow>::build {};
PRO_DEF_MEM_DISPATCH(Other_Test_Model_Private_Read, other_private_value);
struct Other_Test_Model_Private_Facade : aethera::facade_builder::add_convention<Other_Test_Model_Private_Read, int() const>::support_relocation<pro::constraint_level::nothrow>::build {};
struct Test_Model_Prop {
int base_number{};
int number{};
int other{};
};
struct Test_Model_Prop { int base_number{}; int number{}; int other{}; };
struct Test_Model_Numbers : std::vector<int> {};
struct Test_Model_Base : aethera::Model_Layer<aethera::Root, aethera::Storage_Registration<Test_Model_Prop, aethera::Import_Struct_With_Dirty>, aethera::Storage_Registration<Test_Model_Numbers, aethera::Import_List>> {
using Prop = Test_Model_Prop;
@@ -25,30 +17,11 @@ struct Test_Model_Base : aethera::Model_Layer<aethera::Root, aethera::Storage_Re
struct Builder : Prev_Builder {};
};
struct Test_Model : aethera::Def<Test_Model, Test_Model_Base> {
struct Private : Prev_Private {
int private_value() const { return 41; }
int other_private_value() const { return 43; }
};
struct Private : Prev_Private { int private_value() const { return 41; } int other_private_value() const { return 43; } };
};
struct Test_Renderable : aethera::Def<Test_Renderable, aethera::Renderable<Test_Renderable>> {
struct Private : Prev_Private {
void advance_renderable() { ++advance_count; auto& graph = internal_taskflow(); graph->clear(); (void)graph->add("test.prepare", [] {}); }
int advance_count{};
};
};
struct Test_Dag_Renderable : aethera::Def<Test_Dag_Renderable, aethera::Renderable<Test_Dag_Renderable>> {
Test_Dag_Renderable(std::shared_ptr<std::vector<int>> advance_order, int id) : Def(std::move(advance_order), id) {}
struct Private : Prev_Private {
Private(std::shared_ptr<std::vector<int>> advance_order, int id) : advance_order(std::move(advance_order)), id(id) {}
void advance_renderable() { advance_order->push_back(id); auto& graph = internal_taskflow(); graph->clear(); (void)graph->add("prepare", [] {}); }
std::shared_ptr<std::vector<int>> advance_order;
int id{};
};
};
static_assert(pro::proxiable<aethera::detail::Model_Private_Pointer<Test_Renderable*>, aethera::scene::Renderable>);
static_assert(pro::proxiable<aethera::detail::Model_Private_Pointer<aethera::Scene*>, aethera::frame_policy::Scene>);
static_assert(pro::proxiable<aethera::detail::Model_Private_Pointer<Test_Model*>, Test_Model_Private_Facade>);
static_assert(pro::proxiable<aethera::detail::Model_Private_Pointer<Test_Model*>, Other_Test_Model_Private_Facade>);
TEST(Model, Registered_Value_Types_Build_Independent_Storages) {
Test_Model::Builder builder;
int struct_initializer_calls{};
@@ -68,6 +41,7 @@ TEST(Model, Registered_Value_Types_Build_Independent_Storages) {
numbers.internal.advance();
EXPECT_EQ(*numbers.internal.use(), (std::vector<int>{13, 17}));
}
TEST(Model, Registered_Struct_Write_Publishes_To_The_Same_Tagged_Storage) {
auto model = Test_Model::Builder{}.build();
model->write<aethera::Import_Struct_With_Dirty, Test_Model_Prop>(&Test_Model_Prop::number, 13);
@@ -75,91 +49,11 @@ TEST(Model, Registered_Struct_Write_Publishes_To_The_Same_Tagged_Storage) {
properties.internal.advance();
EXPECT_EQ(properties.internal.use()->number, 13);
}
TEST(Model, Consumer_Facade_Selects_One_Model_Private_View) {
auto model = aethera::make_model_proxy<Test_Model_Private_Facade, Test_Model>();
EXPECT_EQ(model->private_value(), 41);
auto other_model = aethera::make_model_proxy<Other_Test_Model_Private_Facade, Test_Model>();
EXPECT_EQ(other_model->other_private_value(), 43);
}
TEST(Model, Renderable_Facade_Uses_Private_Task_Graph_Capabilities) {
auto renderable = aethera::make_model_proxy<aethera::scene::Renderable, Test_Renderable>();
renderable->advance();
auto graph = renderable->taskflow();
EXPECT_TRUE(graph);
}
TEST(Model, Scene_Advances_Renderables_And_Composes_Task_Graph_In_Dag_Order) {
auto advance_order = std::make_shared<std::vector<int>>();
aethera::Dag<aethera::scene::Renderable>::Builder builder;
builder.add({2}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 2));
builder.add({1}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 1));
builder.add({3}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 3));
builder.add_dependency({2}, {1});
builder.add_dependency({3}, {1});
auto graph = builder.build();
ASSERT_TRUE(graph.has_value());
aethera::Scene scene;
EXPECT_TRUE(scene.d->create_frame());
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
EXPECT_TRUE(scene.d->advance().has_value());
EXPECT_EQ(*advance_order, (std::vector<int>{1, 2, 3}));
EXPECT_FALSE(scene.d->taskflow()->empty());
advance_order->clear();
std::optional<aethera::Edit_Dag_Result> edit_error;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>([](aethera::Dag<aethera::scene::Renderable>::Editor& editor) { editor.remove({99}); }, [&](std::expected<void, aethera::Edit_Dag_Result> result) { if (!result) edit_error = result.error(); });
EXPECT_TRUE(scene.d->advance().has_value());
ASSERT_TRUE(edit_error.has_value());
EXPECT_EQ(*edit_error, aethera::Edit_Dag_Result::node_not_found);
EXPECT_EQ(*advance_order, (std::vector<int>{1, 2, 3}));
}
TEST(Model, Dag_Rejects_Unallocated_Id_And_Missing_Object) {
aethera::Dag<aethera::scene::Renderable>::Builder invalid_id_builder;
invalid_id_builder.add({0}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Renderable>());
const auto invalid_id = invalid_id_builder.build();
ASSERT_FALSE(invalid_id.has_value());
EXPECT_EQ(invalid_id.error(), aethera::Build_Dag_Result::invalid_node_id);
aethera::Dag<aethera::scene::Renderable>::Builder missing_object_builder;
missing_object_builder.add({1}, {});
const auto missing_object = missing_object_builder.build();
ASSERT_FALSE(missing_object.has_value());
EXPECT_EQ(missing_object.error(), aethera::Build_Dag_Result::node_object_missing);
}
TEST(Model, Owned_Dag_Rolls_Back_The_Whole_Invalid_Edit) {
auto advance_order = std::make_shared<std::vector<int>>();
aethera::Dag<aethera::scene::Renderable>::Builder builder;
builder.add({1}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 1));
auto graph = builder.build();
ASSERT_TRUE(graph.has_value());
aethera::Scene scene;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
ASSERT_TRUE(scene.d->advance().has_value());
advance_order->clear();
std::optional<aethera::Edit_Dag_Result> edit_error;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>([advance_order](aethera::Dag<aethera::scene::Renderable>::Editor& editor) { editor.add({2}, aethera::make_model_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 2)); editor.add_dependency({2}, {99}); }, [&](std::expected<void, aethera::Edit_Dag_Result> result) { if (!result) edit_error = result.error(); });
ASSERT_TRUE(scene.d->advance().has_value());
ASSERT_TRUE(edit_error.has_value());
EXPECT_EQ(*edit_error, aethera::Edit_Dag_Result::node_not_found);
EXPECT_EQ(*advance_order, (std::vector<int>{1}));
}
TEST(Model, Scene_Render_Returns_Known_Submission_Result) {
aethera::Scene scene;
auto frame = scene.d->create_frame();
bool completed{};
EXPECT_EQ(scene.d->render(frame, [&](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) { completed = true; }), aethera::frame_policy::Render_Scene_Result::runtime_not_initialized);
EXPECT_FALSE(completed);
aethera::proxy<aethera::frame_policy::Frame> empty_frame;
EXPECT_EQ(scene.d->render(empty_frame, [&](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) {}), aethera::frame_policy::Render_Scene_Result::frame_missing);
EXPECT_EQ(scene.d->render(frame, {}), aethera::frame_policy::Render_Scene_Result::completion_missing);
}
TEST(Model, Scene_Rejects_The_Same_Renderable_Task_Graph_Twice) {
Test_Renderable renderable;
aethera::Dag<aethera::scene::Renderable>::Builder builder;
builder.add({1}, aethera::model_proxy_view<aethera::scene::Renderable>(renderable));
builder.add({2}, aethera::model_proxy_view<aethera::scene::Renderable>(renderable));
auto graph = builder.build();
ASSERT_TRUE(graph.has_value());
aethera::Scene scene;
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
auto frame = scene.d->create_frame();
EXPECT_EQ(scene.d->render(frame, [](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) {}), aethera::frame_policy::Render_Scene_Result::task_graph_invalid);
}
} // namespace
+17 -12
View File
@@ -25,27 +25,35 @@ if (Aethera_BUILD_TESTS)
find_package(benchmark CONFIG REQUIRED)
endif ()
set(Aethera_Kernel_source_dir "${CMAKE_CURRENT_LIST_DIR}/kernel/include")
set(Aethera_Kernel_module_dir "${CMAKE_CURRENT_LIST_DIR}/kernel/module")
set(Aethera_concurrentqueue_dir "${CMAKE_CURRENT_LIST_DIR}/third_party/concurrentqueue-1.0.5")
add_subdirectory("${Aethera_concurrentqueue_dir}"
"${CMAKE_CURRENT_BINARY_DIR}/aethera_concurrentqueue" EXCLUDE_FROM_ALL)
append_glob_source(Aethera_Kernel_sources "${Aethera_Kernel_source_dir}")
add_library(Aethera_Kernel STATIC ${Aethera_Kernel_sources})
target_include_directories(Aethera_Kernel PUBLIC
append_glob_source(Aethera_Kernel_Core_sources "${Aethera_Kernel_source_dir}")
add_library(Aethera_Kernel_Core STATIC ${Aethera_Kernel_Core_sources})
target_include_directories(Aethera_Kernel_Core PUBLIC
"$<BUILD_INTERFACE:${Aethera_Kernel_source_dir}>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/third_party>"
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/third_party/ratas-master/src>"
)
target_compile_features(Aethera_Kernel PUBLIC cxx_std_20)
target_link_libraries(Aethera_Kernel PUBLIC
Taskflow::Taskflow
target_compile_features(Aethera_Kernel_Core PUBLIC cxx_std_20)
target_link_libraries(Aethera_Kernel_Core PUBLIC
magic_enum::magic_enum
msft_proxy4::proxy
"$<BUILD_INTERFACE:concurrentqueue>"
)
target_link_libraries(Aethera_Kernel PRIVATE Threads::Threads)
if (MSVC)
target_compile_options(Aethera_Kernel PUBLIC /utf-8)
target_compile_options(Aethera_Kernel_Core PUBLIC /utf-8)
endif ()
include("${Aethera_Kernel_module_dir}/main.cmake")
add_library(Aethera_Kernel INTERFACE)
target_link_libraries(Aethera_Kernel INTERFACE
Aethera_Kernel_Core
Aethera_Kernel_Task_Flow
Aethera_Kernel_Time_Thread
Aethera_Kernel_Renderable
Aethera_Kernel_Scene
Aethera_Kernel_Frame_Policy)
if (Aethera_BUILD_TESTS)
set(Aethera_Kernel_test_dir "${CMAKE_CURRENT_LIST_DIR}/kernel/test")
set(Aethera_Kernel_cdb
@@ -57,10 +65,7 @@ if (Aethera_BUILD_TESTS)
append_glob_source(Aethera_Kernel_module_test_sources
"${Aethera_Kernel_test_dir}/concurrent"
"${Aethera_Kernel_test_dir}/model"
"${Aethera_Kernel_test_dir}/task_flow"
"${Aethera_Kernel_test_dir}/time_thread"
"${Aethera_Kernel_test_dir}/function/frame_policy")
"${Aethera_Kernel_test_dir}/model")
foreach (Aethera_Kernel_module_test_source IN LISTS Aethera_Kernel_module_test_sources)
if (NOT Aethera_Kernel_module_test_source MATCHES "\\.(c|cc|cpp|cxx)$")
continue()