A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5
6namespace Unity.VisualScripting
7{
8 public abstract class State : GraphElement<StateGraph>, IState
9 {
10 public class Data : IGraphElementData
11 {
12 public bool isActive;
13
14 public bool hasEntered;
15 }
16
17 public class DebugData : IStateDebugData
18 {
19 public int lastEnterFrame { get; set; }
20
21 public float lastExitTime { get; set; }
22
23 public Exception runtimeException { get; set; }
24 }
25
26 public IGraphElementData CreateData()
27 {
28 return new Data();
29 }
30
31 public IGraphElementDebugData CreateDebugData()
32 {
33 return new DebugData();
34 }
35
36 [Serialize]
37 public bool isStart { get; set; }
38
39 [DoNotSerialize]
40 public virtual bool canBeSource => true;
41
42 [DoNotSerialize]
43 public virtual bool canBeDestination => true;
44
45 public override void BeforeRemove()
46 {
47 base.BeforeRemove();
48
49 Disconnect();
50 }
51
52 public override void Instantiate(GraphReference instance)
53 {
54 base.Instantiate(instance);
55
56 var data = instance.GetElementData<Data>(this);
57
58 if (this is IGraphEventListener listener && data.isActive)
59 {
60 listener.StartListening(instance);
61 }
62 else if (isStart && !data.hasEntered && graph.IsListening(instance))
63 {
64 using (var flow = Flow.New(instance))
65 {
66 OnEnter(flow, StateEnterReason.Start);
67 }
68 }
69 }
70
71 public override void Uninstantiate(GraphReference instance)
72 {
73 if (this is IGraphEventListener listener)
74 {
75 listener.StopListening(instance);
76 }
77
78 base.Uninstantiate(instance);
79 }
80
81 #region Poutine
82
83 protected void CopyFrom(State source)
84 {
85 base.CopyFrom(source);
86
87 isStart = source.isStart;
88 width = source.width;
89 }
90
91 #endregion
92
93 #region Transitions
94
95 public IEnumerable<IStateTransition> outgoingTransitions => graph?.transitions.WithSource(this) ?? Enumerable.Empty<IStateTransition>();
96
97 public IEnumerable<IStateTransition> incomingTransitions => graph?.transitions.WithDestination(this) ?? Enumerable.Empty<IStateTransition>();
98
99 protected List<IStateTransition> outgoingTransitionsNoAlloc => graph?.transitions.WithSourceNoAlloc(this) ?? Empty<IStateTransition>.list;
100
101 public IEnumerable<IStateTransition> transitions => LinqUtility.Concat<IStateTransition>(outgoingTransitions, incomingTransitions);
102
103 public void Disconnect()
104 {
105 foreach (var transition in transitions.ToArray())
106 {
107 graph.transitions.Remove(transition);
108 }
109 }
110
111 #endregion
112
113 #region Lifecycle
114
115 public virtual void OnEnter(Flow flow, StateEnterReason reason)
116 {
117 var data = flow.stack.GetElementData<Data>(this);
118
119 if (data.isActive) // Prevent re-entry from Any State
120 {
121 return;
122 }
123
124 data.isActive = true;
125
126 data.hasEntered = true;
127
128 foreach (var transition in outgoingTransitionsNoAlloc)
129 {
130 // Start listening for the transition's events
131 // before entering the state in case OnEnterState
132 // actually instantly triggers a transition via event
133 // http://support.ludiq.io/topics/261-event-timing-issue/
134 (transition as IGraphEventListener)?.StartListening(flow.stack);
135 }
136
137 if (flow.enableDebug)
138 {
139 var editorData = flow.stack.GetElementDebugData<DebugData>(this);
140
141 editorData.lastEnterFrame = EditorTimeBinding.frame;
142 }
143
144 OnEnterImplementation(flow);
145
146 foreach (var transition in outgoingTransitionsNoAlloc)
147 {
148 try
149 {
150 transition.OnEnter(flow);
151 }
152 catch (Exception ex)
153 {
154 transition.HandleException(flow.stack, ex);
155 throw;
156 }
157 }
158 }
159
160 public virtual void OnExit(Flow flow, StateExitReason reason)
161 {
162 var data = flow.stack.GetElementData<Data>(this);
163
164 if (!data.isActive)
165 {
166 return;
167 }
168
169 OnExitImplementation(flow);
170
171 data.isActive = false;
172
173 if (flow.enableDebug)
174 {
175 var editorData = flow.stack.GetElementDebugData<DebugData>(this);
176
177 editorData.lastExitTime = EditorTimeBinding.time;
178 }
179
180 foreach (var transition in outgoingTransitionsNoAlloc)
181 {
182 try
183 {
184 transition.OnExit(flow);
185 }
186 catch (Exception ex)
187 {
188 transition.HandleException(flow.stack, ex);
189 throw;
190 }
191 }
192 }
193
194 protected virtual void OnEnterImplementation(Flow flow) { }
195
196 protected virtual void UpdateImplementation(Flow flow) { }
197
198 protected virtual void FixedUpdateImplementation(Flow flow) { }
199
200 protected virtual void LateUpdateImplementation(Flow flow) { }
201
202 protected virtual void OnExitImplementation(Flow flow) { }
203
204 public virtual void OnBranchTo(Flow flow, IState destination) { }
205
206 #endregion
207
208 #region Widget
209
210 public const float DefaultWidth = 170;
211
212 [Serialize]
213 public Vector2 position { get; set; }
214
215 [Serialize]
216 public float width { get; set; } = DefaultWidth;
217
218 #endregion
219
220 #region Analytics
221
222 public override AnalyticsIdentifier GetAnalyticsIdentifier()
223 {
224 var aid = new AnalyticsIdentifier
225 {
226 Identifier = GetType().FullName,
227 Namespace = GetType().Namespace,
228 };
229 aid.Hashcode = aid.Identifier.GetHashCode();
230 return aid;
231 }
232
233 #endregion
234 }
235}