Skip to main content

Runtime Scene Instancing

This guide describes the current development API, including callbacks that receive rhea::ErrorReport. These signatures differ from v0.1.1.

Rhea can load a .scene file as a reusable resource and create one or more runtime instances from it. This is useful for characters, enemies, props, projectiles, and other objects that share a configured structure.

The important distinction is:

  • A scene resource is the parsed and loaded definition of a .scene file.
  • A scene instance is a runtime object tree created from that definition.

Loading a scene resource does not automatically place a new object in the world. It makes the definition available for instantiation.

Defining a reusable scene​

The root section describes the object that will be created. Child sections describe its configured components and units.

For example, cube2.scene defines a reusable item:

[scene]
name="cube2"
type="//meworla/item"

[transform]
type="//meworla/transform"

[mesh]
type="//meworla/meshcomponent"
meshRef="//local/resources/cube2_mesh"

[physics]
type="//meworla/physicsdatacomponent"
mass=20.0f

[physics/collider]
type="//meworla/collidercomponent"
shape="box"

Keep reusable templates close to their local origin. Placement normally belongs to the scene that instantiates the template, rather than to the template itself.

Instantiating a template from another scene​

Use //meworla/sceneinstance to declare an instance inside another .scene file:

[cube2]
type="//meworla/sceneinstance"
source="//local/cube2.scene"
position=(130.0f, 2.0f, 15.0f)

SceneInstance is an item and therefore acts as the placement anchor. The referenced scene is instantiated below it. Its local transforms are evaluated relative to that anchor.

This keeps responsibilities clear:

  • cube2.scene defines what the object is.
  • The containing scene defines where this particular instance is placed.

Instantiating from C++​

SceneResource::spawn() builds a fresh instance, attaches it to the supplied parent, resolves its runtime references, and calls ready() after the instance has been attached.

auto* resource = doCast<rhea::SceneResource>(
app->getUnitTree()->find("//local/gnar.scene"));

if (!resource) {
// Load the scene resource first; see the example below.
return;
}

resource->spawn(target_scene, "player", [](rhea::ErrorReport report, rhea::Unit* instance) {
if (report.isError()) {
// Report all entries in report.errors; cancellation is also a non-success result.
return;
}

// The instance is attached and ready here.
});

Spawning may complete immediately or later because referenced resources can still need loading or preparation. Continue dependent work from the completion handler, not from code following spawn(). Keep the parent and application alive until completion, and capture temporary data by value.

Building without attaching​

buildInstance() creates a complete but detached object tree and does not call ready():

rhea::Resource::InstanceRequest request;
request.name = "preview";
request.basepath = "//local/previews/preview";

resource->buildInstance(request, [](rhea::ErrorReport report, rhea::Unit* instance) {
if (report.isError()) {
// Report the diagnostics; no usable instance is returned.
return;
}

// Inspect the detached tree. This example does not retain it.
instance->destroy();
});

Use this lower-level operation only when the caller must control attachment and activation. For this request, basepath is the intended full runtime root path, including the instance name. If retaining the tree, attach it to the intended parent, check attachment success, and call ready() only after attachment. Destroy a detached tree if it will not be used. Normal gameplay code should prefer spawn().

Loading before spawning​

The lookup example above assumes the template is already loaded. To load it explicitly, check the loading report and then the separate spawn report. These callbacks use different argument orders:

app->loadResource("//local/gnar.scene", "//meworla/scene",
[target_scene](rhea::Component* component, rhea::ErrorReport load_report) {
if (load_report.isError()) {
// Display or log load_report.errors.
return;
}

auto* resource = doCast<rhea::SceneResource>(component);
if (!resource) {
// The loaded resource is not a scene definition.
return;
}

resource->spawn(target_scene, "player",
[load_report = std::move(load_report)]
(rhea::ErrorReport spawn_report, rhea::Unit* instance) mutable {
load_report.merge(std::move(spawn_report));
if (load_report.isError()) {
// Display or log the accumulated diagnostics.
return;
}

// instance is attached and ready.
});
});

The examples assume the relevant Application, SceneResource, and UnitTree headers and a valid parent. Include <Rhea/Error.h> and <utility> for reports and moves. A pointer captured by value does not extend the target object's lifetime.

Handling instantiation failures​

