Fibers - Askowl Cooperative Multitasking
It turns out that precompiling fibers is not enough. A fiber will, but it's very nature, survive long after the code that called it has moved on. In OO a class instance is the closure. Fortunately, C# 7 has syntax that can make it concise with a minimum of visible scaffolding. If you use the the following pattern you can use fibers any time and anywhere without worrying about the typical multi-tasking concerns. I create an inner class to provide a closure for the fiber to run in. It inherits from the Fiber.Closure generic. The first generic argument is the new class so that underlying code can create it or reuse it from a cache. The second generic argument can be a struct, class or tuple. The latter is the most convenient. Like a struct it does not use the heap or garbage collector, but it doesn't need to be defined separately. A tuple is created with a list of items in brackets. Add and fill in the one abstract method And top up the already created fiber instance with the work you want to perform. The parameters provided when the fiber starts are available in Scope. Fiber step actions can be lambdas or references to methods. In both cases it takes a single parameter fiber and does not have a return value. And that is it. For a simple fiber this only takes 4 lines Calling a fiber is as simple as calling the static Go routine. Note the double brackets. The inner one builds a tuple struct to send. For code clarity you can add the tuple component names A fiber closure exposes the the OnComplete emitter and the fiber that was run. For WaitFor, neither is needed as there is a closure override. If we need the results later we will need to take a copy of the scope. Because a tuple is a struct, the assignment is a copy, not a reference. I'll finish up with a real-world example. It is production code taken from the CustomAsset package for calling external asynchronous services. The above is just placeholders for code in the service class. CallService below is the only public method needed to call the service. The precompiled fiber wrapper is of type DelayedCache so that is can return itself to the recycling queue after completing it's task. We create the precompiled fiber in the Activities called from the constructor. Since this class is cached, there will only be as many copies as the maximum number of concurrent calls. The single static method Go gets a precompiled fiber from the cache, prepares it and runs it in one fluid movement. It returns a reference to the CallServiceFiber instance we are using, providing access to the OnComplete emitter and the scope. In case you are interested, MethodCache calls a method overload based in the data type of the single parameter. It returns null for synchronous and an emitter for asynchronous functions. WaitFor continues immediately if the emitter is null. Download Unity3D Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Join my Patreon page at https://www.patreon.com/paulmarrington.
Why Use Lambdas for Fibers?
Parameters to all the fiber steps are set in stone when the fiber is built. This is most relevant to precompiled fibers. I am using immediate fibers here for simplicity. This example works because the emitter is set before either fiber is built. On the other hand this one fails because each fiber is using a different emitter. The solution is to use a lambda. The emitter reference is used when needed, not when the fiber is compiled. Almost all Fibers built-in commands have a lambda version in addition to the direct parameter approach. If in doubt, use the lambda. Download Unity3D Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Join my Patreon page at https://www.patreon.com/paulmarrington.
Interrupting Running Fibers
Copyright 2019 (C) email@example.com http://www.askowl.net/unity-packages There are times when you will need to prematurely terminate a running fiber. It could be a service that is taking too long, or it could be your program has moved on and not longer needs a result a fiber has been waiting for. The first option is a simple timeout. Once the time has expired the calling fiber is terminated. Often we need to know when a fiber has terminated rather than completed it's allotted task. For this we need two fibers. The main fiber will do it's thing then cause the timeout fiber to exit once it is no longer needed. The timeout monitor fiber will force the main fiber to exit early. If it worked the second counter update will not occur Another way to do the same thing is using an emitter. Now that I have shown three ways to abort a running fiber, I can tell you that the second and third are not necessary. From release 2.0.2 and above, Fibers includes an Aborted flag, so we can use `Timeout`, `Exit` or `CancelOn` and it will provide us with the information we need. Download Unity3D Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Join my Patreon page at https://www.patreon.com/paulmarrington.
Copyright 2019 (C) firstname.lastname@example.org http://www.askowl.net/unity-packages Fibers are asynchronous. While Do methods can have access to the surrounding class this causes tight coupling and limited scope. Take service custom assets for example. A service will provide multiple entry points, each wrapped in a method. Most services are asynchronous, so will wait on response with a fiber. Each service method returns different types of information. All service method responses can be assigned in the constructor. The anonymous classes created to generate method references all happen here and hence only once for the containing class instance. If the class is cached, this will only happen once per app execution. This is a simulated service method where a response occurs after 1/10th of a second. Here is a simple context with only one value. Typically it will be data from both the service communications as well as the service if all is well. This is the method called in the response. It is responsible for all the hard lifting needed with the information provided by the service. Download Unity3D Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Join my Patreon page at https://www.patreon.com/paulmarrington.
Emitters in Fibers
Copyright 2019 (C) email@example.com http://www.askowl.net/unity-packages The Emitter class has been moved from Able to Fibers and given a bit of an overhaul. As the primary inter-fiber communications method it holds more prominence in release 2.0 of fibers. On the face of it an emitter has one function. Accept listeners and call them when fire is called. A listener function is called when the emitter is fired. It is given a reference to the emitter in case it is called out of context. If it returns false it is removed from the list and never called again. An emitter is cached and disposable. In practice a `using` statement is unlikely in an asynchronous world. There is a problem with the example above. Emitters need to be efficient as they may be called hundreds of times a seconds. The problem here is `Listen(emitterAction)`. Whenever a function is turned into a delegate an anonymous class is created, exercising the garbage collector. Any form of delegate acts the same - lambdas, inner functions, instance or static members, anything. Unless you are absolutely certain that Listen will not be called often it is best to cache the delegate. The anonymous class is only instantiated once when the class is first loaded, then reused as needed. `emitterFiredInstance` is static because otherwise we would have to create the listener in the constructor. I often do it that way. The anonymous class is only instantiated once when the class is first loaded, then reused as needed. That works if the data is class-centric, but what if it needs to be specific to the method? Fortunately an emitter can hold context. By making this context disposable, it is possible to cache and reuse it rather than relying on garbage collection. WHen an emitter is disposed of, it's context is also - returning the data object to the cache for reuse. There are many situations where an emitter is going to be used once and then discarded. The only catch is that data has been returned to the recycle bin. This is not a problem if you use the context data in the listener. Download Unity3D Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Join my Patreon page at https://www.patreon.com/paulmarrington.
After some research with the decompiler I worked out that all function references are created equal, no matter if it is a lambda, inner function or class method. An anonymous class is generated and instantiated whenever a reference is created. The best way to reduce the load on garbage collection is to precompile the fibers such that the classes are only instantiated once. Start still have value as we don't need to precompile for fibers that are only used once or monitors that remain in an infinite loop. Precompiled fibers are created with Instance instead of start If we want to change a variable as a parameter we need to wrap it in a function so that the current value is picked up Here is a test for the fixed 300ms precompiled fiber and they are first run when Go is called explicitly or implicitly with WaitFor or AsCoroutine. We can mix fibers with coroutines where it makes sense This next example should two advantages of precompiled One that we can set variables that will be used in the next fiber run And two that we can reuse fibers without involving the garbage collector - assuming that we do not need AsCoroutine in production code One more important use for precompiled fibers is reusable code similar to functions in sequential programming THe second fiber will wait until the first is done. In real code we may want to wait for one action to complete before starting another. Download Unity3D Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Join my Patreon page at https://www.patreon.com/paulmarrington.
An Introduction to Fibers
I have always liked cooperative multitasking - and the control it gives me. Unity has chosen coroutines as the preferred cooperative multitasking model. Unfortunately, coroutines exercise the garbage collector. Fibers is not a plug-in replacement as it uses a different cooperative multitasking model. You define a method or function for each step, then string the steps together into a Fibers command. Here, let me show you. Introduction 1. This example is using inner functions for each step 2. First, we define any variables needed to communicate between steps 3. Next, create descriptively named inner functions for each step 4. Here is a very simple fiber where step1 will execute on the next frame and step2 on the frame after 5. If you were to run this the lines would display 0, 1 and 11 6. We can provide the same functionality in the context of a class Builtins 1. Fibers can be run from and synchronised to coroutines 2. Begin/Again is a repetitive loop. Use break in an action to leave 3. The above example is the same as Begin/Repeat 4. Begin/End is a block that does not repeat. Use Break to leave early. 5. To abort a fiber as you would for an error, call Exit() 6. Idle/Restart can build up a fiber from parts 7. or be used to synchronise fibers 8. Separate actions in times by frame count 9. and specify which update function to use 10. Give any asynchronous task an emitter 11. Run Unity-type coroutines 12. with or without a frame-count between steps 13. last but definitely not least, delay for a specific time period Download on the Unity Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Please join my Patreon page at https://www.patreon.com/paulmarrington.
Fibers in Pools
This video contains a real-world example of using Fibers to solve a problem in comparison to Coroutines. It includes code cribbed from the Askowl Pools package. Every pooled game object has a monitor MonoBehaviour attached. When the game object is disabled, it returns to a pool for reuse. The obvious solution does not work. Unity does not allow the parent of a game object to change while it is in the process of being enabled or disabled. The classic Unity solution is to set the parent in Update(). We are pooling because there are potentially a lot of objects in use at once. Think of raindrops, asteroids or sparks from a fire. The task scheduler that calls Update() could have thousands of extra functions to call each frame. Even if there are not enough game objects to cause a performance problem on a mobile device, the extra work will cause additional battery drain. Here is the solution to the same problem, but using coroutines. There are times where using Update() is not convenient or suitable. This solution would have a similar performance since the setParentOnReturn() state machine is called every frame by the task scheduler. At last, we come to the Fibers version. We now add an emitter that fires whenever an item is attached to an empty stack. Since Fibers do not need to start from a MonoBehaviour, we can start them from the static constructor and dispense with the coroutineRunning boolean. The Fibers command is an infinite loop that cycles only when there are game objects to pool. The WaitFor command will continue once the emitter fires in OnDisable. Download Unity3D Store for free at https://assetstore.unity.com/packages/slug/133094. Read the full documentation at http://www.askowl.net/unity-fibers. Join my Patreon page at https://www.patreon.com/paulmarrington.