-
Notifications
You must be signed in to change notification settings - Fork 1
/
MultiTrackStateMachine.cs
112 lines (99 loc) · 4.86 KB
/
MultiTrackStateMachine.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace BAStudio.StatePattern
{
/// <summary>
/// SideTracks get updated by order.
/// SideTrack get notified when main state changed.
/// SideTracks receives events as well.
/// AutoStateCache is not shared bewteen tracks.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TRACK">Must be ~int32.</typeparam>
public partial class MultiTrackStateMachine<T, TRACK> : StateMachine<T> where TRACK : unmanaged, System.Enum
{
public MultiTrackStateMachine(T target) : base(target)
{
if (System.Enum.GetUnderlyingType(typeof(TRACK)) != typeof(int))
throw new ArgumentOutOfRangeException("The underlying type of the Enum must be int");
var minMax = EnumExtension.MinMaxInt<TRACK>();
if (minMax.min < 0) throw new ArgumentOutOfRangeException("The first value of the enum must be bigger then zero");
if (minMax.max > 8) LogFormat("The TRACK enum has a max underlying value of {0}, internally a State[{0}] is being created, are you sure about this?", minMax.max);
SideTracks = new State[minMax.max + 1];
}
public State[] SideTracks { get; protected set; }
public event Action<TRACK, State, State> OnSideTrackStateChanging;
public event Action<TRACK, State, State> OnSideTrackStateChanged;
protected Dictionary<(TRACK, Type), State> AutoSideTrackStateCache { get; set; }
/// <summary>
/// <para>Change the state to the provide instance, with parameter supplied.</para>
/// <para>It is recommended to use the generic version instead.</para>
/// <para>However, this could be useful in situations like state instances carry different data, or a non-stateful state is shared by massive amount of StateMachines.</para>
/// </summary>
public virtual void ChangeSideTrackState(TRACK track, State state, object parameter = null)
{
int tId = track.AsInt32();
var prev = SideTracks[tId];
PreSideTrackStateChange(prev, state, track);
SideTracks[tId] = state;
DeliverComponents(state);
state.OnEntered(this, prev, Subject, parameter);
PostSideTrackStateChange(prev, state, track);
}
/// <summary>
/// <para>Change the state to the specified type, with parameter supplied.</para>
/// <para>The StateMachine automatically manages and keeps the state objects used.</para>
/// </summary>
public virtual void ChangeSideTrackState<S>(TRACK track, object parameter = null) where S : State, new()
{
if (AutoSideTrackStateCache == null) AutoSideTrackStateCache = new Dictionary<(TRACK, Type), State>();
(TRACK track, Type) key = (track, typeof(S));
if (!AutoSideTrackStateCache.ContainsKey(key)) AutoSideTrackStateCache.Add(key, new S());
ChangeSideTrackState(track, AutoSideTrackStateCache[key], parameter);
}
protected virtual void PreSideTrackStateChange (State fromState, State toState, TRACK sideTrack)
{
if (debugOutput != null) LogFormat("A StateMachine<{0}> is switching SIDETRACK#{3} from {1} to {2}.", Subject.GetType().Name, fromState?.GetType()?.Name, toState.GetType().Name, sideTrack);
fromState?.OnLeaving(this, toState, Subject);
OnSideTrackStateChanging?.Invoke(sideTrack, fromState, toState);
}
protected virtual void PostSideTrackStateChange (State fromState, State toState, TRACK sideTrack)
{
if (debugOutput != null) LogFormat("A MultiTrackStateMachine<{0}> has switched SIDETRACK#{3} from {1} to {2}.", Subject.GetType().Name, fromState?.GetType()?.Name, toState.GetType().Name, sideTrack);
SendEvent(new SideTrackStateChangedEvent(sideTrack, fromState, toState));
OnSideTrackStateChanged?.Invoke(sideTrack, fromState, toState);
}
public override void Update ()
{
if (UpdatePaused) return;
if (Subject == null) throw new System.NullReferenceException("Target is null.");
UpdateMainState();
UpdateSideTracks();
UpdatePopStates();
}
protected void UpdateSideTracks ()
{
for (int i = 0; i < SideTracks.Length; i++)
if (SideTracks[i] is not NoOpState)
{
if (SideTracks[i] == null) throw new System.NullReferenceException("SideTrack is null. Did you set a state after instantiate this controller?");
else SideTracks[i].Update(this, Subject);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void SendEventToSideTracks <E> (E ev)
{
for (int i = 0; i < SideTracks.Length; i++)
if (SideTracks[i] is IEventReceiverState<T, E> ers) ers.ReceiveEvent(this, Subject, ev);
}
public override bool SendEvent<E> (E ev)
{
LogFormat("A MultiTrackStateMachine<{0}> is invoking {1}, the active state (receiver) is {2}", Subject.GetType().Name, CurrentState?.GetType()?.Name, ev.GetType().Name);
SendEventToCurrentState(ev);
SendEventToSideTracks(ev);
SendEventToPopupStates(ev);
return true;
}
}
}