From f20f27259c28dbff6697958b8727d39d24f74ce1 Mon Sep 17 00:00:00 2001 From: ousttrue Date: Fri, 28 Dec 2018 21:15:54 +0900 Subject: [PATCH] Squashed 'DepthFirstScheduler/' content from commit d0c62ba git-subtree-dir: DepthFirstScheduler git-subtree-split: d0c62ba0fbef280af0ef5ec41963bef847185efb --- Editor.meta | 9 + Editor/DepthFirstScheduler.cs | 24 +++ Editor/DepthFirstScheduler.cs.meta | 12 ++ Functor.cs | 192 ++++++++++++++++++ Functor.cs.meta | 12 ++ IEnumeratorExtensions.cs | 31 +++ IEnumeratorExtensions.cs.meta | 11 ++ LICENSE | 21 ++ LICENSE.meta | 8 + LockQueue.cs | 95 +++++++++ LockQueue.cs.meta | 12 ++ MainThreadDispatcher.cs | 150 ++++++++++++++ MainThreadDispatcher.cs.meta | 12 ++ MonitorQueue.cs | 47 +++++ MonitorQueue.cs.meta | 12 ++ README.md | 66 +++++++ README.md.meta | 8 + Schedulable.cs | 241 +++++++++++++++++++++++ Schedulable.cs.meta | 12 ++ Scheduler.cs | 9 + Scheduler.cs.meta | 12 ++ Scheduler.meta | 8 + Scheduler/CurrentThreadScheduler.cs | 102 ++++++++++ Scheduler/CurrentThreadScheduler.cs.meta | 11 ++ Scheduler/StepScheduler.cs | 52 +++++ Scheduler/StepScheduler.cs.meta | 12 ++ Scheduler/ThreadPoolScheduler.cs | 42 ++++ Scheduler/ThreadPoolScheduler.cs.meta | 12 ++ Scheduler/ThreadScheduler.cs | 101 ++++++++++ Scheduler/ThreadScheduler.cs.meta | 12 ++ TaskChain.cs | 91 +++++++++ TaskChain.cs.meta | 12 ++ Unit.cs | 41 ++++ Unit.cs.meta | 12 ++ 34 files changed, 1504 insertions(+) create mode 100644 Editor.meta create mode 100644 Editor/DepthFirstScheduler.cs create mode 100644 Editor/DepthFirstScheduler.cs.meta create mode 100644 Functor.cs create mode 100644 Functor.cs.meta create mode 100644 IEnumeratorExtensions.cs create mode 100644 IEnumeratorExtensions.cs.meta create mode 100644 LICENSE create mode 100644 LICENSE.meta create mode 100644 LockQueue.cs create mode 100644 LockQueue.cs.meta create mode 100644 MainThreadDispatcher.cs create mode 100644 MainThreadDispatcher.cs.meta create mode 100644 MonitorQueue.cs create mode 100644 MonitorQueue.cs.meta create mode 100644 README.md create mode 100644 README.md.meta create mode 100644 Schedulable.cs create mode 100644 Schedulable.cs.meta create mode 100644 Scheduler.cs create mode 100644 Scheduler.cs.meta create mode 100644 Scheduler.meta create mode 100644 Scheduler/CurrentThreadScheduler.cs create mode 100644 Scheduler/CurrentThreadScheduler.cs.meta create mode 100644 Scheduler/StepScheduler.cs create mode 100644 Scheduler/StepScheduler.cs.meta create mode 100644 Scheduler/ThreadPoolScheduler.cs create mode 100644 Scheduler/ThreadPoolScheduler.cs.meta create mode 100644 Scheduler/ThreadScheduler.cs create mode 100644 Scheduler/ThreadScheduler.cs.meta create mode 100644 TaskChain.cs create mode 100644 TaskChain.cs.meta create mode 100644 Unit.cs create mode 100644 Unit.cs.meta diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 000000000..d8c297a96 --- /dev/null +++ b/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: cc0ab91a2011fcf4d925dd94bccdb243 +folderAsset: yes +timeCreated: 1540294739 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/DepthFirstScheduler.cs b/Editor/DepthFirstScheduler.cs new file mode 100644 index 000000000..fe09df4dd --- /dev/null +++ b/Editor/DepthFirstScheduler.cs @@ -0,0 +1,24 @@ +using NUnit.Framework; +using System.Linq; + + +namespace DepthFirstScheduler +{ + public class DepthFirstScheduler + { + [Test] + public void ScheduleTreeTest() + { + var s = Schedulable.Create(); + + var tasks = s.GetRoot().Traverse().ToArray(); + Assert.AreEqual(2, tasks.Length); + + var task_int = s.AddTask(Scheduler.CurrentThread, () => 0); + task_int = task_int.ContinueWith(Scheduler.CurrentThread, _ => 1); + + var status = s.Execute(); + Assert.AreEqual(ExecutionStatus.Done, status); + } + } +} diff --git a/Editor/DepthFirstScheduler.cs.meta b/Editor/DepthFirstScheduler.cs.meta new file mode 100644 index 000000000..8053a0ee4 --- /dev/null +++ b/Editor/DepthFirstScheduler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3003e87b5df13394eadfda925bb816e5 +timeCreated: 1540294752 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Functor.cs b/Functor.cs new file mode 100644 index 000000000..3ddf9fd97 --- /dev/null +++ b/Functor.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace DepthFirstScheduler +{ + public enum ExecutionStatus + { + Unknown, + Done, + Continue, // coroutine or schedulable + Error, + } + + public interface IFunctor + { + T GetResult(); + Exception GetError(); + ExecutionStatus Execute(); + } + + #region Functor + public class Functor : IFunctor + { + T m_result; + public T GetResult() + { + return m_result; + } + + Exception m_error; + public Exception GetError() + { + return m_error; + } + + Action m_pred; + public Functor(Func func) + { + m_pred = () => m_result = func(); + } + + public ExecutionStatus Execute() + { + try + { + m_pred(); + return ExecutionStatus.Done; + } + catch (Exception ex) + { + m_error = ex; + return ExecutionStatus.Error; + } + } + } + + public static class Functor + { + /// + /// 引数の型を隠蔽した実行器を生成する + /// + /// 引数の型 + /// 結果の型 + /// + /// + /// + public static Functor Create(Func arg, Func pred) + { + return new Functor(() => pred(arg())); + } + } + #endregion + + #region CoroutineFunctor + public class CoroutineFunctor : IFunctor + { + T m_result; + public T GetResult() + { + return m_result; + } + + Exception m_error; + public Exception GetError() + { + return m_error; + } + + Func m_arg; + Func m_starter; + Stack m_it; + public CoroutineFunctor(Func arg, Func starter) + { + m_arg = arg; + m_starter = starter; + } + + public ExecutionStatus Execute() + { + if (m_it == null) + { + m_result = m_arg(); + m_it = new Stack(); + m_it.Push(m_starter(m_result)); + } + + try + { + if (m_it.Count!=0) + { + if (m_it.Peek().MoveNext()) + { + var nested = m_it.Peek().Current as IEnumerator; + if (nested!=null) + { + m_it.Push(nested); + } + } + else + { + m_it.Pop(); + } + return ExecutionStatus.Continue; + } + else + { + return ExecutionStatus.Done; + } + + } + catch(Exception ex) + { + m_error = ex; + return ExecutionStatus.Error; + } + } + } + + public static class CoroutineFunctor + { + public static CoroutineFunctor Create(Func arg, Func starter) + { + return new CoroutineFunctor(arg, starter); + } + } + #endregion + + /* + public class SchedulableFunctor : IFunctor + { + Schedulable m_schedulable; + Func> m_starter; + TaskChain m_chain; + + public SchedulableFunctor(Func> starter) + { + m_starter = starter; + } + + public ExecutionStatus Execute() + { + if (m_chain == null) + { + m_schedulable = m_starter(); + m_chain = TaskChain.Schedule(m_schedulable, ex => m_error = ex); + } + + return m_chain.Next(); + } + + Exception m_error; + public Exception GetError() + { + return m_error; + } + + public T GetResult() + { + return m_schedulable.Func.GetResult(); + } + } + + public static class SchedulableFunctor + { + public static SchedulableFunctor Create(Func> starter) + { + return new SchedulableFunctor(starter); + } + } + */ +} diff --git a/Functor.cs.meta b/Functor.cs.meta new file mode 100644 index 000000000..713019b19 --- /dev/null +++ b/Functor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c320f3f577dde634a871dc88266c2a20 +timeCreated: 1520084196 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/IEnumeratorExtensions.cs b/IEnumeratorExtensions.cs new file mode 100644 index 000000000..2d11c0ebc --- /dev/null +++ b/IEnumeratorExtensions.cs @@ -0,0 +1,31 @@ +using System.Collections; +using System.Collections.Generic; + + +namespace + DepthFirstScheduler +{ + public static class IEnumeratorExtensions + { + public static void CoroutinetoEnd(this IEnumerator coroutine) + { + var stack = new Stack(); + stack.Push(coroutine); + while (stack.Count > 0) + { + if (stack.Peek().MoveNext()) + { + var nested = stack.Peek().Current as IEnumerator; + if (nested != null) + { + stack.Push(nested); + } + } + else + { + stack.Pop(); + } + } + } + } +} diff --git a/IEnumeratorExtensions.cs.meta b/IEnumeratorExtensions.cs.meta new file mode 100644 index 000000000..b8a9f110a --- /dev/null +++ b/IEnumeratorExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4719fc9e6319c654b8a4818bef2c3a85 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..3299d454f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 ousttrue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSE.meta b/LICENSE.meta new file mode 100644 index 000000000..2ea0914dd --- /dev/null +++ b/LICENSE.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b673d33707cbc5446870804437f3cbea +timeCreated: 1535290899 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/LockQueue.cs b/LockQueue.cs new file mode 100644 index 000000000..22f88ddd1 --- /dev/null +++ b/LockQueue.cs @@ -0,0 +1,95 @@ +using System.Collections; +using System.Collections.Generic; + + +namespace DepthFirstScheduler +{ + public class LockQueue where T : class + { + List m_queue = new List(); + public int Count + { + get + { + lock (((ICollection)m_queue).SyncRoot) + { + return m_queue.Count; + } + } + } + + public void Enqueue(T t) + { + lock (((ICollection)m_queue).SyncRoot) + { + m_queue.Add(t); + } + } + + public T Dequeue(out int remain) + { + lock (((ICollection)m_queue).SyncRoot) + { + if (m_queue.Count == 0) + { + remain = 0; + return null; + } + var item = m_queue[0]; + m_queue.RemoveAt(0); + remain = m_queue.Count; + return item; + } + } + + public T Dequeue() + { + lock (((ICollection)m_queue).SyncRoot) + { + if (m_queue.Count == 0) return null; + var item = m_queue[0]; + m_queue.RemoveAt(0); + return item; + } + } + } + + public class LockQueueForValue where T : struct + { + List m_queue = new List(); + public int Count + { + get + { + lock (((ICollection)m_queue).SyncRoot) + { + return m_queue.Count; + } + } + } + + public void Enqueue(T t) + { + lock (((ICollection)m_queue).SyncRoot) + { + m_queue.Add(t); + } + } + + public bool TryDequeue(out T t) + { + lock (((ICollection)m_queue).SyncRoot) + { + if (m_queue.Count == 0) + { + t = default(T); + return false; + } + + t = m_queue[0]; + m_queue.RemoveAt(0); + return true; + } + } + } +} diff --git a/LockQueue.cs.meta b/LockQueue.cs.meta new file mode 100644 index 000000000..cc5d5cfa9 --- /dev/null +++ b/LockQueue.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f6b1eb436e400704ab6e2ad6eff29f4b +timeCreated: 1519978475 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MainThreadDispatcher.cs b/MainThreadDispatcher.cs new file mode 100644 index 000000000..ee2bdcfc4 --- /dev/null +++ b/MainThreadDispatcher.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace DepthFirstScheduler +{ + /// + /// UniRxのMainThreadDispatcherを参考にした。 + /// * https://github.com/neuecc/UniRx/blob/master/Assets/Plugins/UniRx/Scripts/UnityEngineBridge/MainThreadDispatcher.cs + /// + public class MainThreadDispatcher : MonoBehaviour + { + + [Header("Debug")] + public int TaskCount; + + IEnumerable Ancestors(Transform t) + { + yield return t; + + if (t.parent != null) + { + foreach (var x in Ancestors(t.parent)) + { + yield return x; + } + } + } + + private void Update() + { + TaskCount = Scheduler.MainThread.UpdateAndGetTaskCount(); + } + + static MainThreadDispatcher instance; + static bool initialized; + static bool isQuitting = false; + + public static bool IsInitialized + { + get { return initialized && instance != null; } + } + + [ThreadStatic] + static object mainThreadToken; + + public static MainThreadDispatcher Instance + { + get + { + Initialize(); + return instance; + } + } + + public static void Initialize() + { + if (!initialized) + { +#if UNITY_EDITOR + if (!Application.isPlaying) + { + return; + } +#endif + MainThreadDispatcher dispatcher = null; + + try + { + dispatcher = GameObject.FindObjectOfType(); + } + catch + { + // Throw exception when calling from a worker thread. + var ex = new Exception( + "DepthFirstScheduler requires a MainThreadDispatcher component created on the main thread." + + " Make sure it is added to the scene before calling DepthFirstScheduler from a worker thread."); + UnityEngine.Debug.LogException(ex); + throw ex; + } + + if (isQuitting) + { + // don't create new instance after quitting + // avoid "Some objects were not cleaned up when closing the scene find target" error. + return; + } + + if (dispatcher == null) + { + // awake call immediately from UnityEngine + new GameObject("DepthFirstScheduler").AddComponent(); + } + else + { + dispatcher.Awake(); // force awake + } + } + } + + public static bool IsInMainThread + { + get + { + return (mainThreadToken != null); + } + } + + + void Awake() + { + if (instance == null) + { + Debug.Log("Initialize UniTask.MainThredDispatcher"); + + instance = this; + mainThreadToken = new object(); + initialized = true; + + DontDestroyOnLoad(gameObject); + } + else + { + if (this != instance) + { + Debug.LogWarning("There is already a MainThreadDispatcher in the scene."); + } + } + } + + void OnDestroy() + { + if (instance == this) + { + instance = GameObject.FindObjectOfType(); + initialized = instance != null; + } + + if (Scheduler.SingleWorkerThread != null) + { + Scheduler.SingleWorkerThread.Dispose(); + } + } + + void OnApplicationQuit() + { + isQuitting = true; + } + } +} diff --git a/MainThreadDispatcher.cs.meta b/MainThreadDispatcher.cs.meta new file mode 100644 index 000000000..5777e1aba --- /dev/null +++ b/MainThreadDispatcher.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b030a9507e46dd3488c080be0227b219 +timeCreated: 1519977925 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MonitorQueue.cs b/MonitorQueue.cs new file mode 100644 index 000000000..ace9ef219 --- /dev/null +++ b/MonitorQueue.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Threading; + + +namespace DepthFirstScheduler +{ + /// + /// http://blogs.msdn.com/b/toub/archive/2006/04/12/blocking-queues.aspx + /// + /// 終了を通知するにはnullを投入する手が使える + /// + /// + public class MonitorQueue + { + private Int32 _count = 0; + public Int32 Count + { + get + { + return _count; + } + } + + private Queue _queue = new Queue(); + + public T Dequeue() + { + lock (_queue) + { + while (_count <= 0) Monitor.Wait(_queue); + _count--; + return _queue.Dequeue(); + } + } + + public void Enqueue(T data) + { + lock (_queue) + { + _queue.Enqueue(data); + _count++; + Monitor.Pulse(_queue); + } + } + } +} diff --git a/MonitorQueue.cs.meta b/MonitorQueue.cs.meta new file mode 100644 index 000000000..2b6391747 --- /dev/null +++ b/MonitorQueue.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5e968e04ff53e1c4a9a2869b7cf76055 +timeCreated: 1519990411 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 000000000..f62408945 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# DepthFirstScheduler(深さ優先スケジューラー) +Asynchronous task scheduler for Unity-5.6 or later + +これは、Unity5.6でTaskが無いことを補完するためのライブラリです。 +木構造にタスクを組み立てて深さ優先で消化します。 + +* タスクの実行スケジューラー(Unityメインスレッドやスレッドプール)を指定できる + +# 使い方 + +```cs +var schedulable = new Schedulable(); + +schedulable + .AddTask(Scheduler.ThreadPool, () => // 子供のタスクを追加する + { + return glTF_VRM_Material.Parse(ctx.Json); + }) + .ContinueWith(Scheduler.MainThread, gltfMaterials => // 兄弟のタスクを追加する + { + ctx.MaterialImporter = new VRMMaterialImporter(ctx, gltfMaterials); + }) + .Subscribe(Scheduler.MainThread, onLoaded, onError); + ; +``` + +# Schedulable +T型の結果を返すタスク。 + +## AddTask(IScheduler scheduler, Func firstTask) + +子供のタスクを追加する。 + +ToDo: 一つ目の子供に引数を渡す手段が無い + +## ContinueWith + +## ContinueWithCoroutine + +## OnExecute + +動的にタスクを追加するためのHook。 + +中で、 + +``` +parent.AddTask +``` + +することで実行時に子タスクを追加できる。 + +## Subscribe +タスクの実行を開始する。 +実行結果を得る。 + + +# Scheduler +## StepScheduler +Unity +## CurrentThreadScheduler +即時 +## ThreadPoolScheduler +スレッド +## ThreadScheduler +スレッド + diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 000000000..7d1cb78be --- /dev/null +++ b/README.md.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1231fa531fdd33f4baccc935c18c0872 +timeCreated: 1535290899 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Schedulable.cs b/Schedulable.cs new file mode 100644 index 000000000..96a198d1d --- /dev/null +++ b/Schedulable.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections; +using System.Collections.Generic; +#if ((NET_4_6 || NET_STANDARD_2_0) && UNITY_2017_1_OR_NEWER) +using System.Threading.Tasks; +#endif + +namespace DepthFirstScheduler +{ + public interface ISchedulable + { + /// 実行が終了したか?Coroutineの実行が一回で終わらない場合がある + ExecutionStatus Execute(); + Exception GetError(); + IScheduler Schedulder { get; } + + ISchedulable Parent { get; set; } + void AddChild(ISchedulable child); + IEnumerable Traverse(); + } + + public static class ISchedulableExtensions + { + public static ISchedulable GetRoot(this ISchedulable self) + { + var current = self; + while (current.Parent != null) + { + current = current.Parent; + } + return current; + } + } + + public class NoParentException: Exception + { + public NoParentException():base("No parent task can't ContinueWith or OnExecute. First AddTask") + { + } + } + + public class Schedulable : ISchedulable + { + List m_children = new List(); + public void AddChild(ISchedulable child) + { + child.Parent = this; + m_children.Add(child); + } + public IEnumerable Traverse() + { + yield return this; + + foreach (var child in m_children) + { + foreach (var x in child.Traverse()) + { + yield return x; + } + } + } + + public ISchedulable Parent + { + get; + set; + } + + public IScheduler Schedulder + { + get; + private set; + } + + public IFunctor Func + { + get; + private set; + } + + public Exception GetError() + { + return Func.GetError(); + } + + public Schedulable() + { + } + + public Schedulable(IScheduler scheduler, IFunctor func) + { + Schedulder = scheduler; + Func = func; + } + + public ExecutionStatus Execute() + { + if (Func == null) + { + return ExecutionStatus.Done; + } + return Func.Execute(); + } + + /// + /// スケジュールされたタスクをすべて即時に実行する + /// + public void ExecuteAll() + { + foreach (var x in this.GetRoot().Traverse()) + { + while (true) + { + var status = x.Execute(); + if (status != ExecutionStatus.Continue) + { + if (status == ExecutionStatus.Error) + { + throw x.GetError(); + } + break; + } + // Coroutineタスクが継続している + } + } + } + + public Schedulable AddTask(IScheduler scheduler, Action pred) + { + return AddTask(scheduler, () => { pred(); return Unit.Default; }); + } + + public Schedulable AddTask(IScheduler scheduler, Func pred) + { + var schedulable = new Schedulable(scheduler, Functor.Create(() => Unit.Default, _ => pred())); + AddChild(schedulable); + return schedulable; + } + + public Schedulable AddCoroutine(IScheduler scheduler, Func starter) + { + var func = CoroutineFunctor.Create(() => default(T), _ => starter()); + var schedulable = new Schedulable(scheduler, func); + AddChild(schedulable); + return schedulable; + } + + public Schedulable ContinueWith(IScheduler scheduler, Action pred) + { + return ContinueWith(scheduler, t => { pred(t); return Unit.Default; }); + } + + public Schedulable ContinueWith(IScheduler scheduler, Func pred) + { + if (Parent == null) + { + throw new NoParentException(); + } + + Func getResult = null; + if (Func != null) + { + getResult = Func.GetResult; + } + var func = Functor.Create(getResult, pred); + var schedulable = new Schedulable(scheduler, func); + Parent.AddChild(schedulable); + return schedulable; + } + + public Schedulable ContinueWithCoroutine(IScheduler scheduler, Func starter) + { + if (Parent == null) + { + throw new NoParentException(); + } + + var func = CoroutineFunctor.Create(() => default(T), _ => starter()); + var schedulable = new Schedulable(scheduler, func); + Parent.AddChild(schedulable); + return schedulable; + } + + public Schedulable OnExecute(IScheduler scheduler, Action> pred) + { + if (Parent == null) + { + throw new NoParentException(); + } + + Func getResult = null; + if (Func != null) + { + getResult = Func.GetResult; + } + + var schedulable = new Schedulable(); + schedulable.Func = Functor.Create(getResult, _ => { pred(schedulable); return Unit.Default; }); + Parent.AddChild(schedulable); + return schedulable; + } + } + + public static class Schedulable + { + public static Schedulable Create() + { + return new Schedulable().AddTask(Scheduler.CurrentThread, () => + { + }); + } + } + + public static class SchedulableExtensions + { + public static void Subscribe( + this Schedulable schedulable, + IScheduler scheduler, + Action onCompleted, + Action onError) + { + schedulable.ContinueWith(scheduler, onCompleted); + TaskChain.Schedule(schedulable.GetRoot(), onError); + } + +#if ((NET_4_6 || NET_STANDARD_2_0) && UNITY_2017_1_OR_NEWER) + public static Task ToTask(this Schedulable schedulable) + { + return ToTask(schedulable, Scheduler.MainThread); + } + + public static Task ToTask(this Schedulable schedulable, IScheduler scheduler) + { + var tcs = new TaskCompletionSource(); + schedulable.Subscribe(scheduler, r => tcs.TrySetResult(r), ex => tcs.TrySetException(ex)); + return tcs.Task; + } +#endif + + } +} diff --git a/Schedulable.cs.meta b/Schedulable.cs.meta new file mode 100644 index 000000000..439f4a26b --- /dev/null +++ b/Schedulable.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 45e21e7db278f344fbaeffc4c4b82b1e +timeCreated: 1519981307 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scheduler.cs b/Scheduler.cs new file mode 100644 index 000000000..20ae4d4a1 --- /dev/null +++ b/Scheduler.cs @@ -0,0 +1,9 @@ +using System; + +namespace DepthFirstScheduler +{ + public interface IScheduler : IDisposable + { + void Enqueue(TaskChain item); + } +} diff --git a/Scheduler.cs.meta b/Scheduler.cs.meta new file mode 100644 index 000000000..38b437574 --- /dev/null +++ b/Scheduler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5a160dc164df4094dbbc892055ac82ae +timeCreated: 1520084097 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scheduler.meta b/Scheduler.meta new file mode 100644 index 000000000..efdb87bad --- /dev/null +++ b/Scheduler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0700fb042f010694782d238049678651 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scheduler/CurrentThreadScheduler.cs b/Scheduler/CurrentThreadScheduler.cs new file mode 100644 index 000000000..2fa03e26b --- /dev/null +++ b/Scheduler/CurrentThreadScheduler.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace DepthFirstScheduler +{ + public static partial class Scheduler + { + private static IScheduler currentThread; + + public static IScheduler CurrentThread + { + get { return currentThread ?? (currentThread = new CurrentThreadScheduler()); } + } + + public class CurrentThreadScheduler : IScheduler + { + [ThreadStatic] + private static Queue queue; + + private static Queue GetQueue() + { + return queue; + } + + private static void SetQueue(Queue newQueue) + { + queue = newQueue; + } + + public void Enqueue(TaskChain item) + { + var q = GetQueue(); + + if (q == null) + { + q = new Queue(5); + q.Enqueue(item); + SetQueue(q); + + try + { + Trampoline.Run(q); + } + finally + { + SetQueue(null); + } + } + else + { + q.Enqueue(item); + } + } + + #region IDisposable Support + + private bool disposedValue = false; + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + var queue = GetQueue(); + if (queue != null) queue.Clear(); + SetQueue(null); + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + + #endregion + } + + static class Trampoline + { + public static void Run(Queue queue) + { + while (queue.Count > 0) + { + var chain = queue.Dequeue(); + + while (true) + { + var status = chain.Next(); + if (status != ExecutionStatus.Continue) + { + break; + } + } + } + } + } + } +} diff --git a/Scheduler/CurrentThreadScheduler.cs.meta b/Scheduler/CurrentThreadScheduler.cs.meta new file mode 100644 index 000000000..57b7907fd --- /dev/null +++ b/Scheduler/CurrentThreadScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 21979cc7137a17d4ea8b6202381a02d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scheduler/StepScheduler.cs b/Scheduler/StepScheduler.cs new file mode 100644 index 000000000..e5406ebe0 --- /dev/null +++ b/Scheduler/StepScheduler.cs @@ -0,0 +1,52 @@ +namespace DepthFirstScheduler +{ + public static partial class Scheduler + { + private static StepScheduler mainThread; + + public static StepScheduler MainThread + { + get + { + if (mainThread != null) return mainThread; + mainThread = new StepScheduler(); + MainThreadDispatcher.Initialize(); + return mainThread; + } + } + + public class StepScheduler : IScheduler + { + LockQueue m_taskQueue = new LockQueue(); + + public void Enqueue(TaskChain item) + { + m_taskQueue.Enqueue(item); + } + + TaskChain m_chain; + + public int UpdateAndGetTaskCount() + { + if (m_chain != null) + { + var status = m_chain.Next(); + if (status == ExecutionStatus.Continue) + { + // m_item継続中 + return m_taskQueue.Count; + } + m_chain = null; + } + + int count; + m_chain = m_taskQueue.Dequeue(out count); + return count; + } + + public void Dispose() + { + } + } + } +} diff --git a/Scheduler/StepScheduler.cs.meta b/Scheduler/StepScheduler.cs.meta new file mode 100644 index 000000000..f5e8c0920 --- /dev/null +++ b/Scheduler/StepScheduler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 84161938c020c37419bc79f021f849fb +timeCreated: 1520062584 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scheduler/ThreadPoolScheduler.cs b/Scheduler/ThreadPoolScheduler.cs new file mode 100644 index 000000000..6c2dbea93 --- /dev/null +++ b/Scheduler/ThreadPoolScheduler.cs @@ -0,0 +1,42 @@ +using System; + +namespace DepthFirstScheduler +{ + public static partial class Scheduler + { + private static IScheduler threadPool; + + public static IScheduler ThreadPool + { + get { return threadPool ?? (threadPool = new ThreadPoolScheduler()); } + } + + public class ThreadPoolScheduler : IScheduler + { + public void Enqueue(TaskChain item) + { + System.Threading.ThreadPool.QueueUserWorkItem(_ => + { + if (item == null) + { + return; + } + + while (true) + { + var status = item.Next(); + if (status != ExecutionStatus.Continue) + { + break; + } + } + + }); + } + + public void Dispose() + { + } + } + } +} diff --git a/Scheduler/ThreadPoolScheduler.cs.meta b/Scheduler/ThreadPoolScheduler.cs.meta new file mode 100644 index 000000000..42a8066ec --- /dev/null +++ b/Scheduler/ThreadPoolScheduler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d8a8d78a486bbdf4b95ae83d02b480d8 +timeCreated: 1524110819 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scheduler/ThreadScheduler.cs b/Scheduler/ThreadScheduler.cs new file mode 100644 index 000000000..0c44c5507 --- /dev/null +++ b/Scheduler/ThreadScheduler.cs @@ -0,0 +1,101 @@ +using System; +using System.Threading; + +namespace DepthFirstScheduler +{ + public static partial class Scheduler + { + private static IScheduler singleWorkerThread; + + public static IScheduler SingleWorkerThread + { + get { return singleWorkerThread ?? (singleWorkerThread = new ThreadScheduler()); } + } + + public class ThreadScheduler : IScheduler + { + MonitorQueue m_queue = new MonitorQueue(); + + Thread m_thread; + + public ThreadScheduler() + { + // start worker thread + m_thread = new Thread(new ParameterizedThreadStart(Worker)); + m_thread.Start(m_queue); + } + + static void Worker(Object arg) + { + MonitorQueue queue = (MonitorQueue)arg; + while (true) + { + var chain = queue.Dequeue(); + if (chain == null) + { + break; + } + + while (true) + { + var status = chain.Next(); + if (status != ExecutionStatus.Continue) + { + break; + } + } + } + + // end + } + + public void Enqueue(TaskChain item) + { + m_queue.Enqueue(item); + } + + #region IDisposable Support + + private bool disposedValue = false; // 重複する呼び出しを検出するには + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: マネージ状態を破棄します (マネージ オブジェクト)。 + if (m_thread != null) + { + m_queue.Enqueue(null); + m_thread.Join(); + m_thread = null; + } + } + + // TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下のファイナライザーをオーバーライドします。 + // TODO: 大きなフィールドを null に設定します。 + + disposedValue = true; + } + } + + // TODO: 上の Dispose(bool disposing) にアンマネージ リソースを解放するコードが含まれる場合にのみ、ファイナライザーをオーバーライドします。 + // ~ThreadScheduler() { + // // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 + // Dispose(false); + // } + + // このコードは、破棄可能なパターンを正しく実装できるように追加されました。 + public void Dispose() + { + // このコードを変更しないでください。クリーンアップ コードを上の Dispose(bool disposing) に記述します。 + Dispose(true); + // TODO: 上のファイナライザーがオーバーライドされる場合は、次の行のコメントを解除してください。 + // GC.SuppressFinalize(this); + } + + #endregion + } + } +} diff --git a/Scheduler/ThreadScheduler.cs.meta b/Scheduler/ThreadScheduler.cs.meta new file mode 100644 index 000000000..9e4cb5569 --- /dev/null +++ b/Scheduler/ThreadScheduler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8df1de98bdc0b534bbe68e8f2f8a858f +timeCreated: 1520062592 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TaskChain.cs b/TaskChain.cs new file mode 100644 index 000000000..7928e5bd6 --- /dev/null +++ b/TaskChain.cs @@ -0,0 +1,91 @@ +using System; +using System.Linq; +using System.Collections.Generic; + + +namespace DepthFirstScheduler +{ + public enum ChainStatus + { + Unknown, + Continue, + Done, + Error, + } + + public class TaskChain + { + public IEnumerator Enumerator; + public Action OnError; + public ChainStatus ChainStatus; + + public static TaskChain Schedule(ISchedulable schedulable, Action onError) + { + var item = new TaskChain + { + Enumerator = schedulable.Traverse().GetEnumerator(), + OnError = onError + }; + + if (item.Enumerator.MoveNext()) + { + if (item.Enumerator.Current.Schedulder == null) + { + // default + Scheduler.MainThread.Enqueue(item); + } + else + { + item.Enumerator.Current.Schedulder.Enqueue(item); + } + } + + return item; + } + + /// + /// + /// + /// + public ExecutionStatus Next() + { + if (this.ChainStatus == ChainStatus.Done + || this.ChainStatus== ChainStatus.Error) + { + return ExecutionStatus.Done; + } + + { + var status = Enumerator.Current.Execute(); + if (status == ExecutionStatus.Error) + { + ChainStatus = ChainStatus.Error; + OnError(Enumerator.Current.GetError()); + } + if (status == ExecutionStatus.Continue) + { + // 中断(coroutine) + ChainStatus = ChainStatus.Continue; + return ExecutionStatus.Continue; + } + } + + if (!Enumerator.MoveNext()) + { + // 終了 + ChainStatus = ChainStatus.Done; + return ExecutionStatus.Done; + } + + if (Enumerator.Current.Schedulder != null) + { + // Scheduleして中断 + ChainStatus = ChainStatus.Continue; + Enumerator.Current.Schedulder.Enqueue(this); + return ExecutionStatus.Done; + } + + return Next(); + } + } +} diff --git a/TaskChain.cs.meta b/TaskChain.cs.meta new file mode 100644 index 000000000..e50b9f19b --- /dev/null +++ b/TaskChain.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 38fc7e076ba93a847ad72d3d459d06b6 +timeCreated: 1520084019 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Unit.cs b/Unit.cs new file mode 100644 index 000000000..c3a041d1e --- /dev/null +++ b/Unit.cs @@ -0,0 +1,41 @@ +using System; + +namespace DepthFirstScheduler +{ + [Serializable] + public struct Unit : IEquatable + { + static readonly Unit @default = new Unit(); + + public static Unit Default { get { return @default; } } + + public static bool operator ==(Unit first, Unit second) + { + return true; + } + + public static bool operator !=(Unit first, Unit second) + { + return false; + } + + public bool Equals(Unit other) + { + return true; + } + public override bool Equals(object obj) + { + return obj is Unit; + } + + public override int GetHashCode() + { + return 0; + } + + public override string ToString() + { + return "()"; + } + } +} diff --git a/Unit.cs.meta b/Unit.cs.meta new file mode 100644 index 000000000..24f0ea548 --- /dev/null +++ b/Unit.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 98bd42f8c3a895d4bac6cbcf523878bb +timeCreated: 1520081817 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: