Skip to content

Shareable Components Neofox: hug_duck_heart

two fennecs happily holding a huge cardboard box together

Neofox: thumbsup Shared State Made Simple

Need the same data across multiple entities? Shareable components let you use reference types to share a single instance between entities. One update, everyone sees it!

What is a Shareable?

A Shareable component is simply a reference type (class or record) used as a component. Multiple entities can hold references to the same instance, enabling efficient shared state.

cs
public class SharedData
{
    public int Value;
}

var sharedData = new SharedData { Value = 42 };

var entity1 = world.Spawn().Add(sharedData);
var entity2 = world.Spawn().Add(sharedData);

// Both entities reference the same instance!
sharedData.Value = 100;  // Both see Value = 100

When to Use Shareables

ScenarioWhy Shareable?
Heavyweight objectsExpensive to create or copy
Global configurationSettings shared across entities
External referencesGame engine nodes, textures, etc.
Shared counters/stateSynchronized values
Large data structuresDon't want copies everywhere

Usage Examples

Basic Sharing

cs
public class TeamConfig
{
    public string Name;
    public int Score;
}

var blueTeam = new TeamConfig 
{ 
    Name = "Blue", 
    Score = 0 
};

// All blue team members share the same config
foreach (var entity in blueTeamEntities)
{
    entity.Add(blueTeam);
}

// Update score once, all members see it
blueTeam.Score += 10;

Processing with Streams

cs
// A mutable record (or class)
record SharedData(int Value)
{
    public int Value { get; set; } = Value;
}
cs
using var world = new World();
var stream = world.Query<SharedData>().Stream();

var sharedData = new SharedData(42);
world.Entity().Add(sharedData).Spawn(5); // 5 entities share this

stream.For((ref SharedData data) =>
{
    data.Value++;  // Increments once per entity!
    Console.WriteLine(data.ToString());
});

sharedData.Value++;  // Increment outside of runner
Console.WriteLine();

stream.For((ref SharedData data) =>
{
    Console.WriteLine(data.ToString());
});
plaintext
SharedData { Value = 43 }
SharedData { Value = 44 }
SharedData { Value = 45 }
SharedData { Value = 46 }
SharedData { Value = 47 }

SharedData { Value = 48 }
SharedData { Value = 48 }
SharedData { Value = 48 }
SharedData { Value = 48 }
SharedData { Value = 48 }

With EntitySpawner

cs
var sharedTexture = LoadTexture("enemy.png");

// Bulk spawn entities with shared reference
world.Entity()
    .Add(new Position())
    .Add(new Health { Value = 100 })
    .Add(sharedTexture)  // All share this texture
    .Add<Enemy>()
    .Spawn(1000);
ShareableObject Link
Plain componentRelation to object
One per type per entityMultiple of same type allowed
entity.Add(obj)entity.Add(Link.With(obj))
Groups by archetype normallyGroups entities by linked object
cs
Bank chase = new("Chase");

// Shareable: bob has chase as his plain Bank component
bob.Add(chase);

// Object Link: bob has a Bank relation TO chase
bob.Add(Link.With(chase));
// bob can also have another Bank link!
bob.Add(Link.With(targo));

See Object Links for more on relation-style object references.

Performance Considerations Neofox: think

Neofox: science The Trade-offs

  1. Reduced memory – One instance instead of N copies
  2. Instant updates – Change once, affects all entities
  3. Indirection cost – Each access requires a pointer dereference
  4. Cache implications – Data may not be contiguous in memory

Neofox: owo Iteration Performance

Queries that include reference components in their Stream Types will have slightly slower iteration due to memory indirection. However, queries that don't include the shareable as a Stream Type won't be affected!

cs
// Slower: iterating through shared references
stream.For((ref SharedData data) => { ... });

// Not affected: SharedData not in stream types
var stream = world.Query<Position>()
    .Has<SharedData>()  // Just filtering, not streaming
    .Stream();

Best Practices

  1. Use for truly shared state – If each entity needs different values, use value types
  2. Be mindful of mutation – Changes affect all entities immediately
  3. Consider lifecycle – Ensure shared objects outlive the entities using them
  4. Don't over-share – Not everything needs to be shared

Quick Reference

AspectShareables
TypeReference types (class, record)
StorageHeap, referenced by entities
SharingSame instance across entities
MemoryOne allocation, many references
Best ForExpensive objects, shared state

Constraints

  • Must be a reference type (class or record, not struct)
  • Must be notnull
  • Entity can only have one plain component of each type
  • For multiple of same type, use Object Links

fennecs is released under the MIT License. Neofox is released under the CC BY-NC-SA 4.0 License.