Component
Component gives an entity data and properties
- Represented as a user struct with the
IComponentmarker interface - Implemented as struct purely for performance reasons (SoA storage)
- Supports lifecycle hooks:
OnAdd,OnDelete,CopyTo,Write,Read - Can be enabled/disabled without removing data — opt-in via the
IDisableablemarker
Example:
public struct Position : IComponent {
public Vector3 Value;
}
public struct Velocity : IComponent {
public float Value;
}
public struct Name : IComponent {
public string Val;
}
Requires registration in the world between creation and initialization
W.Create(WorldConfig.Default());
//...
// Simple registration without configuration (suitable for most cases)
W.Types()
.Component<Position>()
.Component<Velocity>()
.Component<Name>();
// Configuration is provided by implementing IComponentConfig<T> on the component struct
// (see example below)
//...
W.Initialize();
The noDataLifecycle parameter controls whether the framework manages component data lifecycle. By default (noDataLifecycle: false), the framework pre-initializes new storage with defaultValue and resets data to defaultValue on deletion — so entity.Add<T>() returns the configured default. With noDataLifecycle: true, no initialization or cleanup is performed — useful for high-frequency unmanaged types. If OnDelete is defined, the hook handles cleanup regardless of this flag.
To provide configuration, implement the IComponentConfig<T> interface on the component struct. Both manual registration and RegisterAll() will use it automatically:
public struct Health : IComponent, IComponentConfig<Health> {
public float Value;
public ComponentTypeConfig<Health> Config() => new(
defaultValue: new Health { Value = 100f }
);
}
ComponentTypeConfig<T> parameters:
guid— stable identifier for serialization (default — auto-computed from type name)version— data schema version for migration (default — 0)noDataLifecycle— disable framework data management (default — false). Whenfalse, the framework pre-initializes new storage withdefaultValueand resets data todefaultValueon deletion. Whentrue, no initialization or cleanup is performed — useful for high-frequency unmanaged types. IfOnDeleteis defined, the hook handles cleanup regardless of this flagreadWriteStrategy— binary serialization strategy (default — auto-detected)defaultValue— default value for init and deletion (default — none)
Change tracking is enabled by implementing marker interfaces on the component type itself (not via config flags): ITrackableAdded, ITrackableDeleted, ITrackableChanged. See Change Tracking.
Creating entities with components:
// Create an empty entity (no components or tags)
W.Entity entity = W.NewEntity<Default>();
// Create an entity with a specific type and cluster
W.Entity entity = W.NewEntity<Default>(clusterId: 0);
// Create an entity with components — Set returns Entity (overloads from 1 to 8 components)
W.Entity entity = W.NewEntity<Default>().Set(new Position { Value = Vector3.One });
W.Entity entity = W.NewEntity<Default>().Set(
new Position { Value = Vector3.One },
new Velocity { Value = 1f }
);
Adding components:
// Add without value: if component exists → returns ref to existing, hooks are NOT called
// If new → initializes with default, calls OnAdd
ref var position = ref entity.Add<Position>();
// With isNew flag: isNew=true if component was added for the first time
ref var position = ref entity.Add<Position>(out bool isNew);
// Add multiple components in a single call (overloads from 2 to 5)
entity.Add<Position, Velocity>();
// Set with value: ALWAYS overwrites data
// If component exists → OnDelete(old) → replace → OnAdd(new)
// If new → set value → OnAdd
ref var position = ref entity.Set(new Position { Value = Vector3.One });
// Set multiple components with values (overloads from 2 to 12)
entity.Set(new Position { Value = Vector3.One }, new Velocity { Value = 1f });
Add<T>() without a value and Set<T>(T value) with a value have different hook semantics. Without value: if the component already exists, hooks are not called, a ref to current data is returned. With value: data is always overwritten with the full cycle OnDelete → replace → OnAdd.
Data access:
// Get a mutable ref to the component (read and write)
// Does NOT mark as Changed — use Mut<T>() for tracked access
ref var velocity = ref entity.Ref<Velocity>();
velocity.Value += 10f;
// Get a readonly ref to the component — does NOT mark as Changed
ref readonly var pos = ref entity.Read<Position>();
var x = pos.Value.x; // reading OK, no Changed mark
// Get a tracked mutable ref — marks as Changed if the component implements ITrackableChanged
ref var pos = ref entity.Mut<Position>();
pos.Value += delta; // data modified AND marked as Changed
Ref<T>() does NOT mark Changed. Use Mut<T>() when you need change tracking to work with AllChanged<T> filters. In query delegates (For), ref parameters use Mut semantics automatically.
Basic operations:
// Get the number of components on an entity
int count = entity.ComponentsCount();
// Check if a component is present (overloads from 1 to 3 — checks ALL specified)
// Checks presence regardless of enabled/disabled state
bool has = entity.Has<Position>();
bool hasBoth = entity.Has<Position, Velocity>();
bool hasAll = entity.Has<Position, Velocity, Name>();
// Check if at least one of the specified components is present (overloads from 2 to 3)
bool hasAny = entity.HasAny<Position, Velocity>();
bool hasAny3 = entity.HasAny<Position, Velocity, Name>();
// Remove a component (overloads from 1 to 5)
// Calls OnDelete if the component was present; returns true if removed, false if not present
bool deleted = entity.Delete<Position>();
entity.Delete<Position, Velocity>();
entity.Delete<Position, Velocity, Name>();
Enable/Disable:
Disable/Enable is opt-in per component type via the IDisableable marker interface. Only components marked IDisableable allocate the per-component disabled bitmask, expose Disable<T>()/Enable<T>()/HasDisabled<T>()/HasEnabled<T>() on the entity, and can appear in *Disabled query filters. Components without the marker pay no memory or serialization overhead for the disabled state.
// Mark the component as disableable
public struct Position : IComponent, IDisableable {
public Vector3 Value;
}
// Disable a component — data is preserved, but entity is excluded from standard queries
// Returns ToggleResult: MissingComponent, Unchanged, or Changed
ToggleResult disabled = entity.Disable<Position>();
entity.Disable<Position, Velocity>();
entity.Disable<Position, Velocity, Name>();
// Re-enable a component
// Returns ToggleResult: MissingComponent, Unchanged, or Changed
ToggleResult enabled = entity.Enable<Position>();
entity.Enable<Position, Velocity>();
entity.Enable<Position, Velocity, Name>();
// Check that ALL specified components are enabled (overloads from 1 to 3)
bool posEnabled = entity.HasEnabled<Position>();
bool bothEnabled = entity.HasEnabled<Position, Velocity>();
// Check that at least one is enabled (overloads from 2 to 3)
bool anyEnabled = entity.HasEnabledAny<Position, Velocity>();
// Check that ALL specified components are disabled (overloads from 1 to 3)
bool posDisabled = entity.HasDisabled<Position>();
bool bothDisabled = entity.HasDisabled<Position, Velocity>();
// Check that at least one is disabled (overloads from 2 to 3)
bool anyDisabled = entity.HasDisabledAny<Position, Velocity>();
All Disable*/Enable*/Has*Disabled/Has*Enabled methods constrain T : struct, IComponent, IDisableable — calling them on a type without the marker is a compile-time error. The same applies to AllOnlyDisabled<T>, AllWithDisabled<T>, NoneWithDisabled<T>, AnyOnlyDisabled<>, AnyWithDisabled<> query filters.
Disabled components are excluded from standard query filters (All, None, Any), but their data remains in memory. Use WithDisabled/OnlyDisabled filter variants to work with disabled components.
Built-in component types Multi<TValue> (multi-component), Link<TLinkType> and Links<TLinkType> (relations) implement IDisableable out of the box — Disable/Enable on relations and multi-components works without changes on your side.
Copying and moving:
var source = W.NewEntity<Default>().Set(new Position(), new Velocity());
var target = W.NewEntity<Default>();
// Copy specified components to another entity (overloads from 1 to 5)
// The source entity keeps its components
// If CopyTo hook is overridden — custom copy logic is used
// If CopyTo hook is NOT overridden — bitwise copy via Add + disabled state is preserved
// Returns true (for single) if the source had the component
bool copied = source.CopyTo<Position>(target);
source.CopyTo<Position, Velocity>(target);
// Move specified components to another entity (overloads from 1 to 5)
// Performs Copy to target, then Delete from source (OnDelete is called on source)
bool moved = source.MoveTo<Position>(target);
source.MoveTo<Position, Velocity>(target);
Query filters:
Component filters are described in the Queries — Components section.
Lifecycle hooks:
The IComponent interface provides hooks with empty default implementations — override only the ones you need.
Do not leave empty hook implementations. If a hook is not needed — don’t implement it. Unimplemented hooks are not called and create no overhead.
public struct Cooldown : IComponent {
public float Duration;
public float Elapsed;
// Called after the component is added or the value is overwritten via Set(value)
public void OnAdd<TWorld>(World<TWorld>.Entity self) where TWorld : struct, IWorldType {
Elapsed = 0f; // reset timer on every apply
}
// Called before the component is removed (Delete), before overwrite (Set with value),
// and during entity destruction for each component
//
// The `reason` parameter indicates why deletion occurs:
// HookReason.Default — explicit removal or entity destruction
// HookReason.UnloadEntity — entity/chunk unloading
// HookReason.WorldDestroy — world reset/destruction
public void OnDelete<TWorld>(World<TWorld>.Entity self, HookReason reason) where TWorld : struct, IWorldType { }
// Custom copy logic for CopyTo / MoveTo / Clone
// If NOT overridden — bitwise copy + disabled state preservation
// If overridden — completely replaces the default copy logic
public void CopyTo<TWorld>(World<TWorld>.Entity self, World<TWorld>.Entity other, bool disabled)
where TWorld : struct, IWorldType {
ref var copy = ref other.Add<Cooldown>();
copy.Duration = Duration;
copy.Elapsed = 0f; // clone starts from zero
}
// Serialization — write the component to a binary stream
// Required for EntitiesSnapshot (all types), and for non-unmanaged types in any snapshot
public void Write<TWorld>(ref BinaryPackWriter writer, World<TWorld>.Entity self)
where TWorld : struct, IWorldType {
writer.WriteFloat(Duration);
writer.WriteFloat(Elapsed);
}
// Deserialization — read the component from a binary stream
// The version parameter enables data migration between schema versions
public void Read<TWorld>(ref BinaryPackReader reader, World<TWorld>.Entity self, byte version, bool disabled)
where TWorld : struct, IWorldType {
Duration = reader.ReadFloat();
Elapsed = reader.ReadFloat();
}
}
Hook call order for Set(value) on an existing component: OnDelete(old value) → data replacement → OnAdd(new value). For Delete or entity destruction, only OnDelete is called.
Debugging:
// Collect all components of an entity into a list (for inspector/debugging)
// The list is cleared before populating
var components = new List<IComponent>();
entity.GetAllComponents(components);