更新
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
函数定义 函数调用 等参数不允许换行 注意所有facade用到的函数名都定义在这里 D:
|
||||
\ae\proj\Aethera\kernel\kernel\include\global.hpp 这样重复的名称不用重复写 光写facade即可
|
||||
|
||||
枚举定义要换行 加注释 facade_builder 定义也要换行
|
||||
``
|
||||
除非特别说明不允许写任何同步等待代码。
|
||||
除非特别说明不允许写任何同步等待代码。
|
||||
@@ -26,7 +27,9 @@
|
||||
除非特别说明不允许写任何 快照, 任何线程问题先完了都要给我说明 我来审核。
|
||||
``
|
||||
|
||||
跨模块多态对象统一使用 proxy 表达,proxy 的实际所有权由构造时承载的指针类型决定:裸指针为非拥有借用,make_proxy/unique_ptr 为独占所有权,make_proxy_shared/shared_ptr 为共享所有权,proxy_view 为非拥有观察,weak_proxy 为弱所有权。接口和字段注释必须写明实际所有权与生命周期,不再强制使用 not_null<T*> 或 owner<T*> 标注普通指针。
|
||||
跨模块多态对象统一使用 proxy 表达,proxy 的实际所有权由构造时承载的指针类型决定:裸指针为非拥有借用,make_proxy/unique_ptr
|
||||
为独占所有权,make_proxy_shared/shared_ptr 为共享所有权,proxy_view 为非拥有观察,weak_proxy
|
||||
为弱所有权。接口和字段注释必须写明实际所有权与生命周期,不再强制使用 not_null<T*> 或 owner<T*> 标注普通指针。
|
||||
|
||||
我使用 里的这个环境 D:\ae\proj\Aethera\CMakePresets.json "toolchain/vs2022.json"
|
||||
====================[ 构建 | Aethera_Kernel_check | vs2022_debug ]================
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
#include <variant>
|
||||
namespace aethera {
|
||||
enum struct Frame_Policy_Configuration_Result : std::uint8_t { invalid_user_frame_rate, interval_out_of_range, invalid_statistics_window };
|
||||
enum struct Frame_Policy_Render_Submission_Result : std::uint8_t { runtime_not_initialized, task_graph_invalid };
|
||||
enum struct Throttled_Latest_Only_Start_Result : std::uint8_t { already_running, start_in_progress, stop_in_progress, dependency_unavailable, frame_unavailable, invalid_user_frame_rate, interval_out_of_range, invalid_statistics_window };
|
||||
enum struct Throttled_Latest_Only_Stop_Result : std::uint8_t { already_stopped, already_stopping, start_in_progress, completion_missing };
|
||||
namespace frame_policy {
|
||||
enum struct Render_Scene_Result : std::uint8_t { submitted, runtime_not_initialized, task_graph_invalid, frame_missing, completion_missing };
|
||||
} // namespace frame_policy
|
||||
enum struct Frame_Policy_Configuration_Result : std::uint8_t {
|
||||
invalid_user_frame_rate,
|
||||
interval_out_of_range,
|
||||
invalid_statistics_window
|
||||
};
|
||||
enum struct Throttled_Latest_Only_Start_State_Result : std::uint8_t {
|
||||
already_running,
|
||||
start_in_progress,
|
||||
stop_in_progress,
|
||||
dependency_unavailable,
|
||||
frame_unavailable
|
||||
};
|
||||
using Throttled_Latest_Only_Start_Result = std::variant<Throttled_Latest_Only_Start_State_Result, Frame_Policy_Configuration_Result>;
|
||||
enum struct Throttled_Latest_Only_Stop_Result : std::uint8_t {
|
||||
already_stopped,
|
||||
already_stopping,
|
||||
start_in_progress,
|
||||
completion_missing
|
||||
};
|
||||
} // namespace aethera
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
#pragma once
|
||||
#include "export/export.h"
|
||||
#include "global.hpp"
|
||||
#include "scene/export/export.h"
|
||||
#include "time_thread/export/export.h"
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <proxy/proxy.h>
|
||||
|
||||
namespace aethera::frame_policy {
|
||||
using Timer_Id = aethera::Timer_Id;
|
||||
using Timer_Callback = aethera::Timer_Callback;
|
||||
struct Frame : facade_builder::build {};
|
||||
using Frame_Completion = std::function<void(proxy<Frame>&)>;
|
||||
using Render_Completion = std::function<void(proxy<Frame>&, std::exception_ptr)>;
|
||||
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 {};
|
||||
using Frame_Completion = std::function<void(proxy<scene::Frame>&)>;
|
||||
struct Scene : facade_builder::add_convention<_create_frame, proxy<scene::Frame>()>::add_convention<_render, scene::Render_Result(proxy<scene::Frame>&, scene::Render_Completion)>::support_relocation<pro::constraint_level::nothrow>::build {};
|
||||
struct Sink : facade_builder::add_convention<_send, void(proxy<scene::Frame>&, Frame_Completion)>::support_relocation<pro::constraint_level::nothrow>::build {};
|
||||
using Timer_Service = time_thread::Timer_Service_Frame_Facade;
|
||||
proxy<Timer_Service> make_timer_service();
|
||||
} // namespace aethera::frame_policy
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "../export/export.h"
|
||||
#include "scene/export/export.h"
|
||||
#include "concurrent/base/Concurrent_Struct.hpp"
|
||||
#include "model/Model.hpp"
|
||||
#include "statistics/Sliding_Statistics.hpp"
|
||||
@@ -35,7 +36,7 @@ struct Frame_Policy_State {
|
||||
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 拒绝最近一次渲染提交时的策略级原因;下一次提交成功后清空。 */
|
||||
std::optional<scene::Render_Error> render_submission_error; /* Scene 拒绝最近一次渲染提交时的原始原因;下一次提交成功后清空。 */
|
||||
};
|
||||
} // namespace aethera
|
||||
#include "Frame_Policy.ipp"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "Throttled_Latest_Only.hpp"
|
||||
#include <algorithm>
|
||||
#include <concepts>
|
||||
#include <exception>
|
||||
#include <utility>
|
||||
namespace aethera {
|
||||
@@ -7,16 +8,6 @@ namespace {
|
||||
auto positive_duration(std::chrono::steady_clock::duration duration) noexcept -> std::chrono::nanoseconds {
|
||||
return std::max(std::chrono::nanoseconds{1}, std::chrono::duration_cast<std::chrono::nanoseconds>(duration));
|
||||
}
|
||||
auto start_result(Frame_Policy::Configuration_Result result) noexcept -> Throttled_Latest_only::Start_Result {
|
||||
using Configuration_Result = Frame_Policy::Configuration_Result;
|
||||
using Start_Result = Throttled_Latest_only::Start_Result;
|
||||
switch (result) {
|
||||
case Configuration_Result::invalid_user_frame_rate: return Start_Result::invalid_user_frame_rate;
|
||||
case Configuration_Result::interval_out_of_range: return Start_Result::interval_out_of_range;
|
||||
case Configuration_Result::invalid_statistics_window: return Start_Result::invalid_statistics_window;
|
||||
}
|
||||
std::terminate();
|
||||
}
|
||||
} // namespace
|
||||
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 {
|
||||
@@ -53,23 +44,23 @@ auto Throttled_Latest_only::Private::prepare_start() -> std::expected<std::chron
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
switch (phase) {
|
||||
case Phase::running: return std::unexpected(Start_Result::already_running);
|
||||
case Phase::starting: return std::unexpected(Start_Result::start_in_progress);
|
||||
case Phase::stopping: return std::unexpected(Start_Result::stop_in_progress);
|
||||
case Phase::running: return std::unexpected(Start_Result{Start_State_Result::already_running});
|
||||
case Phase::starting: return std::unexpected(Start_Result{Start_State_Result::start_in_progress});
|
||||
case Phase::stopping: return std::unexpected(Start_Result{Start_State_Result::stop_in_progress});
|
||||
case Phase::stopped: break;
|
||||
}
|
||||
if (!timer_service || !scene || !sink) {
|
||||
return std::unexpected(Start_Result::dependency_unavailable);
|
||||
return std::unexpected(Start_Result{Start_State_Result::dependency_unavailable});
|
||||
}
|
||||
phase = Phase::starting;
|
||||
}
|
||||
std::array<proxy<frame_policy::Frame>, Frame_Count> created_frames;
|
||||
std::array<proxy<scene::Frame>, Frame_Count> created_frames;
|
||||
try {
|
||||
for (auto& created_frame : created_frames) {
|
||||
created_frame = scene->create_frame();
|
||||
if (!created_frame) {
|
||||
rollback_start();
|
||||
return std::unexpected(Start_Result::frame_unavailable);
|
||||
return std::unexpected(Start_Result{Start_State_Result::frame_unavailable});
|
||||
}
|
||||
}
|
||||
std::lock_guard lock(mutex);
|
||||
@@ -84,7 +75,7 @@ auto Throttled_Latest_only::Private::prepare_start() -> std::expected<std::chron
|
||||
publish_state();
|
||||
if (!interval) {
|
||||
phase = Phase::stopped;
|
||||
return std::unexpected(start_result(interval.error()));
|
||||
return std::unexpected(Start_Result{interval.error()});
|
||||
}
|
||||
for (std::size_t index = 0; index < Frame_Count; ++index) {
|
||||
if (frames[index].frame) {
|
||||
@@ -101,7 +92,7 @@ auto Throttled_Latest_only::Private::prepare_start() -> std::expected<std::chron
|
||||
}
|
||||
}
|
||||
void Throttled_Latest_only::Private::rollback_start() {
|
||||
std::array<proxy<frame_policy::Frame>, Frame_Count> retired_frames;
|
||||
std::array<proxy<scene::Frame>, Frame_Count> retired_frames;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
if (phase != Phase::starting || timer_id) {
|
||||
@@ -220,7 +211,7 @@ void Throttled_Latest_only::Private::frame_due() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const auto result = scene->render(frames[selected_index].frame, [this, selected_index](proxy<frame_policy::Frame>& completed_frame, std::exception_ptr failure) {
|
||||
const auto result = scene->render(frames[selected_index].frame, [this, selected_index](proxy<scene::Frame>& completed_frame, std::exception_ptr failure) {
|
||||
if (failure) {
|
||||
abandon_render(selected_index);
|
||||
std::rethrow_exception(failure);
|
||||
@@ -228,29 +219,30 @@ void Throttled_Latest_only::Private::frame_due() {
|
||||
rendered(selected_index, completed_frame);
|
||||
});
|
||||
record_render_submission(result);
|
||||
if (result != frame_policy::Render_Scene_Result::submitted) abandon_render(selected_index);
|
||||
if (!result) abandon_render(selected_index);
|
||||
}
|
||||
catch (...) {
|
||||
abandon_render(selected_index);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
void Throttled_Latest_only::Private::record_render_submission(frame_policy::Render_Scene_Result result) {
|
||||
void Throttled_Latest_only::Private::record_render_submission(scene::Render_Result result) {
|
||||
std::lock_guard lock(mutex);
|
||||
auto& state = *get<Frame_Policy_State>().internal.use();
|
||||
switch (result) {
|
||||
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();
|
||||
if (result) state.render_submission_error.reset();
|
||||
else {
|
||||
const bool impossible = std::visit([](auto error) {
|
||||
using Error = decltype(error);
|
||||
if constexpr (std::same_as<Error, scene::Render_State_Result>) return true;
|
||||
if constexpr (std::same_as<Error, Run_Taskflow_Result>) return error == Run_Taskflow_Result::completion_missing || error == Run_Taskflow_Result::observation_unavailable;
|
||||
return false;
|
||||
}, result.error());
|
||||
if (impossible) std::terminate();
|
||||
state.render_submission_error = result.error();
|
||||
}
|
||||
publish_state();
|
||||
}
|
||||
void Throttled_Latest_only::Private::rendered(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame) {
|
||||
void Throttled_Latest_only::Private::rendered(std::size_t slot_index, proxy<scene::Frame>& completed_frame) {
|
||||
bool dispatch = false;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
@@ -298,7 +290,7 @@ void Throttled_Latest_only::Private::rendered(std::size_t slot_index, proxy<fram
|
||||
}
|
||||
void Throttled_Latest_only::Private::dispatch_send(std::size_t slot_index) {
|
||||
try {
|
||||
sink->send(frames[slot_index].frame, [this, slot_index](proxy<frame_policy::Frame>& completed_frame) {
|
||||
sink->send(frames[slot_index].frame, [this, slot_index](proxy<scene::Frame>& completed_frame) {
|
||||
sent(slot_index, completed_frame);
|
||||
});
|
||||
}
|
||||
@@ -307,7 +299,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<frame_policy::Frame>& completed_frame) {
|
||||
void Throttled_Latest_only::Private::sent(std::size_t slot_index, proxy<scene::Frame>& completed_frame) {
|
||||
std::size_t next_send_index = Frame_Count;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
@@ -361,7 +353,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<frame_policy::Frame>, Frame_Count> retired_frames;
|
||||
std::array<proxy<scene::Frame>, Frame_Count> retired_frames;
|
||||
Stop_Completion completion;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace aethera {
|
||||
*/
|
||||
struct Throttled_Latest_only : Def<Throttled_Latest_only, Frame_Policy> {
|
||||
using Start_Result = Throttled_Latest_Only_Start_Result;
|
||||
using Start_State_Result = Throttled_Latest_Only_Start_State_Result;
|
||||
using Stop_Result = Throttled_Latest_Only_Stop_Result;
|
||||
using Stop_Completion = std::function<void()>;
|
||||
struct Private;
|
||||
|
||||
@@ -18,7 +18,7 @@ struct Throttled_Latest_only::Private : Prev_Private {
|
||||
sending
|
||||
};
|
||||
struct Frame_Slot {
|
||||
proxy<frame_policy::Frame> frame; /* 帧池在本槽唯一拥有并循环复用的物理帧。 */
|
||||
proxy<scene::Frame> frame; /* 帧池在本槽唯一拥有并循环复用的物理帧。 */
|
||||
Frame_Phase phase{Frame_Phase::idle}; /* 本槽当前用途的唯一权威状态。 */
|
||||
std::chrono::steady_clock::time_point render_start; /* 当前帧开始渲染的单调时刻。 */
|
||||
std::chrono::steady_clock::time_point send_start; /* 当前帧开始发送的单调时刻。 */
|
||||
@@ -35,10 +35,10 @@ private:
|
||||
void cancel_failed();
|
||||
void timer_cancelled();
|
||||
void frame_due();
|
||||
void record_render_submission(frame_policy::Render_Scene_Result result);
|
||||
void rendered(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame);
|
||||
void record_render_submission(scene::Render_Result result);
|
||||
void rendered(std::size_t slot_index, proxy<scene::Frame>& completed_frame);
|
||||
void dispatch_send(std::size_t slot_index);
|
||||
void sent(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame);
|
||||
void sent(std::size_t slot_index, proxy<scene::Frame>& completed_frame);
|
||||
void abandon_render(std::size_t slot_index);
|
||||
void abandon_send(std::size_t slot_index);
|
||||
void finish_stop_if_ready();
|
||||
|
||||
@@ -18,12 +18,12 @@ namespace {
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
struct Pending_Frame {
|
||||
std::reference_wrapper<aethera::proxy<aethera::frame_policy::Frame>> frame; /* 被 Scene/Sink 借用的策略帧。 */
|
||||
std::reference_wrapper<aethera::proxy<aethera::scene::Frame>> frame; /* 被 Scene/Sink 借用的策略帧。 */
|
||||
aethera::frame_policy::Frame_Completion completion; /* 归还同一帧的完成回调所有权。 */
|
||||
};
|
||||
struct Pending_Render_Frame {
|
||||
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. */
|
||||
std::reference_wrapper<aethera::proxy<aethera::scene::Frame>> frame; /* Borrowed policy frame; the policy outlives the pending render. */
|
||||
aethera::scene::Render_Completion completion; /* Owns the render completion until this frame is returned. */
|
||||
};
|
||||
|
||||
struct Frame_Policy_Test_Control {
|
||||
@@ -55,7 +55,7 @@ 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::frame_policy::Frame>*>
|
||||
std::vector<aethera::proxy<aethera::scene::Frame>*>
|
||||
rendered_frame_addresses; /* 可空、非拥有;记录每次渲染采用的槽位地址。 */
|
||||
aethera::frame_policy::Timer_Callback timer_callback; /* 周期服务持有的到期回调。 */
|
||||
aethera::frame_policy::Timer_Callback cancel_completion; /* 测试显式完成异步取消的回调。 */
|
||||
@@ -66,7 +66,7 @@ struct Frame_Policy_Test_Control {
|
||||
std::size_t frames_alive{}; /* 尚未析构的物理帧数量。 */
|
||||
std::size_t render_calls{}; /* Scene 已接受的渲染借用次数。 */
|
||||
std::size_t send_calls{}; /* Sink 已接受的发送借用次数。 */
|
||||
aethera::frame_policy::Render_Scene_Result render_result{aethera::frame_policy::Render_Scene_Result::submitted}; /* Scene 对下一次及后续渲染提交返回的结果。 */
|
||||
aethera::scene::Render_Result render_result{}; /* 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 内立即归还帧。 */
|
||||
@@ -122,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::frame_policy::Frame> create_frame() {
|
||||
aethera::proxy<aethera::scene::Frame> create_frame() {
|
||||
++control->frames_created;
|
||||
return pro::make_proxy<aethera::frame_policy::Frame, Test_Frame>(control);
|
||||
return pro::make_proxy<aethera::scene::Frame, Test_Frame>(control);
|
||||
}
|
||||
|
||||
aethera::frame_policy::Render_Scene_Result render(aethera::proxy<aethera::frame_policy::Frame>& frame, aethera::frame_policy::Render_Completion completion) {
|
||||
aethera::scene::Render_Result render(aethera::proxy<aethera::scene::Frame>& frame, aethera::scene::Render_Completion completion) {
|
||||
control->rendered_frame_addresses.push_back(std::addressof(frame));
|
||||
++control->render_calls;
|
||||
if (control->render_result != aethera::frame_policy::Render_Scene_Result::submitted) return control->render_result;
|
||||
if (!control->render_result) return control->render_result;
|
||||
if (control->complete_render_synchronously) {
|
||||
completion(frame, std::exchange(control->render_failure, {}));
|
||||
return aethera::frame_policy::Render_Scene_Result::submitted;
|
||||
return {};
|
||||
}
|
||||
control->rendering_frames.push_back({frame, std::move(completion)});
|
||||
return aethera::frame_policy::Render_Scene_Result::submitted;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<Frame_Policy_Test_Control> control; /* Scene 测试行为的共享控制数据。 */
|
||||
@@ -150,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::frame_policy::Frame>& frame, aethera::frame_policy::Frame_Completion completion) {
|
||||
void send(aethera::proxy<aethera::scene::Frame>& frame, aethera::frame_policy::Frame_Completion completion) {
|
||||
++control->send_calls;
|
||||
if (control->throw_from_send) {
|
||||
throw std::runtime_error("test sink failure");
|
||||
@@ -183,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::frame_policy::Render_Scene_Result::runtime_not_initialized;
|
||||
control->render_result = std::unexpected(aethera::scene::Render_Error{aethera::Run_Taskflow_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::frame_policy::Render_Scene_Result::submitted;
|
||||
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(std::get<aethera::Run_Taskflow_Result>(*state.render_submission_error), aethera::Run_Taskflow_Result::runtime_not_initialized); });
|
||||
control->render_result = aethera::scene::Render_Result{};
|
||||
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();
|
||||
@@ -432,7 +432,7 @@ TEST(Throttled_Latest_Only, Reports_Invalid_Configuration) {
|
||||
auto missing_dependencies = missing_builder.build();
|
||||
const auto missing_start = missing_dependencies->start();
|
||||
ASSERT_FALSE(missing_start);
|
||||
EXPECT_EQ(missing_start.error(), aethera::Throttled_Latest_only::Start_Result::dependency_unavailable);
|
||||
EXPECT_EQ(std::get<aethera::Throttled_Latest_only::Start_State_Result>(missing_start.error()), aethera::Throttled_Latest_only::Start_State_Result::dependency_unavailable);
|
||||
const auto missing_stop = missing_dependencies->stop({});
|
||||
ASSERT_FALSE(missing_stop);
|
||||
EXPECT_EQ(missing_stop.error(), aethera::Throttled_Latest_only::Stop_Result::completion_missing);
|
||||
@@ -441,20 +441,20 @@ TEST(Throttled_Latest_Only, Reports_Invalid_Configuration) {
|
||||
auto policy = make_policy(control, 0.0);
|
||||
const auto invalid_rate = policy->start();
|
||||
ASSERT_FALSE(invalid_rate);
|
||||
EXPECT_EQ(invalid_rate.error(), aethera::Throttled_Latest_only::Start_Result::invalid_user_frame_rate);
|
||||
EXPECT_EQ(std::get<aethera::Frame_Policy_Configuration_Result>(invalid_rate.error()), aethera::Frame_Policy_Configuration_Result::invalid_user_frame_rate);
|
||||
auto tiny_rate = make_policy(control, std::numeric_limits<double>::denorm_min());
|
||||
const auto invalid_interval = tiny_rate->start();
|
||||
ASSERT_FALSE(invalid_interval);
|
||||
EXPECT_EQ(invalid_interval.error(), aethera::Throttled_Latest_only::Start_Result::interval_out_of_range);
|
||||
EXPECT_EQ(std::get<aethera::Frame_Policy_Configuration_Result>(invalid_interval.error()), aethera::Frame_Policy_Configuration_Result::interval_out_of_range);
|
||||
|
||||
auto invalid_statistics = make_policy(control, 60.0, false, false, true, 0);
|
||||
const auto invalid_statistics_result = invalid_statistics->start();
|
||||
ASSERT_FALSE(invalid_statistics_result);
|
||||
EXPECT_EQ(invalid_statistics_result.error(), aethera::Throttled_Latest_only::Start_Result::invalid_statistics_window);
|
||||
EXPECT_EQ(std::get<aethera::Frame_Policy_Configuration_Result>(invalid_statistics_result.error()), aethera::Frame_Policy_Configuration_Result::invalid_statistics_window);
|
||||
auto invalid_adaptive_window = make_policy(control, std::nullopt, true, false, false, 0);
|
||||
const auto invalid_adaptive_result = invalid_adaptive_window->start();
|
||||
ASSERT_FALSE(invalid_adaptive_result);
|
||||
EXPECT_EQ(invalid_adaptive_result.error(), aethera::Throttled_Latest_only::Start_Result::invalid_statistics_window);
|
||||
EXPECT_EQ(std::get<aethera::Frame_Policy_Configuration_Result>(invalid_adaptive_result.error()), aethera::Frame_Policy_Configuration_Result::invalid_statistics_window);
|
||||
}
|
||||
|
||||
TEST(Throttled_Latest_Only, Render_Failure_Releases_Slot_Before_It_Escapes) {
|
||||
|
||||
@@ -16,11 +16,11 @@ aethera_kernel_add_module(Aethera_Kernel_Time_Thread time_thread)
|
||||
add_library(Aethera_Kernel::Time_Thread ALIAS Aethera_Kernel_Time_Thread)
|
||||
target_link_libraries(Aethera_Kernel_Time_Thread PRIVATE Threads::Threads)
|
||||
|
||||
aethera_kernel_add_module(Aethera_Kernel_Frame_Policy frame_policy Aethera_Kernel_Time_Thread)
|
||||
add_library(Aethera_Kernel::Frame_Policy ALIAS Aethera_Kernel_Frame_Policy)
|
||||
|
||||
aethera_kernel_add_module(Aethera_Kernel_Renderable renderable Aethera_Kernel_Task_Flow)
|
||||
add_library(Aethera_Kernel::Renderable ALIAS Aethera_Kernel_Renderable)
|
||||
|
||||
aethera_kernel_add_module(Aethera_Kernel_Scene scene Aethera_Kernel_Task_Flow Aethera_Kernel_Renderable Aethera_Kernel_Frame_Policy)
|
||||
aethera_kernel_add_module(Aethera_Kernel_Scene scene Aethera_Kernel_Task_Flow)
|
||||
add_library(Aethera_Kernel::Scene ALIAS Aethera_Kernel_Scene)
|
||||
|
||||
aethera_kernel_add_module(Aethera_Kernel_Frame_Policy frame_policy Aethera_Kernel_Time_Thread)
|
||||
add_library(Aethera_Kernel::Frame_Policy ALIAS Aethera_Kernel_Frame_Policy)
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
#pragma once
|
||||
#include "global.hpp"
|
||||
#include "task_flow/export/export.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace aethera {
|
||||
enum struct Scene_Create_Taskflow_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed, dependency_unavailable };
|
||||
} // namespace aethera
|
||||
#include <exception>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <proxy/proxy.h>
|
||||
#include <variant>
|
||||
namespace aethera::scene {
|
||||
struct Frame : facade_builder::build {};
|
||||
using Render_Completion = std::function<void(proxy<Frame>&, std::exception_ptr)>;
|
||||
enum struct Render_State_Result : std::uint8_t {
|
||||
frame_missing,
|
||||
completion_missing
|
||||
};
|
||||
using Render_Error = std::variant<Render_State_Result, Compose_Task_Graph_Result, Precede_Task_Graph_Result, Run_Taskflow_Result>;
|
||||
using Render_Result = std::expected<void, Render_Error>;
|
||||
} // namespace aethera::scene
|
||||
|
||||
@@ -9,42 +9,36 @@ struct Scene_Frame {};
|
||||
}
|
||||
proxy<scene::Task_Graph> scene::make_task_graph(std::string name) { return task_flow::make_task_graph(std::move(name)); }
|
||||
Scene::Private::Private() = default;
|
||||
proxy<frame_policy::Frame> Scene::Private::create_frame() {
|
||||
return pro::make_proxy<frame_policy::Frame, detail::Scene_Frame>();
|
||||
proxy<scene::Frame> Scene::Private::create_frame() {
|
||||
return pro::make_proxy<scene::Frame, detail::Scene_Frame>();
|
||||
}
|
||||
proxy<scene::Task_Graph>& Scene::Private::taskflow() {
|
||||
return graph;
|
||||
}
|
||||
auto Scene::Private::create_taskflow() -> std::expected<proxy<scene::Task_Graph>, Create_Taskflow_Result> {
|
||||
auto Scene::Private::create_taskflow() -> std::expected<proxy<scene::Task_Graph>, Create_Taskflow_Error> {
|
||||
auto result = scene::make_task_graph("scene.renderables");
|
||||
std::unordered_map<std::uint64_t, proxy<task_flow::Task_Node>> nodes;
|
||||
auto& renderables = get<Scene_Renderable_Dag>().internal.use();
|
||||
std::optional<Create_Taskflow_Result> error;
|
||||
std::optional<Create_Taskflow_Error> error;
|
||||
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
|
||||
if (error) return;
|
||||
auto component = renderable->taskflow();
|
||||
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;
|
||||
}
|
||||
error = composed.error();
|
||||
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;
|
||||
const auto dependency = result->precede(nodes.at(source.value), nodes.at(target.value));
|
||||
if (dependency != Precede_Task_Graph_Result::preceded) error = dependency;
|
||||
});
|
||||
if (error) return std::unexpected(*error);
|
||||
return result;
|
||||
}
|
||||
auto Scene::Private::advance() -> std::expected<void, Create_Taskflow_Result> {
|
||||
auto Scene::Private::advance() -> std::expected<void, Create_Taskflow_Error> {
|
||||
auto& renderables = get<Scene_Renderable_Dag>();
|
||||
renderables.internal.advance();
|
||||
renderables.internal.use().for_each_topological([](Dag_Node_Id, proxy<scene::Renderable>& renderable) {
|
||||
@@ -55,20 +49,16 @@ auto Scene::Private::advance() -> std::expected<void, Create_Taskflow_Result> {
|
||||
graph = std::move(*next_graph);
|
||||
return {};
|
||||
}
|
||||
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;
|
||||
scene::Render_Result Scene::Private::render(proxy<scene::Frame>& frame, scene::Render_Completion completion) {
|
||||
if (!frame) return std::unexpected(scene::Render_Error{scene::Render_State_Result::frame_missing});
|
||||
if (!completion) return std::unexpected(scene::Render_Error{scene::Render_State_Result::completion_missing});
|
||||
const auto advanced = advance();
|
||||
if (!advanced) return std::unexpected(std::visit([](auto error) -> scene::Render_Error { return error; }, advanced.error()));
|
||||
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_Taskflow_Result::submitted: return frame_policy::Render_Scene_Result::submitted;
|
||||
case Run_Taskflow_Result::runtime_not_initialized: return frame_policy::Render_Scene_Result::runtime_not_initialized;
|
||||
case Run_Taskflow_Result::completion_missing: return frame_policy::Render_Scene_Result::completion_missing;
|
||||
case Run_Taskflow_Result::observation_unavailable: std::terminate();
|
||||
}
|
||||
std::terminate();
|
||||
if (result != Run_Taskflow_Result::submitted) return std::unexpected(scene::Render_Error{result});
|
||||
return {};
|
||||
}
|
||||
Scene::Scene() = default;
|
||||
Scene::~Scene() noexcept = default;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
#include "../export/export.h"
|
||||
#include "frame_policy/rely_facade.h"
|
||||
#include "model/Model.hpp"
|
||||
#include "relation/Dag.hpp"
|
||||
#include <functional>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#pragma once
|
||||
#include <variant>
|
||||
namespace aethera {
|
||||
struct Scene::Private : Prev_Private {
|
||||
using Create_Taskflow_Result = Scene_Create_Taskflow_Result;
|
||||
using Create_Taskflow_Error = std::variant<Compose_Task_Graph_Result, Precede_Task_Graph_Result>;
|
||||
Private();
|
||||
proxy<frame_policy::Frame> create_frame();
|
||||
frame_policy::Render_Scene_Result render(proxy<frame_policy::Frame>& frame, frame_policy::Render_Completion completion);
|
||||
proxy<scene::Frame> create_frame();
|
||||
scene::Render_Result render(proxy<scene::Frame>& frame, scene::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>;
|
||||
auto create_taskflow() -> std::expected<proxy<scene::Task_Graph>, Create_Taskflow_Error>;
|
||||
auto advance() -> std::expected<void, Create_Taskflow_Error>;
|
||||
private:
|
||||
proxy<scene::Task_Graph> graph{scene::make_task_graph("scene")}; /* 唯一拥有当前组合任务图的跨模块代理。 */
|
||||
};
|
||||
|
||||
@@ -19,8 +19,6 @@ struct Test_Dag_Renderable : aethera::Def<Test_Dag_Renderable, aethera::Renderab
|
||||
int id{};
|
||||
};
|
||||
};
|
||||
static_assert(pro::proxiable<aethera::detail::Model_Private_Pointer<aethera::Scene*>, aethera::frame_policy::Scene>);
|
||||
|
||||
TEST(Scene, Advances_Renderables_And_Composes_Task_Graph_In_Dag_Order) {
|
||||
auto advance_order = std::make_shared<std::vector<int>>();
|
||||
aethera::Dag<aethera::scene::Renderable>::Builder builder;
|
||||
@@ -81,11 +79,17 @@ TEST(Scene, Render_Returns_Known_Submission_Result) {
|
||||
aethera::Scene scene;
|
||||
auto frame = scene.d->create_frame();
|
||||
bool completed{};
|
||||
EXPECT_EQ(scene.d->render(frame, [&](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) { completed = true; }), aethera::frame_policy::Render_Scene_Result::runtime_not_initialized);
|
||||
const auto unavailable_runtime = scene.d->render(frame, [&](aethera::proxy<aethera::scene::Frame>&, std::exception_ptr) { completed = true; });
|
||||
ASSERT_FALSE(unavailable_runtime);
|
||||
EXPECT_EQ(std::get<aethera::Run_Taskflow_Result>(unavailable_runtime.error()), aethera::Run_Taskflow_Result::runtime_not_initialized);
|
||||
EXPECT_FALSE(completed);
|
||||
aethera::proxy<aethera::frame_policy::Frame> empty_frame;
|
||||
EXPECT_EQ(scene.d->render(empty_frame, [&](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) {}), aethera::frame_policy::Render_Scene_Result::frame_missing);
|
||||
EXPECT_EQ(scene.d->render(frame, {}), aethera::frame_policy::Render_Scene_Result::completion_missing);
|
||||
aethera::proxy<aethera::scene::Frame> empty_frame;
|
||||
const auto missing_frame = scene.d->render(empty_frame, [&](aethera::proxy<aethera::scene::Frame>&, std::exception_ptr) {});
|
||||
ASSERT_FALSE(missing_frame);
|
||||
EXPECT_EQ(std::get<aethera::scene::Render_State_Result>(missing_frame.error()), aethera::scene::Render_State_Result::frame_missing);
|
||||
const auto missing_completion = scene.d->render(frame, {});
|
||||
ASSERT_FALSE(missing_completion);
|
||||
EXPECT_EQ(std::get<aethera::scene::Render_State_Result>(missing_completion.error()), aethera::scene::Render_State_Result::completion_missing);
|
||||
}
|
||||
|
||||
TEST(Scene, Rejects_The_Same_Renderable_Task_Graph_Twice) {
|
||||
@@ -98,6 +102,8 @@ TEST(Scene, Rejects_The_Same_Renderable_Task_Graph_Twice) {
|
||||
aethera::Scene scene;
|
||||
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
|
||||
auto frame = scene.d->create_frame();
|
||||
EXPECT_EQ(scene.d->render(frame, [](aethera::proxy<aethera::frame_policy::Frame>&, std::exception_ptr) {}), aethera::frame_policy::Render_Scene_Result::task_graph_invalid);
|
||||
const auto rendered = scene.d->render(frame, [](aethera::proxy<aethera::scene::Frame>&, std::exception_ptr) {});
|
||||
ASSERT_FALSE(rendered);
|
||||
EXPECT_EQ(std::get<aethera::Compose_Task_Graph_Result>(rendered.error()), aethera::Compose_Task_Graph_Result::already_composed);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -6,25 +6,70 @@
|
||||
#include <functional>
|
||||
#include <proxy/proxy.h>
|
||||
#include <string>
|
||||
|
||||
namespace aethera {
|
||||
enum struct Add_Task_Graph_Result : std::uint8_t { work_missing };
|
||||
enum struct Add_Condition_Task_Graph_Result : std::uint8_t { condition_missing };
|
||||
enum struct Compose_Task_Graph_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed };
|
||||
enum struct Precede_Task_Graph_Result : std::uint8_t { preceded, node_unavailable, node_stale, different_graph };
|
||||
enum struct Describe_Task_Graph_Result : std::uint8_t { described, key_missing };
|
||||
enum struct Describe_Task_Graph_Node_Result : std::uint8_t { described, node_unavailable, node_stale, key_missing };
|
||||
enum struct Run_Taskflow_Result : std::uint8_t { submitted, runtime_not_initialized, completion_missing, observation_unavailable };
|
||||
enum struct Taskflow_Unknown_Failure_Policy : std::uint8_t { fast_fail, exception };
|
||||
enum struct Initialize_Task_Runtime_Result : std::uint8_t { initialized, invalid_worker_count, exception_boundary_missing, exception_boundary_unexpected, already_initialized, initialization_in_progress };
|
||||
enum struct Take_Taskflow_Observation_Result : std::uint8_t { not_recorded, recording, already_taken };
|
||||
enum struct Add_Task_Graph_Result : std::uint8_t {
|
||||
work_missing
|
||||
};
|
||||
enum struct Add_Condition_Task_Graph_Result : std::uint8_t {
|
||||
condition_missing
|
||||
};
|
||||
enum struct Compose_Task_Graph_Result : std::uint8_t {
|
||||
component_unavailable,
|
||||
dependency_cycle,
|
||||
already_composed
|
||||
};
|
||||
enum struct Precede_Task_Graph_Result : std::uint8_t {
|
||||
preceded,
|
||||
node_unavailable,
|
||||
node_stale,
|
||||
different_graph
|
||||
};
|
||||
enum struct Describe_Task_Graph_Result : std::uint8_t {
|
||||
described,
|
||||
key_missing
|
||||
};
|
||||
enum struct Describe_Task_Graph_Node_Result : std::uint8_t {
|
||||
described,
|
||||
node_unavailable,
|
||||
node_stale,
|
||||
key_missing
|
||||
};
|
||||
enum struct Run_Taskflow_Result : std::uint8_t {
|
||||
submitted,
|
||||
runtime_not_initialized,
|
||||
completion_missing,
|
||||
observation_unavailable
|
||||
};
|
||||
enum struct Taskflow_Unknown_Failure_Policy : std::uint8_t {
|
||||
fast_fail,
|
||||
exception
|
||||
};
|
||||
enum struct Initialize_Task_Runtime_Result : std::uint8_t {
|
||||
initialized,
|
||||
invalid_worker_count,
|
||||
exception_boundary_missing,
|
||||
exception_boundary_unexpected,
|
||||
already_initialized,
|
||||
initialization_in_progress
|
||||
};
|
||||
enum struct Take_Taskflow_Observation_Result : std::uint8_t {
|
||||
not_recorded,
|
||||
recording,
|
||||
already_taken
|
||||
};
|
||||
using Taskflow_Completion = std::function<void(std::exception_ptr)>;
|
||||
using Taskflow_Exception_Boundary = std::function<void(std::exception_ptr)>;
|
||||
namespace task_flow {
|
||||
struct Task_Node : facade_builder::add_skill<pro::skills::rtti>::build {};
|
||||
struct Task_Graph_Component : facade_builder::add_skill<pro::skills::rtti>::build {};
|
||||
struct Task_Graph_Common_Facade : facade_builder::add_convention<_component, proxy<Task_Graph_Component>()>::add_convention<_empty, bool() const noexcept>::support_relocation<pro::constraint_level::nothrow>::build {};
|
||||
struct Task_Graph_Build_Facade : facade_builder::add_facade_with_substitution<Task_Graph_Common_Facade>::add_convention<_add, std::expected<proxy<Task_Node>, Add_Task_Graph_Result>(std::string, std::function<void()>)>::add_convention<_clear, void()>::build {};
|
||||
struct Task_Graph_Common_Facade : facade_builder
|
||||
::add_convention<_component, proxy<Task_Graph_Component>()>
|
||||
::add_convention<_empty, bool() const noexcept>
|
||||
::support_relocation<pro::constraint_level::nothrow>
|
||||
::build {};
|
||||
struct Task_Graph_Build_Facade : facade_builder::add_facade_with_substitution<Task_Graph_Common_Facade>
|
||||
::add_convention<_add, std::expected<proxy<Task_Node>, Add_Task_Graph_Result>(std::string, std::function<void()>)>
|
||||
::add_convention<_clear, void()>::build {};
|
||||
struct Task_Graph_Compose_Facade : facade_builder::add_facade_with_substitution<Task_Graph_Common_Facade>::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<_run, Run_Taskflow_Result(Taskflow_Completion)>::build {};
|
||||
struct Task_Graph_Facade : facade_builder::add_facade_with_substitution<Task_Graph_Build_Facade>::add_facade_with_substitution<Task_Graph_Compose_Facade>::build {};
|
||||
proxy<Task_Graph_Facade> make_task_graph(std::string name);
|
||||
|
||||
Reference in New Issue
Block a user