优化
This commit is contained in:
@@ -162,6 +162,13 @@ Taskflow_Frame_Trace Render_Frame::take_taskflow_trace() {
|
||||
result.markers.push_back(Frame_Trace_Point{
|
||||
static_cast<Frame_Trace_Marker>(index), decode_present_value(encoded)});
|
||||
}
|
||||
for (std::size_t index = 0; index < measurement_count; ++index) {
|
||||
const auto encoded = d->measurements[index].load(std::memory_order_acquire);
|
||||
if (!encoded) continue;
|
||||
result.measurements.push_back(Frame_Trace_Value{
|
||||
static_cast<Frame_Trace_Measurement>(index),
|
||||
decode_present_value(encoded)});
|
||||
}
|
||||
result.graphs = std::move(d->taskflow_graphs);
|
||||
for (auto& worker : d->taskflow_workers) {
|
||||
for (auto& task : worker.tasks)
|
||||
|
||||
@@ -11,6 +11,11 @@ struct Taskflow_Frame_Access;
|
||||
struct Frame_Statistics_Sample;
|
||||
enum struct Frame_Trace_Marker : std::uint8_t {
|
||||
created,
|
||||
plot_update_started,
|
||||
plot_update_finished,
|
||||
scene_render_entered,
|
||||
scene_advance_started,
|
||||
scene_advance_finished,
|
||||
scene_render_requested,
|
||||
scene_render_started,
|
||||
event_dispatch_started,
|
||||
@@ -119,6 +124,7 @@ struct Taskflow_Frame_Trace {
|
||||
std::uint64_t created_time_unix_ns{}; /* 帧创建 Unix 时间,单位纳秒。 */
|
||||
std::size_t worker_count{}; /* 捕获时全局 Executor 的 worker 数。 */
|
||||
std::vector<Frame_Trace_Point> markers{}; /* 与该帧 DAG 共用时间原点的原始流水线时间点。 */
|
||||
std::vector<Frame_Trace_Value> measurements{}; /* 本帧原始耗时测量;不另建统计副本。 */
|
||||
std::vector<Taskflow_Graph_Trace> graphs{}; /* 本帧主动执行的业务 DAG 元信息。 */
|
||||
std::vector<Taskflow_Task_Trace> tasks{}; /* 本帧窗口内原生 Observer 完成的任务执行。 */
|
||||
};
|
||||
|
||||
@@ -176,6 +176,7 @@ TEST(task_graph_observer, business_dag_and_native_execution_share_node_identity)
|
||||
frame.mark(aethera::Frame_Trace_Marker::paint_frame_target_started);
|
||||
frame.mark(aethera::Frame_Trace_Marker::paint_frame_target_finished);
|
||||
frame.mark(aethera::Frame_Trace_Marker::paint_finished);
|
||||
frame.record(aethera::Frame_Trace_Measurement::plot_update_ns, 125'000);
|
||||
frame.request_taskflow_trace();
|
||||
ASSERT_TRUE(aethera::detail::begin_taskflow_trace(frame));
|
||||
aethera::detail::run_taskflow(frame_graph, frame, "test.scene.paint");
|
||||
@@ -189,6 +190,12 @@ TEST(task_graph_observer, business_dag_and_native_execution_share_node_identity)
|
||||
trace.markers,
|
||||
aethera::Frame_Trace_Marker::paint_frame_target_started,
|
||||
&aethera::Frame_Trace_Point::marker));
|
||||
const auto plot_update = std::ranges::find(
|
||||
trace.measurements,
|
||||
aethera::Frame_Trace_Measurement::plot_update_ns,
|
||||
&aethera::Frame_Trace_Value::measurement);
|
||||
ASSERT_NE(plot_update, trace.measurements.end());
|
||||
EXPECT_EQ(plot_update->value_ns, 125'000u);
|
||||
EXPECT_EQ(trace.graphs.front().stage, "test.scene.paint");
|
||||
EXPECT_TRUE(trace.graphs.front().completed);
|
||||
EXPECT_EQ(std::ranges::count_if(trace.graphs.front().nodes, [](const auto& node) {
|
||||
|
||||
@@ -328,6 +328,10 @@ void Render_Scene_2D::Private::ensure_frame_taskflow(Object* object) {
|
||||
template <Attached Object>
|
||||
std::expected<void, Render_Scene_2D::Render_Result>
|
||||
Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) {
|
||||
if (!frame)
|
||||
throw std::invalid_argument(
|
||||
"Render_Scene_2D requires a non-null external frame");
|
||||
frame->mark(Frame_Trace_Marker::scene_render_entered);
|
||||
Frame_Callback callback;
|
||||
Frame_Callback retired_callback;
|
||||
{
|
||||
@@ -353,7 +357,9 @@ Render_Scene_2D::Private::render(Object* object, Frame_2D* frame) {
|
||||
* no Taskflow is running. The completion callback only publishes
|
||||
* results; it must not modify a graph that is still executing.
|
||||
*/
|
||||
frame->mark(Frame_Trace_Marker::scene_advance_started);
|
||||
double_buffer::detail::Internal_Access::advance(object);
|
||||
frame->mark(Frame_Trace_Marker::scene_advance_finished);
|
||||
const Prop& prop = double_buffer::detail::Internal_Access::current_prop_layer<Render_Scene_2D::Base_Tag>(object);
|
||||
if (!prop.view_active) {
|
||||
release_admission();
|
||||
|
||||
@@ -281,6 +281,7 @@ void Render_Scene_3D::Private::ensure_frame_taskflow(Object* object) {
|
||||
template <Attached Object>
|
||||
Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object, Frame_3D* frame) {
|
||||
if (!frame) throw std::invalid_argument("Render_Scene_3D requires a non-null external frame");
|
||||
frame->mark(Frame_Trace_Marker::scene_render_entered);
|
||||
{
|
||||
std::lock_guard lock(render_mutex);
|
||||
if (!frame_callback)
|
||||
@@ -301,7 +302,9 @@ Render_Scene_3D::Render_Result Render_Scene_3D::Private::render(Object* object,
|
||||
* 在创建或执行 frame_taskflow 前一次性推进完整 Scene,确保公共
|
||||
* Render_Graph_Tag 已构建并且 frame DAG 引用的是同一权威运行图。
|
||||
*/
|
||||
frame->mark(Frame_Trace_Marker::scene_advance_started);
|
||||
double_buffer::detail::Internal_Access::advance(object);
|
||||
frame->mark(Frame_Trace_Marker::scene_advance_finished);
|
||||
const Prop& prop = static_cast<const Prop&>(
|
||||
double_buffer::detail::Internal_Access::current_prop(object));
|
||||
if (!prop.view_active) return reject_frame(Render_Result::view_inactive);
|
||||
|
||||
+24
-9
@@ -205,6 +205,10 @@ nlohmann::json taskflow_trace_json(
|
||||
for (const auto& marker : trace.markers)
|
||||
markers[magic_enum::enum_name(marker.marker)] =
|
||||
static_cast<double>(marker.elapsed_ns) / 1'000'000.0;
|
||||
nlohmann::json measurements = nlohmann::json::object();
|
||||
for (const auto& measurement : trace.measurements)
|
||||
measurements[magic_enum::enum_name(measurement.measurement)] =
|
||||
static_cast<double>(measurement.value_ns) / 1'000'000.0;
|
||||
nlohmann::json graphs = nlohmann::json::array();
|
||||
std::unordered_map<std::uint64_t, std::string> node_ids;
|
||||
for (const auto& graph : trace.graphs) {
|
||||
@@ -274,6 +278,7 @@ nlohmann::json taskflow_trace_json(
|
||||
{"created_time_unix_ns", trace.created_time_unix_ns},
|
||||
{"worker_count", trace.worker_count},
|
||||
{"markers", std::move(markers)},
|
||||
{"measurements", std::move(measurements)},
|
||||
{"graphs", std::move(graphs)},
|
||||
{"executions", std::move(executions)}};
|
||||
}
|
||||
@@ -761,14 +766,32 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
try {
|
||||
tick.width = streams.width;
|
||||
tick.height = streams.height;
|
||||
const std::uint64_t sequence = next_frame_sequence++;
|
||||
const Frame_Identity identity{
|
||||
sequence, tick.sequence == 0 ? sequence : tick.sequence};
|
||||
Render_Frame* logical_frame{};
|
||||
if (auto* frame_2d = std::get_if<std::unique_ptr<Frame_2D>>(&managed->frame)) {
|
||||
(*frame_2d)->begin(identity, Frame_2D::native_pixel_format);
|
||||
logical_frame = frame_2d->get();
|
||||
} else {
|
||||
auto& frame_3d = std::get<std::unique_ptr<Frame_3D>>(managed->frame);
|
||||
frame_3d->begin(identity,
|
||||
pacing.video_enabled ? Frame_3D_Output::pixels
|
||||
: Frame_3D_Output::diagnostics,
|
||||
Frame_3D::native_pixel_format);
|
||||
logical_frame = frame_3d.get();
|
||||
}
|
||||
taskflow_trace_claimed = mark_taskflow_trace(*logical_frame);
|
||||
/*
|
||||
* 各图的采样、网格构造和属性快照都在 Plot 自己的准备域完成。
|
||||
* 各图的采样、网格构造和属性读取都在 Plot 自己的准备域完成。
|
||||
* 进入 Scene::render 后只剩已经准备好的 Visual 批次与轻量提交;
|
||||
* 共享 Render Domain 不承担业务数据生成。
|
||||
*/
|
||||
logical_frame->mark(Frame_Trace_Marker::plot_update_started);
|
||||
const auto update_started = std::chrono::steady_clock::now();
|
||||
view->update(tick);
|
||||
const auto update_elapsed = std::chrono::steady_clock::now() - update_started;
|
||||
logical_frame->mark(Frame_Trace_Marker::plot_update_finished);
|
||||
const auto tick_queue_elapsed = tick.issued_at.time_since_epoch().count() == 0
|
||||
? std::chrono::steady_clock::duration::zero()
|
||||
: update_started - tick.issued_at;
|
||||
@@ -783,12 +806,8 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
nanoseconds(update_elapsed));
|
||||
};
|
||||
|
||||
const std::uint64_t sequence = next_frame_sequence++;
|
||||
const Frame_Identity identity{sequence, tick.sequence == 0 ? sequence : tick.sequence};
|
||||
if (auto* scene_2d = std::get_if<std::unique_ptr<Scene_2D>>(&scene)) {
|
||||
auto& output = *std::get<std::unique_ptr<Frame_2D>>(managed->frame);
|
||||
output.begin(identity, Frame_2D::native_pixel_format);
|
||||
taskflow_trace_claimed = mark_taskflow_trace(output);
|
||||
record_plot_measurements(output);
|
||||
(*scene_2d)->set<&Render_Scene_2D::Prop::viewport>(
|
||||
Size{static_cast<int>(tick.width), static_cast<int>(tick.height)});
|
||||
@@ -807,10 +826,6 @@ void Plot::Private::render_frame(Plot_Render_Tick tick) {
|
||||
return;
|
||||
}
|
||||
auto& output = *std::get<std::unique_ptr<Frame_3D>>(managed->frame);
|
||||
output.begin(identity, pacing.video_enabled ? Frame_3D_Output::pixels
|
||||
: Frame_3D_Output::diagnostics,
|
||||
Frame_3D::native_pixel_format);
|
||||
taskflow_trace_claimed = mark_taskflow_trace(output);
|
||||
record_plot_measurements(output);
|
||||
auto& scene_3d = std::get<std::unique_ptr<Scene_3D>>(scene);
|
||||
scene_3d->set<&Render_Scene_3D::Prop::viewport>(Extent{tick.width, tick.height});
|
||||
|
||||
+164
-52
@@ -85,7 +85,8 @@ type Taskflow_Execution_Trace = {native_id: string; node_id: string; worker_id:
|
||||
cpu_time_coarse: boolean; observer_entry_ms: number; observer_exit_ms: number;
|
||||
observer_entry_cpu_ms: number; observer_exit_cpu_ms: number; queue_wait_ms: number};
|
||||
type Taskflow_Frame_Trace = {sequence: number; correlation_id: number; created_time_unix_ns: number; worker_count: number;
|
||||
markers?: Record<string, number>; graphs: Taskflow_Graph_Trace[]; executions: Taskflow_Execution_Trace[]};
|
||||
markers?: Record<string, number>; measurements?: Record<string, number>;
|
||||
graphs: Taskflow_Graph_Trace[]; executions: Taskflow_Execution_Trace[]};
|
||||
type Taskflow_Frame_Response = {protocol: "aethera.taskflow.frames"; version: 1; requested: number; remaining: number;
|
||||
captured: number; complete: boolean; frames: Taskflow_Frame_Trace[]; media_requested?: number;
|
||||
media_captured?: number; media_remaining?: number};
|
||||
@@ -1306,13 +1307,35 @@ function Taskflow_Node_Label({node, sample, summary, state, level}: {
|
||||
<span>协作等待 {sample ? milliseconds(sample.cooperative_wait_ms) : "--"} · 未归因墙时 {sample ? `${sample.cpu_time_coarse ? "≈ " : ""}${milliseconds(Math.max(0, duration - sample.cooperative_wait_ms - sample.cpu_duration_ms))}` : "--"}</span>
|
||||
<span>Observer {sample ? milliseconds(sample.observer_entry_ms + sample.observer_exit_ms) : "--"}</span></>}
|
||||
{Object.entries(node.attributes ?? {}).map(([key, value]) => <span key={key}>{key}:{value}</span>)}
|
||||
{state ? <details className="taskflowNodeState nodrag nopan"
|
||||
onPointerDown={event => event.stopPropagation()} onClick={event => event.stopPropagation()}
|
||||
onWheel={event => event.stopPropagation()}><summary>运行状态 JSON</summary>
|
||||
<pre>{JSON.stringify(state, null, 2)}</pre></details> : null}
|
||||
</div>;
|
||||
}
|
||||
|
||||
function Taskflow_Node_Inspector({node, execution, state, on_clear}: {
|
||||
node?: Taskflow_Node_Trace; execution?: Taskflow_Execution_Trace;
|
||||
state?: unknown; on_clear?: () => void;
|
||||
}) {
|
||||
if (!node) return <aside className="taskflowNodeInspector taskflowNodeInspectorEmpty">
|
||||
<strong>任务节点信息</strong><p>在拓扑图或时间线中选择一个任务,查看该帧记录的归属、属性、状态和执行时刻。</p>
|
||||
</aside>;
|
||||
return <aside className="taskflowNodeInspector"><header><div>
|
||||
<strong>{taskflow_node_name(node.name, node)}</strong>
|
||||
<span>{node.owner ? `${node.owner.label} · ${node.owner.component}` : "无组件归属"}</span>
|
||||
</div>{on_clear ? <button type="button" onClick={on_clear}>清除</button> : null}</header>
|
||||
<section><h4>任务语义</h4><p>{node.name}</p><code>{node.id}</code>
|
||||
<dl><div><dt>节点类型</dt><dd>{node.type}</dd></div>
|
||||
<div><dt>Worker</dt><dd>{execution ? `W${execution.worker_id}` : "--"}</dd></div>
|
||||
<div><dt>就绪</dt><dd>{execution ? `+${execution.ready_ms.toFixed(3)} ms` : "--"}</dd></div>
|
||||
<div><dt>开始</dt><dd>{execution ? `+${execution.started_ms.toFixed(3)} ms` : "--"}</dd></div>
|
||||
<div><dt>执行</dt><dd>{execution ? milliseconds(execution.duration_ms) : "--"}</dd></div>
|
||||
<div><dt>排队</dt><dd>{execution ? milliseconds(execution.queue_wait_ms) : "--"}</dd></div></dl>
|
||||
</section>
|
||||
<section><h4>该帧属性 Prop</h4><pre>{JSON.stringify(node.prop ?? {}, null, 2)}</pre></section>
|
||||
<section><h4>该帧状态 State</h4><pre>{JSON.stringify(node.state ?? state ?? {}, null, 2)}</pre></section>
|
||||
{Object.keys(node.attributes ?? {}).length ? <section><h4>任务元数据</h4>
|
||||
<pre>{JSON.stringify(node.attributes, null, 2)}</pre></section> : null}
|
||||
</aside>;
|
||||
}
|
||||
|
||||
function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskflow_Execution_Trace[]) {
|
||||
const native_ids = new Set(graph.nodes.map(node => node.native_id));
|
||||
const rows = executions.filter(value => native_ids.has(value.native_id));
|
||||
@@ -1730,11 +1753,17 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
|
||||
const [viewport_width, set_viewport_width] = useState(1000);
|
||||
const [viewport_height, set_viewport_height] = useState(720);
|
||||
const [local_fullscreen, set_local_fullscreen] = useState(false);
|
||||
const [selected_node_id, set_selected_node_id] = useState("");
|
||||
const fullscreen = controlled_fullscreen ?? local_fullscreen;
|
||||
const set_fullscreen = (value: boolean) => on_fullscreen_change ? on_fullscreen_change(value) : set_local_fullscreen(value);
|
||||
const displayed_graph = useMemo(() => expanded_taskflow_graph(graph), [graph]);
|
||||
const selected_node = displayed_graph.nodes.find(node => node.id === selected_node_id);
|
||||
const selected_execution = selected_node
|
||||
? executions.find(value => value.native_id === selected_node.native_id) : undefined;
|
||||
const selected_state = selected_node
|
||||
? taskflow_node_state(selected_node, components, frame, gallery_state) : undefined;
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const displayed_graph = expanded_taskflow_graph(graph);
|
||||
const node_id = new Map(displayed_graph.nodes.map(node => [node.native_id, node.id]));
|
||||
const execution = new Map(executions.map(value => [value.native_id, value]));
|
||||
const analysis = taskflow_graph_analysis(displayed_graph, executions);
|
||||
@@ -1772,7 +1801,7 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
|
||||
},
|
||||
children: displayed_graph.nodes.map(node => {
|
||||
const attributes = Object.keys(node.attributes ?? {}).length;
|
||||
const base_height = taskflow_node_state(node, components, frame, gallery_state) ? 266 : aggregate ? 198 : 188;
|
||||
const base_height = aggregate ? 198 : 188;
|
||||
return {id: node.id, width: 300, height: base_height + Math.min(attributes, 6) * 16};
|
||||
}),
|
||||
edges: flow_edges.map(edge => ({id: edge.id, sources: [edge.source], targets: [edge.target]}))
|
||||
@@ -1803,7 +1832,7 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
|
||||
set_edges(flow_edges);
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [graph, executions, frame, aggregate, components, gallery_state]);
|
||||
}, [displayed_graph, executions, frame, aggregate, components, gallery_state]);
|
||||
const copy_topology = async () => {
|
||||
const native_ids = new Set(graph.nodes.map(node => node.native_id));
|
||||
try {
|
||||
@@ -1811,6 +1840,7 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
|
||||
sequence: frame?.sequence,
|
||||
correlation_id: frame?.correlation_id,
|
||||
markers: frame?.markers ?? {},
|
||||
measurements: frame?.measurements ?? {},
|
||||
graph,
|
||||
expanded_graph: expanded_taskflow_graph(graph),
|
||||
/* 保留当前选中拓扑,同时把本帧其余真实 Task_Graph 一并复制。
|
||||
@@ -1835,7 +1865,8 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
|
||||
const flow = <ReactFlow key={viewport_key} nodes={nodes} edges={edges}
|
||||
fitView fitViewOptions={{padding: .12, maxZoom: 1}} nodesDraggable={false} nodesConnectable={false}
|
||||
zoomOnScroll={false} preventScrolling={false}
|
||||
elementsSelectable={false} minZoom={.2} maxZoom={2.5}>
|
||||
elementsSelectable minZoom={.2} maxZoom={2.5}
|
||||
onNodeClick={(_, node) => set_selected_node_id(node.id)}>
|
||||
<Background color="#233956" gap={22}/><MiniMap pannable zoomable/><Controls showInteractive={false}/>
|
||||
</ReactFlow>;
|
||||
return <section className={`taskflowDagSection${fullscreen ? " taskflowDagFullscreen" : ""}`}><header className="taskflowDagToolbar">
|
||||
@@ -1858,19 +1889,23 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_
|
||||
<span><i className="taskflowLegendExecuted"/>已执行,执行时间为主</span>
|
||||
<span><i className="taskflowLegendWaiting"/>真实排队时间大于执行时间</span></>}
|
||||
</div>
|
||||
</header>{fullscreen ? <div className="taskflowDag taskflowDagFill">{flow}</div> : <ResizableBox className="taskflowResizable taskflowDagResizable"
|
||||
width={viewport_width} height={viewport_height} axis="both" minConstraints={[640, 420]}
|
||||
maxConstraints={[12000, 12000]} resizeHandles={["e", "s", "se"]}
|
||||
onResize={(_, data) => { set_viewport_width(data.size.width); set_viewport_height(data.size.height); }}>
|
||||
<div className="taskflowDag">{flow}</div></ResizableBox>}</section>;
|
||||
</header><div className="taskflowInspectionLayout">
|
||||
<Taskflow_Node_Inspector node={selected_node} execution={selected_execution}
|
||||
state={selected_state} on_clear={() => set_selected_node_id("")}/>
|
||||
{fullscreen ? <div className="taskflowDag taskflowDagFill">{flow}</div> : <ResizableBox className="taskflowResizable taskflowDagResizable"
|
||||
width={viewport_width} height={viewport_height} axis="both" minConstraints={[640, 420]}
|
||||
maxConstraints={[12000, 12000]} resizeHandles={["e", "s", "se"]}
|
||||
onResize={(_, data) => { set_viewport_width(data.size.width); set_viewport_height(data.size.height); }}>
|
||||
<div className="taskflowDag">{flow}</div></ResizableBox>}
|
||||
</div></section>;
|
||||
}
|
||||
|
||||
type Taskflow_Timeline_Group = TimelineGroupBase & {
|
||||
node_name: string; worker_id?: number; started_ms?: number; type?: string;
|
||||
};
|
||||
type Taskflow_Timeline_Phase = "topology" | "queue" | "observer_entry" | "body" | "observer_exit" | "completion_tail";
|
||||
type Taskflow_Timeline_Phase = "lifecycle" | "topology" | "queue" | "observer_entry" | "body" | "observer_exit" | "completion_tail";
|
||||
type Taskflow_Timeline_Item = TimelineItemBase<number> & {
|
||||
phase: Taskflow_Timeline_Phase;
|
||||
phase: Taskflow_Timeline_Phase; node_id?: string;
|
||||
};
|
||||
const taskflow_timeline_keys = {
|
||||
groupIdKey: "id", groupTitleKey: "title", groupLabelKey: "title",
|
||||
@@ -1886,8 +1921,9 @@ const taskflow_timeline_time_steps = {
|
||||
* react-calendar-timeline 使用日历毫秒。把 1 帧内毫秒放大为 1 日历秒,
|
||||
* 既保留微小任务的可缩放宽度,又始终以 Render_Frame 创建时刻为零点显示。
|
||||
*/
|
||||
function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_fullscreen, on_fullscreen_change, on_fullscreen_view_change, frame_navigation}: {
|
||||
function Taskflow_Timeline({graph, executions, frame, components, gallery_state, fullscreen: controlled_fullscreen, on_fullscreen_change, on_fullscreen_view_change, frame_navigation}: {
|
||||
graph: Taskflow_Graph_Trace; executions: Taskflow_Execution_Trace[]; frame?: Taskflow_Frame_Trace; fullscreen?: boolean;
|
||||
components: Component[]; gallery_state?: Gallery_Pipeline_State | null;
|
||||
on_fullscreen_change?: (value: boolean) => void; on_fullscreen_view_change?: (view: Taskflow_Fullscreen_View) => void;
|
||||
frame_navigation?: Taskflow_Frame_Navigation;
|
||||
}) {
|
||||
@@ -1914,23 +1950,97 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
if (!(type_visibility[type] ?? type !== "condition")) return false;
|
||||
return !hot_only || execution.duration_ms >= maximum_duration * hot_threshold_percent / 100;
|
||||
}).sort((left, right) => left.started_ms - right.started_ms || left.worker_id - right.worker_id);
|
||||
const groups: Taskflow_Timeline_Group[] = [{
|
||||
id: "topology", title: <div className="taskflowTimelineGroupTitle"><strong>Topology</strong>
|
||||
<span>{graph.stage}</span></div>, node_name: graph.name, type: "topology"
|
||||
}];
|
||||
const groups: Taskflow_Timeline_Group[] = [];
|
||||
const items: Taskflow_Timeline_Item[] = [];
|
||||
const encode_time = (milliseconds: number) => origin + milliseconds * scale;
|
||||
const add_item = (id: string, group: string, phase: Taskflow_Timeline_Phase,
|
||||
start: number, end: number, label: string, task_name: string, detail: string) => {
|
||||
start: number, end: number, label: string, task_name: string, detail: string,
|
||||
node_id?: string) => {
|
||||
if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return;
|
||||
const visible_end = Math.max(end, start + .002);
|
||||
const topology_offset = start - graph.submitted_ms;
|
||||
items.push({id, group, phase, title: label,
|
||||
items.push({id, group, phase, node_id, title: label,
|
||||
start_time: encode_time(start), end_time: encode_time(visible_end),
|
||||
canMove: false, canResize: false, canChangeGroup: false,
|
||||
className: `taskflowTimelineItem taskflowTimelineItem-${phase}`,
|
||||
itemProps: {title: `任务名称:${task_name}\n时间起点:+${start.toFixed(3)} ms(相对帧创建)\n相对 Topology:${topology_offset >= 0 ? "+" : ""}${topology_offset.toFixed(3)} ms\n时间终点:+${end.toFixed(3)} ms\n持续时间:${milliseconds(Math.max(0, end - start))}\n${detail}`}});
|
||||
};
|
||||
const markers = frame?.markers ?? {};
|
||||
const measurements = frame?.measurements ?? {};
|
||||
const marker = (key: string) => markers[key];
|
||||
const add_lifecycle_group = (id: string, title: string, detail: string) =>
|
||||
groups.push({id, title: <div className="taskflowTimelineGroupTitle taskflowTimelineLifecycleTitle">
|
||||
<strong>{title}</strong><span>{detail}</span></div>, node_name: id, type: "lifecycle"});
|
||||
if (frame) {
|
||||
add_lifecycle_group("lifecycle-plot", "Plot 调度与更新", "tick → 获得帧槽 → view.update");
|
||||
const update_started = marker("plot_update_started") ?? 0;
|
||||
const tick_queue = measurements.plot_tick_queue_ns ?? 0;
|
||||
add_item("lifecycle-tick-queue", "lifecycle-plot", "lifecycle",
|
||||
update_started - tick_queue, update_started,
|
||||
`Tick 排队 ${milliseconds(tick_queue)}`, "Plot tick 排队", "时钟 tick 发出到 Plot 开始更新");
|
||||
add_item("lifecycle-update", "lifecycle-plot", "lifecycle", update_started,
|
||||
marker("plot_update_finished") ?? update_started,
|
||||
`数据更新 ${milliseconds(measurements.plot_update_ns ?? 0)}`, "Plot view.update", "数据生成、属性写入和网格准备");
|
||||
|
||||
add_lifecycle_group("lifecycle-scene", "Scene render 准入", "render 入口 → advance → Taskflow 提交");
|
||||
const scene_entered = marker("scene_render_entered") ?? marker("plot_update_finished") ?? graph.submitted_ms;
|
||||
const advance_started = marker("scene_advance_started");
|
||||
const advance_finished = marker("scene_advance_finished");
|
||||
if (advance_started !== undefined) {
|
||||
add_item("lifecycle-scene-admission", "lifecycle-scene", "lifecycle",
|
||||
scene_entered, advance_started,
|
||||
`Scene 准入 ${milliseconds(Math.max(0, advance_started - scene_entered))}`,
|
||||
"Scene render 准入", "进入 Scene::render 到开始推进 Scene");
|
||||
}
|
||||
if (advance_started !== undefined && advance_finished !== undefined) {
|
||||
add_item("lifecycle-scene-advance", "lifecycle-scene", "lifecycle",
|
||||
advance_started, advance_finished,
|
||||
`Scene advance ${milliseconds(advance_finished - advance_started)}`,
|
||||
"Scene advance", "推进 Scene/Renderable 属性、状态和任务图");
|
||||
}
|
||||
const submit_started = advance_finished ?? advance_started ?? scene_entered;
|
||||
add_item("lifecycle-scene-submit", "lifecycle-scene", "lifecycle",
|
||||
submit_started, graph.submitted_ms,
|
||||
`构图/提交 ${milliseconds(Math.max(0, graph.submitted_ms - submit_started))}`,
|
||||
"Scene 构图与提交", "advance 完成到 Taskflow graph submitted");
|
||||
|
||||
add_lifecycle_group("lifecycle-render", "Scene CPU 阶段", "事件 → Prepare → Paint");
|
||||
const lifecycle_intervals: Array<[string, string, string, string]> = [
|
||||
["event_dispatch_started", "event_dispatch_finished", "事件分发", "event"],
|
||||
["prepare_started", "prepare_finished", "Prepare", "prepare"],
|
||||
["paint_started", "paint_finished", "Paint", "paint"]
|
||||
];
|
||||
lifecycle_intervals.forEach(([start_key, end_key, label, id]) => {
|
||||
const start = marker(start_key); const end = marker(end_key);
|
||||
if (start !== undefined && end !== undefined)
|
||||
add_item(`lifecycle-${id}`, "lifecycle-render", "lifecycle", start, end,
|
||||
`${label} ${milliseconds(end - start)}`, label, `${start_key} → ${end_key}`);
|
||||
});
|
||||
|
||||
add_lifecycle_group("lifecycle-backend", "后端与 GPU", "准备 → 队列 → GPU → 回读");
|
||||
const backend_intervals: Array<[string, string, string, string]> = [
|
||||
["backend_prepare_started", "backend_prepare_finished", "后端准备", "backend-prepare"],
|
||||
["backend_queue_entered", "backend_queue_left", "提交队列", "backend-queue"],
|
||||
["gpu_submitted", "gpu_completed", "GPU 执行", "gpu"],
|
||||
["readback_started", "readback_finished", "像素回读", "readback"]
|
||||
];
|
||||
backend_intervals.forEach(([start_key, end_key, label, id]) => {
|
||||
const start = marker(start_key); const end = marker(end_key);
|
||||
if (start !== undefined && end !== undefined)
|
||||
add_item(`lifecycle-${id}`, "lifecycle-backend", "lifecycle", start, end,
|
||||
`${label} ${milliseconds(end - start)}`, label, `${start_key} → ${end_key}`);
|
||||
});
|
||||
|
||||
add_lifecycle_group("lifecycle-publish", "完成与发布", "Scene 完成 → callback → frame ready");
|
||||
const callback_started = marker("callback_started");
|
||||
const callback_finished = marker("callback_finished");
|
||||
if (callback_started !== undefined && callback_finished !== undefined)
|
||||
add_item("lifecycle-callback", "lifecycle-publish", "lifecycle", callback_started, callback_finished,
|
||||
`完成回调 ${milliseconds(callback_finished - callback_started)}`, "完成帧回调",
|
||||
`其中 Plot publish 测量 ${milliseconds(measurements.plot_publish_ns ?? 0)}`);
|
||||
}
|
||||
groups.push({id: "topology", title: <div className="taskflowTimelineGroupTitle"><strong>Topology</strong>
|
||||
<span>{graph.stage}</span></div>, node_name: graph.name, type: "topology"});
|
||||
add_item("topology", "topology", "topology", graph.submitted_ms,
|
||||
graph.finished_ms, `Topology ${milliseconds(graph.finished_ms - graph.submitted_ms)}`,
|
||||
graph.name || graph.stage, `${graph.stage} · Topology 墙钟`);
|
||||
@@ -1965,33 +2075,37 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
if (diagnostic) {
|
||||
add_item(`${group}-runtime`, group, "completion_tail", execution.started_ms,
|
||||
execution.finished_ms, `${taskflow_node_name(node_name, node)} ${milliseconds(execution.duration_ms)}`,
|
||||
node_name, diagnostic_detail);
|
||||
node_name, diagnostic_detail, node?.id);
|
||||
return;
|
||||
}
|
||||
add_item(`${group}-queue`, group, "queue", execution.ready_ms, execution.entered_ms,
|
||||
`排队 ${milliseconds(execution.queue_wait_ms)}`, node_name, diagnostic_detail);
|
||||
`排队 ${milliseconds(execution.queue_wait_ms)}`, node_name, diagnostic_detail, node?.id);
|
||||
add_item(`${group}-entry`, group, "observer_entry", execution.entered_ms, execution.started_ms,
|
||||
`entry ${milliseconds(execution.observer_entry_ms)}`, node_name, diagnostic_detail);
|
||||
`entry ${milliseconds(execution.observer_entry_ms)}`, node_name, diagnostic_detail, node?.id);
|
||||
add_item(`${group}-body`, group, "body", execution.started_ms, execution.finished_ms,
|
||||
`执行 ${milliseconds(execution.duration_ms)}`, node_name, diagnostic_detail);
|
||||
`执行 ${milliseconds(execution.duration_ms)}`, node_name, diagnostic_detail, node?.id);
|
||||
add_item(`${group}-exit`, group, "observer_exit", execution.finished_ms, execution.completed_ms,
|
||||
`exit ${milliseconds(execution.observer_exit_ms)}`, node_name, diagnostic_detail);
|
||||
`exit ${milliseconds(execution.observer_exit_ms)}`, node_name, diagnostic_detail, node?.id);
|
||||
});
|
||||
const extent = Math.max(.1, graph.finished_ms,
|
||||
...rows.flatMap(row => [row.completed_ms, row.finished_ms]));
|
||||
const snapshots = [...new Map(rows.map(row => node_by_native_id.get(row.native_id))
|
||||
.filter((node): node is Taskflow_Node_Trace => Boolean(node?.owner))
|
||||
.map(node => [node.id, node])).values()];
|
||||
return {groups, items, end: origin + extent * 1.06 * scale,
|
||||
span: Math.max(.1, extent * 1.06) * scale, executions: rows.length,
|
||||
last_completed, last_task_completed, snapshots};
|
||||
}, [graph, executions, type_visibility, hot_only, hot_threshold_percent, selected_node_id]);
|
||||
...rows.flatMap(row => [row.completed_ms, row.finished_ms]),
|
||||
...items.map(item => (item.end_time - origin) / scale));
|
||||
const earliest = Math.min(0, ...items.map(item => (item.start_time - origin) / scale));
|
||||
return {groups, items, start: origin + earliest * 1.06 * scale,
|
||||
end: origin + extent * 1.06 * scale,
|
||||
span: Math.max(.1, (extent - earliest) * 1.06) * scale, executions: rows.length,
|
||||
last_completed, last_task_completed};
|
||||
}, [graph, executions, frame, type_visibility, hot_only, hot_threshold_percent, selected_node_id]);
|
||||
const selected_node = graph.nodes.find(node => node.id === selected_node_id);
|
||||
const selected_execution = selected_node
|
||||
? executions.find(value => value.native_id === selected_node.native_id) : undefined;
|
||||
const selected_state = selected_node
|
||||
? taskflow_node_state(selected_node, components, frame, gallery_state) : undefined;
|
||||
const timeline_view_key = `${graph.stage}:${graph.name}`;
|
||||
const visible_range = visible_ranges[timeline_view_key];
|
||||
const timeline_time_props = visible_range
|
||||
? {visibleTimeStart: visible_range.start, visibleTimeEnd: visible_range.end}
|
||||
: {defaultTimeStart: origin, defaultTimeEnd: model.end};
|
||||
: {defaultTimeStart: model.start, defaultTimeEnd: model.end};
|
||||
const copy_timeline = async () => {
|
||||
const native_ids = new Set(graph.nodes.map(node => node.native_id));
|
||||
try {
|
||||
@@ -1999,6 +2113,7 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
sequence: frame?.sequence,
|
||||
correlation_id: frame?.correlation_id,
|
||||
markers: frame?.markers ?? {},
|
||||
measurements: frame?.measurements ?? {},
|
||||
graph,
|
||||
timeline: {origin: "render_frame_created", submitted_ms: graph.submitted_ms, finished_ms: graph.finished_ms,
|
||||
wall_time_ms: Math.max(0, graph.finished_ms - graph.submitted_ms), last_execution_completed_ms: model.last_completed,
|
||||
@@ -2027,6 +2142,7 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
<button onClick={() => void copy_timeline()}>{copy_state}</button>
|
||||
</div>
|
||||
<div className="taskflowLegend" aria-label="Taskflow 时间轴颜色说明">
|
||||
<span><i className="taskflowTimelineLegendLifecycle"/>帧生命周期</span>
|
||||
<span><i className="taskflowTimelineLegendTopology"/>Topology 墙钟</span>
|
||||
<span><i className="taskflowTimelineLegendQueue"/>Executor 排队</span>
|
||||
<span><i className="taskflowTimelineLegendObserver"/>Observer</span>
|
||||
@@ -2047,19 +2163,9 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
onChange={event => set_hot_threshold_percent(Math.min(100, Math.max(1, Number(event.target.value) || 1)))}/>%
|
||||
</label>
|
||||
</div>
|
||||
{selected_node ? <section className="taskflowSelectedCapture">
|
||||
<header><div><strong>{taskflow_node_name(selected_node.name, selected_node)}</strong>
|
||||
<span>{selected_node.owner ? `${selected_node.owner.label} · ${selected_node.owner.component}` : "无组件归属"}</span></div>
|
||||
<button type="button" onClick={() => set_selected_node_id("")}>关闭</button></header>
|
||||
<div><article><h4>任务说明</h4><p>{selected_node.name}</p><code>{selected_node.id}</code></article>
|
||||
<article><h4>捕获时属性 Prop</h4><pre>{JSON.stringify(selected_node.prop ?? {}, null, 2)}</pre></article>
|
||||
<article><h4>捕获时状态 State</h4><pre>{JSON.stringify(selected_node.state ?? {}, null, 2)}</pre></article></div>
|
||||
</section> : <div className="taskflowTimelineSelectionHint">点击左侧任务名称,可查看该节点捕获时的 Prop / State。</div>}
|
||||
{model.snapshots.length ? <details className="taskflowTimelineSnapshots"><summary>时间线节点属性 / 状态({model.snapshots.length})</summary>
|
||||
<div>{model.snapshots.map(node => <article key={node.id}><strong>{taskflow_node_name(node.name, node)}</strong>
|
||||
<span>{node.owner?.label} · {node.owner?.component}</span>
|
||||
<pre>{JSON.stringify({prop: node.prop ?? {}, state: node.state ?? {}}, null, 2)}</pre></article>)}</div>
|
||||
</details> : null}
|
||||
<div className="taskflowInspectionLayout taskflowTimelineInspectionLayout">
|
||||
<Taskflow_Node_Inspector node={selected_node} execution={selected_execution}
|
||||
state={selected_state} on_clear={() => set_selected_node_id("")}/>
|
||||
<div className="taskflowTimelineViewport">
|
||||
<Timeline<Taskflow_Timeline_Item, Taskflow_Timeline_Group>
|
||||
key={timeline_view_key} groups={model.groups} items={model.items} keys={taskflow_timeline_keys}
|
||||
@@ -2072,7 +2178,11 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
lineHeight={line_height} itemHeightRatio={.68} itemVerticalGap={5}
|
||||
minZoom={10} maxZoom={Math.max(model.span * 20, 1000)} buffer={1}
|
||||
canMove={false} canResize={false} canChangeGroup={false}
|
||||
canSelect={false} stackItems={false} traditionalZoom
|
||||
canSelect stackItems={false} traditionalZoom
|
||||
onItemSelect={item_id => {
|
||||
const item = model.items.find(value => String(value.id) === String(item_id));
|
||||
if (item?.node_id) set_selected_node_id(item.node_id);
|
||||
}}
|
||||
timeSteps={taskflow_timeline_time_steps}
|
||||
groupRenderer={({group}) => group.title}>
|
||||
<TimelineHeaders className="taskflowTimelineHeaders">
|
||||
@@ -2081,7 +2191,7 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
<DateHeader unit="primaryHeader" labelFormat={() => "相对帧创建时刻的偏移"}/>
|
||||
<DateHeader labelFormat={([start]) => {
|
||||
const offset = (start.valueOf() - origin) / scale;
|
||||
return `+${offset.toLocaleString("zh-CN", {maximumFractionDigits: 3})} ms`;
|
||||
return `${offset >= 0 ? "+" : ""}${offset.toLocaleString("zh-CN", {maximumFractionDigits: 3})} ms`;
|
||||
}}/>
|
||||
</TimelineHeaders>
|
||||
<TimelineMarkers>
|
||||
@@ -2094,6 +2204,7 @@ function Taskflow_Timeline({graph, executions, frame, fullscreen: controlled_ful
|
||||
</TimelineMarkers>
|
||||
</Timeline>
|
||||
</div>
|
||||
</div>
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -2260,6 +2371,7 @@ function Taskflow_Frame_Pane({plot, components}: {plot: Plot; components: Compon
|
||||
on_fullscreen_view_change={switch_fullscreen_view} frame_navigation={frame_navigation}/></>
|
||||
: displayed_view_mode === "timeline" && graph
|
||||
? <Taskflow_Timeline graph={graph} executions={frame.executions} frame={frame}
|
||||
components={components} gallery_state={gallery_state}
|
||||
fullscreen={fullscreen_view === "timeline"} on_fullscreen_change={value => set_fullscreen_view(value ? "timeline" : null)}
|
||||
on_fullscreen_view_change={switch_fullscreen_view} frame_navigation={frame_navigation}/>
|
||||
: <div className="diagnosticEmpty"><strong>该帧没有所选 Taskflow 阶段</strong><span>切换到“多帧聚合拓扑”仍可查看其他帧中的同一业务阶段。</span></div>}
|
||||
|
||||
@@ -178,27 +178,23 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
|
||||
.taskflowTimelineFilters input { accent-color: #5ce4c2; }
|
||||
.taskflowHotThreshold input[type="range"] { width: 110px; }
|
||||
.taskflowHotThreshold input[type="number"] { width: 54px; padding: 3px 5px; color: #dce8f8; border: 1px solid #29435e; border-radius: 5px; background: #08111e; }
|
||||
.taskflowTimelineSelectionHint { padding: 8px 11px; color: #7189a8; border-left: 2px solid #29435e; font-size: 11px; }
|
||||
.taskflowSelectedCapture { overflow: hidden; border: 1px solid #37607f; border-radius: 9px; background: #091321; }
|
||||
.taskflowSelectedCapture > header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 9px 11px; border-bottom: 1px solid #203651; background: #0d1b2c; }
|
||||
.taskflowSelectedCapture > header > div { display: grid; gap: 3px; }
|
||||
.taskflowSelectedCapture > header strong { color: #dce8f8; }
|
||||
.taskflowSelectedCapture > header span { color: #7189a8; font-size: 10px; }
|
||||
.taskflowSelectedCapture > header button { padding: 5px 9px; color: #9db1cb; border: 1px solid #29435e; border-radius: 6px; background: #08111e; cursor: pointer; }
|
||||
.taskflowSelectedCapture > div { display: grid; grid-template-columns: minmax(180px, .7fr) repeat(2, minmax(240px, 1fr)); }
|
||||
.taskflowSelectedCapture article { min-width: 0; padding: 10px 11px; border-right: 1px solid #203651; }
|
||||
.taskflowSelectedCapture article:last-child { border-right: 0; }
|
||||
.taskflowSelectedCapture h4 { margin: 0 0 7px; color: #8fa7c4; font-size: 10px; font-weight: 600; }
|
||||
.taskflowSelectedCapture p { margin: 0 0 6px; color: #dce8f8; font: 11px/1.4 ui-monospace, monospace; }
|
||||
.taskflowSelectedCapture code { color: #6f87a6; font: 9px/1.4 ui-monospace, monospace; overflow-wrap: anywhere; }
|
||||
.taskflowSelectedCapture pre { max-height: 210px; overflow: auto; margin: 0; color: #b9cce3; font: 10px/1.45 ui-monospace, monospace; }
|
||||
.taskflowTimelineSnapshots { padding: 9px 11px; color: #91a5c0; border: 1px solid #29435e; border-radius: 8px; background: #091321; }
|
||||
.taskflowTimelineSnapshots > summary { cursor: pointer; }
|
||||
.taskflowTimelineSnapshots > div { display: grid; gap: 8px; margin-top: 9px; }
|
||||
.taskflowTimelineSnapshots article { display: grid; gap: 4px; padding: 8px; border: 1px solid #203651; border-radius: 7px; background: #08111e; }
|
||||
.taskflowTimelineSnapshots article strong { color: #dce8f8; }
|
||||
.taskflowTimelineSnapshots article span { color: #6f87a6; font-size: 10px; }
|
||||
.taskflowTimelineSnapshots pre { overflow: auto; margin: 3px 0 0; color: #b9cce3; font: 10px/1.45 ui-monospace, monospace; }
|
||||
.taskflowInspectionLayout { display: grid; grid-template-columns: 330px minmax(0, 1fr); min-width: 0; min-height: 0; }
|
||||
.taskflowNodeInspector { min-width: 0; max-height: 760px; overflow: auto; padding: 11px; border-right: 1px solid #213653; background: #091321; scrollbar-gutter: stable; }
|
||||
.taskflowNodeInspector > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 9px; padding-bottom: 10px; border-bottom: 1px solid #203651; }
|
||||
.taskflowNodeInspector > header > div { display: grid; gap: 4px; min-width: 0; }
|
||||
.taskflowNodeInspector > header strong, .taskflowNodeInspectorEmpty > strong { color: #dce8f8; font-size: 12px; line-height: 1.35; overflow-wrap: anywhere; }
|
||||
.taskflowNodeInspector > header span { color: #7189a8; font-size: 10px; }
|
||||
.taskflowNodeInspector > header button { flex: none; padding: 5px 8px; color: #9db1cb; border: 1px solid #29435e; border-radius: 6px; background: #08111e; cursor: pointer; }
|
||||
.taskflowNodeInspector > section { min-width: 0; padding: 10px 0; border-bottom: 1px solid #1b2d45; }
|
||||
.taskflowNodeInspector h4 { margin: 0 0 7px; color: #70d9c0; font-size: 10px; font-weight: 600; }
|
||||
.taskflowNodeInspector p, .taskflowNodeInspectorEmpty p { margin: 0 0 6px; color: #a9bbd1; font: 10px/1.5 ui-monospace, monospace; overflow-wrap: anywhere; }
|
||||
.taskflowNodeInspector code { color: #6f87a6; font: 9px/1.4 ui-monospace, monospace; overflow-wrap: anywhere; }
|
||||
.taskflowNodeInspector dl { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin: 10px 0 0; }
|
||||
.taskflowNodeInspector dl > div { min-width: 0; padding: 6px; border: 1px solid #203651; border-radius: 5px; background: #08111e; }
|
||||
.taskflowNodeInspector dt { color: #7189a8; font-size: 8px; }
|
||||
.taskflowNodeInspector dd { margin: 3px 0 0; color: #d0dced; font: 9px/1.3 ui-monospace, monospace; overflow-wrap: anywhere; }
|
||||
.taskflowNodeInspector pre { max-height: 230px; overflow: auto; margin: 0; color: #b9cce3; font: 9px/1.45 ui-monospace, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.taskflowNodeInspectorEmpty { display: grid; align-content: start; gap: 8px; color: #7189a8; }
|
||||
.taskflowError { margin: 0; padding: 10px 12px; color: #ff9bae; border: 1px solid #71334a; border-radius: 8px; background: #27101a; }
|
||||
.taskflowGraphSummary, .taskflowRuntimeSummary { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; margin: 0; }
|
||||
.taskflowGraphSummary > div, .taskflowRuntimeSummary > div { min-width: 0; padding: 10px; border: 1px solid #213653; border-radius: 9px; background: #0a1422; }
|
||||
@@ -239,9 +235,6 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
|
||||
.taskflowNodeLabel header i { flex: none; padding: 2px 5px; color: #74d8c0; border: 1px solid #2d675d; border-radius: 99px; font: 9px/1 ui-monospace, monospace; font-style: normal; }
|
||||
.taskflowNodeLabel code { color: #6f89aa; font: 9px/1.25 ui-monospace, monospace; overflow-wrap: anywhere; white-space: normal; user-select: text !important; }
|
||||
.taskflowNodeLabel span { color: #91a5c0; font: 9px/1.25 ui-monospace, monospace; }
|
||||
.taskflowNodeState { overflow: hidden; margin-top: 3px; border: 1px solid #29435e; border-radius: 5px; background: #07101c; pointer-events: auto; }
|
||||
.taskflowNodeState summary { padding: 4px 6px; color: #70d9c0; cursor: pointer; font: 9px/1.2 ui-monospace, monospace; user-select: text; }
|
||||
.taskflowNodeState pre { max-height: 86px; overflow: auto; margin: 0; padding: 6px; color: #b8cae0; border-top: 1px solid #213653; font: 8px/1.35 ui-monospace, monospace; white-space: pre-wrap; user-select: text; }
|
||||
.react-flow__edge-path { stroke: #557594; stroke-width: 1.5; }
|
||||
.react-flow__controls button { color: #dce8f8; border-color: #29435e; background: #101d2f; }
|
||||
.react-flow__minimap { border: 1px solid #29435e; background: #0a1422; }
|
||||
@@ -271,6 +264,8 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
|
||||
|
||||
.taskflowDagFullscreen { position: fixed; inset: 0; z-index: 10000; display: flex; flex-direction: column; width: auto; height: auto; min-width: 0; min-height: 0; margin: 0; overflow: hidden; border: 0; border-radius: 0; background: #07101c; }
|
||||
.taskflowDagFullscreen .taskflowDagToolbar { flex: none; }
|
||||
.taskflowDagFullscreen .taskflowInspectionLayout { flex: 1; min-height: 0; }
|
||||
.taskflowDagFullscreen .taskflowNodeInspector { max-height: none; }
|
||||
.taskflowDagFullscreen .taskflowDagFill { flex: 1; min-height: 0; cursor: zoom-out; }
|
||||
.taskflowDagSection:not(.taskflowDagFullscreen) .taskflowDag { cursor: zoom-in; }
|
||||
.taskflowDagFill { flex: 1; min-height: 0; }
|
||||
@@ -283,7 +278,7 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
|
||||
.taskflowTimelineGroupTitle.active { border-left-color: #5ce4c2; background: #132b3d; }
|
||||
.taskflowTimelineGroupTitle strong { color: #dce8f8; font: 10px/1.2 ui-monospace, monospace; }
|
||||
.taskflowTimelineGroupTitle span { color: #7189a8; font: 8px/1.2 ui-monospace, monospace; }
|
||||
@media (max-width: 900px) { .taskflowSelectedCapture > div { grid-template-columns: 1fr; } .taskflowSelectedCapture article { border-right: 0; border-bottom: 1px solid #203651; } }
|
||||
@media (max-width: 900px) { .taskflowInspectionLayout { grid-template-columns: 260px minmax(0, 1fr); } }
|
||||
.taskflowTimelineSidebarHeader { display: flex; align-items: center; height: 100%; padding: 0 10px; color: #91a5c0; background: #101d2f; font: 10px/1.2 ui-monospace, monospace; }
|
||||
.taskflowTimelineViewport .rct-sidebar { border-color: #29405e; background: #091321; }
|
||||
.taskflowTimelineViewport .rct-sidebar .rct-sidebar-row { overflow: visible; border-color: #1d304a; }
|
||||
@@ -296,6 +291,7 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
|
||||
.taskflowTimelineViewport .rct-item { border: 0; border-radius: 5px; box-shadow: none; }
|
||||
.taskflowTimelineViewport .rct-item .rct-item-content { padding: 0 5px; color: #e6f0fb; font: 8px/1 ui-monospace, monospace; }
|
||||
.taskflowTimelineItem-topology { background: #265979 !important; }
|
||||
.taskflowTimelineItem-lifecycle { background: #315172 !important; }
|
||||
.taskflowTimelineItem-queue { background: #8b5c24 !important; }
|
||||
.taskflowTimelineItem-observer_entry, .taskflowTimelineItem-observer_exit { background: #67468b !important; }
|
||||
.taskflowTimelineItem-body { background: #197462 !important; }
|
||||
@@ -304,12 +300,15 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
|
||||
.taskflowTimelineMarkerSubmitted { background: #e0a65d; }
|
||||
.taskflowTimelineMarkerFinished { background: #ef6a82; }
|
||||
.taskflowTimelineLegendTopology { border-color: #397da4 !important; background: #265979 !important; }
|
||||
.taskflowTimelineLegendLifecycle { border-color: #557da4 !important; background: #315172 !important; }
|
||||
.taskflowTimelineLegendQueue { border-color: #c0843e !important; background: #8b5c24 !important; }
|
||||
.taskflowTimelineLegendObserver { border-color: #9d74c8 !important; background: #67468b !important; }
|
||||
.taskflowTimelineLegendBody { border-color: #35ad92 !important; background: #197462 !important; }
|
||||
.taskflowTimelineLegendCompletion { border-color: #b75d79 !important; background: #78354b !important; }
|
||||
.taskflowTimelineFullscreen { position: fixed; inset: 0; z-index: 10000; display: flex; flex-direction: column; min-width: 0; overflow: hidden; border: 0; border-radius: 0; background: #07101c; }
|
||||
.taskflowTimelineFullscreen .taskflowTimelineViewport { flex: 1; max-height: none; min-height: 0; }
|
||||
.taskflowTimelineFullscreen .taskflowInspectionLayout { flex: 1; min-height: 0; }
|
||||
.taskflowTimelineFullscreen .taskflowNodeInspector { max-height: none; }
|
||||
.taskflowTimelineFullscreen .taskflowTimelineViewport { max-height: none; min-height: 0; }
|
||||
|
||||
|
||||
.componentCard { margin-bottom: 13px; overflow: hidden; border: 1px solid #213653; border-radius: 11px; background: #0a1422; }
|
||||
@@ -369,7 +368,7 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; }
|
||||
|
||||
/* Taskflow diagnostic nodes: text selection + explicit node copying. */
|
||||
.taskflowNodeLabel, .taskflowNodeLabel code, .taskflowNodeLabel span,
|
||||
.taskflowNodeLabel strong, .taskflowNodeState pre, .taskflowNodeState summary {
|
||||
.taskflowNodeLabel strong {
|
||||
-webkit-user-select: text !important;
|
||||
user-select: text !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user