Home

Awesome

Enum Dispatcher

Action dispatching is an important part in Flux and Redux, to ensure any "data store" could change its data without caring about anything else other than user's action. Unit testing paradise! Just dispatch actions and see what the data have become.

I am bringing this workflow to Unity, but erasing the "string action label" pain point of actions in JavaScript by using C#'s enum instead. Redux's designer said that, he advised using string as an action's key rather than JS Symbol because it is easily serializable and allows time travel. In Unity I care more about C# tooling provided by enum.

Action principle of Redux / Flux

Benefits in Unity

The "classic" way of handling action in Unity is something like EventTrigger will call a public method connected in the inspector, as the starting point. Then that object may further connected to other object via exposed variable, it ask the required objects to do something in chain. This chain is troubling when it goes on for about 2-3 layers, you started wondering who called this or change the data, and that "who" is not your user.

It is equivalent to "cascade update problem" that Facebook mentioned in their Flux documentation.

By only do something in itself based on solely action, you rarely need to chain (public) method calls to tell others to be in sync because others are also handling the action in themselves. Receiving a broadcasted action thanks to callback magic may seems cheating at first, but Unity's connected fields are not that strong either. I think it even became missing and cause bigger problem than callbacks.

Terms

Coding style

Declaration and dispatching

Your action declaration may looks like this, along with some simplified usages :

public class MainMenu
{
    public enum Action
    {
        QuitGame,
        ToModeSelect,
        ToCredits,
        TouchedEmptyArea,
        JoystickMoved,
        [F(Navigation)] LeftButton, //F attribute is short for flags.
        [F(Navigation)] RightButton,
    }

    public enum PayloadKey
    {
        TouchCoordinate,
        DirectionVector,
        Weight,
    }

    public const string Navigation = nameof(Navigation);

    public void ToModeSelectButtonOnClick()
    {
        //Without payload
        Dispatcher.Dispatch(MainMenu.Action.ToModeSelect);
    }

    public void EmptyAreaOnPointerDown()
    {
        //With payload (as a tuple of the key and `object`)
        Dispatcher.Dispatch(MainMenu.Action.TouchedEmptyArea, 
            (MainMenu.PayloadKey.TouchCoordinate, new Vector2(100,150))
        );
    }

    public void JoystickOnMove()
    {
        //Recommended style to enhance readability with multiple payload is to explicitly type `payload:` parameter name.
        Dispatcher.Dispatch(MainMenu.Action.JobstickMoved, 
            payload:
            (MainMenu.PayloadKey.DirectionVector, new Vector2(1,0)),
            (MainMenu.PayloadKey.Weight, 13)
        );
    }
}

Action handling

How to directly check for that exact action with if : Use .Is.

private void OnAction(DispatchAction action)
{
    if (action.Is(MusicSelect.Action.SelectSong))
    {
        ...
    }
    else if(action.Is(MusicSelect.Action.SelectDifficulty))
    {
        ...
    }
}

How to handle 2 categories at once with if and switch case : Use .Category<T> then use the generic-typed out variable with switch.

private void OnAction(DispatchAction action)
{
    if (action.Category<MusicSelect.Action>(out var actMs)) switch (actMs)
    {
        case MusicSelect.Action.SelectSong:
            ...
    }
    else if (action.Category<MusicStart.Action>(out var act)) switch (act)
    {
        case MusicStart.Action.Begin:
            ...
        case MusicStart.Action.BeginEditor:
            ...
        case MusicStart.Action.ToggleRivalView:
            ...
        case MusicStart.Action.ChangeChartDifficulty:
            ...
    }

    ...
}

For how to do it in C# Jobs, please see the Tests folder.

Why enum? Not string?

Pain points in doing so

Dependencies

Why it has to do anything with ECS at all? I don't want to depends on ECS package.

I could design it as a static enum dispatcher where anyone can receive the action. However I decided to bring ECS into play :

And so Enum Dispatcher's asmdef requires Entity package present. Install them from Package Manager.

Also it is a good bridge from normal world to ECS. For example, Normally you connect the uGUI Button's On Click to some public methods. It is not possible to connect with ECS's system since they are not in the scene. With Enum Dispatcher, all uGUI Button in the game no longer ever have to contains any logic other than dispatching an enum action. ECS system is now able to respond to button press, also your MonoBehaviour things can subscribe as an action receiver as well. Also it is awesome for unit testing now that you can mock user's behaviour by just dispatching actions over and over.

Architecture

How to use

Please see usage examples from the Tests folder, where you will witness an epic fight with monsters.