Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manual entries about upcoming coroutines #165

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions manual/scripting/coroutines/coroutines-advanced.md
Original file line number Diff line number Diff line change
@@ -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
}
```
***
212 changes: 212 additions & 0 deletions manual/scripting/coroutines/coroutines-basics.md
Original file line number Diff line number Diff line change
@@ -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<CoroutineSequence>()
->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<CoroutineHandle> _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<Light> BlinkSource;
API_FIELD() ScirptingObjectReference<AudioSource> BeedSource;

private:
ScriptingObjectReference<CoroutineSequence> _tickingSequence;
ScriptingObjectReference<CoroutineHandle> _tickingHandle;

void Explode()
{
SetEnabled(false);
// Use your imagination here.
}

public:
void OnStart() override
{
_tickingSequence = New<CoroutineSequence>()
->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;
}
};
```
***
18 changes: 18 additions & 0 deletions manual/scripting/coroutines/coroutines-rationale.md
Original file line number Diff line number Diff line change
@@ -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.
13 changes: 13 additions & 0 deletions manual/scripting/coroutines/coroutines.md
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 4 additions & 0 deletions manual/scripting/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions manual/toc.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Loading