DOTS and 'Complex' Data Structure Confusion

I have a graph-based system in mind that I’m conceptually struggling to prototype using ECS for Job / Burst benefits.

Images attached.

  • Black dots are Anchors

  • Dark Blue lines are Segments (the curves)

  • Light Blue Lines with a dot represent Segment Control Points

  • Purple and Green ring are splines (contour and hole, respectively)

  • Orange area is a Shape.


The Data (Conceptual):

  • Anchors

  • Points distributed in world space (Nodes)

  • Segments

  • connections between two anchors, with two control points for curves (Edges)

  • Splines

  • A linked list of segments

  • LUTs

  • A simple array of points generated by a segment. Fidelity is the array length.

  • Shapes

  • A collection of Splines that form the contour, holes, and Steiner points of a region / mesh.

The Structs (Conceptual):

struct Anchor  { float3 Value; }

struct Segment  {
    int anchorAIndex;
    float3 controlA;
    int anchorBIndex;
    float3 controlB;
    float length;
    int fidelity;   // for generating LUT
}

struct SegmentLUTPoint { float3 Value; }

struct Spline {
    bool closed;
    float Length;
}

struct SplineSegmentIndex { int Value; }

struct Shape {}


struct ShapeContourSplineIndex { int Value; }
struct ShapeHoleSplineIndex { int Value; }
struct ShapeSteinerSplineIndex { int Value; }

The data structure is a question of mine, but the real question is how should I store it?

  • Should I use Entities? If so, what should an Entity hold?

  • SegmentLUTPoint, SplineSegmentIndex, ShapeContourSplineIndex, ShapeHoleSplineIndex, and ShapeSteinerSplineIndex as IBufferElementData?

  • Anchor, Segment, Spline, Shape as IComponentData on separate Entities?

  • Should I create a non-DOTS “Graph” class, with native collections of the data above, and let a JobComponentSystem reference / own it?

Computations (Jobs)

  • Curve / Spline: Point at T, Closest Point, Distance, Intersects, etc
  • Shape: Point inside, Mesh Generation (future implementation)

Maybe something like this which various ComponentSystems could reference?

struct Node {
    float3 Value;
}

struct Edge
{
    int NodeA;
    int NodeB;
    float3 ControlA;
    float3 ControlB;
    float length;
    AABB aabb;
}

struct Spline
{
    float Length;
    AABB aabb;
}

struct Shape
{
    AABB aabb;
}

class Graph
{
    NativeList<Node> Nodes;
    NativeHashMap<int, ~type~> Node~Type~Map; // int -> Node index, ~type~ could be Color, Rotation, ...

    NativeList<Edge> Edges;
    NativeHashMap<int, EdgeControls> EdgeEdgeControlsMap; // int -> Edge index
    NativeMultiHashMap<int, float3> EdgeLUTMap; // int -> Edge index

    NativeArray<Spline> Splines;
    NativeMultiHashMap<int, int> SplineEdgesMap; // int -> Spline index, int -> Edge index

    NativeArray<Shape> Shapes;
    NativeMultiHashMap<int, int> ShapeSplineContourMap; // int -> Shape index, int -> Spline index
    NativeMultiHashMap<int, int> ShapeSplineHoleMap; // int -> Shape index, int -> Spline index
    NativeMultiHashMap<int, int> ShapeSplineSteinerMap; // int -> Shape index, int -> Spline index
    NativeHashMap<int, Mesh> ShapeMeshMap; // int -> Shape index
}

Maybe everything is an entity?

struct Node : IComponentData {} // Position component as value?
// other components: Color, Rotation, Scale, ...

struct Edge : IComponentData
{
    Entity NodeA;
    Entity NodeB;
    float3 ControlA;
    float3 ControlB;
    float Length;
    AABB aabb;
}

struct EdgeLUTPoint : IBufferElementData
{
    float3 Value;
}

struct Spline : IComponentData
{
    float Length;
    AABB aabb;
}

struct SplineEdge : IBufferElementData
{
    Entity Value;
}

struct Shape : IComponentData {}

struct ShapeSplineContour : IBufferElementData
{
    Entity Value;
}

struct ShapeSplineHole : IBufferElementData
{
    Entity Value;
}

struct ShapeSplineSteiner : IBufferElementData
{
    Entity Value;
}

If after creation this data structure is meant to be immutable, you want to use the Blob API for this. The last part of this video will provide some information on it.

I’d like for this system to be editable at runtime. Would working with blobs at runtime be an issue?

If after creation (even at runtime) this structure is immutable, blobs make sense. Otherwise, this is when I would break out into unsafe space and use a pointer in an ISystemStateComponentData that allocates dynamically for what it needs.

I’ve never used system components before. I’ve just read some posts about them and I cannot determine what the advantage would be. What did you have in mind?

IComponentData and ISystemStateComponentData work the same except when you destroy an entity.

When you destroy an entity with ISystemStateComponentData, all components are removed from the entity as usual but the ISystemStateComponentData still remains. This lets you have a query that lets you clean up the ISystemStateComponentData before removing it to destroy the entity.

I believe DreamingImLatios is suggesting this so you can clean up the pointer.

As I am not good at explaining things I’ll demonstrate instead.

public unsafe struct SystemState : ISystemStateComponentData
{
    public void* Pointer;
}

public struct Tag : IComponentData
{
}

public class SystemStateSystem : ComponentSystem
{
    protected override unsafe void  OnUpdate()
    {
        // do some work
        this.Entities.WithAll<Tag>().ForEach((ref SystemState state) =>
        {
            var ptr = state.Pointer;
        });

        // either entity was destroyed or tag component was removed, we need to cleanup pointer memory
        this.Entities.WithNone<Tag>().ForEach((Entity entity, ref SystemState state) =>
        {
            UnsafeUtility.Free(state.Pointer, Allocator.Persistent);

            // If you remove the SystemStateComponent and there are no other components left, the entity will now be deleted
            this.EntityManager.RemoveComponent<SystemState>(entity);
        });
    }
}

If you did not use SystemState when the Entity was destroyed the memory would not be freed.

4 Likes