修复大错误移动到prop
This commit is contained in:
@@ -11,6 +11,9 @@
|
|||||||
## 状态与接口
|
## 状态与接口
|
||||||
|
|
||||||
* 每个状态只能有一个权威来源。双缓冲交换后的当前结构就是稳定读面,跨对象直接读取该结构;禁止为无锁访问再复制一份快照、View、镜像字段或同步缓存。
|
* 每个状态只能有一个权威来源。双缓冲交换后的当前结构就是稳定读面,跨对象直接读取该结构;禁止为无锁访问再复制一份快照、View、镜像字段或同步缓存。
|
||||||
|
* `Prop` 保存外部可读写的业务属性,`State` 保存实现向外发布的运行结果,`Private` 保存实现细节;三者不得互相复制并手工同步。
|
||||||
|
* 每个 `Def` 定义层自动以自身类型生成 `Base_Tag`;Prop、State、Private 分别在隔离的标签空间中复用该标签,禁止再声明 `XXX_Prop_Tag`、`XXX_State_Tag` 或 `XXX_Private_Tag`。
|
||||||
|
* 依赖可以选择 Prop/State 的单字段或整个 `Base_Tag` 层;字段写入必须同时发出字段级和所属层级变更,使用方按实际重建粒度选择一种依赖。
|
||||||
* `Root` 只保存一个最终 `Private` 指针;`Builder::build()` 校验成功后创建并挂接完整 Private,`Root` 通过公共 Private 基类的虚析构统一释放。禁止直接公开该指针。
|
* `Root` 只保存一个最终 `Private` 指针;`Builder::build()` 校验成功后创建并挂接完整 Private,`Root` 通过公共 Private 基类的虚析构统一释放。禁止直接公开该指针。
|
||||||
* 能从权威结构查询或计算的数据即时获取,不保存为成员。类只保存自身职责需要且无法推导的状态,并检查每个新增成员的读写者和生命周期。
|
* 能从权威结构查询或计算的数据即时获取,不保存为成员。类只保存自身职责需要且无法推导的状态,并检查每个新增成员的读写者和生命周期。
|
||||||
* 公共接口只表达业务语义,不暴露 `Private`、内部指针、线程状态或缓冲区角色;接口保持正交,不增加空配置、未完成接口、无消费者统计或只做转发的 getter/setter。
|
* 公共接口只表达业务语义,不暴露 `Private`、内部指针、线程状态或缓冲区角色;接口保持正交,不增加空配置、未完成接口、无消费者统计或只做转发的 getter/setter。
|
||||||
|
|||||||
@@ -157,6 +157,16 @@ public:
|
|||||||
void disconnect(Root* object) {
|
void disconnect(Root* object) {
|
||||||
edit_dependency_graph->disconnect_impl(object);
|
edit_dependency_graph->disconnect_impl(object);
|
||||||
}
|
}
|
||||||
|
/* 添加 Prop 字段级依赖;source 通过 set 修改该字段时目标阶段变脏。 */
|
||||||
|
template <auto Member, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Prop_Dependency_Source<Member> Source>
|
||||||
|
Node* add_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph->template add_dependency_runtime<Bound_Object, Target, Source>(target, source, detail::dependency_id<detail::Prop_Dependency_Key<Member>>(), target_tag, target_dirty_key, bind_node_data);
|
||||||
|
}
|
||||||
|
/* 添加 Prop 声明层级依赖;Owner 声明的任一字段通过 set 修改时目标阶段变脏。 */
|
||||||
|
template <typename Prop_Tag, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Prop_Layer_Dependency_Source<Prop_Tag> Source>
|
||||||
|
Node* add_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph->template add_dependency_runtime<Bound_Object, Target, Source>(target, source, detail::dependency_id<detail::Prop_Layer_Dependency_Key<Prop_Tag>>(), target_tag, target_dirty_key, bind_node_data);
|
||||||
|
}
|
||||||
template <auto Member, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::State_Dependency_Source<Member> Source>
|
template <auto Member, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::State_Dependency_Source<Member> Source>
|
||||||
Node* add_dependency(Target* target, Source* source) {
|
Node* add_dependency(Target* target, Source* source) {
|
||||||
return edit_dependency_graph->template add_dependency_runtime<Bound_Object, Target, Source>(
|
return edit_dependency_graph->template add_dependency_runtime<Bound_Object, Target, Source>(
|
||||||
@@ -206,6 +216,14 @@ public:
|
|||||||
bool remove_dependency(Target* target, Source* source) {
|
bool remove_dependency(Target* target, Source* source) {
|
||||||
return edit_dependency_graph->remove_edge(target, source, detail::dependency_id<detail::State_Dependency_Key<Member>>(), target_tag);
|
return edit_dependency_graph->remove_edge(target, source, detail::dependency_id<detail::State_Dependency_Key<Member>>(), target_tag);
|
||||||
}
|
}
|
||||||
|
template <auto Member, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Prop_Dependency_Source<Member> Source>
|
||||||
|
bool remove_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph->remove_edge(target, source, detail::dependency_id<detail::Prop_Dependency_Key<Member>>(), target_tag);
|
||||||
|
}
|
||||||
|
template <typename Prop_Tag, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::Prop_Layer_Dependency_Source<Prop_Tag> Source>
|
||||||
|
bool remove_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph->remove_edge(target, source, detail::dependency_id<detail::Prop_Layer_Dependency_Key<Prop_Tag>>(), target_tag);
|
||||||
|
}
|
||||||
/* 移除指定 State Tag 的状态层级依赖,不影响同对象上的字段级依赖。 */
|
/* 移除指定 State Tag 的状态层级依赖,不影响同对象上的字段级依赖。 */
|
||||||
template <typename State_Tag, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::State_Layer_Dependency_Source<State_Tag> Source>
|
template <typename State_Tag, detail::Bound_Dependency_Graph_Target<Bound_Object> Target, detail::State_Layer_Dependency_Source<State_Tag> Source>
|
||||||
bool remove_state_dependency(Target* target, Source* source) {
|
bool remove_state_dependency(Target* target, Source* source) {
|
||||||
|
|||||||
@@ -286,6 +286,14 @@ struct Root::Builder {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, auto Member, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::Prop_Dependency_Source<Member> Source>
|
||||||
|
Builder& add_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph<Dependency_Graph_Tag>([&](auto& editor) { editor.template add_prop_dependency<Member>(target, source); });
|
||||||
|
}
|
||||||
|
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, typename Prop_Tag, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::Prop_Layer_Dependency_Source<Prop_Tag> Source>
|
||||||
|
Builder& add_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph<Dependency_Graph_Tag>([&](auto& editor) { editor.template add_prop_dependency<Prop_Tag>(target, source); });
|
||||||
|
}
|
||||||
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, auto Member, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::State_Dependency_Source<Member> Source>
|
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, auto Member, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::State_Dependency_Source<Member> Source>
|
||||||
Builder& add_dependency(Target* target, Source* source) {
|
Builder& add_dependency(Target* target, Source* source) {
|
||||||
return edit_dependency_graph<Dependency_Graph_Tag>(
|
return edit_dependency_graph<Dependency_Graph_Tag>(
|
||||||
@@ -327,6 +335,14 @@ struct Root::Builder {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, auto Member, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::Prop_Dependency_Source<Member> Source>
|
||||||
|
Builder& remove_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph<Dependency_Graph_Tag>([&](auto& editor) { editor.template remove_prop_dependency<Member>(target, source); });
|
||||||
|
}
|
||||||
|
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, typename Prop_Tag, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::Prop_Layer_Dependency_Source<Prop_Tag> Source>
|
||||||
|
Builder& remove_prop_dependency(Target* target, Source* source) {
|
||||||
|
return edit_dependency_graph<Dependency_Graph_Tag>([&](auto& editor) { editor.template remove_prop_dependency<Prop_Tag>(target, source); });
|
||||||
|
}
|
||||||
/* 从构造期依赖图中移除指定 State Tag 的状态层级依赖。 */
|
/* 从构造期依赖图中移除指定 State Tag 的状态层级依赖。 */
|
||||||
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, typename State_Tag, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::State_Layer_Dependency_Source<State_Tag> Source>
|
template <detail::Dependency_Graph_Tag_In<typename Object::Dependency_Graph_Types> Dependency_Graph_Tag, typename State_Tag, detail::Bound_Dependency_Graph_Target<detail::Dependency_Graph_Bound_Object<Dependency_Graph_Tag, typename Object::Dependency_Graph_Types>> Target, detail::State_Layer_Dependency_Source<State_Tag> Source>
|
||||||
Builder& remove_state_dependency(Target* target, Source* source) {
|
Builder& remove_state_dependency(Target* target, Source* source) {
|
||||||
|
|||||||
@@ -145,10 +145,19 @@ struct Publish_Double_Buffer : Double_Buffer<Value> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
namespace detail {
|
namespace detail {
|
||||||
|
struct Prop_Root {
|
||||||
|
bool operator==(const Prop_Root&) const = default;
|
||||||
|
};
|
||||||
struct State_Root {
|
struct State_Root {
|
||||||
bool operator==(const State_Root&) const = default;
|
bool operator==(const State_Root&) const = default;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
template <typename Tag, Prop_State Prev = detail::Prop_Root>
|
||||||
|
struct Prop_Type : Prev {
|
||||||
|
using Tag_Type = Tag;
|
||||||
|
using Prev_Prop = Prev;
|
||||||
|
bool operator==(const Prop_Type&) const = default;
|
||||||
|
};
|
||||||
// State_Type 用 Tag 标记每一层状态,Prev_State 把 CRTP 继承链上的状态按层串起来,供精确回调和类型约束使用。
|
// State_Type 用 Tag 标记每一层状态,Prev_State 把 CRTP 继承链上的状态按层串起来,供精确回调和类型约束使用。
|
||||||
template <typename Tag, Prop_State Prev = detail::State_Root>
|
template <typename Tag, Prop_State Prev = detail::State_Root>
|
||||||
struct State_Type : Prev {
|
struct State_Type : Prev {
|
||||||
@@ -207,19 +216,27 @@ struct Is_State_Type : std::false_type {};
|
|||||||
template <typename Tag, typename Prev>
|
template <typename Tag, typename Prev>
|
||||||
struct Is_State_Type<State_Type<Tag, Prev>> : std::true_type {};
|
struct Is_State_Type<State_Type<Tag, Prev>> : std::true_type {};
|
||||||
template <typename Value>
|
template <typename Value>
|
||||||
|
struct Is_Prop_Type : std::false_type {};
|
||||||
|
template <typename Tag, typename Prev>
|
||||||
|
struct Is_Prop_Type<Prop_Type<Tag, Prev>> : std::true_type {};
|
||||||
|
template <typename Value>
|
||||||
concept Buffer_Type = Is_Tagged_Buffer<Value>::value;
|
concept Buffer_Type = Is_Tagged_Buffer<Value>::value;
|
||||||
template <typename Value>
|
template <typename Value>
|
||||||
concept Dependency_Graph_Mechanism = Is_Dependency_Graph_Type<Value>::value;
|
concept Dependency_Graph_Mechanism = Is_Dependency_Graph_Type<Value>::value;
|
||||||
template <typename Value>
|
template <typename Value>
|
||||||
concept State_Mechanism = Is_State_Type<Value>::value;
|
concept Mechanism_Type = Buffer_Type<Value> || Dependency_Graph_Mechanism<Value>;
|
||||||
template <typename Value>
|
|
||||||
concept Mechanism_Type = Buffer_Type<Value> || Dependency_Graph_Mechanism<Value> || State_Mechanism<Value>;
|
|
||||||
template <typename Value>
|
template <typename Value>
|
||||||
concept Tagged_State = Prop_State<Value> && requires {
|
concept Tagged_State = Prop_State<Value> && requires {
|
||||||
typename Value::Tag_Type;
|
typename Value::Tag_Type;
|
||||||
typename Value::Prev_State;
|
typename Value::Prev_State;
|
||||||
requires std::derived_from<Value, State_Type<typename Value::Tag_Type, typename Value::Prev_State>>;
|
requires std::derived_from<Value, State_Type<typename Value::Tag_Type, typename Value::Prev_State>>;
|
||||||
};
|
};
|
||||||
|
template <typename Value>
|
||||||
|
concept Tagged_Prop = Prop_State<Value> && requires {
|
||||||
|
typename Value::Tag_Type;
|
||||||
|
typename Value::Prev_Prop;
|
||||||
|
requires std::derived_from<Value, Prop_Type<typename Value::Tag_Type, typename Value::Prev_Prop>>;
|
||||||
|
};
|
||||||
template <typename State_T, typename = void>
|
template <typename State_T, typename = void>
|
||||||
struct State_Layers {
|
struct State_Layers {
|
||||||
using Type = std::tuple<>;
|
using Type = std::tuple<>;
|
||||||
@@ -233,6 +250,16 @@ struct State_Layers<State_T, std::void_t<typename State_T::Tag_Type, typename St
|
|||||||
};
|
};
|
||||||
template <typename State_T>
|
template <typename State_T>
|
||||||
using State_Layers_T = typename State_Layers<State_T>::Type;
|
using State_Layers_T = typename State_Layers<State_T>::Type;
|
||||||
|
template <typename Prop_T, typename = void>
|
||||||
|
struct Prop_Layers {
|
||||||
|
using Type = std::tuple<>;
|
||||||
|
};
|
||||||
|
template <typename Prop_T>
|
||||||
|
struct Prop_Layers<Prop_T, std::void_t<typename Prop_T::Tag_Type, typename Prop_T::Prev_Prop>> {
|
||||||
|
using Type = decltype(std::tuple_cat(std::declval<std::tuple<Prop_T>>(), std::declval<typename Prop_Layers<typename Prop_T::Prev_Prop>::Type>()));
|
||||||
|
};
|
||||||
|
template <typename Prop_T>
|
||||||
|
using Prop_Layers_T = typename Prop_Layers<Prop_T>::Type;
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct Member_Pointer_Traits;
|
struct Member_Pointer_Traits;
|
||||||
template <typename Member, typename Owner>
|
template <typename Member, typename Owner>
|
||||||
@@ -250,6 +277,14 @@ concept State_Member = requires {
|
|||||||
};
|
};
|
||||||
template <auto Member>
|
template <auto Member>
|
||||||
using State_Member_Tag = typename Member_Pointer_Traits<decltype(Member)>::Owner_Type::Tag_Type;
|
using State_Member_Tag = typename Member_Pointer_Traits<decltype(Member)>::Owner_Type::Tag_Type;
|
||||||
|
template <auto Member, typename Prop>
|
||||||
|
concept Prop_Member = requires {
|
||||||
|
requires std::is_member_object_pointer_v<decltype(Member)>;
|
||||||
|
requires std::derived_from<Prop, typename Member_Pointer_Traits<decltype(Member)>::Owner_Type>;
|
||||||
|
requires Tagged_Prop<typename Member_Pointer_Traits<decltype(Member)>::Owner_Type>;
|
||||||
|
};
|
||||||
|
template <auto Member>
|
||||||
|
using Prop_Member_Tag = typename Member_Pointer_Traits<decltype(Member)>::Owner_Type::Tag_Type;
|
||||||
template <typename Value, auto Member, typename State>
|
template <typename Value, auto Member, typename State>
|
||||||
concept State_Member_Settable = requires(State& state, Value&& value) {
|
concept State_Member_Settable = requires(State& state, Value&& value) {
|
||||||
requires State_Member<Member, State>;
|
requires State_Member<Member, State>;
|
||||||
@@ -259,8 +294,6 @@ template <typename T>
|
|||||||
using Mechanism_Buffer_Tuple = std::conditional_t<Buffer_Type<T>, std::tuple<T>, std::tuple<>>;
|
using Mechanism_Buffer_Tuple = std::conditional_t<Buffer_Type<T>, std::tuple<T>, std::tuple<>>;
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using Mechanism_Dependency_Graph_Tuple = std::conditional_t<Dependency_Graph_Mechanism<T>, std::tuple<T>, std::tuple<>>;
|
using Mechanism_Dependency_Graph_Tuple = std::conditional_t<Dependency_Graph_Mechanism<T>, std::tuple<T>, std::tuple<>>;
|
||||||
template <typename T>
|
|
||||||
using Mechanism_State_Tuple = std::conditional_t<State_Mechanism<T>, std::tuple<T>, std::tuple<>>;
|
|
||||||
template <typename Tag, typename Tuple>
|
template <typename Tag, typename Tuple>
|
||||||
struct Has_Tag : std::false_type {};
|
struct Has_Tag : std::false_type {};
|
||||||
template <typename Tag, typename... Types>
|
template <typename Tag, typename... Types>
|
||||||
@@ -334,6 +367,19 @@ struct Is_State_List<std::tuple<States...>> : Tagged_List_Check<
|
|||||||
> {};
|
> {};
|
||||||
template <typename Tuple>
|
template <typename Tuple>
|
||||||
concept State_List = Is_State_List<Tuple>::value;
|
concept State_List = Is_State_List<Tuple>::value;
|
||||||
|
template <typename Tuple>
|
||||||
|
struct Is_Prop_List : std::false_type {};
|
||||||
|
template <typename... Props>
|
||||||
|
struct Is_Prop_List<std::tuple<Props...>> : Tagged_List_Check<(Tagged_Prop<Props> && ...), Props...> {};
|
||||||
|
template <typename Tuple>
|
||||||
|
concept Prop_List = Is_Prop_List<Tuple>::value;
|
||||||
|
template <typename Prop_T>
|
||||||
|
concept Prop_Chain = Tagged_Prop<Prop_T> && Prop_List<Prop_Layers_T<Prop_T>>;
|
||||||
|
template <typename Tag, typename Tuple>
|
||||||
|
concept Prop_Tag_In = requires {
|
||||||
|
requires Prop_List<Tuple>;
|
||||||
|
requires Has_Tag<Tag, Tuple>::value;
|
||||||
|
};
|
||||||
template <typename State_T>
|
template <typename State_T>
|
||||||
concept State_Chain = Tagged_State<State_T> && State_List<State_Layers_T<State_T>>;
|
concept State_Chain = Tagged_State<State_T> && State_List<State_Layers_T<State_T>>;
|
||||||
template <typename Tag, typename Tuple>
|
template <typename Tag, typename Tuple>
|
||||||
@@ -384,6 +430,15 @@ public:
|
|||||||
};
|
};
|
||||||
template <typename Tag, typename State_T>
|
template <typename Tag, typename State_T>
|
||||||
using State_Value = typename State_By_Tag<Tag, State_T>::Type;
|
using State_Value = typename State_By_Tag<Tag, State_T>::Type;
|
||||||
|
template <typename Tag, typename Prop_T>
|
||||||
|
struct Prop_By_Tag {
|
||||||
|
private:
|
||||||
|
using Layers = Prop_Layers_T<Prop_T>;
|
||||||
|
public:
|
||||||
|
using Type = std::tuple_element_t<tag_index_v<Tag, Layers>, Layers>;
|
||||||
|
};
|
||||||
|
template <typename Tag, typename Prop_T>
|
||||||
|
using Prop_Value = typename Prop_By_Tag<Tag, Prop_T>::Type;
|
||||||
template <typename Callback, typename Tag, typename State_T, typename States>
|
template <typename Callback, typename Tag, typename State_T, typename States>
|
||||||
concept State_Callback_For = State_Tag_In<Tag, States> && State_Chain_Matches<State_T, States> && std::invocable<Callback, const State_Value<Tag, State_T>&>;
|
concept State_Callback_For = State_Tag_In<Tag, States> && State_Chain_Matches<State_T, States> && std::invocable<Callback, const State_Value<Tag, State_T>&>;
|
||||||
template <typename Tuple>
|
template <typename Tuple>
|
||||||
@@ -522,8 +577,53 @@ public:
|
|||||||
};
|
};
|
||||||
template <typename State_Type> requires detail::State_Chain<std::remove_const_t<State_Type>>
|
template <typename State_Type> requires detail::State_Chain<std::remove_const_t<State_Type>>
|
||||||
State_Access(State_Type&) -> State_Access<State_Type>;
|
State_Access(State_Type&) -> State_Access<State_Type>;
|
||||||
|
/* Prop_Access 按定义层的 Base_Tag 选择 Prop;Prop 与 State 的标签空间彼此隔离。 */
|
||||||
|
template <typename Prop_Type> requires detail::Prop_Chain<std::remove_const_t<Prop_Type>>
|
||||||
|
class Prop_Access {
|
||||||
|
private:
|
||||||
|
using Value_Type = std::remove_const_t<Prop_Type>;
|
||||||
|
Prop_Type* prop;
|
||||||
|
template <typename Other_Prop_Type> requires detail::Prop_Chain<std::remove_const_t<Other_Prop_Type>>
|
||||||
|
friend class Prop_Access;
|
||||||
|
public:
|
||||||
|
explicit Prop_Access(Prop_Type& value) noexcept : prop(&value) {}
|
||||||
|
template <typename Source_Prop_Type> requires std::convertible_to<Source_Prop_Type*, Prop_Type*>
|
||||||
|
Prop_Access(const Prop_Access<Source_Prop_Type>& source) noexcept : prop(source.prop) {}
|
||||||
|
template <typename Tag> requires detail::Prop_Tag_In<Tag, detail::Prop_Layers_T<Value_Type>>
|
||||||
|
[[nodiscard]] decltype(auto) get() const noexcept {
|
||||||
|
using Layer = detail::Prop_Value<Tag, Value_Type>;
|
||||||
|
if constexpr (std::is_const_v<Prop_Type>) return static_cast<const Layer&>(*prop);
|
||||||
|
else return static_cast<Layer&>(*prop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <typename Prop_Type> requires detail::Prop_Chain<std::remove_const_t<Prop_Type>>
|
||||||
|
Prop_Access(Prop_Type&) -> Prop_Access<Prop_Type>;
|
||||||
|
/* Private_Access 使用同一个定义层 Base_Tag 选择 Private;该标签空间不与 Prop/State 混用。 */
|
||||||
|
template <typename Private_Type>
|
||||||
|
class Private_Access {
|
||||||
|
private:
|
||||||
|
Private_Type* private_data;
|
||||||
|
template <typename Other_Private_Type>
|
||||||
|
friend class Private_Access;
|
||||||
|
public:
|
||||||
|
explicit Private_Access(Private_Type& value) noexcept : private_data(&value) {}
|
||||||
|
template <typename Source_Private_Type> requires std::convertible_to<Source_Private_Type*, Private_Type*>
|
||||||
|
Private_Access(const Private_Access<Source_Private_Type>& source) noexcept : private_data(source.private_data) {}
|
||||||
|
template <typename Tag> requires requires { typename Tag::Private; } && std::derived_from<std::remove_const_t<Private_Type>, typename Tag::Private>
|
||||||
|
[[nodiscard]] decltype(auto) get() const noexcept {
|
||||||
|
using Layer = typename Tag::Private;
|
||||||
|
if constexpr (std::is_const_v<Private_Type>) return static_cast<const Layer&>(*private_data);
|
||||||
|
else return static_cast<Layer&>(*private_data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <typename Private_Type>
|
||||||
|
Private_Access(Private_Type&) -> Private_Access<Private_Type>;
|
||||||
namespace detail {
|
namespace detail {
|
||||||
template <auto Member>
|
template <auto Member>
|
||||||
|
struct Prop_Dependency_Key {};
|
||||||
|
template <typename Tag>
|
||||||
|
struct Prop_Layer_Dependency_Key {};
|
||||||
|
template <auto Member>
|
||||||
struct State_Dependency_Key {};
|
struct State_Dependency_Key {};
|
||||||
template <typename Tag>
|
template <typename Tag>
|
||||||
struct State_Layer_Dependency_Key {};
|
struct State_Layer_Dependency_Key {};
|
||||||
@@ -563,10 +663,10 @@ enum class Dependency_Graph_Error {
|
|||||||
missing_dependency,
|
missing_dependency,
|
||||||
cycle
|
cycle
|
||||||
};
|
};
|
||||||
struct Root_State_Tag {};
|
|
||||||
struct Root {
|
struct Root {
|
||||||
/* 所有实现层 Private 的公共析构基类;Root 通过该类型唯一拥有最终 Private。 */
|
/* 所有实现层 Private 的公共析构基类;Root 通过该类型唯一拥有最终 Private。 */
|
||||||
struct Private {
|
struct Private {
|
||||||
|
using Tag_Type = Root;
|
||||||
virtual ~Private() = default;
|
virtual ~Private() = default;
|
||||||
/* CRTP 默认:Builder 挂接最终 Private 后按基类到派生类绑定最终对象类型;Root 层不处理。 */
|
/* CRTP 默认:Builder 挂接最终 Private 后按基类到派生类绑定最终对象类型;Root 层不处理。 */
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
@@ -574,9 +674,12 @@ struct Root {
|
|||||||
};
|
};
|
||||||
using Buffers = std::tuple<>;
|
using Buffers = std::tuple<>;
|
||||||
using Dependency_Graph_Types = std::tuple<>;
|
using Dependency_Graph_Types = std::tuple<>;
|
||||||
using States = std::tuple<State_Type<Root_State_Tag>>;
|
using Base_Tag = Root;
|
||||||
struct Prop {};
|
using States = std::tuple<State_Type<Base_Tag>>;
|
||||||
struct State : State_Type<Root_State_Tag> {
|
struct Prop : Prop_Type<Base_Tag> {
|
||||||
|
bool operator==(const Prop&) const = default;
|
||||||
|
};
|
||||||
|
struct State : State_Type<Base_Tag> {
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
@@ -666,8 +769,10 @@ namespace detail {
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
concept Dependency_Object = Attached<T> && requires {
|
concept Dependency_Object = Attached<T> && requires {
|
||||||
requires Root_Derived<T>;
|
requires Root_Derived<T>;
|
||||||
|
typename T::Prop;
|
||||||
typename T::State;
|
typename T::State;
|
||||||
typename T::Buffers;
|
typename T::Buffers;
|
||||||
|
requires Prop_State<typename T::Prop>;
|
||||||
requires Prop_State<typename T::State>;
|
requires Prop_State<typename T::State>;
|
||||||
requires Buffer_List<typename T::Buffers>;
|
requires Buffer_List<typename T::Buffers>;
|
||||||
};
|
};
|
||||||
@@ -680,6 +785,16 @@ concept Bound_Dependency_Graph_Target = Bound_Dependency_Object<T> && std::deriv
|
|||||||
template <typename T, typename Bound>
|
template <typename T, typename Bound>
|
||||||
concept Dependency_Graph_Target = Root_Derived<T> && std::derived_from<T, Bound>;
|
concept Dependency_Graph_Target = Root_Derived<T> && std::derived_from<T, Bound>;
|
||||||
template <typename Source, auto Member>
|
template <typename Source, auto Member>
|
||||||
|
concept Prop_Dependency_Source = requires {
|
||||||
|
requires Dependency_Object<Source>;
|
||||||
|
requires Prop_Member<Member, typename Source::Prop>;
|
||||||
|
};
|
||||||
|
template <typename Source, typename Tag>
|
||||||
|
concept Prop_Layer_Dependency_Source = requires {
|
||||||
|
requires Dependency_Object<Source>;
|
||||||
|
requires Prop_Tag_In<Tag, Prop_Layers_T<typename Source::Prop>>;
|
||||||
|
};
|
||||||
|
template <typename Source, auto Member>
|
||||||
concept State_Dependency_Source = requires {
|
concept State_Dependency_Source = requires {
|
||||||
requires Dependency_Object<Source>;
|
requires Dependency_Object<Source>;
|
||||||
requires State_Member<Member, typename Source::State>;
|
requires State_Member<Member, typename Source::State>;
|
||||||
|
|||||||
@@ -6,18 +6,20 @@ concept Object = requires {
|
|||||||
requires Object_Root<T>;
|
requires Object_Root<T>;
|
||||||
typename T::This_Object;
|
typename T::This_Object;
|
||||||
typename T::Prev_Object;
|
typename T::Prev_Object;
|
||||||
|
typename T::Base_Tag;
|
||||||
typename T::Prev_Prop;
|
typename T::Prev_Prop;
|
||||||
typename T::State_Tag;
|
typename T::Prev_State;
|
||||||
typename T::template Prev_State<typename T::State_Tag>;
|
|
||||||
typename T::template Prev_Builder<T>;
|
typename T::template Prev_Builder<T>;
|
||||||
typename T::Prev_Private;
|
typename T::Prev_Private;
|
||||||
typename T::States;
|
typename T::States;
|
||||||
requires std::same_as<typename T::This_Object, T>;
|
requires std::same_as<typename T::This_Object, T>;
|
||||||
|
requires std::same_as<typename T::Base_Tag, T>;
|
||||||
requires Object_Root<typename T::Prev_Object>;
|
requires Object_Root<typename T::Prev_Object>;
|
||||||
requires std::derived_from<typename T::Prop, typename T::Prev_Prop>;
|
requires std::derived_from<typename T::Prop, typename T::Prev_Prop>;
|
||||||
requires detail::State_Tag_In<typename T::State_Tag, typename T::States>;
|
requires detail::Tagged_Prop<typename T::Prop>;
|
||||||
requires detail::State_Chain<typename T::template Prev_State<typename T::State_Tag>>;
|
requires detail::State_Tag_In<typename T::Base_Tag, typename T::States>;
|
||||||
requires std::derived_from<typename T::State, typename T::template Prev_State<typename T::State_Tag>>;
|
requires detail::State_Chain<typename T::Prev_State>;
|
||||||
|
requires std::derived_from<typename T::State, typename T::Prev_State>;
|
||||||
requires detail::State_Chain_Matches<typename T::State, typename T::States>;
|
requires detail::State_Chain_Matches<typename T::State, typename T::States>;
|
||||||
requires std::derived_from<typename T::template Builder<T>, typename T::template Prev_Builder<T>>;
|
requires std::derived_from<typename T::template Builder<T>, typename T::template Prev_Builder<T>>;
|
||||||
requires std::derived_from<typename T::Private, typename T::Prev_Private>;
|
requires std::derived_from<typename T::Private, typename T::Prev_Private>;
|
||||||
@@ -33,26 +35,16 @@ using Impl_Dependency_Graph_Types = decltype(std::tuple_cat(
|
|||||||
std::declval<typename Base::Dependency_Graph_Types>(),
|
std::declval<typename Base::Dependency_Graph_Types>(),
|
||||||
std::declval<Mechanism_Dependency_Graph_Tuple<Local_Mechanisms>>()...
|
std::declval<Mechanism_Dependency_Graph_Tuple<Local_Mechanisms>>()...
|
||||||
));
|
));
|
||||||
template <typename... Local_Mechanisms>
|
template <typename Self, typename Base>
|
||||||
using Local_States = decltype(std::tuple_cat(
|
|
||||||
std::declval<Mechanism_State_Tuple<Local_Mechanisms>>()...
|
|
||||||
));
|
|
||||||
template <typename Base, typename... Local_Mechanisms>
|
|
||||||
using Impl_States = decltype(std::tuple_cat(
|
using Impl_States = decltype(std::tuple_cat(
|
||||||
std::declval<typename Base::States>(),
|
std::declval<typename Base::States>(),
|
||||||
std::declval<Local_States<Local_Mechanisms...>>()
|
std::declval<std::tuple<State_Type<Self>>>()
|
||||||
));
|
));
|
||||||
template <typename Base, typename... Local_Mechanisms>
|
template <typename Self, typename Base, typename... Local_Mechanisms>
|
||||||
using Impl_State_Base = Rebind_State_T<
|
|
||||||
std::tuple_element_t<0, Local_States<Local_Mechanisms...>>,
|
|
||||||
typename Base::State
|
|
||||||
>;
|
|
||||||
template <typename Base, typename... Local_Mechanisms>
|
|
||||||
concept Impl_Mechanisms = Object_Root<Base> && (Mechanism_Type<Local_Mechanisms> && ...) && requires {
|
concept Impl_Mechanisms = Object_Root<Base> && (Mechanism_Type<Local_Mechanisms> && ...) && requires {
|
||||||
requires std::tuple_size_v<Local_States<Local_Mechanisms...>> == 1;
|
|
||||||
requires Buffer_List<Impl_Buffers<Base, Local_Mechanisms...>>;
|
requires Buffer_List<Impl_Buffers<Base, Local_Mechanisms...>>;
|
||||||
requires Dependency_Graph_List<Impl_Dependency_Graph_Types<Base, Local_Mechanisms...>>;
|
requires Dependency_Graph_List<Impl_Dependency_Graph_Types<Base, Local_Mechanisms...>>;
|
||||||
requires State_List<Impl_States<Base, Local_Mechanisms...>>;
|
requires State_List<Impl_States<Self, Base>>;
|
||||||
};
|
};
|
||||||
template <typename Callback, typename Private, typename Object_T>
|
template <typename Callback, typename Private, typename Object_T>
|
||||||
concept Process_Callback_For = requires(Private& private_data, Object_T* object, Callback&& callback) {
|
concept Process_Callback_For = requires(Private& private_data, Object_T* object, Callback&& callback) {
|
||||||
@@ -61,27 +53,33 @@ concept Process_Callback_For = requires(Private& private_data, Object_T* object,
|
|||||||
}
|
}
|
||||||
// Impl 在编译期把 Buffer/Dependency_Graph/State 三类机制叠加到继承链;每一层只声明自己的机制,最终类型汇总完整能力。
|
// Impl 在编译期把 Buffer/Dependency_Graph/State 三类机制叠加到继承链;每一层只声明自己的机制,最终类型汇总完整能力。
|
||||||
template <typename Self, Object_Root Base, detail::Mechanism_Type... Local_Mechanisms> requires
|
template <typename Self, Object_Root Base, detail::Mechanism_Type... Local_Mechanisms> requires
|
||||||
detail::Impl_Mechanisms<Base, Local_Mechanisms...>
|
detail::Impl_Mechanisms<Self, Base, Local_Mechanisms...>
|
||||||
struct Def : Base {
|
struct Def : Base {
|
||||||
using This_Object = Self;
|
using This_Object = Self;
|
||||||
using Prev_Object = Base;
|
using Prev_Object = Base;
|
||||||
using Prev_Prop = typename Base::Prop;
|
using Base_Tag = Self;
|
||||||
|
using Prev_Prop = Prop_Type<Base_Tag, typename Base::Prop>;
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
using Prev_Builder = typename Base::template Builder<Object>;
|
using Prev_Builder = typename Base::template Builder<Object>;
|
||||||
using State_Declaration = std::tuple_element_t<0, detail::Local_States<Local_Mechanisms...>>;
|
using Prev_State = State_Type<Base_Tag, typename Base::State>;
|
||||||
using State_Tag = typename State_Declaration::Tag_Type;
|
|
||||||
template <std::same_as<State_Tag> Tag>
|
|
||||||
using Prev_State = detail::Rebind_State_T<State_Declaration, typename Base::State>;
|
|
||||||
using Base_Private = typename Base::Private;
|
using Base_Private = typename Base::Private;
|
||||||
using Buffers = detail::Impl_Buffers<Base, Local_Mechanisms...>;
|
using Buffers = detail::Impl_Buffers<Base, Local_Mechanisms...>;
|
||||||
using Dependency_Graph_Types = detail::Impl_Dependency_Graph_Types<Base, Local_Mechanisms...>;
|
using Dependency_Graph_Types = detail::Impl_Dependency_Graph_Types<Base, Local_Mechanisms...>;
|
||||||
using States = detail::Impl_States<Base, Local_Mechanisms...>;
|
using States = detail::Impl_States<Self, Base>;
|
||||||
struct Private : Base_Private {
|
struct Private : Base_Private {
|
||||||
|
using Tag_Type = Base_Tag;
|
||||||
|
using Prev_Private = Base_Private;
|
||||||
/* CRTP 默认:继续调用上一 Private 层的最终对象绑定;派生 Private 覆盖时必须先调用此实现。 */
|
/* CRTP 默认:继续调用上一 Private 层的最终对象绑定;派生 Private 覆盖时必须先调用此实现。 */
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
void bind_private_crtp(Object* object) {
|
void bind_private_crtp(Object* object) {
|
||||||
Base_Private::bind_private_crtp(object);
|
Base_Private::bind_private_crtp(object);
|
||||||
}
|
}
|
||||||
|
/* CRTP 可覆盖:写入 Prop 成员前按基类到派生类顺序调用。 */
|
||||||
|
template <typename Owner, typename Member, typename Prop_T>
|
||||||
|
void before_prop_set(Self* object, Member Owner::* member, Prop_Access<Prop_T> props) {}
|
||||||
|
/* CRTP 可覆盖:写入 Prop 成员后按派生类到基类顺序调用。 */
|
||||||
|
template <typename Owner, typename Member, typename Prop_T>
|
||||||
|
void after_prop_set(Self* object, Member Owner::* member, Prop_Access<Prop_T> props) {}
|
||||||
/* CRTP 可覆盖:写入 State 成员前按基类到派生类顺序调用;pending_states.get<Tag>() 返回对应可写状态层。 */
|
/* CRTP 可覆盖:写入 State 成员前按基类到派生类顺序调用;pending_states.get<Tag>() 返回对应可写状态层。 */
|
||||||
template <typename Owner, typename Member, typename State_T>
|
template <typename Owner, typename Member, typename State_T>
|
||||||
void before_state_set(Self* object, Member Owner::* member, State_Access<State_T> pending_states) {}
|
void before_state_set(Self* object, Member Owner::* member, State_Access<State_T> pending_states) {}
|
||||||
@@ -157,6 +155,24 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <typename Owner, typename Member>
|
template <typename Owner, typename Member>
|
||||||
|
void before_prop_set(Member Owner::* member) {
|
||||||
|
Prop_Access props{*data().current};
|
||||||
|
auto callback = [&](auto& private_data) { private_data.before_prop_set(this, member, props); };
|
||||||
|
walk_private<false, Obj>(callback);
|
||||||
|
}
|
||||||
|
template <typename Owner, typename Member>
|
||||||
|
void after_prop_set(Member Owner::* member) {
|
||||||
|
Prop_Access props{*data().current};
|
||||||
|
auto callback = [&](auto& private_data) { private_data.after_prop_set(this, member, props); };
|
||||||
|
walk_private<true, Obj>(callback);
|
||||||
|
}
|
||||||
|
template <auto Member>
|
||||||
|
void emit_prop_dependencies() {
|
||||||
|
this->emit_dependency_source(detail::dependency_id<detail::Prop_Dependency_Key<Member>>());
|
||||||
|
using Prop_Tag = detail::Prop_Member_Tag<Member>;
|
||||||
|
this->emit_dependency_source(detail::dependency_id<detail::Prop_Layer_Dependency_Key<Prop_Tag>>());
|
||||||
|
}
|
||||||
|
template <typename Owner, typename Member>
|
||||||
void before_state_set(Member Owner::* member) {
|
void before_state_set(Member Owner::* member) {
|
||||||
State_Access pending_states{*data().state.pending};
|
State_Access pending_states{*data().state.pending};
|
||||||
auto callback = [&](auto& private_data) {
|
auto callback = [&](auto& private_data) {
|
||||||
@@ -267,12 +283,36 @@ public:
|
|||||||
void for_each_current_dependency_graph(Callback&& callback) const {
|
void for_each_current_dependency_graph(Callback&& callback) const {
|
||||||
data().dependency_graph_storage.for_each_current(std::forward<Callback>(callback));
|
data().dependency_graph_storage.for_each_current(std::forward<Callback>(callback));
|
||||||
}
|
}
|
||||||
template <typename Owner, typename Member, detail::Prop_Member_Settable<Prop, Owner, Member> Value>
|
template <auto Member, typename Value> requires detail::Prop_Member<Member, Prop> && requires(Prop& prop, Value&& value) { prop.*Member = std::forward<Value>(value); }
|
||||||
Impl& set(Member Owner::* member, Value&& value) {
|
Impl& set(Value&& value) {
|
||||||
std::lock_guard<Lock> guard(lock);
|
std::lock_guard<Lock> guard(lock);
|
||||||
data().current->*member = std::forward<Value>(value);
|
before_prop_set(Member);
|
||||||
|
data().current->*Member = std::forward<Value>(value);
|
||||||
|
after_prop_set(Member);
|
||||||
|
emit_prop_dependencies<Member>();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
template <auto Member> requires detail::Prop_Dependency_Source<Impl, Member>
|
||||||
|
[[nodiscard]] auto get() const {
|
||||||
|
std::lock_guard<Lock> guard(lock);
|
||||||
|
return data().current->*Member;
|
||||||
|
}
|
||||||
|
template <typename Tag> requires detail::Prop_Tag_In<Tag, detail::Prop_Layers_T<Prop>>
|
||||||
|
[[nodiscard]] const auto& read_prop() const noexcept {
|
||||||
|
using Layer = detail::Prop_Value<Tag, Prop>;
|
||||||
|
return static_cast<const Layer&>(*data().current);
|
||||||
|
}
|
||||||
|
template <auto... Members, typename Callback> requires
|
||||||
|
(sizeof...(Members) > 0) &&
|
||||||
|
(detail::Prop_Member<Members, Prop> && ...) &&
|
||||||
|
std::invocable<Callback, Prop_Access<Prop>>
|
||||||
|
void update_prop(Callback&& callback) {
|
||||||
|
std::lock_guard<Lock> guard(lock);
|
||||||
|
(before_prop_set(Members), ...);
|
||||||
|
std::invoke(std::forward<Callback>(callback), Prop_Access{*data().current});
|
||||||
|
(after_prop_set(Members), ...);
|
||||||
|
(emit_prop_dependencies<Members>(), ...);
|
||||||
|
}
|
||||||
template <detail::State_Tag_In<States> Tag, detail::State_Callback_For<Tag, State, States> Callback>
|
template <detail::State_Tag_In<States> Tag, detail::State_Callback_For<Tag, State, States> Callback>
|
||||||
void set_state_callback(Callback&& callback) {
|
void set_state_callback(Callback&& callback) {
|
||||||
std::lock_guard<Lock> guard(lock);
|
std::lock_guard<Lock> guard(lock);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using double_buffer::Def;
|
|||||||
using double_buffer::Impl;
|
using double_buffer::Impl;
|
||||||
using double_buffer::State_Type;
|
using double_buffer::State_Type;
|
||||||
using double_buffer::State_Access;
|
using double_buffer::State_Access;
|
||||||
|
using double_buffer::Prop_Access;
|
||||||
using double_buffer::Pmr;
|
using double_buffer::Pmr;
|
||||||
using double_buffer::Root;
|
using double_buffer::Root;
|
||||||
using double_buffer::Tagged_Buffer;
|
using double_buffer::Tagged_Buffer;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ struct Color_Cache {
|
|||||||
virtual ~Color_Cache() = default;
|
virtual ~Color_Cache() = default;
|
||||||
};
|
};
|
||||||
/* Renderable 状态标签,用于访问和订阅 Renderable::State。 */
|
/* Renderable 状态标签,用于访问和订阅 Renderable::State。 */
|
||||||
struct Renderable_State_Tag {};
|
|
||||||
struct Renderable;
|
struct Renderable;
|
||||||
/* 最终 Private 提供 handle_event(T*, const Event&) 时具备事件处理能力。 */
|
/* 最终 Private 提供 handle_event(T*, const Event&) 时具备事件处理能力。 */
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -28,8 +27,8 @@ concept Prepare_Data_Renderable = Attached<T> && requires(typename T::Private& p
|
|||||||
* 子图首次执行前一定构建,之后由 should_rebuild_prepare_graph(...) 决定是否重建。
|
* 子图首次执行前一定构建,之后由 should_rebuild_prepare_graph(...) 决定是否重建。
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Prepare_Graph_Renderable = Attached<T> && requires(typename T::Private& private_data, T* object, const typename T::State& state) {
|
concept Prepare_Graph_Renderable = Attached<T> && requires(typename T::Private& private_data, T* object, const typename T::Prop& prop) {
|
||||||
{ private_data.build_prepare_graph(object, state) } -> std::same_as<tf::Taskflow>;
|
{ private_data.build_prepare_graph(object, prop) } -> std::same_as<tf::Taskflow>;
|
||||||
};
|
};
|
||||||
/* 最终 Private 声明 No_Prepare 时,该 Renderable 不参与 Prepare 阶段。 */
|
/* 最终 Private 声明 No_Prepare 时,该 Renderable 不参与 Prepare 阶段。 */
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@@ -48,29 +47,29 @@ concept Paint_Data_Renderable = Attached<T> && requires(typename T::Private& pri
|
|||||||
* 子图首次执行前一定构建,之后由 should_rebuild_paint_graph(...) 决定是否重建。
|
* 子图首次执行前一定构建,之后由 should_rebuild_paint_graph(...) 决定是否重建。
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Paint_Graph_Renderable = Attached<T> && requires(typename T::Private& private_data, T* object, const typename T::State& state) {
|
concept Paint_Graph_Renderable = Attached<T> && requires(typename T::Private& private_data, T* object, const typename T::Prop& prop) {
|
||||||
{ private_data.build_paint_graph(object, state) } -> std::same_as<tf::Taskflow>;
|
{ private_data.build_paint_graph(object, prop) } -> std::same_as<tf::Taskflow>;
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
* 最终可交给 Scene 执行的 Renderable 契约。
|
* 最终可交给 Scene 执行的 Renderable 契约。
|
||||||
* Prepare 与 Paint 各自至少提供数据模式或子图模式之一,并继承 Renderable 提供的默认阶段策略。
|
* Prepare 与 Paint 各自至少提供数据模式或子图模式之一,并继承 Renderable 提供的默认阶段策略。
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Renderable_Object = Attached<T> && std::derived_from<T, Renderable> && requires(typename T::Private& private_data, T* object, const typename T::State& state, bool dirty) {
|
concept Renderable_Object = Attached<T> && std::derived_from<T, Renderable> && requires(typename T::Private& private_data, T* object, const typename T::State& state, const typename T::Prop& prop, bool dirty) {
|
||||||
{ private_data.should_prepare(object, state, dirty) } -> std::same_as<bool>;
|
{ private_data.should_prepare(object, state, dirty) } -> std::same_as<bool>;
|
||||||
{ private_data.should_paint(object, state, dirty) } -> std::same_as<bool>;
|
{ private_data.should_paint(object, state, dirty) } -> std::same_as<bool>;
|
||||||
{ private_data.should_rebuild_prepare_graph(object, state) } -> std::same_as<bool>;
|
{ private_data.should_rebuild_prepare_graph(object, prop) } -> std::same_as<bool>;
|
||||||
{ private_data.should_rebuild_paint_graph(object, state) } -> std::same_as<bool>;
|
{ private_data.should_rebuild_paint_graph(object, prop) } -> std::same_as<bool>;
|
||||||
} && (No_Prepare_Renderable<T> || Prepare_Data_Renderable<T> || Prepare_Graph_Renderable<T>) && (Paint_Data_Renderable<T> || Paint_Graph_Renderable<T>);
|
} && (No_Prepare_Renderable<T> || Prepare_Data_Renderable<T> || Prepare_Graph_Renderable<T>) && (Paint_Data_Renderable<T> || Paint_Graph_Renderable<T>);
|
||||||
/*
|
/*
|
||||||
* Renderable 定义 Scene 可调度对象的公共机制。
|
* Renderable 定义 Scene 可调度对象的公共机制。
|
||||||
* 用户继续派生该定义,在派生类型的 Private 中提供 Prepare/Paint 定制点,并最终使用 Impl<T> 创建可运行实例。
|
* 用户继续派生该定义,在派生类型的 Private 中提供 Prepare/Paint 定制点,并最终使用 Impl<T> 创建可运行实例。
|
||||||
*/
|
*/
|
||||||
struct Renderable : Def<Renderable, Root, State_Type<Renderable_State_Tag>> {
|
struct Renderable : Def<Renderable, Root> {
|
||||||
/* Renderable 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */
|
/* Renderable 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
/* Renderable 每次 Scene 执行后发布的阶段状态与统计。 */
|
/* Renderable 每次 Scene 执行后发布的阶段状态与统计。 */
|
||||||
struct State : Prev_State<Renderable_State_Tag> {
|
struct State : Prev_State {
|
||||||
bool prepare_dirty{}; /* Prepare 条件判断时观察到的 Prepare_Data_Tag dirty 状态。 */
|
bool prepare_dirty{}; /* Prepare 条件判断时观察到的 Prepare_Data_Tag dirty 状态。 */
|
||||||
bool paint_dirty{}; /* Paint 条件判断时观察到的 Paint_Tag dirty 状态。 */
|
bool paint_dirty{}; /* Paint 条件判断时观察到的 Paint_Tag dirty 状态。 */
|
||||||
bool prepare_executed{}; /* 本次 Scene 执行是否运行了 Prepare 数据函数或子图。 */
|
bool prepare_executed{}; /* 本次 Scene 执行是否运行了 Prepare 数据函数或子图。 */
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ struct Renderable::Private : Prev_Private {
|
|||||||
bool prepare_graph_built{}; /* Prepare 子图是否至少成功构建过一次。 */
|
bool prepare_graph_built{}; /* Prepare 子图是否至少成功构建过一次。 */
|
||||||
bool paint_graph_built{}; /* Paint 子图是否至少成功构建过一次。 */
|
bool paint_graph_built{}; /* Paint 子图是否至少成功构建过一次。 */
|
||||||
/* CRTP 可覆盖:决定已选中子图模式的 Prepare 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */
|
/* CRTP 可覆盖:决定已选中子图模式的 Prepare 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */
|
||||||
bool should_rebuild_prepare_graph(Attached auto* object, const State& state);
|
bool should_rebuild_prepare_graph(Attached auto* object, const Prop& prop);
|
||||||
/* CRTP 可覆盖:决定已选中子图模式的 Paint 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */
|
/* CRTP 可覆盖:决定已选中子图模式的 Paint 子图是否重建;object 为最终对象,state 为当前发布状态;默认返回 false。 */
|
||||||
bool should_rebuild_paint_graph(Attached auto* object, const State& state);
|
bool should_rebuild_paint_graph(Attached auto* object, const Prop& prop);
|
||||||
/* CRTP 可覆盖:决定本次是否执行 Prepare;object 为最终对象,state 为当前发布状态,dirty 为 Prepare dirty;默认返回 dirty。 */
|
/* CRTP 可覆盖:决定本次是否执行 Prepare;object 为最终对象,state 为当前发布状态,dirty 为 Prepare dirty;默认返回 dirty。 */
|
||||||
bool should_prepare(Attached auto* object, const State& state, bool dirty);
|
bool should_prepare(Attached auto* object, const State& state, bool dirty);
|
||||||
/* CRTP 可覆盖:决定本次是否执行 Paint;object 为最终对象,state 为当前发布状态,dirty 为 Paint dirty;默认返回 dirty。 */
|
/* CRTP 可覆盖:决定本次是否执行 Paint;object 为最终对象,state 为当前发布状态,dirty 为 Paint dirty;默认返回 dirty。 */
|
||||||
@@ -63,10 +63,10 @@ void Renderable::run_prepare_data(Object* object) {
|
|||||||
};
|
};
|
||||||
walk_private<true, typename Object::Attached_Object>(object, callback);
|
walk_private<true, typename Object::Attached_Object>(object, callback);
|
||||||
}
|
}
|
||||||
inline bool Renderable::Private::should_rebuild_prepare_graph(Attached auto*, const State&) {
|
inline bool Renderable::Private::should_rebuild_prepare_graph(Attached auto*, const Prop&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
inline bool Renderable::Private::should_rebuild_paint_graph(Attached auto*, const State&) {
|
inline bool Renderable::Private::should_rebuild_paint_graph(Attached auto*, const Prop&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
inline bool Renderable::Private::should_prepare(Attached auto*, const State&, bool dirty) {
|
inline bool Renderable::Private::should_prepare(Attached auto*, const State&, bool dirty) {
|
||||||
@@ -117,7 +117,7 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
|||||||
[](Root* root) {
|
[](Root* root) {
|
||||||
auto* value = static_cast<Object*>(root);
|
auto* value = static_cast<Object*>(root);
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||||
return private_data.should_rebuild_prepare_graph(value, *private_data.state.current);
|
return private_data.should_rebuild_prepare_graph(value, *private_data.current);
|
||||||
},
|
},
|
||||||
[]() -> Private::Stage_Run {
|
[]() -> Private::Stage_Run {
|
||||||
if constexpr (Prepare_Data_Renderable<Object> && !No_Prepare_Renderable<Object>) {
|
if constexpr (Prepare_Data_Renderable<Object> && !No_Prepare_Renderable<Object>) {
|
||||||
@@ -135,7 +135,7 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
|||||||
return [](Root* root) {
|
return [](Root* root) {
|
||||||
auto* value = static_cast<Object*>(root);
|
auto* value = static_cast<Object*>(root);
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||||
return private_data.build_prepare_graph(value, *private_data.state.current);
|
return private_data.build_prepare_graph(value, *private_data.current);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -152,7 +152,7 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
|||||||
[](Root* root) {
|
[](Root* root) {
|
||||||
auto* value = static_cast<Object*>(root);
|
auto* value = static_cast<Object*>(root);
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||||
return private_data.should_rebuild_paint_graph(value, *private_data.state.current);
|
return private_data.should_rebuild_paint_graph(value, *private_data.current);
|
||||||
},
|
},
|
||||||
[]() -> Private::Stage_Run {
|
[]() -> Private::Stage_Run {
|
||||||
if constexpr (Paint_Data_Renderable<Object>) {
|
if constexpr (Paint_Data_Renderable<Object>) {
|
||||||
@@ -171,7 +171,7 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
|||||||
return [](Root* root) {
|
return [](Root* root) {
|
||||||
auto* value = static_cast<Object*>(root);
|
auto* value = static_cast<Object*>(root);
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
auto& private_data = static_cast<typename Object::Private&>(*value->d);
|
||||||
return private_data.build_paint_graph(value, *private_data.state.current);
|
return private_data.build_paint_graph(value, *private_data.current);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -187,7 +187,7 @@ inline void Renderable::bind_dependency_graph_object(Attached auto* object) {
|
|||||||
},
|
},
|
||||||
[](Root* root) {
|
[](Root* root) {
|
||||||
auto* value = static_cast<Object*>(root);
|
auto* value = static_cast<Object*>(root);
|
||||||
value->template notify_state<Renderable_State_Tag>();
|
value->template notify_state<Renderable::Base_Tag>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,16 +2,15 @@
|
|||||||
#include "renderable.hpp"
|
#include "renderable.hpp"
|
||||||
namespace aethera {
|
namespace aethera {
|
||||||
/* Scene 状态标签,用于访问和订阅 Scene::State。 */
|
/* Scene 状态标签,用于访问和订阅 Scene::State。 */
|
||||||
struct Scene_State_Tag {};
|
|
||||||
/*
|
/*
|
||||||
* Scene 汇总 Prepare/Paint 两张 Dependency_Graph 图并构建总 Taskflow。
|
* Scene 汇总 Prepare/Paint 两张 Dependency_Graph 图并构建总 Taskflow。
|
||||||
* 用户最终通过 Impl<Scene> 创建可使用实例;编辑 Dependency_Graph 后调用 advance() 提交结构变化,再调用 process(...) 执行当前场景。
|
* 用户最终通过 Impl<Scene> 创建可使用实例;编辑 Dependency_Graph 后调用 advance() 提交结构变化,再调用 process(...) 执行当前场景。
|
||||||
*/
|
*/
|
||||||
struct Scene : Def<Scene, Root, State_Type<Scene_State_Tag>, Dependency_Graph_Type<Prepare_Data_Tag, Renderable>, Dependency_Graph_Type<Paint_Tag, Renderable>> {
|
struct Scene : Def<Scene, Root, Dependency_Graph_Type<Prepare_Data_Tag, Renderable>, Dependency_Graph_Type<Paint_Tag, Renderable>> {
|
||||||
/* Scene 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */
|
/* Scene 当前没有额外发布属性;派生定义可在自己的 Prop 中继续追加字段。 */
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
/* Scene 每次 process(...) 后发布的总图结构与执行统计。 */
|
/* Scene 每次 process(...) 后发布的总图结构与执行统计。 */
|
||||||
struct State : Prev_State<Scene_State_Tag> {
|
struct State : Prev_State {
|
||||||
bool taskflow_rebuilt{}; /* 本次 process(...) 前的 advance 是否重新构建了总 Taskflow。 */
|
bool taskflow_rebuilt{}; /* 本次 process(...) 前的 advance 是否重新构建了总 Taskflow。 */
|
||||||
std::size_t renderable_count{}; /* Prepare/Paint 两张依赖图中去重后的 Renderable 数量。 */
|
std::size_t renderable_count{}; /* Prepare/Paint 两张依赖图中去重后的 Renderable 数量。 */
|
||||||
std::size_t taskflow_task_count{}; /* 当前总 Taskflow 中的任务节点数量。 */
|
std::size_t taskflow_task_count{}; /* 当前总 Taskflow 中的任务节点数量。 */
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ void Scene::Private::process(Object* object, Callback&& callback) requires std::
|
|||||||
auto& state = static_cast<State&>(*private_data.state.current);
|
auto& state = static_cast<State&>(*private_data.state.current);
|
||||||
state.taskflow_execution_time_ns = 0;
|
state.taskflow_execution_time_ns = 0;
|
||||||
if (runtime->taskflow && !runtime->taskflow->empty()) state.taskflow_execution_time_ns = detail::run_taskflow(*runtime->taskflow);
|
if (runtime->taskflow && !runtime->taskflow->empty()) state.taskflow_execution_time_ns = detail::run_taskflow(*runtime->taskflow);
|
||||||
object->template notify_state<Scene_State_Tag>();
|
object->template notify_state<Scene::Base_Tag>();
|
||||||
Result result;
|
Result result;
|
||||||
std::invoke(std::forward<Callback>(callback), std::as_const(result));
|
std::invoke(std::forward<Callback>(callback), std::as_const(result));
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ void Scene::Private::after_advance(Object* object,
|
|||||||
const Prop* current_prop,
|
const Prop* current_prop,
|
||||||
State_Access<State> current_states) {
|
State_Access<State> current_states) {
|
||||||
auto* resource = detail::task_memory_resource();
|
auto* resource = detail::task_memory_resource();
|
||||||
auto& scene_state = current_states.get<Scene_State_Tag>();
|
auto& scene_state = current_states.get<Scene::Base_Tag>();
|
||||||
scene_state.taskflow_rebuilt = false;
|
scene_state.taskflow_rebuilt = false;
|
||||||
std::pmr::unordered_set<Root*> advanced_objects{resource};
|
std::pmr::unordered_set<Root*> advanced_objects{resource};
|
||||||
advanced_objects.insert(object);
|
advanced_objects.insert(object);
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
#include "double_buffer/model.hpp"
|
#include "double_buffer/model.hpp"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
namespace {
|
namespace {
|
||||||
struct Node_State_Tag {};
|
|
||||||
struct Graph_State_Tag {};
|
|
||||||
struct Graph_Tag {};
|
struct Graph_Tag {};
|
||||||
struct Node_Object : double_buffer::Def<Node_Object, double_buffer::Root, double_buffer::State_Type<Node_State_Tag>> {
|
struct Node_Object : double_buffer::Def<Node_Object, double_buffer::Root> {
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop { int value{}; int other{}; };
|
||||||
struct State : Prev_State<Node_State_Tag> {
|
struct State : Prev_State {
|
||||||
int value{}; /* 字段级依赖测试使用的状态值。 */
|
int value{}; /* 字段级依赖测试使用的状态值。 */
|
||||||
int other{}; /* 状态层级依赖测试使用的同层其他值。 */
|
int other{}; /* 状态层级依赖测试使用的同层其他值。 */
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
@@ -14,9 +12,9 @@ struct Node_Object : double_buffer::Def<Node_Object, double_buffer::Root, double
|
|||||||
struct Private : Prev_Private {};
|
struct Private : Prev_Private {};
|
||||||
};
|
};
|
||||||
using Node = double_buffer::Impl<Node_Object>;
|
using Node = double_buffer::Impl<Node_Object>;
|
||||||
struct Graph_Object : double_buffer::Def<Graph_Object, double_buffer::Root, double_buffer::State_Type<Graph_State_Tag>, double_buffer::Dependency_Graph_Type<Graph_Tag, Node_Object>> {
|
struct Graph_Object : double_buffer::Def<Graph_Object, double_buffer::Root, double_buffer::Dependency_Graph_Type<Graph_Tag, Node_Object>> {
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
struct State : Prev_State<Graph_State_Tag> {
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
};
|
};
|
||||||
struct Private : Prev_Private {};
|
struct Private : Prev_Private {};
|
||||||
@@ -35,7 +33,8 @@ std::unique_ptr<Object_Type> build_object() {
|
|||||||
static_assert(!double_buffer::detail::Dependency_Object<Node_Object>);
|
static_assert(!double_buffer::detail::Dependency_Object<Node_Object>);
|
||||||
static_assert(double_buffer::detail::Dependency_Object<Node>);
|
static_assert(double_buffer::detail::Dependency_Object<Node>);
|
||||||
static_assert(!double_buffer::detail::State_Dependency_Source<Node_Object, &Node_Object::State::value>);
|
static_assert(!double_buffer::detail::State_Dependency_Source<Node_Object, &Node_Object::State::value>);
|
||||||
static_assert(double_buffer::detail::State_Layer_Dependency_Source<Node, Node_State_Tag>);
|
static_assert(double_buffer::detail::State_Layer_Dependency_Source<Node, Node_Object::Base_Tag>);
|
||||||
|
static_assert(double_buffer::detail::Prop_Layer_Dependency_Source<Node, Node_Object::Base_Tag>);
|
||||||
}
|
}
|
||||||
TEST(dependency_graph_storage, edited_graph_commits_and_stays_synchronized) {
|
TEST(dependency_graph_storage, edited_graph_commits_and_stays_synchronized) {
|
||||||
auto first = build_object<Node>();
|
auto first = build_object<Node>();
|
||||||
@@ -111,7 +110,7 @@ TEST(dependency_graph, state_dependency_granularity_is_selectable) {
|
|||||||
editor.add(layer_source.get());
|
editor.add(layer_source.get());
|
||||||
editor.add(layer_target.get());
|
editor.add(layer_target.get());
|
||||||
editor.template add_dependency<&Node_Object::State::value>(member_target.get(), member_source.get());
|
editor.template add_dependency<&Node_Object::State::value>(member_target.get(), member_source.get());
|
||||||
editor.template add_state_dependency<Node_State_Tag>(layer_target.get(), layer_source.get());
|
editor.template add_state_dependency<Node_Object::Base_Tag>(layer_target.get(), layer_source.get());
|
||||||
}
|
}
|
||||||
).has_value());
|
).has_value());
|
||||||
graph->advance();
|
graph->advance();
|
||||||
@@ -122,6 +121,28 @@ TEST(dependency_graph, state_dependency_granularity_is_selectable) {
|
|||||||
layer_source->update_state<&Node_Object::State::other>(3);
|
layer_source->update_state<&Node_Object::State::other>(3);
|
||||||
EXPECT_TRUE(layer_target->dirty<Graph_Tag>());
|
EXPECT_TRUE(layer_target->dirty<Graph_Tag>());
|
||||||
}
|
}
|
||||||
|
TEST(dependency_graph, prop_dependency_granularity_is_selectable) {
|
||||||
|
auto member_source = build_object<Node>();
|
||||||
|
auto member_target = build_object<Node>();
|
||||||
|
auto layer_source = build_object<Node>();
|
||||||
|
auto layer_target = build_object<Node>();
|
||||||
|
auto graph = build_object<Graph>();
|
||||||
|
ASSERT_TRUE(graph->edit_dependency_graph<Graph_Tag>([&](auto& editor) {
|
||||||
|
editor.add(member_source.get());
|
||||||
|
editor.add(member_target.get());
|
||||||
|
editor.add(layer_source.get());
|
||||||
|
editor.add(layer_target.get());
|
||||||
|
editor.template add_prop_dependency<&Node_Object::Prop::value>(member_target.get(), member_source.get());
|
||||||
|
editor.template add_prop_dependency<Node_Object::Base_Tag>(layer_target.get(), layer_source.get());
|
||||||
|
}).has_value());
|
||||||
|
graph->advance();
|
||||||
|
member_source->set<&Node_Object::Prop::other>(1);
|
||||||
|
EXPECT_FALSE(member_target->dirty<Graph_Tag>());
|
||||||
|
member_source->set<&Node_Object::Prop::value>(2);
|
||||||
|
EXPECT_TRUE(member_target->dirty<Graph_Tag>());
|
||||||
|
layer_source->set<&Node_Object::Prop::other>(3);
|
||||||
|
EXPECT_TRUE(layer_target->dirty<Graph_Tag>());
|
||||||
|
}
|
||||||
TEST(dependency_graph, cycle_is_rejected_before_pending_graph_commit) {
|
TEST(dependency_graph, cycle_is_rejected_before_pending_graph_commit) {
|
||||||
auto first = build_object<Node>();
|
auto first = build_object<Node>();
|
||||||
auto second = build_object<Node>();
|
auto second = build_object<Node>();
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
#include "double_buffer/model.hpp"
|
#include "double_buffer/model.hpp"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
namespace {
|
namespace {
|
||||||
struct Object_State_Tag {};
|
|
||||||
struct Object_Buffer_Tag {};
|
struct Object_Buffer_Tag {};
|
||||||
struct Test_Object : double_buffer::Def<Test_Object, double_buffer::Root, double_buffer::State_Type<Object_State_Tag>, double_buffer::Tagged_Buffer<Object_Buffer_Tag, int>> {
|
struct Test_Object : double_buffer::Def<Test_Object, double_buffer::Root, double_buffer::Tagged_Buffer<Object_Buffer_Tag, int>> {
|
||||||
struct Prop : Prev_Prop {
|
struct Prop : Prev_Prop {
|
||||||
int first{};
|
int first{};
|
||||||
int second{};
|
int second{};
|
||||||
};
|
};
|
||||||
struct State : Prev_State<Object_State_Tag> {
|
struct State : Prev_State {
|
||||||
int first{};
|
int first{};
|
||||||
int second{};
|
int second{};
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
@@ -20,20 +19,18 @@ using Object = double_buffer::Impl<Test_Object>;
|
|||||||
inline auto& Test_Object::data_for_test() {
|
inline auto& Test_Object::data_for_test() {
|
||||||
return static_cast<Object::Private&>(*d);
|
return static_cast<Object::Private&>(*d);
|
||||||
}
|
}
|
||||||
struct Derived_State_Tag {};
|
struct Derived_Object : double_buffer::Def<Derived_Object, Test_Object> {
|
||||||
struct Derived_Object : double_buffer::Def<Derived_Object, Test_Object, double_buffer::State_Type<Derived_State_Tag>> {
|
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
struct State : Prev_State<Derived_State_Tag> {
|
struct State : Prev_State {
|
||||||
int derived{};
|
int derived{};
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
};
|
};
|
||||||
struct Private : Prev_Private {};
|
struct Private : Prev_Private {};
|
||||||
};
|
};
|
||||||
using Derived = double_buffer::Impl<Derived_Object>;
|
using Derived = double_buffer::Impl<Derived_Object>;
|
||||||
struct Lifetime_State_Tag {};
|
struct Lifetime_Object : double_buffer::Def<Lifetime_Object, double_buffer::Root> {
|
||||||
struct Lifetime_Object : double_buffer::Def<Lifetime_Object, double_buffer::Root, double_buffer::State_Type<Lifetime_State_Tag>> {
|
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
struct State : Prev_State<Lifetime_State_Tag> {
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
};
|
};
|
||||||
struct Private : Prev_Private {
|
struct Private : Prev_Private {
|
||||||
@@ -84,13 +81,13 @@ TEST(object_buffer, state_commits_and_keeps_incremental_baseline) {
|
|||||||
}
|
}
|
||||||
TEST(object_buffer, prop_publishes_and_keeps_incremental_baseline) {
|
TEST(object_buffer, prop_publishes_and_keeps_incremental_baseline) {
|
||||||
auto object = build_object<Object>();
|
auto object = build_object<Object>();
|
||||||
object->set(&Test_Object::Prop::first, 17);
|
object->set<&Test_Object::Prop::first>(17);
|
||||||
EXPECT_EQ(object->data_for_test().current->first, 17);
|
EXPECT_EQ(object->data_for_test().current->first, 17);
|
||||||
EXPECT_EQ(object->data_for_test().pending->first, 0);
|
EXPECT_EQ(object->data_for_test().pending->first, 0);
|
||||||
object->advance();
|
object->advance();
|
||||||
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
||||||
EXPECT_EQ(object->data_for_test().current->first, 17);
|
EXPECT_EQ(object->data_for_test().current->first, 17);
|
||||||
object->set(&Test_Object::Prop::second, 19);
|
object->set<&Test_Object::Prop::second>(19);
|
||||||
object->advance();
|
object->advance();
|
||||||
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
EXPECT_EQ(object->data_for_test().pending->first, 17);
|
||||||
EXPECT_EQ(object->data_for_test().pending->second, 19);
|
EXPECT_EQ(object->data_for_test().pending->second, 19);
|
||||||
@@ -98,7 +95,7 @@ TEST(object_buffer, prop_publishes_and_keeps_incremental_baseline) {
|
|||||||
TEST(state_tag, callback_publishes_only_requested_layer) {
|
TEST(state_tag, callback_publishes_only_requested_layer) {
|
||||||
auto object = build_object<Object>();
|
auto object = build_object<Object>();
|
||||||
int calls = 0;
|
int calls = 0;
|
||||||
object->set_state_callback<Object_State_Tag>(
|
object->set_state_callback<Test_Object::Base_Tag>(
|
||||||
[&](const auto& state) {
|
[&](const auto& state) {
|
||||||
++calls;
|
++calls;
|
||||||
EXPECT_EQ(state.first, 23);
|
EXPECT_EQ(state.first, 23);
|
||||||
@@ -107,14 +104,14 @@ TEST(state_tag, callback_publishes_only_requested_layer) {
|
|||||||
object->update_state<&Test_Object::State::first>(23);
|
object->update_state<&Test_Object::State::first>(23);
|
||||||
object->advance();
|
object->advance();
|
||||||
EXPECT_EQ(calls, 0);
|
EXPECT_EQ(calls, 0);
|
||||||
object->notify_state<Object_State_Tag>();
|
object->notify_state<Test_Object::Base_Tag>();
|
||||||
EXPECT_EQ(calls, 1);
|
EXPECT_EQ(calls, 1);
|
||||||
}
|
}
|
||||||
static_assert(requires(Object& object) {
|
static_assert(requires(Object& object) {
|
||||||
object.template access_state<Object_State_Tag>([](const auto&) {});
|
object.template access_state<Test_Object::Base_Tag>([](const auto&) {});
|
||||||
});
|
});
|
||||||
static_assert(std::same_as<
|
static_assert(std::same_as<
|
||||||
decltype(std::declval<const Object&>().template read_state<Object_State_Tag>()),
|
decltype(std::declval<const Object&>().template read_state<Test_Object::Base_Tag>()),
|
||||||
const Test_Object::State&
|
const Test_Object::State&
|
||||||
>);
|
>);
|
||||||
struct Missing_State_Tag {};
|
struct Missing_State_Tag {};
|
||||||
@@ -123,12 +120,12 @@ TEST(state_tag, inherited_tags_remain_independently_addressable) {
|
|||||||
auto object = build_object<Derived>();
|
auto object = build_object<Derived>();
|
||||||
int base_calls = 0;
|
int base_calls = 0;
|
||||||
int derived_calls = 0;
|
int derived_calls = 0;
|
||||||
object->set_state_callback<Object_State_Tag>([&](const auto&) { ++base_calls; });
|
object->set_state_callback<Test_Object::Base_Tag>([&](const auto&) { ++base_calls; });
|
||||||
object->set_state_callback<Derived_State_Tag>([&](const auto&) { ++derived_calls; });
|
object->set_state_callback<Derived_Object::Base_Tag>([&](const auto&) { ++derived_calls; });
|
||||||
object->notify_state<Object_State_Tag>();
|
object->notify_state<Test_Object::Base_Tag>();
|
||||||
EXPECT_EQ(base_calls, 1);
|
EXPECT_EQ(base_calls, 1);
|
||||||
EXPECT_EQ(derived_calls, 0);
|
EXPECT_EQ(derived_calls, 0);
|
||||||
object->notify_state<Derived_State_Tag>();
|
object->notify_state<Derived_Object::Base_Tag>();
|
||||||
EXPECT_EQ(base_calls, 1);
|
EXPECT_EQ(base_calls, 1);
|
||||||
EXPECT_EQ(derived_calls, 1);
|
EXPECT_EQ(derived_calls, 1);
|
||||||
}
|
}
|
||||||
@@ -137,31 +134,31 @@ TEST(state_tag, committed_layer_is_directly_readable_by_tag) {
|
|||||||
object->update_state<&Test_Object::State::first>(41);
|
object->update_state<&Test_Object::State::first>(41);
|
||||||
object->update_state<&Derived_Object::State::derived>(43);
|
object->update_state<&Derived_Object::State::derived>(43);
|
||||||
object->advance();
|
object->advance();
|
||||||
EXPECT_EQ(object->read_state<Object_State_Tag>().first, 41);
|
EXPECT_EQ(object->read_state<Test_Object::Base_Tag>().first, 41);
|
||||||
EXPECT_EQ(object->read_state<Derived_State_Tag>().derived, 43);
|
EXPECT_EQ(object->read_state<Derived_Object::Base_Tag>().derived, 43);
|
||||||
}
|
}
|
||||||
static_assert(double_buffer::detail::State_Tag_In<double_buffer::Root_State_Tag, Derived::States>);
|
static_assert(double_buffer::detail::State_Tag_In<double_buffer::Root::Base_Tag, Derived::States>);
|
||||||
static_assert(double_buffer::detail::State_Tag_In<Object_State_Tag, Derived::States>);
|
static_assert(double_buffer::detail::State_Tag_In<Test_Object::Base_Tag, Derived::States>);
|
||||||
static_assert(double_buffer::detail::State_Tag_In<Derived_State_Tag, Derived::States>);
|
static_assert(double_buffer::detail::State_Tag_In<Derived_Object::Base_Tag, Derived::States>);
|
||||||
static_assert(std::same_as<
|
static_assert(std::same_as<
|
||||||
decltype(std::declval<double_buffer::State_Access<Derived::State>>().template get<Object_State_Tag>()),
|
decltype(std::declval<double_buffer::State_Access<Derived::State>>().template get<Test_Object::Base_Tag>()),
|
||||||
Test_Object::State&
|
Test_Object::State&
|
||||||
>);
|
>);
|
||||||
static_assert(std::same_as<
|
static_assert(std::same_as<
|
||||||
decltype(std::declval<double_buffer::State_Access<const Derived::State>>().template get<Derived_State_Tag>()),
|
decltype(std::declval<double_buffer::State_Access<const Derived::State>>().template get<Derived_Object::Base_Tag>()),
|
||||||
const Derived_Object::State&
|
const Derived_Object::State&
|
||||||
>);
|
>);
|
||||||
TEST(state_tag, state_access_selects_mutable_and_const_layers_by_tag) {
|
TEST(state_tag, state_access_selects_mutable_and_const_layers_by_tag) {
|
||||||
Derived::State state;
|
Derived::State state;
|
||||||
double_buffer::State_Access states{state};
|
double_buffer::State_Access states{state};
|
||||||
states.get<Object_State_Tag>().first = 31;
|
states.get<Test_Object::Base_Tag>().first = 31;
|
||||||
states.get<Derived_State_Tag>().derived = 47;
|
states.get<Derived_Object::Base_Tag>().derived = 47;
|
||||||
double_buffer::State_Access<Test_Object::State> base_states = states;
|
double_buffer::State_Access<Test_Object::State> base_states = states;
|
||||||
EXPECT_EQ(base_states.get<Object_State_Tag>().first, 31);
|
EXPECT_EQ(base_states.get<Test_Object::Base_Tag>().first, 31);
|
||||||
const auto& const_state = state;
|
const auto& const_state = state;
|
||||||
double_buffer::State_Access current_states{const_state};
|
double_buffer::State_Access current_states{const_state};
|
||||||
EXPECT_EQ(current_states.get<Object_State_Tag>().first, 31);
|
EXPECT_EQ(current_states.get<Test_Object::Base_Tag>().first, 31);
|
||||||
EXPECT_EQ(current_states.get<Derived_State_Tag>().derived, 47);
|
EXPECT_EQ(current_states.get<Derived_Object::Base_Tag>().derived, 47);
|
||||||
}
|
}
|
||||||
TEST(state_tag, state_chain_keeps_default_equality_usable) {
|
TEST(state_tag, state_chain_keeps_default_equality_usable) {
|
||||||
Test_Object::State first;
|
Test_Object::State first;
|
||||||
@@ -170,3 +167,21 @@ TEST(state_tag, state_chain_keeps_default_equality_usable) {
|
|||||||
second.first = 1;
|
second.first = 1;
|
||||||
EXPECT_FALSE(first == second);
|
EXPECT_FALSE(first == second);
|
||||||
}
|
}
|
||||||
|
static_assert(std::same_as<
|
||||||
|
decltype(std::declval<double_buffer::Prop_Access<Derived::Prop>>().template get<Test_Object::Base_Tag>()),
|
||||||
|
Test_Object::Prop&
|
||||||
|
>);
|
||||||
|
static_assert(std::same_as<
|
||||||
|
decltype(std::declval<double_buffer::Private_Access<Derived_Object::Private>>().template get<Test_Object::Base_Tag>()),
|
||||||
|
Test_Object::Private&
|
||||||
|
>);
|
||||||
|
TEST(base_tag, prop_state_and_private_domains_are_independently_addressable) {
|
||||||
|
Derived::Prop prop;
|
||||||
|
double_buffer::Prop_Access props{prop};
|
||||||
|
props.get<Test_Object::Base_Tag>().first = 53;
|
||||||
|
EXPECT_EQ(props.get<Test_Object::Base_Tag>().first, 53);
|
||||||
|
Derived_Object::Private private_data;
|
||||||
|
double_buffer::Private_Access private_layers{private_data};
|
||||||
|
EXPECT_EQ(&private_layers.get<Test_Object::Base_Tag>(), static_cast<Test_Object::Private*>(&private_data));
|
||||||
|
EXPECT_EQ(&private_layers.get<Derived_Object::Base_Tag>(), static_cast<Derived_Object::Private*>(&private_data));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#include "scene.hpp"
|
#include "scene.hpp"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
namespace {
|
namespace {
|
||||||
struct Direct_State_Tag {};
|
struct Direct_Renderable : double_buffer::Def<Direct_Renderable, aethera::Renderable> {
|
||||||
struct Graph_State_Tag {};
|
|
||||||
struct Direct_Renderable : double_buffer::Def<Direct_Renderable, aethera::Renderable, double_buffer::State_Type<Direct_State_Tag>> {
|
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
struct State : Prev_State<Direct_State_Tag> {
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
};
|
};
|
||||||
struct Private : Prev_Private {
|
struct Private : Prev_Private {
|
||||||
@@ -20,9 +18,9 @@ struct Direct_Renderable : double_buffer::Def<Direct_Renderable, aethera::Render
|
|||||||
};
|
};
|
||||||
Private& data_for_test();
|
Private& data_for_test();
|
||||||
};
|
};
|
||||||
struct Graph_Renderable : double_buffer::Def<Graph_Renderable, aethera::Renderable, double_buffer::State_Type<Graph_State_Tag>> {
|
struct Graph_Renderable : double_buffer::Def<Graph_Renderable, aethera::Renderable> {
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
struct State : Prev_State<Graph_State_Tag> {
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
};
|
};
|
||||||
struct Private : Prev_Private {
|
struct Private : Prev_Private {
|
||||||
@@ -30,13 +28,13 @@ struct Graph_Renderable : double_buffer::Def<Graph_Renderable, aethera::Renderab
|
|||||||
int paint_calls{};
|
int paint_calls{};
|
||||||
int prepare_builds{};
|
int prepare_builds{};
|
||||||
bool rebuild_prepare{};
|
bool rebuild_prepare{};
|
||||||
tf::Taskflow build_prepare_graph(double_buffer::Attached auto*, const State&) {
|
tf::Taskflow build_prepare_graph(double_buffer::Attached auto*, const Prop&) {
|
||||||
++prepare_builds;
|
++prepare_builds;
|
||||||
tf::Taskflow graph;
|
tf::Taskflow graph;
|
||||||
graph.emplace([this] { ++prepare_calls; }).name("test.prepare.graph.task");
|
graph.emplace([this] { ++prepare_calls; }).name("test.prepare.graph.task");
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
bool should_rebuild_prepare_graph(double_buffer::Attached auto*, const State&) {
|
bool should_rebuild_prepare_graph(double_buffer::Attached auto*, const Prop&) {
|
||||||
return std::exchange(rebuild_prepare, false);
|
return std::exchange(rebuild_prepare, false);
|
||||||
}
|
}
|
||||||
void paint(double_buffer::Attached auto*) {
|
void paint(double_buffer::Attached auto*) {
|
||||||
@@ -54,10 +52,9 @@ inline Direct_Renderable::Private& Direct_Renderable::data_for_test() {
|
|||||||
inline Graph_Renderable::Private& Graph_Renderable::data_for_test() {
|
inline Graph_Renderable::Private& Graph_Renderable::data_for_test() {
|
||||||
return static_cast<Private&>(*d);
|
return static_cast<Private&>(*d);
|
||||||
}
|
}
|
||||||
struct Dependency_State_Tag {};
|
struct Dependency_Renderable : double_buffer::Def<Dependency_Renderable, aethera::Renderable> {
|
||||||
struct Dependency_Renderable : double_buffer::Def<Dependency_Renderable, aethera::Renderable, double_buffer::State_Type<Dependency_State_Tag>> {
|
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
struct State : Prev_State<Dependency_State_Tag> {
|
struct State : Prev_State {
|
||||||
int revision{};
|
int revision{};
|
||||||
bool operator==(const State&) const = default;
|
bool operator==(const State&) const = default;
|
||||||
};
|
};
|
||||||
@@ -141,12 +138,12 @@ TEST(renderable_state, scene_and_renderable_callbacks_publish_at_stage_boundarie
|
|||||||
int renderable_updates = 0;
|
int renderable_updates = 0;
|
||||||
int scene_updates = 0;
|
int scene_updates = 0;
|
||||||
int runtime_updates = 0;
|
int runtime_updates = 0;
|
||||||
renderable->set_state_callback<aethera::Renderable_State_Tag>([&](const auto& state) {
|
renderable->set_state_callback<aethera::Renderable::Base_Tag>([&](const auto& state) {
|
||||||
++renderable_updates;
|
++renderable_updates;
|
||||||
EXPECT_TRUE(state.prepare_executed);
|
EXPECT_TRUE(state.prepare_executed);
|
||||||
EXPECT_TRUE(state.paint_executed);
|
EXPECT_TRUE(state.paint_executed);
|
||||||
});
|
});
|
||||||
scene->set_state_callback<aethera::Scene_State_Tag>([&](const auto& state) {
|
scene->set_state_callback<aethera::Scene::Base_Tag>([&](const auto& state) {
|
||||||
++scene_updates;
|
++scene_updates;
|
||||||
EXPECT_GT(state.taskflow_task_count, 0u);
|
EXPECT_GT(state.taskflow_task_count, 0u);
|
||||||
});
|
});
|
||||||
@@ -169,7 +166,7 @@ TEST(scene_state, structural_statistics_survive_a_process_without_rebuild) {
|
|||||||
std::size_t task_count{};
|
std::size_t task_count{};
|
||||||
std::size_t dependency_count{};
|
std::size_t dependency_count{};
|
||||||
int updates{};
|
int updates{};
|
||||||
scene->set_state_callback<aethera::Scene_State_Tag>([&](const auto& state) {
|
scene->set_state_callback<aethera::Scene::Base_Tag>([&](const auto& state) {
|
||||||
EXPECT_EQ(state.renderable_count, 1u);
|
EXPECT_EQ(state.renderable_count, 1u);
|
||||||
EXPECT_GT(state.taskflow_task_count, 0u);
|
EXPECT_GT(state.taskflow_task_count, 0u);
|
||||||
if (updates == 0) {
|
if (updates == 0) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
bool Abs_Axis::State::operator==(const State&) const = default;
|
bool Abs_Axis::State::operator==(const State&) const = default;
|
||||||
|
bool Abs_Axis::Prop::operator==(const Prop&) const = default;
|
||||||
|
|
||||||
Axis_Range Abs_Axis::Private::coordinate_range(const Root* object) const {
|
Axis_Range Abs_Axis::Private::coordinate_range(const Root* object) const {
|
||||||
return dispatch->coordinate_range(object);
|
return dispatch->coordinate_range(object);
|
||||||
|
|||||||
@@ -4,10 +4,8 @@
|
|||||||
#include <renderable.hpp>
|
#include <renderable.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
/* Abs_Axis 状态标签,用于访问和订阅坐标轴布局状态。 */
|
|
||||||
struct Abs_Axis_State_Tag {};
|
|
||||||
struct Abs_Axis;
|
struct Abs_Axis;
|
||||||
/* 最终轴 Private 的完整计算能力契约;所有结果直接来自当前 State。 */
|
/* 最终轴 Private 的完整计算能力契约;所有结果直接来自当前 Prop。 */
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept Axis_Object = Renderable_Object<T> && std::derived_from<T, Abs_Axis> && requires(
|
concept Axis_Object = Renderable_Object<T> && std::derived_from<T, Abs_Axis> && requires(
|
||||||
const typename T::Private& private_data,
|
const typename T::Private& private_data,
|
||||||
@@ -21,12 +19,9 @@ concept Axis_Object = Renderable_Object<T> && std::derived_from<T, Abs_Axis> &&
|
|||||||
{ private_data.sub_tick_count(object, tick) } -> std::same_as<Axis_Tick_Count>;
|
{ private_data.sub_tick_count(object, tick) } -> std::same_as<Axis_Tick_Count>;
|
||||||
};
|
};
|
||||||
/* 所有二维坐标轴共享的定义层;最终通过 Impl<Derived_Axis> 创建运行时对象。 */
|
/* 所有二维坐标轴共享的定义层;最终通过 Impl<Derived_Axis> 创建运行时对象。 */
|
||||||
struct Abs_Axis : Def<Abs_Axis, Renderable, State_Type<Abs_Axis_State_Tag>,
|
struct Abs_Axis : Def<Abs_Axis, Renderable,
|
||||||
Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||||
/* Abs_Axis 不发布额外属性。 */
|
struct Prop : Prev_Prop {
|
||||||
struct Prop : Prev_Prop {};
|
|
||||||
/* 双缓冲交换后供轴计算直接读取的布局权威状态。 */
|
|
||||||
struct State : Prev_State<Abs_Axis_State_Tag> {
|
|
||||||
Point_F position{}; /* 坐标轴起点在画布中的二维像素位置。 */
|
Point_F position{}; /* 坐标轴起点在画布中的二维像素位置。 */
|
||||||
Size canvas_size{}; /* 颜色缓存与裁剪区域使用的画布像素尺寸。 */
|
Size canvas_size{}; /* 颜色缓存与裁剪区域使用的画布像素尺寸。 */
|
||||||
Axis_Pixel_Length pixel_length{}; /* 从坐标起点到终点的轴向像素跨度;为 0 时反算返回坐标起点。 */
|
Axis_Pixel_Length pixel_length{}; /* 从坐标起点到终点的轴向像素跨度;为 0 时反算返回坐标起点。 */
|
||||||
@@ -39,11 +34,15 @@ struct Abs_Axis : Def<Abs_Axis, Renderable, State_Type<Abs_Axis_State_Tag>,
|
|||||||
Pen unit_text_pen{Color::white()}; /* 刻度标签和单位文本使用的前景样式。 */
|
Pen unit_text_pen{Color::white()}; /* 刻度标签和单位文本使用的前景样式。 */
|
||||||
Brush unit_text_background_brush{}; /* 单位文本背景填充;none 表示不填充。 */
|
Brush unit_text_background_brush{}; /* 单位文本背景填充;none 表示不填充。 */
|
||||||
Axis_Label_Rotation label_rotation_degrees{}; /* 刻度标签顺时针旋转角度,单位为度。 */
|
Axis_Label_Rotation label_rotation_degrees{}; /* 刻度标签顺时针旋转角度,单位为度。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
/* 坐标轴当前没有额外发布状态。 */
|
||||||
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
/* 完整声明及派生轴 CRTP 能力契约见 Abs_Axis.ipp。 */
|
/* 完整声明及派生轴 CRTP 能力契约见 Abs_Axis.ipp。 */
|
||||||
struct Private;
|
struct Private;
|
||||||
/* 返回由最终轴 State 计算得到的当前有向坐标区间。 */
|
/* 返回由最终轴 Prop 计算得到的当前有向坐标区间。 */
|
||||||
[[nodiscard]] Axis_Range coordinate_range() const;
|
[[nodiscard]] Axis_Range coordinate_range() const;
|
||||||
/* 将坐标值映射到当前轴向像素位置。 */
|
/* 将坐标值映射到当前轴向像素位置。 */
|
||||||
[[nodiscard]] Axis_Pixel_Position coordinate_to_pixel(Axis_Coordinate coordinate) const;
|
[[nodiscard]] Axis_Pixel_Position coordinate_to_pixel(Axis_Coordinate coordinate) const;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ struct Abs_Axis::Private : Prev_Private {
|
|||||||
* double tick_step(const T* object, Axis_Range coordinate_range) const:返回主刻度步长。
|
* double tick_step(const T* object, Axis_Range coordinate_range) const:返回主刻度步长。
|
||||||
* std::string tick_label(const T* object, double tick) const:返回主刻度显示文本。
|
* std::string tick_label(const T* object, double tick) const:返回主刻度显示文本。
|
||||||
* int sub_tick_count(const T* object, double major_step) const:返回次刻度数量;默认固定返回 4。
|
* int sub_tick_count(const T* object, double major_step) const:返回次刻度数量;默认固定返回 4。
|
||||||
* 坐标映射直接读取 Abs_Axis_State_Tag 状态并调用最终 Private 的 coordinate_range(...),不保存变换快照。
|
* 坐标映射直接读取 Abs_Axis::Base_Tag 状态并调用最终 Private 的 coordinate_range(...),不保存变换快照。
|
||||||
*/
|
*/
|
||||||
using Coordinate_Range_Call = Axis_Range (*)(const Root*);
|
using Coordinate_Range_Call = Axis_Range (*)(const Root*);
|
||||||
using Scalar_Call = double (*)(const Root*, double);
|
using Scalar_Call = double (*)(const Root*, double);
|
||||||
@@ -81,7 +81,7 @@ const Abs_Axis::Private::Dispatch& Abs_Axis::Private::dispatch_for() {
|
|||||||
[](const Root* root, double coordinate) {
|
[](const Root* root, double coordinate) {
|
||||||
auto* object = static_cast<const Object*>(root);
|
auto* object = static_cast<const Object*>(root);
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
||||||
const auto& axis_state = static_cast<const State&>(*private_data.state.current);
|
const auto& axis_state = static_cast<const Prop&>(*private_data.current);
|
||||||
const Axis_Range range = private_data.coordinate_range(object);
|
const Axis_Range range = private_data.coordinate_range(object);
|
||||||
const double coordinate_length = range.length();
|
const double coordinate_length = range.length();
|
||||||
const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal
|
const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal
|
||||||
@@ -92,7 +92,7 @@ const Abs_Axis::Private::Dispatch& Abs_Axis::Private::dispatch_for() {
|
|||||||
[](const Root* root, double pixel) {
|
[](const Root* root, double pixel) {
|
||||||
auto* object = static_cast<const Object*>(root);
|
auto* object = static_cast<const Object*>(root);
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
||||||
const auto& axis_state = static_cast<const State&>(*private_data.state.current);
|
const auto& axis_state = static_cast<const Prop&>(*private_data.current);
|
||||||
const Axis_Range range = private_data.coordinate_range(object);
|
const Axis_Range range = private_data.coordinate_range(object);
|
||||||
if (axis_state.pixel_length == 0.0) return range.origin;
|
if (axis_state.pixel_length == 0.0) return range.origin;
|
||||||
const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal
|
const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal
|
||||||
@@ -102,7 +102,7 @@ const Abs_Axis::Private::Dispatch& Abs_Axis::Private::dispatch_for() {
|
|||||||
[](const Root* root, Point_F point) {
|
[](const Root* root, Point_F point) {
|
||||||
auto* object = static_cast<const Object*>(root);
|
auto* object = static_cast<const Object*>(root);
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
||||||
const auto& axis_state = static_cast<const State&>(*private_data.state.current);
|
const auto& axis_state = static_cast<const Prop&>(*private_data.current);
|
||||||
const double pixel = axis_state.orientation == Axis_Orientation::horizontal ? point.x : point.y;
|
const double pixel = axis_state.orientation == Axis_Orientation::horizontal ? point.x : point.y;
|
||||||
const Axis_Range range = private_data.coordinate_range(object);
|
const Axis_Range range = private_data.coordinate_range(object);
|
||||||
if (axis_state.pixel_length == 0.0) return range.origin;
|
if (axis_state.pixel_length == 0.0) return range.origin;
|
||||||
@@ -113,7 +113,7 @@ const Abs_Axis::Private::Dispatch& Abs_Axis::Private::dispatch_for() {
|
|||||||
[](const Root* root, Axis_Range coordinate_range) {
|
[](const Root* root, Axis_Range coordinate_range) {
|
||||||
auto* object = static_cast<const Object*>(root);
|
auto* object = static_cast<const Object*>(root);
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
const auto& private_data = static_cast<const typename Object::Private&>(*object->d);
|
||||||
const auto& axis_state = static_cast<const State&>(*private_data.state.current);
|
const auto& axis_state = static_cast<const Prop&>(*private_data.current);
|
||||||
const Axis_Range range = private_data.coordinate_range(object);
|
const Axis_Range range = private_data.coordinate_range(object);
|
||||||
const auto map = [&](double coordinate) {
|
const auto map = [&](double coordinate) {
|
||||||
const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal
|
const double pixel_origin = axis_state.orientation == Axis_Orientation::horizontal
|
||||||
@@ -151,7 +151,7 @@ void Abs_Axis::Private::bind_private_crtp(Object* object) {
|
|||||||
inline void Abs_Axis::Private::prepare_data(Attached auto* object) {
|
inline void Abs_Axis::Private::prepare_data(Attached auto* object) {
|
||||||
using Object = std::remove_pointer_t<decltype(object)>;
|
using Object = std::remove_pointer_t<decltype(object)>;
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const Prop&>(*private_data.current);
|
||||||
auto& output = prepared;
|
auto& output = prepared;
|
||||||
output = {};
|
output = {};
|
||||||
if (state.pixel_length == 0.0 || state.canvas_size.empty()) return;
|
if (state.pixel_length == 0.0 || state.canvas_size.empty()) return;
|
||||||
@@ -206,7 +206,7 @@ inline void Abs_Axis::Private::prepare_data(Attached auto* object) {
|
|||||||
inline void Abs_Axis::Private::paint(Attached auto* object) {
|
inline void Abs_Axis::Private::paint(Attached auto* object) {
|
||||||
using Object = std::remove_pointer_t<decltype(object)>;
|
using Object = std::remove_pointer_t<decltype(object)>;
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const Prop&>(*private_data.current);
|
||||||
auto& cache = object->template pending_buffer<Color_Cache>();
|
auto& cache = object->template pending_buffer<Color_Cache>();
|
||||||
cache.ensure_size(state.canvas_size);
|
cache.ensure_size(state.canvas_size);
|
||||||
cache.clear();
|
cache.clear();
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Numeric_Axis.hpp"
|
#include "Numeric_Axis.hpp"
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
/* Frequency_Axis 状态标签,用于独立订阅频率轴层。 */
|
|
||||||
struct Frequency_Axis_State_Tag {};
|
|
||||||
/* 根据数值量级自动选择 Hz、kHz 或 MHz 标签的数值轴。 */
|
/* 根据数值量级自动选择 Hz、kHz 或 MHz 标签的数值轴。 */
|
||||||
struct Frequency_Axis : Def<Frequency_Axis, Numeric_Axis, State_Type<Frequency_Axis_State_Tag>> {
|
struct Frequency_Axis : Def<Frequency_Axis, Numeric_Axis> {
|
||||||
/* Frequency_Axis 不发布额外属性。 */
|
/* Frequency_Axis 不发布额外属性。 */
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {};
|
||||||
/* Frequency_Axis 没有重复保存数值轴状态,仅保留独立状态层。 */
|
/* Frequency_Axis 没有重复保存数值轴状态,仅保留独立状态层。 */
|
||||||
struct State : Prev_State<Frequency_Axis_State_Tag> {
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
/* 完整声明及频率标签 CRTP 覆盖见 Frequency_Axis.ipp。 */
|
/* 完整声明及频率标签 CRTP 覆盖见 Frequency_Axis.ipp。 */
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ struct Frequency_Axis::Private : Prev_Private {
|
|||||||
inline std::string Frequency_Axis::Private::tick_label(const Attached auto* object, double tick) const {
|
inline std::string Frequency_Axis::Private::tick_label(const Attached auto* object, double tick) const {
|
||||||
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
||||||
const auto& final_private = static_cast<const typename Object::Private&>(*this);
|
const auto& final_private = static_cast<const typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const Numeric_Axis::State&>(*final_private.state.current);
|
const auto& state = static_cast<const Numeric_Axis::Prop&>(*final_private.current);
|
||||||
const double absolute = std::abs(tick);
|
const double absolute = std::abs(tick);
|
||||||
if (absolute >= 1'000'000.0) return localized_number(tick / 1'000'000.0, state.precision, state.locale) + " MHz";
|
if (absolute >= 1'000'000.0) return localized_number(tick / 1'000'000.0, state.precision, state.locale) + " MHz";
|
||||||
if (absolute >= 1'000.0) return localized_number(tick / 1'000.0, state.precision, state.locale) + " kHz";
|
if (absolute >= 1'000.0) return localized_number(tick / 1'000.0, state.precision, state.locale) + " kHz";
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
bool Numeric_Axis::State::operator==(const State&) const = default;
|
bool Numeric_Axis::State::operator==(const State&) const = default;
|
||||||
|
bool Numeric_Axis::Prop::operator==(const Prop&) const = default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Abs_Axis.hpp"
|
#include "Abs_Axis.hpp"
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
/* Numeric_Axis 状态标签,用于访问和订阅数值轴状态。 */
|
|
||||||
struct Numeric_Axis_State_Tag {};
|
|
||||||
/* 具有显式数值范围和十进制标签的坐标轴定义层。 */
|
/* 具有显式数值范围和十进制标签的坐标轴定义层。 */
|
||||||
struct Numeric_Axis : Def<Numeric_Axis, Abs_Axis, State_Type<Numeric_Axis_State_Tag>> {
|
struct Numeric_Axis : Def<Numeric_Axis, Abs_Axis> {
|
||||||
/* Numeric_Axis 不发布额外属性。 */
|
struct Prop : Prev_Prop {
|
||||||
struct Prop : Prev_Prop {};
|
|
||||||
/* 数值轴的权威状态;范围和标签格式都由该层直接提供。 */
|
|
||||||
struct State : Prev_State<Numeric_Axis_State_Tag> {
|
|
||||||
Axis_Range coordinate_range{0.0, 20.0}; /* 当前有向数值区间;必须有限且非零。 */
|
Axis_Range coordinate_range{0.0, 20.0}; /* 当前有向数值区间;必须有限且非零。 */
|
||||||
Axis_Label_Precision precision{2}; /* 标签最大小数位数;格式化时限制到 0..12。 */
|
Axis_Label_Precision precision{2}; /* 标签最大小数位数;格式化时限制到 0..12。 */
|
||||||
Number_Locale locale{}; /* 数值标签的小数点规则。 */
|
Number_Locale locale{}; /* 数值标签的小数点规则。 */
|
||||||
bool wheel_enabled{true}; /* 是否允许滚轮以指针位置为锚点缩放坐标范围。 */
|
bool wheel_enabled{true}; /* 是否允许滚轮以指针位置为锚点缩放坐标范围。 */
|
||||||
bool drag_enabled{true}; /* 是否允许按住鼠标左键拖动坐标范围。 */
|
bool drag_enabled{true}; /* 是否允许按住鼠标左键拖动坐标范围。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
/* 数值轴当前没有额外发布状态。 */
|
||||||
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
/* 完整声明及数值轴 CRTP 能力见 Numeric_Axis.ipp。 */
|
/* 完整声明及数值轴 CRTP 能力见 Numeric_Axis.ipp。 */
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ struct Numeric_Axis::Private : Prev_Private {
|
|||||||
std::mutex interaction_mutex{}; /* 保护跨事件保留的拖动手势状态。 */
|
std::mutex interaction_mutex{}; /* 保护跨事件保留的拖动手势状态。 */
|
||||||
bool dragging{}; /* 左键拖动手势是否已经开始且尚未释放。 */
|
bool dragging{}; /* 左键拖动手势是否已经开始且尚未释放。 */
|
||||||
Point_F last_pointer{}; /* 上一个拖动事件的位置,单位为画布局部像素。 */
|
Point_F last_pointer{}; /* 上一个拖动事件的位置,单位为画布局部像素。 */
|
||||||
/* CRTP 实现:直接返回 Numeric_Axis_State_Tag 中唯一保存的数值范围。 */
|
/* CRTP 实现:直接返回 Numeric_Axis::Base_Tag 中唯一保存的数值范围。 */
|
||||||
[[nodiscard]] Axis_Range coordinate_range(const Attached auto* object) const;
|
[[nodiscard]] Axis_Range coordinate_range(const Attached auto* object) const;
|
||||||
/* CRTP 实现:使用 Abs_Axis::Private 的 1/2/5 十进制算法计算主刻度。 */
|
/* CRTP 实现:使用 Abs_Axis::Private 的 1/2/5 十进制算法计算主刻度。 */
|
||||||
[[nodiscard]] double tick_step(const Attached auto* object, Axis_Range coordinate_range) const;
|
[[nodiscard]] double tick_step(const Attached auto* object, Axis_Range coordinate_range) const;
|
||||||
@@ -16,13 +16,13 @@ struct Numeric_Axis::Private : Prev_Private {
|
|||||||
/* CRTP 实现:处理滚轮缩放和左键拖动,并在消费事件后标记 Prepare dirty。 */
|
/* CRTP 实现:处理滚轮缩放和左键拖动,并在消费事件后标记 Prepare dirty。 */
|
||||||
void handle_event(Attached auto* object, const Event& event);
|
void handle_event(Attached auto* object, const Event& event);
|
||||||
/* CRTP State 钩子:拒绝非有限、零长度范围以及 0..12 之外的精度,并恢复本次无效写入。 */
|
/* CRTP State 钩子:拒绝非有限、零长度范围以及 0..12 之外的精度,并恢复本次无效写入。 */
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type>
|
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||||
void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> pending_states);
|
void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> props);
|
||||||
};
|
};
|
||||||
inline Axis_Range Numeric_Axis::Private::coordinate_range(const Attached auto* object) const {
|
inline Axis_Range Numeric_Axis::Private::coordinate_range(const Attached auto* object) const {
|
||||||
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
||||||
return static_cast<const State&>(*private_data.state.current).coordinate_range;
|
return static_cast<const Prop&>(*private_data.current).coordinate_range;
|
||||||
}
|
}
|
||||||
inline double Numeric_Axis::Private::tick_step(const Attached auto*, Axis_Range coordinate_range) const {
|
inline double Numeric_Axis::Private::tick_step(const Attached auto*, Axis_Range coordinate_range) const {
|
||||||
return nice_tick_step(coordinate_range);
|
return nice_tick_step(coordinate_range);
|
||||||
@@ -30,14 +30,14 @@ inline double Numeric_Axis::Private::tick_step(const Attached auto*, Axis_Range
|
|||||||
inline std::string Numeric_Axis::Private::tick_label(const Attached auto* object, double tick) const {
|
inline std::string Numeric_Axis::Private::tick_label(const Attached auto* object, double tick) const {
|
||||||
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const Prop&>(*private_data.current);
|
||||||
return localized_number(tick, state.precision, state.locale);
|
return localized_number(tick, state.precision, state.locale);
|
||||||
}
|
}
|
||||||
inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Event& event) {
|
inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Event& event) {
|
||||||
using Object = std::remove_pointer_t<decltype(object)>;
|
using Object = std::remove_pointer_t<decltype(object)>;
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||||
const auto& numeric_state = static_cast<const State&>(*private_data.state.current);
|
const auto& numeric_state = static_cast<const Prop&>(*private_data.current);
|
||||||
const auto& axis_state = static_cast<const Abs_Axis::State&>(*private_data.state.current);
|
const auto& axis_state = static_cast<const Abs_Axis::Prop&>(*private_data.current);
|
||||||
const auto* pointer = dynamic_cast<const Pointer_Event_Capability*>(&event);
|
const auto* pointer = dynamic_cast<const Pointer_Event_Capability*>(&event);
|
||||||
if (event.type == Event_Type::wheel && numeric_state.wheel_enabled) {
|
if (event.type == Event_Type::wheel && numeric_state.wheel_enabled) {
|
||||||
const auto* wheel = dynamic_cast<const Wheel_Event_Capability*>(&event);
|
const auto* wheel = dynamic_cast<const Wheel_Event_Capability*>(&event);
|
||||||
@@ -47,7 +47,7 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve
|
|||||||
const double anchor = pixel_to_coordinate(object, anchor_pixel);
|
const double anchor = pixel_to_coordinate(object, anchor_pixel);
|
||||||
const double factor = wheel->angle_delta_y_value() >= 0.0 ? 0.9 : 1.1;
|
const double factor = wheel->angle_delta_y_value() >= 0.0 ? 0.9 : 1.1;
|
||||||
const Axis_Range range = numeric_state.coordinate_range;
|
const Axis_Range range = numeric_state.coordinate_range;
|
||||||
object->template update_state<&State::coordinate_range>(Axis_Range{
|
object->template set<&Prop::coordinate_range>(Axis_Range{
|
||||||
anchor + (range.origin - anchor) * factor,
|
anchor + (range.origin - anchor) * factor,
|
||||||
anchor + (range.target - anchor) * factor
|
anchor + (range.target - anchor) * factor
|
||||||
});
|
});
|
||||||
@@ -76,7 +76,7 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve
|
|||||||
? current.x - previous.x : current.y - previous.y;
|
? current.x - previous.x : current.y - previous.y;
|
||||||
const double shift = axis_state.pixel_length == 0.0
|
const double shift = axis_state.pixel_length == 0.0
|
||||||
? 0.0 : -delta * numeric_state.coordinate_range.length() / axis_state.pixel_length;
|
? 0.0 : -delta * numeric_state.coordinate_range.length() / axis_state.pixel_length;
|
||||||
object->template update_state<&State::coordinate_range>(Axis_Range{
|
object->template set<&Prop::coordinate_range>(Axis_Range{
|
||||||
numeric_state.coordinate_range.origin + shift,
|
numeric_state.coordinate_range.origin + shift,
|
||||||
numeric_state.coordinate_range.target + shift
|
numeric_state.coordinate_range.target + shift
|
||||||
});
|
});
|
||||||
@@ -91,20 +91,20 @@ inline void Numeric_Axis::Private::handle_event(Attached auto* object, const Eve
|
|||||||
if (was_dragging) event.accept();
|
if (was_dragging) event.accept();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type>
|
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||||
void Numeric_Axis::Private::after_state_set(Object*, Member Owner::* member, State_Access<State_Type> pending_states) {
|
void Numeric_Axis::Private::after_prop_set(Object*, Member Owner::* member, Prop_Access<Prop_Type> props) {
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
||||||
auto& pending = pending_states.template get<Numeric_Axis_State_Tag>();
|
auto& pending = props.template get<Numeric_Axis::Base_Tag>();
|
||||||
const auto& current = static_cast<const State&>(*private_data.state.current);
|
const auto& current = static_cast<const Prop&>(*private_data.pending);
|
||||||
if constexpr (std::same_as<Owner, State> && std::same_as<Member, Axis_Range>) {
|
if constexpr (std::same_as<Owner, Prop> && std::same_as<Member, Axis_Range>) {
|
||||||
if (member != &State::coordinate_range) return;
|
if (member != &Prop::coordinate_range) return;
|
||||||
const Axis_Range range = pending.coordinate_range;
|
const Axis_Range range = pending.coordinate_range;
|
||||||
if (std::isfinite(range.origin) && std::isfinite(range.target) && range.size() > 0.0) return;
|
if (std::isfinite(range.origin) && std::isfinite(range.target) && range.size() > 0.0) return;
|
||||||
pending.coordinate_range = current.coordinate_range;
|
pending.coordinate_range = current.coordinate_range;
|
||||||
throw std::invalid_argument("numeric axis coordinate range must be finite and non-empty");
|
throw std::invalid_argument("numeric axis coordinate range must be finite and non-empty");
|
||||||
}
|
}
|
||||||
else if constexpr (std::same_as<Owner, State> && std::same_as<Member, int>) {
|
else if constexpr (std::same_as<Owner, Prop> && std::same_as<Member, int>) {
|
||||||
if (member != &State::precision || (pending.precision >= 0 && pending.precision <= 12)) return;
|
if (member != &Prop::precision || (pending.precision >= 0 && pending.precision <= 12)) return;
|
||||||
pending.precision = current.precision;
|
pending.precision = current.precision;
|
||||||
throw std::invalid_argument("numeric axis precision must be between 0 and 12");
|
throw std::invalid_argument("numeric axis precision must be between 0 and 12");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
bool Time_Axis::State::operator==(const State&) const = default;
|
bool Time_Axis::State::operator==(const State&) const = default;
|
||||||
|
bool Time_Axis::Prop::operator==(const Prop&) const = default;
|
||||||
|
|
||||||
std::size_t Time_Axis::Private::time_point_count(const Root* object) const {
|
std::size_t Time_Axis::Private::time_point_count(const Root* object) const {
|
||||||
return time_dispatch->time_point_count(object);
|
return time_dispatch->time_point_count(object);
|
||||||
|
|||||||
@@ -5,19 +5,18 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
/* Time_Axis 状态标签,用于访问和订阅时间轴状态。 */
|
|
||||||
struct Time_Axis_State_Tag {};
|
|
||||||
/* 将连续样本序号显示为一天内时间文本的坐标轴。 */
|
/* 将连续样本序号显示为一天内时间文本的坐标轴。 */
|
||||||
struct Time_Axis : Def<Time_Axis, Abs_Axis, State_Type<Time_Axis_State_Tag>> {
|
struct Time_Axis : Def<Time_Axis, Abs_Axis> {
|
||||||
/* Time_Axis 不发布额外属性。 */
|
struct Prop : Prev_Prop {
|
||||||
struct Prop : Prev_Prop {};
|
|
||||||
/* 时间轴样本与显示参数的唯一权威状态。 */
|
|
||||||
struct State : Prev_State<Time_Axis_State_Tag> {
|
|
||||||
Axis_Visible_Count visible_count{100}; /* 当前坐标区间最多覆盖的样本数量;小于 2 时按 2 计算。 */
|
Axis_Visible_Count visible_count{100}; /* 当前坐标区间最多覆盖的样本数量;小于 2 时按 2 计算。 */
|
||||||
Axis_Pixel_Length tick_label_spacing_px{8.0}; /* 相邻标签之间预留的像素间距;负值按 0 计算。 */
|
Axis_Pixel_Length tick_label_spacing_px{8.0}; /* 相邻标签之间预留的像素间距;负值按 0 计算。 */
|
||||||
Axis_Pixel_Length estimated_label_width_px{48.0}; /* 当前字体下单个时间标签的估算像素宽度;小于 48 时按 48 计算。 */
|
Axis_Pixel_Length estimated_label_width_px{48.0}; /* 当前字体下单个时间标签的估算像素宽度;小于 48 时按 48 计算。 */
|
||||||
std::string format{"mm:ss.zzz"}; /* 标签格式;支持 hh、HH、mm、ss 和 zzz。 */
|
std::string format{"mm:ss.zzz"}; /* 标签格式;支持 hh、HH、mm、ss 和 zzz。 */
|
||||||
bool newest_at_start{}; /* true 时最新样本位于坐标区间起点。 */
|
bool newest_at_start{}; /* true 时最新样本位于坐标区间起点。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
/* 时间样本是由 append_time 发布给观察方的运行状态。 */
|
||||||
|
struct State : Prev_State {
|
||||||
Axis_Time_Tick next_tick{}; /* 下一次 append_time 分配的单调样本序号。 */
|
Axis_Time_Tick next_tick{}; /* 下一次 append_time 分配的单调样本序号。 */
|
||||||
std::deque<std::pair<Axis_Time_Tick, Time_Of_Day>> samples{}; /* tick 到时间的保留窗口;最多保留 max(512, visible_count*4) 项。 */
|
std::deque<std::pair<Axis_Time_Tick, Time_Of_Day>> samples{}; /* tick 到时间的保留窗口;最多保留 max(512, visible_count*4) 项。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
|
|||||||
@@ -33,16 +33,17 @@ inline Axis_Range Time_Axis::Private::coordinate_range(const Attached auto* obje
|
|||||||
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const State&>(*private_data.state.current);
|
||||||
|
const auto& prop = static_cast<const Prop&>(*private_data.current);
|
||||||
const int latest = std::max(1, state.next_tick - 1);
|
const int latest = std::max(1, state.next_tick - 1);
|
||||||
const int earliest = std::max(0, latest - std::max(2, state.visible_count) + 1);
|
const int earliest = std::max(0, latest - std::max(2, prop.visible_count) + 1);
|
||||||
if (state.newest_at_start) return {static_cast<double>(latest), static_cast<double>(earliest)};
|
if (prop.newest_at_start) return {static_cast<double>(latest), static_cast<double>(earliest)};
|
||||||
return {static_cast<double>(earliest), static_cast<double>(latest)};
|
return {static_cast<double>(earliest), static_cast<double>(latest)};
|
||||||
}
|
}
|
||||||
inline double Time_Axis::Private::tick_step(const Attached auto* object, Axis_Range coordinate_range) const {
|
inline double Time_Axis::Private::tick_step(const Attached auto* object, Axis_Range coordinate_range) const {
|
||||||
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
||||||
const auto& time_state = static_cast<const State&>(*private_data.state.current);
|
const auto& time_state = static_cast<const Prop&>(*private_data.current);
|
||||||
const auto& axis_state = static_cast<const Abs_Axis::State&>(*private_data.state.current);
|
const auto& axis_state = static_cast<const Abs_Axis::Prop&>(*private_data.current);
|
||||||
const double label_width = std::max(48.0, time_state.estimated_label_width_px);
|
const double label_width = std::max(48.0, time_state.estimated_label_width_px);
|
||||||
const double label_count = std::max(1.0, axis_state.pixel_length / (label_width + std::max(0.0, time_state.tick_label_spacing_px)));
|
const double label_count = std::max(1.0, axis_state.pixel_length / (label_width + std::max(0.0, time_state.tick_label_spacing_px)));
|
||||||
return std::max(1.0, std::ceil(coordinate_range.size() / label_count));
|
return std::max(1.0, std::ceil(coordinate_range.size() / label_count));
|
||||||
@@ -51,11 +52,12 @@ inline std::string Time_Axis::Private::tick_label(const Attached auto* object, d
|
|||||||
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
using Object = std::remove_cv_t<std::remove_pointer_t<decltype(object)>>;
|
||||||
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const State&>(*private_data.state.current);
|
||||||
|
const auto& prop = static_cast<const Prop&>(*private_data.current);
|
||||||
const int target = static_cast<int>(std::llround(tick));
|
const int target = static_cast<int>(std::llround(tick));
|
||||||
const auto current = std::find_if(state.samples.begin(), state.samples.end(), [target](const auto& sample) {
|
const auto current = std::find_if(state.samples.begin(), state.samples.end(), [target](const auto& sample) {
|
||||||
return sample.first == target;
|
return sample.first == target;
|
||||||
});
|
});
|
||||||
return current == state.samples.end() ? std::string{} : formatted_time(current->second, state.format);
|
return current == state.samples.end() ? std::string{} : formatted_time(current->second, prop.format);
|
||||||
}
|
}
|
||||||
template <Axis_Object Object>
|
template <Axis_Object Object>
|
||||||
const Time_Axis::Private::Time_Dispatch& Time_Axis::Private::time_dispatch_for() {
|
const Time_Axis::Private::Time_Dispatch& Time_Axis::Private::time_dispatch_for() {
|
||||||
@@ -68,12 +70,13 @@ const Time_Axis::Private::Time_Dispatch& Time_Axis::Private::time_dispatch_for()
|
|||||||
},
|
},
|
||||||
[](Root* root, Time_Of_Day time) {
|
[](Root* root, Time_Of_Day time) {
|
||||||
auto* object = static_cast<Object*>(root);
|
auto* object = static_cast<Object*>(root);
|
||||||
|
const auto visible_count = object->template read_prop<Time_Axis::Base_Tag>().visible_count;
|
||||||
int tick{};
|
int tick{};
|
||||||
object->template update_state<&State::next_tick, &State::samples>([&](State_Access<typename Object::State> states) {
|
object->template update_state<&State::next_tick, &State::samples>([&](State_Access<typename Object::State> states) {
|
||||||
auto& state = states.template get<Time_Axis_State_Tag>();
|
auto& state = states.template get<Time_Axis::Base_Tag>();
|
||||||
tick = state.next_tick++;
|
tick = state.next_tick++;
|
||||||
state.samples.emplace_back(tick, time);
|
state.samples.emplace_back(tick, time);
|
||||||
const auto limit = static_cast<std::size_t>(std::max(512, std::max(2, state.visible_count) * 4));
|
const auto limit = static_cast<std::size_t>(std::max(512, std::max(2, visible_count) * 4));
|
||||||
while (state.samples.size() > limit) state.samples.pop_front();
|
while (state.samples.size() > limit) state.samples.pop_front();
|
||||||
});
|
});
|
||||||
return tick;
|
return tick;
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
#include "Afterglow.hpp"
|
#include "Afterglow.hpp"
|
||||||
namespace aethera::render_2d { bool Afterglow::State::operator==(const State&) const = default; void Afterglow::append_spectrum(std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append(this, values); } void Afterglow::append_spectrum(std::pmr::vector<Plot_Value>&& values) { append_spectrum(std::span<const Plot_Value>(values.data(), values.size())); } std::size_t Afterglow::history_count() const { return static_cast<const Private&>(*d).dispatch->history_count(this); } std::size_t Afterglow::latest_spectrum_point_count() const { return static_cast<const Private&>(*d).dispatch->latest_count(this); } std::size_t Afterglow::rendered_cell_count() const { return static_cast<const Private&>(*d).dispatch->rendered_count(this); } }
|
namespace aethera::render_2d { bool Afterglow::Prop::operator==(const Prop&) const = default;
|
||||||
|
bool Afterglow::State::operator==(const State&) const = default; void Afterglow::append_spectrum(std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append(this, values); } void Afterglow::append_spectrum(std::pmr::vector<Plot_Value>&& values) { append_spectrum(std::span<const Plot_Value>(values.data(), values.size())); } std::size_t Afterglow::history_count() const { return static_cast<const Private&>(*d).dispatch->history_count(this); } std::size_t Afterglow::latest_spectrum_point_count() const { return static_cast<const Private&>(*d).dispatch->latest_count(this); } std::size_t Afterglow::rendered_cell_count() const { return static_cast<const Private&>(*d).dispatch->rendered_count(this); } }
|
||||||
|
|||||||
@@ -9,11 +9,9 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
struct Afterglow_State_Tag {};
|
struct Afterglow : Def<Afterglow, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||||
struct Afterglow : Def<Afterglow, Renderable, State_Type<Afterglow_State_Tag>, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
|
||||||
using Scene_Object = Impl<Render_Scene_2D>; using Frequency_Object = Impl<Frequency_Axis>; using Power_Object = Impl<Numeric_Axis>;
|
using Scene_Object = Impl<Render_Scene_2D>; using Frequency_Object = Impl<Frequency_Axis>; using Power_Object = Impl<Numeric_Axis>;
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {
|
||||||
struct State : Prev_State<Afterglow_State_Tag> {
|
|
||||||
Axis_Range frequency_range{0.0, 10.0}; /* 输入频谱覆盖的频率范围。 */
|
Axis_Range frequency_range{0.0, 10.0}; /* 输入频谱覆盖的频率范围。 */
|
||||||
Axis_Range power_range{0.0, 10.0}; /* 色块纵向覆盖的功率范围。 */
|
Axis_Range power_range{0.0, 10.0}; /* 色块纵向覆盖的功率范围。 */
|
||||||
std::size_t frequency_point_size{}; /* 栅格频率列数;零值使用最新频谱尺寸。 */
|
std::size_t frequency_point_size{}; /* 栅格频率列数;零值使用最新频谱尺寸。 */
|
||||||
@@ -24,6 +22,12 @@ struct Afterglow : Def<Afterglow, Renderable, State_Type<Afterglow_State_Tag>, T
|
|||||||
Plot_Ratio attenuation_rate{0.2}; /* 每增加一帧历史的强度衰减比例,限制到 0..1。 */
|
Plot_Ratio attenuation_rate{0.2}; /* 每增加一帧历史的强度衰减比例,限制到 0..1。 */
|
||||||
Color_Map color_map{}; /* 强度到颜色的映射。 */
|
Color_Map color_map{}; /* 强度到颜色的映射。 */
|
||||||
std::vector<std::vector<Plot_Value>> spectra{}; /* 从旧到新的历史频谱唯一权威集合。 */
|
std::vector<std::vector<Plot_Value>> spectra{}; /* 从旧到新的历史频谱唯一权威集合。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
|
std::size_t history_count{}; /* 当前发布的历史频谱帧数。 */
|
||||||
|
std::size_t latest_spectrum_point_count{}; /* 最新频谱包含的点数。 */
|
||||||
|
std::size_t rendered_cell_count{}; /* 最近一次 Prepare 生成的色块数。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|||||||
@@ -30,34 +30,36 @@ struct Afterglow::Private : Prev_Private {
|
|||||||
void bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value);
|
void bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value);
|
||||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||||
/* CRTP 覆盖:构建累加、归一化和着色三阶段 Prepare 子图。 */
|
/* CRTP 覆盖:构建累加、归一化和着色三阶段 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:构建消费色块矩阵的 Paint 子图。 */
|
/* CRTP 覆盖:构建消费色块矩阵的 Paint 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state);
|
||||||
template <Attached Object> void prepare_frame(Object* object); template <Attached Object> void accumulate_partition(Object* object, Plot_Partition_Count index); void normalize_frame(); template <Attached Object> void color_partition(Object* object, Plot_Partition_Count index); template <Attached Object> void paint_frame(Object* object);
|
template <Attached Object> void prepare_frame(Object* object); template <Attached Object> void accumulate_partition(Object* object, Plot_Partition_Count index); void normalize_frame(); template <Attached Object> void color_partition(Object* object, Plot_Partition_Count index); template <Attached Object> void paint_frame(Object* object);
|
||||||
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> states);
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> states);
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||||
};
|
};
|
||||||
template <typename Object> Afterglow::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), power_axis(power_axis_value) {}
|
template <typename Object> Afterglow::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), power_axis(power_axis_value) {}
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Afterglow::Builder<Object>::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); static_cast<typename Object::Private&>(*plot->d).bind_sources(scene, frequency_axis, power_axis); auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(frequency_axis); prepare.add(power_axis); prepare.add(plot.get()); prepare.template add_state_dependency<Render_Scene_2D_State_Tag>(plot.get(), scene); prepare.template add_state_dependency<Abs_Axis_State_Tag>(plot.get(), frequency_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(plot.get(), frequency_axis); prepare.template add_state_dependency<Abs_Axis_State_Tag>(plot.get(), power_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(plot.get(), power_axis); paint.add(frequency_axis); paint.add(power_axis); paint.add(plot.get()); }); if (!graph_result) return std::unexpected(graph_result.error()); return std::move(plot); }
|
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Afterglow::Builder<Object>::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); static_cast<typename Object::Private&>(*plot->d).bind_sources(scene, frequency_axis, power_axis); auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(frequency_axis); prepare.add(power_axis); prepare.add(plot.get()); prepare.template add_prop_dependency<Render_Scene_2D::Base_Tag>(plot.get(), scene); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(plot.get(), frequency_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(plot.get(), frequency_axis); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(plot.get(), power_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(plot.get(), power_axis); paint.add(frequency_axis); paint.add(power_axis); paint.add(plot.get()); }); if (!graph_result) return std::unexpected(graph_result.error()); return std::move(plot); }
|
||||||
template <typename Values> void Afterglow::append_spectrum(const Values& values) { append_spectrum(std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
template <typename Values> void Afterglow::append_spectrum(const Values& values) { append_spectrum(std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
||||||
template <Attached Object> bool Afterglow::Private::should_rebuild_prepare_graph(Object*, const State& state) { const std::size_t available = state.spectra.empty() ? 0 : state.spectra.back().size(); const std::size_t columns = state.frequency_point_size ? std::min(state.frequency_point_size, available) : available; return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, std::max<std::size_t>(1, columns)); }
|
template <Attached Object> bool Afterglow::Private::should_rebuild_prepare_graph(Object*, const Prop& state) { const std::size_t available = state.spectra.empty() ? 0 : state.spectra.back().size(); const std::size_t columns = state.frequency_point_size ? std::min(state.frequency_point_size, available) : available; return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, std::max<std::size_t>(1, columns)); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
tf::Taskflow Afterglow::Private::build_prepare_graph(Object* object, const State& state) { const std::size_t available = state.spectra.empty() ? 0 : state.spectra.back().size(); const std::size_t columns = state.frequency_point_size ? std::min(state.frequency_point_size, available) : available; graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, std::max<std::size_t>(1, columns)); tf::Taskflow graph; auto begin = graph.emplace([this, object] { prepare_frame(object); }).name("afterglow.prepare.frame"); auto normalize = graph.emplace([this] { normalize_frame(); }).name("afterglow.prepare.normalize"); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto accumulate = graph.emplace([this, object, index] { accumulate_partition(object, index); }).name("afterglow.prepare.accumulate"); auto color = graph.emplace([this, object, index] { color_partition(object, index); }).name("afterglow.prepare.color"); begin.precede(accumulate); accumulate.precede(normalize); normalize.precede(color); } return graph; }
|
tf::Taskflow Afterglow::Private::build_prepare_graph(Object* object, const Prop& state) { const std::size_t available = state.spectra.empty() ? 0 : state.spectra.back().size(); const std::size_t columns = state.frequency_point_size ? std::min(state.frequency_point_size, available) : available; graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, std::max<std::size_t>(1, columns)); tf::Taskflow graph; auto begin = graph.emplace([this, object] { prepare_frame(object); }).name("afterglow.prepare.frame"); auto normalize = graph.emplace([this] { normalize_frame(); }).name("afterglow.prepare.normalize"); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto accumulate = graph.emplace([this, object, index] { accumulate_partition(object, index); }).name("afterglow.prepare.accumulate"); auto color = graph.emplace([this, object, index] { color_partition(object, index); }).name("afterglow.prepare.color"); begin.precede(accumulate); accumulate.precede(normalize); normalize.precede(color); } return graph; }
|
||||||
template <Attached Object> tf::Taskflow Afterglow::Private::build_paint_graph(Object* object, const State&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("afterglow.paint.frame"); return graph; }
|
template <Attached Object> tf::Taskflow Afterglow::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("afterglow.paint.frame"); return graph; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Afterglow::Private::prepare_frame(Object* object) { const auto& state = object->template read_state<Afterglow_State_Tag>(); const auto& frequency_layout = frequency_axis->template read_state<Abs_Axis_State_Tag>(); const auto& power_layout = power_axis->template read_state<Abs_Axis_State_Tag>(); const std::size_t available = state.spectra.empty() ? 0 : state.spectra.back().size(); const int columns = static_cast<int>(state.frequency_point_size ? std::min(state.frequency_point_size, available) : available); const int rows = static_cast<int>(state.power_point_size ? state.power_point_size : std::max<Axis_Pixel_Length>(1.0, std::abs(power_layout.pixel_length))); prepared = {}; prepared.canvas = scene->template read_state<Render_Scene_2D_State_Tag>().viewport; prepared.layout = detail::raster_layout(frequency_axis, state.frequency_range, columns, power_axis, state.power_range, rows, frequency_layout.orientation, power_layout.orientation); if (prepared.canvas.empty() || !prepared.layout.valid()) return; const std::size_t cells = static_cast<std::size_t>(columns) * rows; prepared.intensity.assign(cells, 0.0); prepared.pixels.assign(cells, 0); prepared.valid = true; }
|
void Afterglow::Private::prepare_frame(Object* object) { const auto& state = object->template read_prop<Afterglow::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const std::size_t available = state.spectra.empty() ? 0 : state.spectra.back().size(); const int columns = static_cast<int>(state.frequency_point_size ? std::min(state.frequency_point_size, available) : available); const int rows = static_cast<int>(state.power_point_size ? state.power_point_size : std::max<Axis_Pixel_Length>(1.0, std::abs(power_layout.pixel_length))); prepared = {}; prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport; prepared.layout = detail::raster_layout(frequency_axis, state.frequency_range, columns, power_axis, state.power_range, rows, frequency_layout.orientation, power_layout.orientation); if (prepared.canvas.empty() || !prepared.layout.valid()) return; const std::size_t cells = static_cast<std::size_t>(columns) * rows; prepared.intensity.assign(cells, 0.0); prepared.pixels.assign(cells, 0); prepared.valid = true; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Afterglow::Private::accumulate_partition(Object* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = object->template read_state<Afterglow_State_Tag>(); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; const int rows = prepared.layout.first_horizontal ? prepared.layout.height : prepared.layout.width; const auto [first, last] = detail::raster_partition_range(static_cast<std::size_t>(columns), index, graph_partition_count); Plot_Ratio attenuation{1.0}; const Plot_Ratio decay = 1.0 - std::clamp(state.attenuation_rate, 0.0, 1.0); for (auto spectrum = state.spectra.rbegin(); spectrum != state.spectra.rend() && attenuation >= 0.01; ++spectrum, attenuation *= decay) { const std::size_t count = std::min<std::size_t>(columns, spectrum->size()); for (std::size_t column = first; column < std::min(last, count); ++column) { const int row = std::clamp(static_cast<int>(detail::normalized_plot_value((*spectrum)[column], state.power_range) * (rows - 1)), 0, rows - 1); prepared.intensity[static_cast<std::size_t>(row) * columns + column] += attenuation; if (state.interpolate && row + 1 < rows) prepared.intensity[static_cast<std::size_t>(row + 1) * columns + column] += attenuation * 0.35; } } }
|
void Afterglow::Private::accumulate_partition(Object* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = object->template read_prop<Afterglow::Base_Tag>(); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; const int rows = prepared.layout.first_horizontal ? prepared.layout.height : prepared.layout.width; const auto [first, last] = detail::raster_partition_range(static_cast<std::size_t>(columns), index, graph_partition_count); Plot_Ratio attenuation{1.0}; const Plot_Ratio decay = 1.0 - std::clamp(state.attenuation_rate, 0.0, 1.0); for (auto spectrum = state.spectra.rbegin(); spectrum != state.spectra.rend() && attenuation >= 0.01; ++spectrum, attenuation *= decay) { const std::size_t count = std::min<std::size_t>(columns, spectrum->size()); for (std::size_t column = first; column < std::min(last, count); ++column) { const int row = std::clamp(static_cast<int>(detail::normalized_plot_value((*spectrum)[column], state.power_range) * (rows - 1)), 0, rows - 1); prepared.intensity[static_cast<std::size_t>(row) * columns + column] += attenuation; if (state.interpolate && row + 1 < rows) prepared.intensity[static_cast<std::size_t>(row + 1) * columns + column] += attenuation * 0.35; } } }
|
||||||
inline void Afterglow::Private::normalize_frame() { if (prepared.valid && !prepared.intensity.empty()) prepared.maximum = std::max<Plot_Ratio>(1.0, *std::max_element(prepared.intensity.begin(), prepared.intensity.end())); }
|
inline void Afterglow::Private::normalize_frame() { if (prepared.valid && !prepared.intensity.empty()) prepared.maximum = std::max<Plot_Ratio>(1.0, *std::max_element(prepared.intensity.begin(), prepared.intensity.end())); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Afterglow::Private::color_partition(Object* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = object->template read_state<Afterglow_State_Tag>(); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; const int rows = prepared.layout.first_horizontal ? prepared.layout.height : prepared.layout.width; const auto [first, last] = detail::raster_partition_range(static_cast<std::size_t>(columns), index, graph_partition_count); for (std::size_t column = first; column < last; ++column) for (int row = 0; row < rows; ++row) { const std::size_t cell = static_cast<std::size_t>(row) * columns + column; prepared.pixels[prepared.layout.index(static_cast<int>(column), row)] = premultiply(state.color_map.sample(prepared.intensity[cell] / prepared.maximum)); } }
|
void Afterglow::Private::color_partition(Object* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = object->template read_prop<Afterglow::Base_Tag>(); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; const int rows = prepared.layout.first_horizontal ? prepared.layout.height : prepared.layout.width; const auto [first, last] = detail::raster_partition_range(static_cast<std::size_t>(columns), index, graph_partition_count); for (std::size_t column = first; column < last; ++column) for (int row = 0; row < rows; ++row) { const std::size_t cell = static_cast<std::size_t>(row) * columns + column; prepared.pixels[prepared.layout.index(static_cast<int>(column), row)] = premultiply(state.color_map.sample(prepared.intensity[cell] / prepared.maximum)); } }
|
||||||
template <Attached Object> void Afterglow::Private::paint_frame(Object* object) { auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); detail::paint_raster(painter, prepared.layout, prepared.pixels, Image_Interpolation_Mode::bilinear); }
|
template <Attached Object> void Afterglow::Private::paint_frame(Object* object) { auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); detail::paint_raster(painter, prepared.layout, prepared.pixels, Image_Interpolation_Mode::bilinear); }
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void Afterglow::Private::after_state_set(Object* object, Member Owner::*, State_Access<State_Type>) { if constexpr (std::same_as<Owner, State>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void Afterglow::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void Afterglow::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { auto& state = pending_states.template get<Afterglow::Base_Tag>(); const auto& prop = static_cast<const Prop&>(*current_prop); state.history_count = prop.spectra.size(); state.latest_spectrum_point_count = prop.spectra.empty() ? 0 : prop.spectra.back().size(); state.rendered_cell_count = prepared.valid ? prepared.pixels.size() : 0; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
const Afterglow::Private::Dispatch& Afterglow::Private::dispatch_for() { static const Dispatch value{[](Root* root, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); object->template update_state<&State::spectra>([values](State_Access<typename Object::State> states) { auto& state = states.template get<Afterglow_State_Tag>(); state.spectra.emplace_back(values.begin(), values.end()); constexpr std::size_t history_limit = 64; while (state.spectra.size() > history_limit) state.spectra.erase(state.spectra.begin()); }); }, [](const Root* root) { return static_cast<const Object*>(root)->template read_state<Afterglow_State_Tag>().spectra.size(); }, [](const Root* root) { const auto& spectra = static_cast<const Object*>(root)->template read_state<Afterglow_State_Tag>().spectra; return spectra.empty() ? 0 : spectra.back().size(); }, [](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); return data.prepared.valid ? data.prepared.pixels.size() : 0; }}; return value; }
|
const Afterglow::Private::Dispatch& Afterglow::Private::dispatch_for() { static const Dispatch value{[](Root* root, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); object->template update_prop<&Prop::spectra>([values](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Afterglow::Base_Tag>(); state.spectra.emplace_back(values.begin(), values.end()); constexpr std::size_t history_limit = 64; while (state.spectra.size() > history_limit) state.spectra.erase(state.spectra.begin()); }); }, [](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Afterglow::Base_Tag>().spectra.size(); }, [](const Root* root) { const auto& spectra = static_cast<const Object*>(root)->template read_prop<Afterglow::Base_Tag>().spectra; return spectra.empty() ? 0 : spectra.back().size(); }, [](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); return data.prepared.valid ? data.prepared.pixels.size() : 0; }}; return value; }
|
||||||
template <Attached Object> void Afterglow::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
template <Attached Object> void Afterglow::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
||||||
inline void Afterglow::Private::bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { scene = scene_value; frequency_axis = frequency_axis_value; power_axis = power_axis_value; }
|
inline void Afterglow::Private::bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) { scene = scene_value; frequency_axis = frequency_axis_value; power_axis = power_axis_value; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
#include "Constellation_Diagram.hpp"
|
#include "Constellation_Diagram.hpp"
|
||||||
namespace aethera::render_2d { bool Constellation_Point::operator==(const Constellation_Point&) const = default; bool Constellation_Diagram::State::operator==(const State&) const = default; void Constellation_Diagram::append_point(Point_F point) { static_cast<Private&>(*d).dispatch->append(this, point); } std::size_t Constellation_Diagram::point_count() const { return static_cast<const Private&>(*d).dispatch->count(this); } void Constellation_Diagram::fit_square_to_axes() { static_cast<Private&>(*d).dispatch->fit(this); } }
|
namespace aethera::render_2d { bool Constellation_Point::operator==(const Constellation_Point&) const = default; bool Constellation_Diagram::Prop::operator==(const Prop&) const = default;
|
||||||
|
bool Constellation_Diagram::State::operator==(const State&) const = default; void Constellation_Diagram::append_point(Point_F point) { static_cast<Private&>(*d).dispatch->append(this, point); } std::size_t Constellation_Diagram::point_count() const { return static_cast<const Private&>(*d).dispatch->count(this); } void Constellation_Diagram::fit_square_to_axes() { static_cast<Private&>(*d).dispatch->fit(this); } }
|
||||||
|
|||||||
@@ -8,12 +8,10 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
enum class Constellation_Diagram_Type : std::uint8_t { psk4 = 4, psk8 = 8, psk16 = 16 };
|
enum class Constellation_Diagram_Type : std::uint8_t { psk4 = 4, psk8 = 8, psk16 = 16 };
|
||||||
struct Constellation_Diagram_State_Tag {};
|
|
||||||
struct Constellation_Point { Point_F point{}; Plot_Duration_Milliseconds submitted_at_ms{}; bool operator==(const Constellation_Point&) const; };
|
struct Constellation_Point { Point_F point{}; Plot_Duration_Milliseconds submitted_at_ms{}; bool operator==(const Constellation_Point&) const; };
|
||||||
struct Constellation_Diagram : Def<Constellation_Diagram, Renderable, State_Type<Constellation_Diagram_State_Tag>, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
struct Constellation_Diagram : Def<Constellation_Diagram, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||||
using Scene_Object = Impl<Render_Scene_2D>; using Axis_Object = Impl<Numeric_Axis>;
|
using Scene_Object = Impl<Render_Scene_2D>; using Axis_Object = Impl<Numeric_Axis>;
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {
|
||||||
struct State : Prev_State<Constellation_Diagram_State_Tag> {
|
|
||||||
Axis_Range i_range{0.0, 100.0}; /* 同相分量显示范围。 */
|
Axis_Range i_range{0.0, 100.0}; /* 同相分量显示范围。 */
|
||||||
Axis_Range q_range{0.0, 100.0}; /* 正交分量显示范围。 */
|
Axis_Range q_range{0.0, 100.0}; /* 正交分量显示范围。 */
|
||||||
Color point_color{Color::red_color()}; /* 接收点颜色。 */
|
Color point_color{Color::red_color()}; /* 接收点颜色。 */
|
||||||
@@ -22,6 +20,10 @@ struct Constellation_Diagram : Def<Constellation_Diagram, Renderable, State_Type
|
|||||||
Constellation_Diagram_Type type{Constellation_Diagram_Type::psk8}; /* 理想 PSK 锚点数量。 */
|
Constellation_Diagram_Type type{Constellation_Diagram_Type::psk8}; /* 理想 PSK 锚点数量。 */
|
||||||
Plot_Ratio phase_offset_radians{}; /* 理想锚点相位偏移,单位为弧度。 */
|
Plot_Ratio phase_offset_radians{}; /* 理想锚点相位偏移,单位为弧度。 */
|
||||||
std::vector<Constellation_Point> points{}; /* 已提交且尚未过期的点。 */
|
std::vector<Constellation_Point> points{}; /* 已提交且尚未过期的点。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
|
std::size_t point_count{}; /* 当前发布且尚未过期的点数。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|||||||
@@ -31,19 +31,21 @@ struct Constellation_Diagram::Private : Prev_Private {
|
|||||||
/* CRTP 覆盖:直接绘制已准备的星座点与锚点。 */
|
/* CRTP 覆盖:直接绘制已准备的星座点与锚点。 */
|
||||||
template <Attached Object> void paint(Object* object);
|
template <Attached Object> void paint(Object* object);
|
||||||
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> states);
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> states);
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||||
[[nodiscard]] static Plot_Duration_Milliseconds now_ms();
|
[[nodiscard]] static Plot_Duration_Milliseconds now_ms();
|
||||||
};
|
};
|
||||||
template <typename Object> Constellation_Diagram::Builder<Object>::Builder(Scene_Object* scene_value, Axis_Object* i_axis_value, Axis_Object* q_axis_value) : Base(), scene(scene_value), i_axis(i_axis_value), q_axis(q_axis_value) {}
|
template <typename Object> Constellation_Diagram::Builder<Object>::Builder(Scene_Object* scene_value, Axis_Object* i_axis_value, Axis_Object* q_axis_value) : Base(), scene(scene_value), i_axis(i_axis_value), q_axis(q_axis_value) {}
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Constellation_Diagram::Builder<Object>::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); static_cast<typename Object::Private&>(*plot->d).bind_sources(scene, i_axis, q_axis); auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(i_axis); prepare.add(q_axis); prepare.add(plot.get()); prepare.template add_state_dependency<Render_Scene_2D_State_Tag>(plot.get(), scene); prepare.template add_state_dependency<Abs_Axis_State_Tag>(plot.get(), i_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(plot.get(), i_axis); prepare.template add_state_dependency<Abs_Axis_State_Tag>(plot.get(), q_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(plot.get(), q_axis); paint.add(i_axis); paint.add(q_axis); paint.add(plot.get()); }); if (!graph_result) return std::unexpected(graph_result.error()); return std::move(plot); }
|
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Constellation_Diagram::Builder<Object>::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); static_cast<typename Object::Private&>(*plot->d).bind_sources(scene, i_axis, q_axis); auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(i_axis); prepare.add(q_axis); prepare.add(plot.get()); prepare.template add_prop_dependency<Render_Scene_2D::Base_Tag>(plot.get(), scene); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(plot.get(), i_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(plot.get(), i_axis); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(plot.get(), q_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(plot.get(), q_axis); paint.add(i_axis); paint.add(q_axis); paint.add(plot.get()); }); if (!graph_result) return std::unexpected(graph_result.error()); return std::move(plot); }
|
||||||
inline Plot_Duration_Milliseconds Constellation_Diagram::Private::now_ms() { return static_cast<Plot_Duration_Milliseconds>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()); }
|
inline Plot_Duration_Milliseconds Constellation_Diagram::Private::now_ms() { return static_cast<Plot_Duration_Milliseconds>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count()); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Constellation_Diagram::Private::prepare_data(Object* object) { const auto& state = object->template read_state<Constellation_Diagram_State_Tag>(); const auto& i_layout = i_axis->template read_state<Abs_Axis_State_Tag>(); const auto& q_layout = q_axis->template read_state<Abs_Axis_State_Tag>(); prepared = {}; prepared.canvas = scene->template read_state<Render_Scene_2D_State_Tag>().viewport; if (prepared.canvas.empty() || i_layout.orientation == q_layout.orientation) return; const auto current = now_ms(); for (const auto& value : state.points) if (current - value.submitted_at_ms <= state.point_lifetime_ms) prepared.points.push_back(detail::map_plot_point(i_axis, value.point.x, q_axis, value.point.y, i_layout.orientation)); const int count = static_cast<int>(state.type); const Plot_Coordinate center_i = state.i_range.center(); const Plot_Coordinate center_q = state.q_range.center(); const Plot_Coordinate radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4; for (int index = 0; index < count; ++index) { const Plot_Ratio angle = state.phase_offset_radians + 2.0 * std::numbers::pi * index / count; prepared.anchors.push_back(detail::map_plot_point(i_axis, center_i + std::cos(angle) * radius, q_axis, center_q + std::sin(angle) * radius, i_layout.orientation)); } prepared.valid = true; object->template mark_dirty<Paint_Tag>(); }
|
void Constellation_Diagram::Private::prepare_data(Object* object) { const auto& state = object->template read_prop<Constellation_Diagram::Base_Tag>(); const auto& i_layout = i_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& q_layout = q_axis->template read_prop<Abs_Axis::Base_Tag>(); prepared = {}; prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport; if (prepared.canvas.empty() || i_layout.orientation == q_layout.orientation) return; const auto current = now_ms(); for (const auto& value : state.points) if (current - value.submitted_at_ms <= state.point_lifetime_ms) prepared.points.push_back(detail::map_plot_point(i_axis, value.point.x, q_axis, value.point.y, i_layout.orientation)); const int count = static_cast<int>(state.type); const Plot_Coordinate center_i = state.i_range.center(); const Plot_Coordinate center_q = state.q_range.center(); const Plot_Coordinate radius = std::min(state.i_range.size(), state.q_range.size()) * 0.4; for (int index = 0; index < count; ++index) { const Plot_Ratio angle = state.phase_offset_radians + 2.0 * std::numbers::pi * index / count; prepared.anchors.push_back(detail::map_plot_point(i_axis, center_i + std::cos(angle) * radius, q_axis, center_q + std::sin(angle) * radius, i_layout.orientation)); } prepared.valid = true; object->template mark_dirty<Paint_Tag>(); }
|
||||||
template <Attached Object> void Constellation_Diagram::Private::paint(Object* object) { const auto& state = object->template read_state<Constellation_Diagram_State_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); for (const auto& anchor : prepared.anchors) painter.circle(anchor, 4.0, Pen{state.anchor_color}, Brush{state.anchor_color, Brush_Style::solid}); for (const auto& point : prepared.points) painter.circle(point, 2.0, Pen{state.point_color}, Brush{state.point_color, Brush_Style::solid}); }
|
template <Attached Object> void Constellation_Diagram::Private::paint(Object* object) { const auto& state = object->template read_prop<Constellation_Diagram::Base_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); for (const auto& anchor : prepared.anchors) painter.circle(anchor, 4.0, Pen{state.anchor_color}, Brush{state.anchor_color, Brush_Style::solid}); for (const auto& point : prepared.points) painter.circle(point, 2.0, Pen{state.point_color}, Brush{state.point_color, Brush_Style::solid}); }
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void Constellation_Diagram::Private::after_state_set(Object* object, Member Owner::*, State_Access<State_Type>) { if constexpr (std::same_as<Owner, State>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void Constellation_Diagram::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void Constellation_Diagram::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { pending_states.template get<Constellation_Diagram::Base_Tag>().point_count = static_cast<const Prop&>(*current_prop).points.size(); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
const Constellation_Diagram::Private::Dispatch& Constellation_Diagram::Private::dispatch_for() { static const Dispatch value{[](Root* root, Point_F point) { auto* object = static_cast<Object*>(root); const auto submitted = Private::now_ms(); object->template update_state<&State::points>([=](State_Access<typename Object::State> states) { auto& state = states.template get<Constellation_Diagram_State_Tag>(); state.points.erase(std::remove_if(state.points.begin(), state.points.end(), [=](const auto& value) { return submitted - value.submitted_at_ms > state.point_lifetime_ms; }), state.points.end()); state.points.push_back({point, submitted}); }); }, [](const Root* root) { return static_cast<const Object*>(root)->template read_state<Constellation_Diagram_State_Tag>().points.size(); }, [](Root* root) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); const auto& state = object->template read_state<Constellation_Diagram_State_Tag>(); const Plot_Coordinate size = std::max(state.i_range.size(), state.q_range.size()); const Plot_Coordinate i_center = state.i_range.center(); const Plot_Coordinate q_center = state.q_range.center(); data.i_axis->template update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{i_center - size * 0.5, i_center + size * 0.5}); data.q_axis->template update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{q_center - size * 0.5, q_center + size * 0.5}); }}; return value; }
|
const Constellation_Diagram::Private::Dispatch& Constellation_Diagram::Private::dispatch_for() { static const Dispatch value{[](Root* root, Point_F point) { auto* object = static_cast<Object*>(root); const auto submitted = Private::now_ms(); object->template update_prop<&Prop::points>([=](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Constellation_Diagram::Base_Tag>(); state.points.erase(std::remove_if(state.points.begin(), state.points.end(), [=](const auto& value) { return submitted - value.submitted_at_ms > state.point_lifetime_ms; }), state.points.end()); state.points.push_back({point, submitted}); }); }, [](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Constellation_Diagram::Base_Tag>().points.size(); }, [](Root* root) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); const auto& state = object->template read_prop<Constellation_Diagram::Base_Tag>(); const Plot_Coordinate size = std::max(state.i_range.size(), state.q_range.size()); const Plot_Coordinate i_center = state.i_range.center(); const Plot_Coordinate q_center = state.q_range.center(); data.i_axis->template set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{i_center - size * 0.5, i_center + size * 0.5}); data.q_axis->template set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{q_center - size * 0.5, q_center + size * 0.5}); }}; return value; }
|
||||||
template <Attached Object> void Constellation_Diagram::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
template <Attached Object> void Constellation_Diagram::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
||||||
inline void Constellation_Diagram::Private::bind_sources(Scene_Object* scene_value, Axis_Object* i_axis_value, Axis_Object* q_axis_value) { scene = scene_value; i_axis = i_axis_value; q_axis = q_axis_value; }
|
inline void Constellation_Diagram::Private::bind_sources(Scene_Object* scene_value, Axis_Object* i_axis_value, Axis_Object* q_axis_value) { scene = scene_value; i_axis = i_axis_value; q_axis = q_axis_value; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Frequency_Trace.hpp"
|
#include "Frequency_Trace.hpp"
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
bool Frequency_Trace_Sample::operator==(const Frequency_Trace_Sample&) const = default;
|
bool Frequency_Trace_Sample::operator==(const Frequency_Trace_Sample&) const = default;
|
||||||
|
bool Frequency_Trace::Prop::operator==(const Prop&) const = default;
|
||||||
bool Frequency_Trace::State::operator==(const State&) const = default;
|
bool Frequency_Trace::State::operator==(const State&) const = default;
|
||||||
void Frequency_Trace::append_sample(Plot_Time_Tick tick, Plot_Value value) { static_cast<Private&>(*d).dispatch->append(this, tick, value); }
|
void Frequency_Trace::append_sample(Plot_Time_Tick tick, Plot_Value value) { static_cast<Private&>(*d).dispatch->append(this, tick, value); }
|
||||||
void Frequency_Trace::append_sample(Time_Of_Day time, Plot_Value value) { static_cast<Private&>(*d).dispatch->append_time(this, time, value); }
|
void Frequency_Trace::append_sample(Time_Of_Day time, Plot_Value value) { static_cast<Private&>(*d).dispatch->append_time(this, time, value); }
|
||||||
|
|||||||
@@ -7,22 +7,25 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
struct Frequency_Trace_State_Tag {};
|
|
||||||
struct Frequency_Trace_Sample {
|
struct Frequency_Trace_Sample {
|
||||||
Plot_Time_Tick tick{}; /* 时间轴上的单调样本序号。 */
|
Plot_Time_Tick tick{}; /* 时间轴上的单调样本序号。 */
|
||||||
Plot_Value value{}; /* 该时间点对应的频率值。 */
|
Plot_Value value{}; /* 该时间点对应的频率值。 */
|
||||||
bool operator==(const Frequency_Trace_Sample&) const;
|
bool operator==(const Frequency_Trace_Sample&) const;
|
||||||
};
|
};
|
||||||
struct Frequency_Trace : Def<Frequency_Trace, Renderable, State_Type<Frequency_Trace_State_Tag>, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
struct Frequency_Trace : Def<Frequency_Trace, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||||
using Scene_Object = Impl<Render_Scene_2D>;
|
using Scene_Object = Impl<Render_Scene_2D>;
|
||||||
using Time_Object = Impl<Time_Axis>;
|
using Time_Object = Impl<Time_Axis>;
|
||||||
using Value_Object = Impl<Numeric_Axis>;
|
using Value_Object = Impl<Numeric_Axis>;
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {
|
||||||
struct State : Prev_State<Frequency_Trace_State_Tag> {
|
|
||||||
Pen pen{Color::yellow()}; /* 频率轨迹折线样式。 */
|
Pen pen{Color::yellow()}; /* 频率轨迹折线样式。 */
|
||||||
Plot_Partition_Mode partition_mode{Plot_Partition_Mode::automatic}; /* Prepare 子图分块策略。 */
|
Plot_Partition_Mode partition_mode{Plot_Partition_Mode::automatic}; /* Prepare 子图分块策略。 */
|
||||||
Plot_Partition_Count partition_count{1}; /* fixed 模式的分块数量。 */
|
Plot_Partition_Count partition_count{1}; /* fixed 模式的分块数量。 */
|
||||||
std::vector<Frequency_Trace_Sample> samples{}; /* 已提交轨迹样本的唯一权威集合。 */
|
std::vector<Frequency_Trace_Sample> samples{}; /* 已提交轨迹样本的唯一权威集合。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
|
std::size_t sample_count{}; /* 当前发布的轨迹样本数。 */
|
||||||
|
std::size_t rendered_point_count{}; /* 最近一次 Prepare 生成的折线点数。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|||||||
@@ -29,16 +29,17 @@ struct Frequency_Trace::Private : Prev_Private {
|
|||||||
void bind_sources(Scene_Object* scene_value, Time_Object* time_axis_value, Value_Object* value_axis_value);
|
void bind_sources(Scene_Object* scene_value, Time_Object* time_axis_value, Value_Object* value_axis_value);
|
||||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||||
/* CRTP 覆盖:按当前样本规模构建分块 Prepare 子图。 */
|
/* CRTP 覆盖:按当前样本规模构建分块 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:构建消费已准备曲线的 Paint 子图。 */
|
/* CRTP 覆盖:构建消费已准备曲线的 Paint 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state);
|
||||||
template <Attached Object> void prepare_frame(Object* object, Plot_Partition_Count partition_count);
|
template <Attached Object> void prepare_frame(Object* object, Plot_Partition_Count partition_count);
|
||||||
template <Attached Object> void prepare_partition(Object* object, Plot_Partition_Count partition_index);
|
template <Attached Object> void prepare_partition(Object* object, Plot_Partition_Count partition_index);
|
||||||
template <Attached Object> void paint_frame(Object* object);
|
template <Attached Object> void paint_frame(Object* object);
|
||||||
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> pending_states);
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> pending_states);
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||||
};
|
};
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
Frequency_Trace::Builder<Object>::Builder(Scene_Object* scene_value, Time_Object* time_axis_value, Value_Object* value_axis_value) : Base(), scene(scene_value), time_axis(time_axis_value), value_axis(value_axis_value) {}
|
Frequency_Trace::Builder<Object>::Builder(Scene_Object* scene_value, Time_Object* time_axis_value, Value_Object* value_axis_value) : Base(), scene(scene_value), time_axis(time_axis_value), value_axis(value_axis_value) {}
|
||||||
@@ -48,50 +49,52 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Frequency_Trace::
|
|||||||
static_cast<typename Object::Private&>(*trace->d).bind_sources(scene, time_axis, value_axis);
|
static_cast<typename Object::Private&>(*trace->d).bind_sources(scene, time_axis, value_axis);
|
||||||
auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) {
|
auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) {
|
||||||
prepare.add(time_axis); prepare.add(value_axis); prepare.add(trace.get());
|
prepare.add(time_axis); prepare.add(value_axis); prepare.add(trace.get());
|
||||||
prepare.template add_state_dependency<Render_Scene_2D_State_Tag>(trace.get(), scene);
|
prepare.template add_prop_dependency<Render_Scene_2D::Base_Tag>(trace.get(), scene);
|
||||||
prepare.template add_state_dependency<Abs_Axis_State_Tag>(trace.get(), time_axis); prepare.template add_state_dependency<Time_Axis_State_Tag>(trace.get(), time_axis);
|
prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(trace.get(), time_axis); prepare.template add_prop_dependency<Time_Axis::Base_Tag>(trace.get(), time_axis);
|
||||||
prepare.template add_state_dependency<Abs_Axis_State_Tag>(trace.get(), value_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(trace.get(), value_axis);
|
prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(trace.get(), value_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(trace.get(), value_axis);
|
||||||
paint.add(time_axis); paint.add(value_axis); paint.add(trace.get());
|
paint.add(time_axis); paint.add(value_axis); paint.add(trace.get());
|
||||||
});
|
});
|
||||||
if (!graph_result) return std::unexpected(graph_result.error()); return std::move(trace);
|
if (!graph_result) return std::unexpected(graph_result.error()); return std::move(trace);
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
bool Frequency_Trace::Private::should_rebuild_prepare_graph(Object* object, const State& state) { return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, state.samples.size()); }
|
bool Frequency_Trace::Private::should_rebuild_prepare_graph(Object* object, const Prop& state) { return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, state.samples.size()); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
tf::Taskflow Frequency_Trace::Private::build_prepare_graph(Object* object, const State& state) {
|
tf::Taskflow Frequency_Trace::Private::build_prepare_graph(Object* object, const Prop& state) {
|
||||||
graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, state.samples.size()); tf::Taskflow graph;
|
graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, state.samples.size()); tf::Taskflow graph;
|
||||||
auto begin = graph.emplace([this, object] { prepare_frame(object, graph_partition_count); }).name("frequency_trace.prepare.frame");
|
auto begin = graph.emplace([this, object] { prepare_frame(object, graph_partition_count); }).name("frequency_trace.prepare.frame");
|
||||||
for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.emplace([this, object, index] { prepare_partition(object, index); }).name("frequency_trace.prepare.partition"); begin.precede(task); }
|
for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.emplace([this, object, index] { prepare_partition(object, index); }).name("frequency_trace.prepare.partition"); begin.precede(task); }
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
tf::Taskflow Frequency_Trace::Private::build_paint_graph(Object* object, const State&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("frequency_trace.paint.frame"); return graph; }
|
tf::Taskflow Frequency_Trace::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("frequency_trace.paint.frame"); return graph; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Frequency_Trace::Private::prepare_frame(Object* object, Plot_Partition_Count partition_count) {
|
void Frequency_Trace::Private::prepare_frame(Object* object, Plot_Partition_Count partition_count) {
|
||||||
const auto& state = object->template read_state<Frequency_Trace_State_Tag>(); const auto& time_layout = time_axis->template read_state<Abs_Axis_State_Tag>(); const auto& value_layout = value_axis->template read_state<Abs_Axis_State_Tag>();
|
const auto& state = object->template read_prop<Frequency_Trace::Base_Tag>(); const auto& time_layout = time_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& value_layout = value_axis->template read_prop<Abs_Axis::Base_Tag>();
|
||||||
prepared = {}; prepared.partitions.resize(partition_count); prepared.canvas = scene->template read_state<Render_Scene_2D_State_Tag>().viewport;
|
prepared = {}; prepared.partitions.resize(partition_count); prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport;
|
||||||
if (prepared.canvas.empty() || time_layout.orientation == value_layout.orientation || state.samples.empty()) return;
|
if (prepared.canvas.empty() || time_layout.orientation == value_layout.orientation || state.samples.empty()) return;
|
||||||
prepared.values.reserve(state.samples.size()); for (const auto& sample : state.samples) prepared.values.push_back(sample.value);
|
prepared.values.reserve(state.samples.size()); for (const auto& sample : state.samples) prepared.values.push_back(sample.value);
|
||||||
prepared.domain = {static_cast<Plot_Coordinate>(state.samples.front().tick), static_cast<Plot_Coordinate>(state.samples.back().tick)}; prepared.valid = true;
|
prepared.domain = {static_cast<Plot_Coordinate>(state.samples.front().tick), static_cast<Plot_Coordinate>(state.samples.back().tick)}; prepared.valid = true;
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Frequency_Trace::Private::prepare_partition(Object* object, Plot_Partition_Count partition_index) {
|
void Frequency_Trace::Private::prepare_partition(Object* object, Plot_Partition_Count partition_index) {
|
||||||
if (!prepared.valid) return; const auto& state = object->template read_state<Frequency_Trace_State_Tag>(); const auto& time_layout = time_axis->template read_state<Abs_Axis_State_Tag>(); const auto& value_layout = value_axis->template read_state<Abs_Axis_State_Tag>(); const auto& value_state = value_axis->template read_state<Numeric_Axis_State_Tag>();
|
if (!prepared.valid) return; const auto& state = object->template read_prop<Frequency_Trace::Base_Tag>(); const auto& time_layout = time_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& value_layout = value_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& value_state = value_axis->template read_prop<Numeric_Axis::Base_Tag>();
|
||||||
const auto range = detail::curve_partition_range(prepared.values.size(), partition_index, prepared.partitions.size(), prepared.domain);
|
const auto range = detail::curve_partition_range(prepared.values.size(), partition_index, prepared.partitions.size(), prepared.domain);
|
||||||
prepared.partitions[partition_index] = detail::prepare_curve(std::span<const Plot_Value>(prepared.values).subspan(range.first_sample, range.sample_count), range.domain, Line_Interpolation_Mode::linear_value, true, time_axis->coordinate_range(), value_state.coordinate_range, time_axis, value_axis, time_layout.orientation, value_layout.orientation);
|
prepared.partitions[partition_index] = detail::prepare_curve(std::span<const Plot_Value>(prepared.values).subspan(range.first_sample, range.sample_count), range.domain, Line_Interpolation_Mode::linear_value, true, time_axis->coordinate_range(), value_state.coordinate_range, time_axis, value_axis, time_layout.orientation, value_layout.orientation);
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Frequency_Trace::Private::paint_frame(Object* object) {
|
void Frequency_Trace::Private::paint_frame(Object* object) {
|
||||||
const auto& state = object->template read_state<Frequency_Trace_State_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); for (const auto& curve : prepared.partitions) detail::paint_curve(painter, curve, state.pen);
|
const auto& state = object->template read_prop<Frequency_Trace::Base_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); for (const auto& curve : prepared.partitions) detail::paint_curve(painter, curve, state.pen);
|
||||||
}
|
}
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type>
|
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||||
void Frequency_Trace::Private::after_state_set(Object* object, Member Owner::*, State_Access<State_Type>) { if constexpr (std::same_as<Owner, State>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
void Frequency_Trace::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type>
|
||||||
|
void Frequency_Trace::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { auto& state = pending_states.template get<Frequency_Trace::Base_Tag>(); const auto& prop = static_cast<const Prop&>(*current_prop); state.sample_count = prop.samples.size(); state.rendered_point_count = 0; for (const auto& partition : prepared.partitions) state.rendered_point_count += partition.points.size(); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
const Frequency_Trace::Private::Dispatch& Frequency_Trace::Private::dispatch_for() {
|
const Frequency_Trace::Private::Dispatch& Frequency_Trace::Private::dispatch_for() {
|
||||||
static const Dispatch value{
|
static const Dispatch value{
|
||||||
[](Root* root, Plot_Time_Tick tick, Plot_Value sample_value) { auto* object = static_cast<Object*>(root); object->template update_state<&State::samples>([=](State_Access<typename Object::State> states) { states.template get<Frequency_Trace_State_Tag>().samples.push_back({tick, sample_value}); }); },
|
[](Root* root, Plot_Time_Tick tick, Plot_Value sample_value) { auto* object = static_cast<Object*>(root); object->template update_prop<&Prop::samples>([=](Prop_Access<typename Object::Prop> props) { props.template get<Frequency_Trace::Base_Tag>().samples.push_back({tick, sample_value}); }); },
|
||||||
[](Root* root, Time_Of_Day time, Plot_Value sample_value) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); const Plot_Time_Tick tick = data.time_axis->append_time(time); object->template update_state<&State::samples>([=](State_Access<typename Object::State> states) { states.template get<Frequency_Trace_State_Tag>().samples.push_back({tick, sample_value}); }); },
|
[](Root* root, Time_Of_Day time, Plot_Value sample_value) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); const Plot_Time_Tick tick = data.time_axis->append_time(time); object->template update_prop<&Prop::samples>([=](Prop_Access<typename Object::Prop> props) { props.template get<Frequency_Trace::Base_Tag>().samples.push_back({tick, sample_value}); }); },
|
||||||
[](const Root* root) { return static_cast<const Object*>(root)->template read_state<Frequency_Trace_State_Tag>().samples.size(); },
|
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Frequency_Trace::Base_Tag>().samples.size(); },
|
||||||
[](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); std::size_t result{}; for (const auto& curve : data.prepared.partitions) result += curve.points.size(); return result; }
|
[](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); std::size_t result{}; for (const auto& curve : data.prepared.partitions) result += curve.points.size(); return result; }
|
||||||
}; return value;
|
}; return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "Selection_Rectangle_Overlay.hpp"
|
#include "Selection_Rectangle_Overlay.hpp"
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
|
bool Selection_Rectangle_Overlay::Prop::operator==(const Prop&) const = default;
|
||||||
bool Selection_Rectangle_Overlay::State::operator==(const State&) const = default;
|
bool Selection_Rectangle_Overlay::State::operator==(const State&) const = default;
|
||||||
std::vector<Rect_F> Selection_Rectangle_Overlay::selected_regions() const { return static_cast<const Private&>(*d).dispatch->selected_regions(this); }
|
std::vector<Rect_F> Selection_Rectangle_Overlay::selected_regions() const { return static_cast<const Private&>(*d).dispatch->selected_regions(this); }
|
||||||
void Selection_Rectangle_Overlay::clear_selected_regions() { static_cast<Private&>(*d).dispatch->clear_selected_regions(this); }
|
void Selection_Rectangle_Overlay::clear_selected_regions() { static_cast<Private&>(*d).dispatch->clear_selected_regions(this); }
|
||||||
|
|||||||
@@ -7,17 +7,19 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
struct Selection_Rectangle_Overlay_State_Tag {};
|
struct Selection_Rectangle_Overlay : Def<Selection_Rectangle_Overlay, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||||
struct Selection_Rectangle_Overlay : Def<Selection_Rectangle_Overlay, Renderable, State_Type<Selection_Rectangle_Overlay_State_Tag>, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
|
||||||
using Scene_Object = Impl<Render_Scene_2D>;
|
using Scene_Object = Impl<Render_Scene_2D>;
|
||||||
using Axis_Object = Impl<Numeric_Axis>;
|
using Axis_Object = Impl<Numeric_Axis>;
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {
|
||||||
struct State : Prev_State<Selection_Rectangle_Overlay_State_Tag> {
|
|
||||||
Font label_font{}; /* 选择范围标签使用的字体。 */
|
Font label_font{}; /* 选择范围标签使用的字体。 */
|
||||||
Pen label_pen{Color::white()}; /* 选择范围标签的文字样式。 */
|
Pen label_pen{Color::white()}; /* 选择范围标签的文字样式。 */
|
||||||
Brush selection_brush{Color{0, 0, 255, 50}, Brush_Style::solid}; /* 选择矩形内部填充。 */
|
Brush selection_brush{Color{0, 0, 255, 50}, Brush_Style::solid}; /* 选择矩形内部填充。 */
|
||||||
Pen selection_border_pen{Color::white(), 1.0, Line_Style::dash}; /* 选择矩形边框样式。 */
|
Pen selection_border_pen{Color::white(), 1.0, Line_Style::dash}; /* 选择矩形边框样式。 */
|
||||||
std::vector<Rect_F> selected_regions{}; /* 已完成选择的轴坐标矩形。 */
|
std::vector<Rect_F> selected_regions{}; /* 已完成选择的轴坐标矩形。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
|
std::size_t selected_region_count{}; /* 当前已完成选择的矩形数量。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ struct Selection_Rectangle_Overlay::Private : Prev_Private {
|
|||||||
/* CRTP 覆盖:处理拖拽并更新权威选择区域状态。 */
|
/* CRTP 覆盖:处理拖拽并更新权威选择区域状态。 */
|
||||||
template <Attached Object> void handle_event(Object* object, const Event& event);
|
template <Attached Object> void handle_event(Object* object, const Event& event);
|
||||||
/* CRTP 覆盖:本类状态写入后只标记 Paint 数据失效。 */
|
/* CRTP 覆盖:本类状态写入后只标记 Paint 数据失效。 */
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> pending_states);
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> pending_states);
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||||
};
|
};
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
Selection_Rectangle_Overlay::Builder<Object>::Builder(Scene_Object* scene_value, Axis_Object* horizontal_axis_value, Axis_Object* vertical_axis_value) : Base(), scene(scene_value), horizontal_axis(horizontal_axis_value), vertical_axis(vertical_axis_value) {}
|
Selection_Rectangle_Overlay::Builder<Object>::Builder(Scene_Object* scene_value, Axis_Object* horizontal_axis_value, Axis_Object* vertical_axis_value) : Base(), scene(scene_value), horizontal_axis(horizontal_axis_value), vertical_axis(vertical_axis_value) {}
|
||||||
@@ -38,11 +39,11 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Selection_Rectang
|
|||||||
static_cast<typename Object::Private&>(*overlay->d).bind_sources(scene, horizontal_axis, vertical_axis);
|
static_cast<typename Object::Private&>(*overlay->d).bind_sources(scene, horizontal_axis, vertical_axis);
|
||||||
auto graph_result = scene->template edit_dependency_graph<Paint_Tag>([&](auto& paint) {
|
auto graph_result = scene->template edit_dependency_graph<Paint_Tag>([&](auto& paint) {
|
||||||
paint.add(overlay.get());
|
paint.add(overlay.get());
|
||||||
paint.template add_state_dependency<Render_Scene_2D_State_Tag>(overlay.get(), scene);
|
paint.template add_prop_dependency<Render_Scene_2D::Base_Tag>(overlay.get(), scene);
|
||||||
paint.template add_state_dependency<Abs_Axis_State_Tag>(overlay.get(), horizontal_axis);
|
paint.template add_prop_dependency<Abs_Axis::Base_Tag>(overlay.get(), horizontal_axis);
|
||||||
paint.template add_state_dependency<Numeric_Axis_State_Tag>(overlay.get(), horizontal_axis);
|
paint.template add_prop_dependency<Numeric_Axis::Base_Tag>(overlay.get(), horizontal_axis);
|
||||||
paint.template add_state_dependency<Abs_Axis_State_Tag>(overlay.get(), vertical_axis);
|
paint.template add_prop_dependency<Abs_Axis::Base_Tag>(overlay.get(), vertical_axis);
|
||||||
paint.template add_state_dependency<Numeric_Axis_State_Tag>(overlay.get(), vertical_axis);
|
paint.template add_prop_dependency<Numeric_Axis::Base_Tag>(overlay.get(), vertical_axis);
|
||||||
});
|
});
|
||||||
if (!graph_result) return std::unexpected(graph_result.error());
|
if (!graph_result) return std::unexpected(graph_result.error());
|
||||||
return std::move(overlay);
|
return std::move(overlay);
|
||||||
@@ -50,8 +51,8 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Selection_Rectang
|
|||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Selection_Rectangle_Overlay::Private::paint(Object* object) {
|
void Selection_Rectangle_Overlay::Private::paint(Object* object) {
|
||||||
auto& data = static_cast<typename Object::Private&>(*this);
|
auto& data = static_cast<typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*data.state.current);
|
const auto& state = static_cast<const Prop&>(*data.current);
|
||||||
const Size canvas = scene->template read_state<Render_Scene_2D_State_Tag>().viewport;
|
const Size canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport;
|
||||||
auto& cache = object->template pending_buffer<Color_Cache>();
|
auto& cache = object->template pending_buffer<Color_Cache>();
|
||||||
cache.ensure_size(canvas); cache.clear();
|
cache.ensure_size(canvas); cache.clear();
|
||||||
if (canvas.empty()) return;
|
if (canvas.empty()) return;
|
||||||
@@ -76,16 +77,18 @@ void Selection_Rectangle_Overlay::Private::handle_event(Object* object, const Ev
|
|||||||
const Axis_Coordinate second_x = horizontal_axis->point_to_coordinate(drag_current);
|
const Axis_Coordinate second_x = horizontal_axis->point_to_coordinate(drag_current);
|
||||||
const Axis_Coordinate first_y = vertical_axis->point_to_coordinate(drag_origin);
|
const Axis_Coordinate first_y = vertical_axis->point_to_coordinate(drag_origin);
|
||||||
const Axis_Coordinate second_y = vertical_axis->point_to_coordinate(drag_current);
|
const Axis_Coordinate second_y = vertical_axis->point_to_coordinate(drag_current);
|
||||||
object->template update_state<&State::selected_regions>([=](State_Access<typename Object::State> states) { states.template get<Selection_Rectangle_Overlay_State_Tag>().selected_regions.push_back(Rect_F{first_x, first_y, second_x - first_x, second_y - first_y}.normalized()); });
|
object->template update_prop<&Prop::selected_regions>([=](Prop_Access<typename Object::Prop> props) { props.template get<Selection_Rectangle_Overlay::Base_Tag>().selected_regions.push_back(Rect_F{first_x, first_y, second_x - first_x, second_y - first_y}.normalized()); });
|
||||||
event.accept();
|
event.accept();
|
||||||
}
|
}
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type>
|
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||||
void Selection_Rectangle_Overlay::Private::after_state_set(Object* object, Member Owner::*, State_Access<State_Type>) { if constexpr (std::same_as<Owner, State>) object->template mark_dirty<Paint_Tag>(); }
|
void Selection_Rectangle_Overlay::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Paint_Tag>(); }
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type>
|
||||||
|
void Selection_Rectangle_Overlay::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { pending_states.template get<Selection_Rectangle_Overlay::Base_Tag>().selected_region_count = static_cast<const Prop&>(*current_prop).selected_regions.size(); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
const Selection_Rectangle_Overlay::Private::Dispatch& Selection_Rectangle_Overlay::Private::dispatch_for() {
|
const Selection_Rectangle_Overlay::Private::Dispatch& Selection_Rectangle_Overlay::Private::dispatch_for() {
|
||||||
static const Dispatch value{
|
static const Dispatch value{
|
||||||
[](const Root* root) { return static_cast<const Object*>(root)->template read_state<Selection_Rectangle_Overlay_State_Tag>().selected_regions; },
|
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Selection_Rectangle_Overlay::Base_Tag>().selected_regions; },
|
||||||
[](Root* root) { auto* object = static_cast<Object*>(root); object->template update_state<&State::selected_regions>(std::vector<Rect_F>{}); }
|
[](Root* root) { static_cast<Object*>(root)->template set<&Prop::selected_regions>(std::vector<Rect_F>{}); }
|
||||||
};
|
};
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Spectrum.hpp"
|
#include "Spectrum.hpp"
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
bool Spectrum_Frame::operator==(const Spectrum_Frame&) const = default;
|
bool Spectrum_Frame::operator==(const Spectrum_Frame&) const = default;
|
||||||
|
bool Spectrum::Prop::operator==(const Prop&) const = default;
|
||||||
bool Spectrum::State::operator==(const State&) const = default;
|
bool Spectrum::State::operator==(const State&) const = default;
|
||||||
void Spectrum::update_samples(std::span<const Spectrum_Power> values) {
|
void Spectrum::update_samples(std::span<const Spectrum_Power> values) {
|
||||||
static_cast<Private&>(*d).dispatch->update_samples(this, values);
|
static_cast<Private&>(*d).dispatch->update_samples(this, values);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ using Spectrum_Power = Plot_Value;
|
|||||||
using Spectrum_Interpolation_Ratio = Plot_Ratio;
|
using Spectrum_Interpolation_Ratio = Plot_Ratio;
|
||||||
using Spectrum_Marker_Index = Plot_Index;
|
using Spectrum_Marker_Index = Plot_Index;
|
||||||
using Spectrum_Partition_Mode = Plot_Partition_Mode;
|
using Spectrum_Partition_Mode = Plot_Partition_Mode;
|
||||||
struct Spectrum_State_Tag {};
|
|
||||||
struct Spectrum_Frame_Tag {};
|
struct Spectrum_Frame_Tag {};
|
||||||
struct Spectrum_Frame {
|
struct Spectrum_Frame {
|
||||||
std::vector<Spectrum_Power> samples{}; /* 最近提交的当前频谱功率样本。 */
|
std::vector<Spectrum_Power> samples{}; /* 最近提交的当前频谱功率样本。 */
|
||||||
@@ -24,12 +23,11 @@ struct Spectrum_Frame {
|
|||||||
bool operator==(const Spectrum_Frame&) const;
|
bool operator==(const Spectrum_Frame&) const;
|
||||||
};
|
};
|
||||||
/* 使用频率轴和功率轴分块准备、绘制当前值、保持曲线及频率标记。 */
|
/* 使用频率轴和功率轴分块准备、绘制当前值、保持曲线及频率标记。 */
|
||||||
struct Spectrum : Def<Spectrum, Renderable, State_Type<Spectrum_State_Tag>, Tagged_Buffer<Color_Cache, Blend2D_Cache>, Tagged_Buffer<Spectrum_Frame_Tag, Spectrum_Frame>> {
|
struct Spectrum : Def<Spectrum, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>, Tagged_Buffer<Spectrum_Frame_Tag, Spectrum_Frame>> {
|
||||||
using Scene_Object = Impl<Render_Scene_2D>;
|
using Scene_Object = Impl<Render_Scene_2D>;
|
||||||
using Frequency_Object = Impl<Frequency_Axis>;
|
using Frequency_Object = Impl<Frequency_Axis>;
|
||||||
using Power_Object = Impl<Numeric_Axis>;
|
using Power_Object = Impl<Numeric_Axis>;
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {
|
||||||
struct State : Prev_State<Spectrum_State_Tag> {
|
|
||||||
Axis_Range frequency_range{}; /* 输入样本首尾对应的有向频率范围,单位为 Hz。 */
|
Axis_Range frequency_range{}; /* 输入样本首尾对应的有向频率范围,单位为 Hz。 */
|
||||||
Spectrum_Frequency center_frequency{50.0}; /* 中心频率标记位置,单位为 Hz。 */
|
Spectrum_Frequency center_frequency{50.0}; /* 中心频率标记位置,单位为 Hz。 */
|
||||||
Axis_Range sweep_frequency_range{40.0, 60.0}; /* 扫频背景覆盖的频率范围,单位为 Hz。 */
|
Axis_Range sweep_frequency_range{40.0, 60.0}; /* 扫频背景覆盖的频率范围,单位为 Hz。 */
|
||||||
@@ -54,6 +52,12 @@ struct Spectrum : Def<Spectrum, Renderable, State_Type<Spectrum_State_Tag>, Tagg
|
|||||||
Brush sweep_region_brush{Color{255, 255, 0, 100}, Brush_Style::solid}; /* 扫频区域背景样式。 */
|
Brush sweep_region_brush{Color{255, 255, 0, 100}, Brush_Style::solid}; /* 扫频区域背景样式。 */
|
||||||
std::vector<Spectrum_Frequency> custom_markers{}; /* 自定义频率标记的唯一权威集合,单位为 Hz。 */
|
std::vector<Spectrum_Frequency> custom_markers{}; /* 自定义频率标记的唯一权威集合,单位为 Hz。 */
|
||||||
Spectrum_Marker_Index selected_marker{-1}; /* 当前选中标记下标;-1 表示未选择。 */
|
Spectrum_Marker_Index selected_marker{-1}; /* 当前选中标记下标;-1 表示未选择。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
|
std::size_t sample_count{}; /* 最近一次发布帧的样本数。 */
|
||||||
|
std::size_t rendered_point_count{}; /* 最近一次 Prepare 生成的曲线点数。 */
|
||||||
|
std::size_t selectable_marker_count{}; /* 当前可选择的自定义标记数。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
/* 完整声明、分块子图、内部状态操作和 CRTP 分派见 Spectrum.ipp。 */
|
/* 完整声明、分块子图、内部状态操作和 CRTP 分派见 Spectrum.ipp。 */
|
||||||
|
|||||||
@@ -80,18 +80,19 @@ struct Spectrum::Private : Prev_Private {
|
|||||||
template <Attached Object> [[nodiscard]] Set_Marker_Frequency_Result set_marker_frequency(Object* object, Spectrum_Marker_Index index, Spectrum_Frequency frequency);
|
template <Attached Object> [[nodiscard]] Set_Marker_Frequency_Result set_marker_frequency(Object* object, Spectrum_Marker_Index index, Spectrum_Frequency frequency);
|
||||||
template <Attached Object> [[nodiscard]] Set_Current_Marker_Frequency_Result set_current_marker_frequency(Object* object, Spectrum_Frequency frequency);
|
template <Attached Object> [[nodiscard]] Set_Current_Marker_Frequency_Result set_current_marker_frequency(Object* object, Spectrum_Frequency frequency);
|
||||||
/* CRTP State 钩子:Spectrum 业务状态写入后标记自身 Prepare;其他继承层状态由各自 Private 负责。 */
|
/* CRTP State 钩子:Spectrum 业务状态写入后标记自身 Prepare;其他继承层状态由各自 Private 负责。 */
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> pending_states);
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> pending_states);
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||||
/* CRTP 子图能力:按当前样本数和 State 分块策略构建并行 Prepare 图。 */
|
/* CRTP 子图能力:按当前样本数和 State 分块策略构建并行 Prepare 图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 子图能力:构建背景、分块曲线和覆盖标记的 Paint 图。 */
|
/* CRTP 子图能力:构建背景、分块曲线和覆盖标记的 Paint 图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:样本规模或分块配置改变时重建 Prepare 子图。 */
|
/* CRTP 覆盖:样本规模或分块配置改变时重建 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state);
|
||||||
template <Attached Object> [[nodiscard]] std::size_t desired_partition_count(const Object* object, const State& state) const;
|
template <Attached Object> [[nodiscard]] std::size_t desired_partition_count(const Object* object, const Prop& state) const;
|
||||||
template <Attached Object> void prepare_frame(Object* object, std::size_t partition_count);
|
template <Attached Object> void prepare_frame(Object* object, std::size_t partition_count);
|
||||||
template <Attached Object> void prepare_partition(Object* object, std::size_t partition_index);
|
template <Attached Object> void prepare_partition(Object* object, std::size_t partition_index);
|
||||||
template <Attached Object> void paint_frame(Object* object);
|
template <Attached Object> void paint_frame(Object* object);
|
||||||
[[nodiscard]] static std::expected<Spectrum_Power, Power_At_Result> power_at(const State& state, const Spectrum_Frame& frame, Spectrum_Frequency frequency);
|
[[nodiscard]] static std::expected<Spectrum_Power, Power_At_Result> power_at(const Prop& state, const Spectrum_Frame& frame, Spectrum_Frequency frequency);
|
||||||
};
|
};
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
Spectrum::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), power_axis(power_axis_value) {}
|
Spectrum::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), power_axis(power_axis_value) {}
|
||||||
@@ -105,11 +106,11 @@ std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Spectrum::Builder
|
|||||||
prepare.add(frequency_axis);
|
prepare.add(frequency_axis);
|
||||||
prepare.add(power_axis);
|
prepare.add(power_axis);
|
||||||
prepare.add(spectrum.get());
|
prepare.add(spectrum.get());
|
||||||
prepare.template add_state_dependency<Render_Scene_2D_State_Tag>(spectrum.get(), scene);
|
prepare.template add_prop_dependency<Render_Scene_2D::Base_Tag>(spectrum.get(), scene);
|
||||||
prepare.template add_state_dependency<Abs_Axis_State_Tag>(spectrum.get(), frequency_axis);
|
prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(spectrum.get(), frequency_axis);
|
||||||
prepare.template add_state_dependency<Numeric_Axis_State_Tag>(spectrum.get(), frequency_axis);
|
prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(spectrum.get(), frequency_axis);
|
||||||
prepare.template add_state_dependency<Abs_Axis_State_Tag>(spectrum.get(), power_axis);
|
prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(spectrum.get(), power_axis);
|
||||||
prepare.template add_state_dependency<Numeric_Axis_State_Tag>(spectrum.get(), power_axis);
|
prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(spectrum.get(), power_axis);
|
||||||
paint.add(frequency_axis);
|
paint.add(frequency_axis);
|
||||||
paint.add(power_axis);
|
paint.add(power_axis);
|
||||||
paint.add(spectrum.get());
|
paint.add(spectrum.get());
|
||||||
@@ -121,7 +122,7 @@ template <typename Values>
|
|||||||
void Spectrum::update_samples(const Values& values) {
|
void Spectrum::update_samples(const Values& values) {
|
||||||
update_samples(std::span<const Spectrum_Power>(std::data(values), std::size(values)));
|
update_samples(std::span<const Spectrum_Power>(std::data(values), std::size(values)));
|
||||||
}
|
}
|
||||||
inline std::expected<Spectrum_Power, Spectrum::Power_At_Result> Spectrum::Private::power_at(const State& state, const Spectrum_Frame& frame, Spectrum_Frequency frequency) {
|
inline std::expected<Spectrum_Power, Spectrum::Power_At_Result> Spectrum::Private::power_at(const Prop& state, const Spectrum_Frame& frame, Spectrum_Frequency frequency) {
|
||||||
if (frame.samples.empty()) return std::unexpected(Power_At_Result::no_samples);
|
if (frame.samples.empty()) return std::unexpected(Power_At_Result::no_samples);
|
||||||
if (state.frequency_range.length() == 0.0) return std::unexpected(Power_At_Result::invalid_frequency_range);
|
if (state.frequency_range.length() == 0.0) return std::unexpected(Power_At_Result::invalid_frequency_range);
|
||||||
if (!state.frequency_range.contains(frequency)) return std::unexpected(Power_At_Result::frequency_out_of_range);
|
if (!state.frequency_range.contains(frequency)) return std::unexpected(Power_At_Result::frequency_out_of_range);
|
||||||
@@ -133,15 +134,15 @@ inline std::expected<Spectrum_Power, Spectrum::Power_At_Result> Spectrum::Privat
|
|||||||
return frame.samples[lower] * (1.0 - fraction) + frame.samples[upper] * fraction;
|
return frame.samples[lower] * (1.0 - fraction) + frame.samples[upper] * fraction;
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
std::size_t Spectrum::Private::desired_partition_count(const Object* object, const State& state) const {
|
std::size_t Spectrum::Private::desired_partition_count(const Object* object, const Prop& state) const {
|
||||||
return detail::curve_partition_count(state.partition_mode, state.partition_count, object->template current_buffer<Spectrum_Frame_Tag>().samples.size());
|
return detail::curve_partition_count(state.partition_mode, state.partition_count, object->template current_buffer<Spectrum_Frame_Tag>().samples.size());
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
bool Spectrum::Private::should_rebuild_prepare_graph(Object* object, const State& state) {
|
bool Spectrum::Private::should_rebuild_prepare_graph(Object* object, const Prop& state) {
|
||||||
return prepare_graph_partition_count != desired_partition_count(object, state);
|
return prepare_graph_partition_count != desired_partition_count(object, state);
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
tf::Taskflow Spectrum::Private::build_prepare_graph(Object* object, const State& state) {
|
tf::Taskflow Spectrum::Private::build_prepare_graph(Object* object, const Prop& state) {
|
||||||
const std::size_t partition_count = desired_partition_count(object, state);
|
const std::size_t partition_count = desired_partition_count(object, state);
|
||||||
prepare_graph_partition_count = partition_count;
|
prepare_graph_partition_count = partition_count;
|
||||||
tf::Taskflow graph;
|
tf::Taskflow graph;
|
||||||
@@ -153,7 +154,7 @@ tf::Taskflow Spectrum::Private::build_prepare_graph(Object* object, const State&
|
|||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
tf::Taskflow Spectrum::Private::build_paint_graph(Object* object, const State&) {
|
tf::Taskflow Spectrum::Private::build_paint_graph(Object* object, const Prop&) {
|
||||||
tf::Taskflow graph;
|
tf::Taskflow graph;
|
||||||
graph.emplace([this, object] { paint_frame(object); }).name("spectrum.paint.frame");
|
graph.emplace([this, object] { paint_frame(object); }).name("spectrum.paint.frame");
|
||||||
return graph;
|
return graph;
|
||||||
@@ -161,12 +162,12 @@ tf::Taskflow Spectrum::Private::build_paint_graph(Object* object, const State&)
|
|||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::prepare_frame(Object* object, std::size_t partition_count) {
|
void Spectrum::Private::prepare_frame(Object* object, std::size_t partition_count) {
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const Prop&>(*private_data.current);
|
||||||
const auto& frame = object->template current_buffer<Spectrum_Frame_Tag>();
|
const auto& frame = object->template current_buffer<Spectrum_Frame_Tag>();
|
||||||
const auto& scene_state = scene->template read_state<Render_Scene_2D_State_Tag>();
|
const auto& scene_state = scene->template read_prop<Render_Scene_2D::Base_Tag>();
|
||||||
const auto& frequency_layout = frequency_axis->template read_state<Abs_Axis_State_Tag>();
|
const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>();
|
||||||
const auto& power_layout = power_axis->template read_state<Abs_Axis_State_Tag>();
|
const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>();
|
||||||
const auto& power_state = power_axis->template read_state<Numeric_Axis_State_Tag>();
|
const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>();
|
||||||
prepared = {};
|
prepared = {};
|
||||||
prepared.partitions.resize(partition_count);
|
prepared.partitions.resize(partition_count);
|
||||||
if (frequency_layout.orientation == power_layout.orientation) return;
|
if (frequency_layout.orientation == power_layout.orientation) return;
|
||||||
@@ -196,13 +197,13 @@ template <Attached Object>
|
|||||||
void Spectrum::Private::prepare_partition(Object* object, std::size_t partition_index) {
|
void Spectrum::Private::prepare_partition(Object* object, std::size_t partition_index) {
|
||||||
if (!prepared.valid || partition_index >= prepared.partitions.size()) return;
|
if (!prepared.valid || partition_index >= prepared.partitions.size()) return;
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const Prop&>(*private_data.current);
|
||||||
const auto& frame = object->template current_buffer<Spectrum_Frame_Tag>();
|
const auto& frame = object->template current_buffer<Spectrum_Frame_Tag>();
|
||||||
if (frame.samples.empty()) return;
|
if (frame.samples.empty()) return;
|
||||||
const auto& frequency_layout = frequency_axis->template read_state<Abs_Axis_State_Tag>();
|
const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>();
|
||||||
const auto& power_layout = power_axis->template read_state<Abs_Axis_State_Tag>();
|
const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>();
|
||||||
const auto& frequency_state = frequency_axis->template read_state<Numeric_Axis_State_Tag>();
|
const auto& frequency_state = frequency_axis->template read_prop<Numeric_Axis::Base_Tag>();
|
||||||
const auto& power_state = power_axis->template read_state<Numeric_Axis_State_Tag>();
|
const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>();
|
||||||
const auto range = detail::curve_partition_range(frame.samples.size(), partition_index, prepared.partitions.size(), state.frequency_range);
|
const auto range = detail::curve_partition_range(frame.samples.size(), partition_index, prepared.partitions.size(), state.frequency_range);
|
||||||
auto& partition = prepared.partitions[partition_index];
|
auto& partition = prepared.partitions[partition_index];
|
||||||
partition.clip = detail::map_plot_rect(frequency_axis, range.domain, power_axis, power_state.coordinate_range, frequency_layout.orientation);
|
partition.clip = detail::map_plot_rect(frequency_axis, range.domain, power_axis, power_state.coordinate_range, frequency_layout.orientation);
|
||||||
@@ -213,7 +214,7 @@ void Spectrum::Private::prepare_partition(Object* object, std::size_t partition_
|
|||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::paint_frame(Object* object) {
|
void Spectrum::Private::paint_frame(Object* object) {
|
||||||
auto& private_data = static_cast<typename Object::Private&>(*this);
|
auto& private_data = static_cast<typename Object::Private&>(*this);
|
||||||
const auto& state = static_cast<const State&>(*private_data.state.current);
|
const auto& state = static_cast<const Prop&>(*private_data.current);
|
||||||
auto& cache = object->template pending_buffer<Color_Cache>();
|
auto& cache = object->template pending_buffer<Color_Cache>();
|
||||||
cache.ensure_size(prepared.canvas_size);
|
cache.ensure_size(prepared.canvas_size);
|
||||||
cache.clear();
|
cache.clear();
|
||||||
@@ -252,13 +253,13 @@ void Spectrum::Private::update_samples(Object* object, std::span<const Spectrum_
|
|||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::add_marker(Object* object, Spectrum_Frequency frequency) {
|
void Spectrum::Private::add_marker(Object* object, Spectrum_Frequency frequency) {
|
||||||
object->template update_state<&State::custom_markers>([frequency](State_Access<typename Object::State> states) { states.template get<Spectrum_State_Tag>().custom_markers.push_back(frequency); });
|
object->template update_prop<&Prop::custom_markers>([frequency](Prop_Access<typename Object::Prop> props) { props.template get<Spectrum::Base_Tag>().custom_markers.push_back(frequency); });
|
||||||
object->template mark_dirty<Prepare_Data_Tag>();
|
object->template mark_dirty<Prepare_Data_Tag>();
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::remove_marker(Object* object, Spectrum_Frequency frequency) {
|
void Spectrum::Private::remove_marker(Object* object, Spectrum_Frequency frequency) {
|
||||||
object->template update_state<&State::custom_markers, &State::selected_marker>([frequency](State_Access<typename Object::State> states) {
|
object->template update_prop<&Prop::custom_markers, &Prop::selected_marker>([frequency](Prop_Access<typename Object::Prop> props) {
|
||||||
auto& state = states.template get<Spectrum_State_Tag>();
|
auto& state = props.template get<Spectrum::Base_Tag>();
|
||||||
if (state.custom_markers.empty()) return;
|
if (state.custom_markers.empty()) return;
|
||||||
const auto closest = std::min_element(state.custom_markers.begin(), state.custom_markers.end(), [frequency](Spectrum_Frequency left, Spectrum_Frequency right) { return std::abs(left - frequency) < std::abs(right - frequency); });
|
const auto closest = std::min_element(state.custom_markers.begin(), state.custom_markers.end(), [frequency](Spectrum_Frequency left, Spectrum_Frequency right) { return std::abs(left - frequency) < std::abs(right - frequency); });
|
||||||
const Spectrum_Marker_Index removed = std::distance(state.custom_markers.begin(), closest);
|
const Spectrum_Marker_Index removed = std::distance(state.custom_markers.begin(), closest);
|
||||||
@@ -270,8 +271,8 @@ void Spectrum::Private::remove_marker(Object* object, Spectrum_Frequency frequen
|
|||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::remove_selected_marker(Object* object) {
|
void Spectrum::Private::remove_selected_marker(Object* object) {
|
||||||
object->template update_state<&State::custom_markers, &State::selected_marker>([](State_Access<typename Object::State> states) {
|
object->template update_prop<&Prop::custom_markers, &Prop::selected_marker>([](Prop_Access<typename Object::Prop> props) {
|
||||||
auto& state = states.template get<Spectrum_State_Tag>();
|
auto& state = props.template get<Spectrum::Base_Tag>();
|
||||||
if (state.selected_marker < 0 || static_cast<std::size_t>(state.selected_marker) >= state.custom_markers.size()) return;
|
if (state.selected_marker < 0 || static_cast<std::size_t>(state.selected_marker) >= state.custom_markers.size()) return;
|
||||||
state.custom_markers.erase(state.custom_markers.begin() + state.selected_marker);
|
state.custom_markers.erase(state.custom_markers.begin() + state.selected_marker);
|
||||||
state.selected_marker = -1;
|
state.selected_marker = -1;
|
||||||
@@ -280,40 +281,49 @@ void Spectrum::Private::remove_selected_marker(Object* object) {
|
|||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::clear_markers(Object* object) {
|
void Spectrum::Private::clear_markers(Object* object) {
|
||||||
object->template update_state<&State::custom_markers, &State::selected_marker>([](State_Access<typename Object::State> states) { auto& state = states.template get<Spectrum_State_Tag>(); state.custom_markers.clear(); state.selected_marker = -1; });
|
object->template update_prop<&Prop::custom_markers, &Prop::selected_marker>([](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Spectrum::Base_Tag>(); state.custom_markers.clear(); state.selected_marker = -1; });
|
||||||
object->template mark_dirty<Prepare_Data_Tag>();
|
object->template mark_dirty<Prepare_Data_Tag>();
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::set_selected_marker(Object* object, Spectrum_Marker_Index index) {
|
void Spectrum::Private::set_selected_marker(Object* object, Spectrum_Marker_Index index) {
|
||||||
object->template update_state<&State::selected_marker>([index](State_Access<typename Object::State> states) { auto& state = states.template get<Spectrum_State_Tag>(); state.selected_marker = index >= 0 && static_cast<std::size_t>(index) < state.custom_markers.size() ? index : -1; });
|
object->template update_prop<&Prop::selected_marker>([index](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Spectrum::Base_Tag>(); state.selected_marker = index >= 0 && static_cast<std::size_t>(index) < state.custom_markers.size() ? index : -1; });
|
||||||
object->template mark_dirty<Prepare_Data_Tag>();
|
object->template mark_dirty<Prepare_Data_Tag>();
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::select_next_marker(Object* object) {
|
void Spectrum::Private::select_next_marker(Object* object) {
|
||||||
object->template update_state<&State::selected_marker>([](State_Access<typename Object::State> states) { auto& state = states.template get<Spectrum_State_Tag>(); state.selected_marker = state.custom_markers.empty() ? -1 : (state.selected_marker + 1) % static_cast<Spectrum_Marker_Index>(state.custom_markers.size()); });
|
object->template update_prop<&Prop::selected_marker>([](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Spectrum::Base_Tag>(); state.selected_marker = state.custom_markers.empty() ? -1 : (state.selected_marker + 1) % static_cast<Spectrum_Marker_Index>(state.custom_markers.size()); });
|
||||||
object->template mark_dirty<Prepare_Data_Tag>();
|
object->template mark_dirty<Prepare_Data_Tag>();
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Spectrum::Private::select_previous_marker(Object* object) {
|
void Spectrum::Private::select_previous_marker(Object* object) {
|
||||||
object->template update_state<&State::selected_marker>([](State_Access<typename Object::State> states) { auto& state = states.template get<Spectrum_State_Tag>(); state.selected_marker = state.custom_markers.empty() ? -1 : (state.selected_marker <= 0 ? static_cast<Spectrum_Marker_Index>(state.custom_markers.size()) : state.selected_marker) - 1; });
|
object->template update_prop<&Prop::selected_marker>([](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Spectrum::Base_Tag>(); state.selected_marker = state.custom_markers.empty() ? -1 : (state.selected_marker <= 0 ? static_cast<Spectrum_Marker_Index>(state.custom_markers.size()) : state.selected_marker) - 1; });
|
||||||
object->template mark_dirty<Prepare_Data_Tag>();
|
object->template mark_dirty<Prepare_Data_Tag>();
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
Spectrum::Set_Marker_Frequency_Result Spectrum::Private::set_marker_frequency(Object* object, Spectrum_Marker_Index index, Spectrum_Frequency frequency) {
|
Spectrum::Set_Marker_Frequency_Result Spectrum::Private::set_marker_frequency(Object* object, Spectrum_Marker_Index index, Spectrum_Frequency frequency) {
|
||||||
if (index < 0 || static_cast<std::size_t>(index) >= object->template read_state<Spectrum_State_Tag>().custom_markers.size()) return Set_Marker_Frequency_Result::index_out_of_range;
|
if (index < 0 || static_cast<std::size_t>(index) >= object->template read_prop<Spectrum::Base_Tag>().custom_markers.size()) return Set_Marker_Frequency_Result::index_out_of_range;
|
||||||
object->template update_state<&State::custom_markers>([index, frequency](State_Access<typename Object::State> states) { states.template get<Spectrum_State_Tag>().custom_markers[static_cast<std::size_t>(index)] = frequency; });
|
object->template update_prop<&Prop::custom_markers>([index, frequency](Prop_Access<typename Object::Prop> props) { props.template get<Spectrum::Base_Tag>().custom_markers[static_cast<std::size_t>(index)] = frequency; });
|
||||||
object->template mark_dirty<Prepare_Data_Tag>();
|
object->template mark_dirty<Prepare_Data_Tag>();
|
||||||
return Set_Marker_Frequency_Result::updated;
|
return Set_Marker_Frequency_Result::updated;
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
Spectrum::Set_Current_Marker_Frequency_Result Spectrum::Private::set_current_marker_frequency(Object* object, Spectrum_Frequency frequency) {
|
Spectrum::Set_Current_Marker_Frequency_Result Spectrum::Private::set_current_marker_frequency(Object* object, Spectrum_Frequency frequency) {
|
||||||
const Spectrum_Marker_Index index = object->template read_state<Spectrum_State_Tag>().selected_marker;
|
const Spectrum_Marker_Index index = object->template read_prop<Spectrum::Base_Tag>().selected_marker;
|
||||||
if (index < 0) return Set_Current_Marker_Frequency_Result::no_selection;
|
if (index < 0) return Set_Current_Marker_Frequency_Result::no_selection;
|
||||||
return set_marker_frequency(object, index, frequency) == Set_Marker_Frequency_Result::updated ? Set_Current_Marker_Frequency_Result::updated : Set_Current_Marker_Frequency_Result::no_selection;
|
return set_marker_frequency(object, index, frequency) == Set_Marker_Frequency_Result::updated ? Set_Current_Marker_Frequency_Result::updated : Set_Current_Marker_Frequency_Result::no_selection;
|
||||||
}
|
}
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type>
|
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||||
void Spectrum::Private::after_state_set(Object* object, Member Owner::*, State_Access<State_Type>) {
|
void Spectrum::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) {
|
||||||
if constexpr (std::same_as<Owner, State>) object->template mark_dirty<Prepare_Data_Tag>();
|
if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>();
|
||||||
|
}
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type>
|
||||||
|
void Spectrum::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) {
|
||||||
|
const auto& private_data = static_cast<const typename Object::Private&>(*this);
|
||||||
|
auto& state = pending_states.template get<Spectrum::Base_Tag>();
|
||||||
|
state.sample_count = private_data.buffer_storage.template get<Spectrum_Frame_Tag>().pending->samples.size();
|
||||||
|
state.rendered_point_count = 0;
|
||||||
|
for (const auto& partition : prepared.partitions) state.rendered_point_count += partition.current.points.size();
|
||||||
|
state.selectable_marker_count = static_cast<const Prop&>(*current_prop).custom_markers.size();
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
const Spectrum::Private::Dispatch& Spectrum::Private::dispatch_for() {
|
const Spectrum::Private::Dispatch& Spectrum::Private::dispatch_for() {
|
||||||
@@ -321,17 +331,17 @@ const Spectrum::Private::Dispatch& Spectrum::Private::dispatch_for() {
|
|||||||
[](Root* root, std::span<const Spectrum_Power> values) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).update_samples(object, values); },
|
[](Root* root, std::span<const Spectrum_Power> values) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).update_samples(object, values); },
|
||||||
[](const Root* root) { return static_cast<const Object*>(root)->template current_buffer<Spectrum_Frame_Tag>().samples.size(); },
|
[](const Root* root) { return static_cast<const Object*>(root)->template current_buffer<Spectrum_Frame_Tag>().samples.size(); },
|
||||||
[](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); std::size_t count{}; for (const auto& partition : data.prepared.partitions) count += partition.current.points.size(); return count; },
|
[](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); std::size_t count{}; for (const auto& partition : data.prepared.partitions) count += partition.current.points.size(); return count; },
|
||||||
[](const Root* root, Spectrum_Frequency frequency) { const auto* object = static_cast<const Object*>(root); const auto& data = static_cast<const typename Object::Private&>(*object->d); return Private::power_at(static_cast<const State&>(*data.state.current), object->template current_buffer<Spectrum_Frame_Tag>(), frequency); },
|
[](const Root* root, Spectrum_Frequency frequency) { const auto* object = static_cast<const Object*>(root); const auto& data = static_cast<const typename Object::Private&>(*object->d); return Private::power_at(static_cast<const Prop&>(*data.current), object->template current_buffer<Spectrum_Frame_Tag>(), frequency); },
|
||||||
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).add_marker(object, frequency); },
|
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).add_marker(object, frequency); },
|
||||||
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).remove_marker(object, frequency); },
|
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).remove_marker(object, frequency); },
|
||||||
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).remove_selected_marker(object); },
|
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).remove_selected_marker(object); },
|
||||||
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).clear_markers(object); },
|
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).clear_markers(object); },
|
||||||
[](const Root* root) { return static_cast<const Object*>(root)->template read_state<Spectrum_State_Tag>().custom_markers.size(); },
|
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Spectrum::Base_Tag>().custom_markers.size(); },
|
||||||
[](const Root* root) { return static_cast<const Object*>(root)->template read_state<Spectrum_State_Tag>().selected_marker; },
|
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Spectrum::Base_Tag>().selected_marker; },
|
||||||
[](Root* root, Spectrum_Marker_Index index) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).set_selected_marker(object, index); },
|
[](Root* root, Spectrum_Marker_Index index) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).set_selected_marker(object, index); },
|
||||||
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).select_next_marker(object); },
|
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).select_next_marker(object); },
|
||||||
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).select_previous_marker(object); },
|
[](Root* root) { auto* object = static_cast<Object*>(root); static_cast<typename Object::Private&>(*object->d).select_previous_marker(object); },
|
||||||
[](const Root* root, Spectrum_Marker_Index index) -> std::expected<Spectrum_Frequency, Marker_Frequency_Result> { const auto& markers = static_cast<const Object*>(root)->template read_state<Spectrum_State_Tag>().custom_markers; if (index < 0 || static_cast<std::size_t>(index) >= markers.size()) return std::unexpected(Marker_Frequency_Result::index_out_of_range); return markers[static_cast<std::size_t>(index)]; },
|
[](const Root* root, Spectrum_Marker_Index index) -> std::expected<Spectrum_Frequency, Marker_Frequency_Result> { const auto& markers = static_cast<const Object*>(root)->template read_prop<Spectrum::Base_Tag>().custom_markers; if (index < 0 || static_cast<std::size_t>(index) >= markers.size()) return std::unexpected(Marker_Frequency_Result::index_out_of_range); return markers[static_cast<std::size_t>(index)]; },
|
||||||
[](Root* root, Spectrum_Marker_Index index, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); return static_cast<typename Object::Private&>(*object->d).set_marker_frequency(object, index, frequency); },
|
[](Root* root, Spectrum_Marker_Index index, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); return static_cast<typename Object::Private&>(*object->d).set_marker_frequency(object, index, frequency); },
|
||||||
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); return static_cast<typename Object::Private&>(*object->d).set_current_marker_frequency(object, frequency); }
|
[](Root* root, Spectrum_Frequency frequency) { auto* object = static_cast<Object*>(root); return static_cast<typename Object::Private&>(*object->d).set_current_marker_frequency(object, frequency); }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "Sweep_Spectrum.hpp"
|
#include "Sweep_Spectrum.hpp"
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
|
bool Sweep_Spectrum::Prop::operator==(const Prop&) const = default;
|
||||||
bool Sweep_Spectrum::State::operator==(const State&) const = default;
|
bool Sweep_Spectrum::State::operator==(const State&) const = default;
|
||||||
void Sweep_Spectrum::append_block(std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append(this, values); }
|
void Sweep_Spectrum::append_block(std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append(this, values); }
|
||||||
void Sweep_Spectrum::append_block(std::pmr::vector<Plot_Value>&& values) { append_block(std::span<const Plot_Value>(values.data(), values.size())); }
|
void Sweep_Spectrum::append_block(std::pmr::vector<Plot_Value>&& values) { append_block(std::span<const Plot_Value>(values.data(), values.size())); }
|
||||||
|
|||||||
@@ -9,13 +9,11 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
struct Sweep_Spectrum_State_Tag {};
|
struct Sweep_Spectrum : Def<Sweep_Spectrum, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||||
struct Sweep_Spectrum : Def<Sweep_Spectrum, Renderable, State_Type<Sweep_Spectrum_State_Tag>, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
|
||||||
using Scene_Object = Impl<Render_Scene_2D>;
|
using Scene_Object = Impl<Render_Scene_2D>;
|
||||||
using Frequency_Object = Impl<Frequency_Axis>;
|
using Frequency_Object = Impl<Frequency_Axis>;
|
||||||
using Power_Object = Impl<Numeric_Axis>;
|
using Power_Object = Impl<Numeric_Axis>;
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {
|
||||||
struct State : Prev_State<Sweep_Spectrum_State_Tag> {
|
|
||||||
Axis_Range frequency_range{}; /* 全部扫描块覆盖的频率范围。 */
|
Axis_Range frequency_range{}; /* 全部扫描块覆盖的频率范围。 */
|
||||||
std::size_t bins_per_block{}; /* 每个扫描块期望的功率点数;零值接受首块尺寸。 */
|
std::size_t bins_per_block{}; /* 每个扫描块期望的功率点数;零值接受首块尺寸。 */
|
||||||
std::size_t block_count{1}; /* 最多保留的扫描块数;零值按 1 处理。 */
|
std::size_t block_count{1}; /* 最多保留的扫描块数;零值按 1 处理。 */
|
||||||
@@ -26,6 +24,12 @@ struct Sweep_Spectrum : Def<Sweep_Spectrum, Renderable, State_Type<Sweep_Spectru
|
|||||||
bool visible_range_only{true}; /* 是否裁掉频率轴可见范围外的线段。 */
|
bool visible_range_only{true}; /* 是否裁掉频率轴可见范围外的线段。 */
|
||||||
Line_Interpolation_Mode interpolation_mode{Line_Interpolation_Mode::linear_value}; /* 相邻功率点插值方式。 */
|
Line_Interpolation_Mode interpolation_mode{Line_Interpolation_Mode::linear_value}; /* 相邻功率点插值方式。 */
|
||||||
std::vector<std::vector<Plot_Value>> blocks{}; /* 已提交扫描块的唯一权威集合。 */
|
std::vector<std::vector<Plot_Value>> blocks{}; /* 已提交扫描块的唯一权威集合。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
|
std::size_t stored_block_count{}; /* 当前发布的扫描块数。 */
|
||||||
|
std::size_t stored_point_count{}; /* 当前发布的扫描点总数。 */
|
||||||
|
std::size_t rendered_point_count{}; /* 最近一次 Prepare 生成的曲线点数。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|||||||
@@ -29,52 +29,55 @@ struct Sweep_Spectrum::Private : Prev_Private {
|
|||||||
void bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value);
|
void bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value);
|
||||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||||
/* CRTP 覆盖:按扫描点规模构建分块 Prepare 子图。 */
|
/* CRTP 覆盖:按扫描点规模构建分块 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:构建消费曲线分块的 Paint 子图。 */
|
/* CRTP 覆盖:构建消费曲线分块的 Paint 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state);
|
||||||
template <Attached Object> void prepare_frame(Object* object);
|
template <Attached Object> void prepare_frame(Object* object);
|
||||||
template <Attached Object> void prepare_partition(Object* object, Plot_Partition_Count index);
|
template <Attached Object> void prepare_partition(Object* object, Plot_Partition_Count index);
|
||||||
template <Attached Object> void paint_frame(Object* object);
|
template <Attached Object> void paint_frame(Object* object);
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> states);
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> states);
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||||
};
|
};
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
Sweep_Spectrum::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), power_axis(power_axis_value) {}
|
Sweep_Spectrum::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Power_Object* power_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), power_axis(power_axis_value) {}
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Sweep_Spectrum::Builder<Object>::build() {
|
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Sweep_Spectrum::Builder<Object>::build() {
|
||||||
auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto sweep = std::move(result).value(); static_cast<typename Object::Private&>(*sweep->d).bind_sources(scene, frequency_axis, power_axis);
|
auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto sweep = std::move(result).value(); static_cast<typename Object::Private&>(*sweep->d).bind_sources(scene, frequency_axis, power_axis);
|
||||||
auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(frequency_axis); prepare.add(power_axis); prepare.add(sweep.get()); prepare.template add_state_dependency<Render_Scene_2D_State_Tag>(sweep.get(), scene); prepare.template add_state_dependency<Abs_Axis_State_Tag>(sweep.get(), frequency_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(sweep.get(), frequency_axis); prepare.template add_state_dependency<Abs_Axis_State_Tag>(sweep.get(), power_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(sweep.get(), power_axis); paint.add(frequency_axis); paint.add(power_axis); paint.add(sweep.get()); });
|
auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(frequency_axis); prepare.add(power_axis); prepare.add(sweep.get()); prepare.template add_prop_dependency<Render_Scene_2D::Base_Tag>(sweep.get(), scene); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(sweep.get(), frequency_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(sweep.get(), frequency_axis); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(sweep.get(), power_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(sweep.get(), power_axis); paint.add(frequency_axis); paint.add(power_axis); paint.add(sweep.get()); });
|
||||||
if (!graph_result) return std::unexpected(graph_result.error()); return std::move(sweep);
|
if (!graph_result) return std::unexpected(graph_result.error()); return std::move(sweep);
|
||||||
}
|
}
|
||||||
template <typename Values> void Sweep_Spectrum::append_block(const Values& values) { append_block(std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
template <typename Values> void Sweep_Spectrum::append_block(const Values& values) { append_block(std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
bool Sweep_Spectrum::Private::should_rebuild_prepare_graph(Object*, const State& state) { std::size_t points{}; for (const auto& block : state.blocks) points += block.size(); return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, points); }
|
bool Sweep_Spectrum::Private::should_rebuild_prepare_graph(Object*, const Prop& state) { std::size_t points{}; for (const auto& block : state.blocks) points += block.size(); return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, points); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
tf::Taskflow Sweep_Spectrum::Private::build_prepare_graph(Object* object, const State& state) { std::size_t points{}; for (const auto& block : state.blocks) points += block.size(); graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, points); tf::Taskflow graph; auto begin = graph.emplace([this, object] { prepare_frame(object); }).name("sweep_spectrum.prepare.frame"); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.emplace([this, object, index] { prepare_partition(object, index); }).name("sweep_spectrum.prepare.partition"); begin.precede(task); } return graph; }
|
tf::Taskflow Sweep_Spectrum::Private::build_prepare_graph(Object* object, const Prop& state) { std::size_t points{}; for (const auto& block : state.blocks) points += block.size(); graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, points); tf::Taskflow graph; auto begin = graph.emplace([this, object] { prepare_frame(object); }).name("sweep_spectrum.prepare.frame"); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.emplace([this, object, index] { prepare_partition(object, index); }).name("sweep_spectrum.prepare.partition"); begin.precede(task); } return graph; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
tf::Taskflow Sweep_Spectrum::Private::build_paint_graph(Object* object, const State&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("sweep_spectrum.paint.frame"); return graph; }
|
tf::Taskflow Sweep_Spectrum::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("sweep_spectrum.paint.frame"); return graph; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Sweep_Spectrum::Private::prepare_frame(Object* object) {
|
void Sweep_Spectrum::Private::prepare_frame(Object* object) {
|
||||||
const auto& state = object->template read_state<Sweep_Spectrum_State_Tag>(); const auto& frequency_layout = frequency_axis->template read_state<Abs_Axis_State_Tag>(); const auto& power_layout = power_axis->template read_state<Abs_Axis_State_Tag>(); const auto& power_state = power_axis->template read_state<Numeric_Axis_State_Tag>(); prepared = {}; prepared.partitions.resize(graph_partition_count); prepared.canvas = scene->template read_state<Render_Scene_2D_State_Tag>().viewport;
|
const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>(); prepared = {}; prepared.partitions.resize(graph_partition_count); prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport;
|
||||||
if (prepared.canvas.empty() || frequency_layout.orientation == power_layout.orientation) return; for (const auto& block : state.blocks) prepared.values.insert(prepared.values.end(), block.begin(), block.end()); if (prepared.values.empty()) return;
|
if (prepared.canvas.empty() || frequency_layout.orientation == power_layout.orientation) return; for (const auto& block : state.blocks) prepared.values.insert(prepared.values.end(), block.begin(), block.end()); if (prepared.values.empty()) return;
|
||||||
prepared.marker_first = detail::map_plot_point(frequency_axis, state.frequency_range.target, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation); prepared.marker_second = detail::map_plot_point(frequency_axis, state.frequency_range.target, power_axis, power_state.coordinate_range.target, frequency_layout.orientation); prepared.valid = true;
|
prepared.marker_first = detail::map_plot_point(frequency_axis, state.frequency_range.target, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation); prepared.marker_second = detail::map_plot_point(frequency_axis, state.frequency_range.target, power_axis, power_state.coordinate_range.target, frequency_layout.orientation); prepared.valid = true;
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Sweep_Spectrum::Private::prepare_partition(Object* object, Plot_Partition_Count index) {
|
void Sweep_Spectrum::Private::prepare_partition(Object* object, Plot_Partition_Count index) {
|
||||||
if (!prepared.valid) return; const auto& state = object->template read_state<Sweep_Spectrum_State_Tag>(); const auto& frequency_layout = frequency_axis->template read_state<Abs_Axis_State_Tag>(); const auto& power_layout = power_axis->template read_state<Abs_Axis_State_Tag>(); const auto& frequency_state = frequency_axis->template read_state<Numeric_Axis_State_Tag>(); const auto& power_state = power_axis->template read_state<Numeric_Axis_State_Tag>(); const auto range = detail::curve_partition_range(prepared.values.size(), index, prepared.partitions.size(), state.frequency_range);
|
if (!prepared.valid) return; const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& frequency_state = frequency_axis->template read_prop<Numeric_Axis::Base_Tag>(); const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>(); const auto range = detail::curve_partition_range(prepared.values.size(), index, prepared.partitions.size(), state.frequency_range);
|
||||||
prepared.partitions[index] = detail::prepare_curve(std::span<const Plot_Value>(prepared.values).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
prepared.partitions[index] = detail::prepare_curve(std::span<const Plot_Value>(prepared.values).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
||||||
}
|
}
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Sweep_Spectrum::Private::paint_frame(Object* object) { const auto& state = object->template read_state<Sweep_Spectrum_State_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); for (const auto& curve : prepared.partitions) detail::paint_curve(painter, curve, state.pen); painter.line(prepared.marker_first, prepared.marker_second, state.current_frequency_pen); }
|
void Sweep_Spectrum::Private::paint_frame(Object* object) { const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); for (const auto& curve : prepared.partitions) detail::paint_curve(painter, curve, state.pen); painter.line(prepared.marker_first, prepared.marker_second, state.current_frequency_pen); }
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type>
|
template <typename Object, typename Owner, typename Member, typename Prop_Type>
|
||||||
void Sweep_Spectrum::Private::after_state_set(Object* object, Member Owner::*, State_Access<State_Type>) { if constexpr (std::same_as<Owner, State>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
void Sweep_Spectrum::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type>
|
||||||
|
void Sweep_Spectrum::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { auto& state = pending_states.template get<Sweep_Spectrum::Base_Tag>(); const auto& prop = static_cast<const Prop&>(*current_prop); state.stored_block_count = prop.blocks.size(); state.stored_point_count = 0; for (const auto& block : prop.blocks) state.stored_point_count += block.size(); state.rendered_point_count = 0; for (const auto& partition : prepared.partitions) state.rendered_point_count += partition.points.size(); }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
const Sweep_Spectrum::Private::Dispatch& Sweep_Spectrum::Private::dispatch_for() {
|
const Sweep_Spectrum::Private::Dispatch& Sweep_Spectrum::Private::dispatch_for() {
|
||||||
static const Dispatch value{
|
static const Dispatch value{
|
||||||
[](Root* root, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); object->template update_state<&State::blocks>([values](State_Access<typename Object::State> states) { auto& state = states.template get<Sweep_Spectrum_State_Tag>(); state.blocks.emplace_back(values.begin(), values.end()); const std::size_t limit = std::max<std::size_t>(1, state.block_count); while (state.blocks.size() > limit) state.blocks.erase(state.blocks.begin()); }); },
|
[](Root* root, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); object->template update_prop<&Prop::blocks>([values](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Sweep_Spectrum::Base_Tag>(); state.blocks.emplace_back(values.begin(), values.end()); const std::size_t limit = std::max<std::size_t>(1, state.block_count); while (state.blocks.size() > limit) state.blocks.erase(state.blocks.begin()); }); },
|
||||||
[](const Root* root) { return static_cast<const Object*>(root)->template read_state<Sweep_Spectrum_State_Tag>().blocks.size(); },
|
[](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Sweep_Spectrum::Base_Tag>().blocks.size(); },
|
||||||
[](const Root* root) { const auto& blocks = static_cast<const Object*>(root)->template read_state<Sweep_Spectrum_State_Tag>().blocks; std::size_t count{}; for (const auto& block : blocks) count += block.size(); return count; },
|
[](const Root* root) { const auto& blocks = static_cast<const Object*>(root)->template read_prop<Sweep_Spectrum::Base_Tag>().blocks; std::size_t count{}; for (const auto& block : blocks) count += block.size(); return count; },
|
||||||
[](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); std::size_t count{}; for (const auto& curve : data.prepared.partitions) count += curve.points.size(); return count; }
|
[](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); std::size_t count{}; for (const auto& curve : data.prepared.partitions) count += curve.points.size(); return count; }
|
||||||
}; return value;
|
}; return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
#include "Waterfall.hpp"
|
#include "Waterfall.hpp"
|
||||||
namespace aethera::render_2d { bool Waterfall_Row::operator==(const Waterfall_Row&) const = default; bool Waterfall::State::operator==(const State&) const = default; void Waterfall::append_row(Plot_Time_Tick tick, std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append(this, tick, values); } void Waterfall::append_row(Plot_Time_Tick tick, std::pmr::vector<Plot_Value>&& values) { append_row(tick, std::span<const Plot_Value>(values.data(), values.size())); } void Waterfall::append_row(Time_Of_Day time, std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append_time(this, time, values); } void Waterfall::append_row(Time_Of_Day time, std::pmr::vector<Plot_Value>&& values) { append_row(time, std::span<const Plot_Value>(values.data(), values.size())); } std::size_t Waterfall::row_count() const { return static_cast<const Private&>(*d).dispatch->row_count(this); } std::size_t Waterfall::stored_point_count() const { return static_cast<const Private&>(*d).dispatch->point_count(this); } std::size_t Waterfall::rendered_cell_count() const { return static_cast<const Private&>(*d).dispatch->rendered_count(this); } }
|
namespace aethera::render_2d { bool Waterfall_Row::operator==(const Waterfall_Row&) const = default; bool Waterfall::Prop::operator==(const Prop&) const = default;
|
||||||
|
bool Waterfall::State::operator==(const State&) const = default; void Waterfall::append_row(Plot_Time_Tick tick, std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append(this, tick, values); } void Waterfall::append_row(Plot_Time_Tick tick, std::pmr::vector<Plot_Value>&& values) { append_row(tick, std::span<const Plot_Value>(values.data(), values.size())); } void Waterfall::append_row(Time_Of_Day time, std::span<const Plot_Value> values) { static_cast<Private&>(*d).dispatch->append_time(this, time, values); } void Waterfall::append_row(Time_Of_Day time, std::pmr::vector<Plot_Value>&& values) { append_row(time, std::span<const Plot_Value>(values.data(), values.size())); } std::size_t Waterfall::row_count() const { return static_cast<const Private&>(*d).dispatch->row_count(this); } std::size_t Waterfall::stored_point_count() const { return static_cast<const Private&>(*d).dispatch->point_count(this); } std::size_t Waterfall::rendered_cell_count() const { return static_cast<const Private&>(*d).dispatch->rendered_count(this); } }
|
||||||
|
|||||||
@@ -10,12 +10,10 @@
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
struct Waterfall_State_Tag {};
|
|
||||||
struct Waterfall_Row { Plot_Time_Tick tick{}; std::vector<Plot_Value> values{}; bool operator==(const Waterfall_Row&) const; };
|
struct Waterfall_Row { Plot_Time_Tick tick{}; std::vector<Plot_Value> values{}; bool operator==(const Waterfall_Row&) const; };
|
||||||
struct Waterfall : Def<Waterfall, Renderable, State_Type<Waterfall_State_Tag>, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
struct Waterfall : Def<Waterfall, Renderable, Tagged_Buffer<Color_Cache, Blend2D_Cache>> {
|
||||||
using Scene_Object = Impl<Render_Scene_2D>; using Frequency_Object = Impl<Frequency_Axis>; using Time_Object = Impl<Time_Axis>;
|
using Scene_Object = Impl<Render_Scene_2D>; using Frequency_Object = Impl<Frequency_Axis>; using Time_Object = Impl<Time_Axis>;
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop, Hover_Tooltip_Properties {
|
||||||
struct State : Prev_State<Waterfall_State_Tag>, Hover_Tooltip_Properties {
|
|
||||||
Axis_Range frequency_range{0.0, 10.0}; /* 每行频谱覆盖的频率范围。 */
|
Axis_Range frequency_range{0.0, 10.0}; /* 每行频谱覆盖的频率范围。 */
|
||||||
Axis_Range power_range{0.0, 10.0}; /* 颜色映射使用的功率范围。 */
|
Axis_Range power_range{0.0, 10.0}; /* 颜色映射使用的功率范围。 */
|
||||||
std::size_t frequency_bin_count{}; /* 目标频率列数;零值使用最新行尺寸。 */
|
std::size_t frequency_bin_count{}; /* 目标频率列数;零值使用最新行尺寸。 */
|
||||||
@@ -25,6 +23,12 @@ struct Waterfall : Def<Waterfall, Renderable, State_Type<Waterfall_State_Tag>, T
|
|||||||
Image_Interpolation_Mode interpolation_mode{Image_Interpolation_Mode::nearest}; /* 栅格放大时的图像插值方式。 */
|
Image_Interpolation_Mode interpolation_mode{Image_Interpolation_Mode::nearest}; /* 栅格放大时的图像插值方式。 */
|
||||||
Color_Map color_map{}; /* 功率到颜色的映射。 */
|
Color_Map color_map{}; /* 功率到颜色的映射。 */
|
||||||
std::vector<Waterfall_Row> rows{}; /* 从旧到新的瀑布行唯一权威集合。 */
|
std::vector<Waterfall_Row> rows{}; /* 从旧到新的瀑布行唯一权威集合。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
|
std::size_t row_count{}; /* 当前发布的瀑布行数。 */
|
||||||
|
std::size_t stored_point_count{}; /* 当前发布的功率点总数。 */
|
||||||
|
std::size_t rendered_cell_count{}; /* 最近一次 Prepare 生成的色块数。 */
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
struct Private;
|
struct Private;
|
||||||
|
|||||||
@@ -36,34 +36,36 @@ struct Waterfall::Private : Prev_Private {
|
|||||||
void bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Time_Object* time_axis_value);
|
void bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Time_Object* time_axis_value);
|
||||||
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
template <Attached Object> [[nodiscard]] static const Dispatch& dispatch_for();
|
||||||
/* CRTP 覆盖:按当前色块工作量构建分块 Prepare 子图。 */
|
/* CRTP 覆盖:按当前色块工作量构建分块 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_prepare_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:构建消费色块矩阵和提示信息的 Paint 子图。 */
|
/* CRTP 覆盖:构建消费色块矩阵和提示信息的 Paint 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] tf::Taskflow build_paint_graph(Object* object, const Prop& state);
|
||||||
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
/* CRTP 覆盖:分块数量改变时请求重建 Prepare 子图。 */
|
||||||
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const State& state);
|
template <Attached Object> [[nodiscard]] bool should_rebuild_prepare_graph(Object* object, const Prop& state);
|
||||||
template <Attached Object> void prepare_frame(Object* object); template <Attached Object> void prepare_partition(Object* object, Plot_Partition_Count index); template <Attached Object> void paint_frame(Object* object);
|
template <Attached Object> void prepare_frame(Object* object); template <Attached Object> void prepare_partition(Object* object, Plot_Partition_Count index); template <Attached Object> void paint_frame(Object* object);
|
||||||
/* CRTP 覆盖:更新 hover 位置并请求重绘。 */
|
/* CRTP 覆盖:更新 hover 位置并请求重绘。 */
|
||||||
template <Attached Object> void handle_event(Object* object, const Event& event);
|
template <Attached Object> void handle_event(Object* object, const Event& event);
|
||||||
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
/* CRTP 覆盖:本类状态写入后标记 Prepare 数据失效。 */
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void after_state_set(Object* object, Member Owner::* member, State_Access<State_Type> states);
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void after_prop_set(Object* object, Member Owner::* member, Prop_Access<Prop_Type> states);
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void before_advance(Object* object, Prop_Type* pending_prop, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type> current_states);
|
||||||
};
|
};
|
||||||
template <typename Object> Waterfall::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Time_Object* time_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), time_axis(time_axis_value) {}
|
template <typename Object> Waterfall::Builder<Object>::Builder(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Time_Object* time_axis_value) : Base(), scene(scene_value), frequency_axis(frequency_axis_value), time_axis(time_axis_value) {}
|
||||||
template <typename Object>
|
template <typename Object>
|
||||||
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Waterfall::Builder<Object>::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); static_cast<typename Object::Private&>(*plot->d).bind_sources(scene, frequency_axis, time_axis); auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(frequency_axis); prepare.add(time_axis); prepare.add(plot.get()); prepare.template add_state_dependency<Render_Scene_2D_State_Tag>(plot.get(), scene); prepare.template add_state_dependency<Abs_Axis_State_Tag>(plot.get(), frequency_axis); prepare.template add_state_dependency<Numeric_Axis_State_Tag>(plot.get(), frequency_axis); prepare.template add_state_dependency<Abs_Axis_State_Tag>(plot.get(), time_axis); prepare.template add_state_dependency<Time_Axis_State_Tag>(plot.get(), time_axis); paint.add(frequency_axis); paint.add(time_axis); paint.add(plot.get()); }); if (!graph_result) return std::unexpected(graph_result.error()); return std::move(plot); }
|
std::expected<std::unique_ptr<Object>, Dependency_Graph_Error> Waterfall::Builder<Object>::build() { auto result = Base::build(); if (!result) return std::unexpected(result.error()); auto plot = std::move(result).value(); static_cast<typename Object::Private&>(*plot->d).bind_sources(scene, frequency_axis, time_axis); auto graph_result = scene->template edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) { prepare.add(frequency_axis); prepare.add(time_axis); prepare.add(plot.get()); prepare.template add_prop_dependency<Render_Scene_2D::Base_Tag>(plot.get(), scene); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(plot.get(), frequency_axis); prepare.template add_prop_dependency<Numeric_Axis::Base_Tag>(plot.get(), frequency_axis); prepare.template add_prop_dependency<Abs_Axis::Base_Tag>(plot.get(), time_axis); prepare.template add_prop_dependency<Time_Axis::Base_Tag>(plot.get(), time_axis); paint.add(frequency_axis); paint.add(time_axis); paint.add(plot.get()); }); if (!graph_result) return std::unexpected(graph_result.error()); return std::move(plot); }
|
||||||
template <typename Values> void Waterfall::append_row(Plot_Time_Tick tick, const Values& values) { append_row(tick, std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
template <typename Values> void Waterfall::append_row(Plot_Time_Tick tick, const Values& values) { append_row(tick, std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
||||||
template <typename Values> void Waterfall::append_row(Time_Of_Day time, const Values& values) { append_row(time, std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
template <typename Values> void Waterfall::append_row(Time_Of_Day time, const Values& values) { append_row(time, std::span<const Plot_Value>(std::data(values), std::size(values))); }
|
||||||
template <Attached Object> bool Waterfall::Private::should_rebuild_prepare_graph(Object*, const State& state) { const std::size_t cells = state.rows.size() * (state.frequency_bin_count ? state.frequency_bin_count : state.rows.empty() ? 1 : state.rows.back().values.size()); return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, cells); }
|
template <Attached Object> bool Waterfall::Private::should_rebuild_prepare_graph(Object*, const Prop& state) { const std::size_t cells = state.rows.size() * (state.frequency_bin_count ? state.frequency_bin_count : state.rows.empty() ? 1 : state.rows.back().values.size()); return graph_partition_count != detail::curve_partition_count(state.partition_mode, state.partition_count, cells); }
|
||||||
template <Attached Object> tf::Taskflow Waterfall::Private::build_prepare_graph(Object* object, const State& state) { const std::size_t cells = state.rows.size() * (state.frequency_bin_count ? state.frequency_bin_count : state.rows.empty() ? 1 : state.rows.back().values.size()); graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, cells); tf::Taskflow graph; auto begin = graph.emplace([this, object] { prepare_frame(object); }).name("waterfall.prepare.frame"); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.emplace([this, object, index] { prepare_partition(object, index); }).name("waterfall.prepare.partition"); begin.precede(task); } return graph; }
|
template <Attached Object> tf::Taskflow Waterfall::Private::build_prepare_graph(Object* object, const Prop& state) { const std::size_t cells = state.rows.size() * (state.frequency_bin_count ? state.frequency_bin_count : state.rows.empty() ? 1 : state.rows.back().values.size()); graph_partition_count = detail::curve_partition_count(state.partition_mode, state.partition_count, cells); tf::Taskflow graph; auto begin = graph.emplace([this, object] { prepare_frame(object); }).name("waterfall.prepare.frame"); for (Plot_Partition_Count index = 0; index < graph_partition_count; ++index) { auto task = graph.emplace([this, object, index] { prepare_partition(object, index); }).name("waterfall.prepare.partition"); begin.precede(task); } return graph; }
|
||||||
template <Attached Object> tf::Taskflow Waterfall::Private::build_paint_graph(Object* object, const State&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("waterfall.paint.frame"); return graph; }
|
template <Attached Object> tf::Taskflow Waterfall::Private::build_paint_graph(Object* object, const Prop&) { tf::Taskflow graph; graph.emplace([this, object] { paint_frame(object); }).name("waterfall.paint.frame"); return graph; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Waterfall::Private::prepare_frame(Object* object) { const auto& state = object->template read_state<Waterfall_State_Tag>(); const auto& frequency_layout = frequency_axis->template read_state<Abs_Axis_State_Tag>(); const auto& time_layout = time_axis->template read_state<Abs_Axis_State_Tag>(); prepared = {}; prepared.canvas = scene->template read_state<Render_Scene_2D_State_Tag>().viewport; if (state.rows.empty()) return; const auto shortest = std::min_element(state.rows.begin(), state.rows.end(), [](const Waterfall_Row& left, const Waterfall_Row& right) { return left.values.size() < right.values.size(); }); const std::size_t available = shortest->values.size(); const int source_columns = static_cast<int>(state.frequency_bin_count ? std::min(state.frequency_bin_count, available) : available); const auto selection = detail::raster_axis_selection(state.frequency_range, frequency_axis->coordinate_range(), source_columns, state.visible_range_only); if (!selection) return; const int rows = static_cast<int>(state.rows.size()); const Axis_Range time_range = rows == 1 ? time_axis->coordinate_range() : Axis_Range{static_cast<Axis_Coordinate>(state.rows.front().tick), static_cast<Axis_Coordinate>(state.rows.back().tick)}; prepared.layout = detail::raster_layout(frequency_axis, selection->range, selection->count(), time_axis, time_range, rows, frequency_layout.orientation, time_layout.orientation); if (prepared.canvas.empty() || !prepared.layout.valid()) return; prepared.source_first = selection->first; prepared.pixels.assign(static_cast<std::size_t>(prepared.layout.width) * prepared.layout.height, 0); if (state.tooltip_enabled && tooltip.active && prepared.layout.target.contains(tooltip.position)) { std::ostringstream text; text << std::fixed << std::setprecision(2) << frequency_axis->point_to_coordinate(tooltip.position) << " Hz"; prepared.tooltip_text = text.str(); prepared.tooltip_box = {tooltip.position.x + 8.0, tooltip.position.y + 8.0, 110.0, 24.0}; } prepared.valid = true; }
|
void Waterfall::Private::prepare_frame(Object* object) { const auto& state = object->template read_prop<Waterfall::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& time_layout = time_axis->template read_prop<Abs_Axis::Base_Tag>(); prepared = {}; prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport; if (state.rows.empty()) return; const auto shortest = std::min_element(state.rows.begin(), state.rows.end(), [](const Waterfall_Row& left, const Waterfall_Row& right) { return left.values.size() < right.values.size(); }); const std::size_t available = shortest->values.size(); const int source_columns = static_cast<int>(state.frequency_bin_count ? std::min(state.frequency_bin_count, available) : available); const auto selection = detail::raster_axis_selection(state.frequency_range, frequency_axis->coordinate_range(), source_columns, state.visible_range_only); if (!selection) return; const int rows = static_cast<int>(state.rows.size()); const Axis_Range time_range = rows == 1 ? time_axis->coordinate_range() : Axis_Range{static_cast<Axis_Coordinate>(state.rows.front().tick), static_cast<Axis_Coordinate>(state.rows.back().tick)}; prepared.layout = detail::raster_layout(frequency_axis, selection->range, selection->count(), time_axis, time_range, rows, frequency_layout.orientation, time_layout.orientation); if (prepared.canvas.empty() || !prepared.layout.valid()) return; prepared.source_first = selection->first; prepared.pixels.assign(static_cast<std::size_t>(prepared.layout.width) * prepared.layout.height, 0); if (state.tooltip_enabled && tooltip.active && prepared.layout.target.contains(tooltip.position)) { std::ostringstream text; text << std::fixed << std::setprecision(2) << frequency_axis->point_to_coordinate(tooltip.position) << " Hz"; prepared.tooltip_text = text.str(); prepared.tooltip_box = {tooltip.position.x + 8.0, tooltip.position.y + 8.0, 110.0, 24.0}; } prepared.valid = true; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
void Waterfall::Private::prepare_partition(Object* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = object->template read_state<Waterfall_State_Tag>(); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; const std::size_t cells = static_cast<std::size_t>(columns) * state.rows.size(); const auto [first, last] = detail::raster_partition_range(cells, index, graph_partition_count); for (std::size_t cell = first; cell < last; ++cell) { const std::size_t row_index = cell / static_cast<std::size_t>(columns); const int column = static_cast<int>(cell % static_cast<std::size_t>(columns)); const auto& values = state.rows[row_index].values; const std::size_t source = static_cast<std::size_t>(prepared.source_first + column); prepared.pixels[prepared.layout.index(column, static_cast<int>(row_index))] = premultiply(state.color_map.sample(detail::normalized_plot_value(values[source], state.power_range))); } }
|
void Waterfall::Private::prepare_partition(Object* object, Plot_Partition_Count index) { if (!prepared.valid) return; const auto& state = object->template read_prop<Waterfall::Base_Tag>(); const int columns = prepared.layout.first_horizontal ? prepared.layout.width : prepared.layout.height; const std::size_t cells = static_cast<std::size_t>(columns) * state.rows.size(); const auto [first, last] = detail::raster_partition_range(cells, index, graph_partition_count); for (std::size_t cell = first; cell < last; ++cell) { const std::size_t row_index = cell / static_cast<std::size_t>(columns); const int column = static_cast<int>(cell % static_cast<std::size_t>(columns)); const auto& values = state.rows[row_index].values; const std::size_t source = static_cast<std::size_t>(prepared.source_first + column); prepared.pixels[prepared.layout.index(column, static_cast<int>(row_index))] = premultiply(state.color_map.sample(detail::normalized_plot_value(values[source], state.power_range))); } }
|
||||||
template <Attached Object> void Waterfall::Private::paint_frame(Object* object) { const auto& state = object->template read_state<Waterfall_State_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); detail::paint_raster(painter, prepared.layout, prepared.pixels, state.interpolation_mode); if (!prepared.tooltip_text.empty()) { painter.rect(prepared.tooltip_box, Pen{state.tooltip_text_pen.color}, state.tooltip_background_brush); painter.text({prepared.tooltip_box.x + 4.0, prepared.tooltip_box.y + 3.0}, prepared.tooltip_text, state.tooltip_font, state.tooltip_text_pen); } }
|
template <Attached Object> void Waterfall::Private::paint_frame(Object* object) { const auto& state = object->template read_prop<Waterfall::Base_Tag>(); auto& cache = object->template pending_buffer<Color_Cache>(); cache.ensure_size(prepared.canvas); cache.clear(); if (!prepared.valid) return; detail::Painter painter(cache, prepared.canvas); detail::paint_raster(painter, prepared.layout, prepared.pixels, state.interpolation_mode); if (!prepared.tooltip_text.empty()) { painter.rect(prepared.tooltip_box, Pen{state.tooltip_text_pen.color}, state.tooltip_background_brush); painter.text({prepared.tooltip_box.x + 4.0, prepared.tooltip_box.y + 3.0}, prepared.tooltip_text, state.tooltip_font, state.tooltip_text_pen); } }
|
||||||
template <Attached Object> void Waterfall::Private::handle_event(Object* object, const Event& event) { if (detail::update_hover_tooltip(tooltip, event)) object->template mark_dirty<Paint_Tag>(); }
|
template <Attached Object> void Waterfall::Private::handle_event(Object* object, const Event& event) { if (detail::update_hover_tooltip(tooltip, event)) object->template mark_dirty<Paint_Tag>(); }
|
||||||
template <typename Object, typename Owner, typename Member, typename State_Type> void Waterfall::Private::after_state_set(Object* object, Member Owner::*, State_Access<State_Type>) { if constexpr (std::same_as<Owner, State>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
template <typename Object, typename Owner, typename Member, typename Prop_Type> void Waterfall::Private::after_prop_set(Object* object, Member Owner::*, Prop_Access<Prop_Type>) { if constexpr (std::same_as<Owner, Prop>) object->template mark_dirty<Prepare_Data_Tag>(); }
|
||||||
|
template <typename Object, typename Prop_Type, typename State_Type> void Waterfall::Private::before_advance(Object*, Prop_Type*, State_Access<State_Type> pending_states, const Prop_Type* current_prop, State_Access<const State_Type>) { auto& state = pending_states.template get<Waterfall::Base_Tag>(); const auto& prop = static_cast<const Prop&>(*current_prop); state.row_count = prop.rows.size(); state.stored_point_count = 0; for (const auto& row : prop.rows) state.stored_point_count += row.values.size(); state.rendered_cell_count = prepared.valid ? prepared.pixels.size() : 0; }
|
||||||
template <Attached Object>
|
template <Attached Object>
|
||||||
const Waterfall::Private::Dispatch& Waterfall::Private::dispatch_for() { static const Dispatch value{[](Root* root, Plot_Time_Tick tick, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); const std::size_t row_limit = static_cast<std::size_t>(std::max<Axis_Visible_Count>(2, data.time_axis->template read_state<Time_Axis_State_Tag>().visible_count)); object->template update_state<&State::rows>([=](State_Access<typename Object::State> states) { auto& state = states.template get<Waterfall_State_Tag>(); state.rows.push_back({tick, {values.begin(), values.end()}}); while (state.rows.size() > row_limit) state.rows.erase(state.rows.begin()); }); }, [](Root* root, Time_Of_Day time, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); data.dispatch->append(root, data.time_axis->append_time(time), values); }, [](const Root* root) { return static_cast<const Object*>(root)->template read_state<Waterfall_State_Tag>().rows.size(); }, [](const Root* root) { const auto& rows = static_cast<const Object*>(root)->template read_state<Waterfall_State_Tag>().rows; std::size_t count{}; for (const auto& row : rows) count += row.values.size(); return count; }, [](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); return data.prepared.valid ? data.prepared.pixels.size() : 0; }}; return value; }
|
const Waterfall::Private::Dispatch& Waterfall::Private::dispatch_for() { static const Dispatch value{[](Root* root, Plot_Time_Tick tick, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); const std::size_t row_limit = static_cast<std::size_t>(std::max<Axis_Visible_Count>(2, data.time_axis->template read_prop<Time_Axis::Base_Tag>().visible_count)); object->template update_prop<&Prop::rows>([=](Prop_Access<typename Object::Prop> props) { auto& state = props.template get<Waterfall::Base_Tag>(); state.rows.push_back({tick, {values.begin(), values.end()}}); while (state.rows.size() > row_limit) state.rows.erase(state.rows.begin()); }); }, [](Root* root, Time_Of_Day time, std::span<const Plot_Value> values) { auto* object = static_cast<Object*>(root); auto& data = static_cast<typename Object::Private&>(*object->d); data.dispatch->append(root, data.time_axis->append_time(time), values); }, [](const Root* root) { return static_cast<const Object*>(root)->template read_prop<Waterfall::Base_Tag>().rows.size(); }, [](const Root* root) { const auto& rows = static_cast<const Object*>(root)->template read_prop<Waterfall::Base_Tag>().rows; std::size_t count{}; for (const auto& row : rows) count += row.values.size(); return count; }, [](const Root* root) { const auto& data = static_cast<const typename Object::Private&>(*static_cast<const Object*>(root)->d); return data.prepared.valid ? data.prepared.pixels.size() : 0; }}; return value; }
|
||||||
template <Attached Object> void Waterfall::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
template <Attached Object> void Waterfall::Private::bind_private_crtp(Object* object) { Prev_Private::bind_private_crtp(object); dispatch = &dispatch_for<Object>(); }
|
||||||
inline void Waterfall::Private::bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Time_Object* time_axis_value) { scene = scene_value; frequency_axis = frequency_axis_value; time_axis = time_axis_value; }
|
inline void Waterfall::Private::bind_sources(Scene_Object* scene_value, Frequency_Object* frequency_axis_value, Time_Object* time_axis_value) { scene = scene_value; frequency_axis = frequency_axis_value; time_axis = time_axis_value; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Render_Scene_2D.hpp"
|
#include "Render_Scene_2D.hpp"
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
bool Render_Scene_2D::State::operator==(const State&) const = default;
|
bool Render_Scene_2D::State::operator==(const State&) const = default;
|
||||||
|
bool Render_Scene_2D::Prop::operator==(const Prop&) const = default;
|
||||||
|
|
||||||
Render_Frame_Status Render_Scene_2D::render_frame() {
|
Render_Frame_Status Render_Scene_2D::render_frame() {
|
||||||
return static_cast<Private&>(*d).dispatch->render_frame(this);
|
return static_cast<Private&>(*d).dispatch->render_frame(this);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "../render/Blend2D_Cache.hpp"
|
#include "../render/Blend2D_Cache.hpp"
|
||||||
#include <scene.hpp>
|
#include <scene.hpp>
|
||||||
namespace aethera::render_2d {
|
namespace aethera::render_2d {
|
||||||
struct Render_Scene_2D_State_Tag {};
|
|
||||||
struct Scene_Color_Cache_Tag {};
|
struct Scene_Color_Cache_Tag {};
|
||||||
enum class Render_Frame_Status : std::uint8_t {
|
enum class Render_Frame_Status : std::uint8_t {
|
||||||
rendered,
|
rendered,
|
||||||
@@ -11,13 +10,15 @@ enum class Render_Frame_Status : std::uint8_t {
|
|||||||
empty_viewport
|
empty_viewport
|
||||||
};
|
};
|
||||||
/* 执行二维 Renderable 图、合成颜色层并发布最终像素帧。 */
|
/* 执行二维 Renderable 图、合成颜色层并发布最终像素帧。 */
|
||||||
struct Render_Scene_2D : Def<Render_Scene_2D, Scene, State_Type<Render_Scene_2D_State_Tag>,
|
struct Render_Scene_2D : Def<Render_Scene_2D, Scene,
|
||||||
Tagged_Buffer<Scene_Color_Cache_Tag, Blend2D_Cache>> {
|
Tagged_Buffer<Scene_Color_Cache_Tag, Blend2D_Cache>> {
|
||||||
struct Prop : Prev_Prop {};
|
struct Prop : Prev_Prop {
|
||||||
struct State : Prev_State<Render_Scene_2D_State_Tag> {
|
|
||||||
Size viewport{}; /* 最终帧的像素尺寸;空尺寸不执行渲染。 */
|
Size viewport{}; /* 最终帧的像素尺寸;空尺寸不执行渲染。 */
|
||||||
Color background{Color::black()}; /* 每帧合成前写入的背景颜色。 */
|
Color background{Color::black()}; /* 每帧合成前写入的背景颜色。 */
|
||||||
bool view_active{}; /* 视图是否接受 render_frame 请求。 */
|
bool view_active{}; /* 视图是否接受 render_frame 请求。 */
|
||||||
|
bool operator==(const Prop&) const;
|
||||||
|
};
|
||||||
|
struct State : Prev_State {
|
||||||
bool operator==(const State&) const;
|
bool operator==(const State&) const;
|
||||||
};
|
};
|
||||||
/* 完整声明、合成顺序和 CRTP 分派见 Render_Scene_2D.ipp。 */
|
/* 完整声明、合成顺序和 CRTP 分派见 Render_Scene_2D.ipp。 */
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ struct Render_Scene_2D::Private : Prev_Private {
|
|||||||
template <Attached Object, typename Callback>
|
template <Attached Object, typename Callback>
|
||||||
void Render_Scene_2D::Private::process(Object* object, Callback&& callback)
|
void Render_Scene_2D::Private::process(Object* object, Callback&& callback)
|
||||||
requires std::invocable<Callback, const Render_Frame_Status&> {
|
requires std::invocable<Callback, const Render_Frame_Status&> {
|
||||||
const auto& state = static_cast<const State&>(*static_cast<typename Object::Private&>(*this).state.current);
|
const auto& state = static_cast<const Prop&>(*static_cast<typename Object::Private&>(*this).current);
|
||||||
if (!state.view_active) {
|
if (!state.view_active) {
|
||||||
const Render_Frame_Status status = Render_Frame_Status::view_inactive;
|
const Render_Frame_Status status = Render_Frame_Status::view_inactive;
|
||||||
std::invoke(std::forward<Callback>(callback), status);
|
std::invoke(std::forward<Callback>(callback), status);
|
||||||
@@ -92,7 +92,7 @@ const Render_Scene_2D::Private::Dispatch& Render_Scene_2D::Private::dispatch_for
|
|||||||
static_cast<typename Object::Private&>(*object->d).dispatch_event(object, event);
|
static_cast<typename Object::Private&>(*object->d).dispatch_event(object, event);
|
||||||
},
|
},
|
||||||
[](Root* root, bool active) {
|
[](Root* root, bool active) {
|
||||||
static_cast<Object*>(root)->template update_state<&State::view_active>(active);
|
static_cast<Object*>(root)->template set<&Prop::view_active>(active);
|
||||||
},
|
},
|
||||||
[](const Root* root) {
|
[](const Root* root) {
|
||||||
const auto* object = static_cast<const Object*>(root);
|
const auto* object = static_cast<const Object*>(root);
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ std::unique_ptr<Object> build_axis() {
|
|||||||
TEST(axis_dispatch, numeric_axis_public_shell_reads_final_private_state) {
|
TEST(axis_dispatch, numeric_axis_public_shell_reads_final_private_state) {
|
||||||
using Object = Impl<Numeric_Axis>;
|
using Object = Impl<Numeric_Axis>;
|
||||||
auto axis = build_axis<Object>();
|
auto axis = build_axis<Object>();
|
||||||
axis->update_state<&Abs_Axis::State::position>(Point_F{10.0, 0.0});
|
axis->set<&Abs_Axis::Prop::position>(Point_F{10.0, 0.0});
|
||||||
axis->update_state<&Abs_Axis::State::pixel_length>(200.0);
|
axis->set<&Abs_Axis::Prop::pixel_length>(200.0);
|
||||||
axis->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{0.0, 20.0});
|
axis->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 20.0});
|
||||||
axis->advance();
|
axis->advance();
|
||||||
EXPECT_EQ(axis->coordinate_range(), (Axis_Range{0.0, 20.0}));
|
EXPECT_EQ(axis->coordinate_range(), (Axis_Range{0.0, 20.0}));
|
||||||
EXPECT_DOUBLE_EQ(axis->coordinate_to_pixel(5.0), 60.0);
|
EXPECT_DOUBLE_EQ(axis->coordinate_to_pixel(5.0), 60.0);
|
||||||
@@ -30,10 +30,10 @@ TEST(axis_dispatch, numeric_axis_public_shell_reads_final_private_state) {
|
|||||||
TEST(axis_dispatch, point_conversion_uses_current_orientation) {
|
TEST(axis_dispatch, point_conversion_uses_current_orientation) {
|
||||||
using Object = Impl<Numeric_Axis>;
|
using Object = Impl<Numeric_Axis>;
|
||||||
auto axis = build_axis<Object>();
|
auto axis = build_axis<Object>();
|
||||||
axis->update_state<&Abs_Axis::State::position>(Point_F{0.0, 20.0});
|
axis->set<&Abs_Axis::Prop::position>(Point_F{0.0, 20.0});
|
||||||
axis->update_state<&Abs_Axis::State::pixel_length>(100.0);
|
axis->set<&Abs_Axis::Prop::pixel_length>(100.0);
|
||||||
axis->update_state<&Abs_Axis::State::orientation>(Axis_Orientation::vertical);
|
axis->set<&Abs_Axis::Prop::orientation>(Axis_Orientation::vertical);
|
||||||
axis->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{0.0, 10.0});
|
axis->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 10.0});
|
||||||
axis->advance();
|
axis->advance();
|
||||||
EXPECT_DOUBLE_EQ(axis->point_to_coordinate({90.0, 70.0}), 5.0);
|
EXPECT_DOUBLE_EQ(axis->point_to_coordinate({90.0, 70.0}), 5.0);
|
||||||
}
|
}
|
||||||
@@ -60,9 +60,9 @@ TEST(axis_render, scene_prepares_and_paints_axis_cache) {
|
|||||||
initialize_runtime(2);
|
initialize_runtime(2);
|
||||||
auto axis = build_axis<Object>();
|
auto axis = build_axis<Object>();
|
||||||
auto scene = build_axis<Scene_Object>();
|
auto scene = build_axis<Scene_Object>();
|
||||||
axis->update_state<&Abs_Axis::State::position>(Point_F{8.0, 8.0});
|
axis->set<&Abs_Axis::Prop::position>(Point_F{8.0, 8.0});
|
||||||
axis->update_state<&Abs_Axis::State::canvas_size>(Size{160, 64});
|
axis->set<&Abs_Axis::Prop::canvas_size>(Size{160, 64});
|
||||||
axis->update_state<&Abs_Axis::State::pixel_length>(120.0);
|
axis->set<&Abs_Axis::Prop::pixel_length>(120.0);
|
||||||
ASSERT_TRUE((scene->edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) {
|
ASSERT_TRUE((scene->edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) {
|
||||||
prepare.add(axis.get());
|
prepare.add(axis.get());
|
||||||
paint.add(axis.get());
|
paint.add(axis.get());
|
||||||
@@ -80,9 +80,9 @@ TEST(axis_render, scene_prepares_and_paints_axis_cache) {
|
|||||||
TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) {
|
TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) {
|
||||||
using Object = Impl<Numeric_Axis>;
|
using Object = Impl<Numeric_Axis>;
|
||||||
auto axis = build_axis<Object>();
|
auto axis = build_axis<Object>();
|
||||||
axis->update_state<&Abs_Axis::State::position>(Point_F{0.0, 0.0});
|
axis->set<&Abs_Axis::Prop::position>(Point_F{0.0, 0.0});
|
||||||
axis->update_state<&Abs_Axis::State::pixel_length>(100.0);
|
axis->set<&Abs_Axis::Prop::pixel_length>(100.0);
|
||||||
axis->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{0.0, 10.0});
|
axis->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 10.0});
|
||||||
axis->advance();
|
axis->advance();
|
||||||
Wheel_Event wheel;
|
Wheel_Event wheel;
|
||||||
wheel.position = {50.0, 0.0};
|
wheel.position = {50.0, 0.0};
|
||||||
@@ -105,7 +105,7 @@ TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) {
|
|||||||
TEST(axis_state, invalid_numeric_range_is_rejected_without_poisoning_pending_state) {
|
TEST(axis_state, invalid_numeric_range_is_rejected_without_poisoning_pending_state) {
|
||||||
using Object = Impl<Numeric_Axis>;
|
using Object = Impl<Numeric_Axis>;
|
||||||
auto axis = build_axis<Object>();
|
auto axis = build_axis<Object>();
|
||||||
EXPECT_THROW((axis->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{1.0, 1.0})), std::invalid_argument);
|
EXPECT_THROW((axis->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{1.0, 1.0})), std::invalid_argument);
|
||||||
axis->advance();
|
axis->advance();
|
||||||
EXPECT_EQ(axis->coordinate_range(), (Axis_Range{0.0, 20.0}));
|
EXPECT_EQ(axis->coordinate_range(), (Axis_Range{0.0, 20.0}));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ std::unique_ptr<Object> build_object(Args&&... args) {
|
|||||||
|
|
||||||
template <typename Axis>
|
template <typename Axis>
|
||||||
void configure_axis(Axis* axis, Axis_Orientation orientation, Point_F position, Axis_Pixel_Length length, Size canvas) {
|
void configure_axis(Axis* axis, Axis_Orientation orientation, Point_F position, Axis_Pixel_Length length, Size canvas) {
|
||||||
axis->template update_state<&Abs_Axis::State::orientation>(orientation);
|
axis->template set<&Abs_Axis::Prop::orientation>(orientation);
|
||||||
axis->template update_state<&Abs_Axis::State::position>(position);
|
axis->template set<&Abs_Axis::Prop::position>(position);
|
||||||
axis->template update_state<&Abs_Axis::State::pixel_length>(length);
|
axis->template set<&Abs_Axis::Prop::pixel_length>(length);
|
||||||
axis->template update_state<&Abs_Axis::State::canvas_size>(canvas);
|
axis->template set<&Abs_Axis::Prop::canvas_size>(canvas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,22 +42,22 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) {
|
|||||||
configure_axis(power.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
configure_axis(power.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||||
configure_axis(time.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
configure_axis(time.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
||||||
configure_axis(value.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
configure_axis(value.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||||
frequency->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{0.0, 100.0});
|
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||||
power->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{-100.0, 0.0});
|
power->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-100.0, 0.0});
|
||||||
value->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{0.0, 100.0});
|
value->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||||
auto trace = build_object<Trace>(scene.get(), time.get(), value.get());
|
auto trace = build_object<Trace>(scene.get(), time.get(), value.get());
|
||||||
auto sweep = build_object<Sweep>(scene.get(), frequency.get(), power.get());
|
auto sweep = build_object<Sweep>(scene.get(), frequency.get(), power.get());
|
||||||
trace->update_state<&Frequency_Trace::State::partition_mode>(Plot_Partition_Mode::fixed);
|
trace->set<&Frequency_Trace::Prop::partition_mode>(Plot_Partition_Mode::fixed);
|
||||||
trace->update_state<&Frequency_Trace::State::partition_count>(2u);
|
trace->set<&Frequency_Trace::Prop::partition_count>(2u);
|
||||||
trace->append_sample(0, 10.0);
|
trace->append_sample(0, 10.0);
|
||||||
trace->append_sample(1, 50.0);
|
trace->append_sample(1, 50.0);
|
||||||
trace->append_sample(2, 90.0);
|
trace->append_sample(2, 90.0);
|
||||||
sweep->update_state<&Sweep_Spectrum::State::frequency_range>(Axis_Range{0.0, 100.0});
|
sweep->set<&Sweep_Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||||
sweep->update_state<&Sweep_Spectrum::State::partition_mode>(Plot_Partition_Mode::fixed);
|
sweep->set<&Sweep_Spectrum::Prop::partition_mode>(Plot_Partition_Mode::fixed);
|
||||||
sweep->update_state<&Sweep_Spectrum::State::partition_count>(2u);
|
sweep->set<&Sweep_Spectrum::Prop::partition_count>(2u);
|
||||||
const std::array<Plot_Value, 4> block{-90.0, -60.0, -30.0, -10.0};
|
const std::array<Plot_Value, 4> block{-90.0, -60.0, -30.0, -10.0};
|
||||||
sweep->append_block(block);
|
sweep->append_block(block);
|
||||||
scene->update_state<&Render_Scene_2D::State::viewport>(canvas);
|
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||||
scene->activate_view();
|
scene->activate_view();
|
||||||
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
||||||
EXPECT_EQ(trace->sample_count(), 3u);
|
EXPECT_EQ(trace->sample_count(), 3u);
|
||||||
@@ -82,20 +82,20 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) {
|
|||||||
configure_axis(frequency.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
configure_axis(frequency.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
||||||
configure_axis(power.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
configure_axis(power.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||||
configure_axis(time.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
configure_axis(time.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||||
frequency->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{0.0, 100.0});
|
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||||
power->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{-100.0, 0.0});
|
power->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-100.0, 0.0});
|
||||||
auto glow = build_object<Glow>(scene.get(), frequency.get(), power.get());
|
auto glow = build_object<Glow>(scene.get(), frequency.get(), power.get());
|
||||||
auto waterfall = build_object<Fall>(scene.get(), frequency.get(), time.get());
|
auto waterfall = build_object<Fall>(scene.get(), frequency.get(), time.get());
|
||||||
glow->update_state<&Afterglow::State::frequency_range>(Axis_Range{0.0, 100.0});
|
glow->set<&Afterglow::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||||
glow->update_state<&Afterglow::State::power_range>(Axis_Range{-100.0, 0.0});
|
glow->set<&Afterglow::Prop::power_range>(Axis_Range{-100.0, 0.0});
|
||||||
glow->update_state<&Afterglow::State::power_point_size>(16u);
|
glow->set<&Afterglow::Prop::power_point_size>(16u);
|
||||||
waterfall->update_state<&Waterfall::State::frequency_range>(Axis_Range{0.0, 100.0});
|
waterfall->set<&Waterfall::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||||
waterfall->update_state<&Waterfall::State::power_range>(Axis_Range{-100.0, 0.0});
|
waterfall->set<&Waterfall::Prop::power_range>(Axis_Range{-100.0, 0.0});
|
||||||
const std::array<Plot_Value, 4> row{-90.0, -60.0, -30.0, -10.0};
|
const std::array<Plot_Value, 4> row{-90.0, -60.0, -30.0, -10.0};
|
||||||
glow->append_spectrum(row);
|
glow->append_spectrum(row);
|
||||||
waterfall->append_row(0, row);
|
waterfall->append_row(0, row);
|
||||||
waterfall->append_row(1, row);
|
waterfall->append_row(1, row);
|
||||||
scene->update_state<&Render_Scene_2D::State::viewport>(canvas);
|
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||||
scene->activate_view();
|
scene->activate_view();
|
||||||
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
||||||
EXPECT_EQ(glow->history_count(), 1u);
|
EXPECT_EQ(glow->history_count(), 1u);
|
||||||
@@ -116,17 +116,17 @@ TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) {
|
|||||||
auto vertical = build_object<Numeric>();
|
auto vertical = build_object<Numeric>();
|
||||||
configure_axis(horizontal.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
configure_axis(horizontal.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
||||||
configure_axis(vertical.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
configure_axis(vertical.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||||
horizontal->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{-1.0, 1.0});
|
horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-1.0, 1.0});
|
||||||
vertical->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{-1.0, 1.0});
|
vertical->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-1.0, 1.0});
|
||||||
auto diagram = build_object<Diagram>(scene.get(), horizontal.get(), vertical.get());
|
auto diagram = build_object<Diagram>(scene.get(), horizontal.get(), vertical.get());
|
||||||
auto overlay = build_object<Overlay>(scene.get(), horizontal.get(), vertical.get());
|
auto overlay = build_object<Overlay>(scene.get(), horizontal.get(), vertical.get());
|
||||||
diagram->update_state<&Constellation_Diagram::State::i_range>(Axis_Range{-1.0, 1.0});
|
diagram->set<&Constellation_Diagram::Prop::i_range>(Axis_Range{-1.0, 1.0});
|
||||||
diagram->update_state<&Constellation_Diagram::State::q_range>(Axis_Range{-1.0, 1.0});
|
diagram->set<&Constellation_Diagram::Prop::q_range>(Axis_Range{-1.0, 1.0});
|
||||||
diagram->append_point({0.25, -0.25});
|
diagram->append_point({0.25, -0.25});
|
||||||
scene->update_state<&Render_Scene_2D::State::viewport>(canvas);
|
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||||
scene->activate_view();
|
scene->activate_view();
|
||||||
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
||||||
EXPECT_EQ(diagram->point_count(), 1u);
|
EXPECT_EQ(diagram->point_count(), 1u);
|
||||||
EXPECT_EQ(overlay->read_state<Renderable_State_Tag>().prepare_task_count, 0u);
|
EXPECT_EQ(overlay->read_state<Renderable::Base_Tag>().prepare_task_count, 0u);
|
||||||
EXPECT_EQ(overlay->read_state<Renderable_State_Tag>().paint_task_count, 1u);
|
EXPECT_EQ(overlay->read_state<Renderable::Base_Tag>().paint_task_count, 1u);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ TEST(spectrum_data, publishes_samples_and_interpolates_power) {
|
|||||||
const auto missing = spectrum->power_at(25.0);
|
const auto missing = spectrum->power_at(25.0);
|
||||||
ASSERT_FALSE(missing.has_value());
|
ASSERT_FALSE(missing.has_value());
|
||||||
EXPECT_EQ(missing.error(), Spectrum::Power_At_Result::no_samples);
|
EXPECT_EQ(missing.error(), Spectrum::Power_At_Result::no_samples);
|
||||||
spectrum->update_state<&Spectrum::State::frequency_range>(Axis_Range{0.0, 100.0});
|
spectrum->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||||
const double samples[]{-100.0, -50.0, 0.0};
|
const double samples[]{-100.0, -50.0, 0.0};
|
||||||
spectrum->update_samples(samples);
|
spectrum->update_samples(samples);
|
||||||
spectrum->advance();
|
spectrum->advance();
|
||||||
@@ -84,24 +84,24 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
|
|||||||
auto power = build_object<Power>();
|
auto power = build_object<Power>();
|
||||||
auto spectrum = build_object<Spectrum_Object>(scene.get(), frequency.get(), power.get());
|
auto spectrum = build_object<Spectrum_Object>(scene.get(), frequency.get(), power.get());
|
||||||
const Size canvas{160, 120};
|
const Size canvas{160, 120};
|
||||||
frequency->update_state<&Abs_Axis::State::position>(Point_F{20.0, 100.0});
|
frequency->set<&Abs_Axis::Prop::position>(Point_F{20.0, 100.0});
|
||||||
frequency->update_state<&Abs_Axis::State::canvas_size>(canvas);
|
frequency->set<&Abs_Axis::Prop::canvas_size>(canvas);
|
||||||
frequency->update_state<&Abs_Axis::State::pixel_length>(120.0);
|
frequency->set<&Abs_Axis::Prop::pixel_length>(120.0);
|
||||||
frequency->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{0.0, 100.0});
|
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||||
power->update_state<&Abs_Axis::State::position>(Point_F{20.0, 100.0});
|
power->set<&Abs_Axis::Prop::position>(Point_F{20.0, 100.0});
|
||||||
power->update_state<&Abs_Axis::State::canvas_size>(canvas);
|
power->set<&Abs_Axis::Prop::canvas_size>(canvas);
|
||||||
power->update_state<&Abs_Axis::State::pixel_length>(-80.0);
|
power->set<&Abs_Axis::Prop::pixel_length>(-80.0);
|
||||||
power->update_state<&Abs_Axis::State::orientation>(Axis_Orientation::vertical);
|
power->set<&Abs_Axis::Prop::orientation>(Axis_Orientation::vertical);
|
||||||
power->update_state<&Numeric_Axis::State::coordinate_range>(Axis_Range{-100.0, 0.0});
|
power->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-100.0, 0.0});
|
||||||
spectrum->update_state<&Spectrum::State::frequency_range>(Axis_Range{0.0, 100.0});
|
spectrum->set<&Spectrum::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||||
spectrum->update_state<&Spectrum::State::partition_mode>(Spectrum_Partition_Mode::fixed);
|
spectrum->set<&Spectrum::Prop::partition_mode>(Spectrum_Partition_Mode::fixed);
|
||||||
spectrum->update_state<&Spectrum::State::partition_count>(3u);
|
spectrum->set<&Spectrum::Prop::partition_count>(3u);
|
||||||
spectrum->update_state<&Spectrum::State::sweep_region_visible>(true);
|
spectrum->set<&Spectrum::Prop::sweep_region_visible>(true);
|
||||||
spectrum->update_state<&Spectrum::State::max_hold_visible>(true);
|
spectrum->set<&Spectrum::Prop::max_hold_visible>(true);
|
||||||
const double samples[]{-90.0, -65.0, -20.0, -45.0, -75.0};
|
const double samples[]{-90.0, -65.0, -20.0, -45.0, -75.0};
|
||||||
spectrum->update_samples(samples);
|
spectrum->update_samples(samples);
|
||||||
scene->update_state<&Render_Scene_2D::State::viewport>(canvas);
|
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||||
scene->update_state<&Render_Scene_2D::State::background>(Color::transparent());
|
scene->set<&Render_Scene_2D::Prop::background>(Color::transparent());
|
||||||
scene->activate_view();
|
scene->activate_view();
|
||||||
const auto prepare_graph = scene->pending_dependency_graph<Prepare_Data_Tag>();
|
const auto prepare_graph = scene->pending_dependency_graph<Prepare_Data_Tag>();
|
||||||
EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), scene.get()));
|
EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), scene.get()));
|
||||||
@@ -109,23 +109,23 @@ TEST(render_scene_2d, composites_axes_and_spectrum_into_final_frame) {
|
|||||||
EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), power.get()));
|
EXPECT_TRUE(prepare_graph.depends_on(spectrum.get(), power.get()));
|
||||||
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
||||||
EXPECT_GT(spectrum->rendered_point_count(), 0u);
|
EXPECT_GT(spectrum->rendered_point_count(), 0u);
|
||||||
const auto& render_state = spectrum->read_state<Renderable_State_Tag>();
|
const auto& render_state = spectrum->read_state<Renderable::Base_Tag>();
|
||||||
EXPECT_EQ(render_state.prepare_task_count, 4u);
|
EXPECT_EQ(render_state.prepare_task_count, 4u);
|
||||||
EXPECT_EQ(render_state.paint_task_count, 1u);
|
EXPECT_EQ(render_state.paint_task_count, 1u);
|
||||||
const Image_View frame = scene->frame_view();
|
const Image_View frame = scene->frame_view();
|
||||||
ASSERT_FALSE(frame.empty());
|
ASSERT_FALSE(frame.empty());
|
||||||
EXPECT_TRUE(contains_color(frame));
|
EXPECT_TRUE(contains_color(frame));
|
||||||
spectrum->update_state<&Spectrum::State::partition_count>(2u);
|
spectrum->set<&Spectrum::Prop::partition_count>(2u);
|
||||||
spectrum->update_samples(samples);
|
spectrum->update_samples(samples);
|
||||||
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
||||||
const auto& rebuilt_state = spectrum->read_state<Renderable_State_Tag>();
|
const auto& rebuilt_state = spectrum->read_state<Renderable::Base_Tag>();
|
||||||
EXPECT_TRUE(rebuilt_state.prepare_graph_rebuilt);
|
EXPECT_TRUE(rebuilt_state.prepare_graph_rebuilt);
|
||||||
EXPECT_EQ(rebuilt_state.prepare_task_count, 3u);
|
EXPECT_EQ(rebuilt_state.prepare_task_count, 3u);
|
||||||
EXPECT_TRUE(contains_color(scene->frame_view()));
|
EXPECT_TRUE(contains_color(scene->frame_view()));
|
||||||
const Size resized_canvas{200, 140};
|
const Size resized_canvas{200, 140};
|
||||||
scene->update_state<&Render_Scene_2D::State::viewport>(resized_canvas);
|
scene->set<&Render_Scene_2D::Prop::viewport>(resized_canvas);
|
||||||
frequency->update_state<&Abs_Axis::State::canvas_size>(resized_canvas);
|
frequency->set<&Abs_Axis::Prop::canvas_size>(resized_canvas);
|
||||||
power->update_state<&Abs_Axis::State::canvas_size>(resized_canvas);
|
power->set<&Abs_Axis::Prop::canvas_size>(resized_canvas);
|
||||||
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
EXPECT_EQ(scene->render_frame(), Render_Frame_Status::rendered);
|
||||||
const Image_View resized_frame = scene->frame_view();
|
const Image_View resized_frame = scene->frame_view();
|
||||||
EXPECT_EQ(resized_frame.width, resized_canvas.width);
|
EXPECT_EQ(resized_frame.width, resized_canvas.width);
|
||||||
|
|||||||
Reference in New Issue
Block a user