Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does Execute function mandatory to return IStateTransitionFacade Transition #75

Open
onedevapp opened this issue Jan 12, 2025 · 3 comments
Labels

Comments

@onedevapp
Copy link

onedevapp commented Jan 12, 2025

I've tried to implement the unistate, to replace my exisitng state controller. So the execute funciton is nearly forcing to implement the transition to either goback, goexit or goto, making state fully depeneded on _view.WaitForClick to get some action done.

public override async UniTask<StateTransitionInfo> Execute(CancellationToken token)
{
    await _view.Show(token);
    await _view.WaitForClick(token);
    return Transition.GoBack();
}
  1. Instead make it univoid and make it still avaiable to call transition.goback or transition.exit so that multiple button clicks can redirect based on the user needs and no more waiting.
  2. ealier ive used, enum for state mapping and calling state transition using messagepipe, every state has its own scope and attached to view along with presenter as entry point. But in uniState, unable to do the same setup with unistate. can u share demo on how to achieve this
  3. Also when using with addressables, like to have prewarm where before calling goto to some state, ive to prewarm state and then requesting to goto will be a useful feature.
@bazyleu
Copy link
Owner

bazyleu commented Jan 15, 2025

Thanks for reaching out and sharing your questions about using UniState as a replacement for your existing state controller. Below are some suggestions and clarifications for each of the points you raised:

  1. The philosophy behind UniState is that it uses straightforward asynchronous code, so states are required to return results. However, you can still write in the style you mentioned - without having to do await _view.WaitForClick(token);. Here’s an example:
           UniTaskCompletionSource<StateTransitionInfo> _completionSource = new UniTaskCompletionSource<StateTransitionInfo>();

            public override UniTask<StateTransitionInfo> Execute(CancellationToken token)
            {
                await _view.Show(token);
                // Add subscription to OnExitButtonClicked and OnNextButtonClicked
                return await _completionSource.Task;
            }

            private void OnExitButtonClicked() => _completionSource.TrySetResult(Transition.GoToExit());
            private void OnNextButtonClicked() => _completionSource.TrySetResult(Transition.GoTo<NextState>());
  1. Unfortunately, I am not entirely sure I understand your question. Maybe the answer you’re looking for can be found in the What is a State section of the sample code. If you still can’t find the solution, please share a code sample and describe what you are trying to achieve, and I will try to help.

  2. I would not recommend doing the prewarm immediately before calling GoTo. Instead, here are three approaches you could consider, each suitable for different scenarios:
    (a) Use the public async UniTask Initialize(CancellationToken token) method within the state, and add the cleanup logic in Disposables. This is a good approach for prewarming resources. The core idea is to initialize the state’s resources right inside the state itself and release them when exiting (no need to manual disposing, will be done automatically).

        public class BarState : StateBase
        {
            public override UniTask<StateTransitionInfo> Execute(CancellationToken token)
            {
                //..

                return Transition.GoTo<FooState>();
            }
        }

        public class FooState : StateBase
        {
            private const string AddressableFooKey = "FooViewKey";
            private readonly IAdressableManager _addressableManager;

            private IView _view;

            public FooState(IAdressableManager addressableManager)
            {
                _addressableManager = addressableManager;
            }

            public override async UniTask Initialize(CancellationToken token)
            {
                _view = await _addressableManager.Prewarn(AddressableFooKey, token);

                Disposables.Add(()=> _addressableManager.Cleanup(_view));
                //  Disposables.Add(_view); // another option if IView is disposable
            }

            public override UniTask<StateTransitionInfo> Execute(CancellationToken token)
            {
                _view.Show();
                // ...
            }
        }

(b) If prewarming takes time and you need to ensure a seamless transition between states, you can use approach (a) in combination with the [InitializeOnStateTransition] attribute (link). In this scenario, FooState is initialized before BarState’s Exit() callback is triggered. That means you can hide UI/Prefabs/GameObjects in BarState.Exit(), and right after that, FooState will already be prewarmed.

(c) If your Addressable assets are intended for use across multiple states, and it’s more efficient to prewarm them once, the optimal solution is to employ an internal state machine. Here’s how it works: the root state handles the prewarm step and then invokes the internal state machine that processes all states using those assets. When the internal state machine finishes, the root state also completes, and it can then clean up the Addressable assets. I recommend following the principle that whoever allocates the resources should also release them. This is why states provide a Disposables property—to make the cleanup process simpler.

        public class BazState : StateBase
        {
            private const string AddressableBazKey = "SomeKey";
            private readonly IAdressableManager _addressableManager;

            public BazStateBazState(IAdressableManager addressableManager)
            {
                _addressableManager = addressableManager;
            }

            public override async UniTask Initialize(CancellationToken token)
            {
                var prewarmedAsset = await _addressableManager.Prewarn(AddressableBazKey, token);

                Disposables.Add(prewarmedAsset);
            }

            public override UniTask<StateTransitionInfo> Execute(CancellationToken token)
            {
                var stateMachine = StateMachineFactory.Create<StateMachine>();

                await stateMachine.Execute<NextState>(token);

                return Transition.GoToExit();
            }
        }

@onedevapp
Copy link
Author

onedevapp commented Jan 20, 2025

For point 2. instead of calling Transition.GoTo() or Transition.GoToExit() im using messagepipe (https://github.com/Cysharp/MessagePipe) messaging library which can be integrated into vcontainer (https://vcontainer.hadashikick.jp/integrations/messagepipe) directly. Now at any point ill be publishing msg event for screen transition so that i dont need Transition.GoTo() at all.

@bazyleu
Copy link
Owner

bazyleu commented Jan 22, 2025

When you call Transition.GoToExit() (or any other transition), it doesn’t immediately create a state. Instead, it produces a StateTransitionInfo object, which the state machine uses later to create the required state. This means you can create a collection of StateTransitionInfo objects and store them in a dictionary, keyed by the message/event type/string (or any other identifier), with the StateTransitionInfo as the value.

The state can then wait for an event, look it up in your dictionary, and return the corresponding transition. If you want to build your application around this approach, you can move this logic into your base state (inheriting from UniState.BaseState) and then let all your other states inherit from your base. Here is a rough sketch of how it might look:

    public abstract class MessageStateBase : StateBase
    {
        private UniTaskCompletionSource<StateTransitionInfo> _completionSource = new();
        private Dictionary<string, StateTransitionInfo> _transitionDictionary = new();

        // Write state specific logic here for each separate state
        protected abstract UniTask StateLogic(CancellationToken token);

        public override UniTask Initialize(CancellationToken token)
        {
            // Note! Just an example how dictionary can be filled.
            // In real project it is better to share data between states but not fill it each time
            _transitionDictionary = new Dictionary<string, StateTransitionInfo>()
            {
                { "message_foo", Transition.GoTo<FooState>() },
                { "message_bar", Transition.GoTo<BarState>() },
                { "message_exit", Transition.GoToExit() }
            };

            return base.Initialize(token);
        }

        public async override UniTask<StateTransitionInfo> Execute(CancellationToken token)
        {
            await StateLogic(token);

            // Add subscription to OnMessageReceived

            return await _completionSource.Task;
        }

        // Callback on receiving message from any message framework
        private void OnMessageReceived(string message) =>
            _completionSource.TrySetResult(_transitionDictionary[message]);
    }

Additionally, if you want to fill a collection of transitions outside the state, you can skip using the Transition property in the state and directly use StateTransitionFactory. It has an API that lets you create any transition you need.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants