encodeTree method
Encodes tree for the wire, or returns null when the exposed semantics are
unchanged since the last send (so a dirty frame that didn't actually alter
the accessible tree costs zero bytes).
update names the nodes whose wire form may have changed AND carries the
live nodes themselves (nextNodesById); on a patch frame the encoder
redacts and re-serializes ONLY those, straight from the live tree — never
building a full SemanticInspectionSnapshot. The owner's node equality
compares exactly the fields the wire carries — including bounds and child
order — so a node pushed to a new position (bounds shift, no model rebuild)
still lands in the update. A debug-only oracle re-runs the full flatten
every frame and asserts the incrementally-maintained _sent still equals
the ground truth, so any gap between the update and the wire form fails
loudly in tests.
Implementation
Uint8List? encodeTree(SemanticTree tree, {SemanticTreeUpdate? update}) {
_rejection = null;
final rootId = sanitizeForDisplay(tree.root.id.value);
if (!_sentFull) {
final flat = _flattenTree(tree);
return _encodeFull(flat, rootId: rootId, flattenedNodes: flat.length);
}
if (update == null) {
// No update: fall back to the full flatten + structural compare.
final flat = _flattenTree(tree);
final encoded = _encodeFlattenedPatch(
flat,
rootId: rootId,
flattenedNodes: flat.length,
);
if (_sentFull) _assertTreeOracle(tree);
return encoded;
}
// O(changed): redact + serialize only the nodes the update names. Stage the
// edits without touching [_sent]; validation and byte accounting happen
// before commit so a rejected state can only force the NEXT send to FULL,
// never leave a PATCH base the peer did not receive.
var flattened = 0;
final staged = <String, Map<String, Object?>>{};
final touched = <String>{};
for (final id in update.added.followedBy(update.updated)) {
final node = update.nextNodesById[id];
if (node == null) continue; // named-changed but gone from the tree.
flattened++;
final json = SemanticInspectionNode.flattenLiveNode(node);
final wireId = json['id']! as String;
touched.add(wireId);
final prior = staged[wireId] ?? _sent[wireId];
if (prior == null || !_jsonEquals(prior, json)) {
staged[wireId] = json;
}
}
final removed = <String>{};
for (final id in update.removed) {
final wireId = sanitizeForDisplay(id.value);
// A changed node may carry this same wire id — a duplicate, or two raw
// ids that sanitize alike — in which case it is LIVE. Do not delete it.
if (touched.contains(wireId)) continue;
if (_sent.containsKey(wireId)) removed.add(wireId);
}
final encoded = _finishPatch(
rootId: rootId,
staged: staged,
removed: removed,
flattenedNodes: flattened,
);
if (_sentFull) _assertTreeOracle(tree);
return encoded;
}