reloadScene function

Future<SceneDiff> reloadScene(
  1. Node liveRoot,
  2. SceneDocument oldDocument,
  3. SceneDocument newDocument, {
  4. FsceneComponentRegistry? registry,
  5. 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;
}