Cross-world ECS access — global world from scene systems, propagation, bridge components, PersistentEntities. Use when scene systems need global-world state, implementing propagation, or using ISceneIsCurrentListener.
Scanned 5/27/2026
Install via CLI
openskills install decentraland/unity-explorer---
name: cross-world-ecs-access
description: "Cross-world ECS access — global world from scene systems, propagation, bridge components, PersistentEntities. Use when scene systems need global-world state, implementing propagation, or using ISceneIsCurrentListener."
user-invocable: false
---
# Cross-World ECS Access
## Sources
- `docs/architecture-overview.md` — World architecture (global vs scene worlds)
- `docs/development-guide.md` — ECS system design, component mutation rules
---
## Injection Chain
The global world reaches scene systems through a plugin injection chain:
1. `DynamicWorldContainer` creates the global `World` and `GlobalWorldFactory`
2. `StaticContainer` instantiates `IDCLWorldPlugin` instances, passing the global world to plugin constructors
3. Plugin stores `globalWorld` as a field
4. On scene creation, `InjectToWorld` passes `globalWorld` to system constructors
> **Note:** `ECSWorldInstanceSharedDependencies` provides scene-specific metadata (scene data, partition, CRDT writer) but does NOT carry the global world reference. The global world must be passed through the plugin constructor.
---
## Two Access Patterns
### Pattern A: Direct Global World Access
For O(1) reads/writes on known global entities. The scene system stores `globalWorld` and the target entity at construction, then calls `Get`/`TryGet` directly in queries. Best when the target entity is known at construction time (player input, camera state, settings).
### Pattern B: CRDT Bridge
For indirect sync without direct world references. Uses `IECSToCRDTWriter` to serialize data back to the scene runtime. Best for scene-to-runtime communication and CRDT-synchronized data. See **sdk-component-implementation** skill for `PutMessage`/`AppendMessage`/`DeleteMessage` patterns.
---
## PersistentEntities
Well-known entities created once per scene world, guaranteed alive for the world's lifetime. Received via `InjectToWorld`'s `in PersistentEntities persistentEntities` parameter.
```csharp
public struct PersistentEntities
{
public readonly Entity Player; // scene-local player entity
public readonly Entity Camera; // scene-local camera entity
public readonly Entity SceneRoot; // root entity (modifiable by scene creator)
public readonly Entity SceneContainer; // container of root (internal use only)
}
```
---
## Bridge Components
### PlayerCRDTEntity (global world)
Lives on global-world avatar entities. Holds references to the scene the player is assigned to and the corresponding scene-world entity.
```csharp
public struct PlayerCRDTEntity : IDirtyMarker
{
public CRDTEntity CRDTEntity { get; }
public ISceneFacade? SceneFacade { get; private set; }
public Entity SceneWorldEntity { get; private set; }
public bool AssignedToScene => SceneFacade != null;
public void AssignToScene(ISceneFacade sceneFacade, Entity sceneWorldEntity) { ... }
public void RemoveFromScene() { ... }
}
```
### PlayerSceneCRDTEntity (scene world)
Lives on scene-world entities that represent a player. Holds the CRDT entity ID for writing back to the scene runtime.
```csharp
public struct PlayerSceneCRDTEntity : IDirtyMarker
{
public readonly CRDTEntity CRDTEntity;
}
```
**Relationship:** A global `PlayerCRDTEntity` points to a scene via `SceneFacade` + `SceneWorldEntity`. The scene entity has a `PlayerSceneCRDTEntity` pointing back via `CRDTEntity`. Propagation systems use this bidirectional link to sync data across worlds.
---
## Propagation Systems
- **Global-to-Scene (`PlayerTransformPropagationSystem`):** Runs in the global world, queries global entities, writes into scene worlds via `playerCRDTEntity.SceneFacade.EcsExecutor.World`.
- **Scene-to-Global:** Scene systems write to the global world directly using Pattern A. The system reads scene-world SDK components and writes the result to `globalWorld.Get<T>(playerEntity)`.
---
## ISceneIsCurrentListener Safety Pattern
When a scene system modifies global state, it must only do so while the scene is current. Implement `ISceneIsCurrentListener` to:
- Guard every `Update()` with `sceneStateProvider.IsCurrent`
- Re-apply state in `OnSceneIsCurrentChanged(true)`
- Reset to defaults in `OnSceneIsCurrentChanged(false)`
- Register via `sceneIsCurrentListeners.Add(system)` in the plugin
> `ISceneIsCurrentListener.OnSceneIsCurrentChanged` is called even if the scene has stopped with an error, ensuring global state is always cleaned up.
---
## Safety Rules
> See CLAUDE.md SS5 for full mutation rules.
1. **Use `ref var` for mutations, never `var` alone** — `var` copies the component; changes are lost
2. **No structural changes after obtaining `ref`** — `World.Add`/`World.Remove` invalidates outstanding `ref`/`in`/`out` pointers. Complete all reads/writes first, then apply structural changes
3. **Prefer `TryGet` for safety on known entities** — avoids exceptions when the component may not exist
4. **Guard global writes with `IsCurrent` check** — always check `sceneStateProvider.IsCurrent` in `Update()` and implement `ISceneIsCurrentListener` to reset on scene exit
5. **`ECSWorldInstanceSharedDependencies` provides scene metadata, NOT global world** — the global world must be injected through the plugin constructor
6. **Register for cleanup** — systems that modify global state should implement `IFinalizeWorldSystem` and be added to `finalizeWorldSystems` so global state is reset on world disposal
---
## Detailed Reference
For detailed code examples, see [reference.md](reference.md).
---
## Cross-References
- **ecs-system-and-component-design** — `ref var` mutation rules (CLAUDE.md SS5), component cleanup lifecycle, system design patterns
- **plugin-architecture** — `InjectToWorld` signature, `ECSWorldInstanceSharedDependencies`, plugin constructor injection chain
- **sdk-component-implementation** — `IECSToCRDTWriter` for CRDT bridge pattern (`PutMessage`/`AppendMessage`/`DeleteMessage`)
No comments yet. Be the first to comment!