├── .gitignore ├── Editor.meta ├── Editor ├── CoroutineHostEditor.cs ├── CoroutineHostEditor.cs.meta ├── Editor.cs ├── Editor.cs.meta ├── Lumpn.Threading.Editor.asmdef └── Lumpn.Threading.Editor.asmdef.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── AssemblyInfo.cs ├── AssemblyInfo.cs.meta ├── Callback.cs ├── Callback.cs.meta ├── CallbackAwaiter.cs ├── CallbackAwaiter.cs.meta ├── CallbackAwaiterBase.cs ├── CallbackAwaiterBase.cs.meta ├── CoroutineHost.cs ├── CoroutineHost.cs.meta ├── CoroutineWrapper.cs ├── CoroutineWrapper.cs.meta ├── ISynchronizationContext.cs ├── ISynchronizationContext.cs.meta ├── IThread.cs ├── IThread.cs.meta ├── Lumpn.Threading.asmdef ├── Lumpn.Threading.asmdef.meta ├── Pool.cs ├── Pool.cs.meta ├── Task.cs ├── Task.cs.meta ├── ThreadExtensions.cs ├── ThreadExtensions.cs.meta ├── ThreadUtils.cs ├── ThreadUtils.cs.meta ├── UnityThread.cs ├── UnityThread.cs.meta ├── Utils.meta ├── Utils │ ├── CoroutineUtils.cs │ ├── CoroutineUtils.cs.meta │ ├── StackExtensions.cs │ └── StackExtensions.cs.meta ├── WorkerThread.cs ├── WorkerThread.cs.meta ├── YieldInstructionWrapper.cs └── YieldInstructionWrapper.cs.meta ├── Samples~ └── ContextSwitchingSamples │ ├── Resources.meta │ ├── Resources │ ├── Material.mat │ └── Material.mat.meta │ ├── Scenes.meta │ ├── Scenes │ ├── ThreadingDemo.unity │ └── ThreadingDemo.unity.meta │ ├── Scripts.meta │ └── Scripts │ ├── Editor.meta │ ├── Editor │ ├── SwitchContextDemoEditor.cs │ └── SwitchContextDemoEditor.cs.meta │ ├── SwitchContextDemo.cs │ └── SwitchContextDemo.cs.meta ├── Tests.meta ├── Tests ├── Editor.meta └── Editor │ ├── CoroutineHandlerTest.cs │ ├── CoroutineHandlerTest.cs.meta │ ├── Lumpn.Threading.Editor.Tests.asmdef │ ├── Lumpn.Threading.Editor.Tests.asmdef.meta │ ├── ManualThread.cs │ ├── ManualThread.cs.meta │ ├── StackTest.cs │ ├── StackTest.cs.meta │ ├── TaskTest.cs │ ├── TaskTest.cs.meta │ ├── UnityThreadTest.cs │ ├── UnityThreadTest.cs.meta │ ├── WorkerThreadTest.cs │ └── WorkerThreadTest.cs.meta ├── package.json └── package.json.meta /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | **/[Ll]ibrary/ 6 | **/[Tt]emp/ 7 | **/[Oo]bj/ 8 | **/[Bb]uild/ 9 | **/[Bb]uilds/ 10 | **/[Ll]ogs/ 11 | **/[Mm]emoryCaptures/ 12 | **/[Uu]serSettings/ 13 | 14 | # Asset meta data should only be ignored when the corresponding asset is also ignored 15 | !/[Aa]ssets/**/*.meta 16 | 17 | # Uncomment this line if you wish to ignore the asset store tools plugin 18 | # /[Aa]ssets/AssetStoreTools* 19 | 20 | # Autogenerated Jetbrains Rider plugin 21 | [Aa]ssets/Plugins/Editor/JetBrains* 22 | 23 | # Visual Studio cache directory 24 | .vs/ 25 | 26 | # Gradle cache directory 27 | .gradle/ 28 | 29 | # Autogenerated VS/MD/Consulo solution and project files 30 | ExportedObj/ 31 | .consulo/ 32 | *.csproj 33 | *.unityproj 34 | *.sln 35 | *.suo 36 | *.tmp 37 | *.user 38 | *.userprefs 39 | *.pidb 40 | *.booproj 41 | *.svd 42 | *.pdb 43 | *.mdb 44 | *.opendb 45 | *.VC.db 46 | 47 | # Unity3D generated meta files 48 | *.pidb.meta 49 | *.pdb.meta 50 | *.mdb.meta 51 | 52 | # Unity3D generated file on crash reports 53 | sysinfo.txt 54 | 55 | # Builds 56 | *.apk 57 | *.unitypackage 58 | 59 | # Crashlytics generated file 60 | crashlytics-build.properties 61 | 62 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7fc5d141663d9084ba2b9ee88b8e2d0e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/CoroutineHostEditor.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using UnityEditor; 6 | 7 | namespace Lumpn.Threading 8 | { 9 | [CustomEditor(typeof(CoroutineHost))] 10 | public sealed class CoroutineHostEditor : Editor 11 | { 12 | public override void OnInspectorGUI(CoroutineHost host) 13 | { 14 | EditorGUILayout.IntField("Queue Length", host.queueLength); 15 | 16 | Repaint(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Editor/CoroutineHostEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: beedcbd0ff6d15846b08a4e9e66546c7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Editor.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Lumpn.Threading 9 | { 10 | public abstract class Editor : Editor where T : Object 11 | { 12 | public sealed override void OnInspectorGUI() 13 | { 14 | base.OnInspectorGUI(); 15 | 16 | EditorGUILayout.Separator(); 17 | EditorGUILayout.BeginVertical(GUI.skin.box); 18 | OnInspectorGUI((T)target); 19 | EditorGUILayout.EndVertical(); 20 | } 21 | 22 | public abstract void OnInspectorGUI(T target); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Editor/Editor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9ce8337a86611a4d90168c55daa90e9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lumpn.Threading.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lumpn.Threading.Editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:daffcfbc5d71a5a4eb88169476f7cdb6" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Editor/Lumpn.Threading.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a264956dcc6552844b92b175702f5b1c 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jonas Boetel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 64a85b0adf1de9d4dab3143fd2ddacd6 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unity-threading 2 | Non-allocating async/await facilities for Unity. Coroutines that flow across threads, callbacks, and yield instructions. 3 | 4 | ## Disclaimer 5 | You should probably just use [UniTask](https://github.com/Cysharp/UniTask) instead of this package. Coroutines are nice, but `async` is better, and [UniTask](https://github.com/Cysharp/UniTask) is a great package with good documentation and an active community. 6 | 7 | ## Installation 8 | Download the entire repository from https://github.com/lumpn/unity-threading and use Unity's built in package manager to [add package from disk](https://docs.unity3d.com/Manual/upm-ui-local.html). 9 | 10 | ## Usage 11 | ```csharp 12 | IEnumerator SaveAsync() 13 | { 14 | saveIcon.SetActive(true); // on Unity thread 15 | 16 | yield return ioThread.Context; // switch to I/O thread 17 | File.WriteAllBytes("savegame.dat", data); // on I/O thread 18 | 19 | var awaiter = new CallbackAwaiter(); 20 | StorageAPI.WriteAsync("savegame", awaiter.Call); 21 | yield return awaiter; // wait for callback 22 | 23 | yield return unityThread.Context; // switch back to Unity thread 24 | saveIcon.SetActive(false); // on Unity thread 25 | } 26 | ``` 27 | 28 | ### Context switching 29 | ```csharp 30 | class SaveGameManager : MonoBehaviour 31 | { 32 | IThread unityThread, ioThread; 33 | 34 | void Start() 35 | { 36 | unityThread = ThreadUtils.StartUnityThread("SaveGameManager", 10, this); 37 | ioThread = ThreadUtils.StartWorkerThread("Workers", "I/O", ThreadPriority.Normal, 10); 38 | } 39 | 40 | void OnDestroy() 41 | { 42 | ThreadUtils.StopThread(ioThread); 43 | ThreadUtils.StopThread(unityThread); 44 | } 45 | 46 | void Save() 47 | { 48 | unityThread.StartCoroutine(SaveAsync()); 49 | } 50 | 51 | IEnumerator SaveAsync() 52 | { 53 | saveIcon.SetActive(true); // on Unity thread 54 | yield return ioThread.Context; // switch to I/O thread 55 | 56 | var data = GatherSaveData(); 57 | File.WriteAllBytes("savegame.dat", data); // on I/O thread 58 | 59 | yield return unityThread.Context; // switch back to Unity thread 60 | saveIcon.SetActive(false); // on Unity thread 61 | } 62 | } 63 | ``` 64 | 65 | ### Callback handling 66 | ```csharp 67 | class SaveGameManager : MonoBehaviour 68 | { 69 | IEnumerator LoadAsync(string fileName) 70 | { 71 | var awaiter = new CallbackAwaiter(); 72 | StorageAPI.LoadBytesAsync(fileName, awaiter.Call); 73 | yield return awaiter; // wait for callback 74 | 75 | var buffer = awaiter.arg; 76 | RestoreSaveData(buffer); 77 | } 78 | } 79 | ``` 80 | 81 | ### Yield instructions 82 | ```csharp 83 | class SaveGameManager : MonoBehaviour 84 | { 85 | IEnumerator RestoreOptionsAsync() 86 | { 87 | busyIndicator.SetActive(true); 88 | 89 | var bundleRequest = AssetBundle.LoadFromFileAsync("options"); 90 | yield return bundleRequest; 91 | 92 | var bundle = bundleRequest.bundle; 93 | var assetRequest = bundle.LoadAssetAsync("options.bytes"); 94 | yield return assetRequest; 95 | 96 | var text = (TextAsset)assetRequest.asset; 97 | var data = text.bytes; 98 | 99 | yield return ioThread.Context; 100 | File.WriteAllBytes("options.dat", data); // on I/O thread 101 | 102 | // simulate really slow HDD 103 | yield return new WaitForSeconds(5f); // on I/O thread 104 | 105 | yield return unityThread.Context; 106 | busyIndicator.SetActive(false); 107 | } 108 | } 109 | ``` 110 | 111 | ## Notes 112 | * See `SwitchContextDemo` for details. 113 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fdf12b6d0941b1246b2ab66d3c9143e3 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 59d115a39b453cf46b9a92dfdbee0a80 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Runtime.CompilerServices; 6 | 7 | [assembly: InternalsVisibleTo("Lumpn.Threading.Editor.Tests")] 8 | -------------------------------------------------------------------------------- /Runtime/AssemblyInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5dbdbab7bb7d8fe4c9724b6ee2353a4f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Callback.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | namespace Lumpn.Threading 6 | { 7 | public delegate void Callback(object owner, object state); 8 | } 9 | -------------------------------------------------------------------------------- /Runtime/Callback.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae5c5805ec4127b4bb80e279fb81aa95 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/CallbackAwaiter.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | namespace Lumpn.Threading 6 | { 7 | public sealed class CallbackAwaiter : CallbackAwaiterBase 8 | { 9 | public void Call() 10 | { 11 | SetCalled(); 12 | } 13 | } 14 | 15 | public sealed class CallbackAwaiter : CallbackAwaiterBase 16 | { 17 | public T1 arg1 { get; private set; } 18 | 19 | public void Call(T1 arg1) 20 | { 21 | this.arg1 = arg1; 22 | SetCalled(); 23 | } 24 | } 25 | 26 | public sealed class CallbackAwaiter : CallbackAwaiterBase 27 | { 28 | public T1 arg1 { get; private set; } 29 | public T2 arg2 { get; private set; } 30 | 31 | public void Call(T1 arg1, T2 arg2) 32 | { 33 | this.arg1 = arg1; 34 | this.arg2 = arg2; 35 | SetCalled(); 36 | } 37 | } 38 | 39 | public sealed class CallbackAwaiter : CallbackAwaiterBase 40 | { 41 | public T1 arg1 { get; private set; } 42 | public T2 arg2 { get; private set; } 43 | public T3 arg3 { get; private set; } 44 | 45 | public void Call(T1 arg1, T2 arg2, T3 arg3) 46 | { 47 | this.arg1 = arg1; 48 | this.arg2 = arg2; 49 | this.arg3 = arg3; 50 | SetCalled(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Runtime/CallbackAwaiter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0224550d535b62e4580bd5cfc9ca9cb6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/CallbackAwaiterBase.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Threading; 6 | 7 | namespace Lumpn.Threading 8 | { 9 | public abstract class CallbackAwaiterBase 10 | { 11 | private const int statusNone = 0; 12 | private const int statusInitialized = 1; 13 | private const int statusCalled = 2; 14 | 15 | private ISynchronizationContext originalContext; 16 | private CoroutineWrapper coroutineWrapper; 17 | private volatile int status = statusNone; 18 | 19 | internal void Initialize(ISynchronizationContext originalContext, CoroutineWrapper coroutineWrapper) 20 | { 21 | this.originalContext = originalContext; 22 | this.coroutineWrapper = coroutineWrapper; 23 | 24 | int previousStatus = Interlocked.CompareExchange(ref status, statusInitialized, statusNone); 25 | if (previousStatus == statusCalled) 26 | { 27 | // awaiter has already been called -> resume 28 | ResumeCoroutine(); 29 | } 30 | } 31 | 32 | protected void SetCalled() 33 | { 34 | int previousStatus = Interlocked.CompareExchange(ref status, statusCalled, statusNone); 35 | if (previousStatus == statusInitialized) 36 | { 37 | // awaiter has already been initialized -> resume 38 | ResumeCoroutine(); 39 | } 40 | } 41 | 42 | private void ResumeCoroutine() 43 | { 44 | // resume coroutine on its original synchronization context 45 | coroutineWrapper.ContinueOn(originalContext); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Runtime/CallbackAwaiterBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a3fbcd3a4d27b9448a6befb0bfc8ee50 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/CoroutineHost.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections; 6 | using System.Threading; 7 | using UnityEngine; 8 | 9 | namespace Lumpn.Threading 10 | { 11 | public sealed class CoroutineHost : MonoBehaviour 12 | { 13 | private readonly UnityThread unityThread = new UnityThread(nameof(CoroutineHost), 10); 14 | 15 | public int queueLength { get { return unityThread.queueLength; } } 16 | 17 | IEnumerator Start() 18 | { 19 | return unityThread.Start(); 20 | } 21 | 22 | internal void Handle(YieldInstruction yieldInstruction, ISynchronizationContext context, CoroutineWrapper wrapper) 23 | { 24 | Log("Handle yield instruction"); 25 | var yieldWrapper = YieldInstructionWrapper.Create(yieldInstruction, context, wrapper); 26 | unityThread.Post(StartYieldWrapper, this, yieldWrapper); 27 | } 28 | 29 | private static void StartYieldWrapper(object owner, object state) 30 | { 31 | Log("Start yield wrapper"); 32 | var host = (CoroutineHost)owner; 33 | var wrapper = (YieldInstructionWrapper)state; 34 | 35 | host.StartCoroutine(wrapper); 36 | } 37 | 38 | private static void Log(object msg) 39 | { 40 | var thread = Thread.CurrentThread; 41 | Debug.LogFormat("Thread {0} ({1}): {2}", thread.ManagedThreadId, thread.Name, msg); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Runtime/CoroutineHost.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 31e1795c29841c8479b9dbc707d255ac 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/CoroutineWrapper.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using UnityEngine; 8 | 9 | namespace Lumpn.Threading 10 | { 11 | internal sealed class CoroutineWrapper : CustomYieldInstruction 12 | { 13 | private static readonly Pool pool = new Pool(100); 14 | 15 | private readonly Stack stack = new Stack(); 16 | private CoroutineHost host; 17 | 18 | public override bool keepWaiting { get { return stack.Count > 0; } } 19 | 20 | public void ContinueOn(ISynchronizationContext context) 21 | { 22 | context.Post(AdvanceCoroutine, this, context); 23 | } 24 | 25 | private static void AdvanceCoroutine(object owner, object state) 26 | { 27 | var wrapper = (CoroutineWrapper)owner; 28 | var context = (ISynchronizationContext)state; 29 | 30 | if (wrapper.AdvanceCoroutine(context)) 31 | { 32 | wrapper.ContinueOn(context); 33 | } 34 | } 35 | 36 | /// returns true, iff the coroutine should 37 | /// continue running on the same context 38 | private bool AdvanceCoroutine(ISynchronizationContext context) 39 | { 40 | if (stack.Count < 1) 41 | { 42 | pool.Return(this); 43 | return false; // done 44 | } 45 | 46 | var iter = stack.Peek(); 47 | if (!iter.MoveNext()) 48 | { 49 | stack.Pop(); 50 | return true; 51 | } 52 | 53 | var currentElement = iter.Current; 54 | 55 | // handle nesting 56 | if (currentElement is IEnumerator subroutine) 57 | { 58 | stack.Push(subroutine); 59 | return true; 60 | } 61 | 62 | // handle switching context 63 | if (currentElement is ISynchronizationContext newContext) 64 | { 65 | ContinueOn(newContext); 66 | return false; // continuing on new context instead 67 | } 68 | 69 | // handle awaiting callbacks 70 | if (currentElement is CallbackAwaiterBase awaiter) 71 | { 72 | awaiter.Initialize(context, this); 73 | return false; // awaiting callback instead 74 | } 75 | 76 | // handle yield instructions 77 | if (currentElement is YieldInstruction yieldInstruction) 78 | { 79 | host.Handle(yieldInstruction, context, this); 80 | return false; // awaiting yield instruction instead 81 | } 82 | 83 | return true; 84 | } 85 | 86 | public static CoroutineWrapper StartCoroutine(CoroutineHost host, ISynchronizationContext context, IEnumerator coroutine) 87 | { 88 | var wrapper = pool.Get(); 89 | wrapper.host = host; 90 | wrapper.stack.Push(coroutine); 91 | wrapper.ContinueOn(context); 92 | return wrapper; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Runtime/CoroutineWrapper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c001dca1c139524b869d9e4dc9357cf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ISynchronizationContext.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | namespace Lumpn.Threading 6 | { 7 | public interface ISynchronizationContext 8 | { 9 | void Post(Callback callback, object owner, object state); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Runtime/ISynchronizationContext.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ea3b54a1778c14e4eaa05e1a5357bfdb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/IThread.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | namespace Lumpn.Threading 6 | { 7 | public interface IThread : ISynchronizationContext 8 | { 9 | bool isRunning { get; } 10 | bool isIdle { get; } 11 | int queueLength { get; } 12 | ISynchronizationContext context { get; } 13 | 14 | void Stop(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Runtime/IThread.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e072bb8f2c6454f408d9fe2bbc61efce 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Lumpn.Threading.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lumpn.Threading", 3 | "rootNamespace": "", 4 | "references": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [], 13 | "noEngineReferences": false 14 | } -------------------------------------------------------------------------------- /Runtime/Lumpn.Threading.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: daffcfbc5d71a5a4eb88169476f7cdb6 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Pool.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections.Generic; 6 | 7 | namespace Lumpn.Threading 8 | { 9 | internal sealed class Pool where T : new() 10 | { 11 | private readonly Stack pool; 12 | 13 | public Pool(int initialSize) 14 | { 15 | this.pool = new Stack(initialSize); 16 | } 17 | 18 | public T Get() 19 | { 20 | lock (pool) 21 | { 22 | return pool.PopOrNew(); 23 | } 24 | } 25 | 26 | public void Return(T obj) 27 | { 28 | lock (pool) 29 | { 30 | pool.Push(obj); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Runtime/Pool.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f0f4970a01d9b5e46abc26f3186e2062 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Task.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using UnityEngine; 6 | 7 | namespace Lumpn.Threading 8 | { 9 | internal struct Task 10 | { 11 | private readonly Callback callback; 12 | private readonly object owner; 13 | private readonly object state; 14 | 15 | public Task(Callback callback, object owner, object state) 16 | { 17 | this.callback = callback; 18 | this.owner = owner; 19 | this.state = state; 20 | } 21 | 22 | public void Invoke() 23 | { 24 | try 25 | { 26 | callback(owner, state); 27 | } 28 | catch (System.Exception ex) 29 | { 30 | Debug.LogException(ex); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Runtime/Task.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b75d2e7f637ae9043a2529f864aa52e6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ThreadExtensions.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections; 6 | using UnityEngine; 7 | 8 | namespace Lumpn.Threading 9 | { 10 | public static class ThreadExtensions 11 | { 12 | public static CustomYieldInstruction StartCoroutine(this IThread thread, IEnumerator coroutine, CoroutineHost host) 13 | { 14 | return CoroutineWrapper.StartCoroutine(host, thread, coroutine); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Runtime/ThreadExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7567b92d897893a4a8a31b175487dd1a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ThreadUtils.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using UnityEngine; 6 | using ThreadPriority = System.Threading.ThreadPriority; 7 | 8 | namespace Lumpn.Threading 9 | { 10 | public static class ThreadUtils 11 | { 12 | public static IThread StartWorkerThread(string group, string name, ThreadPriority priority, int initialCapacity) 13 | { 14 | var thread = new WorkerThread(group, name, priority, initialCapacity); 15 | thread.Start(); 16 | return thread; 17 | } 18 | 19 | public static IThread StartUnityThread(string name, int initialCapacity, MonoBehaviour host) 20 | { 21 | var thread = new UnityThread(name, initialCapacity); 22 | host.StartCoroutine(thread.Start()); 23 | return thread; 24 | } 25 | 26 | public static bool StopThread(IThread thread) 27 | { 28 | if (thread == null) return false; 29 | 30 | thread.Stop(); 31 | return true; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Runtime/ThreadUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6c753958b50637649af124f9f484c44e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/UnityThread.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Lumpn.Threading 9 | { 10 | internal sealed class UnityThread : IThread 11 | { 12 | private readonly Queue pendingTasks; 13 | public readonly string name; 14 | private bool started; 15 | private bool canceled; 16 | 17 | public bool isRunning { get { return started && !canceled; } } 18 | public bool isIdle { get { return pendingTasks.Count <= 0; } } 19 | public int queueLength { get { return pendingTasks.Count; } } 20 | public ISynchronizationContext context { get { return this; } } 21 | 22 | public UnityThread(string name, int initialCapacity) 23 | { 24 | this.pendingTasks = new Queue(initialCapacity); 25 | this.name = name; 26 | this.started = false; 27 | this.canceled = false; 28 | } 29 | 30 | public IEnumerator Start() 31 | { 32 | started = true; 33 | while (!canceled) 34 | { 35 | int numTasks; 36 | lock (pendingTasks) 37 | { 38 | numTasks = pendingTasks.Count; 39 | } 40 | 41 | // explicitly only pump as many tasks as are queued right now 42 | // because executing tasks can enqueue more tasks 43 | Run(numTasks); 44 | 45 | yield return CoroutineUtils.waitForNextFrame; 46 | } 47 | } 48 | 49 | public void Stop() 50 | { 51 | lock (pendingTasks) 52 | { 53 | canceled = true; 54 | } 55 | } 56 | 57 | public void Post(Callback callback, object owner, object state) 58 | { 59 | var pendingTask = new Task(callback, owner, state); 60 | lock (pendingTasks) 61 | { 62 | pendingTasks.Enqueue(pendingTask); 63 | } 64 | } 65 | 66 | public override string ToString() 67 | { 68 | return name; 69 | } 70 | 71 | private bool TryDequeue(out Task task) 72 | { 73 | lock (pendingTasks) 74 | { 75 | if (pendingTasks.Count > 0) 76 | { 77 | task = pendingTasks.Dequeue(); 78 | return true; 79 | } 80 | } 81 | 82 | task = default(Task); 83 | return false; 84 | } 85 | 86 | private void Run(int numTasks) 87 | { 88 | for (int i = 0; i < numTasks; i++) 89 | { 90 | if (!TryDequeue(out Task task)) return; 91 | task.Invoke(); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Runtime/UnityThread.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8fe6d5cb563384148a4dd803f03560e3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Utils.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f9830ae9e80987f4487e8e6193bc264b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Utils/CoroutineUtils.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | namespace Lumpn.Threading 6 | { 7 | internal static class CoroutineUtils 8 | { 9 | public static readonly object waitForNextFrame = new object(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Runtime/Utils/CoroutineUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c0977778c3331ba47a8a577be07ca96f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Utils/StackExtensions.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections.Generic; 6 | 7 | namespace Lumpn.Threading 8 | { 9 | public static class StackExtensions 10 | { 11 | public static bool TryPop(this Stack stack, out T value) 12 | { 13 | if (stack.Count > 0) 14 | { 15 | value = stack.Pop(); 16 | return true; 17 | } 18 | 19 | value = default; 20 | return false; 21 | } 22 | 23 | public static T PopOrNew(this Stack stack) where T : new() 24 | { 25 | return (stack.Count > 0) ? stack.Pop() : new T(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Runtime/Utils/StackExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 60205bccc6897db488bfbd08a53fc754 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/WorkerThread.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections.Generic; 6 | using System.Threading; 7 | using UnityEngine.Profiling; 8 | 9 | namespace Lumpn.Threading 10 | { 11 | internal sealed class WorkerThread : IThread 12 | { 13 | private readonly string group; 14 | private readonly string name; 15 | private readonly Thread thread; 16 | private readonly Queue pendingTasks; 17 | 18 | private bool started; 19 | private bool waiting; 20 | private bool canceled; 21 | 22 | public bool isRunning { get { return started && !canceled; } } 23 | public bool isIdle { get { return waiting && pendingTasks.Count <= 0; } } 24 | public int queueLength { get { { return pendingTasks.Count; } } } 25 | public ISynchronizationContext context { get { return this; } } 26 | 27 | public WorkerThread(string group, string name, ThreadPriority priority, int initialCapacity) 28 | { 29 | this.group = group; 30 | this.name = name; 31 | this.pendingTasks = new Queue(initialCapacity); 32 | this.thread = new Thread(ThreadMain) 33 | { 34 | Name = name, 35 | IsBackground = true, 36 | Priority = priority, 37 | }; 38 | } 39 | 40 | public void Start() 41 | { 42 | canceled = false; 43 | thread.Start(this); 44 | } 45 | 46 | public void Stop() 47 | { 48 | lock (pendingTasks) 49 | { 50 | canceled = true; 51 | Monitor.Pulse(pendingTasks); 52 | } 53 | } 54 | 55 | public void Post(Callback callback, object owner, object state) 56 | { 57 | lock (pendingTasks) 58 | { 59 | var pendingTask = new Task(callback, owner, state); 60 | pendingTasks.Enqueue(pendingTask); 61 | Monitor.Pulse(pendingTasks); 62 | } 63 | } 64 | 65 | public override string ToString() 66 | { 67 | return string.Format("{0}: {1}", group, name); 68 | } 69 | 70 | private bool TryDequeue(out Task task) 71 | { 72 | lock (pendingTasks) 73 | { 74 | while (!canceled && pendingTasks.Count <= 0) 75 | { 76 | waiting = true; 77 | Monitor.Wait(pendingTasks); 78 | waiting = false; 79 | } 80 | 81 | if (!canceled) 82 | { 83 | task = pendingTasks.Dequeue(); 84 | return true; 85 | } 86 | } 87 | 88 | task = default(Task); 89 | return false; 90 | } 91 | 92 | private void Run() 93 | { 94 | Profiler.BeginThreadProfiling(group, name); 95 | try 96 | { 97 | started = true; 98 | while (TryDequeue(out Task task)) 99 | { 100 | task.Invoke(); 101 | } 102 | } 103 | finally 104 | { 105 | Profiler.EndThreadProfiling(); 106 | } 107 | } 108 | 109 | private static void ThreadMain(object state) 110 | { 111 | try 112 | { 113 | var worker = (WorkerThread)state; 114 | worker.Run(); 115 | } 116 | catch (ThreadAbortException) 117 | { 118 | UnityEngine.Debug.LogFormat("Aborting thread '{0}'", state); 119 | } 120 | catch (System.Exception ex) 121 | { 122 | UnityEngine.Debug.LogException(ex); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Runtime/WorkerThread.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ad3a28adc7f4b2640b09f5e7f2d91c77 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/YieldInstructionWrapper.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections; 6 | using System.Threading; 7 | using UnityEngine; 8 | 9 | namespace Lumpn.Threading 10 | { 11 | internal sealed class YieldInstructionWrapper : IEnumerator 12 | { 13 | private static readonly Pool pool = new Pool(100); 14 | 15 | private YieldInstruction instruction; 16 | private ISynchronizationContext originalContext; 17 | private CoroutineWrapper coroutineWrapper; 18 | private bool yielded; 19 | 20 | public object Current { get { return instruction; } } 21 | 22 | public bool MoveNext() 23 | { 24 | // yield once, exposing the yield instruction to the caller. 25 | if (!yielded) 26 | { 27 | Log("yielding first time"); 28 | yielded = true; 29 | return true; 30 | } 31 | 32 | // syncronization context called again, therefore the yield instruction 33 | // must have completed. we now continue on the original context. 34 | Log("yielded. continuing on original context."); 35 | coroutineWrapper.ContinueOn(originalContext); 36 | 37 | // also our work is complete -> return to pool 38 | pool.Return(this); 39 | return false; 40 | } 41 | 42 | public void Reset() 43 | { 44 | } 45 | 46 | public override string ToString() 47 | { 48 | return string.Format("instruction '{0}', context '{1}'", instruction, originalContext); 49 | } 50 | 51 | public static YieldInstructionWrapper Create(YieldInstruction instruction, ISynchronizationContext originalContext, CoroutineWrapper coroutineWrapper) 52 | { 53 | var yieldWrapper = pool.Get(); 54 | yieldWrapper.instruction = instruction; 55 | yieldWrapper.originalContext = originalContext; 56 | yieldWrapper.coroutineWrapper = coroutineWrapper; 57 | return yieldWrapper; 58 | } 59 | 60 | private static void Log(object msg) 61 | { 62 | var thread = Thread.CurrentThread; 63 | Debug.LogFormat("Thread {0} ({1}): {2}", thread.ManagedThreadId, thread.Name, msg); 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Runtime/YieldInstructionWrapper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e99061de3b9f3fc45bfd040f5ecc0b3f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0783a4c2d6d86fe4a8ad89eeabd65a52 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Resources/Material.mat: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!21 &2100000 4 | Material: 5 | serializedVersion: 6 6 | m_ObjectHideFlags: 0 7 | m_CorrespondingSourceObject: {fileID: 0} 8 | m_PrefabInstance: {fileID: 0} 9 | m_PrefabAsset: {fileID: 0} 10 | m_Name: Material 11 | m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} 12 | m_ShaderKeywords: 13 | m_LightmapFlags: 4 14 | m_EnableInstancingVariants: 0 15 | m_DoubleSidedGI: 0 16 | m_CustomRenderQueue: -1 17 | stringTagMap: {} 18 | disabledShaderPasses: [] 19 | m_SavedProperties: 20 | serializedVersion: 3 21 | m_TexEnvs: 22 | - _BumpMap: 23 | m_Texture: {fileID: 0} 24 | m_Scale: {x: 1, y: 1} 25 | m_Offset: {x: 0, y: 0} 26 | - _DetailAlbedoMap: 27 | m_Texture: {fileID: 0} 28 | m_Scale: {x: 1, y: 1} 29 | m_Offset: {x: 0, y: 0} 30 | - _DetailMask: 31 | m_Texture: {fileID: 0} 32 | m_Scale: {x: 1, y: 1} 33 | m_Offset: {x: 0, y: 0} 34 | - _DetailNormalMap: 35 | m_Texture: {fileID: 0} 36 | m_Scale: {x: 1, y: 1} 37 | m_Offset: {x: 0, y: 0} 38 | - _EmissionMap: 39 | m_Texture: {fileID: 0} 40 | m_Scale: {x: 1, y: 1} 41 | m_Offset: {x: 0, y: 0} 42 | - _MainTex: 43 | m_Texture: {fileID: 0} 44 | m_Scale: {x: 1, y: 1} 45 | m_Offset: {x: 0, y: 0} 46 | - _MetallicGlossMap: 47 | m_Texture: {fileID: 0} 48 | m_Scale: {x: 1, y: 1} 49 | m_Offset: {x: 0, y: 0} 50 | - _OcclusionMap: 51 | m_Texture: {fileID: 0} 52 | m_Scale: {x: 1, y: 1} 53 | m_Offset: {x: 0, y: 0} 54 | - _ParallaxMap: 55 | m_Texture: {fileID: 0} 56 | m_Scale: {x: 1, y: 1} 57 | m_Offset: {x: 0, y: 0} 58 | m_Floats: 59 | - _BumpScale: 1 60 | - _Cutoff: 0.5 61 | - _DetailNormalMapScale: 1 62 | - _DstBlend: 0 63 | - _GlossMapScale: 1 64 | - _Glossiness: 0.5 65 | - _GlossyReflections: 1 66 | - _Metallic: 0 67 | - _Mode: 0 68 | - _OcclusionStrength: 1 69 | - _Parallax: 0.02 70 | - _SmoothnessTextureChannel: 0 71 | - _SpecularHighlights: 1 72 | - _SrcBlend: 1 73 | - _UVSec: 0 74 | - _ZWrite: 1 75 | m_Colors: 76 | - _Color: {r: 1, g: 1, b: 1, a: 1} 77 | - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} 78 | m_BuildTextureStacks: [] 79 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Resources/Material.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9a3ae7cfa09d9104696482c49c155764 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 2100000 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 66999b4ad7147d840bbbe202428a9a4d 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scenes/ThreadingDemo.unity: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!29 &1 4 | OcclusionCullingSettings: 5 | m_ObjectHideFlags: 0 6 | serializedVersion: 2 7 | m_OcclusionBakeSettings: 8 | smallestOccluder: 5 9 | smallestHole: 0.25 10 | backfaceThreshold: 100 11 | m_SceneGUID: 00000000000000000000000000000000 12 | m_OcclusionCullingData: {fileID: 0} 13 | --- !u!104 &2 14 | RenderSettings: 15 | m_ObjectHideFlags: 0 16 | serializedVersion: 9 17 | m_Fog: 0 18 | m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} 19 | m_FogMode: 3 20 | m_FogDensity: 0.01 21 | m_LinearFogStart: 0 22 | m_LinearFogEnd: 300 23 | m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} 24 | m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} 25 | m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} 26 | m_AmbientIntensity: 1 27 | m_AmbientMode: 0 28 | m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} 29 | m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} 30 | m_HaloStrength: 0.5 31 | m_FlareStrength: 1 32 | m_FlareFadeSpeed: 3 33 | m_HaloTexture: {fileID: 0} 34 | m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} 35 | m_DefaultReflectionMode: 0 36 | m_DefaultReflectionResolution: 128 37 | m_ReflectionBounces: 1 38 | m_ReflectionIntensity: 1 39 | m_CustomReflection: {fileID: 0} 40 | m_Sun: {fileID: 170076734} 41 | m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} 42 | m_UseRadianceAmbientProbe: 0 43 | --- !u!157 &3 44 | LightmapSettings: 45 | m_ObjectHideFlags: 0 46 | serializedVersion: 12 47 | m_GIWorkflowMode: 0 48 | m_GISettings: 49 | serializedVersion: 2 50 | m_BounceScale: 1 51 | m_IndirectOutputScale: 1 52 | m_AlbedoBoost: 1 53 | m_EnvironmentLightingMode: 0 54 | m_EnableBakedLightmaps: 0 55 | m_EnableRealtimeLightmaps: 0 56 | m_LightmapEditorSettings: 57 | serializedVersion: 12 58 | m_Resolution: 2 59 | m_BakeResolution: 10 60 | m_AtlasSize: 512 61 | m_AO: 0 62 | m_AOMaxDistance: 1 63 | m_CompAOExponent: 1 64 | m_CompAOExponentDirect: 0 65 | m_ExtractAmbientOcclusion: 0 66 | m_Padding: 2 67 | m_LightmapParameters: {fileID: 0} 68 | m_LightmapsBakeMode: 1 69 | m_TextureCompression: 1 70 | m_FinalGather: 0 71 | m_FinalGatherFiltering: 1 72 | m_FinalGatherRayCount: 256 73 | m_ReflectionCompression: 2 74 | m_MixedBakeMode: 2 75 | m_BakeBackend: 1 76 | m_PVRSampling: 1 77 | m_PVRDirectSampleCount: 32 78 | m_PVRSampleCount: 256 79 | m_PVRBounces: 2 80 | m_PVREnvironmentSampleCount: 256 81 | m_PVREnvironmentReferencePointCount: 2048 82 | m_PVRFilteringMode: 2 83 | m_PVRDenoiserTypeDirect: 0 84 | m_PVRDenoiserTypeIndirect: 0 85 | m_PVRDenoiserTypeAO: 0 86 | m_PVRFilterTypeDirect: 0 87 | m_PVRFilterTypeIndirect: 0 88 | m_PVRFilterTypeAO: 0 89 | m_PVREnvironmentMIS: 0 90 | m_PVRCulling: 1 91 | m_PVRFilteringGaussRadiusDirect: 1 92 | m_PVRFilteringGaussRadiusIndirect: 5 93 | m_PVRFilteringGaussRadiusAO: 2 94 | m_PVRFilteringAtrousPositionSigmaDirect: 0.5 95 | m_PVRFilteringAtrousPositionSigmaIndirect: 2 96 | m_PVRFilteringAtrousPositionSigmaAO: 1 97 | m_ExportTrainingData: 0 98 | m_TrainingDataDestination: TrainingData 99 | m_LightProbeSampleCountMultiplier: 4 100 | m_LightingDataAsset: {fileID: 0} 101 | m_LightingSettings: {fileID: 4890085278179872738, guid: 58409322fccb2ef4b9289e25ff32070c, type: 2} 102 | --- !u!196 &4 103 | NavMeshSettings: 104 | serializedVersion: 2 105 | m_ObjectHideFlags: 0 106 | m_BuildSettings: 107 | serializedVersion: 2 108 | agentTypeID: 0 109 | agentRadius: 0.5 110 | agentHeight: 2 111 | agentSlope: 45 112 | agentClimb: 0.4 113 | ledgeDropHeight: 0 114 | maxJumpAcrossDistance: 0 115 | minRegionArea: 2 116 | manualCellSize: 0 117 | cellSize: 0.16666667 118 | manualTileSize: 0 119 | tileSize: 256 120 | accuratePlacement: 0 121 | maxJobWorkers: 0 122 | preserveTilesOutsideBounds: 0 123 | debug: 124 | m_Flags: 0 125 | m_NavMeshData: {fileID: 0} 126 | --- !u!1 &170076733 127 | GameObject: 128 | m_ObjectHideFlags: 0 129 | m_CorrespondingSourceObject: {fileID: 0} 130 | m_PrefabInstance: {fileID: 0} 131 | m_PrefabAsset: {fileID: 0} 132 | serializedVersion: 6 133 | m_Component: 134 | - component: {fileID: 170076735} 135 | - component: {fileID: 170076734} 136 | m_Layer: 0 137 | m_Name: Directional Light 138 | m_TagString: Untagged 139 | m_Icon: {fileID: 0} 140 | m_NavMeshLayer: 0 141 | m_StaticEditorFlags: 0 142 | m_IsActive: 1 143 | --- !u!108 &170076734 144 | Light: 145 | m_ObjectHideFlags: 0 146 | m_CorrespondingSourceObject: {fileID: 0} 147 | m_PrefabInstance: {fileID: 0} 148 | m_PrefabAsset: {fileID: 0} 149 | m_GameObject: {fileID: 170076733} 150 | m_Enabled: 1 151 | serializedVersion: 10 152 | m_Type: 1 153 | m_Shape: 0 154 | m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} 155 | m_Intensity: 1 156 | m_Range: 10 157 | m_SpotAngle: 30 158 | m_InnerSpotAngle: 21.80208 159 | m_CookieSize: 10 160 | m_Shadows: 161 | m_Type: 2 162 | m_Resolution: -1 163 | m_CustomResolution: -1 164 | m_Strength: 1 165 | m_Bias: 0.05 166 | m_NormalBias: 0.4 167 | m_NearPlane: 0.2 168 | m_CullingMatrixOverride: 169 | e00: 1 170 | e01: 0 171 | e02: 0 172 | e03: 0 173 | e10: 0 174 | e11: 1 175 | e12: 0 176 | e13: 0 177 | e20: 0 178 | e21: 0 179 | e22: 1 180 | e23: 0 181 | e30: 0 182 | e31: 0 183 | e32: 0 184 | e33: 1 185 | m_UseCullingMatrixOverride: 0 186 | m_Cookie: {fileID: 0} 187 | m_DrawHalo: 0 188 | m_Flare: {fileID: 0} 189 | m_RenderMode: 0 190 | m_CullingMask: 191 | serializedVersion: 2 192 | m_Bits: 4294967295 193 | m_RenderingLayerMask: 1 194 | m_Lightmapping: 4 195 | m_LightShadowCasterMode: 0 196 | m_AreaSize: {x: 1, y: 1} 197 | m_BounceIntensity: 1 198 | m_ColorTemperature: 6570 199 | m_UseColorTemperature: 0 200 | m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} 201 | m_UseBoundingSphereOverride: 0 202 | m_UseViewFrustumForShadowCasterCull: 1 203 | m_ShadowRadius: 0 204 | m_ShadowAngle: 0 205 | --- !u!4 &170076735 206 | Transform: 207 | m_ObjectHideFlags: 0 208 | m_CorrespondingSourceObject: {fileID: 0} 209 | m_PrefabInstance: {fileID: 0} 210 | m_PrefabAsset: {fileID: 0} 211 | m_GameObject: {fileID: 170076733} 212 | m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} 213 | m_LocalPosition: {x: 0, y: 3, z: 0} 214 | m_LocalScale: {x: 1, y: 1, z: 1} 215 | m_Children: [] 216 | m_Father: {fileID: 0} 217 | m_RootOrder: 1 218 | m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} 219 | --- !u!1 &534669902 220 | GameObject: 221 | m_ObjectHideFlags: 0 222 | m_CorrespondingSourceObject: {fileID: 0} 223 | m_PrefabInstance: {fileID: 0} 224 | m_PrefabAsset: {fileID: 0} 225 | serializedVersion: 6 226 | m_Component: 227 | - component: {fileID: 534669905} 228 | - component: {fileID: 534669904} 229 | m_Layer: 0 230 | m_Name: Main Camera 231 | m_TagString: MainCamera 232 | m_Icon: {fileID: 0} 233 | m_NavMeshLayer: 0 234 | m_StaticEditorFlags: 0 235 | m_IsActive: 1 236 | --- !u!20 &534669904 237 | Camera: 238 | m_ObjectHideFlags: 0 239 | m_CorrespondingSourceObject: {fileID: 0} 240 | m_PrefabInstance: {fileID: 0} 241 | m_PrefabAsset: {fileID: 0} 242 | m_GameObject: {fileID: 534669902} 243 | m_Enabled: 1 244 | serializedVersion: 2 245 | m_ClearFlags: 1 246 | m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} 247 | m_projectionMatrixMode: 1 248 | m_GateFitMode: 2 249 | m_FOVAxisMode: 0 250 | m_SensorSize: {x: 36, y: 24} 251 | m_LensShift: {x: 0, y: 0} 252 | m_FocalLength: 50 253 | m_NormalizedViewPortRect: 254 | serializedVersion: 2 255 | x: 0 256 | y: 0 257 | width: 1 258 | height: 1 259 | near clip plane: 0.3 260 | far clip plane: 1000 261 | field of view: 60 262 | orthographic: 0 263 | orthographic size: 5 264 | m_Depth: -1 265 | m_CullingMask: 266 | serializedVersion: 2 267 | m_Bits: 4294967295 268 | m_RenderingPath: -1 269 | m_TargetTexture: {fileID: 0} 270 | m_TargetDisplay: 0 271 | m_TargetEye: 3 272 | m_HDR: 1 273 | m_AllowMSAA: 1 274 | m_AllowDynamicResolution: 0 275 | m_ForceIntoRT: 0 276 | m_OcclusionCulling: 1 277 | m_StereoConvergence: 10 278 | m_StereoSeparation: 0.022 279 | --- !u!4 &534669905 280 | Transform: 281 | m_ObjectHideFlags: 0 282 | m_CorrespondingSourceObject: {fileID: 0} 283 | m_PrefabInstance: {fileID: 0} 284 | m_PrefabAsset: {fileID: 0} 285 | m_GameObject: {fileID: 534669902} 286 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 287 | m_LocalPosition: {x: 0, y: 1, z: -10} 288 | m_LocalScale: {x: 1, y: 1, z: 1} 289 | m_Children: [] 290 | m_Father: {fileID: 0} 291 | m_RootOrder: 0 292 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 293 | --- !u!1 &1468331644 294 | GameObject: 295 | m_ObjectHideFlags: 0 296 | m_CorrespondingSourceObject: {fileID: 0} 297 | m_PrefabInstance: {fileID: 0} 298 | m_PrefabAsset: {fileID: 0} 299 | serializedVersion: 6 300 | m_Component: 301 | - component: {fileID: 1468331646} 302 | - component: {fileID: 1468331647} 303 | - component: {fileID: 1468331645} 304 | m_Layer: 0 305 | m_Name: Demo 306 | m_TagString: Untagged 307 | m_Icon: {fileID: 0} 308 | m_NavMeshLayer: 0 309 | m_StaticEditorFlags: 0 310 | m_IsActive: 1 311 | --- !u!114 &1468331645 312 | MonoBehaviour: 313 | m_ObjectHideFlags: 0 314 | m_CorrespondingSourceObject: {fileID: 0} 315 | m_PrefabInstance: {fileID: 0} 316 | m_PrefabAsset: {fileID: 0} 317 | m_GameObject: {fileID: 1468331644} 318 | m_Enabled: 1 319 | m_EditorHideFlags: 0 320 | m_Script: {fileID: 11500000, guid: 75b4902db579bb44ab45065ba7a421db, type: 3} 321 | m_Name: 322 | m_EditorClassIdentifier: 323 | host: {fileID: 1468331647} 324 | --- !u!4 &1468331646 325 | Transform: 326 | m_ObjectHideFlags: 0 327 | m_CorrespondingSourceObject: {fileID: 0} 328 | m_PrefabInstance: {fileID: 0} 329 | m_PrefabAsset: {fileID: 0} 330 | m_GameObject: {fileID: 1468331644} 331 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 332 | m_LocalPosition: {x: 0, y: 0, z: 0} 333 | m_LocalScale: {x: 1, y: 1, z: 1} 334 | m_Children: [] 335 | m_Father: {fileID: 0} 336 | m_RootOrder: 2 337 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 338 | --- !u!114 &1468331647 339 | MonoBehaviour: 340 | m_ObjectHideFlags: 0 341 | m_CorrespondingSourceObject: {fileID: 0} 342 | m_PrefabInstance: {fileID: 0} 343 | m_PrefabAsset: {fileID: 0} 344 | m_GameObject: {fileID: 1468331644} 345 | m_Enabled: 1 346 | m_EditorHideFlags: 0 347 | m_Script: {fileID: 11500000, guid: 31e1795c29841c8479b9dbc707d255ac, type: 3} 348 | m_Name: 349 | m_EditorClassIdentifier: 350 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scenes/ThreadingDemo.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0dd073962ecb87b4e87198eea3c48473 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2ba4a00fa14c530448215e705e5adcde 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scripts/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b6bba416feab7ab49824c764a48d1e0e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scripts/Editor/SwitchContextDemoEditor.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using UnityEditor; 6 | using UnityEngine; 7 | 8 | namespace Lumpn.Threading.Samples 9 | { 10 | [CustomEditor(typeof(SwitchContextDemo))] 11 | public sealed class SwitchContextDemoEditor : Editor 12 | { 13 | public override void OnInspectorGUI(SwitchContextDemo demo) 14 | { 15 | if (GUILayout.Button("Start Coroutine")) 16 | { 17 | demo.StartSwitchContextCoroutine(); 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scripts/Editor/SwitchContextDemoEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c08bc348c123bb4994fbed9873b4e20 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scripts/SwitchContextDemo.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections; 6 | using System.Threading; 7 | using UnityEngine; 8 | 9 | namespace Lumpn.Threading.Samples 10 | { 11 | public sealed class SwitchContextDemo : MonoBehaviour 12 | { 13 | [SerializeField] private CoroutineHost host; 14 | 15 | private IThread worker1, worker2, unity1, unity2; 16 | 17 | protected void Start() 18 | { 19 | var mainThread = Thread.CurrentThread; 20 | if (mainThread.Name != "Unity") 21 | { 22 | mainThread.Name = "Unity"; 23 | } 24 | 25 | worker1 = ThreadUtils.StartWorkerThread("Demo", "Thread1", System.Threading.ThreadPriority.BelowNormal, 100); 26 | worker2 = ThreadUtils.StartWorkerThread("Demo", "Thread2", System.Threading.ThreadPriority.BelowNormal, 100); 27 | unity1 = ThreadUtils.StartUnityThread("Unity1", 100, this); 28 | unity2 = ThreadUtils.StartUnityThread("Unity2", 100, this); 29 | 30 | // automatically start demo 31 | StartSwitchContextCoroutine(); 32 | } 33 | 34 | protected void OnDestroy() 35 | { 36 | ThreadUtils.StopThread(unity2); 37 | ThreadUtils.StopThread(unity1); 38 | ThreadUtils.StopThread(worker2); 39 | ThreadUtils.StopThread(worker1); 40 | } 41 | 42 | [ContextMenu("Start Coroutine")] 43 | public void StartSwitchContextCoroutine() 44 | { 45 | worker1.StartCoroutine(SwitchContext(), host); 46 | } 47 | 48 | IEnumerator SwitchContext() 49 | { 50 | Log("Switching to worker thread 1"); 51 | yield return worker1.context; 52 | Log("Read voxel data from file"); 53 | 54 | Log("Switching to Unity thread 1"); 55 | yield return unity1.context; 56 | Log("Create GameObject"); 57 | 58 | Log("Pretend waiting for GameObject"); 59 | yield return new WaitForSeconds(1f); 60 | Log("Create GameObject done"); 61 | 62 | Log("Switching to worker thread 2"); 63 | yield return worker2.context; 64 | Log("Compute mesh"); 65 | 66 | Log("Pretend waiting for mesh"); 67 | yield return new WaitForSeconds(1f); 68 | Log("Compute mesh done"); 69 | 70 | Log("Switching to Unity thread 2"); 71 | yield return unity2.context; 72 | Log("Upload mesh"); 73 | 74 | Log("Compute sum"); 75 | var awaiter = new CallbackAwaiter(); 76 | ComputeSumAsync(this, 1, 2, awaiter.Call); 77 | yield return awaiter; 78 | Log(string.Format("Sum computed: {0}", awaiter.arg1)); 79 | 80 | Log("Loading from Resources"); 81 | var request = Resources.LoadAsync("Material"); 82 | 83 | Log("Switching to worker thread 1"); 84 | yield return worker1.context; 85 | 86 | Log("Waiting for load to finish (implicit thread change)"); 87 | yield return request; 88 | Log("Done waiting for load to finish (implicit thread change back)"); 89 | 90 | Log("Switching to Unity thread 1"); 91 | yield return unity1.context; 92 | 93 | Log("Accessing loaded asset"); 94 | Debug.Assert(request.asset, "Failed to load asset"); 95 | } 96 | 97 | private delegate void SumCallback(int sum); 98 | 99 | private static void ComputeSumAsync(MonoBehaviour host, int a, int b, SumCallback callback) 100 | { 101 | host.StartCoroutine(ComputeSumAsyncImpl(a, b, callback)); 102 | } 103 | 104 | private static IEnumerator ComputeSumAsyncImpl(int a, int b, SumCallback callback) 105 | { 106 | Log("Pretending long running computation"); 107 | yield return new WaitForSeconds(2f); 108 | 109 | Log("Returning result via callback"); 110 | int result = a + b; 111 | callback(result); 112 | } 113 | 114 | private static void Log(object msg) 115 | { 116 | var thread = Thread.CurrentThread; 117 | Debug.LogFormat("Thread {0} ({1}): {2}", thread.ManagedThreadId, thread.Name, msg); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Samples~/ContextSwitchingSamples/Scripts/SwitchContextDemo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75b4902db579bb44ab45065ba7a421db 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6dc3c6bb20d0d514e8e771f11b808d8f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 694763722e8ee984bb794529c30c00d0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/Editor/CoroutineHandlerTest.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections; 6 | using NUnit.Framework; 7 | 8 | namespace Lumpn.Threading.Tests 9 | { 10 | [TestFixture] 11 | public sealed class CoroutineHandlerTest 12 | { 13 | private int stepCounter = 0; 14 | 15 | [Test] 16 | public void AdvanceCoroutine() 17 | { 18 | Assert.AreEqual(0, stepCounter); 19 | 20 | var thread = new ManualThread(); 21 | var coroutine = thread.StartCoroutine(Coroutine(), null); 22 | 23 | Assert.AreEqual(0, stepCounter); 24 | Assert.IsTrue(coroutine.keepWaiting); 25 | 26 | bool success; 27 | success = thread.Step(); 28 | Assert.IsTrue(success); 29 | Assert.AreEqual(1, stepCounter); 30 | Assert.IsTrue(coroutine.keepWaiting); 31 | 32 | success = thread.Step(); 33 | Assert.IsTrue(success); 34 | Assert.AreEqual(2, stepCounter); 35 | Assert.IsTrue(coroutine.keepWaiting); 36 | 37 | success = thread.Step(); 38 | Assert.IsTrue(success); 39 | Assert.AreEqual(3, stepCounter); 40 | Assert.IsFalse(coroutine.keepWaiting); 41 | 42 | success = thread.Step(); 43 | Assert.IsTrue(success); 44 | Assert.AreEqual(3, stepCounter); 45 | Assert.IsFalse(coroutine.keepWaiting); 46 | 47 | success = thread.Step(); 48 | Assert.IsFalse(success); 49 | Assert.AreEqual(3, stepCounter); 50 | Assert.IsFalse(coroutine.keepWaiting); 51 | } 52 | 53 | private IEnumerator Coroutine() 54 | { 55 | stepCounter = 1; 56 | yield return null; 57 | stepCounter = 2; 58 | yield return CoroutineUtils.waitForNextFrame; 59 | stepCounter = 3; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Tests/Editor/CoroutineHandlerTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b595859047f4b1741b50d476779b80e1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/Lumpn.Threading.Editor.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lumpn.Threading.Editor.Tests", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:daffcfbc5d71a5a4eb88169476f7cdb6" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Tests/Editor/Lumpn.Threading.Editor.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: db8450292f10d0b40a1081e4f6dba15c 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests/Editor/ManualThread.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections.Generic; 6 | 7 | namespace Lumpn.Threading.Tests 8 | { 9 | public sealed class ManualThread : IThread 10 | { 11 | private readonly Queue pendingTasks = new Queue(); 12 | 13 | public bool isRunning { get { return true; } } 14 | public bool isIdle { get { return pendingTasks.Count <= 0; } } 15 | public int queueLength { get { return pendingTasks.Count; } } 16 | public ISynchronizationContext context { get { return this; } } 17 | 18 | public void Post(Callback callback, object owner, object state) 19 | { 20 | var task = new Task(callback, owner, state); 21 | pendingTasks.Enqueue(task); 22 | } 23 | 24 | public void Stop() { } 25 | 26 | public bool Step() 27 | { 28 | if (pendingTasks.Count <= 0) return false; 29 | 30 | var task = pendingTasks.Dequeue(); 31 | task.Invoke(); 32 | return true; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/Editor/ManualThread.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 141d29d60ea85fd4f98814e194983e05 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/StackTest.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Collections.Generic; 6 | using NUnit.Framework; 7 | 8 | namespace Lumpn.Threading.Tests 9 | { 10 | [TestFixture] 11 | public sealed class StackTest 12 | { 13 | [Test] 14 | public void PushSingle() 15 | { 16 | var pool = new Stack(4); 17 | var obj = new object(); 18 | pool.Push(obj); 19 | Assert.AreEqual(1, pool.Count); 20 | 21 | bool success = pool.TryPop(out object obj2); 22 | Assert.IsTrue(success); 23 | Assert.AreSame(obj, obj2); 24 | } 25 | 26 | [Test] 27 | public void PushMultiple() 28 | { 29 | var pool = new Stack(4); 30 | 31 | for (int i = 0; i < 4; i++) 32 | { 33 | pool.Push(new object()); 34 | } 35 | Assert.AreEqual(4, pool.Count); 36 | 37 | for (int i = 0; i < 4; i++) 38 | { 39 | var success = pool.TryPop(out object obj); 40 | Assert.IsTrue(success); 41 | Assert.IsNotNull(obj); 42 | } 43 | Assert.AreEqual(0, pool.Count); 44 | } 45 | 46 | [Test] 47 | public void PopEmpty() 48 | { 49 | var pool = new Stack(4); 50 | 51 | var success = pool.TryPop(out object obj); 52 | Assert.IsFalse(success); 53 | Assert.IsNull(obj); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Tests/Editor/StackTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b0e24aef270caae409811d55be3bca56 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/TaskTest.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using NUnit.Framework; 6 | 7 | namespace Lumpn.Threading.Tests 8 | { 9 | [TestFixture] 10 | public sealed class TaskTest 11 | { 12 | private int counter = 0; 13 | 14 | [Test] 15 | public void Invoke() 16 | { 17 | var task = new Task(IncrementCounter, this, null); 18 | Assert.AreEqual(0, counter); 19 | 20 | task.Invoke(); 21 | Assert.AreEqual(1, counter); 22 | 23 | task.Invoke(); 24 | Assert.AreEqual(2, counter); 25 | } 26 | 27 | private static void IncrementCounter(object owner, object state) 28 | { 29 | var test = (TaskTest)owner; 30 | test.counter++; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Tests/Editor/TaskTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c364e30add916734ab71880e52b233f5 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/UnityThreadTest.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using NUnit.Framework; 6 | 7 | namespace Lumpn.Threading.Tests 8 | { 9 | [TestFixture] 10 | public sealed class UnityThreadTest 11 | { 12 | private int counter = 0; 13 | 14 | [Test] 15 | public void Post() 16 | { 17 | var thread = new UnityThread("Test", 10); 18 | Assert.IsTrue(thread.isIdle); 19 | Assert.AreEqual(0, thread.queueLength); 20 | Assert.AreEqual(0, counter); 21 | 22 | thread.Post(IncrementCounter, this, null); 23 | thread.Post(IncrementCounter, this, null); 24 | thread.Post(IncrementCounter, this, null); 25 | Assert.IsFalse(thread.isIdle); 26 | Assert.AreEqual(3, thread.queueLength); 27 | Assert.AreEqual(0, counter); 28 | 29 | var pump = thread.Start(); 30 | while (!thread.isIdle) pump.MoveNext(); 31 | 32 | Assert.IsTrue(thread.isIdle); 33 | Assert.AreEqual(0, thread.queueLength); 34 | Assert.AreEqual(3, counter); 35 | 36 | thread.Stop(); 37 | } 38 | 39 | private static void IncrementCounter(object owner, object state) 40 | { 41 | var test = (UnityThreadTest)owner; 42 | test.counter++; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Editor/UnityThreadTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d7434a1a0e2ab0f4b8dc28e56dad7bae 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests/Editor/WorkerThreadTest.cs: -------------------------------------------------------------------------------- 1 | //---------------------------------------- 2 | // MIT License 3 | // Copyright(c) 2019 Jonas Boetel 4 | //---------------------------------------- 5 | using System.Threading; 6 | using NUnit.Framework; 7 | 8 | namespace Lumpn.Threading.Tests 9 | { 10 | [TestFixture] 11 | public sealed class WorkerThreadTest 12 | { 13 | private int counter = 0; 14 | 15 | [Test] 16 | public void Post() 17 | { 18 | var thread = new WorkerThread("Test", "TestPost", ThreadPriority.BelowNormal, 10); 19 | Assert.IsFalse(thread.isIdle); 20 | Assert.AreEqual(0, thread.queueLength); 21 | Assert.AreEqual(0, counter); 22 | 23 | thread.Post(IncrementCounter, this, null); 24 | thread.Post(IncrementCounter, this, null); 25 | thread.Post(IncrementCounter, this, null); 26 | Assert.IsFalse(thread.isIdle); 27 | Assert.AreEqual(3, thread.queueLength); 28 | Assert.AreEqual(0, counter); 29 | 30 | thread.Start(); 31 | while (!thread.isIdle) Thread.Sleep(1); 32 | 33 | Assert.IsTrue(thread.isIdle); 34 | Assert.AreEqual(0, thread.queueLength); 35 | Assert.AreEqual(3, counter); 36 | 37 | thread.Stop(); 38 | } 39 | 40 | private static void IncrementCounter(object owner, object state) 41 | { 42 | var test = (WorkerThreadTest)owner; 43 | test.counter++; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/Editor/WorkerThreadTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6732444f1b07bd24893c6383ae5a428d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "de.lumpn.unity-threading", 3 | "version": "1.1.0", 4 | "displayName": "Lumpn Threading", 5 | "description": "Non-allocating async/await threading facilities. Coroutines that flow across threads, callbacks, and yield instructions.", 6 | "dependencies": { 7 | "com.unity.ext.nunit": "1.0.0" 8 | }, 9 | "keywords": [ 10 | "lumpn", 11 | "thread", 12 | "threading", 13 | "async", 14 | "await" 15 | ], 16 | "author": { 17 | "name": "Jonas Boetel", 18 | "url": "https://github.com/lumpn" 19 | }, 20 | "samples": [ 21 | { 22 | "displayName": "Context Switching Samples", 23 | "path": "Samples~/ContextSwitchingSamples" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7d53431e70da16446aa5e9e535fe728f 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------