更新
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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` 实例化对应存储。
|
||||
|
||||
@@ -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 completion;Frame 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user