loadScene method

Future<Node> loadScene(
  1. String sourcePath, {
  2. String? package,
  3. AssetBundle? bundle,
  4. FsceneComponentRegistry? registry,
  5. SceneReloadCallback? onReload,
  6. Scene? applyStageTo,
})

Loads the scene whose source is sourcePath as a Node.

The composed document and its realized GPU resources (geometry, materials, textures) are cached per scene, so loading the same scene again instantiates a fresh node graph cheaply, sharing those resources.

Pass applyStageTo to also apply the document's stage render settings (environment, exposure, tone mapping, skybox, and sky lighting) to that scene, kept fresh across hot reloads. Pass a custom registry to realize app-defined component types, and onReload to re-apply per-instance customizations after a hot reload patches this instance in place.

Implementation

Future<Node> loadScene(
  String sourcePath, {
  String? package,
  AssetBundle? bundle,
  FsceneComponentRegistry? registry,
  SceneReloadCallback? onReload,
  Scene? applyStageTo,
}) async {
  final key = resolveKey(sourcePath, package: package);
  final assetBundle = bundle ?? rootBundle;

  // Reads the host document and expands any prefab instances, resolving
  // each referenced prefab by source path against this same registry,
  // collecting the asset keys touched into [seen]. (Lazily streamed
  // subtrees register their own assets when loaded; see [loadSubtree].)
  Future<SceneDocument> readComposed(Set<String> seen) async {
    final document = await _readDocument(key, assetBundle);
    return document.nodes.values.any((n) => n.instance != null)
        ? await composeSceneAsync(
            document,
            load: (ref) {
              final refKey = resolveKey(ref.key, package: package);
              seen.add(refKey);
              return _readDocument(refKey, assetBundle);
            },
          )
        : document;
  }

  Future<_SceneTemplate> loadTemplate() async {
    final seen = <String>{key};
    final document = await readComposed(seen);
    final resources = ResourceRealizer(document, bundle: assetBundle);
    await resources.preload();
    return _SceneTemplate(document, resources, seen);
  }

  final pending = _sceneTemplates[key] ??= loadTemplate();
  _SceneTemplate template;
  try {
    template = await pending;
  } catch (_) {
    // Don't cache a failed load; the next call retries.
    _sceneTemplates.remove(key);
    rethrow;
  }

  final root = await realizeSceneAsync(
    template.document,
    registry: registry,
    bundle: assetBundle,
    resources: template.resources,
  );
  if (applyStageTo != null) {
    await realizeStage(template.document, applyStageTo, bundle: assetBundle);
  }

  // Patch the live graph in place when the scene's `.fsceneb` (or one of
  // the prefab `.fsceneb`s it is composed from) changes (debug only; a
  // no-op registration in release). The dependency set is shared with the
  // coordinator and refreshed on each reload. The closure must hold the
  // root (and the stage scene) weakly: the registration owns the closure,
  // so a strong capture would keep every discarded instance alive forever,
  // accumulating registrations that re-patch dead graphs on each reload.
  var current = template.document;
  final dependencies = {...template.dependencies};
  final weakRoot = WeakReference(root);
  final weakStageScene = applyStageTo == null
      ? null
      : WeakReference(applyStageTo);
  if (onReload != null) _reloadCallbacks[root] = onReload;
  HotReloadCoordinator.instance.registerScene(
    root,
    assetKey: key,
    dependencies: dependencies,
    bundle: assetBundle,
    onReload: () async {
      final liveRoot = weakRoot.target;
      if (liveRoot == null) return;
      // The cached template no longer matches the edited assets; drop it
      // so future loads re-read them.
      _sceneTemplates.remove(key);
      final seen = <String>{key};
      final next = await readComposed(seen);
      dependencies
        ..clear()
        ..addAll(seen);
      final diff = await reloadScene(
        liveRoot,
        current,
        next,
        registry: registry,
        bundle: assetBundle,
      );
      current = next;
      final stageScene = weakStageScene?.target;
      if (diff.stageChanged && stageScene != null) {
        await realizeStage(next, stageScene, bundle: assetBundle);
      }
      _reloadCallbacks[liveRoot]?.call(liveRoot);
    },
  );
  return root;
}