Loading, building, preparing, and attaching an instance are separate stages. A file can load successfully but fail to instantiate because types are not registered, references are invalid, or required dependencies cannot be loaded.

ErrorReport::isError() includes both failure and cancellation. Iterate report.errors to present the individual diagnostics; use isCanceled() when cancellation needs different handling. Preserve resource addresses and context when forwarding reports. The source file is available from SceneResource::getSourceResourceId() and is independent of the runtime instance name.

The runtime can report multiple unknown types and dependency failures, but this is not an exhaustive validation pass: a failed prerequisite can prevent later checks. The current report API has no defined success-with-warnings mechanism. See Error handling for status semantics, aggregation, diagnostic formatting, and migration guidance.

Preparing a scene for synchronous spawning​

Some systems must create an object from a synchronous callback. They can declare the scene with ResourceUsage::Prepare during their own reference collection.

Preparation builds and retains a detached, non-ready prototype and loads the dependencies discovered from its configured units. Once preparation has completed, spawnPrepared() copies the prototype, attaches the copy, and makes it ready synchronously.

The basic controller uses this for a default character:

[controller]
type="//meworla/basiccontroller"
default_character="//local/gnar.scene"

Its resource reference is conceptually equivalent to:

refs.push_back({
"default_character",
"//meworla/scene",
getValue("default_character"),
ResourceUsage::Prepare
});

Once preparation has completed successfully, the controller can spawn the character synchronously. You can also prepare explicitly with character_scene->prepare([](rhea::ErrorReport report) { /* check completion */ });; do not spawn before its callback reports success.

rhea::ErrorReport report;
rhea::Unit* character = character_scene->spawnPrepared(
target_scene,
"player",
&report);

if (report.isError() || !character) {
// Handle failure before using character.
return;
}

The optional report pointer is a synchronous output parameter. The method clears it at entry and writes the result of this call; the caller owns the report. Do not call spawnPrepared() before preparation has completed. Prefer asynchronous spawn() unless a synchronous integration point requires preparation.

Resource usage modes​

Components report referenced resources through getReferences(). The usage mode tells the scene runtime what must happen before the owning instance becomes ready.

UsageMeaning
LoadLoad the resource data.
PreparePrepare the resource for later synchronous use.
EnsureReadyCreate or reuse the resource's canonical ready instance.
InstantiateCreate a new instance below the referencing unit.

For example, SceneInstance reports its source as Instantiate. A resource collection reports referenced resource scenes as EnsureReady:

[resources]
type="//meworla/resources"
path="//dragonstemple/resources.scene"

This preserves the established resource-scene behavior: the resource collection becomes available at its canonical location and is not duplicated for every reference.

Paths and names​

The path of a scene resource and the path of an instance are separate concepts.

  • //local/gnar.scene identifies the loaded template resource.
  • The parent and instance name passed to spawn() determine the runtime path of the new object.
  • References inside the template are resolved using the instance request's base path.

The template resource does not need to live below the world or controller that uses it. Treat resource paths as stable identifiers and runtime parentage as object ownership.

Dependency discovery​

Parsing alone cannot discover every dependency because dependency rules belong to the configured component types. During preparation or instantiation, Rhea builds the configured object tree and collects references through each unit's getReferences() implementation.

Consequently, custom components must report all resources needed before ready(). A missing reference can otherwise produce timing-dependent behavior when several resources load in parallel.

Explicit depends declarations in scene files are not currently part of this workflow. They may be added later for dependencies that cannot be expressed by the type system, but getReferences() remains the authoritative mechanism for typed component dependencies.

For a reusable character:

  1. Define the character and its configured components in a dedicated .scene file.
  2. Keep its root transform local unless the template intentionally contains an offset.
  3. Report meshes, materials, animations, and other dependencies from the relevant components.
  4. Use SceneInstance for declarative placement in another scene.
  5. Use asynchronous spawn() for runtime gameplay creation.
  6. Use Prepare and spawnPrepared() only where a synchronous caller makes them necessary.

This model allows the same scene definition to serve as a declarative child, an asynchronously spawned gameplay object, or a prepared synchronous prototype without conflating the loaded resource with its runtime instances.

Asset reference paths​

See Asset References for project-relative paths, local object references, typed reference registration, and importer dependencies. Explicit realm references in this guide remain valid when those realms are configured.