Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hamoid committed Mar 6, 2024
1 parent 40cd970 commit 57720dd
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 29 deletions.
2 changes: 1 addition & 1 deletion docs/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ source "https://rubygems.org"
#
# This will help ensure the proper Jekyll version is running.

gem "jekyll", "~> 4.3.2"
gem "jekyll", "~> 4.3.3"
gem "json"
gem "just-the-docs"

Expand Down
25 changes: 16 additions & 9 deletions docs/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ GEM
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
colorator (1.1.0)
concurrent-ruby (1.2.2)
concurrent-ruby (1.2.3)
em-websocket (0.5.3)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0)
eventmachine (1.2.7)
ffi (1.15.5)
ffi (1.16.3)
forwardable-extended (2.6.0)
google-protobuf (3.23.4-x86_64-linux)
http_parser.rb (0.8.0)
i18n (1.14.1)
i18n (1.14.3)
concurrent-ruby (~> 1.0)
jekyll (4.3.2)
racc (~> 1.7)
jekyll (4.3.3)
addressable (~> 2.4)
colorator (~> 1.0)
em-websocket (~> 0.5)
Expand All @@ -33,6 +34,8 @@ GEM
webrick (~> 1.7)
jekyll-feed (0.17.0)
jekyll (>= 3.7, < 5.0)
jekyll-include-cache (0.2.1)
jekyll (>= 3.7, < 5.0)
jekyll-remote-theme (0.4.3)
addressable (~> 2.0)
jekyll (>= 3.5, < 5.0)
Expand All @@ -46,6 +49,7 @@ GEM
jekyll (>= 3.7, < 5.0)
jekyll-watch (2.2.1)
listen (~> 3.0)
json (2.7.1)
just-the-docs (0.5.4)
jekyll (>= 3.8.5)
jekyll-seo-tag (>= 2.0)
Expand All @@ -55,36 +59,39 @@ GEM
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liquid (4.0.4)
listen (3.8.0)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.4.0)
pathutil (0.16.2)
forwardable-extended (~> 2.6)
public_suffix (5.0.3)
racc (1.7.3)
rake (13.0.6)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (>= 1.0)
ffi (~> 1.0)
rexml (3.2.6)
rouge (4.1.2)
rouge (4.2.0)
rubyzip (2.3.2)
safe_yaml (1.0.5)
sass-embedded (1.64.1-x86_64-linux-gnu)
google-protobuf (~> 3.23)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
unicode-display_width (2.4.2)
unicode-display_width (2.5.0)
webrick (1.8.1)

PLATFORMS
x86_64-linux

DEPENDENCIES
jekyll (~> 4.3.2)
jekyll (~> 4.3.3)
jekyll-feed (~> 0.12)
jekyll-include-cache
jekyll-remote-theme
jekyll-sitemap
json
just-the-docs
tzinfo (~> 1.2)
tzinfo-data
Expand Down
186 changes: 167 additions & 19 deletions docs/interaction/Events.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
layout: default
title: Events
parent: Interaction
last_modified_at: 2024.03.05 16:52:37 +0100
last_modified_at: 2024.03.06 14:54:12 +0100
nav_order: 90
has_children: false
---
Expand All @@ -25,44 +25,43 @@ event will receive it.

```kotlin
class Thing {
val timeEvent = Event<Boolean>()
val timeEvent = Event<Int>("time-event")

private var frame = 0

fun update() {
if (++frame % 60 == 0) {
timeEvent.trigger(Random.bool())
timeEvent.deliver()
timeEvent.trigger(frame / 60)
}
}
}
```

### Sending an event

Notice how events carry a payload, in this case `Boolean`. This is convenient
Notice how events carry a payload, in this case `Int`. This is convenient
because it allows us to transmit information together with the event.
Mouse and Keyboard events contain details about the mouse position or
the key pressed. In this program we are free to choose any type, so lets
just broadcasting a message containing a random boolean value.
just broadcasting a message containing the approximate time in seconds.

Passing a name in the event constructor is not necessary, but can be
useful for logging and debugging.

Another thing to observe is that `timeEvent` is a public variable. If it
was private we couldn't listen to it from outside
by calling `thing.timeEvent.listen { ... }`.

At some point in our program execution we need to call `.trigger()`
to queue an event. We can call it as many times as needed.

Finally, we call `.deliver()` to deliver the queued events to those
listening to them.

