Streaming Deferred Prefabs & Scene GameObjects

Saveable Component (Remember Component) Load Scheduling and Change Detection

Streaming data allows a scene to load within seconds where it would normally take several minutes. The video below demonstrates the faster loading of a scene with over 2,800 GameObjects, each with a RememberGameObject and RememberTransform component attached. These track positions, rotations, active states, destroyed states, and more. With this streaming optimization, the scene now loads in about 10 seconds, compared to the previous 3–4 minutes.

Since Version 1.6.21 Crystal Save SaveableComponents expose three complementary optimisation tools:

  • Skip Saving When Unchanged lets components avoid re-serialising data when a change detector concludes the payload matches the last stored snapshot (for example, RememberTransform and RememberCustomComponents track a cached baseline and bail out when they haven’t drifted beyond their tolerances).

  • Load Priority is a 0–100 slider in the inspector that orders components during the initial restoration pass (higher values are processed first).

  • Defer Until Requested stages component payloads in a deferred queue that you can flush later by scene, GameObject, component ID or explicit unique IDs.

Whenever a component serialises bytes, the save pipeline records its load priority, deferral flag and (when applicable) remembered home scene inside ComponentDataMetadata, so downstream loads and streaming helpers can respect those decisions.

These features deliver the biggest gains when the SaveSystem relies on fast lookups instead of whole-scene scans. Enable the lookup caches and disable the legacy startup scan in Save Settings so component and GameObject IDs resolve through the in-memory dictionaries that Load Priority and deferral expect.

Configuring SaveableComponents

Skip Saving When Unchanged

  1. Select a SaveableComponent (Remember X Component) that supports change detection (all built-in “Remember …” helpers that expose a Save Optimization group, such as transforms, custom components, lights, etc.).

  2. Tick Skip Saving When Unchanged. The component will capture a baseline snapshot in Awake, compare the next SerializeComponentData() payload against that cache, and only persist changes when something actually moved or mutated.

Watch precision-sensitive data. Modules such as RememberTransform use tight but non-zero tolerances (0.0001f) when deciding whether values changed. If you rely on sub-millimetre or high-frequency jitter, leave the optimisation disabled for that component.

Load Priority

  1. In the component inspector expand Load Scheduling and drag the Load Priority slider. Values closer to 100 ensure the component deserialises in the first batch during ComponentManager.ApplyComponentData, while lower numbers sink toward the back of the queue.

  • You can also assign the property at runtime via myComponent.LoadPriority = 80; thanks to the public accessor on SaveableComponent.

Use high priorities for gameplay-critical systems (player stats, quest state) and reserve the low end for cosmetic helpers or streaming content.

Defer Until Requested

  1. Toggle Defer Until Requested in the inspector or set myComponent.DeferLowPriorityUntilRequested = true; from code.

During a load, ComponentManager writes the component’s metadata, partitions the dataset into immediate and deferred entries, and enqueues the latter grouped by home scene (or global scope when none is defined). You also receive an OnDeferredComponentsQueued callback with the affected scene keys.

  • Deferred payloads stay dormant until you explicitly ask for them through the public ProcessDeferred… methods (by scene, by GameObject, by component ID, etc.).

Processing deferred component data

ComponentManager exposes a flexible API for flushing the queue once the relevant content is visible:

// Example: when an additive scene finishes loading
ComponentManager.Instance.ProcessDeferredComponentsForScene("DungeonInterior");

// Example: when a streamed prefab cluster moves within range
ComponentManager.Instance.ProcessDeferredComponentsByUniqueIDs(idsToReveal);

You can inspect pending work through HasDeferredComponents, GetDeferredSceneKeys, or PeekDeferredComponentsForScene, then respond to OnDeferredComponentsQueued to integrate with your own streaming logic.

If you prefer an automated solution, drop a DeferredPrefabRadiusStreamer in your scene. It listens to both prefab and component deferral queues and processes entries once the player comes within range, coordinating with the same APIs shown above.

Compatibility with other SaveManager features

  • Targeted restores (RestoreSingleGameObject…) – These helpers gather component payloads directly from SaveData.ComponentsData and call ComponentManager.ApplyComponentDataToObject(..., forceDuplicateLoads: true). They bypass metadata entirely, so Load Priority and Defer Until Requested settings do not delay the targeted restore, and skip-saving simply means “no data → nothing to overwrite,” which is expected when no changes were captured.

  • Destroyed-object workflows (DestroyWithSnapshot, RestoreDestroyedGameObject, RestoreDestroyedPrefabByAssetID) – When an object is snapshotted before destruction, SaveManager collects all current component bytes and stores them under DestroyedObjectData. Restoration later fetches those bytes via the variant-aware lookup, without consulting deferred metadata. Skip-saving therefore only omits entries when nothing changed, and load scheduling flags have no effect on this path. Prefab instantiation still invites deferred components to apply themselves once the spawned GameObject registers, so defers continue to work for newly created instances.

  • Snapshot-driven scene handoff (LoadSceneAfterSnapshotAndPopulatePendingPrefabsAsync) – The helper collects a transient save, instantiates prefabs, and runs the same ApplyComponentData pass used during a normal load. Immediate entries obey the Load Priority sort order; deferred entries remain queued, so remember to call ComponentManager.ProcessDeferredComponents... (or rely on the radius streamer) once the new scene should come alive.

