This commit is contained in:
2026-09-02 11:09:06 +08:00
parent cc680e5481
commit e7fdbb93c1
31 changed files with 371 additions and 396 deletions
@@ -19,10 +19,10 @@ enum struct Frame_Policy_Render_Submission_Result : std::uint8_t {
};
struct Frame_Policy_Prop {
std::optional<double> user_frames_per_second; /* 有值时作为用户帧率上限;无值时不参与限速。 */
bool render_rate_limit_enabled{}; /* true 时用渲染平均耗时推导理论生成上限。 */
bool send_rate_limit_enabled{}; /* true 时用发送平均耗时推导理论发送上限。 */
bool statistics_enabled{}; /* true 时对外发布完整滑动统计。 */
std::size_t statistics_window_size{120}; /* 统计或理论限速启用时的样本窗口容量。 */
bool render_rate_limit_enabled{}; /* true 时用渲染平均耗时推导理论生成上限。 */
bool send_rate_limit_enabled{}; /* true 时用发送平均耗时推导理论发送上限。 */
bool statistics_enabled{}; /* true 时对外发布完整滑动统计。 */
std::size_t statistics_window_size{120}; /* 统计或理论限速启用时的样本窗口容量。 */
};
struct Frame_Policy_State;
struct Frame_Policy : Model_Layer<Root, Storage_Registration<Frame_Policy_Prop, Import_Struct_With_Dirty>, Storage_Registration<Frame_Policy_State, Export_Struct>> {
@@ -33,16 +33,16 @@ struct Frame_Policy : Model_Layer<Root, Storage_Registration<Frame_Policy_Prop,
struct Private;
};
struct Frame_Policy_State {
std::uint64_t timer_ticks{}; /* 已处理的调度到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 到期时三帧均忙而丢弃的次数。 */
std::uint64_t completed_frames{}; /* 已完成媒体发送的帧数。 */
std::optional<double> render_capacity_fps; /* 有渲染样本时推导出的理论最大生成帧率。 */
std::optional<double> send_capacity_fps; /* 有发送样本时推导出的理论最大发送帧率。 */
std::uint64_t timer_ticks{}; /* 已处理的调度到期次数。 */
std::uint64_t dropped_timer_ticks{}; /* 到期时三帧均忙而丢弃的次数。 */
std::uint64_t completed_frames{}; /* 已完成媒体发送的帧数。 */
std::optional<double> render_capacity_fps; /* 有渲染样本时推导出的理论最大生成帧率。 */
std::optional<double> send_capacity_fps; /* 有发送样本时推导出的理论最大发送帧率。 */
double effective_frames_per_second{Frame_Policy::Default_Frames_Per_Second}; /* 当前有效限速来源的最小值。 */
Statistics_Summary render_time_ns; /* 渲染耗时滑动统计,单位纳秒。 */
Statistics_Summary send_time_ns; /* 发送耗时滑动统计,单位纳秒。 */
Statistics_Summary end_to_end_time_ns; /* 渲染开始至发送完成耗时统计,单位纳秒。 */
std::optional<Frame_Policy_Configuration_Result> configuration_error; /* 动态配置无效时记录原因;有效时为空。 */
Statistics_Summary render_time_ns; /* 渲染耗时滑动统计,单位纳秒。 */
Statistics_Summary send_time_ns; /* 发送耗时滑动统计,单位纳秒。 */
Statistics_Summary end_to_end_time_ns; /* 渲染开始至发送完成耗时统计,单位纳秒。 */
std::optional<Frame_Policy_Configuration_Result> configuration_error; /* 动态配置无效时记录原因;有效时为空。 */
std::optional<Frame_Policy_Render_Submission_Result> render_submission_error; /* Scene 拒绝最近一次渲染提交时的策略级原因;下一次提交成功后清空。 */
};
} // namespace aethera
@@ -18,7 +18,7 @@ auto start_result(Frame_Policy::Configuration_Result result) noexcept -> Throttl
std::terminate();
}
} // namespace
Throttled_Latest_only::Throttled_Latest_only(proxy<Timer_Service> timer_service, proxy<FP_Scene> scene, proxy<FP_Sink> sink) : Def(std::move(timer_service), std::move(scene), std::move(sink)) {}
Throttled_Latest_only::Throttled_Latest_only(proxy<frame_policy::Timer_Service> timer_service, proxy<frame_policy::Scene> scene, proxy<frame_policy::Sink> sink) : Def(std::move(timer_service), std::move(scene), std::move(sink)) {}
Throttled_Latest_only::~Throttled_Latest_only() noexcept {
if (!d->destructible()) {
std::terminate();
@@ -30,13 +30,13 @@ auto Throttled_Latest_only::start() -> std::expected<void, Start_Result> {
auto Throttled_Latest_only::stop(Stop_Completion completion) -> std::expected<void, Stop_Result> {
return d->stop(std::move(completion));
}
Throttled_Latest_only::Private::Private(proxy<Timer_Service> timer_service, proxy<FP_Scene> scene, proxy<FP_Sink> sink) : timer_service(std::move(timer_service)), scene(std::move(scene)), sink(std::move(sink)) {}
Throttled_Latest_only::Private::Private(proxy<frame_policy::Timer_Service> timer_service, proxy<frame_policy::Scene> scene, proxy<frame_policy::Sink> sink) : timer_service(std::move(timer_service)), scene(std::move(scene)), sink(std::move(sink)) {}
auto Throttled_Latest_only::Private::start() -> std::expected<void, Start_Result> {
const auto interval = prepare_start();
if (!interval) {
return std::unexpected(interval.error());
}
Timer_Id scheduled_timer_id{};
frame_policy::Timer_Id scheduled_timer_id{};
try {
scheduled_timer_id = timer_service->schedule_every(*interval, [this] {
frame_due();
@@ -63,11 +63,10 @@ auto Throttled_Latest_only::Private::prepare_start() -> std::expected<std::chron
}
phase = Phase::starting;
}
std::array<proxy<FP_Frame>, Frame_Count> created_frames;
std::array<proxy<frame_policy::Frame>, Frame_Count> created_frames;
try {
auto scene_private = scene->d_ptr();
for (auto& created_frame : created_frames) {
created_frame = scene_private->create_frame();
created_frame = scene->create_frame();
if (!created_frame) {
rollback_start();
return std::unexpected(Start_Result::frame_unavailable);
@@ -102,7 +101,7 @@ auto Throttled_Latest_only::Private::prepare_start() -> std::expected<std::chron
}
}
void Throttled_Latest_only::Private::rollback_start() {
std::array<proxy<FP_Frame>, Frame_Count> retired_frames;
std::array<proxy<frame_policy::Frame>, Frame_Count> retired_frames;
{
std::lock_guard lock(mutex);
if (phase != Phase::starting || timer_id) {
@@ -115,7 +114,7 @@ void Throttled_Latest_only::Private::rollback_start() {
phase = Phase::stopped;
}
}
void Throttled_Latest_only::Private::timer_started(Timer_Id id) {
void Throttled_Latest_only::Private::timer_started(frame_policy::Timer_Id id) {
std::lock_guard lock(mutex);
if (id == 0 || phase != Phase::starting || timer_id) {
std::terminate();
@@ -127,7 +126,7 @@ auto Throttled_Latest_only::Private::stop(Stop_Completion completion) -> std::ex
if (!completion) {
return std::unexpected(Stop_Result::completion_missing);
}
Timer_Id cancelled_timer_id{};
frame_policy::Timer_Id cancelled_timer_id{};
{
std::lock_guard lock(mutex);
switch (phase) {
@@ -179,7 +178,7 @@ void Throttled_Latest_only::Private::timer_cancelled() {
}
void Throttled_Latest_only::Private::frame_due() {
std::size_t selected_index = Frame_Count;
Timer_Id scheduled_timer_id{};
frame_policy::Timer_Id scheduled_timer_id{};
std::optional<std::chrono::nanoseconds> interval;
{
std::lock_guard lock(mutex);
@@ -194,7 +193,9 @@ void Throttled_Latest_only::Private::frame_due() {
if (refreshed_interval) {
interval = *refreshed_interval;
scheduled_timer_id = *timer_id;
const bool rendering = std::ranges::any_of(frames, [](const Frame_Slot& slot) { return slot.phase == Frame_Phase::rendering; });
const bool rendering = std::ranges::any_of(frames, [](const Frame_Slot& slot) {
return slot.phase == Frame_Phase::rendering;
});
if (!rendering) {
for (std::size_t index = 0; index < Frame_Count; ++index) {
if (frames[index].phase == Frame_Phase::idle) {
@@ -219,8 +220,7 @@ void Throttled_Latest_only::Private::frame_due() {
return;
}
try {
auto scene_private = scene->d_ptr();
const auto result = scene_private->render(frames[selected_index].frame, [this, selected_index](proxy<FP_Frame>& completed_frame, std::exception_ptr failure) {
const auto result = scene->render(frames[selected_index].frame, [this, selected_index](proxy<frame_policy::Frame>& completed_frame, std::exception_ptr failure) {
if (failure) {
abandon_render(selected_index);
std::rethrow_exception(failure);
@@ -228,26 +228,29 @@ void Throttled_Latest_only::Private::frame_due() {
rendered(selected_index, completed_frame);
});
record_render_submission(result);
if (result != Render_Scene_Result::submitted) abandon_render(selected_index);
if (result != frame_policy::Render_Scene_Result::submitted) abandon_render(selected_index);
}
catch (...) {
abandon_render(selected_index);
throw;
}
}
void Throttled_Latest_only::Private::record_render_submission(Render_Scene_Result result) {
void Throttled_Latest_only::Private::record_render_submission(frame_policy::Render_Scene_Result result) {
std::lock_guard lock(mutex);
auto& state = *get<Frame_Policy_State>().internal.use();
switch (result) {
case Render_Scene_Result::submitted: state.render_submission_error.reset(); break;
case Render_Scene_Result::runtime_not_initialized: state.render_submission_error = Frame_Policy_Render_Submission_Result::runtime_not_initialized; break;
case Render_Scene_Result::task_graph_invalid: state.render_submission_error = Frame_Policy_Render_Submission_Result::task_graph_invalid; break;
case Render_Scene_Result::frame_missing:
case Render_Scene_Result::completion_missing: std::terminate();
case frame_policy::Render_Scene_Result::submitted: state.render_submission_error.reset();
break;
case frame_policy::Render_Scene_Result::runtime_not_initialized: state.render_submission_error = Frame_Policy_Render_Submission_Result::runtime_not_initialized;
break;
case frame_policy::Render_Scene_Result::task_graph_invalid: state.render_submission_error = Frame_Policy_Render_Submission_Result::task_graph_invalid;
break;
case frame_policy::Render_Scene_Result::frame_missing:
case frame_policy::Render_Scene_Result::completion_missing: std::terminate();
}
publish_state();
}
void Throttled_Latest_only::Private::rendered(std::size_t slot_index, proxy<FP_Frame>& completed_frame) {
void Throttled_Latest_only::Private::rendered(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame) {
bool dispatch = false;
{
std::lock_guard lock(mutex);
@@ -295,8 +298,7 @@ void Throttled_Latest_only::Private::rendered(std::size_t slot_index, proxy<FP_F
}
void Throttled_Latest_only::Private::dispatch_send(std::size_t slot_index) {
try {
auto sink_private = sink->d_ptr();
sink_private->send(frames[slot_index].frame, [this, slot_index](proxy<FP_Frame>& completed_frame) {
sink->send(frames[slot_index].frame, [this, slot_index](proxy<frame_policy::Frame>& completed_frame) {
sent(slot_index, completed_frame);
});
}
@@ -305,7 +307,7 @@ void Throttled_Latest_only::Private::dispatch_send(std::size_t slot_index) {
throw;
}
}
void Throttled_Latest_only::Private::sent(std::size_t slot_index, proxy<FP_Frame>& completed_frame) {
void Throttled_Latest_only::Private::sent(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame) {
std::size_t next_send_index = Frame_Count;
{
std::lock_guard lock(mutex);
@@ -359,7 +361,7 @@ void Throttled_Latest_only::Private::abandon_send(std::size_t slot_index) {
finish_stop_if_ready();
}
void Throttled_Latest_only::Private::finish_stop_if_ready() {
std::array<proxy<FP_Frame>, Frame_Count> retired_frames;
std::array<proxy<frame_policy::Frame>, Frame_Count> retired_frames;
Stop_Completion completion;
{
std::lock_guard lock(mutex);
@@ -1,7 +1,6 @@
#pragma once
#include "function/frame_policy/Frame_Policy.hpp"
#include "function/frame_policy/global.hpp"
#include "time_thread/Timer_Service.hpp"
#include "rely_facade.h"
#include <cstdint>
#include <expected>
#include <functional>
@@ -29,7 +28,7 @@ struct Throttled_Latest_only : Def<Throttled_Latest_only, Frame_Policy> {
};
using Stop_Completion = std::function<void()>;
struct Private;
Throttled_Latest_only(proxy<Timer_Service> timer_service, proxy<FP_Scene> scene, proxy<FP_Sink> sink);
Throttled_Latest_only(proxy<frame_policy::Timer_Service> timer_service, proxy<frame_policy::Scene> scene, proxy<frame_policy::Sink> sink);
~Throttled_Latest_only() noexcept;
auto start() -> std::expected<void, Start_Result>;
auto stop(Stop_Completion completion) -> std::expected<void, Stop_Result>;
@@ -18,39 +18,38 @@ struct Throttled_Latest_only::Private : Prev_Private {
sending
};
struct Frame_Slot {
proxy<FP_Frame> frame; /* 帧池在本槽唯一拥有并循环复用的物理帧。 */
Frame_Phase phase{Frame_Phase::idle}; /* 本槽当前用途的唯一权威状态。 */
proxy<frame_policy::Frame> frame; /* 帧池在本槽唯一拥有并循环复用的物理帧。 */
Frame_Phase phase{Frame_Phase::idle}; /* 本槽当前用途的唯一权威状态。 */
std::chrono::steady_clock::time_point render_start; /* 当前帧开始渲染的单调时刻。 */
std::chrono::steady_clock::time_point send_start; /* 当前帧开始发送的单调时刻。 */
std::chrono::steady_clock::time_point send_start; /* 当前帧开始发送的单调时刻。 */
};
static constexpr std::size_t Frame_Count{3};
Private(proxy<Timer_Service> timer_service, proxy<FP_Scene> scene, proxy<FP_Sink> sink);
Private(proxy<frame_policy::Timer_Service> timer_service, proxy<frame_policy::Scene> scene, proxy<frame_policy::Sink> sink);
auto start() -> std::expected<void, Start_Result>;
auto stop(Stop_Completion completion) -> std::expected<void, Stop_Result>;
[[nodiscard]] bool destructible() const noexcept;
private:
auto prepare_start() -> std::expected<std::chrono::nanoseconds, Start_Result>;
void rollback_start();
void timer_started(Timer_Id id);
void timer_started(frame_policy::Timer_Id id);
void cancel_failed();
void timer_cancelled();
void frame_due();
void record_render_submission(Render_Scene_Result result);
void rendered(std::size_t slot_index, proxy<FP_Frame>& completed_frame);
void record_render_submission(frame_policy::Render_Scene_Result result);
void rendered(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame);
void dispatch_send(std::size_t slot_index);
void sent(std::size_t slot_index, proxy<FP_Frame>& completed_frame);
void sent(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame);
void abandon_render(std::size_t slot_index);
void abandon_send(std::size_t slot_index);
void finish_stop_if_ready();
void publish_state();
proxy<Timer_Service> timer_service; /* 动态重设计时期限的必填业务能力所有权。 */
proxy<FP_Scene> scene; /* 创建三帧并执行渲染的必填业务能力所有权。 */
proxy<FP_Sink> sink; /* 串行发送最新已渲染帧的必填业务能力所有权。 */
proxy<frame_policy::Timer_Service> timer_service; /* 动态重设计时期限的必填业务能力所有权。 */
proxy<frame_policy::Scene> scene; /* 创建三帧并执行渲染的必填业务能力所有权。 */
proxy<frame_policy::Sink> sink; /* 串行发送最新已渲染帧的必填业务能力所有权。 */
std::array<Frame_Slot, Frame_Count> frames; /* 固定三个槽位的轮转帧池。 */
Stop_Completion stop_completion; /* 本次异步停止的唯一完成回调。 */
std::optional<Timer_Id> timer_id; /* running/stopping 阶段的周期计时器标识。 */
mutable std::mutex mutex; /* 保护生命周期、帧槽、Prop 推进、统计和 State 发布。 */
Phase phase{Phase::stopped}; /* start/stop 生命周期的唯一权威状态。 */
Stop_Completion stop_completion; /* 本次异步停止的唯一完成回调。 */
std::optional<frame_policy::Timer_Id> timer_id; /* running/stopping 阶段的周期计时器标识。 */
mutable std::mutex mutex; /* 保护生命周期、帧槽、Prop 推进、统计和 State 发布。 */
Phase phase{Phase::stopped}; /* start/stop 生命周期的唯一权威状态。 */
};
} // namespace aethera
@@ -1,4 +0,0 @@
#include "global.ipp"
#include <exception>
#include <utility>
namespace aethera {}
@@ -1,27 +0,0 @@
#pragma once
#include "model/Model.hpp"
#include <cstdint>
#include <exception>
#include <functional>
#include <proxy/proxy.h>
namespace aethera {
struct FP_Frame : facade_builder::build {};
using FP_Frame_Completion = std::function<void(proxy<FP_Frame>&)>;
using FP_Render_Completion = std::function<void(proxy<FP_Frame>&, std::exception_ptr)>;
enum struct Render_Scene_Result : std::uint8_t {
submitted,
frame_missing,
completion_missing,
runtime_not_initialized,
task_graph_invalid
};
struct FP_Scene_Private : facade_builder
::add_convention<_create_frame, proxy<FP_Frame>()>
::add_convention<_render, Render_Scene_Result(proxy<FP_Frame>&, FP_Render_Completion)>
::build {};
struct FP_Scene : facade_builder::add_convention<_d_ptr, proxy<FP_Scene_Private>()>::build {};
struct FP_Sink_Private : facade_builder
::add_convention<_send, void(proxy<FP_Frame>&, FP_Frame_Completion)>
::build {};
struct FP_Sink : facade_builder::add_convention<_d_ptr, proxy<FP_Sink_Private>()>::build {};
} // namespace aethera
@@ -1,3 +0,0 @@
#pragma once
#include "global.hpp"
@@ -0,0 +1,20 @@
#pragma once
#include "global.hpp"
#include <chrono>
#include <cstdint>
#include <exception>
#include <functional>
#include <proxy/proxy.h>
namespace aethera::frame_policy {
using Timer_Id = std::uint64_t;
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 {};
proxy<Timer_Service> make_timer_service();
} // namespace aethera::frame_policy
@@ -0,0 +1,14 @@
#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,2 +0,0 @@
#pragma once
#include "../../task_flow/task_flow_facade.hpp"
@@ -1,12 +1,10 @@
#pragma once
#include "model/Model.hpp"
#include "task_flow/task_flow_facade.hpp"
#include "rely_facade.h"
namespace aethera {
template <typename Self> struct Renderable : Model_Layer<Root> {
using Renderable_Type = Self;
struct Private;
void advance();
proxy<Task_Graph_Facade>& taskflow();
};
} // namespace aethera
#include "renderable.ipp"
@@ -1,23 +1,15 @@
#pragma once
namespace aethera {
template <typename Self> struct Renderable<Self>::Private : Prev_Private {
void advance(Self& object);
proxy<Task_Graph_Facade>& taskflow();
void advance();
proxy<scene::Task_Graph> taskflow();
proxy<renderable::Task_Graph>& internal_taskflow() noexcept { return graph; }
private:
proxy<Task_Graph_Facade> graph{make_task_graph("renderable")}; /* 唯一拥有本 Renderable 的任务图代理。 */
proxy<renderable::Task_Graph> graph{renderable::make_task_graph("renderable")}; /* 唯一拥有本 Renderable 的任务图代理。 */
};
template <typename Self> void Renderable<Self>::Private::advance(Self& object) {
template <typename Self> void Renderable<Self>::Private::advance() {
auto& private_data = static_cast<typename Self::Private&>(*this);
if constexpr (requires { private_data.advance_renderable(object); }) private_data.advance_renderable(object);
else if constexpr (requires { private_data.advance_renderable(); }) private_data.advance_renderable();
}
template <typename Self> proxy<Task_Graph_Facade>& Renderable<Self>::Private::taskflow() { return graph; }
template <typename Self> void Renderable<Self>::advance() {
auto& object = static_cast<Self&>(*this);
object.d->advance(object);
}
template <typename Self> proxy<Task_Graph_Facade>& Renderable<Self>::taskflow() {
auto& object = static_cast<Self&>(*this);
return object.d->taskflow();
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(); }
} // namespace aethera
+39 -16
View File
@@ -7,41 +7,64 @@ namespace detail {
struct Scene_Frame {};
}
Scene::Private::Private() = default;
proxy<FP_Frame> Scene::Private::create_frame() {
return pro::make_proxy<FP_Frame, detail::Scene_Frame>();
proxy<frame_policy::Frame> Scene::Private::create_frame() {
return pro::make_proxy<frame_policy::Frame, detail::Scene_Frame>();
}
proxy<Task_Graph_Facade>& Scene::Private::taskflow() { return graph; }
auto Scene::Private::create_taskflow() -> std::expected<proxy<Task_Graph_Facade>, Create_Taskflow_Result> {
auto result = make_task_graph("scene.renderables");
std::unordered_map<std::uint64_t, proxy<Task_Node_Facade>> nodes;
proxy<scene::Task_Graph>& Scene::Private::taskflow() {
return graph;
}
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;
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 component = renderable->taskflow()->component(); auto composed = result->compose("renderable." + std::to_string(id.value), component); if (!composed) { switch (composed.error()) { case Compose_Task_Graph_Result::component_unavailable: error = Create_Taskflow_Result::component_unavailable; break; case Compose_Task_Graph_Result::dependency_cycle: error = Create_Taskflow_Result::dependency_cycle; break; case Compose_Task_Graph_Result::already_composed: error = Create_Taskflow_Result::already_composed; break; } return; } nodes.emplace(id.value, std::move(*composed)); });
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
if (error) return;
auto taskflow = renderable->taskflow();
auto component = taskflow->component();
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;
break;
case scene::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;
break;
}
return;
}
nodes.emplace(id.value, std::move(*composed));
});
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)) != Precede_Task_Graph_Result::preceded) error = Create_Taskflow_Result::dependency_unavailable; });
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 (error) return std::unexpected(*error);
return result;
}
auto Scene::Private::advance() -> std::expected<void, Create_Taskflow_Result> {
auto& renderables = get<Scene_Renderable_Dag>();
renderables.internal.advance();
renderables.internal.use().for_each_topological([](Dag_Node_Id, proxy<scene::Renderable>& renderable) { renderable->advance(); });
renderables.internal.use().for_each_topological([](Dag_Node_Id, proxy<scene::Renderable>& renderable) {
renderable->advance();
});
auto next_graph = create_taskflow();
if (!next_graph) return std::unexpected(next_graph.error());
graph = std::move(*next_graph);
return {};
}
Render_Scene_Result Scene::Private::render(proxy<FP_Frame>& frame, FP_Render_Completion completion) {
if (!frame) return Render_Scene_Result::frame_missing;
if (!completion) return Render_Scene_Result::completion_missing;
if (!advance()) return Render_Scene_Result::task_graph_invalid;
frame_policy::Render_Scene_Result Scene::Private::render(proxy<frame_policy::Frame>& frame, frame_policy::Render_Completion completion) {
if (!frame) return frame_policy::Render_Scene_Result::frame_missing;
if (!completion) return frame_policy::Render_Scene_Result::completion_missing;
if (!advance()) return frame_policy::Render_Scene_Result::task_graph_invalid;
const auto result = graph->run([frame = std::ref(frame), completion = std::move(completion)](std::exception_ptr failure) mutable {
completion(frame.get(), std::move(failure));
});
switch (result) {
case Run_Task_Graph_Result::submitted: return Render_Scene_Result::submitted;
case Run_Task_Graph_Result::runtime_not_initialized: return Render_Scene_Result::runtime_not_initialized;
case Run_Task_Graph_Result::completion_missing: return Render_Scene_Result::completion_missing;
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;
}
std::terminate();
}
@@ -1,5 +1,5 @@
#pragma once
#include "../frame_policy/global.hpp"
#include "../frame_policy/rely_facade.h"
#include "model/Model.hpp"
#include "relation/Dag.hpp"
#include <functional>
@@ -3,12 +3,12 @@ namespace aethera {
struct Scene::Private : Prev_Private {
enum struct Create_Taskflow_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed, dependency_unavailable };
Private();
proxy<FP_Frame> create_frame();
Render_Scene_Result render(proxy<FP_Frame>& frame, FP_Render_Completion completion);
proxy<Task_Graph_Facade>& taskflow();
auto create_taskflow() -> std::expected<proxy<Task_Graph_Facade>, Create_Taskflow_Result>;
proxy<frame_policy::Frame> create_frame();
frame_policy::Render_Scene_Result render(proxy<frame_policy::Frame>& frame, frame_policy::Render_Completion completion);
proxy<scene::Task_Graph>& taskflow();
auto create_taskflow() -> std::expected<proxy<scene::Task_Graph>, Create_Taskflow_Result>;
auto advance() -> std::expected<void, Create_Taskflow_Result>;
private:
proxy<Task_Graph_Facade> graph{make_task_graph("scene")}; /* 唯一拥有当前组合任务图的跨模块代理。 */
proxy<scene::Task_Graph> graph{scene::make_task_graph("scene")}; /* 唯一拥有当前组合任务图的跨模块代理。 */
};
} // namespace aethera
@@ -1,9 +1,19 @@
#pragma once
#include "global.hpp"
#include "../../task_flow/task_flow_facade.hpp"
#include <cstdint>
#include <exception>
#include <expected>
#include <functional>
#include <proxy/proxy.h>
#include <string>
namespace aethera::scene {
struct Renderable : facade_builder
::add_convention<_advance, void()>
::add_convention<_taskflow, proxy<Task_Graph_Facade>&()>
::build {};
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
View File
@@ -29,7 +29,6 @@ PRO_MEM_DISPATCH(run_observed);
PRO_MEM_DISPATCH(use);
PRO_MEM_DISPATCH(now);
PRO_MEM_DISPATCH(render);
PRO_MEM_DISPATCH(d_ptr);
PRO_MEM_DISPATCH(schedule_after);
PRO_MEM_DISPATCH(schedule_every);
PRO_MEM_DISPATCH(reschedule);
+19 -7
View File
@@ -8,12 +8,10 @@
#include <utility>
namespace aethera {
namespace detail {
template <typename Private> struct Model_Private_Borrow {
template <typename Private_Facade> requires pro::proxiable<Private*, Private_Facade>
operator proxy<Private_Facade>() const {
return proxy<Private_Facade>{std::addressof(private_data)};
}
Private& private_data; /* 必填、非拥有;有效期不超过返回它的公共 Model 对象。 */
template <typename Model_Pointer> struct Model_Private_Pointer {
using element_type = std::remove_reference_t<decltype(*std::declval<Model_Pointer&>()->d)>;
decltype(auto) operator*() const noexcept { return *model->d; }
Model_Pointer model; /* Owns or borrows the complete Model; dereference exposes its unique Private. */
};
template <typename... Types> struct Model_Type_List {};
template <typename List, typename... Registrations> struct Append_Model_Registrations;
@@ -68,8 +66,22 @@ template <typename Self, typename Base, typename... Registrations> struct Def :
template <template <typename...> typename Concurrent_Storage, typename Tag, typename... Args> requires detail::Readable_Model_Value<Concurrent_Storage, Tag, Self, Args...>
const Self& read(Args&&... args) const;
template <typename... Args> explicit Def(Args&&... args);
auto d_ptr();
std::unique_ptr<Private> d; /* 唯一拥有最终 Private 和完整 Tagged Storage 集。 */
};
template <typename Facade, typename Model> requires pro::proxiable<detail::Model_Private_Pointer<Model*>, Facade>
proxy<Facade> model_proxy_view(Model& model) { return proxy<Facade>{detail::Model_Private_Pointer<Model*>{std::addressof(model)}}; }
template <typename Facade, typename Model> requires pro::proxiable<detail::Model_Private_Pointer<std::unique_ptr<Model>>, Facade>
proxy<Facade> model_proxy(std::unique_ptr<Model> model) { if (!model) return {}; return proxy<Facade>{detail::Model_Private_Pointer<std::unique_ptr<Model>>{std::move(model)}}; }
template <typename Facade, typename Model, typename... Args> requires pro::proxiable<detail::Model_Private_Pointer<std::unique_ptr<Model>>, Facade>
proxy<Facade> make_model_proxy(Args&&... args) { return model_proxy<Facade>(std::make_unique<Model>(std::forward<Args>(args)...)); }
template <typename Facade, typename Model> requires pro::proxiable<detail::Model_Private_Pointer<std::shared_ptr<Model>>, Facade>
proxy<Facade> model_proxy_shared(std::shared_ptr<Model> model) { if (!model) return {}; return proxy<Facade>{detail::Model_Private_Pointer<std::shared_ptr<Model>>{std::move(model)}}; }
template <typename Facade, typename Model, typename... Args> requires pro::proxiable<detail::Model_Private_Pointer<std::shared_ptr<Model>>, Facade>
proxy<Facade> make_model_proxy_shared(Args&&... args) { return model_proxy_shared<Facade>(std::make_shared<Model>(std::forward<Args>(args)...)); }
} // namespace aethera
#include "Model.ipp"
-5
View File
@@ -90,11 +90,6 @@ template <typename Self, typename Base, typename... Registrations>
template <typename... Args>
Def<Self, Base, Registrations...>::Def(Args&&... args) : d(std::make_unique<Private>(std::forward<Args>(args)...)) {}
template <typename Self, typename Base, typename... Registrations>
auto Def<Self, Base, Registrations...>::d_ptr() {
return detail::Model_Private_Borrow<Private>{*d};
}
template <typename Self, typename Base, typename... Registrations>
std::unique_ptr<Self> Def<Self, Base, Registrations...>::Builder::build() {
return std::move(ret);
+2
View File
@@ -4,4 +4,6 @@
`Model_Layer` 只组合注册表,最终 `Def::Private` 根据完整注册表创建唯一的 `Tagged_Storage_Set`。同一 tag 重复注册属于编译期错误。值类型不再根据 Layer 推导,也不存在兼容分支或默认空值类型。
跨模块 facade 直接声明消费方所需的 Private 能力。`make_model_proxy``make_model_proxy_shared``model_proxy_view` 分别承载独占、共享和非拥有 Model 生命周期,同时把 proxy 解引用目标映射到该 Model 唯一的 `Private`;实体公开类不实现 facade 转发函数。
Builder 使用 `set<Concurrent_Storage, Tag>(...)`,最终模型使用 `write<Concurrent_Storage, Tag>(...)``read<Concurrent_Storage, Tag>(...)`。Tag 先定位注册项,再由注册项的 `Value_Type` 实例化对应存储。
+13 -53
View File
@@ -1,63 +1,23 @@
# Task Flow 设计
## 职责
## 模块边界
本模块提供静态业务 DAG、进程级异步执行器和按次可选的性能观察。外部模块只依赖 `Task_Graph_Facade`Taskflow 类型及线程细节不进入公共接口
Task Flow 提供静态业务 DAG、进程级异步执行器和按次可选的诊断观察。Scene、Renderable 等消费模块不包含 Task Flow 的实体头文件;各自在自己的 `rely_facade.h` 中声明所需的最小能力Task Flow 只在实现文件中适配这些 facade
| 组件 | 职责 |
|----------------------------------|------------------------------------------------|
| `Task_Graph` | 构图、组合子图、保存业务节点元数据 |
| `Task_Graph_Execution` | 独占本次执行涉及的根图和子图,并管理运行期借用 |
| `Task_Runtime` | 初始化 Executor、提交执行、传递完成与异常 |
| `Taskflow_Observation` | 表达一次显式记录请求,并独占记录状态 |
| `Taskflow_Observation_Execution` | 持有本次可选记录;提交失败时自动回滚 |
| `Taskflow_Execution_Trace` | 执行完成后移出的原始观察结果 |
每个跨模块 facade 直接表达消费模块需要的 Private 能力。Model proxy 持有或借用完整实体,但把 facade 调用分派到实体唯一的 `Private`。同一底层 `Task_Graph` 可以生成多个消费模块视图,但所有视图共享同一个权威图状态。
`Taskflow_Observation` 专门服务于前台显式请求 N 帧诊断:每帧单独创建一次观察请求并附加原始任务执行数据。它不是业务状态、持续事件机制或跨帧快照;没有请求的帧不建立记录
Task Flow 模块内部的 `Task_Graph``Task_Runtime` 和执行期对象紧密协作,不通过跨模块 proxy
## 执行流程
## 生命周期与线程契约
1. `run_taskflow` 创建 `Task_Graph_Execution`,持有本帧根图及全部子图;调用方保证同一张图只有单帧在途
2. 调用方传入 Observation 时创建逐 Worker 状态,并把执行对象的非拥有指针写入所有原生 Node 的 `data`;未传入时写入空值且不分配记录数据
3. 内部执行对象持有 Graph 租约、Observation 执行对象和完成回调,然后提交给 Taskflow
4. Observer 按锁定的 Taskflow 4.1 对象布局从 TaskView 取得同一 Node 的只读 Task 句柄,并从 WorkerView 取得原生 Worker 借用;Node `data` 为空立即返回,否则由 Worker 直接提供 ID 和队列数据
5. topology 完成回调发布 Observation;Graph 执行租约析构时清除全部 Node `data`,释放 Graph 后调用业务完成回调
## 状态与生命周期
- 构图和执行服从单帧顺序:本帧构图完成后才提交,执行完成前不得重建或再次提交同一张根图或子图,因此不保存运行占用状态。
- 未传入 Observation 时没有记录状态;传入后 `requested → recording → ready → consumed` 是唯一状态链。
- `Taskflow_Execution` 是异步生命周期所有者。调用方可在提交后销毁或移动原 `Task_Graph`/Observation 句柄。
- Node `data` 中的指针是可空、非拥有借用;topology 完成顺序保证清除发生在最后一次 Observer 回调之后、Observation 执行对象析构之前。
- Trace 只保存原始数据。Worker ID 等于 `worker_tasks` 外层下标;后继关系由节点的 `predecessors` 计算。
## 线程契约
- 实现锁定 Taskflow 4.1.0。WorkerView 布局只用于取得必填、非拥有的原生 Worker 借用;每个固定 Worker ID 由一个 Executor 线程串行调用 `on_entry/on_exit`
- 每个 `worker_tasks[worker_id]` 只有对应 Worker 写入;执行完成发布后才允许读取。
- 模块不增加锁、同步等待、事件、线程亲和性或运行态快照。
- Task completion 和 `Taskflow_Exception_Boundary` 都在完成该图的 Taskflow worker 上执行;异常边界只能快速向上运输或记录失败,不得同步等待,也不得再次抛出。
- 实现明确锁定 Taskflow 4.1 的 TaskView/Task/WorkerView 单指针布局。升级 Taskflow 必须重新审核版本、字段布局、对齐以及 topology/Observer 完成顺序。
- 调用方在本帧构图完成后提交,并保证同一根图或子图在完成前不重建、不再次提交
- `Taskflow_Execution` 拥有异步执行生命周期;提交后调用方可以销毁或移动原始图句柄
- 没有诊断请求时不创建观察数据;显式请求只记录该次执行的原始数据
- Task completion 与异常边界都在完成该图的 Taskflow worker 上执行,只能异步向上传输失败或快速终止,不允许同步等待
- 模块不额外引入事件、运行状态快照或同步等待
## 错误语义
- 可预期结果通过各函数自己的结果枚举返回,例如未初始化或 Observation 已占用
- 任务抛出的 Unknown Failure 通过 `std::exception_ptr` 交给完成回调
- 观察写入失败同样只做跨线程异常传递,并在 `Taskflow_Observation::take()` 时重新抛出
- Runtime 初始化只接受一种 completion failure 策略:默认 `fast_fail` 在完成回调抛出时终止进程;`exception` 必须同时提供唯一的 `Taskflow_Exception_Boundary`,由 worker 捕获 completion 异常并交给该边界。Runtime 持有边界副本直至进程结束,其借用捕获也必须满足该生命周期。
- Scene 将任务失败继续交给 Frame Policy completionFrame Policy 必须先归还 rendering slot,再重新抛出,最终由 Runtime 的 completion failure 策略处理。
## 使用
```cpp
Taskflow_Observation observation{
"render_2d.frame"};
const auto result = run_taskflow(
graph,
[observation](std::exception_ptr failure) mutable {
auto trace = observation.take();
report_taskflow_result(std::move(failure), std::move(trace));
},
observation);
```
- 可预期失败通过各消费 facade 自己的结果枚举返回
- 任务异常通过 `std::exception_ptr` 传给 completion
- completion 自身的异常按 Runtime 配置的策略处理;exception 策略必须提供异步异常边界
@@ -0,0 +1,2 @@
#pragma once
namespace aethera::task_flow {}
@@ -1,70 +1,90 @@
#include "task_flow_facade.hpp"
#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 Proxied_Task_Node {
explicit Proxied_Task_Node(Task_Node node) : node(std::move(node)) {}
Task_Node node; /* 此 Proxy 唯一拥有的权威节点句柄。 */
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 Proxied_Task_Graph_Component {
explicit Proxied_Task_Graph_Component(std::shared_ptr<Task_Graph> graph) : graph(std::move(graph)) {}
std::shared_ptr<Task_Graph> 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 Proxied_Task_Graph {
explicit Proxied_Task_Graph(std::string name) : graph(std::make_shared<Task_Graph>(std::move(name))) {}
auto add(std::string name, std::function<void()> work) -> std::expected<proxy<Task_Node_Facade>, Add_Task_Graph_Result>;
auto add_condition(std::string name, std::function<int()> work) -> std::expected<proxy<Task_Node_Facade>, Add_Condition_Task_Graph_Result>;
proxy<Task_Graph_Component_Facade> component();
auto compose(std::string name, proxy<Task_Graph_Component_Facade>& child) -> std::expected<proxy<Task_Node_Facade>, Compose_Task_Graph_Result>;
Precede_Task_Graph_Result precede(proxy<Task_Node_Facade>& before, proxy<Task_Node_Facade>& after);
Describe_Task_Graph_Result describe(std::string key, std::string value);
Describe_Task_Graph_Node_Result describe_node(proxy<Task_Node_Facade>& node, std::string key, std::string value);
void clear();
bool empty() const noexcept;
Run_Task_Graph_Result run(Taskflow_Completion completion);
Run_Observed_Task_Graph_Result run_observed(Taskflow_Completion completion, Taskflow_Observation observation);
std::shared_ptr<Task_Graph> graph; /* 此 Proxy 唯一拥有的权威业务任务图。 */
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
namespace {
detail::Proxied_Task_Node* task_node(proxy<Task_Node_Facade>& value) noexcept {
return value ? proxy_cast<detail::Proxied_Task_Node>(&*value) : nullptr;
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));
}
detail::Proxied_Task_Graph_Component* task_graph_component(proxy<Task_Graph_Component_Facade>& value) noexcept {
return value ? proxy_cast<detail::Proxied_Task_Graph_Component>(&*value) : nullptr;
}
Run_Task_Graph_Result run_result(Run_Taskflow_Result result) noexcept {
switch (result) {
case Run_Taskflow_Result::submitted: return Run_Task_Graph_Result::submitted;
case Run_Taskflow_Result::runtime_not_initialized: return Run_Task_Graph_Result::runtime_not_initialized;
case Run_Taskflow_Result::completion_missing: return Run_Task_Graph_Result::completion_missing;
case Run_Taskflow_Result::observation_unavailable: std::terminate();
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();
}
Run_Observed_Task_Graph_Result run_observed_result(Run_Taskflow_Result result) noexcept {
switch (result) {
case Run_Taskflow_Result::submitted: return Run_Observed_Task_Graph_Result::submitted;
case Run_Taskflow_Result::runtime_not_initialized: return Run_Observed_Task_Graph_Result::runtime_not_initialized;
case Run_Taskflow_Result::completion_missing: return Run_Observed_Task_Graph_Result::completion_missing;
case Run_Taskflow_Result::observation_unavailable: return Run_Observed_Task_Graph_Result::observation_unavailable;
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();
}
} // namespace
auto detail::Proxied_Task_Graph::add(std::string name, std::function<void()> work) -> std::expected<proxy<Task_Node_Facade>, 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_Facade, Proxied_Task_Node>(std::move(*result)); }
auto detail::Proxied_Task_Graph::add_condition(std::string name, std::function<int()> work) -> std::expected<proxy<Task_Node_Facade>, Add_Condition_Task_Graph_Result> { auto result = graph->add_condition(std::move(name), std::move(work)); if (!result) return std::unexpected(result.error()); return pro::make_proxy<Task_Node_Facade, Proxied_Task_Node>(std::move(*result)); }
proxy<Task_Graph_Component_Facade> detail::Proxied_Task_Graph::component() { return pro::make_proxy<Task_Graph_Component_Facade, Proxied_Task_Graph_Component>(graph); }
auto detail::Proxied_Task_Graph::compose(std::string name, proxy<Task_Graph_Component_Facade>& child) -> std::expected<proxy<Task_Node_Facade>, Compose_Task_Graph_Result> { auto* component = task_graph_component(child); if (component == nullptr) return std::unexpected(Compose_Task_Graph_Result::component_unavailable); auto result = graph->compose(std::move(name), *component->graph); if (!result) return std::unexpected(result.error()); return pro::make_proxy<Task_Node_Facade, Proxied_Task_Node>(std::move(*result)); }
Precede_Task_Graph_Result detail::Proxied_Task_Graph::precede(proxy<Task_Node_Facade>& before, proxy<Task_Node_Facade>& after) { auto* before_node = task_node(before); auto* after_node = task_node(after); if (before_node == nullptr || after_node == nullptr) return Precede_Task_Graph_Result::node_unavailable; return before_node->node.precede(after_node->node); }
Describe_Task_Graph_Result detail::Proxied_Task_Graph::describe(std::string key, std::string value) { return graph->describe(std::move(key), std::move(value)); }
Describe_Task_Graph_Node_Result detail::Proxied_Task_Graph::describe_node(proxy<Task_Node_Facade>& node, std::string key, std::string value) { auto* target = task_node(node); return target == nullptr ? Describe_Task_Graph_Node_Result::node_unavailable : target->node.describe(std::move(key), std::move(value)); }
void detail::Proxied_Task_Graph::clear() { graph->clear(); }
bool detail::Proxied_Task_Graph::empty() const noexcept { return graph->empty(); }
Run_Task_Graph_Result detail::Proxied_Task_Graph::run(Taskflow_Completion completion) { return run_result(run_taskflow(*graph, std::move(completion))); }
Run_Observed_Task_Graph_Result detail::Proxied_Task_Graph::run_observed(Taskflow_Completion completion, Taskflow_Observation observation) { return run_observed_result(run_taskflow(*graph, std::move(completion), std::move(observation))); }
proxy<Task_Graph_Facade> make_task_graph(std::string name) { return pro::make_proxy<Task_Graph_Facade, detail::Proxied_Task_Graph>(std::move(name)); }
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
@@ -1,39 +0,0 @@
#pragma once
#include "../global.hpp"
#include "Task_Graph_Types.hpp"
#include "Task_Runtime_Types.hpp"
#include "Taskflow_Observation.hpp"
#include <cstdint>
#include <functional>
#include <expected>
#include <proxy/proxy.h>
#include <string>
namespace aethera {
enum struct Run_Task_Graph_Result : std::uint8_t {
submitted,
runtime_not_initialized,
completion_missing
};
enum struct Run_Observed_Task_Graph_Result : std::uint8_t {
submitted,
runtime_not_initialized,
completion_missing,
observation_unavailable
};
struct Task_Node_Facade : facade_builder::add_skill<pro::skills::rtti>::build {};
struct Task_Graph_Component_Facade : facade_builder::add_skill<pro::skills::rtti>::build {};
struct Task_Graph_Facade : facade_builder
::add_convention<_add, std::expected<proxy<Task_Node_Facade>, Add_Task_Graph_Result>(std::string, std::function<void()>)>
::add_convention<_add_condition, std::expected<proxy<Task_Node_Facade>, Add_Condition_Task_Graph_Result>(std::string, std::function<int()>)>
::add_convention<_component, proxy<Task_Graph_Component_Facade>()>
::add_convention<_compose, std::expected<proxy<Task_Node_Facade>, Compose_Task_Graph_Result>(std::string, proxy<Task_Graph_Component_Facade>&)>
::add_convention<_precede, Precede_Task_Graph_Result(proxy<Task_Node_Facade>&, proxy<Task_Node_Facade>&)>
::add_convention<_describe, Describe_Task_Graph_Result(std::string, std::string)>
::add_convention<_describe_node, Describe_Task_Graph_Node_Result(proxy<Task_Node_Facade>&, std::string, std::string)>
::add_convention<_clear, void()>
::add_convention<_empty, bool() const noexcept>
::add_convention<_run, Run_Task_Graph_Result(Taskflow_Completion)>
::add_convention<_run_observed, Run_Observed_Task_Graph_Result(Taskflow_Completion, Taskflow_Observation)>
::build {};
proxy<Task_Graph_Facade> make_task_graph(std::string name);
} // namespace aethera
@@ -1,6 +1,8 @@
#include "Timer_Service.hpp"
#include "detail/Timer_Scheduler.hpp"
#include "function/frame_policy/rely_facade.h"
#include "model/Model.hpp"
#include <atomic>
#include <condition_variable>
@@ -13,13 +15,15 @@
namespace aethera {
namespace {
struct Steady_Timer_Time_Source {
Timer_Time_Point now() const noexcept {
return std::chrono::steady_clock::now();
}
struct Steady_Timer_Time_Source : Def<Steady_Timer_Time_Source, Root> {
struct Private;
Steady_Timer_Time_Source() = default;
};
struct Steady_Timer_Time_Source::Private : Prev_Private {
Timer_Time_Point now() const noexcept { return std::chrono::steady_clock::now(); }
};
struct Thread_Timer_Service : Immovable {
struct Thread_Timer_Service_State : Immovable {
struct Impl {
static constexpr std::chrono::seconds Maximum_Wait{1};
@@ -138,14 +142,14 @@ struct Thread_Timer_Service : Immovable {
std::deque<Command> commands; /* 提交线程向唯一计时线程转移的命令所有权。 */
};
explicit Thread_Timer_Service(proxy<Timer_Time_Source> time_source) :
explicit Thread_Timer_Service_State(proxy<Timer_Time_Source> time_source) :
impl(std::make_shared<Impl>(std::move(time_source))) {
std::thread([state = impl] {
state->run();
}).detach();
}
~Thread_Timer_Service() {
~Thread_Timer_Service_State() {
impl->push(Impl::Command{Impl::Command_Type::stop});
}
@@ -185,13 +189,21 @@ struct Thread_Timer_Service : 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;
};
}
proxy<Timer_Time_Source> make_steady_timer_time_source() {
return pro::make_proxy<
Timer_Time_Source,
Steady_Timer_Time_Source>();
}
proxy<Timer_Time_Source> make_steady_timer_time_source() { return make_model_proxy<Timer_Time_Source, Steady_Timer_Time_Source>(); }
std::expected<proxy<Timer_Service>, Make_Timer_Service_Result>
make_timer_service(proxy<Timer_Time_Source> time_source) {
@@ -199,8 +211,9 @@ make_timer_service(proxy<Timer_Time_Source> time_source) {
return std::unexpected(
Make_Timer_Service_Result::time_source_unavailable);
}
return pro::make_proxy_shared<
Timer_Service,
Thread_Timer_Service>(std::move(time_source));
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());
}
}
@@ -13,24 +13,20 @@ using Timer_Id = std::uint64_t;
using Timer_Callback = std::function<void()>;
struct Periodic_Timer_Service : facade_builder
struct Timer_Service : facade_builder
::add_convention<
_schedule_every,
Timer_Id(std::chrono::nanoseconds, Timer_Callback)>
::add_convention<
_cancel,
void(Timer_Id, Timer_Callback)>
::support_copy<pro::constraint_level::nontrivial>
::build {};
struct Timer_Service : facade_builder
::add_facade_with_substitution<Periodic_Timer_Service>
::add_convention<
_schedule_after,
Timer_Id(std::chrono::nanoseconds, Timer_Callback)>
::add_convention<
_reschedule,
void(Timer_Id, std::chrono::nanoseconds)>
::support_relocation<pro::constraint_level::nothrow>
::build {};
enum struct Make_Timer_Service_Result : std::uint8_t {
@@ -10,12 +10,7 @@ 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_copy<pro::constraint_level::nontrivial>
::build {};
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();
}
@@ -0,0 +1,2 @@
#pragma once
namespace aethera::time_thread {}
@@ -18,12 +18,12 @@ namespace {
using namespace std::chrono_literals;
struct Pending_Frame {
std::reference_wrapper<aethera::proxy<aethera::FP_Frame>> frame; /* 被 Scene/Sink 借用的策略帧。 */
aethera::FP_Frame_Completion completion; /* 归还同一帧的完成回调所有权。 */
std::reference_wrapper<aethera::proxy<aethera::frame_policy::Frame>> frame; /* 被 Scene/Sink 借用的策略帧。 */
aethera::frame_policy::Frame_Completion completion; /* 归还同一帧的完成回调所有权。 */
};
struct Pending_Render_Frame {
std::reference_wrapper<aethera::proxy<aethera::FP_Frame>> frame; /* Borrowed policy frame; the policy outlives the pending render. */
aethera::FP_Render_Completion completion; /* Owns the render completion until this frame is returned. */
std::reference_wrapper<aethera::proxy<aethera::frame_policy::Frame>> frame; /* Borrowed policy frame; the policy outlives the pending render. */
aethera::frame_policy::Render_Completion completion; /* Owns the render completion until this frame is returned. */
};
struct Frame_Policy_Test_Control {
@@ -55,49 +55,44 @@ struct Frame_Policy_Test_Control {
std::deque<Pending_Render_Frame> rendering_frames; /* Scene 尚未归还的帧及其回调。 */
std::deque<Pending_Frame> sending_frames; /* Sink 尚未归还的帧及其回调。 */
std::vector<aethera::proxy<aethera::FP_Frame>*>
std::vector<aethera::proxy<aethera::frame_policy::Frame>*>
rendered_frame_addresses; /* 可空、非拥有;记录每次渲染采用的槽位地址。 */
aethera::Timer_Callback timer_callback; /* 周期服务持有的到期回调。 */
aethera::Timer_Callback cancel_completion; /* 测试显式完成异步取消的回调。 */
aethera::frame_policy::Timer_Callback timer_callback; /* 周期服务持有的到期回调。 */
aethera::frame_policy::Timer_Callback cancel_completion; /* 测试显式完成异步取消的回调。 */
std::chrono::nanoseconds scheduled_interval{}; /* 策略提交给服务的初始期限。 */
std::chrono::nanoseconds rescheduled_interval{}; /* 策略最近一次计算出的下一帧延迟。 */
aethera::Timer_Id cancelled_timer_id{}; /* 策略请求取消的计时器标识。 */
aethera::frame_policy::Timer_Id cancelled_timer_id{}; /* 策略请求取消的计时器标识。 */
std::size_t frames_created{}; /* Scene 创建的物理帧数量。 */
std::size_t frames_alive{}; /* 尚未析构的物理帧数量。 */
std::size_t render_calls{}; /* Scene 已接受的渲染借用次数。 */
std::size_t send_calls{}; /* Sink 已接受的发送借用次数。 */
aethera::Render_Scene_Result render_result{aethera::Render_Scene_Result::submitted}; /* Scene 对下一次及后续渲染提交返回的结果。 */
aethera::frame_policy::Render_Scene_Result render_result{aethera::frame_policy::Render_Scene_Result::submitted}; /* Scene 对下一次及后续渲染提交返回的结果。 */
std::exception_ptr render_failure{}; /* Failure delivered by the next render completion. */
bool complete_render_synchronously{}; /* Scene 是否在 render 内立即归还帧。 */
bool complete_send_synchronously{}; /* Sink 是否在 send 内立即归还帧。 */
bool throw_from_send{}; /* Sink 是否以 Unknown Failure 退出分派。 */
};
struct Test_Timer_Service {
aethera::Timer_Id schedule_after(std::chrono::nanoseconds interval, aethera::Timer_Callback callback) {
struct Test_Timer_Service : aethera::Def<Test_Timer_Service, aethera::Root> {
struct Private : Prev_Private {
explicit Private(std::shared_ptr<Frame_Policy_Test_Control> control) : control(std::move(control)) {}
aethera::frame_policy::Timer_Id schedule_every(std::chrono::nanoseconds interval, aethera::frame_policy::Timer_Callback callback) {
control->scheduled_interval = interval;
control->timer_callback = std::move(callback);
return Timer_Id;
}
aethera::Timer_Id schedule_every(std::chrono::nanoseconds interval, aethera::Timer_Callback callback) {
control->scheduled_interval = interval;
control->timer_callback = std::move(callback);
return Timer_Id;
}
void cancel(aethera::Timer_Id id, aethera::Timer_Callback completion) {
void cancel(aethera::frame_policy::Timer_Id id, aethera::frame_policy::Timer_Callback completion) {
control->cancelled_timer_id = id;
control->cancel_completion = std::move(completion);
}
void reschedule(aethera::Timer_Id id, std::chrono::nanoseconds interval) {
void reschedule(aethera::frame_policy::Timer_Id id, std::chrono::nanoseconds interval) {
EXPECT_EQ(id, Timer_Id);
control->rescheduled_interval = interval;
}
static constexpr aethera::Timer_Id Timer_Id{7};
static constexpr aethera::frame_policy::Timer_Id Timer_Id{7};
std::shared_ptr<Frame_Policy_Test_Control> control; /* 服务调用记录的共享所有权。 */
};
explicit Test_Timer_Service(std::shared_ptr<Frame_Policy_Test_Control> control) : Def(std::move(control)) {}
};
struct Test_Frame {
@@ -127,21 +122,21 @@ struct Test_Scene : aethera::Def<Test_Scene, aethera::Root> {
struct Test_Scene::Private : Prev_Private {
explicit Private(std::shared_ptr<Frame_Policy_Test_Control> control) : control(std::move(control)) {}
aethera::proxy<aethera::FP_Frame> create_frame() {
aethera::proxy<aethera::frame_policy::Frame> create_frame() {
++control->frames_created;
return pro::make_proxy<aethera::FP_Frame, Test_Frame>(control);
return pro::make_proxy<aethera::frame_policy::Frame, Test_Frame>(control);
}
aethera::Render_Scene_Result render(aethera::proxy<aethera::FP_Frame>& frame, aethera::FP_Render_Completion completion) {
aethera::frame_policy::Render_Scene_Result render(aethera::proxy<aethera::frame_policy::Frame>& frame, aethera::frame_policy::Render_Completion completion) {
control->rendered_frame_addresses.push_back(std::addressof(frame));
++control->render_calls;
if (control->render_result != aethera::Render_Scene_Result::submitted) return control->render_result;
if (control->render_result != aethera::frame_policy::Render_Scene_Result::submitted) return control->render_result;
if (control->complete_render_synchronously) {
completion(frame, std::exchange(control->render_failure, {}));
return aethera::Render_Scene_Result::submitted;
return aethera::frame_policy::Render_Scene_Result::submitted;
}
control->rendering_frames.push_back({frame, std::move(completion)});
return aethera::Render_Scene_Result::submitted;
return aethera::frame_policy::Render_Scene_Result::submitted;
}
std::shared_ptr<Frame_Policy_Test_Control> control; /* Scene 测试行为的共享控制数据。 */
@@ -155,7 +150,7 @@ struct Test_Sink : aethera::Def<Test_Sink, aethera::Root> {
struct Test_Sink::Private : Prev_Private {
explicit Private(std::shared_ptr<Frame_Policy_Test_Control> control) : control(std::move(control)) {}
void send(aethera::proxy<aethera::FP_Frame>& frame, aethera::FP_Frame_Completion completion) {
void send(aethera::proxy<aethera::frame_policy::Frame>& frame, aethera::frame_policy::Frame_Completion completion) {
++control->send_calls;
if (control->throw_from_send) {
throw std::runtime_error("test sink failure");
@@ -171,9 +166,9 @@ struct Test_Sink::Private : Prev_Private {
};
std::unique_ptr<aethera::Throttled_Latest_only> make_policy(const std::shared_ptr<Frame_Policy_Test_Control>& control, std::optional<double> user_frames_per_second = std::nullopt, bool render_rate_limit_enabled = false, bool send_rate_limit_enabled = false, bool statistics_enabled = false, std::size_t statistics_window_size = 120) {
auto timer_service = pro::make_proxy<aethera::Timer_Service, Test_Timer_Service>(control);
auto scene = pro::make_proxy<aethera::FP_Scene, Test_Scene>(control);
auto sink = pro::make_proxy<aethera::FP_Sink, Test_Sink>(control);
auto timer_service = aethera::make_model_proxy<aethera::frame_policy::Timer_Service, Test_Timer_Service>(control);
auto scene = aethera::make_model_proxy<aethera::frame_policy::Scene, Test_Scene>(control);
auto sink = aethera::make_model_proxy<aethera::frame_policy::Sink, Test_Sink>(control);
aethera::Throttled_Latest_only::Builder builder(std::move(timer_service), std::move(scene), std::move(sink));
builder
.set<aethera::Import_Struct_With_Dirty, aethera::Frame_Policy_Prop>(&aethera::Throttled_Latest_only::Prop::user_frames_per_second, user_frames_per_second)
@@ -188,10 +183,10 @@ TEST(Throttled_Latest_Only, Publishes_And_Clears_Scene_Submission_Failure) {
auto control = std::make_shared<Frame_Policy_Test_Control>();
auto policy = make_policy(control);
ASSERT_TRUE(policy->start().has_value());
control->render_result = aethera::Render_Scene_Result::runtime_not_initialized;
control->render_result = aethera::frame_policy::Render_Scene_Result::runtime_not_initialized;
control->fire_timer();
policy->read<aethera::Export_Struct, aethera::Frame_Policy_State>([](const aethera::Frame_Policy_State& state) { ASSERT_TRUE(state.render_submission_error.has_value()); EXPECT_EQ(*state.render_submission_error, aethera::Frame_Policy_Render_Submission_Result::runtime_not_initialized); });
control->render_result = aethera::Render_Scene_Result::submitted;
control->render_result = aethera::frame_policy::Render_Scene_Result::submitted;
control->fire_timer();
policy->read<aethera::Export_Struct, aethera::Frame_Policy_State>([](const aethera::Frame_Policy_State& state) { EXPECT_FALSE(state.render_submission_error.has_value()); });
control->complete_render();
@@ -433,7 +428,7 @@ TEST(Throttled_Latest_Only, Publishes_Optional_Sliding_Statistics) {
}
TEST(Throttled_Latest_Only, Reports_Invalid_Configuration) {
aethera::Throttled_Latest_only::Builder missing_builder(aethera::proxy<aethera::Timer_Service>{}, aethera::proxy<aethera::FP_Scene>{}, aethera::proxy<aethera::FP_Sink>{});
aethera::Throttled_Latest_only::Builder missing_builder(aethera::proxy<aethera::frame_policy::Timer_Service>{}, aethera::proxy<aethera::frame_policy::Scene>{}, aethera::proxy<aethera::frame_policy::Sink>{});
auto missing_dependencies = missing_builder.build();
const auto missing_start = missing_dependencies->start();
ASSERT_FALSE(missing_start);
+30 -31
View File
@@ -9,11 +9,9 @@
#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>::build {};
struct Test_Model_Facade : aethera::facade_builder::add_convention<aethera::_d_ptr, aethera::proxy<Test_Model_Private_Facade>()>::build {};
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>::build {};
struct Other_Test_Model_Facade : aethera::facade_builder::add_convention<aethera::_d_ptr, aethera::proxy<Other_Test_Model_Private_Facade>()>::build {};
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{};
@@ -34,7 +32,7 @@ struct Test_Model : aethera::Def<Test_Model, Test_Model_Base> {
};
struct Test_Renderable : aethera::Def<Test_Renderable, aethera::Renderable<Test_Renderable>> {
struct Private : Prev_Private {
void advance_renderable(Test_Renderable& object) { ++advance_count; object.taskflow()->clear(); (void)object.taskflow()->add("test.prepare", [] {}); }
void advance_renderable() { ++advance_count; auto& graph = internal_taskflow(); graph->clear(); (void)graph->add("test.prepare", [] {}); }
int advance_count{};
};
};
@@ -42,15 +40,15 @@ struct Test_Dag_Renderable : aethera::Def<Test_Dag_Renderable, aethera::Renderab
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(Test_Dag_Renderable& object) { advance_order->push_back(id); object.taskflow()->clear(); (void)object.taskflow()->add("prepare", [] {}); }
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<Test_Renderable*, aethera::scene::Renderable>);
static_assert(pro::proxiable<aethera::Scene*, aethera::FP_Scene>);
static_assert(pro::proxiable<Test_Model*, Test_Model_Facade>);
static_assert(pro::proxiable<Test_Model*, Other_Test_Model_Facade>);
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{};
@@ -77,23 +75,24 @@ TEST(Model, Registered_Struct_Write_Publishes_To_The_Same_Tagged_Storage) {
properties.internal.advance();
EXPECT_EQ(properties.internal.use()->number, 13);
}
TEST(Model, Public_Facade_Borrows_Final_Private_Facade) {
auto public_model = pro::make_proxy<Test_Model_Facade, Test_Model>();
EXPECT_EQ(public_model->d_ptr()->private_value(), 41);
auto other_public_model = pro::make_proxy<Other_Test_Model_Facade, Test_Model>();
EXPECT_EQ(other_public_model->d_ptr()->other_private_value(), 43);
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_Public_Facade_Uses_Task_Graph_Capabilities) {
auto renderable = pro::make_proxy<aethera::scene::Renderable, Test_Renderable>();
TEST(Model, Renderable_Facade_Uses_Private_Task_Graph_Capabilities) {
auto renderable = aethera::make_model_proxy<aethera::scene::Renderable, Test_Renderable>();
renderable->advance();
EXPECT_FALSE(renderable->taskflow()->empty());
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}, pro::make_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 2));
builder.add({1}, pro::make_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 1));
builder.add({3}, pro::make_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 3));
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();
@@ -114,7 +113,7 @@ TEST(Model, Scene_Advances_Renderables_And_Composes_Task_Graph_In_Dag_Order) {
}
TEST(Model, Dag_Rejects_Unallocated_Id_And_Missing_Object) {
aethera::Dag<aethera::scene::Renderable>::Builder invalid_id_builder;
invalid_id_builder.add({0}, pro::make_proxy<aethera::scene::Renderable, Test_Renderable>());
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);
@@ -127,7 +126,7 @@ TEST(Model, Dag_Rejects_Unallocated_Id_And_Missing_Object) {
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}, pro::make_proxy<aethera::scene::Renderable, Test_Dag_Renderable>(advance_order, 1));
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;
@@ -135,7 +134,7 @@ TEST(Model, Owned_Dag_Rolls_Back_The_Whole_Invalid_Edit) {
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}, pro::make_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(); });
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);
@@ -145,22 +144,22 @@ 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::FP_Frame>&, std::exception_ptr) { completed = true; }), aethera::Render_Scene_Result::runtime_not_initialized);
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::FP_Frame> empty_frame;
EXPECT_EQ(scene.d->render(empty_frame, [&](aethera::proxy<aethera::FP_Frame>&, std::exception_ptr) {}), aethera::Render_Scene_Result::frame_missing);
EXPECT_EQ(scene.d->render(frame, {}), aethera::Render_Scene_Result::completion_missing);
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::proxy<aethera::scene::Renderable>{&renderable});
builder.add({2}, aethera::proxy<aethera::scene::Renderable>{&renderable});
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::FP_Frame>&, std::exception_ptr) {}), aethera::Render_Scene_Result::task_graph_invalid);
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
@@ -1,6 +1,7 @@
#include "time_thread/Timer_Service.hpp"
#include "time_thread/Timer_Time_Source.hpp"
#include "time_thread/detail/Timer_Scheduler.hpp"
#include "model/Model.hpp"
#include <gtest/gtest.h>
@@ -28,19 +29,21 @@ concept Timer_Service_Api = requires(
static_assert(Timer_Service_Api<aethera::proxy<aethera::Timer_Service>>);
struct Test_Timer_Time_Source {
aethera::Timer_Time_Point now() const noexcept {
return *current_time;
}
struct Test_Timer_Time_Source : aethera::Def<Test_Timer_Time_Source, aethera::Root> {
struct Private;
explicit Test_Timer_Time_Source(std::shared_ptr<aethera::Timer_Time_Point> current_time) : Def(std::move(current_time)) {}
};
struct Test_Timer_Time_Source::Private : Test_Timer_Time_Source::Prev_Private {
explicit Private(std::shared_ptr<aethera::Timer_Time_Point> current_time) : current_time(std::move(current_time)) {}
aethera::Timer_Time_Point now() const noexcept { return *current_time; }
std::shared_ptr<aethera::Timer_Time_Point> current_time; /* 测试控制的唯一单调时间值。 */
};
struct Timer_Scheduler_Test : testing::Test {
Timer_Scheduler_Test() :
scheduler(pro::make_proxy<
aethera::Timer_Time_Source,
Test_Timer_Time_Source>(current_time)) {}
scheduler(aethera::make_model_proxy<aethera::Timer_Time_Source, Test_Timer_Time_Source>(current_time)) {}
void advance_by(std::chrono::nanoseconds duration) {
*current_time += duration;