State Tree Event Consumption

Hey,

I’m wondering if you can help provide us some guidance regarding using State Trees. We’re currently on 5.6.

We’re using them to handle a complex “Reward System” which hooks into our gameplay which fires off game specific events when players perform actions. We then have a number of state trees with global tasks on them listening for gameplay events they care about. These global tasks on each state tree then send an event to the internal State Tree event queue. This event queue is shared among all state trees for a specific player using the built in State Tree functionality to share an event queue.

Our state tree then follows an example pattern of:

[Image Removed]

ST_OnPlayerKill (Attached Image)

[Global Task] Listen for Event > Send State Tree Event “Event.StateTree.PlayerKill”

> Root

>> Listen for Kill Event : [Transition - OnEvent “Event.StateTree.PlayerKill” -> On Kill Event - Consume Event = true]

>> On Kill Event : [Entry Event Requirement = “Event.StateTree.PlayerKill” - Consume Event = true]

We seem to be facing a bunch of issues with this setup which I can’t quite understand why it doesn’t work properly.

  1. Is event consumption fully functional? I’m finding that I can’t get ANY events to be consumed when just having Consume Event true on the “On Kill Event” entry requirement. It only seems to work when I set Consume Event to true on the transition of the “Listen for Kill Event” state.
  2. We’re seeing that even though theoretically our state trees can complete their execution in a single frame (due to not needing to wait on any states and all logic being performed on state entry) once we enter the “On Kill Event” state. We’re still finding that the state will run over a number of frames and it’s not entirely clear what is causing it to be spread out over multiple frames. Or how the state execution context instanced data is safe to persist that way (given it can contain UObject pointers to constantly changing actors etc). Is there a way to ensure a state tree will operate over a single frame if it doesn’t require waiting in any states?
  3. What is the correct way to architect a state tree to effectively run the state “On Kill Event” once per event added to the event queue and also consume the event. Then after the state “On Kill Event” has finished execution, we would like to go back to Root and check the event queue in “Listen for Kill Event” again and process/consume all events it can.

I realise that we may not be using State Tree in the way it was entirely designed for with spending no real time in states themselves, but it would be nice if we can get this working for our use case with the control it gives our designers.

One of the things I’ve run into a bunch of times is that the Root state, if set to “Try to enter children in order”, will evaluate the states the next frame. You have to be careful of the entry conditions for each child state of the root or else it will keep starting over.

One method I’ve used is to have the root just use “Try to enter” and give it a task that fires an initialization state tree event after the first frame. Then in the root have a transition rule that moves to the desired first state on that event.

It helps prevent the root from constantly reevaluating all its children.

Hi John,

Thanks for the idea! I’ll try making the additional task you talk about and changing the root to “Try to enter”.

One thing I did find when looking deeper in the State Tree code is the following in:

FStateTreeExecutionContext::TickTriggerTransitionsInternal()

// The state selection is repeated up to MaxIteration time. This allows failed EnterState() to potentially find a new state immediately.
// This helps event driven StateTrees to not require another event/tick to find a suitable state.
static constexpr int32 MaxIterations = 5;
for (int32 Iter = 0; Iter < MaxIterations; Iter++)

It seems that regardless of any setup, this hardcoded restriction means realistically we can only perform 5 transitions per frame. Bumping this up to cover the number of states in our tree and we then get it to fully execute in a single frame.

However I don’t really have a reliable way to change the engine to handle this. Our “On Kill Event” state has ~15 states. If we set MaxIterations to 15 we can cover 1 whole event. But if we get 3 kill events come in (some form of multikill) then we will hit the same issue and event consumption/execution will spread out across multiple frames, potentially invalidating any bound objects that may die/delete in subsequent frames.

We’re not really sure how we can effectively solve this.

I’ve found I can collapse some events into having multiple tasks on a state to handle the various situations or use child states to break it up into more logical chunks, leaving fewer transitions on a parent state.

The number of transitions is limited to an arbitrary value to prevent infinite loops of transitions in a single frame. If you need to proceed through each child state every event, could those states have their tasks combined into one state? If ordering of the tasks is the concern, you could use StateTreeDelegates to broadcast when the next task should run logic.

-James