reloadScene function
Future<SceneDiff>
reloadScene(
- Node liveRoot,
- SceneDocument oldDocument,
- SceneDocument newDocument, {
- FsceneComponentRegistry? registry,
- AssetBundle? bundle,
Patches the live graph rooted at liveRoot (as returned by realizeScene,
or loadScene) from oldDocument to newDocument in place.
liveRoot must currently match oldDocument; both documents must be fully
composed. Nodes whose id is unchanged keep their identity. Returns the diff
that was applied (empty when nothing changed).
Implementation
Future<SceneDiff> reloadScene(
Node liveRoot,
SceneDocument oldDocument,
SceneDocument newDocument, {
FsceneComponentRegistry? registry,
AssetBundle? bundle,
}) async {
final diff = diffScene(oldDocument, newDocument);
if (diff.isEmpty) return diff;
final reg = registry ?? defaultComponentRegistry();
final resources = ResourceRealizer(newDocument, bundle: bundle);
await resources.preload();
final context = RealizeContext(newDocument, resources: resources);
// Index the live graph by document id.
final live = <LocalId, Node>{};
void collect(Node node) {
final id = nodeFsceneId(node);
if (id != null) live[id] = node;
for (final child in node.children) {
collect(child);
}
}
collect(liveRoot);
final newParents = _parents(newDocument);
Node parentFor(LocalId id) => live[newParents[id]] ?? liveRoot;
// 1. Detach removed nodes.
for (final id in diff.removed) {
live.remove(id)?.detach();
}
// 2. Create the added nodes (bare), then wire their children and components,
// then attach those still without a parent under their document parent.
for (final id in diff.added) {
final spec = newDocument.nodes[id]!;
live[id] = tagNodeId(
Node(name: spec.name, localTransform: spec.transform.toMatrix4())
..layers = spec.layers
..excludeFromWindingParity = spec.excludeFromWindingParity
..visible = spec.visible,
id,
);
}
for (final id in diff.added) {
final spec = newDocument.nodes[id]!;
final node = live[id]!;
for (final childId in spec.children) {
final child = live[childId];
if (child != null && child.parent == null) node.add(child);
}
_setComponents(node, spec.components, reg, context);
}
for (final id in diff.added) {
final node = live[id]!;
if (node.parent == null) parentFor(id).add(node);
}
// 3. Update surviving nodes.
for (final change in diff.changed) {
final node = live[change.id]!;
final spec = newDocument.nodes[change.id]!;
if (change.transform) {
node.localTransform = spec.transform.toMatrix4();
node.excludeFromWindingParity = spec.excludeFromWindingParity;
}
if (change.name) node.name = spec.name;
if (change.layers) node.layers = spec.layers;
if (change.visible) node.visible = spec.visible;
if (change.reparented) {
node.detach();
parentFor(change.id).add(node);
}
if (change.components) {
_setComponents(node, spec.components, reg, context);
}
}
// 4. Rebuild skins on added nodes and on nodes whose skin changed. The
// renderer rebuilds the joints texture from the bound skin each frame, so
// swapping the skin is enough.
final skinNodes = <LocalId>{
for (final id in diff.added)
if (newDocument.nodes[id]!.skin != null) id,
for (final change in diff.changed)
if (change.skin) change.id,
};
for (final id in skinNodes) {
final node = live[id];
if (node == null) continue;
final spec = newDocument.skins[newDocument.nodes[id]!.skin];
node.skin = spec == null ? null : buildSkin(newDocument, spec, live);
}
// 5. Rebuild and re-bind animations. Clips created from the old animations
// keep playing (matched by name); rest poses come from the document so a
// node frozen mid-playback is not captured at its animated pose.
if (diff.animationsChanged) {
final animations = [
for (final spec in newDocument.animations.values)
buildAnimation(newDocument, spec, live),
].whereType<Animation>().toList();
liveRoot.reloadParsedAnimations(
animations,
restPoseOf: (node) {
final id = nodeFsceneId(node);
return id == null ? null : newDocument.nodes[id]?.transform.toMatrix4();
},
);
}
return diff;
}