Skip to content

Delegates

Runner methods on Steam Queries expect delegates (Actions) to call. The delegate signatures mirror the count and order of the Query's Stream Types.

ComponentAction<> and UniformComponentAction<>

These are invoked by Stream<>.For and Stream<>.Job. The Uniforms are contravariant, which helps with code reuse when you refactor your anonymous, named, or static method signatures to take broader data types.

cs
delegate void ComponentAction<C0>(ref C0 comp0);
delegate void ComponentAction<C0, C1>(ref C0 comp0, ref C1 comp1);
delegate void ComponentAction<C0, C1, C2>(ref C0 comp0, ref C1 comp1, ref C2 comp2);
delegate void ComponentAction<C0, C1, C2, C3>(ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3);
delegate void ComponentAction<C0, C1, C2, C3, C4>(ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3, ref C4 comp4);
cs
delegate void UniformComponentAction<in U, C0>(U uniform, ref C0 comp0);
delegate void UniformComponentAction<in U, C0, C1>(U uniform, ref C0 comp0, ref C1 comp1);  
delegate void UniformComponentAction<in U, C0, C1, C2>(U uniform, ref C0 comp0, ref C1 comp1, ref C2 comp2);
delegate void UniformComponentAction<in U, C0, C1, C2, C3>(U uniform, ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3);
delegate void UniformComponentAction<in U, C0, C1, C2, C3, C4>(U uniform, ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3, ref C4 comp4);

EntityComponentAction<> and UniformEntityComponentAction<>

These are invokable through Stream<>.For. In addition to the Components and optional Uniform, they also receive an EntityRef that can be used to interact structurally with the Entity right then and there (structural changes are deferred until the Runner completes).

EntityRef is a ref struct and cannot leave the delegate's scope – to keep a handle around, store its Entity property (or rely on the implicit conversion to Entity).

cs
delegate void EntityComponentAction<C0>(in EntityRef entity, ref C0 comp0);
delegate void EntityComponentAction<C0, C1>(in EntityRef entity, ref C0 comp0, ref C1 comp1);
delegate void EntityComponentAction<C0, C1, C2>(in EntityRef entity, ref C0 comp0, ref C1 comp1, ref C2 comp2);
delegate void EntityComponentAction<C0, C1, C2, C3>(in EntityRef entity, ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3); 
delegate void EntityComponentAction<C0, C1, C2, C3, C4>(in EntityRef entity, ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3, ref C4 comp4);
cs
delegate void UniformEntityComponentAction<in U, C0>(U uniform, in EntityRef entity, ref C0 comp0);
delegate void UniformEntityComponentAction<in U, C0, C1>(U uniform, in EntityRef entity, ref C0 comp0, ref C1 comp1);
delegate void UniformEntityComponentAction<in U, C0, C1, C2>(U uniform, in EntityRef entity, ref C0 comp0, ref C1 comp1, ref C2 comp2);
delegate void UniformEntityComponentAction<in U, C0, C1, C2, C3>(U uniform, in EntityRef entity, ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3);
delegate void UniformEntityComponentAction<in U, C0, C1, C2, C3, C4>(U uniform, in EntityRef entity, ref C0 comp0, ref C1 comp1, ref C2 comp2, ref C3 comp3, ref C4 comp4);

MemoryAction<> and MemoryUniformAction<>

These are invoked by Stream<>.Raw.

cs
delegate void MemoryAction<C0>(Memory<C0> comp0);
delegate void MemoryAction<C0, C1>(Memory<C0> comp0, Memory<C1> comp1);  
delegate void MemoryAction<C0, C1, C2>(Memory<C0> comp0, Memory<C1> comp1, Memory<C2> comp2);
delegate void MemoryAction<C0, C1, C2, C3>(Memory<C0> comp0, Memory<C1> comp1, Memory<C2> comp2, Memory<C3> comp3);
delegate void MemoryAction<C0, C1, C2, C3, C4>(Memory<C0> comp0, Memory<C1> comp1, Memory<C2> comp2, Memory<C3> comp3, Memory<C4> comp4);
cs
delegate void MemoryUniformAction<in U, C0>(U uniform, Memory<C0> comp0);
delegate void MemoryUniformAction<in U, C0, C1>(U uniform, Memory<C0> comp0, Memory<C1> comp1);
delegate void MemoryUniformAction<in U, C0, C1, C2>(U uniform, Memory<C0> comp0, Memory<C1> comp1, Memory<C2> comp2); 
delegate void MemoryUniformAction<in U, C0, C1, C2, C3>(U uniform, Memory<C0> comp0, Memory<C1> comp1, Memory<C2> comp2, Memory<C3> comp3);
delegate void MemoryUniformAction<in U, C0, C1, C2, C3, C4>(U uniform, Memory<C0> comp0, Memory<C1> comp1, Memory<C2> comp2, Memory<C3> comp3, Memory<C4> comp4);

My name is in-igo Montoya

"You keep using that keyword. I do not think it means what you think it means."

The in keyword in the Uniform* delegates' type parmeters denotes contravariance. This simply means that the U parameter can be a broader type than the one you pass in.

It does not mean readonly or in as in C# method arguments, but uniforms are still always passed by value. If that value is a reference type, you can of course do fun stuff with it... 🦊

Components are always passed by reference because they reside in a special storage object that enables this.

Attentive readers will notice that reference type Uniforms passed to a Job should, of course, be chosen carefully to be thread safe if they are to be mutated from multiple threads. Anything in System.Collections.Concurrent is a faithful friend here.

Uniforms

Uniforms are a way to pass data to your For, Job, and even Raw delegates. They are passed by value, and can be of any type. They are neat to feed data to your delegates that is not part of the component data, but relevant to the operation you are performing.

Typical examples of uniforms are the current dt (delta time) or positions or forces, e.g. for explosions or swarming behaviour, or even a list of UI objects that were clicked.

Varyings

Later versions of fennecs may introduce the concept of "varyings". If you need this feature, please don't hesitate to reach out with some ideas and use cases!

Varyings at Home

Until then, you can use a mutable reference type object as a uniform, and pass it to your For or Job delegate. This way, you can mutate the object from the delegate, and each subsequent calls will see these changes.

Remember: Thread fast, thread hard, and thread safe!

But alas, where many threads live, there also be dragons.

Neofox: hug_dragn_heart