Skip to content

Utility Features

genar edited this page May 12, 2023 · 15 revisions

Manual component registration

Unfortunately, C# has some limitations when it comes to determining byte sizes of generic or managed structs and classes. Therefore it can often happen that an error is thrown when trying to use a component.

To work around such errors, the user can register components himself.

public struct ManagedStruct{ public List<int> SomeInts{ get; set; } }

// Register
var componentType = new ComponentType(ComponentRegistry.Size-1, typeof(ManagedStruct), 8, false); // 8 = Size in bytes of the managed struct.
ComponentRegistry.Add(componentType);

// Use
var entity = world.Create(new ManagedStruct());

Non-Generic API

Almost every generic API has a type-based alternative. This can be used in cases where the types are not known at compile time. However, these APIS are somewhat slower than their generic variant and might also generate garbage. The different variants can be mixed as well. All the Entity-API is also mirrored by the World.

// Create an entity by an array of components instead of generics
var archetype = new ComponentType[]{ typeof(Position), typeof(Velocity), ... };
var entity = world.Create(archetype);

entity.Add(in object cmp);                             // Adds a component to an entity, non generic
entity.AddRange(in object[] components);               // Adds a list of components to an entity
entity.AddRange(in List<object> components);           // Adds a list of components to an entity
entity.Remove(in Type cmpType);                        // Remove a component
entity.RemoveRange(in Type[] componentTypes);          // Remove a list of components
entity.RemoveRange(in List<Type> componentTypes);      // Remove a list of components
entity.Get(in Type cmpType);                           // Get ref to a component
entity.GetRange(in Type[] componenTypes);              // Get ref to a list of components
entity.GetRange(in List<Type> componenTypes);          // Get ref to a list of components
entity.Set(in object cmp);                             // Set component
entity.SetRange(in object[] components);               // Set multiple components
entity.SetRange(in List<object> components);           // Set multiple components
entity.Has(in Type cmpType);                           // Checks if type of component exists on the entity
entity.HasRange(in Type[] componentTypes);             // Checks if types exists on entity
entity.HasRange(in List<Type> componentTypes);         // Checks if types exists on entity

You really should favor the generic variants...

Entity References

In some cases, you may want to reference entities among themselves. Instead of an Entity you should refer to an EntityReference instead. This references an entity and also stores a version to check if the original entity still exists.

using var world = World.Create();

var entity = world.Create(_entityGroup);
var reference = world.Reference(entity);  // This should be stored in your component ( entity.Reference() is also available. )

// Checks if the entity is the same as before by comparing the version and its id
if(reference.IsAlive()) {
    var cmps = reference.Entity.Get(...);
}     

Command Buffers

Entity creation, deletion, and structural changes can happen during a query or entity iteration.
Sometimes, however, it makes sense to record operations and later transfer them to the world.
For this reason, command buffers exist.

var entity = world.Create<Transform, Rotation, int>();                                                               // Entity in world.
var bufferedEntity = commandBuffer.Create(new ComponentType[] {typeof(Transform), typeof(Rotation), typeof(int) });  // Later also generic overloads to get rid of arrays. 
        
// Records operations on the existing entity.
commandBuffer.Set(in entity, new Transform{ X = 20, Y = 20});
commandBuffer.Add(in entity, new Ai());
        
// Records operations on the buffered entity. 
commandBuffer.Set(in bufferedEntity, new Transform{ X = 20, Y = 20});
commandBuffer.Add(in bufferedEntity, new Ai());

// Transfers them into the world. 
commandBuffer.Playback();

Buffers are threadsafe and very fast, perfect for such scenarios. They can and should always be reused! :)

Clone this wiki locally