Lua support (#16)
* adds Lua language support * makes the library generic, allowing for easy extension for more languages * small improvements and fixes
This commit is contained in:
parent
7846b556ca
commit
6726e40768
104 changed files with 3433 additions and 666 deletions
38
.github/workflows/deploy_book.yml
vendored
Normal file
38
.github/workflows/deploy_book.yml
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
name: Deploy book
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write # To push a branch
|
||||||
|
pages: write # To push to a GitHub Pages site
|
||||||
|
id-token: write # To update the deployment status
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install latest mdbook
|
||||||
|
run: |
|
||||||
|
tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name')
|
||||||
|
url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz"
|
||||||
|
mkdir mdbook
|
||||||
|
curl -sSL $url | tar -xz --directory=./mdbook
|
||||||
|
echo `pwd`/mdbook >> $GITHUB_PATH
|
||||||
|
- name: Build Book
|
||||||
|
run: |
|
||||||
|
cd book
|
||||||
|
mdbook build
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
# Upload entire repository
|
||||||
|
path: 'book'
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
8
.github/workflows/rust.yml
vendored
8
.github/workflows/rust.yml
vendored
|
|
@ -17,12 +17,12 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Clippy
|
- name: Clippy
|
||||||
run: cargo clippy --verbose -- -D warnings
|
run: cargo clippy --all-features --verbose -- -D warnings
|
||||||
- name: Build
|
- name: Build
|
||||||
run: cargo build --verbose
|
run: cargo build --all-features --verbose
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test --verbose
|
run: cargo test --all-features --verbose
|
||||||
- name: Install cargo-examples
|
- name: Install cargo-examples
|
||||||
run: cargo install cargo-examples
|
run: cargo install cargo-examples
|
||||||
- name: Run all examples
|
- name: Run all examples
|
||||||
run: cargo examples
|
run: cargo examples --features=lua,rhai
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
.vscode
|
.vscode
|
||||||
|
rust-analyzer.json
|
||||||
|
|
|
||||||
93
Cargo.toml
93
Cargo.toml
|
|
@ -10,13 +10,102 @@ description = "Plugin for Bevy engine that allows you to write some of your game
|
||||||
repository = "https://github.com/jarkonik/bevy_scriptum"
|
repository = "https://github.com/jarkonik/bevy_scriptum"
|
||||||
keywords = ["bevy", "rhai", "scripting", "game", "gamedev"]
|
keywords = ["bevy", "rhai", "scripting", "game", "gamedev"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[features]
|
||||||
|
lua = ["mlua/luajit"]
|
||||||
|
rhai = ["dep:rhai"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy = { default-features = false, version = "0.13.0", features = [
|
bevy = { default-features = false, version = "0.13.0", features = [
|
||||||
"bevy_asset",
|
"bevy_asset",
|
||||||
] }
|
] }
|
||||||
serde = "1.0.162"
|
serde = "1.0.162"
|
||||||
rhai = { version = "1.14.0", features = ["sync", "internals", "unchecked"] }
|
rhai = { version = "1.14.0", features = ["sync", "internals", "unchecked"], optional = true }
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
anyhow = "1.0.82"
|
anyhow = "1.0.82"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
mlua = { version = "0.9.8", features = ["luajit", "vendored", "send"], optional = true }
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "call_function_from_rust_rhai"
|
||||||
|
path = "examples/rhai/call_function_from_rust.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "current_entity_rhai"
|
||||||
|
path = "examples/rhai/current_entity.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "custom_type_rhai"
|
||||||
|
path = "examples/rhai/custom_type.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "ecs_rhai"
|
||||||
|
path = "examples/rhai/ecs.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "entity_variable_rhai"
|
||||||
|
path = "examples/rhai/entity_variable.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "function_params_rhai"
|
||||||
|
path = "examples/rhai/function_params.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "hello_world_rhai"
|
||||||
|
path = "examples/rhai/hello_world.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "non_closure_system_rhai"
|
||||||
|
path = "examples/rhai/non_closure_system.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "promises_rhai"
|
||||||
|
path = "examples/rhai/promises.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "side_effects_rhai"
|
||||||
|
path = "examples/rhai/side_effects.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "call_function_from_rust_lua"
|
||||||
|
path = "examples/lua/call_function_from_rust.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "current_entity_lua"
|
||||||
|
path = "examples/lua/current_entity.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "custom_type_lua"
|
||||||
|
path = "examples/lua/custom_type.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "ecs_lua"
|
||||||
|
path = "examples/lua/ecs.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "entity_variable_lua"
|
||||||
|
path = "examples/lua/entity_variable.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "function_params_lua"
|
||||||
|
path = "examples/lua/function_params.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "hello_world_lua"
|
||||||
|
path = "examples/lua/hello_world.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "non_closure_system_lua"
|
||||||
|
path = "examples/lua/non_closure_system.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "promises_lua"
|
||||||
|
path = "examples/lua/promises.rs"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "side_effects_lua"
|
||||||
|
path = "examples/lua/side_effects.rs"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tracing-subscriber = "0.3.18"
|
||||||
|
mlua = { version = "0.9.8", features = ["luajit", "vendored", "send"] }
|
||||||
|
rhai = { version = "1.14.0", features = ["sync", "internals", "unchecked"] }
|
||||||
|
|
|
||||||
167
README.md
167
README.md
|
|
@ -1,9 +1,14 @@
|
||||||
# bevy_scriptum 📜
|
# bevy_scriptum 📜
|
||||||
|
|
||||||
bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game logic in a scripting language.
|
bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game logic in a scripting language.
|
||||||
Currently, only [Rhai](https://rhai.rs/) is supported, but more languages may be added in the future.
|
Currently [Rhai](https://rhai.rs/) and [Lua](https://lua.org/) are supported, but more languages may be added in the future.
|
||||||
|
|
||||||
It's main advantages include:
|
Everything you need to know to get started with using this library is contained in the
|
||||||
|
[bevy_scriptum book](https://link-to-book.com)
|
||||||
|
|
||||||
|
API docs are available in [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/)
|
||||||
|
|
||||||
|
bevy_scriptum's main advantages include:
|
||||||
- low-boilerplate
|
- low-boilerplate
|
||||||
- easy to use
|
- easy to use
|
||||||
- asynchronicity with a promise-based API
|
- asynchronicity with a promise-based API
|
||||||
|
|
@ -16,17 +21,20 @@ All you need to do is register callbacks on your Bevy app like this:
|
||||||
```rust
|
```rust
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_scriptum::prelude::*;
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugins(ScriptingPlugin::default())
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
.add_script_function(String::from("hello_bevy"), || {
|
runtime.add_function(String::from("hello_bevy"), || {
|
||||||
println!("hello bevy, called from script");
|
println!("hello bevy, called from script");
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
```
|
```
|
||||||
And you can call them in your scripts like this:
|
And you can call them in your scripts like this:
|
||||||
```rhai
|
```lua
|
||||||
hello_bevy();
|
hello_bevy()
|
||||||
```
|
```
|
||||||
|
|
||||||
Every callback function that you expose to the scripting language is also a Bevy system, so you can easily query and mutate ECS components and resources just like you would in a regular Bevy system:
|
Every callback function that you expose to the scripting language is also a Bevy system, so you can easily query and mutate ECS components and resources just like you would in a regular Bevy system:
|
||||||
|
|
@ -34,42 +42,47 @@ Every callback function that you expose to the scripting language is also a Bevy
|
||||||
```rust
|
```rust
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_scriptum::prelude::*;
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct Player;
|
struct Player;
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugins(ScriptingPlugin::default())
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
.add_script_function(
|
runtime.add_function(
|
||||||
String::from("print_player_names"),
|
String::from("print_player_names"),
|
||||||
|players: Query<&Name, With<Player>>| {
|
|players: Query<&Name, With<Player>>| {
|
||||||
for player in &players {
|
for player in &players {
|
||||||
println!("player name: {}", player);
|
println!("player name: {}", player);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pass arguments to your callback functions, just like you would in a regular Bevy system - using `In` structs with tuples:
|
You can also pass arguments to your callback functions, just like you would in a regular Bevy system - using `In` structs with tuples:
|
||||||
```rust
|
```rust
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_scriptum::prelude::*;
|
use bevy_scriptum::prelude::*;
|
||||||
use rhai::ImmutableString;
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugins(ScriptingPlugin::default())
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
.add_script_function(
|
runtime.add_function(
|
||||||
String::from("fun_with_string_param"),
|
String::from("fun_with_string_param"),
|
||||||
|In((x,)): In<(ImmutableString,)>| {
|
|In((x,)): In<(String,)>| {
|
||||||
println!("called with string: '{}'", x);
|
println!("called with string: '{}'", x);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
```
|
```
|
||||||
which you can then call in your script like this:
|
which you can then call in your script like this:
|
||||||
```rhai
|
```lua
|
||||||
fun_with_string_param("Hello world!");
|
fun_with_string_param("Hello world!")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Usage
|
### Usage
|
||||||
|
|
@ -78,65 +91,68 @@ Add the following to your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_scriptum = "0.2"
|
bevy_scriptum = { version = "0.5", features = ["lua"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
or execute `cargo add bevy_scriptum` from your project directory.
|
or execute `cargo add bevy_scriptum --features lua` from your project directory.
|
||||||
|
|
||||||
Add the following to your `main.rs`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::prelude::*;
|
|
||||||
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin::default())
|
|
||||||
.run();
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now start exposing functions to the scripting language. For example, you can expose a function that prints a message to the console:
|
You can now start exposing functions to the scripting language. For example, you can expose a function that prints a message to the console:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use rhai::ImmutableString;
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_scriptum::prelude::*;
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugins(ScriptingPlugin::default())
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
.add_script_function(
|
runtime.add_function(
|
||||||
String::from("my_print"),
|
String::from("my_print"),
|
||||||
|In((x,)): In<(ImmutableString,)>| {
|
|In((x,)): In<(String,)>| {
|
||||||
println!("my_print: '{}'", x);
|
println!("my_print: '{}'", x);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can create a script file in `assets` directory called `script.rhai` that calls this function:
|
Then you can create a script file in `assets` directory called `script.lua` that calls this function:
|
||||||
|
|
||||||
```rhai
|
```lua
|
||||||
my_print("Hello world!");
|
my_print("Hello world!")
|
||||||
```
|
```
|
||||||
|
|
||||||
And spawn a `Script` component with a handle to a script source file`:
|
And spawn an entity with attached `Script` component with a handle to a script source file:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_scriptum::Script;
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("my_print"),
|
||||||
|
|In((x,)): In<(String,)>| {
|
||||||
|
println!("my_print: '{}'", x);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
.add_systems(Startup,|mut commands: Commands, asset_server: Res<AssetServer>| {
|
.add_systems(Startup,|mut commands: Commands, asset_server: Res<AssetServer>| {
|
||||||
commands.spawn(Script::new(asset_server.load("script.rhai")));
|
commands.spawn(Script::<LuaScript>::new(asset_server.load("script.lua")));
|
||||||
});
|
})
|
||||||
|
.run();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You should then see `my_print: 'Hello world!'` printed in your console.
|
||||||
|
|
||||||
### Provided examples
|
### Provided examples
|
||||||
|
|
||||||
You can also try running provided examples by cloning this repository and running `cargo run --example <example_name>`. For example:
|
You can also try running provided examples by cloning this repository and running `cargo run --example <example_name>_<language_name>`. For example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run --example hello_world
|
cargo run --example hello_world_lua
|
||||||
```
|
```
|
||||||
The examples live in `examples` directory and their corresponding scripts live in `assets/examples` directory within the repository.
|
The examples live in `examples` directory and their corresponding scripts live in `assets/examples` directory within the repository.
|
||||||
|
|
||||||
|
|
@ -144,31 +160,46 @@ The examples live in `examples` directory and their corresponding scripts live i
|
||||||
|
|
||||||
| bevy version | bevy_scriptum version |
|
| bevy version | bevy_scriptum version |
|
||||||
|--------------|----------------------|
|
|--------------|----------------------|
|
||||||
| 0.13 | 0.4 |
|
| 0.13 | 0.4-0.5 |
|
||||||
| 0.12 | 0.3 |
|
| 0.12 | 0.3 |
|
||||||
| 0.11 | 0.2 |
|
| 0.11 | 0.2 |
|
||||||
| 0.10 | 0.1 |
|
| 0.10 | 0.1 |
|
||||||
|
|
||||||
### Promises - getting return values from scripts
|
### Promises - getting return values from scripts
|
||||||
|
|
||||||
Every function called from script returns a promise that you can call `.then` with a callback function on. This callback function will be called when the promise is resolved, and will be passed the return value of the function called from script. For example:
|
Every function called from script returns a promise that you can call `:and_then` with a callback function on. This callback function will be called when the promise is resolved, and will be passed the return value of the function called from script. For example:
|
||||||
|
|
||||||
```rhai
|
```lua
|
||||||
get_player_name().then(|name| {
|
get_player_name():and_then(function(name)
|
||||||
print(name);
|
print(name)
|
||||||
});
|
end)
|
||||||
```
|
```
|
||||||
|
which will print out `John` when used with following exposed function:
|
||||||
|
|
||||||
### Access entity from script
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("get_player_name"), || String::from("John"));
|
||||||
|
});
|
||||||
|
````
|
||||||
|
|
||||||
|
## Access entity from script
|
||||||
|
|
||||||
A variable called `entity` is automatically available to all scripts - it represents bevy entity that the `Script` component is attached to.
|
A variable called `entity` is automatically available to all scripts - it represents bevy entity that the `Script` component is attached to.
|
||||||
It exposes `.index()` method that returns bevy entity index.
|
It exposes `index` property that returns bevy entity index.
|
||||||
It is useful for accessing entity's components from scripts.
|
It is useful for accessing entity's components from scripts.
|
||||||
It can be used in the following way:
|
It can be used in the following way:
|
||||||
```rhai
|
```lua
|
||||||
print("Current entity index: " + entity.index());
|
print("Current entity index: " .. entity.index)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`entity` variable is currently not available within promise callbacks.
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
Contributions are welcome! Feel free to open an issue or submit a pull request.
|
Contributions are welcome! Feel free to open an issue or submit a pull request.
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,7 @@ currently being supported with security updates.
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ------------------ |
|
| ------- | ------------------ |
|
||||||
| 0.1 | :x: |
|
| 0.5 | :white_check_mark: |
|
||||||
| 0.2 | :white_check_mark: |
|
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
|
|
||||||
13
assets/examples/lua/call_function_from_rust.lua
Normal file
13
assets/examples/lua/call_function_from_rust.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
local my_state = {
|
||||||
|
iterations = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
function on_update()
|
||||||
|
my_state.iterations = my_state.iterations + 1;
|
||||||
|
print("on_update called " .. my_state.iterations .. " times")
|
||||||
|
|
||||||
|
if my_state.iterations >= 10 then
|
||||||
|
print("calling quit");
|
||||||
|
quit();
|
||||||
|
end
|
||||||
|
end
|
||||||
7
assets/examples/lua/current_entity.lua
Normal file
7
assets/examples/lua/current_entity.lua
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- entity is a global variable that is set to the entity that is currently being processed,
|
||||||
|
-- it is automatically available in all scripts
|
||||||
|
|
||||||
|
-- get name of the entity
|
||||||
|
get_name(entity):and_then(function(name)
|
||||||
|
print(name)
|
||||||
|
end)
|
||||||
4
assets/examples/lua/custom_type.lua
Normal file
4
assets/examples/lua/custom_type.lua
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
-- Create a new instance of MyType
|
||||||
|
my_type = MyType();
|
||||||
|
-- Call registered method
|
||||||
|
print(my_type:my_method())
|
||||||
1
assets/examples/lua/ecs.lua
Normal file
1
assets/examples/lua/ecs.lua
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
print_player_names()
|
||||||
3
assets/examples/lua/entity_variable.lua
Normal file
3
assets/examples/lua/entity_variable.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
-- entity is a global variable that is set to the entity that is currently being processed,
|
||||||
|
-- it is automatically available in all scripts
|
||||||
|
print("Current entity index: " .. entity.index)
|
||||||
4
assets/examples/lua/function_params.lua
Normal file
4
assets/examples/lua/function_params.lua
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
fun_with_string_param("hello")
|
||||||
|
fun_with_i64_param(5)
|
||||||
|
fun_with_multiple_params(5, "hello")
|
||||||
|
fun_with_i64_and_array_param(5, { 1, 2, "third element" })
|
||||||
3
assets/examples/lua/promises.lua
Normal file
3
assets/examples/lua/promises.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
get_player_name():and_then(function(name)
|
||||||
|
print(name)
|
||||||
|
end);
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
// entity is a global variable that is set to the entity that is currently being processed,
|
// entity is a global variable that is set to the entity that is currently being processed,
|
||||||
// it is automatically available in all scripts
|
// it is automatically available in all scripts
|
||||||
print("Current entity index: " + entity.index());
|
print("Current entity index: " + entity.index);
|
||||||
1
assets/examples/rhai/hello_world.rhai
Normal file
1
assets/examples/rhai/hello_world.rhai
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
hello_bevy();
|
||||||
1
assets/examples/rhai/side_effects.rhai
Normal file
1
assets/examples/rhai/side_effects.rhai
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
spawn_entity();
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
function test_func()
|
||||||
|
print("abc" + 5)
|
||||||
|
end
|
||||||
7
assets/tests/lua/call_script_function_with_params.lua
Normal file
7
assets/tests/lua/call_script_function_with_params.lua
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
State = {
|
||||||
|
called_with = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_func(x)
|
||||||
|
called_with = x
|
||||||
|
end
|
||||||
5
assets/tests/lua/promise_runtime_error.lua
Normal file
5
assets/tests/lua/promise_runtime_error.lua
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
function test_func()
|
||||||
|
rust_func():and_then(function(x)
|
||||||
|
print("abc" + 5)
|
||||||
|
end)
|
||||||
|
end
|
||||||
9
assets/tests/lua/return_via_promise.lua
Normal file
9
assets/tests/lua/return_via_promise.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
State = {
|
||||||
|
x = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_func()
|
||||||
|
rust_func():and_then(function(x)
|
||||||
|
State.x = x
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
function test_func()
|
||||||
|
rust_func()
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
function test_func()
|
||||||
|
rust_func(5, "test")
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
function test_func()
|
||||||
|
rust_func(5)
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
State = {
|
||||||
|
times_called = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_func()
|
||||||
|
State.times_called = State.times_called + 1;
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
State = {
|
||||||
|
a_value = nil,
|
||||||
|
b_value = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_func(a, b)
|
||||||
|
State.a_value = a
|
||||||
|
State.b_value = b
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
State = {
|
||||||
|
a_value = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_func(a)
|
||||||
|
State.a_value = a
|
||||||
|
end
|
||||||
3
assets/tests/lua/side_effects.lua
Normal file
3
assets/tests/lua/side_effects.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
function test_func()
|
||||||
|
spawn_entity()
|
||||||
|
end
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn test_func() {
|
||||||
|
print("abc" * 5)
|
||||||
|
}
|
||||||
2
assets/tests/rhai/call_script_function_with_params.rhai
Normal file
2
assets/tests/rhai/call_script_function_with_params.rhai
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
fn test_func(x) {
|
||||||
|
}
|
||||||
5
assets/tests/rhai/promise_runtime_error.rhai
Normal file
5
assets/tests/rhai/promise_runtime_error.rhai
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
fn test_func() {
|
||||||
|
rust_func().then(|x| {
|
||||||
|
print("abc" * 5)
|
||||||
|
})
|
||||||
|
}
|
||||||
9
assets/tests/rhai/return_via_promise.rhai
Normal file
9
assets/tests/rhai/return_via_promise.rhai
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
let state = #{
|
||||||
|
x: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
fn test_func() {
|
||||||
|
rust_func().then(|x| {
|
||||||
|
state.x = x;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn test_func() {
|
||||||
|
rust_func();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn test_func() {
|
||||||
|
rust_func(5, "test");
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn test_func() {
|
||||||
|
rust_func(5);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
let state = #{
|
||||||
|
times_called: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
fn test_func() {
|
||||||
|
state.times_called += 1;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
let state = #{
|
||||||
|
a_value: (),
|
||||||
|
b_value: ()
|
||||||
|
};
|
||||||
|
|
||||||
|
fn test_func(a, b) {
|
||||||
|
state.a_value = a;
|
||||||
|
state.b_value = b;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
let state = #{
|
||||||
|
a_value: ()
|
||||||
|
};
|
||||||
|
|
||||||
|
fn test_func(a) {
|
||||||
|
state.a_value = a
|
||||||
|
}
|
||||||
3
assets/tests/rhai/side_effects.rhai
Normal file
3
assets/tests/rhai/side_effects.rhai
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn test_func() {
|
||||||
|
spawn_entity()
|
||||||
|
}
|
||||||
2
book/.gitignore
vendored
Normal file
2
book/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
book
|
||||||
|
doctest_cache
|
||||||
12
book/book.toml
Normal file
12
book/book.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
[book]
|
||||||
|
authors = ["Jaroslaw Konik"]
|
||||||
|
language = "en"
|
||||||
|
multilingual = false
|
||||||
|
src = "src"
|
||||||
|
title = "bevy_scriptum"
|
||||||
|
|
||||||
|
[preprocessor.keeper]
|
||||||
|
command = "mdbook-keeper"
|
||||||
|
manifest_dir = "../"
|
||||||
|
externs = ["bevy", "bevy_scriptum"]
|
||||||
|
build_features = ["lua", "rhai"]
|
||||||
21
book/src/SUMMARY.md
Normal file
21
book/src/SUMMARY.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
- [Introduction](./introduction.md)
|
||||||
|
- [Runtimes](./runtimes.md)
|
||||||
|
- [Lua](./lua/lua.md)
|
||||||
|
- [Installation](./lua/installation.md)
|
||||||
|
- [Hello World](./lua/hello_world.md)
|
||||||
|
- [Spawning scripts](./lua/spawning_scripts.md)
|
||||||
|
- [Calling Rust from Lua](./lua/calling_rust_from_script.md)
|
||||||
|
- [Calling Lua from Rust](./lua/calling_script_from_rust.md)
|
||||||
|
- [Interacting with bevy in callbacks](./lua/interacting_with_bevy.md)
|
||||||
|
- [Builtin types](./lua/builtin_types.md)
|
||||||
|
- [Builtin variables](./lua/builtin_variables.md)
|
||||||
|
- [Rhai](./rhai/rhai.md)
|
||||||
|
- [Installation](./rhai/installation.md)
|
||||||
|
- [Hello World(TBD)]()
|
||||||
|
- [Multiple runtimes(TBD)]()
|
||||||
|
- [Implementing custom runtimes(TBD)]()
|
||||||
|
- [Workflow](./workflow/workflow.md)
|
||||||
|
- [Live-reload](./workflow/live_reload.md)
|
||||||
|
- [Bevy support matrix](./bevy_support_matrix.md)
|
||||||
8
book/src/bevy_support_matrix.md
Normal file
8
book/src/bevy_support_matrix.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Bevy support matrix
|
||||||
|
|
||||||
|
| bevy version | bevy_scriptum version |
|
||||||
|
|--------------|----------------------|
|
||||||
|
| 0.13 | 0.4-0.5 |
|
||||||
|
| 0.12 | 0.3 |
|
||||||
|
| 0.11 | 0.2 |
|
||||||
|
| 0.10 | 0.1 |
|
||||||
210
book/src/introduction.md
Normal file
210
book/src/introduction.md
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
# bevy_scriptum 📜
|
||||||
|
|
||||||
|
bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game logic in a scripting language.
|
||||||
|
Currently [Rhai](https://rhai.rs/) and [Lua](https://lua.org/) are supported, but more languages may be added in the future.
|
||||||
|
|
||||||
|
API docs are available in [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/)
|
||||||
|
|
||||||
|
bevy_scriptum's main advantages include:
|
||||||
|
- low-boilerplate
|
||||||
|
- easy to use
|
||||||
|
- asynchronicity with a promise-based API
|
||||||
|
- flexibility
|
||||||
|
- hot-reloading
|
||||||
|
|
||||||
|
Scripts are separate files that can be hot-reloaded at runtime. This allows you to quickly iterate on your game logic without having to recompile your game.
|
||||||
|
|
||||||
|
All you need to do is register callbacks on your Bevy app like this:
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("hello_bevy"), || {
|
||||||
|
println!("hello bevy, called from script");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
And you can call them in your scripts like this:
|
||||||
|
```lua
|
||||||
|
hello_bevy()
|
||||||
|
```
|
||||||
|
|
||||||
|
Every callback function that you expose to the scripting language is also a Bevy system, so you can easily query and mutate ECS components and resources just like you would in a regular Bevy system:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Player;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("print_player_names"),
|
||||||
|
|players: Query<&Name, With<Player>>| {
|
||||||
|
for player in &players {
|
||||||
|
println!("player name: {}", player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also pass arguments to your callback functions, just like you would in a regular Bevy system - using `In` structs with tuples:
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("fun_with_string_param"),
|
||||||
|
|In((x,)): In<(String,)>| {
|
||||||
|
println!("called with string: '{}'", x);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
which you can then call in your script like this:
|
||||||
|
```lua
|
||||||
|
fun_with_string_param("Hello world!")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Add the following to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
bevy_scriptum = { version = "0.5", features = ["lua"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
or execute `cargo add bevy_scriptum --features lua` from your project directory.
|
||||||
|
|
||||||
|
You can now start exposing functions to the scripting language. For example, you can expose a function that prints a message to the console:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("my_print"),
|
||||||
|
|In((x,)): In<(String,)>| {
|
||||||
|
println!("my_print: '{}'", x);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can create a script file in `assets` directory called `script.lua` that calls this function:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
my_print("Hello world!")
|
||||||
|
```
|
||||||
|
|
||||||
|
And spawn an entity with attached `Script` component with a handle to a script source file:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("my_print"),
|
||||||
|
|In((x,)): In<(String,)>| {
|
||||||
|
println!("my_print: '{}'", x);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup,|mut commands: Commands, asset_server: Res<AssetServer>| {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(asset_server.load("script.lua")));
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You should then see `my_print: 'Hello world!'` printed in your console.
|
||||||
|
|
||||||
|
### Provided examples
|
||||||
|
|
||||||
|
You can also try running provided examples by cloning this repository and running `cargo run --example <example_name>_<language_name>`. For example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run --example hello_world_lua
|
||||||
|
```
|
||||||
|
The examples live in `examples` directory and their corresponding scripts live in `assets/examples` directory within the repository.
|
||||||
|
|
||||||
|
### Promises - getting return values from scripts
|
||||||
|
|
||||||
|
Every function called from script returns a promise that you can call `:and_then` with a callback function on. This callback function will be called when the promise is resolved, and will be passed the return value of the function called from script. For example:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
get_player_name():and_then(function(name)
|
||||||
|
print(name)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
which will print out `John` when used with following exposed function:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("get_player_name"), || String::from("John"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
````
|
||||||
|
|
||||||
|
## Access entity from script
|
||||||
|
|
||||||
|
A variable called `entity` is automatically available to all scripts - it represents bevy entity that the `Script` component is attached to.
|
||||||
|
It exposes `index` property that returns bevy entity index.
|
||||||
|
It is useful for accessing entity's components from scripts.
|
||||||
|
It can be used in the following way:
|
||||||
|
```lua
|
||||||
|
print("Current entity index: " .. entity.index)
|
||||||
|
```
|
||||||
|
|
||||||
|
`entity` variable is currently not available within promise callbacks.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Feel free to open an issue or submit a pull request.
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
bevy_scriptum is licensed under either of the following, at your option:
|
||||||
|
Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) or MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
|
||||||
87
book/src/lua/builtin_types.md
Normal file
87
book/src/lua/builtin_types.md
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# Builtin types
|
||||||
|
|
||||||
|
bevy_scriptum provides following types that can be used in Lua:
|
||||||
|
|
||||||
|
- ```Vec3```
|
||||||
|
- ```BevyEntity```
|
||||||
|
|
||||||
|
## Vec3
|
||||||
|
|
||||||
|
### Constructor
|
||||||
|
|
||||||
|
`Vec3(x: number, y: number, z: number)`
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
- `x: number`
|
||||||
|
- `y: number`
|
||||||
|
- `z: number`
|
||||||
|
|
||||||
|
|
||||||
|
### Example Lua usage
|
||||||
|
|
||||||
|
```
|
||||||
|
my_vec = Vec3(1, 2, 3)
|
||||||
|
set_translation(entity, my_vec)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Rust usage
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("set_translation"), set_translation);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_translation(
|
||||||
|
In((entity, translation)): In<(BevyEntity, BevyVec3)>,
|
||||||
|
mut entities: Query<&mut Transform>,
|
||||||
|
) {
|
||||||
|
let mut transform = entities.get_mut(entity.0).unwrap();
|
||||||
|
transform.translation = translation.0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## BevyEntity
|
||||||
|
|
||||||
|
### Constructor
|
||||||
|
|
||||||
|
None - instances can only be acquired by using built-in `entity` global variable.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
- `index: integer`
|
||||||
|
|
||||||
|
### Example Lua usage
|
||||||
|
|
||||||
|
```lua
|
||||||
|
print(entity.index)
|
||||||
|
pass_to_rust(entity)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Rust usage
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("pass_to_rust"), |In((entity,)): In<(BevyEntity,)>| {
|
||||||
|
println!("pass_to_rust called with entity: {:?}", entity);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
13
book/src/lua/builtin_variables.md
Normal file
13
book/src/lua/builtin_variables.md
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Builtin variables
|
||||||
|
|
||||||
|
## entity
|
||||||
|
|
||||||
|
A variable called `entity` is automatically available to all scripts - it represents bevy entity that the `Script` component is attached to.
|
||||||
|
It exposes `index` property that returns bevy entity index.
|
||||||
|
It is useful for accessing entity's components from scripts.
|
||||||
|
It can be used in the following way:
|
||||||
|
```lua
|
||||||
|
print("Current entity index: " .. entity.index)
|
||||||
|
```
|
||||||
|
|
||||||
|
`entity` variable is currently not available within promise callbacks.
|
||||||
121
book/src/lua/calling_rust_from_script.md
Normal file
121
book/src/lua/calling_rust_from_script.md
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
# Calling Rust from Lua
|
||||||
|
|
||||||
|
To call a rust function from Lua first you need to register a function
|
||||||
|
within Rust using builder pattern.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
// `runtime` is a builder that you can use to register functions
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For example to register a function called `my_rust_func` you can do the following:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("my_rust_func"), || {
|
||||||
|
println!("my_rust_func has been called");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After you do that the function will be available to Lua code in your spawned scripts.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
my_rust_func()
|
||||||
|
```
|
||||||
|
|
||||||
|
Registered functions can also take parameters. A parameter can be any type
|
||||||
|
that implements `FromLua`.
|
||||||
|
|
||||||
|
Since a registered callback function is a Bevy system, the parameters are passed
|
||||||
|
to it as `In` struct with tuple, which has to be the first parameter of the closure.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("func_with_params"), |args: In<(String, i64)>| {
|
||||||
|
println!("my_rust_func has been called with string {} and i64 {}", args.0.0, args.0.1);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To make it look nicer you can destructure the `In` struct.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("func_with_params"), |In((a, b)): In<(String, i64)>| {
|
||||||
|
println!("my_rust_func has been called with string {} and i64 {}", a, b);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above function can be called from Lua
|
||||||
|
|
||||||
|
```lua
|
||||||
|
func_with_params("abc", 123)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Return value via promise
|
||||||
|
|
||||||
|
Any registered rust function that returns a value will retrurn a promise when
|
||||||
|
called within a script. By calling `:and_then` on the promise you can register
|
||||||
|
a callback that will receive the value returned from Rust function.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("returns_value"), || {
|
||||||
|
123
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```lua
|
||||||
|
returns_value():and_then(function (value)
|
||||||
|
print(value) -- 123
|
||||||
|
end)
|
||||||
|
```
|
||||||
67
book/src/lua/calling_script_from_rust.md
Normal file
67
book/src/lua/calling_script_from_rust.md
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
# Calling Lua from Rust
|
||||||
|
|
||||||
|
To call a function defined in Lua
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function on_update()
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
We need to acquire `LuaRuntime` resource within a bevy system.
|
||||||
|
Then we will be able to call `call_fn` on it, providing the name
|
||||||
|
of the function to call, `LuaScriptData` that has been automatically
|
||||||
|
attached to entity after an entity with script attached has been spawned
|
||||||
|
and its script evaluated, the entity and optionally some arguments.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn call_lua_on_update_from_rust(
|
||||||
|
mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
|
||||||
|
scripting_runtime: ResMut<LuaRuntime>,
|
||||||
|
) {
|
||||||
|
for (entity, mut script_data) in &mut scripted_entities {
|
||||||
|
// calling function named `on_update` defined in lua script
|
||||||
|
scripting_runtime
|
||||||
|
.call_fn("on_update", &mut script_data, entity, ())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also pass some arguments by providing a tuple or `Vec` as the last
|
||||||
|
`call_fn` argument.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn call_lua_on_update_from_rust(
|
||||||
|
mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
|
||||||
|
scripting_runtime: ResMut<LuaRuntime>,
|
||||||
|
) {
|
||||||
|
for (entity, mut script_data) in &mut scripted_entities {
|
||||||
|
scripting_runtime
|
||||||
|
.call_fn("on_update", &mut script_data, entity, (123, String::from("hello")))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
They will be passed to `on_update` Lua function
|
||||||
|
```lua
|
||||||
|
function on_update(a, b)
|
||||||
|
print(a) -- 123
|
||||||
|
print(b) -- hello
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Any type that implements `IntoLua` can be passed as an argument withing the
|
||||||
|
tuple in `call_fn`.
|
||||||
66
book/src/lua/hello_world.md
Normal file
66
book/src/lua/hello_world.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Hello World
|
||||||
|
|
||||||
|
After you are done installing the required crates, you can start developing
|
||||||
|
your first game or application using bevy_scriptum.
|
||||||
|
|
||||||
|
To start using the library you need to first import some structs and traits
|
||||||
|
with Rust `use` statements.
|
||||||
|
|
||||||
|
For convenience there is a main "prelude" module provided called
|
||||||
|
`bevy_scriptum::prelude` and a prelude for each runtime you have enabled as
|
||||||
|
a create feature.
|
||||||
|
|
||||||
|
You can now start exposing functions to the scripting language. For example, you can expose a function that prints a message to the console:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("my_print"),
|
||||||
|
|In((x,)): In<(String,)>| {
|
||||||
|
println!("my_print: '{}'", x);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can create a script file in `assets` directory called `script.lua` that calls this function:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
my_print("Hello world!")
|
||||||
|
```
|
||||||
|
|
||||||
|
And spawn an entity with attached `Script` component with a handle to a script source file:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("my_print"),
|
||||||
|
|In((x,)): In<(String,)>| {
|
||||||
|
println!("my_print: '{}'", x);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup,|mut commands: Commands, asset_server: Res<AssetServer>| {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(asset_server.load("script.lua")));
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You should then see `my_print: 'Hello world!'` printed in your console.
|
||||||
12
book/src/lua/installation.md
Normal file
12
book/src/lua/installation.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Add the following to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
bevy = "0.13"
|
||||||
|
bevy_scriptum = { version = "0.5", features = ["lua"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need a different version of bevy you need to use a matching bevy_scriptum
|
||||||
|
version according to the [bevy support matrix](../bevy_support_matrix.md)
|
||||||
75
book/src/lua/interacting_with_bevy.md
Normal file
75
book/src/lua/interacting_with_bevy.md
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# Interacting with bevy in callbacks
|
||||||
|
|
||||||
|
Every registered function is also just a regular Bevy system.
|
||||||
|
|
||||||
|
That allows you to do anything you would do in a Bevy system.
|
||||||
|
|
||||||
|
You could for example create a callback system function that prints names
|
||||||
|
of all entities with `Player` component.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Player;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("print_player_names"),
|
||||||
|
|players: Query<&Name, With<Player>>| {
|
||||||
|
for player in &players {
|
||||||
|
println!("player name: {}", player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In script:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
print_player_names()
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use functions that interact with Bevy entities and resources and
|
||||||
|
take arguments at the same time. It could be used for example to mutate a
|
||||||
|
component.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Player {
|
||||||
|
health: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("hurt_player"),
|
||||||
|
|In((hit_value,)): In<(i32,)>, mut players: Query<&mut Player>| {
|
||||||
|
let mut player = players.single_mut();
|
||||||
|
player.health -= hit_value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And it could be called in script like:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
hurt_player(5)
|
||||||
|
```
|
||||||
3
book/src/lua/lua.md
Normal file
3
book/src/lua/lua.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Lua
|
||||||
|
|
||||||
|
This chapter demonstrates how to work with bevy_scriptum when using Lua language runtime.
|
||||||
40
book/src/lua/spawning_scripts.md
Normal file
40
book/src/lua/spawning_scripts.md
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Spawning scripts
|
||||||
|
|
||||||
|
To spawn a Lua script you will need to get a handle to a script asset using
|
||||||
|
bevy's `AssetServer`.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn my_spawner(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("my_script.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
After they scripts have been evaled by bevy_scriptum, the entities that they've
|
||||||
|
been attached to will get the `Script::<LuaScript>` component stripped and instead
|
||||||
|
```LuaScriptData``` component will be attached.
|
||||||
|
|
||||||
|
So to query scipted entities you could do something like:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn my_system(
|
||||||
|
mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
|
||||||
|
) {
|
||||||
|
for (entity, mut script_data) in &mut scripted_entities {
|
||||||
|
// do something with scripted entities
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
1
book/src/rhai/hello_world.md
Normal file
1
book/src/rhai/hello_world.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# Hello World
|
||||||
12
book/src/rhai/installation.md
Normal file
12
book/src/rhai/installation.md
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
Add the following to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
bevy = "0.13"
|
||||||
|
bevy_scriptum = { version = "0.5", features = ["rhai"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need a different version of bevy you need to use a matching bevy_scriptum
|
||||||
|
version according to the [bevy support matrix](../bevy_support_matrix.md)
|
||||||
3
book/src/rhai/rhai.md
Normal file
3
book/src/rhai/rhai.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Rhai
|
||||||
|
|
||||||
|
This chapter demonstrates how to work with bevy_scriptum when using Rhai language runtime.
|
||||||
3
book/src/runtimes.md
Normal file
3
book/src/runtimes.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Runtimes
|
||||||
|
|
||||||
|
This chapter demonstrates how to work with bevy_scriptum when using a specific runtime.
|
||||||
139
book/src/workflow/live_reload.md
Normal file
139
book/src/workflow/live_reload.md
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
# Live-reload
|
||||||
|
|
||||||
|
## Bevy included support
|
||||||
|
|
||||||
|
To enable life reload it should be enough to enable `file-watcher` feature
|
||||||
|
within bevy dependency in `Cargo.toml`
|
||||||
|
|
||||||
|
```
|
||||||
|
bevy = { version = "0.13", features = ["file_watcher"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Init-teardown pattern for game development
|
||||||
|
|
||||||
|
It is useful to structure your game in a way that would allow making changes to
|
||||||
|
the scripting code without restarting the game.
|
||||||
|
|
||||||
|
A useful pattern is to hava three functions "init", "update" and "teardown".
|
||||||
|
|
||||||
|
- "init" function will take care of starting the game(spawning the player, the level etc)
|
||||||
|
|
||||||
|
- "update" function will run the main game logic
|
||||||
|
|
||||||
|
- "teardown" function will despawn all the entities so game starts at fresh state.
|
||||||
|
|
||||||
|
This pattern is very easy to implement in bevy_scriptum. All you need is to define all needed functions
|
||||||
|
in script:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
player = {
|
||||||
|
entity = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
-- spawning all needed entities
|
||||||
|
local function init()
|
||||||
|
player.entity = spawn_player()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- game logic here, should be called in a bevy system using call_fn
|
||||||
|
local function update()
|
||||||
|
(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- despawning entities and possible other cleanup logic needed
|
||||||
|
local function teardown()
|
||||||
|
despawn(player.entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- call init to start the game, this will be called on each file-watcher script
|
||||||
|
-- reload
|
||||||
|
init()
|
||||||
|
```
|
||||||
|
|
||||||
|
The function calls can be implemented on Rust side the following way:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::BevyVec3;
|
||||||
|
|
||||||
|
fn init(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("scripts/game.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
|
||||||
|
scripting_runtime: ResMut<LuaRuntime>,
|
||||||
|
) {
|
||||||
|
for (entity, mut script_data) in &mut scripted_entities {
|
||||||
|
scripting_runtime
|
||||||
|
.call_fn("update", &mut script_data, entity, ())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn teardown(
|
||||||
|
mut ev_asset: EventReader<AssetEvent<LuaScript>>,
|
||||||
|
scripting_runtime: ResMut<LuaRuntime>,
|
||||||
|
mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
|
||||||
|
) {
|
||||||
|
for event in ev_asset.read() {
|
||||||
|
if let AssetEvent::Modified { .. } = event {
|
||||||
|
for (entity, mut script_data) in &mut scripted_entities {
|
||||||
|
scripting_runtime
|
||||||
|
.call_fn("teardown", &mut script_data, entity, ())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
And to tie this all together we do the following:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|builder| {
|
||||||
|
builder
|
||||||
|
.add_function(String::from("spawn_player"), spawn_player)
|
||||||
|
.add_function(String::from("despawn"), despawn);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, init)
|
||||||
|
.add_systems(Update, (update, teardown))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init() {} // Implemented elsewhere
|
||||||
|
fn update() {} // Implemented elsewhere
|
||||||
|
fn despawn() {} // Implemented elsewhere
|
||||||
|
fn teardown() {} // Implemented elsewhere
|
||||||
|
fn spawn_player() {} // Implemented elsewhere
|
||||||
|
```
|
||||||
|
|
||||||
|
`despawn` can be implemented as:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn despawn(In((entity,)): In<(BevyEntity,)>, mut commands: Commands) {
|
||||||
|
commands.entity(entity.0).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {} // Implemented elsewhere
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementation of `spawn_player` has been left out as an exercise for the reader.
|
||||||
3
book/src/workflow/workflow.md
Normal file
3
book/src/workflow/workflow.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Workflow
|
||||||
|
|
||||||
|
Demonstration of useful approaches when working with bevy_scriptum.
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_script_function(
|
|
||||||
String::from("get_name"),
|
|
||||||
|In((entity,)): In<(Entity,)>, names: Query<&Name>| {
|
|
||||||
names.get(entity).unwrap().to_string()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|
||||||
commands.spawn((
|
|
||||||
Name::from("MyEntityName"),
|
|
||||||
Script::new(assets_server.load("examples/current_entity.rhai")),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script, ScriptingRuntime};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_script_function(String::from("hello_bevy"), || {
|
|
||||||
println!("hello bevy, called from script");
|
|
||||||
})
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct MyType {
|
|
||||||
my_field: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut scripting_runtime: ResMut<ScriptingRuntime>,
|
|
||||||
assets_server: Res<AssetServer>,
|
|
||||||
) {
|
|
||||||
let engine = scripting_runtime.engine_mut();
|
|
||||||
|
|
||||||
engine
|
|
||||||
.register_type_with_name::<MyType>("MyType")
|
|
||||||
// Register a method on MyType
|
|
||||||
.register_fn("my_method", |my_type_instance: &mut MyType| {
|
|
||||||
my_type_instance.my_field
|
|
||||||
})
|
|
||||||
// Register a "constructor" for MyType
|
|
||||||
.register_fn("new_my_type", || MyType { my_field: 42 });
|
|
||||||
|
|
||||||
commands.spawn(Script::new(assets_server.load("examples/custom_type.rhai")));
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
struct Player;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_script_function(
|
|
||||||
String::from("print_player_names"),
|
|
||||||
|players: Query<&Name, With<Player>>| {
|
|
||||||
for player in &players {
|
|
||||||
println!("player name: {}", player);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|
||||||
commands.spawn((Player, Name::new("John")));
|
|
||||||
commands.spawn((Player, Name::new("Mary")));
|
|
||||||
commands.spawn((Player, Name::new("Alice")));
|
|
||||||
|
|
||||||
commands.spawn(Script::new(assets_server.load("examples/ecs.rhai")));
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|
||||||
commands.spawn(Script::new(
|
|
||||||
assets_server.load("examples/entity_variable.rhai"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
|
||||||
use rhai::ImmutableString;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_script_function(String::from("fun_without_params"), || {
|
|
||||||
println!("called without params");
|
|
||||||
})
|
|
||||||
.add_script_function(
|
|
||||||
String::from("fun_with_string_param"),
|
|
||||||
|In((x,)): In<(ImmutableString,)>| {
|
|
||||||
println!("called with string: '{}'", x);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.add_script_function(
|
|
||||||
String::from("fun_with_i64_param"),
|
|
||||||
|In((x,)): In<(i64,)>| {
|
|
||||||
println!("called with i64: {}", x);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.add_script_function(
|
|
||||||
String::from("fun_with_multiple_params"),
|
|
||||||
|In((x, y)): In<(i64, ImmutableString)>| {
|
|
||||||
println!("called with i64: {} and string: '{}'", x, y);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.add_script_function(
|
|
||||||
String::from("fun_with_i64_and_array_param"),
|
|
||||||
|In((x, y)): In<(i64, rhai::Array)>| {
|
|
||||||
println!(
|
|
||||||
"called with i64: {} and dynamically typed array: '{:?}'",
|
|
||||||
x, y
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|
||||||
commands.spawn(Script::new(
|
|
||||||
assets_server.load("examples/function_params.rhai"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_script_function(String::from("hello_bevy"), || {
|
|
||||||
println!("hello bevy, called from script");
|
|
||||||
})
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|
||||||
commands.spawn(Script::new(assets_server.load("examples/hello_world.rhai")));
|
|
||||||
}
|
|
||||||
50
examples/lua/call_function_from_rust.rs
Normal file
50
examples/lua/call_function_from_rust.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use bevy::{app::AppExit, ecs::event::ManualEventReader, prelude::*};
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
// This is just needed for headless console app, not needed for a regular bevy game
|
||||||
|
// that uses a winit window
|
||||||
|
.set_runner(move |mut app: App| {
|
||||||
|
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||||
|
loop {
|
||||||
|
if let Some(app_exit_events) = app.world.get_resource_mut::<Events<AppExit>>() {
|
||||||
|
if app_exit_event_reader
|
||||||
|
.read(&app_exit_events)
|
||||||
|
.last()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.update();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.add_systems(Update, call_lua_on_update_from_rust)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("quit"), |mut exit: EventWriter<AppExit>| {
|
||||||
|
exit.send(AppExit);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/call_function_from_rust.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_lua_on_update_from_rust(
|
||||||
|
mut scripted_entities: Query<(Entity, &mut LuaScriptData)>,
|
||||||
|
scripting_runtime: ResMut<LuaRuntime>,
|
||||||
|
) {
|
||||||
|
for (entity, mut script_data) in &mut scripted_entities {
|
||||||
|
scripting_runtime
|
||||||
|
.call_fn("on_update", &mut script_data, entity, ())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
examples/lua/current_entity.rs
Normal file
25
examples/lua/current_entity.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("get_name"),
|
||||||
|
|In((BevyEntity(entity),)): In<(BevyEntity,)>, names: Query<&Name>| {
|
||||||
|
names.get(entity).unwrap().to_string()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((
|
||||||
|
Name::from("MyEntityName"),
|
||||||
|
Script::<LuaScript>::new(assets_server.load("examples/lua/current_entity.lua")),
|
||||||
|
));
|
||||||
|
}
|
||||||
50
examples/lua/custom_type.rs
Normal file
50
examples/lua/custom_type.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
use mlua::{UserData, UserDataMethods};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("hello_bevy"), || {
|
||||||
|
println!("hello bevy, called from script");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MyType {
|
||||||
|
my_field: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for MyType {}
|
||||||
|
|
||||||
|
fn startup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut scripting_runtime: ResMut<LuaRuntime>,
|
||||||
|
assets_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
scripting_runtime.with_engine_mut(|engine| {
|
||||||
|
engine
|
||||||
|
.register_userdata_type::<MyType>(|typ| {
|
||||||
|
// Register a method on MyType
|
||||||
|
typ.add_method("my_method", |_, my_type_instance: &MyType, ()| {
|
||||||
|
Ok(my_type_instance.my_field)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Register a "constructor" for MyType
|
||||||
|
let my_type_constructor = engine
|
||||||
|
.create_function(|_, ()| Ok(MyType { my_field: 42 }))
|
||||||
|
.unwrap();
|
||||||
|
engine.globals().set("MyType", my_type_constructor).unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/custom_type.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
33
examples/lua/ecs.rs
Normal file
33
examples/lua/ecs.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Player;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("print_player_names"),
|
||||||
|
|players: Query<&Name, With<Player>>| {
|
||||||
|
for player in &players {
|
||||||
|
println!("player name: {}", player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Player, Name::new("John")));
|
||||||
|
commands.spawn((Player, Name::new("Mary")));
|
||||||
|
commands.spawn((Player, Name::new("Alice")));
|
||||||
|
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/ecs.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
17
examples/lua/entity_variable.rs
Normal file
17
examples/lua/entity_variable.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
use bevy_scriptum::{prelude::*, BuildScriptingRuntime};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|_| {})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/entity_variable.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
59
examples/lua/function_params.rs
Normal file
59
examples/lua/function_params.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime
|
||||||
|
.add_function(String::from("fun_without_params"), || {
|
||||||
|
println!("called without params");
|
||||||
|
})
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_string_param"),
|
||||||
|
|In((x,)): In<(String,)>| {
|
||||||
|
println!("called with string: '{}'", x);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_i64_param"),
|
||||||
|
|In((x,)): In<(i64,)>| {
|
||||||
|
println!("called with i64: {}", x);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_multiple_params"),
|
||||||
|
|In((x, y)): In<(i64, String)>| {
|
||||||
|
println!("called with i64: {} and string: '{}'", x, y);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_i64_and_array_param"),
|
||||||
|
|In((x, y)): In<(i64, mlua::RegistryKey)>, runtime: Res<LuaRuntime>| {
|
||||||
|
runtime.with_engine(|engine| {
|
||||||
|
println!(
|
||||||
|
"called with i64: {} and dynamically typed array: [{:?}]",
|
||||||
|
x,
|
||||||
|
engine
|
||||||
|
.registry_value::<mlua::Table>(&y)
|
||||||
|
.unwrap()
|
||||||
|
.pairs::<usize, mlua::Value>()
|
||||||
|
.map(|pair| pair.unwrap())
|
||||||
|
.map(|(_, v)| format!("{:?}", v))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(",")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/function_params.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
21
examples/lua/hello_world.rs
Normal file
21
examples/lua/hello_world.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("hello_bevy"), || {
|
||||||
|
println!("hello bevy, called from script");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/hello_world.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
23
examples/lua/non_closure_system.rs
Normal file
23
examples/lua/non_closure_system.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("hello_bevy"), hello_bevy_callback_system);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/hello_world.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hello_bevy_callback_system() {
|
||||||
|
println!("hello bevy, called from script");
|
||||||
|
}
|
||||||
26
examples/lua/promises.rs
Normal file
26
examples/lua/promises.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Player;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<LuaRuntime>(|builder| {
|
||||||
|
builder.add_function(
|
||||||
|
String::from("get_player_name"),
|
||||||
|
|player_names: Query<&Name, With<Player>>| player_names.single().to_string(),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Player, Name::new("John")));
|
||||||
|
commands.spawn(Script::<LuaScript>::new(
|
||||||
|
assets_server.load("examples/lua/promises.lua"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use bevy::{app::AppExit, ecs::event::ManualEventReader, prelude::*};
|
use bevy::{app::AppExit, ecs::event::ManualEventReader, prelude::*};
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
#[derive(Component)]
|
|
||||||
struct Comp;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
@ -24,10 +22,11 @@ fn main() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_systems(Startup, startup)
|
.add_systems(Startup, startup)
|
||||||
.add_systems(Update, print_entity_names_and_quit)
|
.add_systems(Update, print_entity_names_and_quit)
|
||||||
.add_script_function(String::from("spawn_entity"), spawn_entity)
|
.add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("spawn_entity"), spawn_entity);
|
||||||
|
})
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,8 +35,8 @@ fn spawn_entity(mut commands: Commands) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
commands.spawn((Script::new(
|
commands.spawn((Script::<LuaScript>::new(
|
||||||
assets_server.load("examples/side_effects.rhai"),
|
assets_server.load("examples/lua/side_effects.lua"),
|
||||||
),));
|
),));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_script_function(String::from("hello_bevy"), hello_bevy_callback_system)
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|
||||||
commands.spawn(Script::new(assets_server.load("examples/hello_world.rhai")));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hello_bevy_callback_system() {
|
|
||||||
println!("hello bevy, called from script");
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy_scriptum::{prelude::*, Script};
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
struct Player;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.add_plugins(DefaultPlugins)
|
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_script_function(
|
|
||||||
String::from("get_player_name"),
|
|
||||||
|player_names: Query<&Name, With<Player>>| player_names.single().to_string(),
|
|
||||||
)
|
|
||||||
.add_systems(Startup, startup)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|
||||||
commands.spawn((Player, Name::new("John")));
|
|
||||||
commands.spawn(Script::new(assets_server.load("examples/promises.rhai")));
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use bevy::{app::AppExit, ecs::event::ManualEventReader, prelude::*};
|
use bevy::{app::AppExit, ecs::event::ManualEventReader, prelude::*};
|
||||||
use bevy_scriptum::{prelude::*, Script, ScriptData, ScriptingRuntime};
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
|
|
@ -21,24 +22,25 @@ fn main() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_plugins(ScriptingPlugin)
|
|
||||||
.add_systems(Startup, startup)
|
.add_systems(Startup, startup)
|
||||||
.add_systems(Update, call_rhai_on_update_from_rust)
|
.add_systems(Update, call_rhai_on_update_from_rust)
|
||||||
.add_script_function(String::from("quit"), |mut exit: EventWriter<AppExit>| {
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
exit.send(AppExit);
|
runtime.add_function(String::from("quit"), |mut exit: EventWriter<AppExit>| {
|
||||||
|
exit.send(AppExit);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
commands.spawn(Script::new(
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
assets_server.load("examples/call_function_from_rust.rhai"),
|
assets_server.load("examples/rhai/call_function_from_rust.rhai"),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call_rhai_on_update_from_rust(
|
fn call_rhai_on_update_from_rust(
|
||||||
mut scripted_entities: Query<(Entity, &mut ScriptData)>,
|
mut scripted_entities: Query<(Entity, &mut RhaiScriptData)>,
|
||||||
mut scripting_runtime: ResMut<ScriptingRuntime>,
|
scripting_runtime: ResMut<RhaiRuntime>,
|
||||||
) {
|
) {
|
||||||
for (entity, mut script_data) in &mut scripted_entities {
|
for (entity, mut script_data) in &mut scripted_entities {
|
||||||
scripting_runtime
|
scripting_runtime
|
||||||
25
examples/rhai/current_entity.rs
Normal file
25
examples/rhai/current_entity.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("get_name"),
|
||||||
|
|In((entity,)): In<(Entity,)>, names: Query<&Name>| {
|
||||||
|
names.get(entity).unwrap().to_string()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((
|
||||||
|
Name::from("MyEntityName"),
|
||||||
|
Script::<RhaiScript>::new(assets_server.load("examples/rhai/current_entity.rhai")),
|
||||||
|
));
|
||||||
|
}
|
||||||
41
examples/rhai/custom_type.rs
Normal file
41
examples/rhai/custom_type.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("hello_bevy"), || {
|
||||||
|
println!("hello bevy, called from script");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct MyType {
|
||||||
|
my_field: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut scripting_runtime: ResMut<RhaiRuntime>,
|
||||||
|
assets_server: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
scripting_runtime.with_engine_mut(|engine| {
|
||||||
|
engine
|
||||||
|
.register_type_with_name::<MyType>("MyType")
|
||||||
|
// Register a method on MyType
|
||||||
|
.register_fn("my_method", |my_type_instance: &mut MyType| {
|
||||||
|
my_type_instance.my_field
|
||||||
|
})
|
||||||
|
// Register a "constructor" for MyType
|
||||||
|
.register_fn("new_my_type", || MyType { my_field: 42 });
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/custom_type.rhai"),
|
||||||
|
));
|
||||||
|
}
|
||||||
33
examples/rhai/ecs.rs
Normal file
33
examples/rhai/ecs.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Player;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
|
runtime.add_function(
|
||||||
|
String::from("print_player_names"),
|
||||||
|
|players: Query<&Name, With<Player>>| {
|
||||||
|
for player in &players {
|
||||||
|
println!("player name: {}", player);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Player, Name::new("John")));
|
||||||
|
commands.spawn((Player, Name::new("Mary")));
|
||||||
|
commands.spawn((Player, Name::new("Alice")));
|
||||||
|
|
||||||
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/ecs.rhai"),
|
||||||
|
));
|
||||||
|
}
|
||||||
17
examples/rhai/entity_variable.rs
Normal file
17
examples/rhai/entity_variable.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
use bevy_scriptum::{prelude::*, BuildScriptingRuntime};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|_| {})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/entity_variable.rhai"),
|
||||||
|
));
|
||||||
|
}
|
||||||
50
examples/rhai/function_params.rs
Normal file
50
examples/rhai/function_params.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
use rhai::ImmutableString;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
|
runtime
|
||||||
|
.add_function(String::from("fun_without_params"), || {
|
||||||
|
println!("called without params");
|
||||||
|
})
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_string_param"),
|
||||||
|
|In((x,)): In<(ImmutableString,)>| {
|
||||||
|
println!("called with string: '{}'", x);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_i64_param"),
|
||||||
|
|In((x,)): In<(i64,)>| {
|
||||||
|
println!("called with i64: {}", x);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_multiple_params"),
|
||||||
|
|In((x, y)): In<(i64, ImmutableString)>| {
|
||||||
|
println!("called with i64: {} and string: '{}'", x, y);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.add_function(
|
||||||
|
String::from("fun_with_i64_and_array_param"),
|
||||||
|
|In((x, y)): In<(i64, rhai::Array)>| {
|
||||||
|
println!(
|
||||||
|
"called with i64: {} and dynamically typed array: '{:?}'",
|
||||||
|
x, y
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/function_params.rhai"),
|
||||||
|
));
|
||||||
|
}
|
||||||
21
examples/rhai/hello_world.rs
Normal file
21
examples/rhai/hello_world.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("hello_bevy"), || {
|
||||||
|
println!("hello bevy, called from script");
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/hello_world.rhai"),
|
||||||
|
));
|
||||||
|
}
|
||||||
23
examples/rhai/non_closure_system.rs
Normal file
23
examples/rhai/non_closure_system.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("hello_bevy"), hello_bevy_callback_system);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/hello_world.rhai"),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hello_bevy_callback_system() {
|
||||||
|
println!("hello bevy, called from script");
|
||||||
|
}
|
||||||
26
examples/rhai/promises.rs
Normal file
26
examples/rhai/promises.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Player;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_scripting::<RhaiRuntime>(|builder| {
|
||||||
|
builder.add_function(
|
||||||
|
String::from("get_player_name"),
|
||||||
|
|player_names: Query<&Name, With<Player>>| player_names.single().to_string(),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Player, Name::new("John")));
|
||||||
|
commands.spawn(Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/promises.rhai"),
|
||||||
|
));
|
||||||
|
}
|
||||||
50
examples/rhai/side_effects.rs
Normal file
50
examples/rhai/side_effects.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use bevy::{app::AppExit, ecs::event::ManualEventReader, prelude::*};
|
||||||
|
use bevy_scriptum::prelude::*;
|
||||||
|
use bevy_scriptum::runtimes::rhai::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
// This is just needed for headless console app, not needed for a regular bevy game
|
||||||
|
// that uses a winit window
|
||||||
|
.set_runner(move |mut app: App| {
|
||||||
|
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
||||||
|
loop {
|
||||||
|
if let Some(app_exit_events) = app.world.get_resource_mut::<Events<AppExit>>() {
|
||||||
|
if app_exit_event_reader
|
||||||
|
.read(&app_exit_events)
|
||||||
|
.last()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.update();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, startup)
|
||||||
|
.add_systems(Update, print_entity_names_and_quit)
|
||||||
|
.add_scripting::<RhaiRuntime>(|runtime| {
|
||||||
|
runtime.add_function(String::from("spawn_entity"), spawn_entity);
|
||||||
|
})
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_entity(mut commands: Commands) {
|
||||||
|
commands.spawn(Name::new("SpawnedEntity"));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||||
|
commands.spawn((Script::<RhaiScript>::new(
|
||||||
|
assets_server.load("examples/rhai/side_effects.rhai"),
|
||||||
|
),));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_entity_names_and_quit(query: Query<&Name>, mut exit: EventWriter<AppExit>) {
|
||||||
|
if !query.is_empty() {
|
||||||
|
for e in &query {
|
||||||
|
println!("{}", e);
|
||||||
|
}
|
||||||
|
exit.send(AppExit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,31 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{
|
||||||
asset::{io::Reader, Asset, AssetLoader, AsyncReadExt as _, LoadContext},
|
asset::{io::Reader, Asset, AssetLoader, AsyncReadExt as _, LoadContext},
|
||||||
reflect::TypePath,
|
|
||||||
utils::BoxedFuture,
|
utils::BoxedFuture,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
/// A script that can be loaded by the [crate::ScriptingPlugin].
|
/// A loader for script assets.
|
||||||
#[derive(Asset, Debug, Deserialize, TypePath)]
|
pub struct ScriptLoader<A: Asset + From<String>> {
|
||||||
pub struct RhaiScript(pub String);
|
_phantom_data: PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
/// A loader for [RhaiScript] assets.
|
impl<A: Asset + From<String>> Default for ScriptLoader<A> {
|
||||||
#[derive(Default)]
|
fn default() -> Self {
|
||||||
pub struct RhaiScriptLoader;
|
Self {
|
||||||
|
_phantom_data: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AssetLoader for RhaiScriptLoader {
|
/// Allows providing an allow-list for extensions of AssetLoader for a Script
|
||||||
type Asset = RhaiScript;
|
/// asset
|
||||||
|
pub trait GetExtensions {
|
||||||
|
fn extensions() -> &'static [&'static str];
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Asset + From<String> + GetExtensions> AssetLoader for ScriptLoader<A> {
|
||||||
|
type Asset = A;
|
||||||
type Settings = ();
|
type Settings = ();
|
||||||
type Error = anyhow::Error;
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
|
@ -23,17 +34,18 @@ impl AssetLoader for RhaiScriptLoader {
|
||||||
reader: &'a mut Reader,
|
reader: &'a mut Reader,
|
||||||
_settings: &'a Self::Settings,
|
_settings: &'a Self::Settings,
|
||||||
_load_context: &'a mut LoadContext,
|
_load_context: &'a mut LoadContext,
|
||||||
) -> BoxedFuture<'a, anyhow::Result<RhaiScript, anyhow::Error>> {
|
) -> BoxedFuture<'a, anyhow::Result<A, anyhow::Error>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
reader.read_to_end(&mut bytes).await?;
|
reader.read_to_end(&mut bytes).await?;
|
||||||
|
|
||||||
let rhai_script = RhaiScript(String::from_utf8(bytes.to_vec())?);
|
let script_text = String::from_utf8(bytes.to_vec())?;
|
||||||
|
let rhai_script: A = script_text.into();
|
||||||
Ok(rhai_script)
|
Ok(rhai_script)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extensions(&self) -> &[&str] {
|
fn extensions(&self) -> &[&str] {
|
||||||
&["rhai"]
|
A::extensions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
src/callback.rs
108
src/callback.rs
|
|
@ -2,64 +2,80 @@ use bevy::prelude::*;
|
||||||
use core::any::TypeId;
|
use core::any::TypeId;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use rhai::{Dynamic, Variant};
|
use crate::{promise::Promise, Runtime};
|
||||||
|
|
||||||
use crate::promise::Promise;
|
|
||||||
|
|
||||||
/// A system that can be used to call a script function.
|
/// A system that can be used to call a script function.
|
||||||
pub struct CallbackSystem {
|
pub struct CallbackSystem<R: Runtime> {
|
||||||
pub(crate) system: Box<dyn System<In = Vec<Dynamic>, Out = Dynamic>>,
|
pub(crate) system: Box<dyn System<In = Vec<R::Value>, Out = R::Value>>,
|
||||||
pub(crate) arg_types: Vec<TypeId>,
|
pub(crate) arg_types: Vec<TypeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct FunctionCallEvent {
|
pub(crate) struct FunctionCallEvent<C: Send, V: Send> {
|
||||||
pub(crate) params: Vec<Dynamic>,
|
pub(crate) params: Vec<V>,
|
||||||
pub(crate) promise: Promise,
|
pub(crate) promise: Promise<C, V>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Calls<C, V> = Arc<Mutex<Vec<FunctionCallEvent<C, V>>>>;
|
||||||
|
|
||||||
/// A struct representing a Bevy system that can be called from a script.
|
/// A struct representing a Bevy system that can be called from a script.
|
||||||
#[derive(Clone)]
|
pub(crate) struct Callback<R: Runtime> {
|
||||||
pub(crate) struct Callback {
|
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
pub(crate) system: Arc<Mutex<CallbackSystem>>,
|
pub(crate) system: Arc<Mutex<CallbackSystem<R>>>,
|
||||||
pub(crate) calls: Arc<Mutex<Vec<FunctionCallEvent>>>,
|
pub(crate) calls: Calls<R::CallContext, R::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallbackSystem {
|
impl<R: Runtime> Clone for Callback<R> {
|
||||||
pub(crate) fn call(&mut self, call: &FunctionCallEvent, world: &mut World) -> Dynamic {
|
fn clone(&self) -> Self {
|
||||||
|
Callback {
|
||||||
|
name: self.name.clone(),
|
||||||
|
system: self.system.clone(),
|
||||||
|
calls: self.calls.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Runtime> CallbackSystem<R> {
|
||||||
|
pub(crate) fn call(
|
||||||
|
&mut self,
|
||||||
|
call: &FunctionCallEvent<R::CallContext, R::Value>,
|
||||||
|
world: &mut World,
|
||||||
|
) -> R::Value {
|
||||||
self.system.run(call.params.clone(), world)
|
self.system.run(call.params.clone(), world)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait that alllows to convert a script callback function into a Bevy [`System`].
|
/// Allows converting to a wrapper type that the library uses internally for data
|
||||||
pub trait RegisterCallbackFunction<
|
pub(crate) trait IntoRuntimeValueWithEngine<'a, V, R: Runtime> {
|
||||||
Out,
|
fn into_runtime_value_with_engine(value: V, engine: &'a R::RawEngine) -> R::Value;
|
||||||
Marker,
|
|
||||||
A: 'static,
|
|
||||||
const N: usize,
|
|
||||||
const X: bool,
|
|
||||||
R: 'static,
|
|
||||||
const F: bool,
|
|
||||||
Args,
|
|
||||||
>: IntoSystem<Args, Out, Marker>
|
|
||||||
{
|
|
||||||
/// Convert this function into a [CallbackSystem].
|
|
||||||
#[must_use]
|
|
||||||
fn into_callback_system(self, world: &mut World) -> CallbackSystem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Out, FN, Marker> RegisterCallbackFunction<Out, Marker, (), 1, false, Dynamic, false, ()> for FN
|
/// Allows converting from a wrapper type that the library uses internally for data to underlying
|
||||||
|
/// concrete type.
|
||||||
|
pub(crate) trait FromRuntimeValueWithEngine<'a, R: Runtime> {
|
||||||
|
fn from_runtime_value_with_engine(value: R::Value, engine: &'a R::RawEngine) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait that alllows to convert a script callback function into a Bevy [`System`].
|
||||||
|
pub trait IntoCallbackSystem<R: Runtime, In, Out, Marker>: IntoSystem<In, Out, Marker> {
|
||||||
|
/// Convert this function into a [CallbackSystem].
|
||||||
|
#[must_use]
|
||||||
|
fn into_callback_system(self, world: &mut World) -> CallbackSystem<R>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Runtime, Out, FN, Marker> IntoCallbackSystem<R, (), Out, Marker> for FN
|
||||||
where
|
where
|
||||||
FN: IntoSystem<(), Out, Marker>,
|
FN: IntoSystem<(), Out, Marker>,
|
||||||
Out: Sync + Variant + Clone,
|
Out: for<'a> IntoRuntimeValueWithEngine<'a, Out, R>,
|
||||||
{
|
{
|
||||||
fn into_callback_system(self, world: &mut World) -> CallbackSystem {
|
fn into_callback_system(self, world: &mut World) -> CallbackSystem<R> {
|
||||||
let mut inner_system = IntoSystem::into_system(self);
|
let mut inner_system = IntoSystem::into_system(self);
|
||||||
inner_system.initialize(world);
|
inner_system.initialize(world);
|
||||||
let system_fn = move |_args: In<Vec<Dynamic>>, world: &mut World| {
|
let system_fn = move |_args: In<Vec<R::Value>>, world: &mut World| {
|
||||||
let result = inner_system.run((), world);
|
let result = inner_system.run((), world);
|
||||||
inner_system.apply_deferred(world);
|
inner_system.apply_deferred(world);
|
||||||
Dynamic::from(result)
|
let mut runtime = world.get_resource_mut::<R>().expect("No runtime resource");
|
||||||
|
runtime
|
||||||
|
.with_engine_mut(move |engine| Out::into_runtime_value_with_engine(result, engine))
|
||||||
};
|
};
|
||||||
let system = IntoSystem::into_system(system_fn);
|
let system = IntoSystem::into_system(system_fn);
|
||||||
CallbackSystem {
|
CallbackSystem {
|
||||||
|
|
@ -71,23 +87,29 @@ where
|
||||||
|
|
||||||
macro_rules! impl_tuple {
|
macro_rules! impl_tuple {
|
||||||
($($idx:tt $t:tt),+) => {
|
($($idx:tt $t:tt),+) => {
|
||||||
impl<$($t,)+ Out, FN, Marker> RegisterCallbackFunction<Out, Marker, ($($t,)+), 1, false, Dynamic, false, ($($t,)+)>
|
impl<RN: Runtime, $($t,)+ Out, FN, Marker> IntoCallbackSystem<RN, ($($t,)+), Out, Marker>
|
||||||
for FN
|
for FN
|
||||||
where
|
where
|
||||||
FN: IntoSystem<($($t,)+), Out, Marker>,
|
FN: IntoSystem<($($t,)+), Out, Marker>,
|
||||||
Out: Sync + Variant + Clone,
|
Out: for<'a> IntoRuntimeValueWithEngine<'a, Out, RN>,
|
||||||
$($t: 'static + Clone,)+
|
$($t: 'static + for<'a> FromRuntimeValueWithEngine<'a, RN>,)+
|
||||||
{
|
{
|
||||||
fn into_callback_system(self, world: &mut World) -> CallbackSystem {
|
fn into_callback_system(self, world: &mut World) -> CallbackSystem<RN> {
|
||||||
let mut inner_system = IntoSystem::into_system(self);
|
let mut inner_system = IntoSystem::into_system(self);
|
||||||
inner_system.initialize(world);
|
inner_system.initialize(world);
|
||||||
let system_fn = move |args: In<Vec<Dynamic>>, world: &mut World| {
|
let system_fn = move |args: In<Vec<RN::Value>>, world: &mut World| {
|
||||||
let args = (
|
let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource");
|
||||||
$(args.0.get($idx).unwrap().clone_cast::<$t>(), )+
|
let args = runtime.with_engine_mut(move |engine| {
|
||||||
);
|
(
|
||||||
|
$($t::from_runtime_value_with_engine(args.get($idx).expect(&format!("Failed to get function argument for index {}", $idx)).clone(), engine), )+
|
||||||
|
)
|
||||||
|
});
|
||||||
let result = inner_system.run(args, world);
|
let result = inner_system.run(args, world);
|
||||||
inner_system.apply_deferred(world);
|
inner_system.apply_deferred(world);
|
||||||
Dynamic::from(result)
|
let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource");
|
||||||
|
runtime.with_engine_mut(move |engine| {
|
||||||
|
Out::into_runtime_value_with_engine(result, engine)
|
||||||
|
})
|
||||||
};
|
};
|
||||||
let system = IntoSystem::into_system(system_fn);
|
let system = IntoSystem::into_system(system_fn);
|
||||||
CallbackSystem {
|
CallbackSystem {
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,14 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use super::assets::RhaiScript;
|
|
||||||
|
|
||||||
/// A component that represents a script.
|
/// A component that represents a script.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Script {
|
pub struct Script<A: Asset> {
|
||||||
pub script: Handle<RhaiScript>,
|
pub script: Handle<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that represents the data of a script. It stores the [rhai::Scope](basically the state of the script, any declared variable etc.)
|
impl<A: Asset> Script<A> {
|
||||||
/// and [rhai::AST] which is a cached AST representation of the script.
|
/// Create a new script component from a handle to a [Script] obtained using [AssetServer].
|
||||||
#[derive(Component)]
|
pub fn new(script: Handle<A>) -> Self {
|
||||||
pub struct ScriptData {
|
|
||||||
pub(crate) scope: rhai::Scope<'static>,
|
|
||||||
pub(crate) ast: rhai::AST,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Script {
|
|
||||||
/// Create a new script component from a handle to a [RhaiScript] obtained using [AssetServer].
|
|
||||||
pub fn new(script: Handle<RhaiScript>) -> Self {
|
|
||||||
Self { script }
|
Self { script }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
433
src/lib.rs
433
src/lib.rs
|
|
@ -1,7 +1,12 @@
|
||||||
//! bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game logic in a scripting language.
|
//! bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game logic in a scripting language.
|
||||||
//! Currently, only [Rhai](https://rhai.rs/) is supported, but more languages may be added in the future.
|
//! Currently [Rhai](https://rhai.rs/) and [Lua](https://lua.org/) are supported, but more languages may be added in the future.
|
||||||
//!
|
//!
|
||||||
//! It's main advantages include:
|
//! Everything you need to know to get started with using this library is contained in the
|
||||||
|
//! [bevy_scriptum book](https://link-to-book.com)
|
||||||
|
//!
|
||||||
|
//! API docs are available in [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/)
|
||||||
|
//!
|
||||||
|
//! bevy_scriptum's main advantages include:
|
||||||
//! - low-boilerplate
|
//! - low-boilerplate
|
||||||
//! - easy to use
|
//! - easy to use
|
||||||
//! - asynchronicity with a promise-based API
|
//! - asynchronicity with a promise-based API
|
||||||
|
|
@ -14,17 +19,20 @@
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use bevy::prelude::*;
|
//! use bevy::prelude::*;
|
||||||
//! use bevy_scriptum::prelude::*;
|
//! use bevy_scriptum::prelude::*;
|
||||||
|
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! App::new()
|
//! App::new()
|
||||||
//! .add_plugins(DefaultPlugins)
|
//! .add_plugins(DefaultPlugins)
|
||||||
//! .add_plugins(ScriptingPlugin::default())
|
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||||
//! .add_script_function(String::from("hello_bevy"), || {
|
//! runtime.add_function(String::from("hello_bevy"), || {
|
||||||
//! println!("hello bevy, called from script");
|
//! println!("hello bevy, called from script");
|
||||||
//! });
|
//! });
|
||||||
|
//! })
|
||||||
|
//! .run();
|
||||||
//! ```
|
//! ```
|
||||||
//! And you can call them in your scripts like this:
|
//! And you can call them in your scripts like this:
|
||||||
//! ```rhai
|
//! ```lua
|
||||||
//! hello_bevy();
|
//! hello_bevy()
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Every callback function that you expose to the scripting language is also a Bevy system, so you can easily query and mutate ECS components and resources just like you would in a regular Bevy system:
|
//! Every callback function that you expose to the scripting language is also a Bevy system, so you can easily query and mutate ECS components and resources just like you would in a regular Bevy system:
|
||||||
|
|
@ -32,42 +40,47 @@
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use bevy::prelude::*;
|
//! use bevy::prelude::*;
|
||||||
//! use bevy_scriptum::prelude::*;
|
//! use bevy_scriptum::prelude::*;
|
||||||
|
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! #[derive(Component)]
|
//! #[derive(Component)]
|
||||||
//! struct Player;
|
//! struct Player;
|
||||||
//!
|
//!
|
||||||
//! App::new()
|
//! App::new()
|
||||||
//! .add_plugins(DefaultPlugins)
|
//! .add_plugins(DefaultPlugins)
|
||||||
//! .add_plugins(ScriptingPlugin::default())
|
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||||
//! .add_script_function(
|
//! runtime.add_function(
|
||||||
//! String::from("print_player_names"),
|
//! String::from("print_player_names"),
|
||||||
//! |players: Query<&Name, With<Player>>| {
|
//! |players: Query<&Name, With<Player>>| {
|
||||||
//! for player in &players {
|
//! for player in &players {
|
||||||
//! println!("player name: {}", player);
|
//! println!("player name: {}", player);
|
||||||
//! }
|
//! }
|
||||||
//! },
|
//! },
|
||||||
//! );
|
//! );
|
||||||
|
//! })
|
||||||
|
//! .run();
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! You can also pass arguments to your callback functions, just like you would in a regular Bevy system - using `In` structs with tuples:
|
//! You can also pass arguments to your callback functions, just like you would in a regular Bevy system - using `In` structs with tuples:
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use bevy::prelude::*;
|
//! use bevy::prelude::*;
|
||||||
//! use bevy_scriptum::prelude::*;
|
//! use bevy_scriptum::prelude::*;
|
||||||
//! use rhai::ImmutableString;
|
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! App::new()
|
//! App::new()
|
||||||
//! .add_plugins(DefaultPlugins)
|
//! .add_plugins(DefaultPlugins)
|
||||||
//! .add_plugins(ScriptingPlugin::default())
|
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||||
//! .add_script_function(
|
//! runtime.add_function(
|
||||||
//! String::from("fun_with_string_param"),
|
//! String::from("fun_with_string_param"),
|
||||||
//! |In((x,)): In<(ImmutableString,)>| {
|
//! |In((x,)): In<(String,)>| {
|
||||||
//! println!("called with string: '{}'", x);
|
//! println!("called with string: '{}'", x);
|
||||||
//! },
|
//! },
|
||||||
//! );
|
//! );
|
||||||
|
//! })
|
||||||
|
//! .run();
|
||||||
//! ```
|
//! ```
|
||||||
//! which you can then call in your script like this:
|
//! which you can then call in your script like this:
|
||||||
//! ```rhai
|
//! ```lua
|
||||||
//! fun_with_string_param("Hello world!");
|
//! fun_with_string_param("Hello world!")
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! ## Usage
|
//! ## Usage
|
||||||
|
|
@ -76,65 +89,68 @@
|
||||||
//!
|
//!
|
||||||
//! ```toml
|
//! ```toml
|
||||||
//! [dependencies]
|
//! [dependencies]
|
||||||
//! bevy_scriptum = "0.2"
|
//! bevy_scriptum = { version = "0.5", features = ["lua"] }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! or execute `cargo add bevy_scriptum` from your project directory.
|
//! or execute `cargo add bevy_scriptum --features lua` from your project directory.
|
||||||
//!
|
|
||||||
//! Add the following to your `main.rs`:
|
|
||||||
//!
|
|
||||||
//! ```rust
|
|
||||||
//! use bevy::prelude::*;
|
|
||||||
//! use bevy_scriptum::prelude::*;
|
|
||||||
//!
|
|
||||||
//! App::new()
|
|
||||||
//! .add_plugins(DefaultPlugins)
|
|
||||||
//! .add_plugins(ScriptingPlugin::default())
|
|
||||||
//! .run();
|
|
||||||
//! ```
|
|
||||||
//!
|
//!
|
||||||
//! You can now start exposing functions to the scripting language. For example, you can expose a function that prints a message to the console:
|
//! You can now start exposing functions to the scripting language. For example, you can expose a function that prints a message to the console:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use rhai::ImmutableString;
|
|
||||||
//! use bevy::prelude::*;
|
//! use bevy::prelude::*;
|
||||||
//! use bevy_scriptum::prelude::*;
|
//! use bevy_scriptum::prelude::*;
|
||||||
|
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! App::new()
|
//! App::new()
|
||||||
//! .add_plugins(DefaultPlugins)
|
//! .add_plugins(DefaultPlugins)
|
||||||
//! .add_plugins(ScriptingPlugin::default())
|
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||||
//! .add_script_function(
|
//! runtime.add_function(
|
||||||
//! String::from("my_print"),
|
//! String::from("my_print"),
|
||||||
//! |In((x,)): In<(ImmutableString,)>| {
|
//! |In((x,)): In<(String,)>| {
|
||||||
//! println!("my_print: '{}'", x);
|
//! println!("my_print: '{}'", x);
|
||||||
//! },
|
//! },
|
||||||
//! );
|
//! );
|
||||||
|
//! })
|
||||||
|
//! .run();
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Then you can create a script file in `assets` directory called `script.rhai` that calls this function:
|
//! Then you can create a script file in `assets` directory called `script.lua` that calls this function:
|
||||||
//!
|
//!
|
||||||
//! ```rhai
|
//! ```lua
|
||||||
//! my_print("Hello world!");
|
//! my_print("Hello world!")
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! And spawn a `Script` component with a handle to a script source file`:
|
//! And spawn an entity with attached `Script` component with a handle to a script source file:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use bevy::prelude::*;
|
//! use bevy::prelude::*;
|
||||||
//! use bevy_scriptum::Script;
|
//! use bevy_scriptum::prelude::*;
|
||||||
|
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
//!
|
//!
|
||||||
//! App::new()
|
//! App::new()
|
||||||
|
//! .add_plugins(DefaultPlugins)
|
||||||
|
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
//! runtime.add_function(
|
||||||
|
//! String::from("my_print"),
|
||||||
|
//! |In((x,)): In<(String,)>| {
|
||||||
|
//! println!("my_print: '{}'", x);
|
||||||
|
//! },
|
||||||
|
//! );
|
||||||
|
//! })
|
||||||
//! .add_systems(Startup,|mut commands: Commands, asset_server: Res<AssetServer>| {
|
//! .add_systems(Startup,|mut commands: Commands, asset_server: Res<AssetServer>| {
|
||||||
//! commands.spawn(Script::new(asset_server.load("script.rhai")));
|
//! commands.spawn(Script::<LuaScript>::new(asset_server.load("script.lua")));
|
||||||
//! });
|
//! })
|
||||||
|
//! .run();
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! You should then see `my_print: 'Hello world!'` printed in your console.
|
||||||
|
//!
|
||||||
//! ## Provided examples
|
//! ## Provided examples
|
||||||
//!
|
//!
|
||||||
//! You can also try running provided examples by cloning this repository and running `cargo run --example <example_name>`. For example:
|
//! You can also try running provided examples by cloning this repository and running `cargo run --example <example_name>_<language_name>`. For example:
|
||||||
//!
|
//!
|
||||||
//! ```bash
|
//! ```bash
|
||||||
//! cargo run --example hello_world
|
//! cargo run --example hello_world_lua
|
||||||
//! ```
|
//! ```
|
||||||
//! The examples live in `examples` directory and their corresponding scripts live in `assets/examples` directory within the repository.
|
//! The examples live in `examples` directory and their corresponding scripts live in `assets/examples` directory within the repository.
|
||||||
//!
|
//!
|
||||||
|
|
@ -142,31 +158,46 @@
|
||||||
//!
|
//!
|
||||||
//! | bevy version | bevy_scriptum version |
|
//! | bevy version | bevy_scriptum version |
|
||||||
//! |--------------|----------------------|
|
//! |--------------|----------------------|
|
||||||
//! | 0.13 | 0.4 |
|
//! | 0.13 | 0.4-0.5 |
|
||||||
//! | 0.12 | 0.3 |
|
//! | 0.12 | 0.3 |
|
||||||
//! | 0.11 | 0.2 |
|
//! | 0.11 | 0.2 |
|
||||||
//! | 0.10 | 0.1 |
|
//! | 0.10 | 0.1 |
|
||||||
//!
|
//!
|
||||||
//! ## Promises - getting return values from scripts
|
//! ## Promises - getting return values from scripts
|
||||||
//!
|
//!
|
||||||
//! Every function called from script returns a promise that you can call `.then` with a callback function on. This callback function will be called when the promise is resolved, and will be passed the return value of the function called from script. For example:
|
//! Every function called from script returns a promise that you can call `:and_then` with a callback function on. This callback function will be called when the promise is resolved, and will be passed the return value of the function called from script. For example:
|
||||||
//!
|
//!
|
||||||
//! ```rhai
|
//! ```lua
|
||||||
//! get_player_name().then(|name| {
|
//! get_player_name():and_then(function(name)
|
||||||
//! print(name);
|
//! print(name)
|
||||||
//! });
|
//! end)
|
||||||
//! ```
|
//! ```
|
||||||
|
//! which will print out `John` when used with following exposed function:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use bevy::prelude::*;
|
||||||
|
//! use bevy_scriptum::prelude::*;
|
||||||
|
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||||
|
//!
|
||||||
|
//! App::new()
|
||||||
|
//! .add_plugins(DefaultPlugins)
|
||||||
|
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||||
|
//! runtime.add_function(String::from("get_player_name"), || String::from("John"));
|
||||||
|
//! });
|
||||||
|
//! ````
|
||||||
//!
|
//!
|
||||||
//! ## Access entity from script
|
//! ## Access entity from script
|
||||||
//!
|
//!
|
||||||
//! A variable called `entity` is automatically available to all scripts - it represents bevy entity that the `Script` component is attached to.
|
//! A variable called `entity` is automatically available to all scripts - it represents bevy entity that the `Script` component is attached to.
|
||||||
//! It exposes `.index()` method that returns bevy entity index.
|
//! It exposes `index` property that returns bevy entity index.
|
||||||
//! It is useful for accessing entity's components from scripts.
|
//! It is useful for accessing entity's components from scripts.
|
||||||
//! It can be used in the following way:
|
//! It can be used in the following way:
|
||||||
//! ```rhai
|
//! ```lua
|
||||||
//! print("Current entity index: " + entity.index());
|
//! print("Current entity index: " .. entity.index)
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! `entity` variable is currently not available within promise callbacks.
|
||||||
|
//!
|
||||||
//! ## Contributing
|
//! ## Contributing
|
||||||
//!
|
//!
|
||||||
//! Contributions are welcome! Feel free to open an issue or submit a pull request.
|
//! Contributions are welcome! Feel free to open an issue or submit a pull request.
|
||||||
|
|
@ -182,19 +213,27 @@ mod components;
|
||||||
mod promise;
|
mod promise;
|
||||||
mod systems;
|
mod systems;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
pub mod runtimes;
|
||||||
|
|
||||||
pub use crate::components::{Script, ScriptData};
|
pub use crate::components::Script;
|
||||||
pub use assets::RhaiScript;
|
use assets::GetExtensions;
|
||||||
|
use promise::Promise;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use std::{
|
||||||
use callback::{Callback, RegisterCallbackFunction};
|
any::TypeId,
|
||||||
use rhai::{CallFnOptions, Dynamic, Engine, EvalAltResult, FuncArgs, ParseError};
|
fmt::Debug,
|
||||||
use systems::{init_callbacks, init_engine, log_errors, process_calls};
|
hash::Hash,
|
||||||
|
marker::PhantomData,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use bevy::{app::MainScheduleOrder, ecs::schedule::ScheduleLabel, prelude::*};
|
||||||
|
use callback::{Callback, IntoCallbackSystem};
|
||||||
|
use systems::{init_callbacks, log_errors, process_calls};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
assets::RhaiScriptLoader,
|
assets::ScriptLoader,
|
||||||
systems::{process_new_scripts, reload_scripts},
|
systems::{process_new_scripts, reload_scripts},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -204,126 +243,178 @@ const ENTITY_VAR_NAME: &str = "entity";
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum ScriptingError {
|
pub enum ScriptingError {
|
||||||
#[error("script runtime error: {0}")]
|
#[error("script runtime error: {0}")]
|
||||||
RuntimeError(#[from] Box<EvalAltResult>),
|
RuntimeError(Box<dyn std::error::Error>),
|
||||||
#[error("script compilation error: {0}")]
|
#[error("script compilation error: {0}")]
|
||||||
CompileError(#[from] ParseError),
|
CompileError(Box<dyn std::error::Error>),
|
||||||
#[error("no runtime resource present")]
|
#[error("no runtime resource present")]
|
||||||
NoRuntimeResource,
|
NoRuntimeResource,
|
||||||
#[error("no settings resource present")]
|
#[error("no settings resource present")]
|
||||||
NoSettingsResource,
|
NoSettingsResource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
/// Trait that represents a scripting runtime/engine. In practice it is
|
||||||
pub struct ScriptingPlugin;
|
/// implemented for a scripint language interpreter and the implementor provides
|
||||||
|
/// function implementations for calling and registering functions within the interpreter.
|
||||||
|
pub trait Runtime: Resource + Default {
|
||||||
|
type Schedule: ScheduleLabel + Debug + Clone + Eq + Hash + Default;
|
||||||
|
type ScriptAsset: Asset + From<String> + GetExtensions;
|
||||||
|
type ScriptData: Component;
|
||||||
|
type CallContext: Send + Clone;
|
||||||
|
type Value: Send + Clone;
|
||||||
|
type RawEngine;
|
||||||
|
|
||||||
impl Plugin for ScriptingPlugin {
|
/// Provides mutable reference to raw scripting engine instance.
|
||||||
fn build(&self, app: &mut App) {
|
/// Can be used to directly interact with an interpreter to use interfaces
|
||||||
app.register_asset_loader(RhaiScriptLoader)
|
/// that bevy_scriptum does not provided adapters for.
|
||||||
.init_asset::<RhaiScript>()
|
fn with_engine_mut<T>(&mut self, f: impl FnOnce(&mut Self::RawEngine) -> T) -> T;
|
||||||
.init_resource::<Callbacks>()
|
|
||||||
.insert_resource(ScriptingRuntime::default())
|
|
||||||
.add_systems(Startup, init_engine.pipe(log_errors))
|
|
||||||
.add_systems(
|
|
||||||
Update,
|
|
||||||
(
|
|
||||||
reload_scripts,
|
|
||||||
process_calls.pipe(log_errors).after(process_new_scripts),
|
|
||||||
init_callbacks.pipe(log_errors),
|
|
||||||
process_new_scripts.pipe(log_errors).after(init_callbacks),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
/// Provides immutable reference to raw scripting engine instance.
|
||||||
pub struct ScriptingRuntime {
|
/// Can be used to directly interact with an interpreter to use interfaces
|
||||||
engine: Engine,
|
/// that bevy_scriptum does not provided adapters for.
|
||||||
}
|
fn with_engine<T>(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T;
|
||||||
|
|
||||||
impl ScriptingRuntime {
|
fn eval(
|
||||||
/// Get a mutable reference to the internal [rhai::Engine].
|
&self,
|
||||||
pub fn engine_mut(&mut self) -> &mut Engine {
|
script: &Self::ScriptAsset,
|
||||||
&mut self.engine
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call a function that is available in the scope of the script.
|
|
||||||
pub fn call_fn(
|
|
||||||
&mut self,
|
|
||||||
function_name: &str,
|
|
||||||
script_data: &mut ScriptData,
|
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
args: impl FuncArgs,
|
) -> Result<Self::ScriptData, ScriptingError>;
|
||||||
) -> Result<(), ScriptingError> {
|
|
||||||
let ast = script_data.ast.clone();
|
/// Registers a new function within the scripting engine. Provided callback
|
||||||
let scope = &mut script_data.scope;
|
/// function will be called when the function with provided name gets called
|
||||||
scope.push(ENTITY_VAR_NAME, entity);
|
/// in script.
|
||||||
let options = CallFnOptions::new().eval_ast(false);
|
fn register_fn(
|
||||||
let result =
|
&mut self,
|
||||||
self.engine
|
name: String,
|
||||||
.call_fn_with_options::<Dynamic>(options, scope, &ast, function_name, args);
|
arg_types: Vec<TypeId>,
|
||||||
scope.remove::<Entity>(ENTITY_VAR_NAME).unwrap();
|
f: impl Fn(
|
||||||
if let Err(err) = result {
|
Self::CallContext,
|
||||||
match *err {
|
Vec<Self::Value>,
|
||||||
rhai::EvalAltResult::ErrorFunctionNotFound(name, _) if name == function_name => {}
|
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
|
||||||
e => Err(Box::new(e))?,
|
+ Send
|
||||||
}
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
) -> Result<(), ScriptingError>;
|
||||||
|
|
||||||
|
/// Calls a function by name defined within the runtime in the context of the
|
||||||
|
/// entity that haas been paassed. Can return a dynamically typed value
|
||||||
|
/// that got returned from the function within a script.
|
||||||
|
fn call_fn(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
script_data: &mut Self::ScriptData,
|
||||||
|
entity: Entity,
|
||||||
|
args: impl for<'a> FuncArgs<'a, Self::Value, Self>,
|
||||||
|
) -> Result<Self::Value, ScriptingError>;
|
||||||
|
|
||||||
|
/// Calls a function by value defined within the runtime in the context of the
|
||||||
|
/// entity that haas been paassed. Can return a dynamically typed value
|
||||||
|
/// that got returned from the function within a script.
|
||||||
|
fn call_fn_from_value(
|
||||||
|
&self,
|
||||||
|
value: &Self::Value,
|
||||||
|
context: &Self::CallContext,
|
||||||
|
args: Vec<Self::Value>,
|
||||||
|
) -> Result<Self::Value, ScriptingError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FuncArgs<'a, V, R: Runtime> {
|
||||||
|
fn parse(self, engine: &'a R::RawEngine) -> Vec<V>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An extension trait for [App] that allows to setup a scripting runtime `R`.
|
||||||
|
pub trait BuildScriptingRuntime {
|
||||||
|
/// Returns a "runtime" type than can be used to setup scripting runtime(
|
||||||
|
/// add scripting functions etc.).
|
||||||
|
fn add_scripting<R: Runtime>(&mut self, f: impl Fn(ScriptingRuntimeBuilder<R>)) -> &mut Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScriptingRuntimeBuilder<'a, R: Runtime> {
|
||||||
|
_phantom_data: PhantomData<R>,
|
||||||
|
world: &'a mut World,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: Runtime> ScriptingRuntimeBuilder<'a, R> {
|
||||||
|
fn new(world: &'a mut World) -> Self {
|
||||||
|
Self {
|
||||||
|
_phantom_data: PhantomData,
|
||||||
|
world,
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// An extension trait for [App] that allows to register a script function.
|
/// Registers a function for calling from within a script.
|
||||||
pub trait AddScriptFunctionAppExt {
|
/// Provided function needs to be a valid bevy system and its
|
||||||
fn add_script_function<
|
/// arguments and return value need to be convertible to runtime
|
||||||
Out,
|
/// value types.
|
||||||
Marker,
|
pub fn add_function<In, Out, Marker>(
|
||||||
A: 'static,
|
self,
|
||||||
const N: usize,
|
|
||||||
const X: bool,
|
|
||||||
R: 'static,
|
|
||||||
const F: bool,
|
|
||||||
Args,
|
|
||||||
>(
|
|
||||||
&mut self,
|
|
||||||
name: String,
|
name: String,
|
||||||
system: impl RegisterCallbackFunction<Out, Marker, A, N, X, R, F, Args>,
|
fun: impl IntoCallbackSystem<R, In, Out, Marker>,
|
||||||
) -> &mut Self;
|
) -> Self {
|
||||||
}
|
let system = fun.into_callback_system(self.world);
|
||||||
|
|
||||||
/// A resource that stores all the callbacks that were registered using [AddScriptFunctionAppExt::add_script_function].
|
let mut callbacks_resource = self.world.resource_mut::<Callbacks<R>>();
|
||||||
#[derive(Resource, Default)]
|
|
||||||
struct Callbacks {
|
|
||||||
uninitialized_callbacks: Vec<Callback>,
|
|
||||||
callbacks: Mutex<Vec<Callback>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddScriptFunctionAppExt for App {
|
|
||||||
fn add_script_function<
|
|
||||||
Out,
|
|
||||||
Marker,
|
|
||||||
A: 'static,
|
|
||||||
const N: usize,
|
|
||||||
const X: bool,
|
|
||||||
R: 'static,
|
|
||||||
const F: bool,
|
|
||||||
Args,
|
|
||||||
>(
|
|
||||||
&mut self,
|
|
||||||
name: String,
|
|
||||||
system: impl RegisterCallbackFunction<Out, Marker, A, N, X, R, F, Args>,
|
|
||||||
) -> &mut Self {
|
|
||||||
let system = system.into_callback_system(&mut self.world);
|
|
||||||
let mut callbacks_resource = self.world.resource_mut::<Callbacks>();
|
|
||||||
|
|
||||||
callbacks_resource.uninitialized_callbacks.push(Callback {
|
callbacks_resource.uninitialized_callbacks.push(Callback {
|
||||||
name,
|
name,
|
||||||
system: Arc::new(Mutex::new(system)),
|
system: Arc::new(Mutex::new(system)),
|
||||||
calls: Arc::new(Mutex::new(vec![])),
|
calls: Arc::new(Mutex::new(vec![])),
|
||||||
});
|
});
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod prelude {
|
impl BuildScriptingRuntime for App {
|
||||||
pub use crate::{AddScriptFunctionAppExt, ScriptingPlugin};
|
/// Adds a scripting runtime. Registers required bevy systems that take
|
||||||
|
/// care of processing and running the scripts.
|
||||||
|
fn add_scripting<R: Runtime>(&mut self, f: impl Fn(ScriptingRuntimeBuilder<R>)) -> &mut Self {
|
||||||
|
self.world
|
||||||
|
.resource_mut::<MainScheduleOrder>()
|
||||||
|
.insert_after(Update, R::Schedule::default());
|
||||||
|
|
||||||
|
self.register_asset_loader(ScriptLoader::<R::ScriptAsset>::default())
|
||||||
|
.init_schedule(R::Schedule::default())
|
||||||
|
.init_asset::<R::ScriptAsset>()
|
||||||
|
.init_resource::<Callbacks<R>>()
|
||||||
|
.insert_resource(R::default())
|
||||||
|
.add_systems(
|
||||||
|
R::Schedule::default(),
|
||||||
|
(
|
||||||
|
reload_scripts::<R>,
|
||||||
|
process_calls::<R>
|
||||||
|
.pipe(log_errors)
|
||||||
|
.after(process_new_scripts::<R>),
|
||||||
|
init_callbacks::<R>.pipe(log_errors),
|
||||||
|
process_new_scripts::<R>
|
||||||
|
.pipe(log_errors)
|
||||||
|
.after(init_callbacks::<R>),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let runtime = ScriptingRuntimeBuilder::<R>::new(&mut self.world);
|
||||||
|
|
||||||
|
f(runtime);
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A resource that stores all the callbacks that were registered using [AddScriptFunctionAppExt::add_function].
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct Callbacks<R: Runtime> {
|
||||||
|
uninitialized_callbacks: Vec<Callback<R>>,
|
||||||
|
callbacks: Mutex<Vec<Callback<R>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Runtime> Default for Callbacks<R> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
uninitialized_callbacks: Default::default(),
|
||||||
|
callbacks: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::{BuildScriptingRuntime as _, Runtime as _, Script};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,80 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[allow(deprecated)]
|
use crate::{Runtime, ScriptingError};
|
||||||
use rhai::{Dynamic, NativeCallContextStore};
|
|
||||||
use rhai::{EvalAltResult, FnPtr};
|
|
||||||
|
|
||||||
/// A struct that represents a function that will get called when the Promise is resolved.
|
/// A struct that represents a function that will get called when the Promise is resolved.
|
||||||
pub(crate) struct PromiseCallback {
|
pub(crate) struct PromiseCallback<C: Send, V: Send> {
|
||||||
callback: Dynamic,
|
callback: V,
|
||||||
following_promise: Arc<Mutex<PromiseInner>>,
|
following_promise: Arc<Mutex<PromiseInner<C, V>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal representation of a Promise.
|
/// Internal representation of a Promise.
|
||||||
pub(crate) struct PromiseInner {
|
pub(crate) struct PromiseInner<C: Send, V: Send> {
|
||||||
pub(crate) callbacks: Vec<PromiseCallback>,
|
pub(crate) callbacks: Vec<PromiseCallback<C, V>>,
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
pub(crate) context_data: NativeCallContextStore,
|
pub(crate) context: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct that represents a Promise.
|
/// A struct that represents a Promise.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Promise {
|
pub struct Promise<C: Send, V: Send> {
|
||||||
pub(crate) inner: Arc<Mutex<PromiseInner>>,
|
pub(crate) inner: Arc<Mutex<PromiseInner<C, V>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PromiseInner {
|
impl<C: Send, V: Send + Clone> PromiseInner<C, V> {
|
||||||
/// Resolve the Promise. This will call all the callbacks that were added to the Promise.
|
/// Resolve the Promise. This will call all the callbacks that were added to the Promise.
|
||||||
fn resolve(
|
fn resolve<R>(&mut self, runtime: &mut R, val: R::Value) -> Result<(), ScriptingError>
|
||||||
&mut self,
|
where
|
||||||
engine: &mut rhai::Engine,
|
R: Runtime<Value = V, CallContext = C>,
|
||||||
val: Dynamic,
|
{
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
|
||||||
for callback in &self.callbacks {
|
for callback in &self.callbacks {
|
||||||
let f = callback.callback.clone_cast::<FnPtr>();
|
let next_val =
|
||||||
#[allow(deprecated)]
|
runtime.call_fn_from_value(&callback.callback, &self.context, vec![val.clone()])?;
|
||||||
let context = self.context_data.create_context(engine);
|
|
||||||
let next_val = if val.is_unit() {
|
|
||||||
f.call_raw(&context, None, [])?
|
|
||||||
} else {
|
|
||||||
f.call_raw(&context, None, [val.clone()])?
|
|
||||||
};
|
|
||||||
callback
|
callback
|
||||||
.following_promise
|
.following_promise
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.expect("Failed to lock promise mutex")
|
||||||
.resolve(engine, next_val)?;
|
.resolve(runtime, next_val)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Promise {
|
impl<C: Clone + Send + 'static, V: Send + Clone> Promise<C, V> {
|
||||||
/// Acquire [Mutex] for writing the promise and resolve it. Call will be forwarded to [PromiseInner::resolve].
|
/// Acquire [Mutex] for writing the promise and resolve it. Call will be forwarded to [PromiseInner::resolve].
|
||||||
pub(crate) fn resolve(
|
pub(crate) fn resolve<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
engine: &mut rhai::Engine,
|
runtime: &mut R,
|
||||||
val: Dynamic,
|
val: R::Value,
|
||||||
) -> Result<(), Box<EvalAltResult>> {
|
) -> Result<(), ScriptingError>
|
||||||
|
where
|
||||||
|
R: Runtime<Value = V, CallContext = C>,
|
||||||
|
{
|
||||||
if let Ok(mut inner) = self.inner.lock() {
|
if let Ok(mut inner) = self.inner.lock() {
|
||||||
inner.resolve(engine, val)?;
|
inner.resolve(runtime, val)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Register a callback that will be called when the [Promise] is resolved.
|
/// Register a callback that will be called when the [Promise] is resolved.
|
||||||
pub(crate) fn then(&mut self, callback: rhai::Dynamic) -> rhai::Dynamic {
|
pub(crate) fn then(&mut self, callback: V) -> Self {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to lock inner promise mutex");
|
||||||
let following_inner = Arc::new(Mutex::new(PromiseInner {
|
let following_inner = Arc::new(Mutex::new(PromiseInner {
|
||||||
callbacks: vec![],
|
callbacks: vec![],
|
||||||
context_data: inner.context_data.clone(),
|
context: inner.context.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
inner.callbacks.push(PromiseCallback {
|
inner.callbacks.push(PromiseCallback {
|
||||||
following_promise: following_inner.clone(),
|
following_promise: following_inner.clone(),
|
||||||
callback,
|
callback,
|
||||||
});
|
});
|
||||||
Dynamic::from(Promise {
|
|
||||||
|
Promise {
|
||||||
inner: following_inner,
|
inner: following_inner,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
335
src/runtimes/lua.rs
Normal file
335
src/runtimes/lua.rs
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
use bevy::{
|
||||||
|
asset::Asset,
|
||||||
|
ecs::{component::Component, entity::Entity, schedule::ScheduleLabel, system::Resource},
|
||||||
|
math::Vec3,
|
||||||
|
reflect::TypePath,
|
||||||
|
};
|
||||||
|
use mlua::{
|
||||||
|
FromLua, Function, IntoLua, IntoLuaMulti, Lua, RegistryKey, UserData, UserDataFields,
|
||||||
|
UserDataMethods, Variadic,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
assets::GetExtensions,
|
||||||
|
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
|
||||||
|
promise::Promise,
|
||||||
|
FuncArgs, Runtime, ScriptingError, ENTITY_VAR_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
type LuaEngine = Arc<Mutex<Lua>>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct LuaValue(Arc<RegistryKey>);
|
||||||
|
|
||||||
|
impl LuaValue {
|
||||||
|
fn new<'a, T: IntoLua<'a>>(engine: &'a Lua, value: T) -> Self {
|
||||||
|
Self(Arc::new(
|
||||||
|
engine
|
||||||
|
.create_registry_value(value)
|
||||||
|
.expect("Error creating a registry key for value"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
pub struct LuaRuntime {
|
||||||
|
engine: LuaEngine,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BevyEntity(pub Entity);
|
||||||
|
|
||||||
|
impl UserData for BevyEntity {}
|
||||||
|
|
||||||
|
impl FromLua<'_> for BevyEntity {
|
||||||
|
fn from_lua(
|
||||||
|
value: mlua::prelude::LuaValue<'_>,
|
||||||
|
_lua: &'_ Lua,
|
||||||
|
) -> mlua::prelude::LuaResult<Self> {
|
||||||
|
match value {
|
||||||
|
mlua::Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||||
|
_ => panic!("got {:?} instead of BevyEntity", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct BevyVec3(pub Vec3);
|
||||||
|
|
||||||
|
impl UserData for BevyVec3 {}
|
||||||
|
|
||||||
|
impl FromLua<'_> for BevyVec3 {
|
||||||
|
fn from_lua(
|
||||||
|
value: mlua::prelude::LuaValue<'_>,
|
||||||
|
_lua: &'_ Lua,
|
||||||
|
) -> mlua::prelude::LuaResult<Self> {
|
||||||
|
match value {
|
||||||
|
mlua::Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
|
||||||
|
_ => panic!("got {:?} instead of BevyVec3", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LuaRuntime {
|
||||||
|
fn default() -> Self {
|
||||||
|
let engine = LuaEngine::default();
|
||||||
|
|
||||||
|
{
|
||||||
|
let engine = engine.lock().expect("Failed to lock engine");
|
||||||
|
engine
|
||||||
|
.register_userdata_type::<BevyEntity>(|typ| {
|
||||||
|
typ.add_field_method_get("index", |_, entity| Ok(entity.0.index()));
|
||||||
|
})
|
||||||
|
.expect("Failed to register BevyEntity userdata type");
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_userdata_type::<Promise<(), LuaValue>>(|typ| {
|
||||||
|
typ.add_method_mut("and_then", |engine, promise, callback: Function| {
|
||||||
|
Ok(Promise::then(promise, LuaValue::new(engine, callback)))
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.expect("Failed to register Promise userdata type");
|
||||||
|
|
||||||
|
engine
|
||||||
|
.register_userdata_type::<BevyVec3>(|typ| {
|
||||||
|
typ.add_field_method_get("x", |_engine, vec| Ok(vec.0.x));
|
||||||
|
typ.add_field_method_get("y", |_engine, vec| Ok(vec.0.y));
|
||||||
|
typ.add_field_method_get("z", |_engine, vec| Ok(vec.0.z));
|
||||||
|
})
|
||||||
|
.expect("Failed to register BevyVec3 userdata type");
|
||||||
|
let vec3_constructor = engine
|
||||||
|
.create_function(|_, (x, y, z)| Ok(BevyVec3(Vec3::new(x, y, z))))
|
||||||
|
.expect("Failed to create Vec3 constructor");
|
||||||
|
engine
|
||||||
|
.globals()
|
||||||
|
.set("Vec3", vec3_constructor)
|
||||||
|
.expect("Failed to set Vec3 global");
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { engine }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(ScheduleLabel, Clone, PartialEq, Eq, Debug, Hash, Default)]
|
||||||
|
pub struct LuaSchedule;
|
||||||
|
|
||||||
|
#[derive(Asset, Debug, Deserialize, TypePath)]
|
||||||
|
pub struct LuaScript(pub String);
|
||||||
|
|
||||||
|
impl GetExtensions for LuaScript {
|
||||||
|
fn extensions() -> &'static [&'static str] {
|
||||||
|
&["lua"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for LuaScript {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct LuaScriptData;
|
||||||
|
|
||||||
|
impl Runtime for LuaRuntime {
|
||||||
|
type Schedule = LuaSchedule;
|
||||||
|
|
||||||
|
type ScriptAsset = LuaScript;
|
||||||
|
|
||||||
|
type ScriptData = LuaScriptData;
|
||||||
|
|
||||||
|
type CallContext = ();
|
||||||
|
|
||||||
|
type Value = LuaValue;
|
||||||
|
|
||||||
|
type RawEngine = Lua;
|
||||||
|
|
||||||
|
fn eval(
|
||||||
|
&self,
|
||||||
|
script: &Self::ScriptAsset,
|
||||||
|
entity: bevy::prelude::Entity,
|
||||||
|
) -> Result<Self::ScriptData, crate::ScriptingError> {
|
||||||
|
self.with_engine(|engine| {
|
||||||
|
engine
|
||||||
|
.globals()
|
||||||
|
.set(ENTITY_VAR_NAME, BevyEntity(entity))
|
||||||
|
.expect("Error setting entity global variable");
|
||||||
|
let result = engine.load(&script.0).exec();
|
||||||
|
engine
|
||||||
|
.globals()
|
||||||
|
.set(ENTITY_VAR_NAME, mlua::Value::Nil)
|
||||||
|
.expect("Error clearing entity global variable");
|
||||||
|
result
|
||||||
|
})
|
||||||
|
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||||
|
Ok(LuaScriptData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_fn(
|
||||||
|
&mut self,
|
||||||
|
name: String,
|
||||||
|
_arg_types: Vec<std::any::TypeId>,
|
||||||
|
f: impl Fn(
|
||||||
|
Self::CallContext,
|
||||||
|
Vec<Self::Value>,
|
||||||
|
) -> Result<
|
||||||
|
crate::promise::Promise<Self::CallContext, Self::Value>,
|
||||||
|
crate::ScriptingError,
|
||||||
|
> + Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static,
|
||||||
|
) -> Result<(), crate::ScriptingError> {
|
||||||
|
self.with_engine(|engine| {
|
||||||
|
let func = engine
|
||||||
|
.create_function(move |engine, args: Variadic<mlua::Value>| {
|
||||||
|
let args = { args.into_iter().map(|x| LuaValue::new(engine, x)).collect() };
|
||||||
|
let result = f((), args).unwrap();
|
||||||
|
Ok(result)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
engine
|
||||||
|
.globals()
|
||||||
|
.set(name, func)
|
||||||
|
.expect("Error registering function in global lua scope");
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_fn(
|
||||||
|
&self,
|
||||||
|
name: &str,
|
||||||
|
_script_data: &mut Self::ScriptData,
|
||||||
|
entity: bevy::prelude::Entity,
|
||||||
|
args: impl for<'a> FuncArgs<'a, Self::Value, Self>,
|
||||||
|
) -> Result<Self::Value, crate::ScriptingError> {
|
||||||
|
self.with_engine(|engine| {
|
||||||
|
engine
|
||||||
|
.globals()
|
||||||
|
.set(ENTITY_VAR_NAME, BevyEntity(entity))
|
||||||
|
.expect("Error setting entity global variable");
|
||||||
|
let func = engine
|
||||||
|
.globals()
|
||||||
|
.get::<_, Function>(name)
|
||||||
|
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||||
|
let args = args
|
||||||
|
.parse(engine)
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| engine.registry_value::<mlua::Value>(&a.0).unwrap());
|
||||||
|
let result = func
|
||||||
|
.call::<_, mlua::Value>(Variadic::from_iter(args))
|
||||||
|
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||||
|
engine
|
||||||
|
.globals()
|
||||||
|
.set(ENTITY_VAR_NAME, mlua::Value::Nil)
|
||||||
|
.expect("Error clearing entity global variable");
|
||||||
|
Ok(LuaValue::new(engine, result))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_fn_from_value(
|
||||||
|
&self,
|
||||||
|
value: &Self::Value,
|
||||||
|
_context: &Self::CallContext,
|
||||||
|
args: Vec<Self::Value>,
|
||||||
|
) -> Result<Self::Value, crate::ScriptingError> {
|
||||||
|
self.with_engine(|engine| {
|
||||||
|
let val = engine
|
||||||
|
.registry_value::<Function>(&value.0)
|
||||||
|
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||||
|
let args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| engine.registry_value::<mlua::Value>(&a.0).unwrap());
|
||||||
|
let result = val
|
||||||
|
.call::<_, mlua::Value>(Variadic::from_iter(args))
|
||||||
|
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||||
|
Ok(LuaValue::new(engine, result))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_engine_mut<T>(&mut self, f: impl FnOnce(&mut Self::RawEngine) -> T) -> T {
|
||||||
|
let mut engine = self.engine.lock().unwrap();
|
||||||
|
f(&mut engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_engine<T>(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T {
|
||||||
|
let engine = self.engine.lock().unwrap();
|
||||||
|
f(&engine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: IntoLuaMulti<'a>> IntoRuntimeValueWithEngine<'a, T, LuaRuntime> for T {
|
||||||
|
fn into_runtime_value_with_engine(value: T, engine: &'a Lua) -> LuaValue {
|
||||||
|
let mut iter = value.into_lua_multi(engine).unwrap().into_iter();
|
||||||
|
if iter.len() > 1 {
|
||||||
|
unimplemented!("Returning multiple values from function");
|
||||||
|
}
|
||||||
|
LuaValue(Arc::new(engine.create_registry_value(iter.next()).unwrap()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: FromLua<'a>> FromRuntimeValueWithEngine<'a, LuaRuntime> for T {
|
||||||
|
fn from_runtime_value_with_engine(value: LuaValue, engine: &'a Lua) -> Self {
|
||||||
|
engine.registry_value(&value.0).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FuncArgs<'_, LuaValue, LuaRuntime> for () {
|
||||||
|
fn parse(self, _engine: &Lua) -> Vec<LuaValue> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T: IntoLua<'a>> FuncArgs<'a, LuaValue, LuaRuntime> for Vec<T> {
|
||||||
|
fn parse(self, engine: &'a Lua) -> Vec<LuaValue> {
|
||||||
|
self.into_iter().map(|x| LuaValue::new(engine, x)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for Promise<(), LuaValue> {}
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::{BevyEntity, BevyVec3, LuaRuntime, LuaScript, LuaScriptData};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_tuple {
|
||||||
|
($($idx:tt $t:tt),+) => {
|
||||||
|
impl<'a, $($t: IntoLua<'a>,)+> FuncArgs<'a, LuaValue, LuaRuntime>
|
||||||
|
for ($($t,)+)
|
||||||
|
{
|
||||||
|
fn parse(self, engine: &'a Lua) -> Vec<LuaValue> {
|
||||||
|
vec![
|
||||||
|
$(LuaValue::new(engine, self.$idx), )+
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S, 19 T, 20 U, 21 V, 22 W, 23 X, 24 Y, 25 Z);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S, 19 T, 20 U, 21 V, 22 W, 23 X, 24 Y);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S, 19 T, 20 U, 21 V, 22 W, 23 X);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S, 19 T, 20 U, 21 V, 22 W);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S, 19 T, 20 U, 21 V);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S, 19 T, 20 U);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S, 19 T);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R, 18 S);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q, 17 R);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P, 16 Q);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O, 15 P);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N, 14 O);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M, 13 N);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L, 12 M);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K, 11 L);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J, 10 K);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I, 9 J);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H, 8 I);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G, 7 H);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F, 6 G);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E, 5 F);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D, 4 E);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C, 3 D);
|
||||||
|
impl_tuple!(0 A, 1 B, 2 C);
|
||||||
|
impl_tuple!(0 A, 1 B);
|
||||||
|
impl_tuple!(0 A);
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue