using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; namespace Barotrauma { public class SingleThreadWorker { private ConcurrentQueue ActionQueue; public static SingleThreadWorker Instance = new SingleThreadWorker(); private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); private readonly SemaphoreSlim actionSignal = new SemaphoreSlim(0); private readonly Task workerTask; private bool disposed; public static readonly SemaphoreSlim SingleThreadActionStandbySignal = new SemaphoreSlim(1); /// /// Initilize a SingleThreadWorker /// SingleThreadWorker or STW for short is a FIFO queue ensure single-thread execution of a series of actions. /// public SingleThreadWorker() { ActionQueue = new ConcurrentQueue(); workerTask = CreateProcessTask(cancellationTokenSource.Token); } public void Dispose() { if (disposed) { return; } disposed = true; cancellationTokenSource.Cancel(); try { actionSignal.Release(); workerTask.Wait(2); } catch (AggregateException) { } catch (ObjectDisposedException) { } cancellationTokenSource.Dispose(); actionSignal.Dispose(); } private async Task CreateProcessTask(CancellationToken token) { while (!token.IsCancellationRequested) { bool lockTaken = false; try { await actionSignal.WaitAsync(100, token); SingleThreadActionStandbySignal.Wait(CancellationToken.None); lockTaken = true; RunActions(); } catch (OperationCanceledException) { break; } finally { if (lockTaken) { SingleThreadActionStandbySignal.Release(); } } } } /// /// Add a pending action in a STW queue. /// DO NOT ABUSE IT OR IT WILL SLOW DOWN MAIN THREAD!!!! /// /// public void AddAction(Action action) { if (disposed || action == null) { return; } // enqueue and let background task handle the rest ActionQueue.Enqueue(action); if (actionSignal.CurrentCount == 0) { actionSignal.Release(); } } /// /// Run all pending actions in the STW queue /// [STAThread] private void RunActions() { while (ActionQueue.TryDequeue(out Action action)) { try { action?.Invoke(); } catch (Exception e) { // Just try-catch and do nothing but print errorlogs. We cannot afford crashing the game. ConsoleColor originalForeground = Console.ForegroundColor; Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"WARNING: Error occurred when running Single Thread Actions." + $"If the server didn't crash or stop responding then this should be fine \n{e}"); Console.ForegroundColor = originalForeground; } } } } }