From 24820fe64653f01356027468ddd5b1f2ee2f27f7 Mon Sep 17 00:00:00 2001 From: Pasha Date: Sun, 25 Jan 2026 00:04:16 +0000 Subject: [PATCH] Added ThreadDispatcher And ThreadSafe.Try --- Assets/Pacore/Runtime/Threading.meta | 3 + .../Runtime/Threading/ThreadDispatcher.cs | 114 ++++++++++++++++++ .../Threading/ThreadDispatcher.cs.meta | 3 + .../Pacore/Runtime/Threading/ThreadSafeTry.cs | 25 ++++ .../Runtime/Threading/ThreadSafeTry.cs.meta | 3 + 5 files changed, 148 insertions(+) create mode 100644 Assets/Pacore/Runtime/Threading.meta create mode 100644 Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs create mode 100644 Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta create mode 100644 Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs create mode 100644 Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta diff --git a/Assets/Pacore/Runtime/Threading.meta b/Assets/Pacore/Runtime/Threading.meta new file mode 100644 index 0000000..dd4d27a --- /dev/null +++ b/Assets/Pacore/Runtime/Threading.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d359635ad84f4f6eaf0635d203692066 +timeCreated: 1769295079 \ No newline at end of file diff --git a/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs new file mode 100644 index 0000000..849d596 --- /dev/null +++ b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs @@ -0,0 +1,114 @@ +using System.Collections.Concurrent; +using PashaBibko.Pacore.Attributes; +using UnityEngine; +using System; +using System.Collections; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Debug = UnityEngine.Debug; + +namespace PashaBibko.Pacore.Threading +{ + [CreateInstanceOnStart] public class ThreadDispatcher : MonoBehaviour + { + private static ConcurrentQueue MainThreadMultistepQueue { get; } = new(); + private static ConcurrentQueue MainThreadImmediateQueue { get; } = new(); + private static ConcurrentQueue BackgroundQueue { get; } = new(); + + private static SemaphoreSlim Semaphore { get; } = new(initialCount: 4); + private static int IsBackgroundProcessing; // Pseudo boolean + + private static IEnumerator ActiveMultistepRoutine { get; set; } + private static ThreadDispatcher Instance; + + private const long MAIN_THREAD_MS_MAX = 5; + + private void Awake() + { + /* Makes sure there is only one instance */ + if (Instance is not null) + { + Debug.LogError($"Cannot have multiple instances of [{nameof(ThreadDispatcher)}]"); + return; + } + + Instance = this; + } + + + public static void QueueMultistep(IEnumerator routine) => MainThreadMultistepQueue.Enqueue(routine); + public static void QueueImmediate(Action action) => MainThreadImmediateQueue.Enqueue(action); + + public static void QueueBackground(Action action) + { + /* Adds to the queue and runs if there are no active threads */ + BackgroundQueue.Enqueue(action); + TriggerBackgroundProcessing(); + } + + private static void TriggerBackgroundProcessing() + { + /* Makes sure there are not too many threads queued */ + if (Interlocked.Exchange(ref IsBackgroundProcessing, 1) != 1) + { + Task.Run(ProcessBackgroundQueue); + } + } + + private static async Task ProcessBackgroundQueue() + { + /* Empties the queue of all tasks */ + while (BackgroundQueue.TryDequeue(out Action task)) + { + await Semaphore.WaitAsync(); + _ = Task.Run(() => + { + ThreadSafe.Try + ( + action: task, + final: () => Semaphore.Release() + ); + }); + } + + /* Cleans up to allow for future procession */ + Interlocked.Exchange(ref IsBackgroundProcessing, 0); + if (!BackgroundQueue.IsEmpty) // Items may be queued during cleanup + { + TriggerBackgroundProcessing(); + } + } + + private void Update() + { + /* Runs the Actions in the immediate queue */ + Stopwatch sw = Stopwatch.StartNew(); // Used to make sure not too much processing is done in one go + while (MainThreadImmediateQueue.TryDequeue(out Action current) && sw.ElapsedMilliseconds < MAIN_THREAD_MS_MAX) + { + current.Invoke(); + } + + /* Runs the multistep actions (if there is still time) */ + while (sw.ElapsedMilliseconds < MAIN_THREAD_MS_MAX) + { + /* Gets a new routine if there is none active */ + if (ActiveMultistepRoutine == null) + { + if (!MainThreadMultistepQueue.TryDequeue(out IEnumerator next)) + { + return; // There is none left so we can return early + } + + ActiveMultistepRoutine = next; + } + + /* Runs the next step in the routine */ + if (!ActiveMultistepRoutine.MoveNext()) + { + ActiveMultistepRoutine = null; // [MoveNext() -> false] means the routine has ended + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta new file mode 100644 index 0000000..caa86c8 --- /dev/null +++ b/Assets/Pacore/Runtime/Threading/ThreadDispatcher.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1ef64778d77647d9991a3b56a831dc5e +timeCreated: 1769293819 \ No newline at end of file diff --git a/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs new file mode 100644 index 0000000..3f08bff --- /dev/null +++ b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs @@ -0,0 +1,25 @@ +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using UnityEngine; +using System; + +namespace PashaBibko.Pacore.Threading +{ + public static class ThreadSafe + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Try([NotNull] Action action, Action final = null) + { + try { action(); } + + /* Makes sure any exceptions are caught and logged properly */ + catch (Exception ex) + { + ThreadDispatcher.QueueImmediate(() => Debug.Log($"Exception: [{ex.Message}]")); + throw; + } + + finally { final?.Invoke(); } + } + } +} \ No newline at end of file diff --git a/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta new file mode 100644 index 0000000..5ad6da4 --- /dev/null +++ b/Assets/Pacore/Runtime/Threading/ThreadSafeTry.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0ffd3b00465e46ae99836711cf00f884 +timeCreated: 1769295315 \ No newline at end of file