When (not) to enable each optimisation

  • Skip Saving When Unchanged – Great for large structures that rarely change (terrain, complex custom components). Avoid when you need per-frame precision beyond the built-in tolerances or when downstream systems expect “last written timestamp” semantics regardless of actual data differences.

  • Load Priority – Use to guarantee critical state (player data, quest state, inventory) becomes available before dependent scripts run. Remember it only affects the main load pipeline—targeted restores ignore the priority sort.

  • Defer Until Requested – Ideal for world streaming, large additive scenes, or cosmetic content that can appear on demand. Pair it with radius-based processing or custom triggers. Avoid deferral for components that gate scene initialization, cut-scenes, or logic that must exist the moment a scene finishes loading.

Troubleshooting & best practices

  • Use ComponentManager.GetDeferredSceneKeys() and PeekDeferredComponentsForScene() to audit staged work during debugging sessions.

  • If legacy saves are missing metadata, the loader falls back to priority 50 and “apply immediately,” so schedule a fresh save after enabling these features to populate the new metadata blocks.

  • Hook OnDeferredComponentsQueued to surface UI notifications or to kick off your streaming logic as soon as new work arrives.

Streaming instantiated SaveablePrefabs (Remember Prefab)

Crystal Save can stagger the restoration of saved prefabs so that they are only instantiated when a player gets close enough, reducing up-front load spikes. This feature is especially useful for scenes with a high number of Remember Prefab instantiations, since a normal load slot call would attempt to load all prefabs at once, potentially causing long load times if Crystal Save has to deserialize thousands of objects.

In this context, the DeferredPrefabRadiusStreamer component drives this streaming workflow by listening for newly deferred prefabs, caching them per scene, and requesting instantiation once they enter a configurable radius around a chosen transform.

Video of included Demo Scene (StreamingDeferredPrefabs):

Feature overview

Deferred streaming builds on Crystal Save’s prefab deferral pipeline. When prefab data is collected, entries flagged with Defer Until Requested are split into a deferred batch;

PrefabManager queues those entries, groups them by scene (with __GLOBAL__ as a catch-all key), and raises OnDeferredPrefabsQueued so listeners can react.

The queued data retains load priorities and home-scene metadata, making it possible to revive only the objects that matter near the player.

How to use?

  • Select your prefab root and ensure it has the SaveablePrefab component (Remember Prefab).

  • Open the inspector’s Advanced Settings foldout. In the Remember Prefab custom inspector, expand Advanced Settings to reveal the less-common options that control persistence behavior, load scheduling, and performance.

  • Scroll to Load Scheduling and enable Defer Until Requested. This checkbox sits alongside the load-priority slider; switching it on tells Crystal Save to bypass the initial restoration pass and leave the prefab in the deferred queue until a manual trigger or DeferredPrefabRadiusStreamer requests it.

  • Save or apply your prefab changes. Once the setting is enabled, the prefab’s saved state will remain deferred after loading, and the radius streamer (or any other trigger-driven call) can pull it in exactly when your gameplay requires.

With this flag active, you can rely on your trigger-based workflow—or the out-of-the-box radius streamer—to bring deferred prefabs online right when the player approaches, keeping initial load bursts under control.

How DeferredPrefabRadiusStreamer works

Lifecycle hooks

The streamer registers for SceneManager.activeSceneChanged and SaveManager.Initialized when it is enabled, ensuring that it reconnects to the active PrefabManager, warms the local cache, and immediately schedules a distance evaluation whenever the save system is ready or the active scene changes.

It safely detaches from events and clears its cache on disable so there are no dangling subscriptions.

Cache hydration and maintenance

Newly deferred batches trigger HandleDeferredPrefabsQueued, which notes the affected scenes and rebuilds per-scene caches by calling PrefabManager.PeekDeferredPrefabsForScene. Each cache is keyed by the prefab’s instance ID, with an empty-string scene key representing global deferrals. Whenever instantiation happens or a scene becomes active, the component refreshes its snapshot so stale entries are pruned.

Distance-based evaluation

On a configurable interval, the streamer builds a lookup that merges global and scene-specific caches, removes any prefabs already instantiated, and compares each pending prefab’s world-space position against the squared activation radius centered on the configured player transform.

Prefabs whose parents are not yet spawned are recursively resolved: if a parent exists in the scene or is already live, the component composes the proper world transform before measuring distance. It can also seed the spawn list with ancestor prefabs to keep parent-child hierarchies intact.

Requesting instantiation

