Performance
State Designer evaluates graphs as Burst-compiled jobs over Entities. This page covers what that architecture costs, which settings bound the per-tick work, and how to measure a result that reflects the running game.
How a graph is executed
Evaluation is not performed by the State Machine component. The component owns the graph data and an entity; the systems evaluate that entity as part of the DOTS system groups.
| Stage | What runs | Threading |
|---|---|---|
| Condition evaluation | GameObject Condition logic and valid-condition counting | Managed code on the main thread |
| Transition evaluation | Choosing which transition triggers | Burst-compiled IJobEntity scheduled with ScheduleParallel |
| Traversal | State status and branch updates | Burst-compiled IJobEntity scheduled with ScheduleParallel |
| Action execution | GameObject Action logic | Managed code on the main thread |
| Cleanup | Entity command buffer playback and flag removal | Burst-compiled jobs |
Graphs are evaluated in parallel across entities, so cost scales with the transitions and Actions actually evaluated rather than with the number of agents alone.
Transition cost scales with active states
Transition evaluation does not scan every transition in the graph. Each state carries the indices of its own outgoing transitions, and the job skips any state whose status is Inactive. A large graph therefore costs little while the machine sits in one small state.
Two consequences follow:
- Wide states are the expensive ones. A state with many outgoing transitions is evaluated in full on every tick that it is active. A graph with many states but few transitions per state is cheaper than a graph with few states and many transitions each.
- A tick can include multiple transitions. After a transition fires, evaluation can continue from the destination state within the same tick, depending on Evaluation Type, its limits, and the next transition’s Conditions and Evaluation Mode. A chain such as A → B → C does not inherently require two ticks. Ordering still matters when choosing between eligible transitions — see Transition Evaluation and Priority.
The Any state is the main thing to watch. Its transitions are eligible while the graph is running, so every Condition on an Any transition is a recurring cost. Keep Any for genuine global interrupts rather than for routing that a specific state could own.
Bound the work with Evaluation Type
Evaluation Type on the State Machine component determines how much of a graph may be evaluated in one tick.
| Evaluation Type | Behavior | Use it when |
|---|---|---|
| Entire Graph | Evaluation can continue through multiple states within the same tick. Already evaluated states are tracked to bound repeated evaluation. | The graph is small, or a transition chain must resolve within the same tick. |
| Count | Evaluation stops after Max Evaluation Count states and resumes on the next tick. | A chain of immediate transitions would otherwise produce a spike, or per-frame cost must stay bounded across many agents. |
Max Evaluation Count accepts 1 through 65535. In Count mode, a chain can continue within the same tick while evaluation budget remains; a lower limit can spread it across multiple ticks. A tick is one graph update, so it does not necessarily correspond to a Unity frame when using Manual Update Mode.
Control when a graph ticks
Update Mode selects between the two runtime cadences:
- Every Frame evaluates the graph on each system-group update.
- Manual evaluates only when project code calls
Tick(). CallingTick()while Update Mode is not Manual logs a warning and does nothing.
Manual mode is the most direct way to reduce cost for graphs that do not need frame-rate decisions. UI flows, turn-based phases, and distant agents are good candidates for event-driven or staggered ticking.
GameObject nodes and Entity nodes
Both routes use the same Burst-compiled transition and traversal jobs. They differ in what happens when a node’s own logic runs.
| GameObject Action or Condition | Entity Action or Condition | |
|---|---|---|
| Node logic | Managed C# on the main thread | Burst-compiled job code |
| Shared Variable access | Values are synchronized between ECS and managed storage around execution | Read and written directly as component data |
| Best suited to | Component-based gameplay, moderate agent counts | Large agent counts and data-oriented gameplay |
The synchronization step is the main additional cost of the GameObject route: ECS-backed Shared Variables are copied to their managed values before the node runs and flushed back afterward. The sync happens once per entity per pass rather than once per node, so several GameObject nodes in one graph do not pay the cost repeatedly in the same pass.
Choose GameObject nodes for ordinary component-based gameplay, and move a graph to Entity nodes when profiling shows that node execution, not evaluation, is the bottleneck. New Nodes covers the authoring differences.
Measure in the Editor and confirm in a build
The evaluation, transition, traversal, and cleanup systems are Burst-compiled in the Editor as well as in a player build, so Editor timings reflect the same compiled code that ships.
The Editor still carries overhead that a build does not: collections and job safety checks, Entities journaling when enabled, and the graph window’s own runtime rendering while a graph is open. Close the State Designer window when taking measurements, and confirm final numbers in a development player build.
If evaluation timings look unexpectedly slow, check that Jobs > Burst > Enable Compilation is on. Disabling it makes every Burst job fall back to managed execution.
Graph size and memory layout
Each graph entity stores its evaluation state as a bitmask sized from the state count when the graph starts. The runtime selects one of five fixed-capacity components, so a graph crossing a size boundary moves to a larger archetype and fits fewer entities per chunk.
The smallest tier covers graphs up to 191 states, which is comfortably above a typical authored graph, and Count mode always uses that smallest tier regardless of state count. The larger tiers extend well beyond any practical graph size.
The storage tier uses the final state count after Subgraphs have been injected. Extracting the same states into a Subgraph improves organization but does not reduce that count. Use a smaller separate graph, or select less referenced content before initialization, when the goal is to keep an agent in a smaller tier.
What to profile
- Close the State Designer window, then profile in the Editor for iteration and in a development player build for final numbers.
- In the Profiler’s Timeline view, confirm that the transition and traversal jobs are spread across worker threads. Single-threaded evaluation usually means only one graph is being evaluated.
- Use the Entities Systems window to identify which State Designer system is consuming time, and confirm that graphs which should be idle are not scheduling work.
- Check main-thread time separately. GameObject Action and Condition logic and the Shared Variable sync run there, so a main-thread spike points at node implementations rather than evaluation.
- Compare a representative agent count rather than one agent. Parallel evaluation shows its benefit only across many entities.