Client LuaCsForBarotrauma
CoroutineManager.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Threading;
7 
8 namespace Barotrauma
9 {
10  abstract class CoroutineStatus
11  {
15 
16  public abstract bool CheckFinished(float deltaTime);
17  public abstract bool EndsCoroutine(CoroutineHandle handle);
18  }
19 
21  {
22  private enum StatusValue
23  {
25  }
26 
27  private readonly StatusValue value;
28 
29  private EnumCoroutineStatus(StatusValue value) { this.value = value; }
30 
31  public new static readonly EnumCoroutineStatus Running = new EnumCoroutineStatus(StatusValue.Running);
32  public new static readonly EnumCoroutineStatus Success = new EnumCoroutineStatus(StatusValue.Success);
33  public new static readonly EnumCoroutineStatus Failure = new EnumCoroutineStatus(StatusValue.Failure);
34 
35  public override bool CheckFinished(float deltaTime)
36  {
37  return true;
38  }
39 
40  public override bool EndsCoroutine(CoroutineHandle handle)
41  {
42  if (value == StatusValue.Failure)
43  {
44  DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
45  }
46  return value != StatusValue.Running;
47  }
48 
49  public override bool Equals(object? obj)
50  {
51  return obj is EnumCoroutineStatus other && value == other.value;
52  }
53 
54  public override int GetHashCode()
55  {
56  return value.GetHashCode();
57  }
58 
59  public override string ToString()
60  {
61  return value.ToString();
62  }
63  }
64 
66  {
67  public readonly float TotalTime;
68 
69  private float timer;
70  private readonly bool ignorePause;
71 
72  public WaitForSeconds(float time, bool ignorePause = true)
73  {
74  timer = time;
75  TotalTime = time;
76  this.ignorePause = ignorePause;
77  }
78 
79  public override bool CheckFinished(float deltaTime)
80  {
81 #if !SERVER
82  if (ignorePause || !CoroutineManager.Paused)
83  {
84  timer -= deltaTime;
85  }
86 #else
87  timer -= deltaTime;
88 #endif
89  return timer <= 0.0f;
90  }
91 
92  public override bool EndsCoroutine(CoroutineHandle handle)
93  {
94  return false;
95  }
96  }
97 
98  sealed class CoroutineHandle
99  {
100  public readonly IEnumerator<CoroutineStatus> Coroutine;
101  public readonly string Name;
102 
104  public bool AbortRequested;
105 
106  public CoroutineHandle(IEnumerator<CoroutineStatus> coroutine, string name = "")
107  {
108  Coroutine = coroutine;
109  Name = string.IsNullOrWhiteSpace(name) ? (coroutine.ToString() ?? "") : name;
110  Exception = null;
111  }
112 
113  }
114 
115  // Keeps track of all running coroutines, and runs them till the end.
116  static class CoroutineManager
117  {
118  static readonly List<CoroutineHandle> Coroutines = new List<CoroutineHandle>();
119 
120  public static float DeltaTime { get; private set; }
121 
122  public static bool Paused { get; private set; }
123 
124  public static CoroutineHandle StartCoroutine(IEnumerable<CoroutineStatus> func, string name = "")
125  {
126  var handle = new CoroutineHandle(func.GetEnumerator(), name);
127  lock (Coroutines)
128  {
129  Coroutines.Add(handle);
130  }
131 
132  return handle;
133  }
134 
135  public static CoroutineHandle Invoke(Action action, float delay = 0f)
136  {
137  return StartCoroutine(DoInvokeAfter(action, delay));
138  }
139 
140  private static IEnumerable<CoroutineStatus> DoInvokeAfter(Action? action, float delay)
141  {
142  if (action == null)
143  {
144  yield return CoroutineStatus.Failure;
145  yield break;
146  }
147 
148  if (delay > 0.0f)
149  {
150  yield return new WaitForSeconds(delay);
151  }
152 
153  action();
154 
155  yield return CoroutineStatus.Success;
156  }
157 
158 
159  public static bool IsCoroutineRunning(string name)
160  {
161  lock (Coroutines)
162  {
163  return Coroutines.Any(c => c.Name == name);
164  }
165  }
166 
167  public static bool IsCoroutineRunning(CoroutineHandle handle)
168  {
169  lock (Coroutines)
170  {
171  return Coroutines.Contains(handle);
172  }
173  }
174 
175  public static void StopCoroutines(string name)
176  {
177  lock (Coroutines)
178  {
179  HandleCoroutineStopping(c => c.Name == name);
180  Coroutines.RemoveAll(c => c.Name == name);
181  }
182  }
183 
184  public static void StopCoroutines(CoroutineHandle handle)
185  {
186  lock (Coroutines)
187  {
188  HandleCoroutineStopping(c => c == handle);
189  Coroutines.RemoveAll(c => c == handle);
190  }
191  }
192 
193  private static void HandleCoroutineStopping(Func<CoroutineHandle, bool> filter)
194  {
195  // No lock here because all callers lock for us
196  foreach (CoroutineHandle coroutine in Coroutines)
197  {
198  if (filter(coroutine))
199  {
200  coroutine.AbortRequested = true;
201  }
202  }
203  }
204 
205  private static bool PerformCoroutineStep(CoroutineHandle handle)
206  {
207  var current = handle.Coroutine.Current;
208  if (current != null)
209  {
210  if (current.EndsCoroutine(handle) || handle.AbortRequested) { return true; }
211  if (!current.CheckFinished(DeltaTime)) { return false; }
212  }
213  if (!handle.Coroutine.MoveNext()) { return true; }
214  return false;
215  }
216 
217  private static bool IsDone(CoroutineHandle handle)
218  {
219 #if !DEBUG
220  try
221  {
222 #endif
223  return PerformCoroutineStep(handle);
224 #if !DEBUG
225  }
226  catch (Exception e)
227  {
228 #if CLIENT && WINDOWS
229  if (e is SharpDX.SharpDXException) { throw; }
230 #endif
231  DebugConsole.ThrowError("Coroutine " + handle.Name + " threw an exception: " + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
232  handle.Exception = e;
233  return true;
234  }
235 #endif
236  }
237  // Updating just means stepping through all the coroutines
238  private static readonly List<CoroutineHandle> coroutinePass = new List<CoroutineHandle>();
239  public static void Update(bool paused, float deltaTime)
240  {
241  Paused = paused;
242  DeltaTime = deltaTime;
243 
244  // Do not optimize this as a for loop directly over the Coroutines list!
245  // Coroutines are able to spawn new coroutines!
246  lock (Coroutines)
247  {
248  coroutinePass.AddRange(Coroutines);
249  }
250  foreach (var coroutine in coroutinePass)
251  {
252  if (!IsDone(coroutine)) { continue; }
253  lock (Coroutines)
254  {
255  Coroutines.Remove(coroutine);
256  }
257  }
258  coroutinePass.Clear();
259  }
260 
261  public static void ListCoroutines()
262  {
263  lock (Coroutines)
264  {
265  DebugConsole.NewMessage("***********");
266  DebugConsole.NewMessage($"{Coroutines.Count} coroutine(s)");
267  foreach (var c in Coroutines)
268  {
269  DebugConsole.NewMessage($"- {c.Name}");
270  }
271  }
272  }
273  }
274 }
readonly IEnumerator< CoroutineStatus > Coroutine
CoroutineHandle(IEnumerator< CoroutineStatus > coroutine, string name="")
abstract bool CheckFinished(float deltaTime)
static CoroutineStatus Running
static CoroutineStatus Failure
static CoroutineStatus Success
abstract bool EndsCoroutine(CoroutineHandle handle)
static new readonly EnumCoroutineStatus Running
override bool Equals(object? obj)
static new readonly EnumCoroutineStatus Failure
override bool CheckFinished(float deltaTime)
static new readonly EnumCoroutineStatus Success
override bool EndsCoroutine(CoroutineHandle handle)
override bool EndsCoroutine(CoroutineHandle handle)
override bool CheckFinished(float deltaTime)
WaitForSeconds(float time, bool ignorePause=true)