When one or more prefabs fall within range, the streamer calls PrefabManager.ProcessDeferredPrefabsByInstanceIDs, handing over the exact IDs to instantiate. The manager dequeues matching entries across scenes, sorts them by load priority, and launches the restore coroutine that rebuilds those objects.

Configuration and prerequisites

  • Player transform: Assign the transform that should act as the distance origin. If it is missing, streaming never runs.

  • Prefab manager override: Optionally supply a specific PrefabManager; otherwise the component resolves SaveManager.Instance.GetPrefabManager at runtime.

  • Activation radius: Controls how far from the player prefabs must be before they are requested. The check uses squared magnitude for efficiency.

  • Refresh interval: Sets how often distance evaluations run; zero forces a check every frame.

Compile-time symbols: Both MEMORYPACK and ARAWN_REMEMBERME must be defined for the streamer (and its supporting data types) to compile.

Why you want to use it

  • Smoother load times: Objects marked for deferral stay in PrefabManager’s queue and are revived only when a player can actually interact with them, reducing the initial instantiation burst after loading a save.

  • Scene-aware streaming: Caches are segmented by scene and global context, so additive scene setups can stream content independently while still honoring cross-scene prefabs.

  • Hierarchy integrity: Recursive transform resolution and parent clustering mean children spawn next to their parents even if those parents are still deferred when the distance check runs.

Why you might skip it

  • Always-on worlds: If gameplay or AI systems rely on distant prefabs being active regardless of player proximity, the streamer will keep them deferred until the player enters the radius, delaying their availability.

  • Update budget concerns: The component runs periodic evaluations (potentially every frame), so extremely tight CPU budgets or very large deferred sets might prefer a bespoke trigger instead of radius polling.

Trigger-driven deferred streaming

Every prefab flagged with DeferLowPriorityUntilRequested remains in the deferred queue until you explicitly ask the PrefabManager to revive it, making the feature a good fit for bespoke triggers such as quest milestones, level-streaming volumes, or scripted cutscenes.

Core API surface

PrefabManager exposes several public entry points so you can flush deferred content on demand:

  • ProcessDeferredPrefabs() restores everything that is still deferred across all scenes in priority order.

  • ProcessDeferredPrefabsForScene(sceneName) limits the restore to a single scene key (empty string targets the global queue).

  • ProcessDeferredPrefabsForAsset(prefabAssetID) and ProcessDeferredPrefabByUniqueID(instanceID) let you target a single prefab asset type or runtime instance, respectively.

  • ProcessDeferredPrefabsByInstanceIDs(instanceIDs) gives you fine-grained control to spawn an arbitrary subset—useful when a trigger volume decides which specific objects should appear.

All of these helpers return the instantiation coroutine, so your systems can await completion or chain additional work afterwards.

Building smart triggers

To decide when and what to request, you can inspect the queue at runtime:

  • GetDeferredSceneKeys() lists every scene that still has deferred entries, while PeekDeferredPrefabsForScene(sceneName) returns a non-destructive snapshot you can filter (e.g., by metadata stored in SaveablePrefabData).

  • Subscribe to OnDeferredPrefabsQueued to be notified whenever new entries arrive, cache the IDs you care about, and trigger the relevant ProcessDeferred… call once your gameplay condition is met.

Because the API mirrors what DeferredPrefabRadiusStreamer does internally, you can port its logic into bespoke systems—for example, gather the deferred IDs that belong to a dungeon wing when the player pulls a lever, then call ProcessDeferredPrefabsByInstanceIDs so the wing animates into place without ever polling distances.

Designer-friendly integrations

If your team prefers visual scripting, Crystal Save ships ready-made nodes/actions that wrap these APIs:

  • Game Creator instructions cover the global, scene, asset, and unique-ID variants, with optional one-frame delays to let destruction events settle before instantiating.

Equivalent Playmaker actions offer the same set of controls, enabling trigger volumes, FSM transitions, or quest states to issue the restore calls without writing custom C#.

When to pick this approach

Choose manual trigger-driven streaming whenever spawn timing is dictated by narrative beats, puzzle states, or complex spatial logic. You retain the memory/performance advantages of deferred prefabs, but avoid the periodic polling cost of the radius streamer and ensure distant systems can light up the moment your gameplay condition fires.

Benefits recap

  • Reduced memory churn: Deferral keeps non-essential prefabs out of memory until needed, and the streamer prunes cached entries once they are instantiated to avoid duplicate work.

  • Consistent player experience: By tying activation to a radius around a meaningful transform (typically the player), you maintain the illusion of a populated world without paying the cost for unseen areas.

  • Extensible events: Because the streamer listens to OnDeferredPrefabsQueued, other systems can piggyback on the same event stream to drive UI or analytics about pending world content.

Use DeferredPrefabRadiusStreamer whenever you want Crystal Save’s deferred prefabs to feel invisible to players—appearing just in time as they explore—while keeping configuration simple through radius and interval tuning.

Flowchart

Last updated