mysimulation/server/tso.common/Utils/GameThread.cs

275 lines
7.6 KiB
C#
Raw Permalink Normal View History

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<UpdateState> 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<UpdateHook> _UpdateHooks = new List<UpdateHook>();
private static UpdateHook[] _UpdateHooksCopy = new UpdateHook[0];
private static List<UpdateHook> _UpdateHooksRemove = new List<UpdateHook>();
private static Queue<Callback<UpdateState>> _UpdateCallbacks = new Queue<Callback<UpdateState>>();
private static Queue<Callback<UpdateState>> _UpdateCallbacksSwap = new Queue<Callback<UpdateState>>();
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<UpdateState> callback)
{
var newHook = new UpdateHook()
{
Callback = callback
};
lock (_UpdateHooks)
{
_UpdateHooks.Add(newHook);
}
return newHook;
}
public static void NextUpdate(Callback<UpdateState> 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<T> NextUpdate<T>(Func<UpdateState, T> callback)
{
TaskCompletionSource<T> task = new TaskCompletionSource<T>();
lock (_UpdateCallbacks)
{
_UpdateCallbacks.Enqueue(x =>
{
task.SetResult(callback(x));
});
}
return TimeoutAfter(task.Task, new TimeSpan(0, 0, 5));
}
public static async Task<TResult> TimeoutAfter<TResult>(Task<TResult> 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<Callback<UpdateState>> _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<UpdateHook> 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();
}
}
}