diff --git a/manual/scripting/coroutines/coroutines-advanced.md b/manual/scripting/coroutines/coroutines-advanced.md new file mode 100644 index 0000000..03e0bc4 --- /dev/null +++ b/manual/scripting/coroutines/coroutines-advanced.md @@ -0,0 +1,63 @@ +# Coroutines Advanced Scenarios + +## Context + +Unfortunately, **coroutines do not have a system to pass context** yet. Contemporary solution is to inject context during sequence declaration. Example: + +# [C#](#tab/code-csharp) +```cs +public class Example +{ + //TODO +} +``` +# [C++](#tab/code-cpp) +```cpp +class Example +{ + //TODO +} +``` +*** + +## Branching + +Flax coroutines use linear sequence of instructions. This means the flow may not be controlled such way that it branches. + +If branching is required, separate sequences must be declared: + +# [C#](#tab/code-csharp) +```cs +public class Example +{ + //TODO +} +``` +# [C++](#tab/code-cpp) +```cpp +class Example +{ + //TODO +} +``` +*** + +## Custom Order + +In advanced scenarios it may be important for the coroutines to follow specific order of execution. To do that, you can manually dispatch an executor. Additionaly, such apporach will may skip unnecessary suspension points. + +# [C#](#tab/code-csharp) +```cs +public class Example +{ + //TODO +} +``` +# [C++](#tab/code-cpp) +```cpp +class Example +{ + //TODO +} +``` +*** diff --git a/manual/scripting/coroutines/coroutines-basics.md b/manual/scripting/coroutines/coroutines-basics.md new file mode 100644 index 0000000..99d3b4a --- /dev/null +++ b/manual/scripting/coroutines/coroutines-basics.md @@ -0,0 +1,212 @@ +# Basics of Coroutines + +Basic coroutine workflow requires 3 steps: + +1. Creating a sequence. +2. Passing the sequence to an executor. +3. (Optional) Manipulating execution with a handle. + +## 1. Creating Sequence + +To create a sequence, a builder pattern is used to define steps. The order of executing those steps is the same as the order of adding them. + +# [C#](#tab/code-csharp) +```cs +CoroutineSequence sequence = new CoroutineSequence() + .ThenRun(() => { Debug.Log("Greeting in 3 seconds!"); }) + .ThenWait(3.0f) + .ThenRun(() => { Debug.Log("Hello World!"); }); +``` +# [C++](#tab/code-cpp) +```cpp +CoroutineSequence* sequence = New() + ->ThenRun([](){ DebugLog::Log(TEXT("Greeting in 3 seconds!")); }) + ->ThenWait(3.0f) + ->ThenRun([](){ DebugLog::Log(TEXT("Hello World!")); }); +``` +*** + +Once you declared the coroutine sequence, you must pass it to what's called executor. + +## 2. Executing Sequence + +Second stage is to pass the sequence to the *executor*. One executor can process multiple executions at once. To distinguish them, executor returns *execution handle*, which is explored in 3rd stage. Let's see the mechanism in action: + +> [!Note] +> In the example, we use Script class as a proxy for shared per-scene executor. If you want to use see how to interact with executors directly, check [advanced scenarios](coroutines-advanced.md). + +# [C#](#tab/code-csharp) +```cs +CoroutineHandle _handle; + +public override OnEnabled() +{ + CoroutineSequence sequence = ...; + _handle = ExecuteOnce(sequence); +} +``` +# [C++](#tab/code-cpp) +```cpp +ScriptingObjectReference _handle; + +void OnEnabled() override +{ + auto sequence = ...; + _handle = ExecuteOnce(sequence); +} +``` +*** + +Sequences can be executed in three run modes: + +| Run Mode | Number of Runs | Auto-Cancel | Manual Cancel | Method Name | +|----------|----------------|-------------|---------------|-------------| +| *Single Run* | 1 | Yes | Yes | `ExecuteOnce` | +| *Repeating Run* | n | Yes | Yes | `ExecuteRepeating` | +| *Looped Run* | infinite | **No** | Yes | `ExecuteLooped` | + +> [!Note] +> Execution starts right after passing a sequence to the executor. If your first step is code, it will be run immediately. + +## 3. Manipulating Execution + +Flax coroutines execution can be manually altered from outside by using the *handles*. To control the flow, there are three available methods: + +- **Pause** to request the executor to halt execution until resuming is requested. +- **Resume** to request the executor to continue potentially paused execution. +- **Cancel** to terminate the execution completelly. + +Each one of them returns a flag indicating if the operation was successful. + +Let's see an example, how to stop coroutine at the end of our script's lifetime: + +# [C#](#tab/code-csharp) +```cs +// ... +public override OnDisabled() +{ + _handle.Cancel(); +} +``` +# [C++](#tab/code-cpp) +```cpp +// ... +void OnDisabled() override +{ + _handle.Cancel(); +} +``` +*** + +## Full Example + +### Example - Ticking Bomb + +*Ticking Bomb* presents the basic use of coroutines. We have a sequence of bomb going 3 times through a 5-seconds cycle turning its diode on for one second. + +> [!Note] +> In this example, we count number of sequence runs manually, so the execution is stopped manually too. + +# [C#](#tab/code-csharp) +```cs +public class Bomb : Script +{ + [Serialize] public int SequenceCount = 3; + [Serialize] public Light BlinkSource; + [Serialize] public AudioSource BeepSource; + + private CoroutineSequence _tickingSequence; + private CoroutineHandle _tickingHandle; + + public override OnStart() + { + _tickingSequence = new CoroutineSequence() + .ThenRun(() => { + BlinkSource.IsActive = true; + BeepSource.Play(); + }) + .ThenWait(1.0f) + .ThenRun(() => { + BlinkSource.IsActive = false; + }) + .ThenWait(4.0f) + .ThenRun(() => { + SequenceCount--; + if (SequenceCount <= 0) + Explode(); + }); + } + + public override OnEnabled() + { + _tickingHandle = ExecuteLooping(_tickingSequence); + } + + public override OnDisabled() + { + _tickingHandle.Cancel(); + _tickingHandle = null; + } + + private void Explode() + { + Enabled = false; + // Use your imagination here. + } +} +``` +# [C++](#tab/code-cpp) +```cpp +API_CLASS() +class Bomb : public Script +{ + DECLARE_SCRIPTING_TYPE(Bomb); + AUTO_SERIALIZE(); + + API_FIELD() int32 SequenceCount = 3; + API_FIELD() ScriptingObjectReference BlinkSource; + API_FIELD() ScirptingObjectReference BeedSource; + +private: + ScriptingObjectReference _tickingSequence; + ScriptingObjectReference _tickingHandle; + + void Explode() + { + SetEnabled(false); + // Use your imagination here. + } + +public: + void OnStart() override + { + _tickingSequence = New() + ->ThenRun([this](){ + BlinkSource.SetIsActive(true); + BeepSource->Play(); + }) + ->ThenWait(1.0f) + ->ThenRun([this](){ + BlinkSource.SetIsActive(false); + }) + ->ThenWait(4.0f) + ->ThenRun([this](){ + --SequenceCount; + if (SequenceCount <= 0>) + Explode(); + }); + } + + void OnEnabled() override + { + _tickingHandle = ExecuteLooping(_tickingSequence); + } + + void OnDisabled() override + { + _tickingHandle->Cancel(); + _tickingHandle = nullptr; + } +}; +``` +*** \ No newline at end of file diff --git a/manual/scripting/coroutines/coroutines-rationale.md b/manual/scripting/coroutines/coroutines-rationale.md new file mode 100644 index 0000000..1d91627 --- /dev/null +++ b/manual/scripting/coroutines/coroutines-rationale.md @@ -0,0 +1,18 @@ +# Rationale + +This sections contains additional information about coroutines which does not regard using the feature. + +## Implementation Decisions + +The following decisions have been made during implementation: + +* `Execution` has been implemented using a union to improve data locality. Waiting-for-seconds events are the most popular, therefor storing executions in continuous block of memory follows data-oriented principles. +* Coroutine execution methods have been split into `ExecuteOnce`, `ExecuteRepeating` and `ExecuteLooping` to + +## Future Works + +This list contains aspects that can be improved in the future: + +* Injecting context, for example using boxing: `::ScriptableObject` and `System.Object`. +* Checking for predicates with interval, for example `WaitUntil(predicate: ..., interval: 3.0f)` would test the condition each 3 seconds. +* Handling errors in runnables and predicated. diff --git a/manual/scripting/coroutines/coroutines.md b/manual/scripting/coroutines/coroutines.md new file mode 100644 index 0000000..8c530c0 --- /dev/null +++ b/manual/scripting/coroutines/coroutines.md @@ -0,0 +1,13 @@ +# Coroutines + +*How often do you fing yourself creating timers? Now make a sequence of them!* + +Coroutines are programs executed alongside your main program. They are used to execute **asynchronous single-threaded** actions. They provide a handy interface for algorithms that require being **suspended and resumed**. + +Coroutines can make your program be more maintainable and potentialy run faster. + +## In this section + +* [Basics of Coroutines](coroutines-basics.md) +* [Advanced Scenarions](coroutines-advanced.md) +* [Rationale and Future Work](coroutines-rationale.md) diff --git a/manual/scripting/index.md b/manual/scripting/index.md index e376cee..dde8706 100644 --- a/manual/scripting/index.md +++ b/manual/scripting/index.md @@ -55,6 +55,10 @@ To start visual scripting see the related documentation [here](visual/index.md). * [Scripts serialization](serialization/index.md) * [Empty Actor](empty-actor.md) * [Engine API](engine-api.md) +* [Coroutines](coroutines/coroutines.md) + * [Basics](coroutines/coroutines-basics.md) + * [Advanced Scenarios](coroutines/coroutines-advanced.md) + * [Rationale](coroutines/coroutines-rationale.md) * [Custom Editors](custom-editors/index.md) * [Custom script editor](tutorials/custom-editor.md) * [Attributes](custom-editors/attributes.md) diff --git a/manual/toc.md b/manual/toc.md index ef3d776..89fc457 100644 --- a/manual/toc.md +++ b/manual/toc.md @@ -171,6 +171,7 @@ ## [Create and use a script](scripting/new-script.md) ## [Script properties and fields](scripting/properties.md) ## [Script events](scripting/events.md) + ## [Accessing scene objects](scripting/scene-objects.md) ## [Creating and destroying objects](scripting/objects-lifetime.md) ## [Attributes](scripting/attributes.md) @@ -181,6 +182,10 @@ ## [Scripts serialization](scripting/serialization/index.md) ## [Empty Actor](scripting/empty-actor.md) ## [Engine API](scripting/engine-api.md) +## [Coroutines](scripting/coroutines/coroutines.md) +### [Basics of Coroutines](scripting/coroutines/coroutines-basics.md) +### [Advanced Scenarios](scripting/coroutines/coroutines-advanced.md) +### [Rationale](scripting/coroutines/coroutines-rationale.md) ## [Custom Editors](scripting/custom-editors/index.md) ### [Custom script editor](scripting/tutorials/custom-editor.md) ### [Attributes](scripting/custom-editors/attributes.md)