Skip to content

Commit 89d98f3

Browse files
authored
Rune support (#99)
* Add support for Rune Adds initial functionality neccesary to support the Rune programming language. Specifically, it adds a `ScriptHost` implementation along with a few supporting data structures. * Add minimal Rune example * Add Rune event recipient example * Cleanup * Cache Vm * Update CI --------- Co-authored-by: Luke Van Wingerden <=>
1 parent 3362815 commit 89d98f3

File tree

12 files changed

+578
-6
lines changed

12 files changed

+578
-6
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ jobs:
4141
uses: actions-rs/cargo@v1
4242
with:
4343
command: check
44-
args: --workspace --features=${{ matrix.run_args.lua }},rhai,teal,lua_script_api,rhai_script_api
44+
args: --workspace --features=${{ matrix.run_args.lua }},rhai,teal,lua_script_api,rhai_script_api,rune
4545
# for non x86 cross-compiled builds
4646
- if: matrix.run_args.cross != null
4747
uses: houseabsolute/actions-rust-cross@v0
4848
with:
4949
command: check
5050
target: ${{ matrix.run_args.cross }}
51-
args: --workspace --features=${{ matrix.run_args.lua }},rhai,teal,lua_script_api,rhai_script_api --profile=ephemeral-build
51+
args: --workspace --features=${{ matrix.run_args.lua }},rhai,teal,lua_script_api,rhai_script_api,rune --profile=ephemeral-build
5252

5353
fmt:
5454
name: Rustfmt
@@ -87,7 +87,7 @@ jobs:
8787
- uses: actions-rs/cargo@v1
8888
with:
8989
command: clippy
90-
args: --features=lua54,rhai,teal,lua_script_api,rhai_script_api --profile=ephemeral-build -- -D warnings
90+
args: --features=lua54,rhai,teal,lua_script_api,rhai_script_api,rune --profile=ephemeral-build -- -D warnings
9191
tests:
9292
name: Tests
9393
runs-on: ubuntu-latest
@@ -116,7 +116,7 @@ jobs:
116116
- uses: actions-rs/cargo@v1
117117
with:
118118
command: test
119-
args: --workspace --features=lua54,rhai,teal,lua_script_api,rhai_script_api --profile=ephemeral-build
119+
args: --workspace --features=lua54,rhai,teal,lua_script_api,rhai_script_api,rune --profile=ephemeral-build
120120
docs:
121121
name: Docs
122122
runs-on: ubuntu-latest

Cargo.toml

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ name = "bevy_mod_scripting"
2121
path = "src/lib.rs"
2222

2323
[package.metadata."docs.rs"]
24-
features = ["lua", "lua54", "rhai", "lua_script_api", "rhai_script_api", "teal"]
24+
features = ["lua", "lua54", "rhai", "lua_script_api", "rhai_script_api", "teal", "rune"]
2525

2626
[package.metadata.release]
2727
pre-release-replacements = [
2828
{ file = "Cargo.toml", search = '^version\s*=\s*.*$', replace = "version = \"{{version}}\"", exactly = 1 },
2929
{ file = "Cargo.toml", search = '^(?P<h>bevy_mod_scripting_core\s*=.*)version\s*=\s*".*"(?P<t>.*)$', replace = "${h}version = \"{{version}}\"${t}", exactly = 1 },
3030
{ file = "Cargo.toml", search = '^(?P<h>bevy_mod_scripting_lua\s*=.*)version\s*=\s*".*"(?P<t>.*)$', replace = "${h}version = \"{{version}}\"${t}", exactly = 1 },
3131
{ file = "Cargo.toml", search = '^(?P<h>bevy_mod_scripting_rhai\s*=.*)version\s*=\s*".*"(?P<t>.*)$', replace = "${h}version = \"{{version}}\"${t}", exactly = 1 },
32+
{ file = "Cargo.toml", search = '^(?P<h>bevy_mod_scripting_rune\s*=.*)version\s*=\s*".*"(?P<t>.*)$', replace = "${h}version = \"{{version}}\"${t}", exactly = 1 },
3233
{ file = "Cargo.toml", search = '^(?P<h>bevy_script_api\s*=.*)version\s*=\s*".*"(?P<t>.*)$', replace = "${h}version = \"{{version}}\"${t}", exactly = 1 },
3334
]
3435

@@ -58,11 +59,15 @@ mlua_async = ["bevy_mod_scripting_lua/mlua_async"]
5859
rhai = ["bevy_mod_scripting_rhai"]
5960
rhai_script_api = ["bevy_script_api/rhai"]
6061

62+
## rune
63+
rune = ["bevy_mod_scripting_rune"]
64+
6165
[dependencies]
6266
bevy = { version = "0.11", default-features = false }
6367
bevy_mod_scripting_core = { path = "bevy_mod_scripting_core", version = "0.3.0" }
6468
bevy_mod_scripting_lua = { path = "languages/bevy_mod_scripting_lua", version = "0.3.0", optional = true }
6569
bevy_mod_scripting_rhai = { path = "languages/bevy_mod_scripting_rhai", version = "0.3.0", optional = true }
70+
bevy_mod_scripting_rune = { path = "languages/bevy_mod_scripting_rune", version = "0.3.0", optional = true }
6671
bevy_script_api = { path = "bevy_script_api", version = "0.3.0", optional = true }
6772

6873
[dev-dependencies]
@@ -84,6 +89,7 @@ members = [
8489
"languages/bevy_mod_scripting_lua_derive",
8590
"languages/bevy_mod_scripting_rhai",
8691
"languages/bevy_mod_scripting_rhai_derive",
92+
"languages/bevy_mod_scripting_rune",
8793
"bevy_mod_scripting_common",
8894
]
8995

@@ -139,7 +145,6 @@ name = "documentation_gen_lua"
139145
path = "examples/lua/documentation_gen.rs"
140146
required-features = ["lua54", "teal", "lua_script_api"]
141147

142-
143148
[[example]]
144149
name = "bevy_api_lua"
145150
path = "examples/lua/bevy_api.rs"
@@ -159,3 +164,13 @@ required-features = ["rhai", "rhai_script_api"]
159164
name = "wrappers"
160165
path = "examples/wrappers.rs"
161166
required-features = ["lua54", "lua_script_api"]
167+
168+
[[example]]
169+
name = "minimal_rune"
170+
path = "examples/rune/minimal.rs"
171+
required-features = ["rune"]
172+
173+
[[example]]
174+
name = "event_recipients_rune"
175+
path = "examples/rune/event_recipients.rs"
176+
required-features = ["rune"]

assets/scripts/event_recipients.rune

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn on_event(id) {
2+
info(`id: ${id}`);
3+
}

assets/scripts/minimal.rune

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn on_event() {
2+
print_fancy("Hello, World!");
3+
}

examples/rune/event_recipients.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use bevy::asset::ChangeWatcher;
2+
use bevy::prelude::*;
3+
use bevy_mod_scripting::prelude::*;
4+
use rand::prelude::SliceRandom;
5+
use std::sync::atomic::AtomicU32;
6+
use std::sync::atomic::Ordering::Relaxed;
7+
use std::time::Duration;
8+
9+
#[derive(Clone)]
10+
pub struct MyRuneArg(usize);
11+
12+
impl Args for MyRuneArg {
13+
fn into_stack(self, stack: &mut rune::runtime::Stack) -> rune::runtime::VmResult<()> {
14+
(self.0,).into_stack(stack)
15+
}
16+
17+
fn try_into_vec(self) -> rune::runtime::VmResult<rune::alloc::Vec<rune::Value>> {
18+
(self.0,).try_into_vec()
19+
}
20+
21+
fn count(&self) -> usize {
22+
1
23+
}
24+
}
25+
26+
/// A custom Rune API.
27+
#[derive(Default)]
28+
pub struct RuneAPIProvider;
29+
30+
impl APIProvider for RuneAPIProvider {
31+
type APITarget = Context;
32+
type DocTarget = RuneDocFragment;
33+
type ScriptContext = RuneScriptContext;
34+
35+
fn attach_api(&mut self, ctx: &mut Self::APITarget) -> Result<(), ScriptError> {
36+
let mut module = rune::Module::new();
37+
38+
module
39+
.function("info", |msg: String| info!("{msg}"))
40+
.build()
41+
.map_err(ScriptError::new_other)?;
42+
43+
ctx.install(module).map_err(ScriptError::new_other)?;
44+
45+
Ok(())
46+
}
47+
}
48+
49+
static COUNTER: AtomicU32 = AtomicU32::new(0);
50+
51+
/// Utility for generating random events from a list.
52+
fn fire_random_event(
53+
w: &mut PriorityEventWriter<RuneEvent<MyRuneArg>>,
54+
events: &[ScriptEventData],
55+
) {
56+
let mut rng = rand::thread_rng();
57+
let id = COUNTER.fetch_add(1, Relaxed);
58+
let arg = MyRuneArg(id as usize);
59+
let event = events
60+
.choose(&mut rng)
61+
.map(|v| RuneEvent {
62+
hook_name: v.0.to_string(),
63+
args: arg,
64+
recipients: v.1.clone(),
65+
})
66+
.unwrap();
67+
68+
info!(
69+
"\t - event: {},\t recipients: {:?},\t id: {}",
70+
event.hook_name, event.recipients, id
71+
);
72+
w.send(event, 0);
73+
}
74+
75+
fn do_update(mut w: PriorityEventWriter<RuneEvent<MyRuneArg>>) {
76+
info!("Update, firing:");
77+
78+
let all_events = [
79+
ScriptEventData("on_event", Recipients::All),
80+
ScriptEventData("on_event", Recipients::ScriptID(0)),
81+
ScriptEventData("on_event", Recipients::ScriptID(1)),
82+
ScriptEventData(
83+
"on_event",
84+
Recipients::ScriptName("scripts/event_recipients.rune".to_owned()),
85+
),
86+
];
87+
88+
// fire random event, for any of the system sets
89+
fire_random_event(&mut w, &all_events);
90+
}
91+
92+
#[derive(Clone)]
93+
pub struct ScriptEventData(&'static str, Recipients);
94+
95+
fn load_scripts(server: Res<AssetServer>, mut commands: Commands) {
96+
// Spawn two identical scripts.
97+
// Their id's will be 0 and 1.
98+
let path = "scripts/event_recipients.rune";
99+
let handle = server.load::<RuneFile, &str>(path);
100+
let scripts = (0..2)
101+
.map(|_| Script::<RuneFile>::new(path.to_string(), handle.clone()))
102+
.collect();
103+
104+
commands
105+
.spawn(())
106+
.insert(ScriptCollection::<RuneFile> { scripts });
107+
}
108+
109+
fn main() {
110+
App::new()
111+
.add_plugins(DefaultPlugins.set(AssetPlugin {
112+
watch_for_changes: ChangeWatcher::with_delay(Duration::from_secs(0)),
113+
..Default::default()
114+
}))
115+
.add_plugins(ScriptingPlugin)
116+
.add_systems(Startup, load_scripts)
117+
// Randomly fire events for either all scripts, the script with an id of `0`,
118+
// or the script with an id of `1`.
119+
.add_systems(Update, do_update)
120+
.add_script_handler::<RuneScriptHost<MyRuneArg>, 0, 0>(PostUpdate)
121+
.add_script_host::<RuneScriptHost<MyRuneArg>>(PostUpdate)
122+
.add_api_provider::<RuneScriptHost<MyRuneArg>>(Box::new(RuneAPIProvider))
123+
.run()
124+
}

examples/rune/minimal.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use bevy::prelude::*;
2+
use bevy_mod_scripting::prelude::*;
3+
4+
fn main() {
5+
App::new()
6+
.add_plugins(DefaultPlugins)
7+
.add_plugins(ScriptingPlugin)
8+
.add_systems(Startup, setup)
9+
.add_systems(Update, update)
10+
.add_script_host::<RuneScriptHost<()>>(PostUpdate)
11+
.add_script_handler::<RuneScriptHost<()>, 0, 1>(PostUpdate)
12+
.add_api_provider::<RuneScriptHost<()>>(Box::new(MyAPIProvider))
13+
.run()
14+
}
15+
16+
struct MyAPIProvider;
17+
18+
impl APIProvider for MyAPIProvider {
19+
type APITarget = Context;
20+
type ScriptContext = RuneScriptContext;
21+
type DocTarget = RuneDocFragment;
22+
23+
fn attach_api(&mut self, api: &mut Self::APITarget) -> Result<(), ScriptError> {
24+
let mut module = rune::Module::new();
25+
26+
module
27+
.function("print_fancy", |msg: &str| println!("✨ {msg} ✨"))
28+
.build()
29+
.map_err(ScriptError::new_other)?;
30+
31+
api.install(module).map_err(ScriptError::new_other)?;
32+
33+
Ok(())
34+
}
35+
}
36+
37+
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
38+
let script_path = "scripts/minimal.rune";
39+
40+
commands.spawn(ScriptCollection::<RuneFile> {
41+
scripts: vec![Script::new(
42+
script_path.to_owned(),
43+
asset_server.load(script_path),
44+
)],
45+
});
46+
}
47+
48+
fn update(mut events: PriorityEventWriter<RuneEvent<()>>) {
49+
events.send(
50+
RuneEvent {
51+
hook_name: "on_event".into(),
52+
args: (),
53+
recipients: Recipients::All,
54+
},
55+
0,
56+
);
57+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[package]
2+
name = "bevy_mod_scripting_rune"
3+
version = "0.3.0"
4+
edition = "2021"
5+
license = "MIT"
6+
description = "Necessary functionality for Rune support with bevy_mod_scripting"
7+
repository = "https://github.com/makspll/bevy_mod_scripting"
8+
homepage = "https://github.com/makspll/bevy_mod_scripting"
9+
keywords = ["bevy", "gamedev", "scripting", "rune"]
10+
categories = ["game-development"]
11+
readme = "readme.md"
12+
13+
[package.metadata.release]
14+
pre-release-replacements = [
15+
{ file = "Cargo.toml", search = '^version\s*=\s*.*$', replace = "version = \"{{version}}\"", exactly = 1 },
16+
{ file = "Cargo.toml", search = '^(?P<h>bevy_mod_scripting_core\s*=.*)version\s*=\s*".*"(?P<t>.*)$', replace = "${h}version = \"{{version}}\"${t}", exactly = 1 },
17+
]
18+
19+
[lib]
20+
name = "bevy_mod_scripting_rune"
21+
path = "src/lib.rs"
22+
23+
[dependencies]
24+
bevy_mod_scripting_core = { path = "../../bevy_mod_scripting_core", version = "0.3.0" }
25+
bevy = "0.11"
26+
rune = "0.13.1"
27+
rune-modules = "0.13.1"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# bevy_mod_scripting_rune
2+
3+
This crate is a part of the ["bevy_mod_scripting" workspace](https://github.com/makspll/bevy_mod_scripting).
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use bevy::{
2+
asset::{AssetLoader, LoadedAsset},
3+
reflect::{TypePath, TypeUuid},
4+
};
5+
use bevy_mod_scripting_core::prelude::*;
6+
use std::sync::Arc;
7+
8+
#[derive(Debug, TypeUuid, TypePath)]
9+
#[uuid = "e4f7d00d-5acd-45fb-a29c-6472718771fc"]
10+
/// A loaded rune file in bytes.
11+
pub struct RuneFile {
12+
/// File content in bytes.
13+
pub bytes: Arc<[u8]>,
14+
}
15+
16+
impl CodeAsset for RuneFile {
17+
fn bytes(&self) -> &[u8] {
18+
&self.bytes
19+
}
20+
}
21+
22+
#[derive(Default)]
23+
/// Enables loading Rune scripts from `.rune` and `.rn` files.
24+
pub struct RuneLoader;
25+
26+
impl AssetLoader for RuneLoader {
27+
fn load<'a>(
28+
&'a self,
29+
bytes: &'a [u8],
30+
load_context: &'a mut bevy::asset::LoadContext,
31+
) -> bevy::utils::BoxedFuture<'a, Result<(), bevy::asset::Error>> {
32+
Box::pin(async move {
33+
load_context.set_default_asset(LoadedAsset::new(RuneFile {
34+
bytes: bytes.into(),
35+
}));
36+
37+
Ok(())
38+
})
39+
}
40+
41+
fn extensions(&self) -> &[&str] {
42+
&["rune", "rn"]
43+
}
44+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use bevy_mod_scripting_core::prelude::*;
2+
3+
pub struct RuneDocFragment;
4+
5+
impl DocFragment for RuneDocFragment {
6+
fn merge(self, _o: Self) -> Self {
7+
todo!()
8+
}
9+
10+
fn gen_docs(self) -> Result<(), ScriptError> {
11+
todo!()
12+
}
13+
14+
fn name(&self) -> &'static str {
15+
todo!()
16+
}
17+
}

0 commit comments

Comments
 (0)