Update: a newer/better version of this tool is available here:
Original post
Links:
Sample project
Package download
What does it do:
This tool gives you a DOTS equivalent to calling interface functions on “objects” without knowing what the type of the object is in advance, even from inside bursted jobs. It generates a struct (either a IComponentData or a IBufferElement) that can assume the role of any struct in the project that implements a given interface.
For example:
- you create a IMyPolyComponent interface that has a “DoSomething()” function
- you create structs A, B, C that implement that interface, and each do different things in DoSomething()
- you press a button to generate a “MyPolyComponent” component that can assume the role of either A, B, or C, but they are all considered as the same component type in the eyes of the ECS.
This allows you to iterate on a set of MyPolyComponent components and call DoSomething() on them, and they’ll each do their own respective implementation of DoSomething(), depending on which of the sub-types (A, B, or C) they have been assigned
The generated struct can either be a “union struct” (will have a size equivalent to the max size among all of its forms), or not a union struct (will have a size equivalent to the total size of all of its forms, but with the advantage of being able to store the data of each of its forms)
When to use:
As you can see in this performance comparison , polymorphic components can be a much better solution than anything involving structural changes if those changes happen relatively frequently. They also have the advantage of making the code much simpler.
They can also be useful in more specific cases like these:
How to use:
How to use
1. Create the interface that defines your polymorphic component
you create an interface with a special attribute on it: this defines a all the functions that the various types within your polymorphic component can have
[PolymorphicComponentDefinition("MyPolyComponent", "_Samples/PolymorphicTest/_GENERATED")]
public interface IMyPolyComp
{
void Update(float deltaTime, ref Translation translation, ref Rotation rotation);
}
2. Create the specific structs that are part of the polymorphic component
you create several structs that implement the interface from point 1: this defines the specific implementations of all the various types that your polymorphic component can assume
[Serializable]
public struct CompA : IMyPolyComp
{
public float MoveSpeed;
public float MoveAmplitude;
[HideInInspector]
public float TimeCounter;
public void Update(float deltaTime, ref Translation translation, ref Rotation rotation)
{
TimeCounter += deltaTime;
translation.Value.y = math.sin(TimeCounter * MoveSpeed) * MoveAmplitude;
}
}
[Serializable]
public struct CompB : IMyPolyComp
{
public float RotationSpeed;
public float3 RotationAxis;
public void Update(float deltaTime, ref Translation translation, ref Rotation rotation)
{
rotation.Value = math.mul(rotation.Value, quaternion.AxisAngle(math.normalizesafe(RotationAxis), RotationSpeed * deltaTime));
}
}
3. Generate the polymorphic component code
you press a button to codegen a polymorphic component based on the interface you created in point 1, and the structs you defined in point 2. The generated component has all the methods of the interface from point 1, but it will automatically call them on whatever specific struct type was assigned to your polymorphic component by the authoring component
[Serializable]
[StructLayout(LayoutKind.Explicit, Size = 20)]
public struct MyPolyComponent : IComponentData
{
public enum TypeId
{
CompA,
CompB,
}
[FieldOffset(0)]
public CompA CompA;
[FieldOffset(0)]
public CompB CompB;
[FieldOffset(16)]
public readonly TypeId CurrentTypeId;
public MyPolyComponent(in CompA c)
{
CompB = default;
CompA = c;
CurrentTypeId = TypeId.CompA;
}
public MyPolyComponent(in CompB c)
{
CompA = default;
CompB = c;
CurrentTypeId = TypeId.CompB;
}
public void Update(Single deltaTime, ref Translation translation, ref Rotation rotation)
{
switch (CurrentTypeId)
{
case TypeId.CompA:
CompA.Update(deltaTime, ref translation, ref rotation);
break;
case TypeId.CompB:
CompB.Update(deltaTime, ref translation, ref rotation);
break;
}
}
}
4. Call polymorphic functions from a system!
public class TestSystem : SystemBase
{
protected override void OnUpdate()
{
float deltaTime = Time.DeltaTime;
Entities.ForEach((Entity entity, ref MyPolyComponent polyComp, ref Translation translation, ref Rotation rotation) =>
{
polyComp.Update(deltaTime, ref translation, ref rotation);
}).Schedule();
}
}