Resources
Resources are an alternative to DI — a simple mechanism for storing and passing user data and services to systems and other methods
- Resources are world-level singletons: shared state that doesn’t belong to any specific entity
- Ideal for configuration, time/delta-time, input state, asset caches, service references
- Two variants: singleton (one per type) and named (multiple per type, distinguished by string key)
- Available in both
CreatedandInitializedworld phases - Every resource type must implement the marker interface
IResource
Singleton Resources
A singleton resource stores exactly one instance of a given type per world. Internally uses static generic storage — access is O(1) with zero dictionary overhead.
Setting a resource:
// User classes and services — must implement IResource
public class GameConfig : IResource { public float Gravity; }
public class InputState : IResource { public Vector2 MousePos; }
// Set a resource in the world
// By default clearOnDestroy = true — the resource will be automatically cleared on World.Destroy()
W.SetResource(new GameConfig { Gravity = 9.81f });
W.SetResource(new InputState(), clearOnDestroy: false); // persists across world re-creation
// If SetResource is called again for the same type, the value is replaced without error
W.SetResource(new GameConfig { Gravity = 4.0f }); // overwrites the previous value
The clearOnDestroy parameter is only applied on the first registration. Replacing an existing resource preserves the original clearOnDestroy setting.
Basic operations:
// Check if a resource of the given type is registered
bool has = W.HasResource<GameConfig>();
// Get a mutable ref to the resource value — modifications are written directly to storage
ref var config = ref W.GetResource<GameConfig>();
config.Gravity = 11.0f; // modified in-place, no setter call needed
// Remove the resource from the world
W.RemoveResource<GameConfig>();
// Resource<T> — zero-cost readonly struct handle for frequent access (no initialization needed)
W.Resource<GameConfig> configHandle;
bool registered = configHandle.IsRegistered;
ref var cfg = ref configHandle.Value;
configHandle.Set(new GameConfig { Gravity = 9.81f }); // register / replace via the handle
configHandle.Remove(); // remove via the handle
Named Resources
Named resources allow multiple instances of the same type, distinguished by string keys. Internally stored in a Dictionary<string, object> with type-safe Box<T> wrappers.
Setting a named resource:
// Set named resources of the same type under different keys
W.SetResource("player_config", new GameConfig { Gravity = 9.81f });
W.SetResource("moon_config", new GameConfig { Gravity = 1.62f });
// If SetResource is called again for an existing key, the value is replaced without error
W.SetResource("player_config", new GameConfig { Gravity = 10.0f }); // overwrites
Basic operations:
// Check if a named resource with the given key exists
bool has = W.HasResource<GameConfig>("player_config");
// Get a mutable ref to the named resource value
ref var config = ref W.GetResource<GameConfig>("player_config");
config.Gravity = 5.0f;
// Remove a named resource by key
W.RemoveResource("player_config");
// NamedResource<T> — struct handle that caches the internal reference after the first access
// Create a handle bound to a key (does not register the resource)
var moonConfig = new W.NamedResource<GameConfig>("moon_config");
bool registered = moonConfig.IsRegistered; // always performs dictionary lookup, not cached
ref var cfg = ref moonConfig.Value; // first call resolves from dictionary and caches; subsequent calls are O(1)
moonConfig.Set(new GameConfig { Gravity = 1.62f }); // register / replace under the bound key
moonConfig.Remove(); // remove the bound key (also drops the cache)
// The cache is automatically invalidated when the resource is removed or the world is destroyed
NamedResource<T> is a mutable struct that caches an internal reference on first Value access. Do not store it in a readonly field or pass by value after first use — the C# compiler will create a defensive copy, discarding the cache and causing a dictionary lookup on every access. Store it in a non-readonly field or local variable.
Lifecycle
W.Create(WorldConfig.Default());
// Resources can be set after Create (no need to wait for Initialize)
W.SetResource(new GameConfig { Gravity = 9.81f });
W.SetResource("debug_flags", new DebugFlags(), clearOnDestroy: false);
W.Initialize();
// Resources remain available during the Initialized phase
ref var config = ref W.GetResource<GameConfig>();
// On Destroy: resources with clearOnDestroy=true are cleared automatically
// Resources with clearOnDestroy=false persist and remain available after the next Create+Initialize cycle
W.Destroy();
Systems-scoped resources
Both Resource<T> and NamedResource<T> also exist at the systems-pipeline level. Each World<TWorld>.Systems<TSystemsType> has its own independent resource storage, isolated from the world’s resources and from other systems groups. The lifecycle of these resources is bound to the systems pipeline: they are cleared on Systems<TSystemsType>.Destroy(), not on World<TWorld>.Destroy().
Use them when a piece of state belongs to a specific system group (e.g., a fixed-step accumulator for FixedSys, a render-only frame buffer for RenderSys) and should not leak into world-level resources or into other pipelines.
Public API
The same method set as on the world is mirrored on Systems<TSystemsType> — only the storage scope differs:
public struct FixedSystems : ISystemsType { }
public abstract class FixedSys : W.Systems<FixedSystems> { }
public struct FixedTime : IResource { public float Accumulator; public float Step; }
// Singleton resource scoped to FixedSys
FixedSys.SetResource(new FixedTime { Step = 1f / 60f });
ref var time = ref FixedSys.GetResource<FixedTime>();
bool has = FixedSys.HasResource<FixedTime>();
FixedSys.RemoveResource<FixedTime>();
// Keyed resource scoped to FixedSys
FixedSys.SetResource("solver_a", new SolverState());
ref var solver = ref FixedSys.GetResource<SolverState>("solver_a");
FixedSys.RemoveResource("solver_a");
Handle structs
World<TWorld>.Systems<TSystemsType>.Resource<T> and World<TWorld>.Systems<TSystemsType>.NamedResource<T> mirror the world-level handles and access the systems-scoped storage directly:
public struct PhysicsSystem : ISystem {
private FixedSys.Resource<FixedTime> _time;
private FixedSys.NamedResource<SolverState> _solver = new("solver_a");
public void Update() {
ref var time = ref _time.Value; // zero-cost handle, no lookup
ref var solver = ref _solver.Value; // dictionary lookup on first access, then cached
// ...
}
}
Both handle types also expose Set(value, clearOnDestroy) and Remove() — the same registration/removal API as on the world or systems pipeline, but invoked directly on the handle (the resource type / key are taken from the handle itself).
The same NamedResource<T> caching warning applies: do not store these handles in readonly fields or pass them by value after the first Value access.
Lifecycle
FixedSys.Create();
// Resources can be set immediately after Create
FixedSys.SetResource(new FixedTime { Step = 1f / 60f });
FixedSys.Add(new PhysicsSystem());
FixedSys.Initialize();
// ... game loop ...
// On Destroy: all FixedSys resources with clearOnDestroy=true are cleared,
// independently of W.Destroy()
FixedSys.Destroy();
Different ISystemsType types (e.g., FixedSys vs RenderSys) keep their resource stores fully independent; the same goes for world-level resources vs any systems-scoped pipeline.
Snapshot serialization
IResource has four optional default-implemented methods. Override Guid() to opt the resource into automatic snapshot serialization:
public interface IResource {
public Guid? Guid() => null; // null → not serialized
public byte Version() => 0;
public void Write(ref BinaryPackWriter writer) {}
public void Read(ref BinaryPackReader reader, byte version) {}
}
- Unmanaged struct (no references):
Write/Readare not required — the framework uses raw memory copy viaUnsafe. - Non-unmanaged type: both
WriteandReadare required —SetResourcethrowsStaticEcsExceptionotherwise. - Same rules apply to both world-scope and
Systems<TSystemsType>-scope resources, singleton and named alike.
Full serialization details (format selection, version migration, standalone CreateResourcesSnapshot / LoadResourcesSnapshot API): see Serialization → Resources serialization.