using FSO.Common.Rendering.Framework.Model; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace FSO.Common.Utils { public class GameThreadInterval { private Callback Callback; private long Interval; private double EndTime = -1; private UpdateHook _TickHook; private bool _Clear; public GameThreadInterval(Callback callback, long interval) { Callback = callback; Interval = interval; _TickHook = GameThread.EveryUpdate(Tick); } public void Clear() { _Clear = true; } private void Tick(UpdateState state) { if (_Clear) { _TickHook.Remove(); return; } var now = state.Time.TotalGameTime.TotalMilliseconds; if (EndTime == -1) { EndTime = now + Interval; } if (EndTime <= now) { Callback(); EndTime = now + Interval; } } } public class GameThreadTimeout { private Callback Callback; private long Delay; private double EndTime = -1; private UpdateHook _TickHook; private bool _Clear; public GameThreadTimeout(Callback callback, long delay) { Callback = callback; Delay = delay; _TickHook = GameThread.EveryUpdate(Tick); } public void Clear(){ _Clear = true; } private void Tick(UpdateState state) { if (_Clear){ _TickHook.Remove(); return; } var now = state.Time.TotalGameTime.TotalMilliseconds; if (EndTime == -1){ EndTime = now + Delay; } if(EndTime <= now){ _TickHook.Remove(); Callback(); } } } public class UpdateHook { public bool RemoveNext = false; public Callback Callback; public void Remove(){ RemoveNext = true; } } public class GameThread { public static bool Killed; public static bool NoGame; public static EventWaitHandle OnKilled = new EventWaitHandle(false, EventResetMode.ManualReset); public static event Action KilledEvent; public static Thread Game; public static bool UpdateExecuting; private static List _UpdateHooks = new List(); private static UpdateHook[] _UpdateHooksCopy = new UpdateHook[0]; private static List _UpdateHooksRemove = new List(); private static Queue> _UpdateCallbacks = new Queue>(); private static Queue> _UpdateCallbacksSwap = new Queue>(); public static AutoResetEvent OnWork = new AutoResetEvent(false); public static void SetKilled() { Killed = true; OnKilled.Set(); KilledEvent?.Invoke(); } public static GameThreadTimeout SetTimeout(Callback callback, long delay) { var result = new GameThreadTimeout(callback, delay); return result; } public static GameThreadInterval SetInterval(Callback callback, long delay) { var result = new GameThreadInterval(callback, delay); return result; } //I know we already have a way to do this with IUIProcess but we need a way for other libs that dont //have the UI code for reference public static UpdateHook EveryUpdate(Callback callback) { var newHook = new UpdateHook() { Callback = callback }; lock (_UpdateHooks) { _UpdateHooks.Add(newHook); } return newHook; } public static void NextUpdate(Callback callback) { lock (_UpdateCallbacks) { _UpdateCallbacks.Enqueue(callback); } OnWork.Set(); } public static void InUpdate(Callback callback) { if (IsInGameThread() && UpdateExecuting){ callback(); }else{ NextUpdate(x => callback()); } } public static bool IsInGameThread() { var thread = Thread.CurrentThread; if (thread == Game || NoGame) { return true; } return false; } public static Task NextUpdate(Func callback) { TaskCompletionSource task = new TaskCompletionSource(); lock (_UpdateCallbacks) { _UpdateCallbacks.Enqueue(x => { task.SetResult(callback(x)); }); } return TimeoutAfter(task.Task, new TimeSpan(0, 0, 5)); } public static async Task TimeoutAfter(Task task, TimeSpan timeout) { using (var timeoutCancellationTokenSource = new CancellationTokenSource()) { var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token)); if (completedTask == task) { timeoutCancellationTokenSource.Cancel(); return await task; // Very important in order to propagate exceptions } else { return default(TResult); } } } public static void DigestUpdate(UpdateState state) { Queue> _callbacks; lock (_UpdateCallbacks) { // Swap the active callbacks queue with the second one, so we can // process entries without fear of more being added. _callbacks = _UpdateCallbacks; _UpdateCallbacks = _UpdateCallbacksSwap; _UpdateCallbacksSwap = _callbacks; } while (_callbacks.Count > 0) { _callbacks.Dequeue()(state); } int hookCount; UpdateHook[] _hooks; List toRemove = _UpdateHooksRemove; lock (_UpdateHooks) { hookCount = _UpdateHooks.Count; if (_UpdateHooksCopy.Length < _UpdateHooks.Count) { _UpdateHooksCopy = _UpdateHooks.ToArray(); } else { _UpdateHooks.CopyTo(_UpdateHooksCopy); } _hooks = _UpdateHooksCopy; } for (int i = 0; i < hookCount; i++) { var item = _hooks[i]; item.Callback(state); if (item.RemoveNext) { toRemove.Add(item); } } if (toRemove.Count > 0) { lock (_UpdateHooks) { foreach (var rem in toRemove) _UpdateHooks.Remove(rem); } toRemove.Clear(); } //finally, check cache controller TimedReferenceController.Tick(); } } }