to broadcast the event. We can call it as many times as needed.

### Listening to an event

The following small program shows how to listen to an event emitted by a class.

First, let's create one instance of the class (`Thing` in this case).
First, let's create one instance of the class called `thing`.

Next, listen to an event this instance can emit (`timeEvent` here).
Next, listen to the event `thing` can emit (`timeEvent`).


```kotlin
Expand All @@ -73,16 +72,165 @@ fun main() = application {
thing.update()
}
thing.timeEvent.listen {
println("timeEvent triggered! It contains a: $it")
println("timeEvent triggered! $it")
}
}
}
```

We see a line appear every second:
```
timeEvent triggered! 1
timeEvent triggered! 2
timeEvent triggered! 3
...
```

## Events in coroutines and threads

By default our OPENRNDR programs run in a single thread, which happens
to be the "rendering thread". But what would happen if we sent
Events from different threads or coroutines? Lets find out.

The `Blob` class is a copy of `Thing` with three changes:

1. To be able to spawn coroutines, we pass a `Program` in the constructor.
2. We add a second event called `doneWaiting`. We use `Unit` as a type
when we don't want to pass any useful data.
3. When the class is constructed, we launch a coroutine to wait
for 3 seconds, then trigger the new `doneWaiting` event.

```kotlin
class Blob(program: Program) {
val timeEvent = Event<Int>("time-event")
val doneWaiting = Event<Unit>("done-waiting")

private var frame = 0

fun update() {
if (++frame % 60 == 0) {
timeEvent.trigger(frame / 60)
}
}

init {
program.launch {
delay(3000)
doneWaiting.trigger(Unit)
}
}
}
```

Now lets use our `Blob` class in a new program
that listens to its two events:

```kotlin
fun main() = application {
program {
val blob = Blob(this)
extend {
blob.update()
}
blob.timeEvent.listen {
println("timeEvent triggered! $it")
}
blob.doneWaiting.listen {
println("done waiting")
}
}
}
```

```
timeEvent triggered! 1
timeEvent triggered! 2
timeEvent triggered! 3
done waiting
timeEvent triggered! 4
...
```

Seems to work! right? There's one issue though:
the `doneWaiting.listen` function does not run on the
rendering thread. This would be the case for events
triggered due to external causes (loading
a file from the Internet and waiting for its completion
or an event coming from a hardware input device).

This will become apparent when we fail to draw on our window:

```
blob.doneWaiting.listen {
drawer.clear(ColorRGBa.WHITE) // <-- will not work
println("done waiting")
}
```

The solution is simple though: when constructing the `Event`, we
set the `postpone` argument to true:

```kotlin
val doneWaiting = Event<Unit>("done-waiting", postpone = true)
```

Now triggering the event does no longer send it immediately, but queues it.
The second part of the solution is to actually deliver the queued events
by calling `deliver()`.

```kotlin
doneWaiting.deliver()
```

It is essential to call `deliver()` from the rendering thread.
Since `extend { }` executes in the rendering thread, and
`extend` calls `update()`, we can let `update()` call `deliver()`
and everything will work nicely.

This is the full program:

```kotlin
class Blob(program: Program) {
val timeEvent = Event<Int>("time-event")
val doneWaiting = Event<Unit>("done-waiting", postpone = true)

private var frame = 0

fun update() {
if (++frame % 60 == 0) {
timeEvent.trigger(frame / 60)
}
// Deliver any queued events
doneWaiting.deliver()
}

init {
program.launch {
delay(3000)
// Queue event outside the rendering thread
doneWaiting.trigger(Unit)
}
}
}
```

There are multiple approaches to including events in our class. For example, a
class could have separate `loadStart` and `loadComplete` events, or instead,
have just one `loadEvent` and have the payload describe if it was
a `start` or a `complete` event. Having two separate events arguably produces
more readable code.
```kotlin
fun main() = application {
program {
val blob = Blob(this)
extend {
blob.update()
}
blob.timeEvent.listen {
println("timeEvent triggered! $it")
}
blob.doneWaiting.listen {
// White flash when this event is received
drawer.clear(ColorRGBa.WHITE)
println("done waiting")
}
}
}
```

[edit on GitHub](https://github.com/openrndr/openrndr-guide/blob/main/src/main/kotlin/docs/40_Interaction/C90_Events.kt){: .btn .btn-github }

0 comments on commit 57720dd

Please sign in to comment.