diff --git a/DepthFirstScheduler/Editor.meta b/DepthFirstScheduler/Editor.meta new file mode 100644 index 000000000..d8c297a96 --- /dev/null +++ b/DepthFirstScheduler/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: cc0ab91a2011fcf4d925dd94bccdb243 +folderAsset: yes +timeCreated: 1540294739 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/DepthFirstScheduler/Editor/DepthFirstScheduler.cs b/DepthFirstScheduler/Editor/DepthFirstScheduler.cs new file mode 100644 index 000000000..fe09df4dd --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Editor/DepthFirstScheduler.cs.meta b/DepthFirstScheduler/Editor/DepthFirstScheduler.cs.meta new file mode 100644 index 000000000..8053a0ee4 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Functor.cs b/DepthFirstScheduler/Functor.cs new file mode 100644 index 000000000..3ddf9fd97 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Functor.cs.meta b/DepthFirstScheduler/Functor.cs.meta new file mode 100644 index 000000000..713019b19 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/IEnumeratorExtensions.cs b/DepthFirstScheduler/IEnumeratorExtensions.cs new file mode 100644 index 000000000..2d11c0ebc --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/IEnumeratorExtensions.cs.meta b/DepthFirstScheduler/IEnumeratorExtensions.cs.meta new file mode 100644 index 000000000..b8a9f110a --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/LICENSE b/DepthFirstScheduler/LICENSE new file mode 100644 index 000000000..3299d454f --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/LICENSE.meta b/DepthFirstScheduler/LICENSE.meta new file mode 100644 index 000000000..2ea0914dd --- /dev/null +++ b/DepthFirstScheduler/LICENSE.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b673d33707cbc5446870804437f3cbea +timeCreated: 1535290899 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/DepthFirstScheduler/LockQueue.cs b/DepthFirstScheduler/LockQueue.cs new file mode 100644 index 000000000..22f88ddd1 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/LockQueue.cs.meta b/DepthFirstScheduler/LockQueue.cs.meta new file mode 100644 index 000000000..cc5d5cfa9 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/MainThreadDispatcher.cs b/DepthFirstScheduler/MainThreadDispatcher.cs new file mode 100644 index 000000000..ee2bdcfc4 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/MainThreadDispatcher.cs.meta b/DepthFirstScheduler/MainThreadDispatcher.cs.meta new file mode 100644 index 000000000..5777e1aba --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/MonitorQueue.cs b/DepthFirstScheduler/MonitorQueue.cs new file mode 100644 index 000000000..ace9ef219 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/MonitorQueue.cs.meta b/DepthFirstScheduler/MonitorQueue.cs.meta new file mode 100644 index 000000000..2b6391747 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/README.md b/DepthFirstScheduler/README.md new file mode 100644 index 000000000..f62408945 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/README.md.meta b/DepthFirstScheduler/README.md.meta new file mode 100644 index 000000000..7d1cb78be --- /dev/null +++ b/DepthFirstScheduler/README.md.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1231fa531fdd33f4baccc935c18c0872 +timeCreated: 1535290899 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/DepthFirstScheduler/Schedulable.cs b/DepthFirstScheduler/Schedulable.cs new file mode 100644 index 000000000..96a198d1d --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Schedulable.cs.meta b/DepthFirstScheduler/Schedulable.cs.meta new file mode 100644 index 000000000..439f4a26b --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler.cs b/DepthFirstScheduler/Scheduler.cs new file mode 100644 index 000000000..20ae4d4a1 --- /dev/null +++ b/DepthFirstScheduler/Scheduler.cs @@ -0,0 +1,9 @@ +using System; + +namespace DepthFirstScheduler +{ + public interface IScheduler : IDisposable + { + void Enqueue(TaskChain item); + } +} diff --git a/DepthFirstScheduler/Scheduler.cs.meta b/DepthFirstScheduler/Scheduler.cs.meta new file mode 100644 index 000000000..38b437574 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler.meta b/DepthFirstScheduler/Scheduler.meta new file mode 100644 index 000000000..efdb87bad --- /dev/null +++ b/DepthFirstScheduler/Scheduler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0700fb042f010694782d238049678651 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/DepthFirstScheduler/Scheduler/CurrentThreadScheduler.cs b/DepthFirstScheduler/Scheduler/CurrentThreadScheduler.cs new file mode 100644 index 000000000..2fa03e26b --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler/CurrentThreadScheduler.cs.meta b/DepthFirstScheduler/Scheduler/CurrentThreadScheduler.cs.meta new file mode 100644 index 000000000..57b7907fd --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler/StepScheduler.cs b/DepthFirstScheduler/Scheduler/StepScheduler.cs new file mode 100644 index 000000000..e5406ebe0 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler/StepScheduler.cs.meta b/DepthFirstScheduler/Scheduler/StepScheduler.cs.meta new file mode 100644 index 000000000..f5e8c0920 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler/ThreadPoolScheduler.cs b/DepthFirstScheduler/Scheduler/ThreadPoolScheduler.cs new file mode 100644 index 000000000..6c2dbea93 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler/ThreadPoolScheduler.cs.meta b/DepthFirstScheduler/Scheduler/ThreadPoolScheduler.cs.meta new file mode 100644 index 000000000..42a8066ec --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler/ThreadScheduler.cs b/DepthFirstScheduler/Scheduler/ThreadScheduler.cs new file mode 100644 index 000000000..0c44c5507 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Scheduler/ThreadScheduler.cs.meta b/DepthFirstScheduler/Scheduler/ThreadScheduler.cs.meta new file mode 100644 index 000000000..9e4cb5569 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/TaskChain.cs b/DepthFirstScheduler/TaskChain.cs new file mode 100644 index 000000000..7928e5bd6 --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/TaskChain.cs.meta b/DepthFirstScheduler/TaskChain.cs.meta new file mode 100644 index 000000000..e50b9f19b --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Unit.cs b/DepthFirstScheduler/Unit.cs new file mode 100644 index 000000000..c3a041d1e --- /dev/null +++ b/DepthFirstScheduler/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/DepthFirstScheduler/Unit.cs.meta b/DepthFirstScheduler/Unit.cs.meta new file mode 100644 index 000000000..24f0ea548 --- /dev/null +++ b/DepthFirstScheduler/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: