The Death of the Update Loop: Inside UniState

How a web-inspired architecture uses asynchronous execution and dependency injection to rescue game developers from state machine spaghetti.

6 min read • View on GitHub • More from bazyleu

A broken mechanical metronome with its pendulum snapped. A smooth ribbon of ink flows from its base, bypassing the rigid gears. This represents escaping the rigid, ticking Update() frame rate in favor of a continuous asynchronous flow.
Breaking free from the rigid frame tick of MonoBehaviour.

UniRx has made it easier to subscribe to and filter the state of variables, but strong references between classes remain a problem. To address this, I created a simple state management library, UniState, which is modeled after Zustand but adjusted for game development. UniState centers around the Store class, designed to manage

beautyfullcastle, Author/Maintainer · Unity Discussions
Key Takeaways

Escaping the Frame Rate

For years, Unity developers have been trapped in the rigid execution of MonoBehaviour.Update(). Building complex state machines meant wrestling with frame-by-frame polling, leading to spaghetti code and tight coupling. UniState abandons this paradigm entirely. It relies on a purely asynchronous lifecycle using UniTask.

Instead of ticking a state machine every frame, UniState allows states to wait for animations, network responses, or user input natively. This happens without blocking the main thread or relying on convoluted callback chains. Memory allocations are kept to an absolute minimum, which is critical for mobile game performance.

public async UniTask Execute(CancellationToken cancellationToken)
{
    // Wait for an animation to finish natively
    await _animator.PlayAsync("OpeningTransition", cancellationToken);
    
    // Proceed with logic without a single Update() tick
    _networkService.Connect();
}

Solving the Payload Problem

Most state machines fail when trying to pass data between isolated states. Moving a Level ID from a loading screen to a gameplay state usually requires global variables or messy singletons. UniState solves this elegantly through its IState implementation.

The Async State Handoff: Injecting payloads between isolated states.

The system captures the payload during the transition request and injects it safely into the next state before execution. This guarantees type safety and eliminates the need for shared mutable state.

Importing Web Architecture to Games

The architectural shift in UniState did not come from a vacuum. The creator, known as bazyleu, transitioned from game client development to backend APIs and web infrastructure. This exposure highlighted the flaws of strong class references often found in reactive Unity frameworks.

Portrait of bazyleu

The Unix Philosophy of Dependency Injection

UniState does not force a proprietary dependency injection container on the developer. It acts as a bridge. By utilizing an ITypeResolver interface, the framework integrates seamlessly with standard Unity DI solutions like VContainer and Zenject.

A standardized shipping container suspended mid-air. Two different styles of industrial cranes reach out with identical locking mechanisms that perfectly fit the container's corners. This illustrates how UniState's logic remains standard while different DI frameworks can maneuver it.
The ITypeResolver abstraction allows different DI frameworks to handle standardized state logic.
FeatureTraditional MonoBehaviourUniState Architecture
ExecutionFrame-bound Update() tickPurely asynchronous UniTask
Data PassingGlobal singletons or tight couplingType-safe Payload injection
Memory ProfileHigh allocation on state swapsZero-allocation transitions
DependenciesHardcoded GetComponent()Injected via ITypeResolver

This modularity means two developers can work on separate states in total isolation. The shipping container of logic remains unchanged, regardless of which crane picks it up.