IEnumerator MainThreadLoopWithNaiveSynchronization() { var bounds = new Bounds(_BoundCenter, _BoundSize); var syncRunner = new SyncRunner(); while (_breakIt == false) { _time = Time.time / 10; //Since we are using the SyncRunner, we don't need to yield the execution //as the SyncRunner is meant to stall the thread where it starts from. //The main thread will be stuck until the multiParallelTask has been //executed. A MultiParallelTaskCollection relies on its own //internal threads to run, so although the Main thread is stuck //the operation will complete _multiParallelTasks.ThreadSafeRunOnSchedule(syncRunner); //then it resumes here, in the main thread, copying the result to the particleDataBuffer. //remember, multiParalleTasks is not executing anymore until the next frame! //so the array is safe to use _particleDataBuffer.SetData(_gpuparticleDataArr); //render the particles. I use DrawMeshInstancedIndirect but //there aren't any compute shaders running. This is so cool! Graphics.DrawMeshInstancedIndirect(_pointMesh, 0, _material, bounds, _GPUInstancingArgsBuffer); //continue the cycle on the next frame yield return(null); } //the application is shutting down. This is not that necessary in a //standalone client, but necessary to stop the thread when the //application is stopped in the Editor to stop all the threads. _multiParallelTasks.ClearAndKill(); TaskRunner.Instance.StopAndCleanupAllDefaultSchedulerTasks(); }
IEnumerator UpdateIT() { var waitForSecondsEnumerator = new WaitForSecondsEnumerator(0.1f); var syncRunner = new SyncRunner <Allocation0Enumerator>(); var syncRunner2 = new SyncRunner <WaitForSecondsEnumerator>(); var task = TaskRunner.Instance.AllocateNewTaskRoutine(syncRunner2).SetEnumeratorRef(ref waitForSecondsEnumerator); var task2 = TaskRunner.Instance.AllocateNewTaskRoutine(syncRunner); var serialtask = new SerialTaskCollection <Allocation0Enumerator>(); int counter = 0; while (true) { yield return(task.Start()); //yield return new Allocation0Enumerator(counter++); //nay this allocates yield return(task2.SetEnumerator(new Allocation0Enumerator(counter++)).Start()); //yay, this doesn't allocate! yield return(serialtask.Add(new Allocation0Enumerator(counter))); //yay, this doesn't allocate! serialtask.Clear(); yield return(null); } }
public Program2() { runner = new SyncRunner <ExtraLeanSveltoTask <UltraSpecializedEnumerator> >(-1); runner2 = new SyncRunner <ExtraLeanSveltoTask <UItraSpecializedEnumeratorClass> >(-1); runner3 = new UpdateMonoRunner <ExtraLeanSveltoTask <UltraSpecializedEnumerator> >("test"); runner4 = new UpdateMonoRunner <LeanSveltoTask <UltraSpecializedEnumerator> >("test2", 100000); runner5 = new UpdateMonoRunner <LeanSveltoTask <IEnumerator <TaskContract> > >("test3", 100000); }
//physicScheduler -> updateScheduler -> coroutineScheduler -> lateScheduler static StandardSchedulers() { syncScheduler = new SyncRunner(false); multiThreadScheduler = new MultiThreadRunner("MultiThreadRunner", true); #if UNITY_5 || UNITY_5_3_OR_NEWER coroutineScheduler = new CoroutineMonoRunner("StandardCoroutineRunner"); physicScheduler = new PhysicMonoRunner("StandardPhysicRunner"); lateScheduler = new LateMonoRunner("StandardLateRunner"); updateScheduler = new UpdateMonoRunner("StandardMonoRunner"); #endif }
IEnumerator UpdateIT() { var waitForSecondsEnumerator = new WaitForSecondsEnumerator(0.1f); var syncRunner = new SyncRunner(); while (true) { yield return(waitForSecondsEnumerator.RunOnSchedule(syncRunner)); yield return(null); } }
IEnumerator MainThreadOperations(ParticleCounter particleCounter) { var bounds = new Bounds(_BoundCenter, _BoundSize); var syncRunner = new SyncRunner(); //these will help with synchronization between threads WaitForSignalEnumerator _waitForSignal = new WaitForSignalEnumerator(() => _breakIt); WaitForSignalEnumerator _otherwaitForSignal = new WaitForSignalEnumerator(); //Start the operations on other threads OperationsRunningOnOtherThreads(_waitForSignal, _otherwaitForSignal) .ThreadSafeRunOnSchedule(StandardSchedulers.multiThreadScheduler); //start the mainloop while (true) { _time = Time.time / 10; //wait until the other thread tell us that the data is ready to be used. //Note that I am stalling the main thread here! This is entirely up to you //if you don't want to stall it, as you can see with the other use cases _otherwaitForSignal.RunOnSchedule(syncRunner); #if BENCHMARK if (PerformanceCheker.PerformanceProfiler.showingFPSValue > 30.0f) { if (particleCounter.particlesLimit >= 16) { particleCounter.particlesLimit -= 16; } PerformanceCheker.PerformanceProfiler.particlesCount = particleCounter.particlesTransformed; } particleCounter.particlesTransformed = 0; #endif _particleDataBuffer.SetData(_gpuparticleDataArr); //render the particles. I use DrawMeshInstancedIndirect but //there aren't any compute shaders running. This is so cool! Graphics.DrawMeshInstancedIndirect(_pointMesh, 0, _material, bounds, _GPUInstancingArgsBuffer); //tell to the other thread that now it can perform the operations //for the next frame. _waitForSignal.Signal(); //continue the cycle on the next frame yield return(null); } }
//yes this is running from another thread IEnumerator MainLoopOnOtherThread() { var syncRunner = new SyncRunner(); var then = DateTime.Now; //Let's start the MainThread Loop RenderingOnCoroutineRunner().ThreadSafeRun(); var CopyBufferOnUpdateRunner = new SimpleEnumerator(this); //let's avoid useless allocations //let's avoid allocations inside the loop Func <bool> onExternalBreak = OnExternalBreak; while (_breakIt == false) { _time = (float)(DateTime.Now - then).TotalSeconds; //Since we are using the SyncRunner, we don't need to yield the execution //as the SyncRunner is meant to stall the thread where it starts from. //The main thread will be stuck until the multiParallelTask has been //executed. A MultiParallelTaskCollection relies on its own //internal threads to run, so although the Main thread is stuck //the operation will complete _multiParallelTasks.ThreadSafeRunOnSchedule(syncRunner); //then it resumes here, however the just computed particles //cannot be passed to the compute buffer now, //as the Unity methods are not thread safe //so I have to run a simple enumerator on the main thread var continuator = CopyBufferOnUpdateRunner.ThreadSafeRunOnSchedule(StandardSchedulers.updateScheduler); //and I will wait it to complete, still exploting the continuation wrapper. //continuators can break on extra conditions too; continuator.BreakOnCondition(onExternalBreak); //We need to wait the MainThread to finish its operation before to run the //next iteration. So let's stall using the syncrunner; continuator.RunOnSchedule(syncRunner); } //the application is shutting down. This is not that necessary in a //standalone client, but necessary to stop the thread when the //application is stopped in the Editor to stop all the threads. _multiParallelTasks.ClearAndKill(); TaskRunner.Instance.StopAndCleanupAllDefaultSchedulerTasks(); yield break; }
IEnumerator WaitForNodesAdded() { //Engines are usually designed to be able to cope with dynamic adding and removing of entities, //but in this case I needed to know when the entities are ready to be processed. This wouldn't be //strictly necessary if I coded the engine in a different way, but I decided to keep it simpler and more readable. //That's why the engine starts immediately a task that waits for the nodes to be added(it assumes that all the //entities are created on the same frame).This demo aims to be allocation free during the main execution, that's //why all the tasks are prepared before hand. In this step, we prepare just one task that runs the main operations //that must be executed on the entities. int count = 0; #if FOURTH_TIER_EXAMPLE BoidNode[] _nodes; #endif do { #if FIRST_TIER_EXAMPLE || SECOND_TIER_EXAMPLE || THIRD_TIER_EXAMPLE count = _nodes.Count; #endif #if FOURTH_TIER_EXAMPLE _nodes = _structNodes.GetList(out count); #endif yield return(null); } while (count == 0); #if TURBO_EXAMPLE int numberOfThreads = (int)Mathf.Min(NUM_OF_THREADS, count); var countn = count / numberOfThreads; _multiParallelTask = new MultiThreadedParallelTaskCollection(numberOfThreads, false); _syncRunner = new SyncRunner(true); for (int i = 0; i < numberOfThreads; i++) { _multiParallelTask.Add(new BoidEnumerator(_nodes, countn * i, countn)); } #elif FIRST_TIER_EXAMPLE || SECOND_TIER_EXAMPLE || THIRD_TIER_EXAMPLE || FOURTH_TIER_EXAMPLE _boidEnumerator = new BoidEnumerator(_nodes, 0, count); #endif _testEnumerator = new TestEnumerator(_printNode); Update().ThreadSafeRunOnSchedule(StandardSchedulers.updateScheduler); }
public void TestPooledTaskMemoryUsage() { var syncRunner = new SyncRunner <SlowTaskStruct>(2000); var task = TaskRunner.Instance.AllocateNewTaskRoutine(syncRunner); task.SetEnumerator(new SlowTaskStruct(1)); task.Start(); Assert.That(() => { task.SetEnumerator(new SlowTaskStruct(1)); }, Is.Not.AllocatingGCMemory()); Assert.That(() => { task.Start(); }, Is.Not.AllocatingGCMemory()); Assert.That(() => { task.SetEnumerator(new SlowTaskStruct(1)); }, Is.Not.AllocatingGCMemory()); Assert.That(() => { task.Start(); }, Is.Not.AllocatingGCMemory()); }
public const uint NUM_OF_THREADS = 8; //must be divisible by 4 for this exercise as I am not handling reminders #endif IEnumerator Update() { SyncRunner _syncRunner = new SyncRunner(); while (true) { #if TURBO_EXAMPLE //note: yielding here is meaningless as it runs on a sync scheduler //which will stop the execution of the thread until the operation is //done. I just didn't want to confuse you. //Note that while the task runs inside the Sync Runner, the //MultiThreadedParallelTaskCollection run on several threads //therefore main threads waits until the other threads are finished //I know it sounds complex, but look, is very simple right? yield return(_multiParallelTask.ThreadSafeRunOnSchedule(_syncRunner)); //try this if you want to see what happens if you don't stall the mainthread //don't be shocked, while the demo will run at thousand of frame per second //the operation will call _testEnumerator still at the same frequency //it would have happened before. However this would be the way to use it //in a normal scenarion, as you don't want the main thread to be stalled. //yield return _multiParallelTask; #else //yield on the sync scheduler as I want hold the main thread on purpose. //In real life you wouldn't use sync scheduler as you don't want to //stall the main thread. //note: RunOnSchedule (and ThreadSafeRunOnSchedule) allows to continue //the operation on another runner without stalling the current one. //yielding it allows the current operation to wait for the result. yield return(_boidEnumerator.RunOnScheduler(_syncRunner)); #endif //run the cached enumerator on the next coroutine phase, yield until it's done. //The thread will spin until is done. Yielding an enumerator on the same //runner actually executes it immediatly. yield return(_testEnumerator); //since _testEnumerator runs synchronously, we need to yield for a frame //at least once, otherwise this enumerator becomes totally synchronously //transforming into an infinite loop. //I understand this can look all obscure if you don't know how yield works :( yield return(null); } }
IEnumerator OperationsRunningOnOtherThreads(WaitForSignalEnumerator waitForSignal, WaitForSignalEnumerator otherWaitForSignal) { //a SyncRunner stop the execution of the thread until the task is not completed //the parameter true means that the runner will sleep in between yields var syncRunner = new SyncRunner(); while (_breakIt == false) { //execute the tasks. The MultiParallelTask is a special collection //that uses N threads on its own to execute the tasks. This thread //doesn't need to do anything else meanwhile and will yield until //is done. That's why the syncrunner can sleep between yields, so //that this thread won't take much CPU just to wait the parallel //tasks to finish _multiParallelTasks.ThreadSafeRunOnSchedule(syncRunner); #if BENCHMARK //do it x4 _multiParallelTasks.ThreadSafeRunOnSchedule(syncRunner); _multiParallelTasks.ThreadSafeRunOnSchedule(syncRunner); _multiParallelTasks.ThreadSafeRunOnSchedule(syncRunner); #endif //the 1 Million particles operation are done, let's signal that the //result can now be used otherWaitForSignal.Signal(); //wait until the application is over or the main thread will tell //us that now we can perform again the particles operation. This //is an explicit while instead of a yield, just because if the _breakIt //condition, which is needed only because if this application runs //in the editor, the threads spawned will not stop until the Editor is //shut down. waitForSignal.RunOnSchedule(syncRunner); } //the application is shutting down. This is not that necessary in a //standalone client, but necessary to stop the thread when the //application is stopped in the Editor to stop all the threads. _multiParallelTasks.ClearAndKill(); TaskRunner.Instance.StopAndCleanupAllDefaultSchedulerTasks(); yield break; }