#What is the best way to implement the "State" pattern in ECS?

1 messages · Page 1 of 1 (latest)

floral burrow
#

So, I started my ECS learning journey with another implementation of a character controller. I plan for this controller to support different types of movement, at least surface movement and flight.

In OOP in this case I would apply a state pattern, but the question is how should this pattern look like in ECS?

I have a couple options and each has its own disadvantages. I was going to dump a wall of text here describing them, but thankfully changed my mind. After all, I didn't create this theme to give someone a migraine, but to find out what architectural solution would you use here?

wide adder
floral burrow
# wide adder each solution will have pros and cons, and the solution can get really specific ...

Thank you very much for the links. I realized that I was missing ISharedComponent. It is perfect for storing state information, as it allows filtering entities at the query level not only by the presence of the component, but also by the value of its fields.

My current implementation of the state machine is organized in such a way that each state is a separate system, and each such system corresponds to a unique IComponentData. System jobs process only those entities that have the corresponding state component enabled.

However, I encountered difficulties in implementing the management of these states. For example, to switch the state, it was necessary to explicitly write the same code for each state component (after all, to work with each component you need to use a personal ComponentLookup).

I ended up writing a crutch using reflection that solved this, but it's obviously a bad solution.

left sentinel
floral burrow
floral burrow
# left sentinel I was thinking of using enableable components to determine states. This would me...

I am now considering using an I Shared Component with a single enum field indicating which state is currently active.

If I understand correctly, the values of the ISharedComponent fields are taken into account when splitting into archetypes.

That is, if two entities have the same set of components, but the shared-component field values are different, they will not be grouped together.

When generating an EntityQuery, you can also specify a shared-component filter by passing a template component to it as an argument.

P. S.

I apologize if I write something incorrectly. I use a translator.

grand lodge
#

Using shared components for rapidly changing states is worst for performance, because simply changing states, may cause 16KB allocations/deallocations, making it extremely unperformant. On top of it - it's locked to main thread only, so multithreading is very limited with it.

wide adder
# left sentinel I was thinking of using enableable components to determine states. This would me...

There is some price to pay, but it's gonna vary wildly based on the quantity of different states (enableable comps) per entity, how homogenously the enabled components are spread out across chunks, how much data the states hold, how heavy the update logic is relatively to the baseline cost of iterating your state components, how often state changes happen, etc...

I had a test project for this which I'd have to dig up again. I implemented two state machines: one with a switch statement over a state enum, and one with enabled components. I measured the performance of both state machines in 2 different tests. In each test, each state holds an int value, and it increments that value when updated, and each state machine is given a random state on start and just stays in that state (so this test doesn't measure state change perf; only state update perf):

Test 1 - 1,000,000 state machines of 10 states each, updated 1 time per frame

  • Switch approach: 2.7ms
  • EnabledComps approach: 9ms

Test 2 - 10,000 state machines of 100 states each, updated 10 times per frame (simulates netcode prediction)

  • Switch approach: 0.28ms
  • EnabledComps approach: 1.61ms

Note: the pertinence of "updated 10 times per frame" is that it highlights the overhead of the enabledComps approach's scheduling of one job per state, as opposed to the switch approach's one job per stateMachine. At 10 updates per frame and 10 different states, switch approach schedules 10 jobs, while enabledComps approach schedules 100 jobs. Unfortunately I'd have to re-run tests to measure that overhead more specifically, because Test 2 also has more states per entity, which would also contribute to making the enabledComps approach slower than the switch approach. In Test 1, the EnabledComps are 3.3x slower than the switch, and in Test 2 they are 5.8x slower. So EnabledComps don't scale as well with state counts and update counts