-
Notifications
You must be signed in to change notification settings - Fork 14
Chore: mvc state machine refactor #6541
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
base: dev
Are you sure you want to change the base?
Conversation
…ss states as param array in the constructor
…f `ChangeState<TState>` across Chat and FSM modules; removed entering initial state in the constructor
|
Windows and Mac build successful in Unity Cloud! You can find a link to the downloadable artifact below. |
| /// </summary> | ||
| public class BlockedChatInputState : ChatInputState | ||
| { | ||
| private readonly MVCStateMachine<ChatInputState> stateMachine; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a questionable improvement as it moves the generic constraint from the base class to the implementations themselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
previously State could have only StateMachine that works on the same State type via its method injection SetMachineAndContext. But even with this constraint States still could have another StateMachine injected directly via constructor (for example if I have some higher level UI/Canvas with its (meta-)states and inside one of such state I want to change states of some child UI of that canvas - I would inject another StateMachine via constructor quite similar to what is currently done in the PR). Thus, it wasn't really a constraint, but a forced dependency.
With removal of SetMachineAndContext (forced injection of the state-machine) it become clear which state really can change to another state or operate on another subset of states (via explicit type in the constructor parameter).
Currently, I found 3 states that don't need forced dependency on the StateMachine:
HiddenChatInputState, InitChatState, HiddenChatState
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my problem with such forced dependency which is hidden somewhere inside a base class is that when I start to read the code and try to figure out who is making state transitions and who is not - I need to use "Find Usages" of the base MVCStateMachine.ChangeState that gives me all usages including not related to my current BaseState, and go from there.
Not a biggest problem of course, and I can move this injection back (but making it base class constructor injection instead of method injection)
Explorer/Assets/DCL/Chat/_Refactor/ChatStates/ChatStateContext.cs
Outdated
Show resolved
Hide resolved
| void Enter(); | ||
| } | ||
|
|
||
| public interface IPayloadedState<in TPayload> : IExitableState |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you justify the usage of IPayloadedState and, thus, the split between IExitableState and IState? In the chat states as far as I recall, there are "parameterized" transitions which are made via a shared state instead of the input parameters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to implement state machine for the Authentication flow and there I would like to Enter some states with parameters. For this refactor I can remove this implementation and merge Exit/Enter into one IState interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
merged it into one interface here 614c59d
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
examples of payloaded states are in this PR
Explorer/Assets/DCL/AuthenticationScreenFlow/AuthenticationFlowStateMachine/ProfileFetchingAuthState.csExplorer/Assets/DCL/AuthenticationScreenFlow/AuthenticationFlowStateMachine/ProfileFetchingAuthState.cs
It is exactly example of the state that implements only IPayloadedState and calling Enter on it without payload would be incorrect. However, there are also states that can implement both IState and IPayloadedState and support both entrance - like this one
https://github.com/decentraland/unity-explorer/pull/6559/files#diff-0d6efdf4b177b277394e07ec898cf444b03f7f3ab52152a85f54040b90f38812R30-R49
Pull Request Description
Simplified and improved the MVC State Machine architecture by removing unnecessary abstractions, applying SOLID principles, and introducing a hybrid approach that combines the benefits of generic typing with interface flexibility.
What does this PR change?
1. Removed TContext from State Machine.
Contextfrom the StateMachine is replaced by direct injection via constructor, becauseContextabstraction had several issues:Replaced solution improves encapsulation and readability — it is clear what is the dependency of each state.
Deleted files:
ChatStateContext.csChatInputStateContext.cs2. Removed State Machine injection into base state
Similarly, injection of
stateMachineand itsdisposeCancellationTokeninto the state were replaced by direct injection in the state constructor, since not every state needed it. Indeed, only one state was depending on thedisposeToken, and only states that perform transitions need the machine reference.3. Removed unused
Update/LateUpdatemethods (YAGNI)Unused methods such as
Update(float deltaTime)andLateUpdate(float deltaTime)were removed, since no state was using them. Moreover, I don't see it coming for such UI-based state machine. More probably, we will extend state machine with transitions rather than with Update-loop behavior.4. Replaced CRTP with Interface-based approach
CRTP (Curiously Recurring Template Pattern) for
MVCStatewas replaced by a simplified approach via interfaces:Before (CRTP):
After (Interfaces):
5. Hybrid Generic + Interface approach for State Machine
Combined the best of both worlds — typed
CurrentStatewith interface flexibility:Benefits:
✅ Compile-time type safety for
CurrentState✅ Support for payloaded states via
IPayloadedState<T>✅ Simpler to understand than CRTP
✅ Flexible — states can implement multiple interfaces
6. Simplified IndependentMVCState
Removed unnecessary generic layers and context from
IndependentMVCState. Now it's a single simple class withActivate()/Deactivate()pattern.Migration
States now receive their dependencies explicitly via constructor:
Files Changed
MVCStateMachine.cs— rewritten with generic + interface approachMVCState.cs— deleted, replaced byIState.csIState.cs— new, containsIExitableState,IState,IPayloadedState<T>IndependentMVCState.cs— simplifiedTest Instructions
This refactor touches mainly chat, but without adding or changing its functionality.
In theory, chat should work as in current
dev, so please make a regression test on the chat features.If possible, please verify that found issue is not presented in the
devQuality Checklist
Code Review Reference
Please review our Code Review Standards before submitting.