Skip to content

Commit c66d460

Browse files
committed
new post
1 parent 91d104a commit c66d460

File tree

8 files changed

+292
-1
lines changed

8 files changed

+292
-1
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
+++
2+
title = "Extending Bevy with C: Communicating via FFI"
3+
date = 2024-11-14
4+
[extra]
5+
tags=["rust","bevy","gamedev"]
6+
hidden = true
7+
custom_summary = "We released the bevy_channel_trigger crate to simplify communication between Bevy and foreign libraries or languages."
8+
+++
9+
10+
In this short post we introduce the recently released [bevy_channel_trigger](https://crates.io/crates/bevy_channel_trigger) crate, why we need it to talk to foreign code and what it sets it apart from alternatives. If you just want to start using it, find it on [GitHub](https://github.com/rustunit/bevy_channel_trigger).
11+
12+
# Why?
13+
14+
<img src="schema.png" alt="schema" style="max-width: 50%" class="inline-img" />
15+
16+
Let's start with why we need to talk to C libraries or any other language in the first place. Bevy is written in Rust, Rust is great but not everything can be supported in Rust natively:
17+
18+
* maybe you want to interface with libraries that are closed source like Apple's native iOS libraries
19+
* maybe you want to talk to web APIs using [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) because your game runs on the web
20+
* maybe because time is finite and you don't want to [RiiR](https://transitiontech.ca/random/RIIR) (Rewrite it in Rust) all the way down
21+
22+
You can just call foreign functions of course right from your Bevy Systems but that is often not a good idea as we don't want to block our game logic. Often these APIs are async as well which means they will produce a result sometime later that we then want to receive back in our Bevy Systems. The schema on the right visualizes this.
23+
24+
This is where channels like [crossbeam](https://github.com/crossbeam-rs/crossbeam), [async-channel](https://docs.rs/async-channel/latest/async_channel/) or [flume](https://github.com/zesterer/flume) come in handy to communicate back into our Bevy game logic.
25+
26+
Lets look at an example.
27+
28+
# Show me an example
29+
30+
```rust,linenos
31+
#[derive(Event)]
32+
struct MyEvent(i32);
33+
34+
fn main() {
35+
use bevy_channel_trigger::ChannelTriggerApp;
36+
37+
let mut app = App::new();
38+
app.add_plugins(MinimalPlugins);
39+
40+
// create channel
41+
let sender = app.add_channel_trigger::<MyEvent>();
42+
43+
// use sender from anywhere:
44+
thread::spawn(move || {
45+
let mut counter = 1;
46+
loop {
47+
// send events back to bevy
48+
sender.send(MyEvent(counter));
49+
thread::sleep(Duration::from_secs(1));
50+
counter += 1;
51+
}
52+
});
53+
54+
// register an observer to receive the events sent via `sender`
55+
app.observe(on_event);
56+
57+
app.run();
58+
}
59+
```
60+
61+
The above example shows how we define an Event type `MyEvent` (line **2**)
62+
that we want to send as a reaction to a foreign function calling us
63+
(via callbacks or whatever the mechanism).
64+
65+
For the purposes of this example we simulate this by spinning off a
66+
separate thread (line **14**) and passing `sender` into it. Since this is
67+
a multiple-producers-single-consumer channel we can clone the sender
68+
as often as we want.
69+
70+
The thread will send this event once a second (line **18**).
71+
We want Bevy systems to react to these Events.
72+
Therefore we register a observer (line **25**) that will trigger every
73+
time this event gets send.
74+
75+
Let's look at the code of the observer:
76+
77+
```rust
78+
fn on_event(trigger: Trigger<MyEvent>) {
79+
let event = trigger.event();
80+
info!("trigger with: {}", event.0);
81+
}
82+
```
83+
84+
Thanks to `bevy_channel_trigger` we can react to these Events now just
85+
like we would with any other Observer. In this example we simply trace
86+
this to the console.
87+
88+
> You can find a more elaborate example in this
89+
[drop images into bevy on the web](https://github.com/rustunit/bevy_web_drop_image_as_sprite) demo.
90+
91+
# How does crossbeam, flume and others compare
92+
93+
You might be wondering why we chose to use **crossbeam** here under the hood.
94+
95+
First of all we abstracted that decision away and can easily exchange the
96+
underlying channel when we want to.
97+
98+
As a matter of fact the initial implementation was using **flume**, but
99+
ultimately we decided to move to crossbeam because it is by far the most
100+
actively maintained channel implementation in Rust and creates less wasm size than flume.
101+
102+
> Flume seems actually more lean in direct comparison to crossbeam but by
103+
using flume we effectively add another dependency because Bevy itself already brings crossbeam along.
104+
105+
Bevy also brings **async-channel** along surprisingly but just like flume
106+
it pales in comparison to crossbeam in regards to maintenance.
107+
108+
# What about bevy_crossbeam_event?
109+
110+
Last but not least let's look at the alternative: **bevy_crossbeam_channel**
111+
We actually used this one before but it dates to a time when the only messaging
112+
system Bevy had was `EventWriter`/`EventReader`. These matter as they are more performant in cases where you want to send massive amounts of events at the expense of slightly less ergonomic event handling.
113+
114+
**But** for our use cases events are used to cross the language barrier primarily and we want to have maximum ergonomics in how to write handlers for these using the new Observer API.
115+
116+
Migration of using `bevy_channel_trigger` in our crates like [bevy_web_popups](https://github.com/rustunit/bevy_web_popups) has already begun an will be finished with the migration to bevy 0.15!
117+
118+
---
119+
120+
You need support building your Bevy or Rust project? Our team of experts can support you! [Contact us.](@/contact.md)
197 KB
Loading
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
8+
<title>rustunit</title>
9+
<meta name="description" content="Rustunit offers software development consulting with a focus on rust, game-development and large scale distributed backend services.">
10+
11+
<link rel="stylesheet" href="https://rustunit.com/fonts.css">
12+
<link rel="stylesheet" href="https://rustunit.com/style.css">
13+
14+
<script src="https://cdn.usefathom.com/script.js" data-site="TSUIPVAW" defer></script>
15+
</head>
16+
17+
<body>
18+
<header>
19+
<a href="https://rustunit.com/">HOME</a>
20+
<a href="https://rustunit.com/#games">GAMES</a>
21+
<a href="https://rustunit.com/blog/">BLOG</a>
22+
<a href="https://rustunit.com/contact/">CONTACT</a>
23+
</header>
24+
25+
<div id="blogpage">
26+
<div class="date">2024-11-14</div>
27+
28+
<div class="hidden">hidden</div>
29+
30+
<h1 class="title">
31+
Extending Bevy with C: Communicating via FFI
32+
</h1>
33+
<div class="content">
34+
<p>In this short post we introduce the recently released <a href="https://crates.io/crates/bevy_channel_trigger">bevy_channel_trigger</a> crate, why we need it to talk to foreign code and what it sets it apart from alternatives. If you just want to start using it, find it on <a href="https://github.com/rustunit/bevy_channel_trigger">GitHub</a>.</p>
35+
<h1 id="why">Why?</h1>
36+
<img src="schema.png" alt="schema" style="max-width: 50%" class="inline-img" />
37+
<p>Let's start with why we need to talk to C libraries or any other language in the first place. Bevy is written in Rust, Rust is great but not everything can be supported in Rust natively:</p>
38+
<ul>
39+
<li>maybe you want to interface with libraries that are closed source like Apple's native iOS libraries</li>
40+
<li>maybe you want to talk to web APIs using <a href="https://github.com/rustwasm/wasm-bindgen">wasm-bindgen</a> because your game runs on the web</li>
41+
<li>maybe because time is finite and you don't want to <a href="https://transitiontech.ca/random/RIIR">RiiR</a> (Rewrite it in Rust) all the way down</li>
42+
</ul>
43+
<p>You can just call foreign functions of course right from your Bevy Systems but that is often not a good idea as we don't want to block our game logic. Often these APIs are async as well which means they will produce a result sometime later that we then want to receive back in our Bevy Systems. The schema on the right visualizes this.</p>
44+
<p>This is where channels like <a href="https://github.com/crossbeam-rs/crossbeam">crossbeam</a>, <a href="https://docs.rs/async-channel/latest/async_channel/">async-channel</a> or <a href="https://github.com/zesterer/flume">flume</a> come in handy to communicate back into our Bevy game logic.</p>
45+
<p>Lets look at an example.</p>
46+
<h1 id="show-me-an-example">Show me an example</h1>
47+
<pre data-linenos data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><table><tbody><tr><td>1</td><td><span style="color:#89ddff;">#[</span><span>derive</span><span style="color:#89ddff;">(</span><span>Event</span><span style="color:#89ddff;">)]
48+
</span></td></tr><tr><td>2</td><td><span style="font-style:italic;color:#c792ea;">struct </span><span>MyEvent</span><span style="color:#89ddff;">(</span><span style="font-style:italic;color:#c792ea;">i32</span><span style="color:#89ddff;">);
49+
</span></td></tr><tr><td>3</td><td><span>
50+
</span></td></tr><tr><td>4</td><td><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">main</span><span style="color:#89ddff;">() {
51+
</span></td></tr><tr><td>5</td><td><span> </span><span style="color:#c792ea;">use </span><span>bevy_channel_trigger</span><span style="color:#89ddff;">::</span><span>ChannelTriggerApp</span><span style="color:#89ddff;">;
52+
</span></td></tr><tr><td>6</td><td><span>
53+
</span></td></tr><tr><td>7</td><td><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> app </span><span style="color:#89ddff;">= </span><span>App</span><span style="color:#89ddff;">::</span><span>new</span><span style="color:#89ddff;">();
54+
</span></td></tr><tr><td>8</td><td><span> app</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">add_plugins</span><span style="color:#89ddff;">(</span><span>MinimalPlugins</span><span style="color:#89ddff;">);
55+
</span></td></tr><tr><td>9</td><td><span>
56+
</span></td></tr><tr><td>10</td><td><span> </span><span style="font-style:italic;color:#4a4a4a;">// create channel
57+
</span></td></tr><tr><td>11</td><td><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> sender </span><span style="color:#89ddff;">=</span><span> app</span><span style="color:#89ddff;">.</span><span>add_channel_trigger</span><span style="color:#89ddff;">::&lt;</span><span>MyEvent</span><span style="color:#89ddff;">&gt;();
58+
</span></td></tr><tr><td>12</td><td><span>
59+
</span></td></tr><tr><td>13</td><td><span> </span><span style="font-style:italic;color:#4a4a4a;">// use sender from anywhere:
60+
</span></td></tr><tr><td>14</td><td><span> thread</span><span style="color:#89ddff;">::</span><span>spawn</span><span style="color:#89ddff;">(</span><span style="color:#c792ea;">move </span><span style="color:#89ddff;">|| {
61+
</span></td></tr><tr><td>15</td><td><span> </span><span style="font-style:italic;color:#c792ea;">let </span><span style="color:#c792ea;">mut</span><span> counter </span><span style="color:#89ddff;">= </span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">;
62+
</span></td></tr><tr><td>16</td><td><span> </span><span style="font-style:italic;color:#c792ea;">loop </span><span style="color:#89ddff;">{
63+
</span></td></tr><tr><td>17</td><td><span> </span><span style="font-style:italic;color:#4a4a4a;">// send events back to bevy
64+
</span></td></tr><tr><td>18</td><td><span> sender</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">send</span><span style="color:#89ddff;">(</span><span>MyEvent</span><span style="color:#89ddff;">(</span><span>counter</span><span style="color:#89ddff;">));
65+
</span></td></tr><tr><td>19</td><td><span> thread</span><span style="color:#89ddff;">::</span><span>sleep</span><span style="color:#89ddff;">(</span><span>Duration</span><span style="color:#89ddff;">::</span><span>from_secs</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">));
66+
</span></td></tr><tr><td>20</td><td><span> counter </span><span style="color:#89ddff;">+= </span><span style="color:#f78c6c;">1</span><span style="color:#89ddff;">;
67+
</span></td></tr><tr><td>21</td><td><span> </span><span style="color:#89ddff;">}
68+
</span></td></tr><tr><td>22</td><td><span> </span><span style="color:#89ddff;">});
69+
</span></td></tr><tr><td>23</td><td><span>
70+
</span></td></tr><tr><td>24</td><td><span> </span><span style="font-style:italic;color:#4a4a4a;">// register an observer to receive the events sent via `sender`
71+
</span></td></tr><tr><td>25</td><td><span> app</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">observe</span><span style="color:#89ddff;">(</span><span>on_event</span><span style="color:#89ddff;">);
72+
</span></td></tr><tr><td>26</td><td><span>
73+
</span></td></tr><tr><td>27</td><td><span> app</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">run</span><span style="color:#89ddff;">();
74+
</span></td></tr><tr><td>28</td><td><span style="color:#89ddff;">}
75+
</span></td></tr></tbody></table></code></pre>
76+
<p>The above example shows how we define an Event type <code>MyEvent</code> (line <strong>2</strong>)
77+
that we want to send as a reaction to a foreign function calling us
78+
(via callbacks or whatever the mechanism).</p>
79+
<p>For the purposes of this example we simulate this by spinning off a
80+
separate thread (line <strong>14</strong>) and passing <code>sender</code> into it. Since this is
81+
a multiple-producers-single-consumer channel we can clone the sender
82+
as often as we want.</p>
83+
<p>The thread will send this event once a second (line <strong>18</strong>).
84+
We want Bevy systems to react to these Events.
85+
Therefore we register a observer (line <strong>25</strong>) that will trigger every
86+
time this event gets send.</p>
87+
<p>Let's look at the code of the observer:</p>
88+
<pre data-lang="rust" style="background-color:#212121;color:#eeffff;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="font-style:italic;color:#c792ea;">fn </span><span style="color:#82aaff;">on_event</span><span style="color:#89ddff;">(</span><span style="color:#f78c6c;">trigger</span><span style="color:#89ddff;">: </span><span>Trigger</span><span style="color:#89ddff;">&lt;</span><span>MyEvent</span><span style="color:#89ddff;">&gt;) {
89+
</span><span> </span><span style="font-style:italic;color:#c792ea;">let</span><span> event </span><span style="color:#89ddff;">=</span><span> trigger</span><span style="color:#89ddff;">.</span><span style="color:#82aaff;">event</span><span style="color:#89ddff;">();
90+
</span><span> info!</span><span style="color:#89ddff;">(&quot;</span><span style="color:#c3e88d;">trigger with: {}</span><span style="color:#89ddff;">&quot;,</span><span> event</span><span style="color:#89ddff;">.</span><span style="color:#f78c6c;">0</span><span style="color:#89ddff;">);
91+
</span><span style="color:#89ddff;">}
92+
</span></code></pre>
93+
<p>Thanks to <code>bevy_channel_trigger</code> we can react to these Events now just
94+
like we would with any other Observer. In this example we simply trace
95+
this to the console.</p>
96+
<blockquote>
97+
<p>You can find a more elaborate example in this
98+
<a href="https://github.com/rustunit/bevy_web_drop_image_as_sprite">drop images into bevy on the web</a> demo.</p>
99+
</blockquote>
100+
<h1 id="how-does-crossbeam-flume-and-others-compare">How does crossbeam, flume and others compare</h1>
101+
<p>You might be wondering why we chose to use <strong>crossbeam</strong> here under the hood.</p>
102+
<p>First of all we abstracted that decision away and can easily exchange the
103+
underlying channel when we want to.</p>
104+
<p>As a matter of fact the initial implementation was using <strong>flume</strong>, but
105+
ultimately we decided to move to crossbeam because it is by far the most
106+
actively maintained channel implementation in Rust and creates less wasm size than flume.</p>
107+
<blockquote>
108+
<p>Flume seems actually more lean in direct comparison to crossbeam but by
109+
using flume we effectively add another dependency because Bevy itself already brings crossbeam along.</p>
110+
</blockquote>
111+
<p>Bevy also brings <strong>async-channel</strong> along surprisingly but just like flume
112+
it pales in comparison to crossbeam in regards to maintenance.</p>
113+
<h1 id="what-about-bevy-crossbeam-event">What about bevy_crossbeam_event?</h1>
114+
<p>Last but not least let's look at the alternative: <strong>bevy_crossbeam_channel</strong>
115+
We actually used this one before but it dates to a time when the only messaging
116+
system Bevy had was <code>EventWriter</code>/<code>EventReader</code>. These matter as they are more performant in cases where you want to send massive amounts of events at the expense of slightly less ergonomic event handling.</p>
117+
<p><strong>But</strong> for our use cases events are used to cross the language barrier primarily and we want to have maximum ergonomics in how to write handlers for these using the new Observer API.</p>
118+
<p>Migration of using <code>bevy_channel_trigger</code> in our crates like <a href="https://github.com/rustunit/bevy_web_popups">bevy_web_popups</a> has already begun an will be finished with the migration to bevy 0.15!</p>
119+
<hr />
120+
<p>You need support building your Bevy or Rust project? Our team of experts can support you! <a href="https://rustunit.com/contact/">Contact us.</a></p>
121+
122+
</div>
123+
</div>
124+
125+
<footer>
126+
127+
<div class="links">
128+
129+
<a rel="me" href="https:&#x2F;&#x2F;www.linkedin.com&#x2F;company&#x2F;rustunit&#x2F;" title="LinkedIn">
130+
<img alt="LinkedIn" class="icon" src="https://rustunit.com/icons/linkedin.svg" />
131+
</a>
132+
133+
<a rel="me" href="&#x2F;contact" title="Contact">
134+
<img alt="Contact" class="icon" src="https://rustunit.com/icons/mail.svg" />
135+
</a>
136+
137+
<a rel="me" href="https:&#x2F;&#x2F;github.com&#x2F;rustunit" title="GitHub">
138+
<img alt="GitHub" class="icon" src="https://rustunit.com/icons/github.svg" />
139+
</a>
140+
141+
<a rel="me" href="https:&#x2F;&#x2F;www.youtube.com&#x2F;@rustunit_com" title="YouTube">
142+
<img alt="YouTube" class="icon" src="https://rustunit.com/icons/youtube.svg" />
143+
</a>
144+
145+
<a rel="me" href="https:&#x2F;&#x2F;mastodon.social&#x2F;@rustunit" title="Mastodon">
146+
<img alt="Mastodon" class="icon" src="https://rustunit.com/icons/mastodon.svg" />
147+
</a>
148+
149+
<a rel="me" href="https:&#x2F;&#x2F;discord.gg&#x2F;rZv4uxSQx3" title="Discord">
150+
<img alt="Discord" class="icon" src="https://rustunit.com/icons/discord.svg" />
151+
</a>
152+
153+
</div>
154+
155+
156+
<div>Copyright © 2024 Rustunit B.V.</div>
157+
</footer>
158+
</body>
159+
160+
</html>
197 KB
Loading

docs/blog/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ <h1>Blog</h1>
2727
<div class="posts">
2828

2929

30+
31+
3032
<div class="post">
3133
<div class="date">2024-10-21</div>
3234
<a href="https://rustunit.com/blog/2024/10-21-bevy-libgdx-atlas/">

docs/sitemap.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
<loc>https://rustunit.com/blog/2024/10-21-bevy-libgdx-atlas/</loc>
1515
<lastmod>2024-10-21</lastmod>
1616
</url>
17+
<url>
18+
<loc>https://rustunit.com/blog/2024/11-14-bevy-channel-trigger/</loc>
19+
<lastmod>2024-11-14</lastmod>
20+
</url>
1721
<url>
1822
<loc>https://rustunit.com/blog/2024/hello-world/</loc>
1923
<lastmod>2024-04-11</lastmod>

0 commit comments

Comments
 (0)