1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
//! "Diff in parallel, push serially": the reconcile convention the data-driven
//! bridges (list reconcile, panel/DataContext reconcile) are built on.
//!
//! Noesis is thread-affine: every engine call has to happen on the one main
//! thread, inside [`NoesisSet::Apply`](crate::NoesisSet::Apply), while holding the
//! `NonSend` `NoesisRenderState`. That serial section is the scarcest resource in
//! the frame; anything done there blocks the whole schedule. So we keep it doing
//! the *minimum*: only the FFI pushes themselves.
//!
//! The split, then, is:
//!
//! 1. **Diff in parallel.** Ordinary `Update` systems (fully parallel, no Noesis
//! state in sight) read the ECS world (component queries, change detection,
//! the reconcile key being [`Entity`]) and compute a *desired delta*: the
//! minimal set of engine operations that would bring Noesis in line with the
//! world. They write that delta into plain `Send` storage: a per-entity
//! `NoesisDelta<Op>` component, or a resource. No FFI happens here, so it
//! parallelizes against the rest of the app for free.
//!
//! 2. **Push serially.** A single system in [`NoesisSet::Apply`](crate::NoesisSet::Apply),
//! holding `NonSendMut<NoesisRenderState>`, *drains* each precomputed delta and
//! pushes it through FFI. It does no diffing (the expensive part already ran
//! in step 1), so the serial section stays short.
//!
//! Why it matters here specifically: the list bridge must emit *minimal*
//! Add/Remove/Move/Update against the bound `ObservableCollection`, never
//! Clear/Reset (Reset destroys selection + scroll; "Reset is the enemy").
//! Computing that minimal diff is real work; doing it in the parallel `Update`
//! phase keeps it off the serial Apply critical path, and the `Op` queue that
//! crosses the boundary is exactly the minimal delta to replay.
//!
//! ```ignore
//! // Step 1 (parallel `Update`): diff the query into a per-panel delta.
//! fn diff_rows(
//! mut panels: Query<&mut NoesisDelta<RowOp>, With<UiPanel>>,
//! rows: Query<(Entity, &Item, &ListedIn), Changed<Item>>,
//! ) {
//! // …compute minimal Add/Remove/Move/Update keyed by row Entity,
//! // `delta.push(op)` for each. No Noesis calls.
//! }
//!
//! // Step 2 (serial `NoesisSet::Apply`): drain + push, no diffing.
//! fn push_rows(
//! mut panels: Query<(Entity, &mut NoesisDelta<RowOp>)>,
//! state: Option<NonSendMut<NoesisRenderState>>,
//! ) {
//! let Some(mut state) = state else { return };
//! for (panel, mut delta) in &mut panels {
//! if delta.is_empty() { continue; }
//! state.apply_row_ops_for(panel, delta.take());
//! }
//! }
//! ```
//!
//! The existing per-element bridges (`text`, `dp`, …) predate this split and
//! still diff inline inside their Apply system; they are cheap enough that it
//! does not matter. New, heavier reconcile bridges should follow the pattern above
//! and carry their delta in `NoesisDelta<Op>`.
use *;
/// A precomputed batch of pending engine operations for one entity, produced by a
/// parallel `Update` "diff" system and drained by the serial
/// [`NoesisSet::Apply`](crate::NoesisSet::Apply) "push" system. See the
/// [module docs](self) for the full convention.
///
/// `Op` is the bridge's own minimal-operation enum (e.g. a list's
/// Add/Remove/Move/Update). The type is a plain `Send` component (it holds no
/// Noesis handles), so it crosses the parallel→serial boundary safely.
// scaffolding; not yet wired into a bridge
pub
// scaffolding; not yet wired into a bridge