更新
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
函数定义 函数调用 等参数不允许换行 注意所有facade用到的函数名都定义在这里 D:
|
函数定义 函数调用 等参数不允许换行 注意所有facade用到的函数名都定义在这里 D:
|
||||||
\ae\proj\Aethera\kernel\kernel\include\global.hpp 这样重复的名称不用重复写 光写facade即可
|
\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"
|
我使用 里的这个环境 D:\ae\proj\Aethera\CMakePresets.json "toolchain/vs2022.json"
|
||||||
====================[ 构建 | Aethera_Kernel_check | vs2022_debug ]================
|
====================[ 构建 | Aethera_Kernel_check | vs2022_debug ]================
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <variant>
|
||||||
namespace aethera {
|
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_Configuration_Result : std::uint8_t {
|
||||||
enum struct Frame_Policy_Render_Submission_Result : std::uint8_t { runtime_not_initialized, task_graph_invalid };
|
invalid_user_frame_rate,
|
||||||
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 };
|
interval_out_of_range,
|
||||||
enum struct Throttled_Latest_Only_Stop_Result : std::uint8_t { already_stopped, already_stopping, start_in_progress, completion_missing };
|
invalid_statistics_window
|
||||||
namespace frame_policy {
|
};
|
||||||
enum struct Render_Scene_Result : std::uint8_t { submitted, runtime_not_initialized, task_graph_invalid, frame_missing, completion_missing };
|
enum struct Throttled_Latest_Only_Start_State_Result : std::uint8_t {
|
||||||
} // namespace frame_policy
|
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
|
} // namespace aethera
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "export/export.h"
|
#include "export/export.h"
|
||||||
#include "global.hpp"
|
#include "global.hpp"
|
||||||
|
#include "scene/export/export.h"
|
||||||
#include "time_thread/export/export.h"
|
#include "time_thread/export/export.h"
|
||||||
#include <exception>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <proxy/proxy.h>
|
#include <proxy/proxy.h>
|
||||||
|
|
||||||
namespace aethera::frame_policy {
|
namespace aethera::frame_policy {
|
||||||
using Timer_Id = aethera::Timer_Id;
|
using Timer_Id = aethera::Timer_Id;
|
||||||
using Timer_Callback = aethera::Timer_Callback;
|
using Timer_Callback = aethera::Timer_Callback;
|
||||||
struct Frame : facade_builder::build {};
|
using Frame_Completion = std::function<void(proxy<scene::Frame>&)>;
|
||||||
using Frame_Completion = std::function<void(proxy<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 {};
|
||||||
using Render_Completion = std::function<void(proxy<Frame>&, std::exception_ptr)>;
|
struct Sink : facade_builder::add_convention<_send, void(proxy<scene::Frame>&, Frame_Completion)>::support_relocation<pro::constraint_level::nothrow>::build {};
|
||||||
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 Timer_Service = time_thread::Timer_Service_Frame_Facade;
|
using Timer_Service = time_thread::Timer_Service_Frame_Facade;
|
||||||
proxy<Timer_Service> make_timer_service();
|
proxy<Timer_Service> make_timer_service();
|
||||||
} // namespace aethera::frame_policy
|
} // namespace aethera::frame_policy
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../export/export.h"
|
#include "../export/export.h"
|
||||||
|
#include "scene/export/export.h"
|
||||||
#include "concurrent/base/Concurrent_Struct.hpp"
|
#include "concurrent/base/Concurrent_Struct.hpp"
|
||||||
#include "model/Model.hpp"
|
#include "model/Model.hpp"
|
||||||
#include "statistics/Sliding_Statistics.hpp"
|
#include "statistics/Sliding_Statistics.hpp"
|
||||||
@@ -35,7 +36,7 @@ struct Frame_Policy_State {
|
|||||||
Statistics_Summary send_time_ns; /* 发送耗时滑动统计,单位纳秒。 */
|
Statistics_Summary send_time_ns; /* 发送耗时滑动统计,单位纳秒。 */
|
||||||
Statistics_Summary end_to_end_time_ns; /* 渲染开始至发送完成耗时统计,单位纳秒。 */
|
Statistics_Summary end_to_end_time_ns; /* 渲染开始至发送完成耗时统计,单位纳秒。 */
|
||||||
std::optional<Frame_Policy_Configuration_Result> configuration_error; /* 动态配置无效时记录原因;有效时为空。 */
|
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
|
} // namespace aethera
|
||||||
#include "Frame_Policy.ipp"
|
#include "Frame_Policy.ipp"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "Throttled_Latest_Only.hpp"
|
#include "Throttled_Latest_Only.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <concepts>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
namespace aethera {
|
namespace aethera {
|
||||||
@@ -7,16 +8,6 @@ namespace {
|
|||||||
auto positive_duration(std::chrono::steady_clock::duration duration) noexcept -> std::chrono::nanoseconds {
|
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));
|
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
|
} // 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(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 {
|
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);
|
std::lock_guard lock(mutex);
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case Phase::running: return std::unexpected(Start_Result::already_running);
|
case Phase::running: return std::unexpected(Start_Result{Start_State_Result::already_running});
|
||||||
case Phase::starting: return std::unexpected(Start_Result::start_in_progress);
|
case Phase::starting: return std::unexpected(Start_Result{Start_State_Result::start_in_progress});
|
||||||
case Phase::stopping: return std::unexpected(Start_Result::stop_in_progress);
|
case Phase::stopping: return std::unexpected(Start_Result{Start_State_Result::stop_in_progress});
|
||||||
case Phase::stopped: break;
|
case Phase::stopped: break;
|
||||||
}
|
}
|
||||||
if (!timer_service || !scene || !sink) {
|
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;
|
phase = Phase::starting;
|
||||||
}
|
}
|
||||||
std::array<proxy<frame_policy::Frame>, Frame_Count> created_frames;
|
std::array<proxy<scene::Frame>, Frame_Count> created_frames;
|
||||||
try {
|
try {
|
||||||
for (auto& created_frame : created_frames) {
|
for (auto& created_frame : created_frames) {
|
||||||
created_frame = scene->create_frame();
|
created_frame = scene->create_frame();
|
||||||
if (!created_frame) {
|
if (!created_frame) {
|
||||||
rollback_start();
|
rollback_start();
|
||||||
return std::unexpected(Start_Result::frame_unavailable);
|
return std::unexpected(Start_Result{Start_State_Result::frame_unavailable});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
@@ -84,7 +75,7 @@ auto Throttled_Latest_only::Private::prepare_start() -> std::expected<std::chron
|
|||||||
publish_state();
|
publish_state();
|
||||||
if (!interval) {
|
if (!interval) {
|
||||||
phase = Phase::stopped;
|
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) {
|
for (std::size_t index = 0; index < Frame_Count; ++index) {
|
||||||
if (frames[index].frame) {
|
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() {
|
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);
|
std::lock_guard lock(mutex);
|
||||||
if (phase != Phase::starting || timer_id) {
|
if (phase != Phase::starting || timer_id) {
|
||||||
@@ -220,7 +211,7 @@ void Throttled_Latest_only::Private::frame_due() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
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) {
|
if (failure) {
|
||||||
abandon_render(selected_index);
|
abandon_render(selected_index);
|
||||||
std::rethrow_exception(failure);
|
std::rethrow_exception(failure);
|
||||||
@@ -228,29 +219,30 @@ void Throttled_Latest_only::Private::frame_due() {
|
|||||||
rendered(selected_index, completed_frame);
|
rendered(selected_index, completed_frame);
|
||||||
});
|
});
|
||||||
record_render_submission(result);
|
record_render_submission(result);
|
||||||
if (result != frame_policy::Render_Scene_Result::submitted) abandon_render(selected_index);
|
if (!result) abandon_render(selected_index);
|
||||||
}
|
}
|
||||||
catch (...) {
|
catch (...) {
|
||||||
abandon_render(selected_index);
|
abandon_render(selected_index);
|
||||||
throw;
|
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);
|
std::lock_guard lock(mutex);
|
||||||
auto& state = *get<Frame_Policy_State>().internal.use();
|
auto& state = *get<Frame_Policy_State>().internal.use();
|
||||||
switch (result) {
|
if (result) state.render_submission_error.reset();
|
||||||
case frame_policy::Render_Scene_Result::submitted: state.render_submission_error.reset();
|
else {
|
||||||
break;
|
const bool impossible = std::visit([](auto error) {
|
||||||
case frame_policy::Render_Scene_Result::runtime_not_initialized: state.render_submission_error = Frame_Policy_Render_Submission_Result::runtime_not_initialized;
|
using Error = decltype(error);
|
||||||
break;
|
if constexpr (std::same_as<Error, scene::Render_State_Result>) return true;
|
||||||
case frame_policy::Render_Scene_Result::task_graph_invalid: state.render_submission_error = Frame_Policy_Render_Submission_Result::task_graph_invalid;
|
if constexpr (std::same_as<Error, Run_Taskflow_Result>) return error == Run_Taskflow_Result::completion_missing || error == Run_Taskflow_Result::observation_unavailable;
|
||||||
break;
|
return false;
|
||||||
case frame_policy::Render_Scene_Result::frame_missing:
|
}, result.error());
|
||||||
case frame_policy::Render_Scene_Result::completion_missing: std::terminate();
|
if (impossible) std::terminate();
|
||||||
|
state.render_submission_error = result.error();
|
||||||
}
|
}
|
||||||
publish_state();
|
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;
|
bool dispatch = false;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mutex);
|
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) {
|
void Throttled_Latest_only::Private::dispatch_send(std::size_t slot_index) {
|
||||||
try {
|
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);
|
sent(slot_index, completed_frame);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -307,7 +299,7 @@ void Throttled_Latest_only::Private::dispatch_send(std::size_t slot_index) {
|
|||||||
throw;
|
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::size_t next_send_index = Frame_Count;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mutex);
|
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();
|
finish_stop_if_ready();
|
||||||
}
|
}
|
||||||
void Throttled_Latest_only::Private::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;
|
Stop_Completion completion;
|
||||||
{
|
{
|
||||||
std::lock_guard lock(mutex);
|
std::lock_guard lock(mutex);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace aethera {
|
|||||||
*/
|
*/
|
||||||
struct Throttled_Latest_only : Def<Throttled_Latest_only, Frame_Policy> {
|
struct Throttled_Latest_only : Def<Throttled_Latest_only, Frame_Policy> {
|
||||||
using Start_Result = Throttled_Latest_Only_Start_Result;
|
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_Result = Throttled_Latest_Only_Stop_Result;
|
||||||
using Stop_Completion = std::function<void()>;
|
using Stop_Completion = std::function<void()>;
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ struct Throttled_Latest_only::Private : Prev_Private {
|
|||||||
sending
|
sending
|
||||||
};
|
};
|
||||||
struct Frame_Slot {
|
struct Frame_Slot {
|
||||||
proxy<frame_policy::Frame> frame; /* 帧池在本槽唯一拥有并循环复用的物理帧。 */
|
proxy<scene::Frame> frame; /* 帧池在本槽唯一拥有并循环复用的物理帧。 */
|
||||||
Frame_Phase phase{Frame_Phase::idle}; /* 本槽当前用途的唯一权威状态。 */
|
Frame_Phase phase{Frame_Phase::idle}; /* 本槽当前用途的唯一权威状态。 */
|
||||||
std::chrono::steady_clock::time_point render_start; /* 当前帧开始渲染的单调时刻。 */
|
std::chrono::steady_clock::time_point render_start; /* 当前帧开始渲染的单调时刻。 */
|
||||||
std::chrono::steady_clock::time_point send_start; /* 当前帧开始发送的单调时刻。 */
|
std::chrono::steady_clock::time_point send_start; /* 当前帧开始发送的单调时刻。 */
|
||||||
@@ -35,10 +35,10 @@ private:
|
|||||||
void cancel_failed();
|
void cancel_failed();
|
||||||
void timer_cancelled();
|
void timer_cancelled();
|
||||||
void frame_due();
|
void frame_due();
|
||||||
void record_render_submission(frame_policy::Render_Scene_Result result);
|
void record_render_submission(scene::Render_Result result);
|
||||||
void rendered(std::size_t slot_index, proxy<frame_policy::Frame>& completed_frame);
|
void rendered(std::size_t slot_index, proxy<scene::Frame>& completed_frame);
|
||||||
void dispatch_send(std::size_t slot_index);
|
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_render(std::size_t slot_index);
|
||||||
void abandon_send(std::size_t slot_index);
|
void abandon_send(std::size_t slot_index);
|
||||||
void finish_stop_if_ready();
|
void finish_stop_if_ready();
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ namespace {
|
|||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
struct Pending_Frame {
|
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; /* 归还同一帧的完成回调所有权。 */
|
aethera::frame_policy::Frame_Completion completion; /* 归还同一帧的完成回调所有权。 */
|
||||||
};
|
};
|
||||||
struct Pending_Render_Frame {
|
struct Pending_Render_Frame {
|
||||||
std::reference_wrapper<aethera::proxy<aethera::frame_policy::Frame>> frame; /* Borrowed policy frame; the policy outlives the pending render. */
|
std::reference_wrapper<aethera::proxy<aethera::scene::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. */
|
aethera::scene::Render_Completion completion; /* Owns the render completion until this frame is returned. */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Frame_Policy_Test_Control {
|
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_Render_Frame> rendering_frames; /* Scene 尚未归还的帧及其回调。 */
|
||||||
std::deque<Pending_Frame> sending_frames; /* Sink 尚未归还的帧及其回调。 */
|
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; /* 可空、非拥有;记录每次渲染采用的槽位地址。 */
|
rendered_frame_addresses; /* 可空、非拥有;记录每次渲染采用的槽位地址。 */
|
||||||
aethera::frame_policy::Timer_Callback timer_callback; /* 周期服务持有的到期回调。 */
|
aethera::frame_policy::Timer_Callback timer_callback; /* 周期服务持有的到期回调。 */
|
||||||
aethera::frame_policy::Timer_Callback cancel_completion; /* 测试显式完成异步取消的回调。 */
|
aethera::frame_policy::Timer_Callback cancel_completion; /* 测试显式完成异步取消的回调。 */
|
||||||
@@ -66,7 +66,7 @@ struct Frame_Policy_Test_Control {
|
|||||||
std::size_t frames_alive{}; /* 尚未析构的物理帧数量。 */
|
std::size_t frames_alive{}; /* 尚未析构的物理帧数量。 */
|
||||||
std::size_t render_calls{}; /* Scene 已接受的渲染借用次数。 */
|
std::size_t render_calls{}; /* Scene 已接受的渲染借用次数。 */
|
||||||
std::size_t send_calls{}; /* Sink 已接受的发送借用次数。 */
|
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. */
|
std::exception_ptr render_failure{}; /* Failure delivered by the next render completion. */
|
||||||
bool complete_render_synchronously{}; /* Scene 是否在 render 内立即归还帧。 */
|
bool complete_render_synchronously{}; /* Scene 是否在 render 内立即归还帧。 */
|
||||||
bool complete_send_synchronously{}; /* Sink 是否在 send 内立即归还帧。 */
|
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 {
|
struct Test_Scene::Private : Prev_Private {
|
||||||
explicit Private(std::shared_ptr<Frame_Policy_Test_Control> control) : control(std::move(control)) {}
|
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;
|
++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->rendered_frame_addresses.push_back(std::addressof(frame));
|
||||||
++control->render_calls;
|
++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) {
|
if (control->complete_render_synchronously) {
|
||||||
completion(frame, std::exchange(control->render_failure, {}));
|
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)});
|
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 测试行为的共享控制数据。 */
|
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 {
|
struct Test_Sink::Private : Prev_Private {
|
||||||
explicit Private(std::shared_ptr<Frame_Policy_Test_Control> control) : control(std::move(control)) {}
|
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;
|
++control->send_calls;
|
||||||
if (control->throw_from_send) {
|
if (control->throw_from_send) {
|
||||||
throw std::runtime_error("test sink failure");
|
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 control = std::make_shared<Frame_Policy_Test_Control>();
|
||||||
auto policy = make_policy(control);
|
auto policy = make_policy(control);
|
||||||
ASSERT_TRUE(policy->start().has_value());
|
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();
|
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); });
|
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::frame_policy::Render_Scene_Result::submitted;
|
control->render_result = aethera::scene::Render_Result{};
|
||||||
control->fire_timer();
|
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()); });
|
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();
|
control->complete_render();
|
||||||
@@ -432,7 +432,7 @@ TEST(Throttled_Latest_Only, Reports_Invalid_Configuration) {
|
|||||||
auto missing_dependencies = missing_builder.build();
|
auto missing_dependencies = missing_builder.build();
|
||||||
const auto missing_start = missing_dependencies->start();
|
const auto missing_start = missing_dependencies->start();
|
||||||
ASSERT_FALSE(missing_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({});
|
const auto missing_stop = missing_dependencies->stop({});
|
||||||
ASSERT_FALSE(missing_stop);
|
ASSERT_FALSE(missing_stop);
|
||||||
EXPECT_EQ(missing_stop.error(), aethera::Throttled_Latest_only::Stop_Result::completion_missing);
|
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);
|
auto policy = make_policy(control, 0.0);
|
||||||
const auto invalid_rate = policy->start();
|
const auto invalid_rate = policy->start();
|
||||||
ASSERT_FALSE(invalid_rate);
|
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());
|
auto tiny_rate = make_policy(control, std::numeric_limits<double>::denorm_min());
|
||||||
const auto invalid_interval = tiny_rate->start();
|
const auto invalid_interval = tiny_rate->start();
|
||||||
ASSERT_FALSE(invalid_interval);
|
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);
|
auto invalid_statistics = make_policy(control, 60.0, false, false, true, 0);
|
||||||
const auto invalid_statistics_result = invalid_statistics->start();
|
const auto invalid_statistics_result = invalid_statistics->start();
|
||||||
ASSERT_FALSE(invalid_statistics_result);
|
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);
|
auto invalid_adaptive_window = make_policy(control, std::nullopt, true, false, false, 0);
|
||||||
const auto invalid_adaptive_result = invalid_adaptive_window->start();
|
const auto invalid_adaptive_result = invalid_adaptive_window->start();
|
||||||
ASSERT_FALSE(invalid_adaptive_result);
|
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) {
|
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)
|
add_library(Aethera_Kernel::Time_Thread ALIAS Aethera_Kernel_Time_Thread)
|
||||||
target_link_libraries(Aethera_Kernel_Time_Thread PRIVATE Threads::Threads)
|
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)
|
aethera_kernel_add_module(Aethera_Kernel_Renderable renderable Aethera_Kernel_Task_Flow)
|
||||||
add_library(Aethera_Kernel::Renderable ALIAS Aethera_Kernel_Renderable)
|
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)
|
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
|
#pragma once
|
||||||
|
#include "global.hpp"
|
||||||
|
#include "task_flow/export/export.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
namespace aethera {
|
#include <expected>
|
||||||
enum struct Scene_Create_Taskflow_Result : std::uint8_t { component_unavailable, dependency_cycle, already_composed, dependency_unavailable };
|
#include <functional>
|
||||||
} // namespace aethera
|
#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)); }
|
proxy<scene::Task_Graph> scene::make_task_graph(std::string name) { return task_flow::make_task_graph(std::move(name)); }
|
||||||
Scene::Private::Private() = default;
|
Scene::Private::Private() = default;
|
||||||
proxy<frame_policy::Frame> Scene::Private::create_frame() {
|
proxy<scene::Frame> Scene::Private::create_frame() {
|
||||||
return pro::make_proxy<frame_policy::Frame, detail::Scene_Frame>();
|
return pro::make_proxy<scene::Frame, detail::Scene_Frame>();
|
||||||
}
|
}
|
||||||
proxy<scene::Task_Graph>& Scene::Private::taskflow() {
|
proxy<scene::Task_Graph>& Scene::Private::taskflow() {
|
||||||
return graph;
|
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");
|
auto result = scene::make_task_graph("scene.renderables");
|
||||||
std::unordered_map<std::uint64_t, proxy<task_flow::Task_Node>> nodes;
|
std::unordered_map<std::uint64_t, proxy<task_flow::Task_Node>> nodes;
|
||||||
auto& renderables = get<Scene_Renderable_Dag>().internal.use();
|
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) {
|
renderables.for_each_topological([&](Dag_Node_Id id, proxy<scene::Renderable>& renderable) {
|
||||||
if (error) return;
|
if (error) return;
|
||||||
auto component = renderable->taskflow();
|
auto component = renderable->taskflow();
|
||||||
auto composed = result->compose("renderable." + std::to_string(id.value), component);
|
auto composed = result->compose("renderable." + std::to_string(id.value), component);
|
||||||
if (!composed) {
|
if (!composed) {
|
||||||
switch (composed.error()) {
|
error = 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;
|
return;
|
||||||
}
|
}
|
||||||
nodes.emplace(id.value, std::move(*composed));
|
nodes.emplace(id.value, std::move(*composed));
|
||||||
});
|
});
|
||||||
if (error) return std::unexpected(*error);
|
if (error) return std::unexpected(*error);
|
||||||
renderables.for_each_dependency([&](Dag_Node_Id target, Dag_Node_Id source) {
|
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);
|
if (error) return std::unexpected(*error);
|
||||||
return result;
|
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>();
|
auto& renderables = get<Scene_Renderable_Dag>();
|
||||||
renderables.internal.advance();
|
renderables.internal.advance();
|
||||||
renderables.internal.use().for_each_topological([](Dag_Node_Id, proxy<scene::Renderable>& renderable) {
|
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);
|
graph = std::move(*next_graph);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
frame_policy::Render_Scene_Result Scene::Private::render(proxy<frame_policy::Frame>& frame, frame_policy::Render_Completion completion) {
|
scene::Render_Result Scene::Private::render(proxy<scene::Frame>& frame, scene::Render_Completion completion) {
|
||||||
if (!frame) return frame_policy::Render_Scene_Result::frame_missing;
|
if (!frame) return std::unexpected(scene::Render_Error{scene::Render_State_Result::frame_missing});
|
||||||
if (!completion) return frame_policy::Render_Scene_Result::completion_missing;
|
if (!completion) return std::unexpected(scene::Render_Error{scene::Render_State_Result::completion_missing});
|
||||||
if (!advance()) return frame_policy::Render_Scene_Result::task_graph_invalid;
|
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 {
|
const auto result = graph->run([frame = std::ref(frame), completion = std::move(completion)](std::exception_ptr failure) mutable {
|
||||||
completion(frame.get(), std::move(failure));
|
completion(frame.get(), std::move(failure));
|
||||||
});
|
});
|
||||||
switch (result) {
|
if (result != Run_Taskflow_Result::submitted) return std::unexpected(scene::Render_Error{result});
|
||||||
case Run_Taskflow_Result::submitted: return frame_policy::Render_Scene_Result::submitted;
|
return {};
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
Scene::Scene() = default;
|
Scene::Scene() = default;
|
||||||
Scene::~Scene() noexcept = default;
|
Scene::~Scene() noexcept = default;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "../export/export.h"
|
#include "../export/export.h"
|
||||||
#include "frame_policy/rely_facade.h"
|
|
||||||
#include "model/Model.hpp"
|
#include "model/Model.hpp"
|
||||||
#include "relation/Dag.hpp"
|
#include "relation/Dag.hpp"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <variant>
|
||||||
namespace aethera {
|
namespace aethera {
|
||||||
struct Scene::Private : Prev_Private {
|
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();
|
Private();
|
||||||
proxy<frame_policy::Frame> create_frame();
|
proxy<scene::Frame> create_frame();
|
||||||
frame_policy::Render_Scene_Result render(proxy<frame_policy::Frame>& frame, frame_policy::Render_Completion completion);
|
scene::Render_Result render(proxy<scene::Frame>& frame, scene::Render_Completion completion);
|
||||||
proxy<scene::Task_Graph>& taskflow();
|
proxy<scene::Task_Graph>& taskflow();
|
||||||
auto create_taskflow() -> std::expected<proxy<scene::Task_Graph>, Create_Taskflow_Result>;
|
auto create_taskflow() -> std::expected<proxy<scene::Task_Graph>, Create_Taskflow_Error>;
|
||||||
auto advance() -> std::expected<void, Create_Taskflow_Result>;
|
auto advance() -> std::expected<void, Create_Taskflow_Error>;
|
||||||
private:
|
private:
|
||||||
proxy<scene::Task_Graph> graph{scene::make_task_graph("scene")}; /* 唯一拥有当前组合任务图的跨模块代理。 */
|
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{};
|
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) {
|
TEST(Scene, Advances_Renderables_And_Composes_Task_Graph_In_Dag_Order) {
|
||||||
auto advance_order = std::make_shared<std::vector<int>>();
|
auto advance_order = std::make_shared<std::vector<int>>();
|
||||||
aethera::Dag<aethera::scene::Renderable>::Builder builder;
|
aethera::Dag<aethera::scene::Renderable>::Builder builder;
|
||||||
@@ -81,11 +79,17 @@ TEST(Scene, Render_Returns_Known_Submission_Result) {
|
|||||||
aethera::Scene scene;
|
aethera::Scene scene;
|
||||||
auto frame = scene.d->create_frame();
|
auto frame = scene.d->create_frame();
|
||||||
bool completed{};
|
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);
|
EXPECT_FALSE(completed);
|
||||||
aethera::proxy<aethera::frame_policy::Frame> empty_frame;
|
aethera::proxy<aethera::scene::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);
|
const auto missing_frame = scene.d->render(empty_frame, [&](aethera::proxy<aethera::scene::Frame>&, std::exception_ptr) {});
|
||||||
EXPECT_EQ(scene.d->render(frame, {}), aethera::frame_policy::Render_Scene_Result::completion_missing);
|
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) {
|
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;
|
aethera::Scene scene;
|
||||||
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
|
scene.write<aethera::Owned_Dag, aethera::Scene_Renderable_Dag>(std::move(*graph));
|
||||||
auto frame = scene.d->create_frame();
|
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
|
} // namespace
|
||||||
|
|||||||
@@ -6,25 +6,70 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <proxy/proxy.h>
|
#include <proxy/proxy.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace aethera {
|
namespace aethera {
|
||||||
enum struct Add_Task_Graph_Result : std::uint8_t { work_missing };
|
enum struct Add_Task_Graph_Result : std::uint8_t {
|
||||||
enum struct Add_Condition_Task_Graph_Result : std::uint8_t { condition_missing };
|
work_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 Add_Condition_Task_Graph_Result : std::uint8_t {
|
||||||
enum struct Describe_Task_Graph_Result : std::uint8_t { described, key_missing };
|
condition_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 Compose_Task_Graph_Result : std::uint8_t {
|
||||||
enum struct Taskflow_Unknown_Failure_Policy : std::uint8_t { fast_fail, exception };
|
component_unavailable,
|
||||||
enum struct Initialize_Task_Runtime_Result : std::uint8_t { initialized, invalid_worker_count, exception_boundary_missing, exception_boundary_unexpected, already_initialized, initialization_in_progress };
|
dependency_cycle,
|
||||||
enum struct Take_Taskflow_Observation_Result : std::uint8_t { not_recorded, recording, already_taken };
|
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_Completion = std::function<void(std::exception_ptr)>;
|
||||||
using Taskflow_Exception_Boundary = std::function<void(std::exception_ptr)>;
|
using Taskflow_Exception_Boundary = std::function<void(std::exception_ptr)>;
|
||||||
namespace task_flow {
|
namespace task_flow {
|
||||||
struct Task_Node : facade_builder::add_skill<pro::skills::rtti>::build {};
|
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_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_Common_Facade : facade_builder
|
||||||
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 {};
|
::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_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 {};
|
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);
|
proxy<Task_Graph_Facade> make_task_graph(std::string name);
|
||||||
|
|||||||
Reference in New Issue
Block a user