Ruby support #1
93 changed files with 5130 additions and 306 deletions
54
.github/workflows/book.yml
vendored
Normal file
54
.github/workflows/book.yml
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
name: Book
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Cache Ruby
|
||||
id: cache-ruby
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: rubies
|
||||
key: ${{ runner.os }}-ruby
|
||||
- name: Install Ruby
|
||||
if: steps.cache-ruby.outputs.cache-hit != 'true'
|
||||
env:
|
||||
CC: clang
|
||||
run: |
|
||||
url="https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.4.tar.gz"
|
||||
prefix=`pwd`/rubies/ruby-3.4
|
||||
mkdir rubies
|
||||
mkdir ruby_src
|
||||
curl -sSL $url | tar -xz
|
||||
cd ruby-3.4.4
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --without-shared --prefix=$prefix
|
||||
make install
|
||||
echo $prefix/bin >> $GITHUB_PATH
|
||||
- name: Add Ruby to PATH
|
||||
run: |
|
||||
prefix=`pwd`/rubies/ruby-3.4
|
||||
echo $prefix/bin >> $GITHUB_PATH
|
||||
- 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: Test Book
|
||||
run: |
|
||||
cd book
|
||||
export CARGO_MANIFEST_DIR=$(pwd)
|
||||
cargo build
|
||||
mdbook test -L target/debug/deps/
|
||||
43
.github/workflows/rust.yml
vendored
43
.github/workflows/rust.yml
vendored
|
|
@ -2,9 +2,9 @@ name: Rust
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
|
@ -15,10 +15,35 @@ jobs:
|
|||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --verbose -- -D warnings
|
||||
- name: Build
|
||||
run: cargo build --all-features --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --all-features --verbose
|
||||
- uses: actions/checkout@v3
|
||||
- name: Cache Ruby
|
||||
id: cache-ruby
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: rubies
|
||||
key: ${{ runner.os }}-ruby
|
||||
- name: Install Ruby
|
||||
if: steps.cache-ruby.outputs.cache-hit != 'true'
|
||||
env:
|
||||
CC: clang
|
||||
run: |
|
||||
url="https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.4.tar.gz"
|
||||
prefix=`pwd`/rubies/ruby-3.4
|
||||
mkdir rubies
|
||||
mkdir ruby_src
|
||||
curl -sSL $url | tar -xz
|
||||
cd ruby-3.4.4
|
||||
mkdir build
|
||||
cd build
|
||||
../configure --without-shared --prefix=$prefix
|
||||
make install
|
||||
- name: Add Ruby to PATH
|
||||
run: |
|
||||
prefix=`pwd`/rubies/ruby-3.4
|
||||
echo $prefix/bin >> $GITHUB_PATH
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features --verbose -- -D warnings
|
||||
- name: Build
|
||||
run: cargo build --all-features --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --all-features --verbose
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
/target
|
||||
/Cargo.lock
|
||||
.vscode
|
||||
rust-analyzer.json
|
||||
.nvim.lua
|
||||
|
|
|
|||
68
Cargo.toml
68
Cargo.toml
|
|
@ -2,17 +2,18 @@
|
|||
name = "bevy_scriptum"
|
||||
authors = ["Jaroslaw Konik <konikjar@gmail.com>"]
|
||||
version = "0.8.1"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
categories = ["game-development"]
|
||||
description = "Plugin for Bevy engine that allows you to write some of your game logic in a scripting language"
|
||||
description = "Plugin for Bevy engine that allows you to write some of your game or application logic in a scripting language"
|
||||
repository = "https://github.com/jarkonik/bevy_scriptum"
|
||||
keywords = ["bevy", "rhai", "scripting", "game", "gamedev"]
|
||||
keywords = ["bevy", "lua", "scripting", "game", "script"]
|
||||
|
||||
[features]
|
||||
lua = ["mlua/luajit"]
|
||||
lua = ["dep:mlua", "mlua/luajit"]
|
||||
rhai = ["dep:rhai"]
|
||||
ruby = ["dep:magnus", "dep:rb-sys"]
|
||||
|
||||
[dependencies]
|
||||
bevy = { default-features = false, version = "0.16", features = ["bevy_asset", "bevy_log"] }
|
||||
|
|
@ -30,6 +31,10 @@ mlua = { version = "0.9.8", features = [
|
|||
"vendored",
|
||||
"send",
|
||||
], optional = true }
|
||||
magnus = { version = "0.7.1", optional = true }
|
||||
rb-sys = { version = "*", default-features = false, features = ["link-ruby", "ruby-static"], optional = true }
|
||||
crossbeam-channel = "0.5.15"
|
||||
libc = "0.2.172"
|
||||
|
||||
[[example]]
|
||||
name = "call_function_from_rust_rhai"
|
||||
|
|
@ -151,6 +156,61 @@ name = "function_return_value_lua"
|
|||
path = "examples/lua/function_return_value.rs"
|
||||
required-features = ["lua"]
|
||||
|
||||
[[example]]
|
||||
name = "call_function_from_rust_ruby"
|
||||
path = "examples/ruby/call_function_from_rust.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "current_entity_ruby"
|
||||
path = "examples/ruby/current_entity.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "custom_type_ruby"
|
||||
path = "examples/ruby/custom_type.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "ecs_ruby"
|
||||
path = "examples/ruby/ecs.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "entity_variable_ruby"
|
||||
path = "examples/ruby/entity_variable.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "function_params_ruby"
|
||||
path = "examples/ruby/function_params.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "function_return_value_ruby"
|
||||
path = "examples/ruby/function_return_value.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "hello_world_ruby"
|
||||
path = "examples/ruby/hello_world.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "multiple_plugins_ruby"
|
||||
path = "examples/ruby/multiple_plugins.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "promises_ruby"
|
||||
path = "examples/ruby/promises.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[[example]]
|
||||
name = "side_effects_ruby"
|
||||
path = "examples/ruby/side_effects.rs"
|
||||
required-features = ["ruby"]
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-subscriber = "0.3.18"
|
||||
mlua = { version = "0.9.8", features = ["luajit", "vendored", "send"] }
|
||||
|
|
|
|||
46
README.md
46
README.md
|
|
@ -2,13 +2,18 @@
|
|||
|
||||

|
||||
|
||||
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.
|
||||
bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game or application logic in a scripting language.
|
||||
### Supported scripting languages/runtimes
|
||||
|
||||
Everything you need to know to get started with using this library is contained in the
|
||||
[bevy_scriptum book](https://jarkonik.github.io/bevy_scriptum/)
|
||||
| language/runtime | cargo feature | documentation chapter |
|
||||
| ----------------- | ------------- | --------------------------------------------------------------- |
|
||||
| 🌙 LuaJIT | `lua` | [link](https://jarkonik.github.io/bevy_scriptum/lua/lua.html) |
|
||||
| 🌾 Rhai | `rhai` | [link](https://jarkonik.github.io/bevy_scriptum/rhai/rhai.html) |
|
||||
| 💎 Ruby | `ruby` | [link](https://jarkonik.github.io/bevy_scriptum/ruby/ruby.html) |
|
||||
|
||||
API docs are available in [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/)
|
||||
Documentation book is available [here](https://jarkonik.github.io/bevy_scriptum/) 📖
|
||||
|
||||
Full API docs are available at [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/) 🧑💻
|
||||
|
||||
bevy_scriptum's main advantages include:
|
||||
- low-boilerplate
|
||||
|
|
@ -17,7 +22,7 @@ bevy_scriptum's main advantages include:
|
|||
- 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.
|
||||
Scripts are separate files that can be hot-reloaded at runtime. This allows you to quickly iterate on your game or application logic without having to recompile it.
|
||||
|
||||
All you need to do is register callbacks on your Bevy app like this:
|
||||
```rust
|
||||
|
|
@ -86,33 +91,6 @@ which you can then call in your script like this:
|
|||
```lua
|
||||
fun_with_string_param("Hello world!")
|
||||
```
|
||||
It is also possible to split the definition of your callback functions up over multiple plugins. This enables you to split up your code by subject and keep the main initialization light and clean.
|
||||
This can be accomplished by using `add_scripting_api`. Be careful though, `add_scripting` has to be called before adding plugins.
|
||||
```rust
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
||||
struct MyPlugin;
|
||||
impl Plugin for MyPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_scripting_api::<LuaRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("hello_from_my_plugin"), || {
|
||||
info!("Hello from MyPlugin");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<LuaRuntime>(|_| {
|
||||
// nice and clean
|
||||
})
|
||||
.add_plugins(MyPlugin)
|
||||
.run();
|
||||
```
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
|
|
@ -120,7 +98,7 @@ Add the following to your `Cargo.toml`:
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
bevy_scriptum = { version = "0.7", features = ["lua"] }
|
||||
bevy_scriptum = { version = "0.8", features = ["lua"] }
|
||||
```
|
||||
|
||||
or execute `cargo add bevy_scriptum --features lua` from your project directory.
|
||||
|
|
|
|||
13
assets/examples/ruby/call_function_from_rust.rb
Normal file
13
assets/examples/ruby/call_function_from_rust.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
$my_state = {
|
||||
iterations: 0,
|
||||
}
|
||||
|
||||
def on_update
|
||||
$my_state[:iterations] += 1
|
||||
puts("on_update called #{$my_state[:iterations]} times")
|
||||
|
||||
if $my_state[:iterations] >= 10
|
||||
print("calling quit");
|
||||
quit()
|
||||
end
|
||||
end
|
||||
3
assets/examples/ruby/current_entity.rb
Normal file
3
assets/examples/ruby/current_entity.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
get_name(Bevy::Entity.current).and_then do |name|
|
||||
puts(name)
|
||||
end
|
||||
4
assets/examples/ruby/custom_type.rb
Normal file
4
assets/examples/ruby/custom_type.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Create a new instance of MyType
|
||||
my_type = MyType.new()
|
||||
# Call registered method
|
||||
puts(my_type.my_method)
|
||||
1
assets/examples/ruby/ecs.rb
Normal file
1
assets/examples/ruby/ecs.rb
Normal file
|
|
@ -0,0 +1 @@
|
|||
print_player_names
|
||||
2
assets/examples/ruby/entity_variable.rb
Normal file
2
assets/examples/ruby/entity_variable.rb
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Bevy::Entity.current can be used to access the entity that is currently being processed
|
||||
puts("Current entity index: #{Bevy::Entity.current.index}")
|
||||
4
assets/examples/ruby/function_params.rb
Normal file
4
assets/examples/ruby/function_params.rb
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/ruby/function_return_value.rb
Normal file
3
assets/examples/ruby/function_return_value.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
def get_value
|
||||
42
|
||||
end
|
||||
1
assets/examples/ruby/hello_world.rb
Normal file
1
assets/examples/ruby/hello_world.rb
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello_bevy()
|
||||
1
assets/examples/ruby/multiple_plugins_plugin_a.rb
Normal file
1
assets/examples/ruby/multiple_plugins_plugin_a.rb
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello_from_plugin_a
|
||||
1
assets/examples/ruby/multiple_plugins_plugin_b.rb
Normal file
1
assets/examples/ruby/multiple_plugins_plugin_b.rb
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello_from_plugin_b_with_parameters("hello", 42)
|
||||
3
assets/examples/ruby/promises.rb
Normal file
3
assets/examples/ruby/promises.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
get_player_name.and_then do |name|
|
||||
puts name
|
||||
end
|
||||
1
assets/examples/ruby/side_effects.rb
Normal file
1
assets/examples/ruby/side_effects.rb
Normal file
|
|
@ -0,0 +1 @@
|
|||
spawn_entity()
|
||||
3
assets/tests/lua/entity_variable.lua
Normal file
3
assets/tests/lua/entity_variable.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
function test_func()
|
||||
rust_func(entity.index)
|
||||
end
|
||||
5
assets/tests/lua/entity_variable_eval.lua
Normal file
5
assets/tests/lua/entity_variable_eval.lua
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
index = entity.index
|
||||
|
||||
function test_func()
|
||||
rust_func(index)
|
||||
end
|
||||
2
assets/tests/lua/eval_that_causes_runtime_error.lua
Normal file
2
assets/tests/lua/eval_that_causes_runtime_error.lua
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mark_called()
|
||||
error()
|
||||
3
assets/tests/lua/pass_entity_from_script.lua
Normal file
3
assets/tests/lua/pass_entity_from_script.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
function test_func()
|
||||
rust_func(entity)
|
||||
end
|
||||
3
assets/tests/lua/pass_vec3_from_script.lua
Normal file
3
assets/tests/lua/pass_vec3_from_script.lua
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
function test_func()
|
||||
rust_func(Vec3(1.5, 2.5, -3.5))
|
||||
end
|
||||
6
assets/tests/lua/pass_vec3_to_script.lua
Normal file
6
assets/tests/lua/pass_vec3_to_script.lua
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
function test_func(vec3)
|
||||
assert(vec3.x == 1.5)
|
||||
assert(vec3.y == 2.5)
|
||||
assert(vec3.z == -3.5)
|
||||
mark_success()
|
||||
end
|
||||
3
assets/tests/rhai/entity_variable.rhai
Normal file
3
assets/tests/rhai/entity_variable.rhai
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn test_func() {
|
||||
rust_func(entity.index);
|
||||
}
|
||||
5
assets/tests/rhai/entity_variable_eval.rhai
Normal file
5
assets/tests/rhai/entity_variable_eval.rhai
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
let index = entity.index;
|
||||
|
||||
fn test_func() {
|
||||
rust_func(index);
|
||||
}
|
||||
2
assets/tests/rhai/eval_that_causes_runtime_error.rhai
Normal file
2
assets/tests/rhai/eval_that_causes_runtime_error.rhai
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mark_called();
|
||||
throw();
|
||||
3
assets/tests/rhai/pass_entity_from_script.rhai
Normal file
3
assets/tests/rhai/pass_entity_from_script.rhai
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn test_func() {
|
||||
rust_func(entity);
|
||||
}
|
||||
3
assets/tests/rhai/pass_vec3_from_script.rhai
Normal file
3
assets/tests/rhai/pass_vec3_from_script.rhai
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn test_func() {
|
||||
rust_func(new_vec3(1.5, 2.5, -3.5));
|
||||
}
|
||||
7
assets/tests/rhai/pass_vec3_to_script.rhai
Normal file
7
assets/tests/rhai/pass_vec3_to_script.rhai
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
fn test_func(vec3) {
|
||||
if type_of(vec3) != "Vec3" { throw() }
|
||||
if vec3.x != 1.5 { throw() }
|
||||
if vec3.y != 2.5 { throw() }
|
||||
if vec3.z != -3.5 { throw() }
|
||||
mark_success();
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
raise
|
||||
end
|
||||
7
assets/tests/ruby/call_script_function_with_params.rb
Normal file
7
assets/tests/ruby/call_script_function_with_params.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
$state = {
|
||||
'called_with' => nil
|
||||
}
|
||||
|
||||
def test_func(val)
|
||||
$state['called_with'] = val
|
||||
end
|
||||
3
assets/tests/ruby/entity_variable.rb
Normal file
3
assets/tests/ruby/entity_variable.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
rust_func(Bevy::Entity.current.index)
|
||||
end
|
||||
5
assets/tests/ruby/entity_variable_eval.rb
Normal file
5
assets/tests/ruby/entity_variable_eval.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
$index = Bevy::Entity.current.index
|
||||
|
||||
def test_func
|
||||
rust_func($index)
|
||||
end
|
||||
2
assets/tests/ruby/eval_that_causes_runtime_error.rb
Normal file
2
assets/tests/ruby/eval_that_causes_runtime_error.rb
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mark_called
|
||||
raise
|
||||
3
assets/tests/ruby/pass_entity_from_script.rb
Normal file
3
assets/tests/ruby/pass_entity_from_script.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
rust_func(Bevy::Entity.current)
|
||||
end
|
||||
3
assets/tests/ruby/pass_vec3_from_script.rb
Normal file
3
assets/tests/ruby/pass_vec3_from_script.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
rust_func(Bevy::Vec3.new(1.5, 2.5, -3.5))
|
||||
end
|
||||
7
assets/tests/ruby/pass_vec3_to_script.rb
Normal file
7
assets/tests/ruby/pass_vec3_to_script.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
def test_func(vec3)
|
||||
raise unless vec3.is_a?(Bevy::Vec3)
|
||||
raise unless vec3.x == 1.5
|
||||
raise unless vec3.y == 2.5
|
||||
raise unless vec3.z == -3.5
|
||||
mark_success
|
||||
end
|
||||
5
assets/tests/ruby/promise_runtime_error.rb
Normal file
5
assets/tests/ruby/promise_runtime_error.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
def test_func
|
||||
rust_func.and_then do |x|
|
||||
raise
|
||||
end
|
||||
end
|
||||
9
assets/tests/ruby/return_via_promise.rb
Normal file
9
assets/tests/ruby/return_via_promise.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
$state = {
|
||||
'x' => nil
|
||||
}
|
||||
|
||||
def test_func
|
||||
rust_func.and_then do |x|
|
||||
$state['x'] = x
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
rust_func
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
rust_func(5, 'test')
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
rust_func(5)
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
$state = {
|
||||
'times_called' => 0
|
||||
}
|
||||
|
||||
def test_func
|
||||
$state['times_called'] += 1
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
$state = {
|
||||
'a_value' => nil,
|
||||
'b_value' => nil
|
||||
}
|
||||
|
||||
def test_func(a, b)
|
||||
$state['a_value'] = a
|
||||
$state['b_value'] = b
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
$state = {
|
||||
'a_value' => nil
|
||||
}
|
||||
|
||||
def test_func(a)
|
||||
$state['a_value'] = a
|
||||
end
|
||||
3
assets/tests/ruby/side_effects.rb
Normal file
3
assets/tests/ruby/side_effects.rb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
def test_func
|
||||
spawn_entity
|
||||
end
|
||||
1
book/.gitignore
vendored
1
book/.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
|||
book
|
||||
doctest_cache
|
||||
target
|
||||
|
|
|
|||
2498
book/Cargo.lock
generated
Normal file
2498
book/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
7
book/Cargo.toml
Normal file
7
book/Cargo.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "bevy_scriptum_book"
|
||||
publish = false
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy_scriptum = { path = "../", features = ["ruby", "lua", "rhai"] }
|
||||
|
|
@ -4,9 +4,3 @@ language = "en"
|
|||
multilingual = false
|
||||
src = "src"
|
||||
title = "bevy_scriptum"
|
||||
|
||||
[preprocessor.keeper]
|
||||
command = "mdbook-keeper"
|
||||
manifest_dir = "../"
|
||||
externs = ["bevy", "bevy_scriptum"]
|
||||
build_features = ["lua", "rhai"]
|
||||
|
|
|
|||
15
book/justfile
Normal file
15
book/justfile
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
export CARGO_MANIFEST_DIR := `pwd`
|
||||
|
||||
build-deps:
|
||||
cargo clean && cargo build
|
||||
|
||||
_test:
|
||||
mdbook test -L target/debug/deps/
|
||||
|
||||
test: build-deps _test
|
||||
|
||||
test-watch: build-deps
|
||||
watchexec --exts md -r just _test
|
||||
|
||||
serve:
|
||||
mdbook serve
|
||||
|
|
@ -2,20 +2,29 @@
|
|||
|
||||
- [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)]()
|
||||
- [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)
|
||||
- [Ruby](./ruby/ruby.md)
|
||||
- [Installation](./ruby/installation.md)
|
||||
- [Hello World](./ruby/hello_world.md)
|
||||
- [Spawning scripts](./ruby/spawning_scripts.md)
|
||||
- [Calling Rust from Ruby](./ruby/calling_rust_from_script.md)
|
||||
- [Calling Ruby from Rust](./ruby/calling_script_from_rust.md)
|
||||
- [Interacting with bevy in callbacks](./ruby/interacting_with_bevy.md)
|
||||
- [Builtin types](./ruby/builtin_types.md)
|
||||
- [Rhai](./rhai/rhai.md)
|
||||
- [Installation](./rhai/installation.md)
|
||||
- [Hello World(TBD)]()
|
||||
- [Multiple plugins](./multiple_plugins.md)
|
||||
- [Multiple runtimes(TBD)]()
|
||||
- [Implementing custom runtimes(TBD)]()
|
||||
- [Workflow](./workflow/workflow.md)
|
||||
- [Live-reload](./workflow/live_reload.md)
|
||||
- [Live-reload](./workflow/live_reload.md)
|
||||
- [Bevy support matrix](./bevy_support_matrix.md)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
# 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.
|
||||
bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game or application logic in a scripting language.
|
||||
|
||||
API docs are available in [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/)
|
||||
## Supported scripting languages/runtimes
|
||||
|
||||
| language/runtime | cargo feature | documentation chapter |
|
||||
| ----------------- | ------------- | --------------------------------------------------------------- |
|
||||
| 🌙 LuaJIT | `lua` | [link](https://jarkonik.github.io/bevy_scriptum/lua/lua.html) |
|
||||
| 🌾 Rhai | `rhai` | [link](https://jarkonik.github.io/bevy_scriptum/rhai/rhai.html) |
|
||||
| 💎 Ruby | `ruby` | [link](https://jarkonik.github.io/bevy_scriptum/ruby/ruby.html) |
|
||||
|
||||
Documentation book is available [here](https://jarkonik.github.io/bevy_scriptum/) 📖
|
||||
|
||||
Full API docs are available at [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/) 🧑💻
|
||||
|
||||
bevy_scriptum's main advantages include:
|
||||
- low-boilerplate
|
||||
|
|
@ -12,10 +21,13 @@ bevy_scriptum's main advantages include:
|
|||
- 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.
|
||||
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 it.
|
||||
|
||||
All you need to do is register callbacks on your Bevy app like this:
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -38,7 +50,11 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_ecs;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -64,7 +80,10 @@ fn main() {
|
|||
```
|
||||
|
||||
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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -88,36 +107,6 @@ which you can then call in your script like this:
|
|||
fun_with_string_param("Hello world!")
|
||||
```
|
||||
|
||||
It is also possible to split the definition of your callback functions up over multiple plugins. This enables you to split up your code by subject and keep the main initialization light and clean.
|
||||
This can be accomplished by using `add_scripting_api`. Be careful though, `add_scripting` has to be called before adding plugins.
|
||||
```rust
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
||||
struct MyPlugin;
|
||||
impl Plugin for MyPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_scripting_api::<LuaRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("hello_from_my_plugin"), || {
|
||||
info!("Hello from MyPlugin");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<LuaRuntime>(|_| {
|
||||
// nice and clean
|
||||
})
|
||||
.add_plugins(MyPlugin)
|
||||
.run();
|
||||
}
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Add the following to your `Cargo.toml`:
|
||||
|
|
@ -131,7 +120,10 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -159,7 +151,10 @@ my_print("Hello world!")
|
|||
|
||||
And spawn an entity with attached `Script` component with a handle to a script source file:
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -204,7 +199,10 @@ end)
|
|||
```
|
||||
which will print out `John` when used with following exposed function:
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
|
|||
0
book/src/lib.rs
Normal file
0
book/src/lib.rs
Normal file
|
|
@ -20,14 +20,17 @@ bevy_scriptum provides following types that can be used in Lua:
|
|||
|
||||
### Example Lua usage
|
||||
|
||||
```
|
||||
```lua
|
||||
my_vec = Vec3(1, 2, 3)
|
||||
set_translation(entity, my_vec)
|
||||
```
|
||||
|
||||
### Example Rust usage
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -69,7 +72,10 @@ pass_to_rust(entity)
|
|||
|
||||
### Example Rust usage
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
To call a rust function from Lua first you need to register a function
|
||||
within Rust using builder pattern.
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -20,7 +23,10 @@ fn main() {
|
|||
|
||||
For example to register a function called `my_rust_func` you can do the following:
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -49,7 +55,10 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -68,7 +77,10 @@ fn main() {
|
|||
|
||||
To make it look nicer you can destructure the `In` struct.
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -97,7 +109,10 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -29,14 +32,15 @@ fn call_lua_on_update_from_rust(
|
|||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
We can also pass some arguments by providing a tuple or `Vec` as the last
|
||||
`call_fn` argument.
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -51,8 +55,6 @@ fn call_lua_on_update_from_rust(
|
|||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
They will be passed to `on_update` Lua function
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -40,7 +43,10 @@ my_print("Hello world!")
|
|||
|
||||
And spawn an entity with attached `Script` component with a handle to a script source file:
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,11 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_ecs;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -42,7 +46,11 @@ 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
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_ecs;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -59,7 +67,7 @@ fn main() {
|
|||
runtime.add_function(
|
||||
String::from("hurt_player"),
|
||||
|In((hit_value,)): In<(i32,)>, mut players: Query<&mut Player>| {
|
||||
let mut player = players.single_mut();
|
||||
let mut player = players.single_mut().unwrap();
|
||||
player.health -= hit_value;
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ To spawn a Lua script you will need to get a handle to a script asset using
|
|||
bevy's `AssetServer`.
|
||||
|
||||
```rust
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -13,8 +16,6 @@ fn my_spawner(mut commands: Commands, assets_server: Res<AssetServer>) {
|
|||
assets_server.load("my_script.lua"),
|
||||
));
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
After they scripts have been evaled by bevy_scriptum, the entities that they've
|
||||
|
|
@ -24,6 +25,9 @@ been attached to will get the `Script::<LuaScript>` component stripped and inste
|
|||
So to query scipted entities you could do something like:
|
||||
|
||||
```rust
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -35,6 +39,4 @@ fn my_system(
|
|||
// do something with scripted entities
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
|
|
|||
34
book/src/multiple_plugins.md
Normal file
34
book/src/multiple_plugins.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Multiple plugins
|
||||
|
||||
It is possible to split the definition of your callback functions up over multiple plugins. This enables you to split up your code by subject and keep the main initialization light and clean.
|
||||
This can be accomplished by using `add_scripting_api`. Be careful though, `add_scripting` has to be called before adding plugins.
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
||||
struct MyPlugin;
|
||||
impl Plugin for MyPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_scripting_api::<LuaRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("hello_from_my_plugin"), || {
|
||||
info!("Hello from MyPlugin");
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Main
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<LuaRuntime>(|_| {
|
||||
// nice and clean
|
||||
})
|
||||
.add_plugins(MyPlugin)
|
||||
.run();
|
||||
}
|
||||
```
|
||||
95
book/src/ruby/builtin_types.md
Normal file
95
book/src/ruby/builtin_types.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# Builtin types
|
||||
|
||||
bevy_scriptum provides following types that can be used in Ruby:
|
||||
|
||||
- `Bevy::Vec3`
|
||||
- `Bevy::Entity`
|
||||
|
||||
## Bevy::Vec3
|
||||
|
||||
### Class Methods
|
||||
|
||||
- `new(x, y, z)`
|
||||
- `current`
|
||||
|
||||
### Instance Methods
|
||||
|
||||
- `x`
|
||||
- `y`
|
||||
- `z`
|
||||
|
||||
### Example Ruby usage
|
||||
|
||||
```ruby
|
||||
my_vec = Bevy::Vec3.new(1, 2, 3)
|
||||
set_translation(entity, my_vec)
|
||||
```
|
||||
|
||||
### Example Rust usage
|
||||
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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;
|
||||
}
|
||||
```
|
||||
|
||||
## Bevy::Entity
|
||||
|
||||
`Bevy::Entity.current` is currently not available within promise callbacks.
|
||||
|
||||
### Constructor
|
||||
|
||||
None - instances can only be acquired by using `Bevy::Entity.current`
|
||||
|
||||
### Class method
|
||||
|
||||
- `index`
|
||||
|
||||
### Example Ruby usage
|
||||
|
||||
```ruby
|
||||
puts(Bevy::Entity.current.index)
|
||||
pass_to_rust(Bevy::Entity.current)
|
||||
```
|
||||
|
||||
### Example Rust usage
|
||||
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("pass_to_rust"), |In((entity,)): In<(BevyEntity,)>| {
|
||||
println!("pass_to_rust called with entity: {:?}", entity);
|
||||
});
|
||||
})
|
||||
.run();
|
||||
}
|
||||
```
|
||||
133
book/src/ruby/calling_rust_from_script.md
Normal file
133
book/src/ruby/calling_rust_from_script.md
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# Calling Rust from Ruby
|
||||
|
||||
To call a rust function from Ruby first you need to register a function
|
||||
within Rust using builder pattern.
|
||||
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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 Ruby code in your spawned scripts.
|
||||
|
||||
```ruby
|
||||
my_rust_func
|
||||
```
|
||||
|
||||
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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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 Ruby
|
||||
|
||||
```ruby
|
||||
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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("returns_value"), || {
|
||||
123
|
||||
});
|
||||
})
|
||||
.run();
|
||||
}
|
||||
```
|
||||
|
||||
```ruby
|
||||
returns_value.and_then do |value|
|
||||
puts(value) # 123
|
||||
end
|
||||
```
|
||||
66
book/src/ruby/calling_script_from_rust.md
Normal file
66
book/src/ruby/calling_script_from_rust.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Calling Ruby from Rust
|
||||
|
||||
To call a function defined in Ruby
|
||||
|
||||
```ruby
|
||||
def on_update
|
||||
end
|
||||
```
|
||||
|
||||
We need to acquire `RubyRuntime` resource within a bevy system.
|
||||
Then we will be able to call `call_fn` on it, providing the name
|
||||
of the function to call, `RubyScriptData` 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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn call_ruby_on_update_from_rust(
|
||||
mut scripted_entities: Query<(Entity, &mut RubyScriptData)>,
|
||||
scripting_runtime: ResMut<RubyRuntime>,
|
||||
) {
|
||||
for (entity, mut script_data) in &mut scripted_entities {
|
||||
// calling function named `on_update` defined in Ruby script
|
||||
scripting_runtime
|
||||
.call_fn("on_update", &mut script_data, entity, ())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can also pass some arguments by providing a tuple or `Vec` as the last
|
||||
`call_fn` argument.
|
||||
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn call_ruby_on_update_from_rust(
|
||||
mut scripted_entities: Query<(Entity, &mut RubyScriptData)>,
|
||||
scripting_runtime: ResMut<RubyRuntime>,
|
||||
) {
|
||||
for (entity, mut script_data) in &mut scripted_entities {
|
||||
scripting_runtime
|
||||
.call_fn("on_update", &mut script_data, entity, (123, String::from("hello")))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
They will be passed to `on_update` Ruby function
|
||||
```ruby
|
||||
def on_update(a, b)
|
||||
puts(a) # 123
|
||||
puts(b) # hello
|
||||
end
|
||||
```
|
||||
72
book/src/ruby/hello_world.md
Normal file
72
book/src/ruby/hello_world.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# 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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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.rb` that calls this function:
|
||||
|
||||
```ruby
|
||||
my_print("Hello world!")
|
||||
```
|
||||
|
||||
And spawn an entity with attached `Script` component with a handle to a script source file:
|
||||
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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::<RubyScript>::new(asset_server.load("script.rb")));
|
||||
})
|
||||
.run();
|
||||
}
|
||||
```
|
||||
|
||||
You should then see `my_print: 'Hello world!'` printed in your console.
|
||||
50
book/src/ruby/installation.md
Normal file
50
book/src/ruby/installation.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Installation
|
||||
|
||||
## Ruby
|
||||
|
||||
To build `bevy_scriptum` with Ruby support a Ruby installation is needed to be
|
||||
present on your development machine.
|
||||
|
||||
The easiest way to produce a compatible Ruby installation is to use [rbenv](https://rbenv.org/).
|
||||
|
||||
After installing `rbenv` along with its `ruby-build` plugin you can build and
|
||||
install a Ruby installation that will work with `bevy_scriptum` by executing:
|
||||
|
||||
```sh
|
||||
CC=clang rbenv install 3.4.4
|
||||
```
|
||||
|
||||
Above assumes that you also have `clang` installed on your system.
|
||||
For `clang` installation instruction consult your
|
||||
OS vendor provided documentation or [clang official webiste](https://clang.llvm.org).
|
||||
|
||||
If you rather not use `rbenv` you are free to supply your own installation of
|
||||
Ruby provided the following is true about it:
|
||||
|
||||
- it is compiled with `clang`
|
||||
- it is compiled as a static library
|
||||
- it is accessible as `ruby` within `PATH` or `RUBY` environment variable is set
|
||||
to path of desired `ruby` binary.
|
||||
|
||||
## Main Library
|
||||
|
||||
Add the following to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
bevy = "0.16"
|
||||
bevy_scriptum = { version = "0.8", features = ["ruby"] }
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
Ruby also needs dynamic symbol resolution and since `bevy_scriptum` links Ruby
|
||||
statically the following `build.rs` file is needed to be present in project
|
||||
root directory.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("cargo:rustc-link-arg=-rdynamic");
|
||||
}
|
||||
```
|
||||
83
book/src/ruby/interacting_with_bevy.md
Normal file
83
book/src/ruby/interacting_with_bevy.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# 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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_ecs;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Player;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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:
|
||||
|
||||
```ruby
|
||||
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,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_ecs;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Player {
|
||||
health: i32
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(
|
||||
String::from("hurt_player"),
|
||||
|In((hit_value,)): In<(i32,)>, mut players: Query<&mut Player>| {
|
||||
let mut player = players.single_mut().unwrap();
|
||||
player.health -= hit_value;
|
||||
},
|
||||
);
|
||||
})
|
||||
.run();
|
||||
}
|
||||
```
|
||||
|
||||
And it could be called in script like:
|
||||
|
||||
```ruby
|
||||
hurt_player(5)
|
||||
```
|
||||
3
book/src/ruby/ruby.md
Normal file
3
book/src/ruby/ruby.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Ruby
|
||||
|
||||
This chapter demonstrates how to work with bevy_scriptum when using Ruby language runtime.
|
||||
42
book/src/ruby/spawning_scripts.md
Normal file
42
book/src/ruby/spawning_scripts.md
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Spawning scripts
|
||||
|
||||
To spawn a Ruby script you will need to get a handle to a script asset using
|
||||
bevy's `AssetServer`.
|
||||
|
||||
```rust
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn my_spawner(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("my_script.rb"),
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
After they scripts have been evaled by bevy_scriptum, the entities that they've
|
||||
been attached to will get the `Script::<RubyScript>` component stripped and instead
|
||||
```RubyScriptData``` component will be attached.
|
||||
|
||||
So to query scipted entities you could do something like:
|
||||
|
||||
```rust
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn my_system(
|
||||
mut scripted_entities: Query<(Entity, &mut RubyScriptData)>,
|
||||
) {
|
||||
for (entity, mut script_data) in &mut scripted_entities {
|
||||
// do something with scripted entities
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -5,22 +5,22 @@
|
|||
To enable live reload it should be enough to enable `file-watcher` feature
|
||||
within bevy dependency in `Cargo.toml`
|
||||
|
||||
```
|
||||
```toml
|
||||
bevy = { version = "0.16", features = ["file_watcher"] }
|
||||
```
|
||||
|
||||
## Init-teardown pattern for game development
|
||||
## Init-teardown pattern
|
||||
|
||||
It is useful to structure your game in a way that would allow making changes to
|
||||
the scripting code without restarting the game.
|
||||
It is useful to structure your application in a way that would allow making changes to
|
||||
the scripting code without restarting the application.
|
||||
|
||||
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)
|
||||
- "init" function will take care of starting the application(spawning the player, the level etc)
|
||||
|
||||
- "update" function will run the main game logic
|
||||
- "update" function will run the main application logic
|
||||
|
||||
- "teardown" function will despawn all the entities so game starts at fresh state.
|
||||
- "teardown" function will despawn all the entities so application 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:
|
||||
|
|
@ -35,7 +35,7 @@ local function init()
|
|||
player.entity = spawn_player()
|
||||
end
|
||||
|
||||
-- game logic here, should be called in a bevy system using call_fn
|
||||
-- application logic here, should be called in a bevy system using call_fn
|
||||
local function update()
|
||||
(...)
|
||||
end
|
||||
|
|
@ -45,7 +45,7 @@ local function teardown()
|
|||
despawn(player.entity)
|
||||
end
|
||||
|
||||
-- call init to start the game, this will be called on each file-watcher script
|
||||
-- call init to start the application, this will be called on each file-watcher script
|
||||
-- reload
|
||||
init()
|
||||
```
|
||||
|
|
@ -53,6 +53,9 @@ init()
|
|||
The function calls can be implemented on Rust side the following way:
|
||||
|
||||
```rust
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -92,13 +95,14 @@ fn teardown(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
And to tie this all together we do the following:
|
||||
|
||||
```rust
|
||||
```rust,no_run
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
|
|
@ -116,24 +120,25 @@ fn main() {
|
|||
.run();
|
||||
}
|
||||
|
||||
fn init() {} // Implemented elsewhere
|
||||
fn update() {} // Implemented elsewhere
|
||||
fn despawn() {} // Implemented elsewhere
|
||||
fn teardown() {} // Implemented elsewhere
|
||||
fn spawn_player() {} // Implemented elsewhere
|
||||
# fn init() {}
|
||||
# fn update() {}
|
||||
# fn despawn() {}
|
||||
# fn teardown() {}
|
||||
# fn spawn_player() {}
|
||||
```
|
||||
|
||||
`despawn` can be implemented as:
|
||||
|
||||
```rust
|
||||
# extern crate bevy;
|
||||
# extern crate bevy_scriptum;
|
||||
|
||||
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.
|
||||
|
|
|
|||
8
build.rs
Normal file
8
build.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fn main() {
|
||||
#[cfg(feature = "ruby")]
|
||||
{
|
||||
println!("cargo:rustc-link-arg=-rdynamic");
|
||||
println!("cargo:rustc-link-arg=-lz");
|
||||
println!("cargo:rustc-link-lib=z");
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ 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
|
||||
// This is just needed for headless console app, not needed for a regular bevy application
|
||||
// that uses a winit window
|
||||
.set_runner(move |mut app: App| {
|
||||
loop {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ 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
|
||||
// This is just needed for headless console app, not needed for a regular bevy application
|
||||
// that uses a winit window
|
||||
.set_runner(move |mut app: App| loop {
|
||||
app.update();
|
||||
if let Some(exit) = app.should_exit() {
|
||||
return exit;
|
||||
.set_runner(move |mut app: App| {
|
||||
loop {
|
||||
app.update();
|
||||
if let Some(exit) = app.should_exit() {
|
||||
return exit;
|
||||
}
|
||||
}
|
||||
})
|
||||
.add_plugins(DefaultPlugins)
|
||||
|
|
|
|||
33
examples/ruby/call_function_from_rust.rs
Normal file
33
examples/ruby/call_function_from_rust.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use bevy::{app::AppExit, prelude::*};
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, startup)
|
||||
.add_systems(Update, call_ruby_on_update_from_rust)
|
||||
.add_scripting::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("quit"), |mut exit: EventWriter<AppExit>| {
|
||||
exit.write(AppExit::Success);
|
||||
});
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/call_function_from_rust.rb"),
|
||||
));
|
||||
}
|
||||
|
||||
fn call_ruby_on_update_from_rust(
|
||||
mut scripted_entities: Query<(Entity, &mut RubyScriptData)>,
|
||||
scripting_runtime: ResMut<RubyRuntime>,
|
||||
) {
|
||||
for (entity, mut script_data) in &mut scripted_entities {
|
||||
scripting_runtime
|
||||
.call_fn("on_update", &mut script_data, entity, ())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
25
examples/ruby/current_entity.rs
Normal file
25
examples/ruby/current_entity.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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::<RubyScript>::new(assets_server.load("examples/ruby/current_entity.rb")),
|
||||
));
|
||||
}
|
||||
54
examples/ruby/custom_type.rs
Normal file
54
examples/ruby/custom_type.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::ScriptingError;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::magnus;
|
||||
use bevy_scriptum::runtimes::ruby::magnus::Module as _;
|
||||
use bevy_scriptum::runtimes::ruby::magnus::Object as _;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("hello_bevy"), || {
|
||||
println!("hello bevy, called from script");
|
||||
});
|
||||
})
|
||||
.add_systems(Startup, startup)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[magnus::wrap(class = "MyType")]
|
||||
struct MyType {
|
||||
my_field: u32,
|
||||
}
|
||||
|
||||
impl MyType {
|
||||
fn new() -> Self {
|
||||
Self { my_field: 42 }
|
||||
}
|
||||
|
||||
fn my_method(&self) -> u32 {
|
||||
self.my_field
|
||||
}
|
||||
}
|
||||
|
||||
fn startup(
|
||||
mut commands: Commands,
|
||||
scripting_runtime: ResMut<RubyRuntime>,
|
||||
assets_server: Res<AssetServer>,
|
||||
) {
|
||||
scripting_runtime
|
||||
.with_engine_send(|ruby| {
|
||||
let my_type = ruby.define_class("MyType", ruby.class_object())?;
|
||||
my_type.define_singleton_method("new", magnus::function!(MyType::new, 0))?;
|
||||
my_type.define_method("my_method", magnus::method!(MyType::my_method, 0))?;
|
||||
|
||||
Ok::<(), ScriptingError>(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/custom_type.rb"),
|
||||
));
|
||||
}
|
||||
33
examples/ruby/ecs.rs
Normal file
33
examples/ruby/ecs.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Player;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/ecs.rb"),
|
||||
));
|
||||
}
|
||||
17
examples/ruby/entity_variable.rs
Normal file
17
examples/ruby/entity_variable.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
use bevy_scriptum::{prelude::*, BuildScriptingRuntime};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|_| {})
|
||||
.add_systems(Startup, startup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/entity_variable.rb"),
|
||||
));
|
||||
}
|
||||
52
examples/ruby/function_params.rs
Normal file
52
examples/ruby/function_params.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::{RArray, prelude::*};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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, RArray)>, runtime: Res<RubyRuntime>| {
|
||||
runtime.with_engine_send(move |ruby| {
|
||||
println!(
|
||||
"called with i64: {} and dynamically typed array: {:?}",
|
||||
x,
|
||||
ruby.get_inner(y.0)
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
})
|
||||
.add_systems(Startup, startup)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/function_params.rb"),
|
||||
));
|
||||
}
|
||||
42
examples/ruby/function_return_value.rs
Normal file
42
examples/ruby/function_return_value.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use bevy::{app::AppExit, prelude::*};
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
use magnus::TryConvert;
|
||||
use magnus::value::InnerValue;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, startup)
|
||||
.add_systems(Update, call_lua_on_update_from_rust)
|
||||
.add_scripting::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("quit"), |mut exit: EventWriter<AppExit>| {
|
||||
exit.write(AppExit::Success);
|
||||
});
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
fn startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/function_return_value.rb"),
|
||||
));
|
||||
}
|
||||
|
||||
fn call_lua_on_update_from_rust(
|
||||
mut scripted_entities: Query<(Entity, &mut RubyScriptData)>,
|
||||
scripting_runtime: ResMut<RubyRuntime>,
|
||||
mut exit: EventWriter<AppExit>,
|
||||
) {
|
||||
for (entity, mut script_data) in &mut scripted_entities {
|
||||
let val = scripting_runtime
|
||||
.call_fn("get_value", &mut script_data, entity, ())
|
||||
.unwrap()
|
||||
.0;
|
||||
scripting_runtime.with_engine(|ruby| {
|
||||
let val: i32 = TryConvert::try_convert(val.get_inner_with(ruby)).unwrap();
|
||||
println!("script returned: {}", val);
|
||||
});
|
||||
exit.write(AppExit::Success);
|
||||
}
|
||||
}
|
||||
21
examples/ruby/hello_world.rs
Normal file
21
examples/ruby/hello_world.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|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::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/hello_world.rb"),
|
||||
));
|
||||
}
|
||||
67
examples/ruby/multiple_plugins.rs
Normal file
67
examples/ruby/multiple_plugins.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
// Plugin A
|
||||
struct PluginA;
|
||||
impl Plugin for PluginA {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_scripting_api::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("hello_from_plugin_a"), || {
|
||||
info!("Hello from Plugin A");
|
||||
});
|
||||
})
|
||||
.add_systems(Startup, plugin_a_startup);
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_a_startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/multiple_plugins_plugin_a.rb"),
|
||||
));
|
||||
}
|
||||
|
||||
// Plugin B
|
||||
struct PluginB;
|
||||
impl Plugin for PluginB {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_scripting_api::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(
|
||||
String::from("hello_from_plugin_b_with_parameters"),
|
||||
hello_from_b,
|
||||
);
|
||||
})
|
||||
.add_systems(Startup, plugin_b_startup);
|
||||
}
|
||||
}
|
||||
|
||||
fn plugin_b_startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/lua/multiple_plugins_plugin_b.lua"),
|
||||
));
|
||||
}
|
||||
|
||||
fn hello_from_b(In((text, x)): In<(String, i32)>) {
|
||||
info!("{} from Plugin B: {}", text, x);
|
||||
}
|
||||
|
||||
// Main
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|runtime| {
|
||||
runtime.add_function(String::from("hello_bevy"), || {
|
||||
info!("hello bevy, called from script");
|
||||
});
|
||||
})
|
||||
.add_systems(Startup, main_startup)
|
||||
.add_plugins(PluginA)
|
||||
.add_plugins(PluginB)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn main_startup(mut commands: Commands, assets_server: Res<AssetServer>) {
|
||||
commands.spawn(Script::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/hello_world.rb"),
|
||||
));
|
||||
}
|
||||
31
examples/ruby/promises.rs
Normal file
31
examples/ruby/promises.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Player;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_scripting::<RubyRuntime>(|builder| {
|
||||
builder.add_function(
|
||||
String::from("get_player_name"),
|
||||
|player_names: Query<&Name, With<Player>>| {
|
||||
player_names
|
||||
.single()
|
||||
.expect("Missing player_names")
|
||||
.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::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/promises.rb"),
|
||||
));
|
||||
}
|
||||
43
examples/ruby/side_effects.rs
Normal file
43
examples/ruby/side_effects.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use bevy::{app::AppExit, prelude::*};
|
||||
use bevy_scriptum::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
// This is just needed for headless console app, not needed for a regular bevy application
|
||||
// that uses a winit window
|
||||
.set_runner(move |mut app: App| {
|
||||
loop {
|
||||
app.update();
|
||||
if let Some(exit) = app.should_exit() {
|
||||
return exit;
|
||||
}
|
||||
}
|
||||
})
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_systems(Startup, startup)
|
||||
.add_systems(Update, print_entity_names_and_quit)
|
||||
.add_scripting::<RubyRuntime>(|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::<RubyScript>::new(
|
||||
assets_server.load("examples/ruby/side_effects.rb"),
|
||||
),));
|
||||
}
|
||||
|
||||
fn print_entity_names_and_quit(query: Query<&Name>, mut exit: EventWriter<AppExit>) {
|
||||
if !query.is_empty() {
|
||||
for e in &query {
|
||||
println!("{}", e);
|
||||
}
|
||||
exit.write(AppExit::Success);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ use bevy::prelude::*;
|
|||
use core::any::TypeId;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::{promise::Promise, Runtime};
|
||||
use crate::{Runtime, promise::Promise};
|
||||
|
||||
/// A system that can be used to call a script function.
|
||||
pub struct CallbackSystem<R: Runtime> {
|
||||
|
|
@ -65,7 +65,7 @@ where
|
|||
fn into_callback_system(self, world: &mut World) -> CallbackSystem<R>;
|
||||
}
|
||||
|
||||
impl<R: Runtime, Out, FN, Marker> IntoCallbackSystem<R, (), Out, Marker> for FN
|
||||
impl<R: Runtime, Out: Send + 'static, FN, Marker> IntoCallbackSystem<R, (), Out, Marker> for FN
|
||||
where
|
||||
FN: IntoSystem<(), Out, Marker>,
|
||||
Out: for<'a> IntoRuntimeValueWithEngine<'a, Out, R>,
|
||||
|
|
@ -77,8 +77,10 @@ where
|
|||
let result = inner_system.run((), world);
|
||||
inner_system.apply_deferred(world);
|
||||
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))
|
||||
|
||||
runtime.with_engine_send_mut(move |engine| {
|
||||
Out::into_runtime_value_with_engine(result, engine)
|
||||
})
|
||||
};
|
||||
let system = IntoSystem::into_system(system_fn);
|
||||
CallbackSystem {
|
||||
|
|
@ -90,27 +92,31 @@ where
|
|||
|
||||
macro_rules! impl_tuple {
|
||||
($($idx:tt $t:tt),+) => {
|
||||
impl<RN: Runtime, $($t,)+ Out, FN, Marker> IntoCallbackSystem<RN, In<($($t,)+)>, Out, Marker>
|
||||
impl<RN: Runtime, $($t,)+ Out: Send + 'static, FN, Marker> IntoCallbackSystem<RN, In<($($t,)+)>, Out, Marker>
|
||||
for FN
|
||||
where
|
||||
FN: IntoSystem<In<($($t,)+)>, Out, Marker>,
|
||||
Out: for<'a> IntoRuntimeValueWithEngine<'a, Out, RN>,
|
||||
$($t: 'static + for<'a> FromRuntimeValueWithEngine<'a, RN>,)+
|
||||
$($t: Send + 'static + for<'a> FromRuntimeValueWithEngine<'a, RN>,)+
|
||||
{
|
||||
fn into_callback_system(self, world: &mut World) -> CallbackSystem<RN> {
|
||||
let mut inner_system = IntoSystem::into_system(self);
|
||||
inner_system.initialize(world);
|
||||
let system_fn = move |args: In<Vec<RN::Value>>, world: &mut World| {
|
||||
let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource");
|
||||
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 args =
|
||||
runtime.with_engine_send_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);
|
||||
inner_system.apply_deferred(world);
|
||||
let mut runtime = world.get_resource_mut::<RN>().expect("No runtime resource");
|
||||
runtime.with_engine_mut(move |engine| {
|
||||
|
||||
runtime.with_engine_send_mut(move |engine| {
|
||||
Out::into_runtime_value_with_engine(result, engine)
|
||||
})
|
||||
};
|
||||
|
|
|
|||
142
src/lib.rs
142
src/lib.rs
|
|
@ -1,12 +1,18 @@
|
|||
//! 
|
||||
//!
|
||||
//! 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.
|
||||
//! bevy_scriptum is a a plugin for [Bevy](https://bevyengine.org/) that allows you to write some of your game or application logic in a scripting language.
|
||||
|
||||
//! ## Supported scripting languages/runtimes
|
||||
//!
|
||||
//! Everything you need to know to get started with using this library is contained in the
|
||||
//! [bevy_scriptum book](https://jarkonik.github.io/bevy_scriptum/)
|
||||
//! | language/runtime | cargo feature | documentation chapter |
|
||||
//! | ----------------- | ------------- | --------------------------------------------------------------- |
|
||||
//! | 🌙 LuaJIT | `lua` | [link](https://jarkonik.github.io/bevy_scriptum/lua/lua.html) |
|
||||
//! | 🌾 Rhai | `rhai` | [link](https://jarkonik.github.io/bevy_scriptum/rhai/rhai.html) |
|
||||
//! | 💎 Ruby | `ruby` | [link](https://jarkonik.github.io/bevy_scriptum/ruby/ruby.html) |
|
||||
//!
|
||||
//! API docs are available in [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/)
|
||||
//! Documentation book is available [here](https://jarkonik.github.io/bevy_scriptum/) 📖
|
||||
//!
|
||||
//! Full API docs are available at [docs.rs](https://docs.rs/bevy_scriptum/latest/bevy_scriptum/) 🧑💻
|
||||
//!
|
||||
//! bevy_scriptum's main advantages include:
|
||||
//! - low-boilerplate
|
||||
|
|
@ -15,14 +21,16 @@
|
|||
//! - 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.
|
||||
//! Scripts are separate files that can be hot-reloaded at runtime. This allows you to quickly iterate on your game or application logic without having to recompile it.
|
||||
//!
|
||||
//! All you need to do is register callbacks on your Bevy app like this:
|
||||
//! ```no_run
|
||||
//! use bevy::prelude::*;
|
||||
//! use bevy_scriptum::prelude::*;
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
//!
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! App::new()
|
||||
//! .add_plugins(DefaultPlugins)
|
||||
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||
|
|
@ -42,11 +50,13 @@
|
|||
//! ```no_run
|
||||
//! use bevy::prelude::*;
|
||||
//! use bevy_scriptum::prelude::*;
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
//!
|
||||
//! #[derive(Component)]
|
||||
//! struct Player;
|
||||
//!
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! App::new()
|
||||
//! .add_plugins(DefaultPlugins)
|
||||
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||
|
|
@ -66,8 +76,10 @@
|
|||
//! ```no_run
|
||||
//! use bevy::prelude::*;
|
||||
//! use bevy_scriptum::prelude::*;
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
//!
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! App::new()
|
||||
//! .add_plugins(DefaultPlugins)
|
||||
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||
|
|
@ -84,33 +96,6 @@
|
|||
//! ```lua
|
||||
//! fun_with_string_param("Hello world!")
|
||||
//! ```
|
||||
//! It is also possible to split the definition of your callback functions up over multiple plugins. This enables you to split up your code by subject and keep the main initialization light and clean.
|
||||
//! This can be accomplished by using `add_scripting_api`. Be careful though, `add_scripting` has to be called before adding plugins.
|
||||
//! ```no_run
|
||||
//! use bevy::prelude::*;
|
||||
//! use bevy_scriptum::prelude::*;
|
||||
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
//!
|
||||
//! struct MyPlugin;
|
||||
//! impl Plugin for MyPlugin {
|
||||
//! fn build(&self, app: &mut App) {
|
||||
//! app.add_scripting_api::<LuaRuntime>(|runtime| {
|
||||
//! runtime.add_function(String::from("hello_from_my_plugin"), || {
|
||||
//! info!("Hello from MyPlugin");
|
||||
//! });
|
||||
//! });
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! App::new()
|
||||
//! .add_plugins(DefaultPlugins)
|
||||
//! .add_scripting::<LuaRuntime>(|_| {
|
||||
//! // nice and clean
|
||||
//! })
|
||||
//! .add_plugins(MyPlugin)
|
||||
//! .run();
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
|
|
@ -128,8 +113,10 @@
|
|||
//! ```no_run
|
||||
//! use bevy::prelude::*;
|
||||
//! use bevy_scriptum::prelude::*;
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
//!
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! App::new()
|
||||
//! .add_plugins(DefaultPlugins)
|
||||
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||
|
|
@ -154,8 +141,10 @@
|
|||
//! ```no_run
|
||||
//! use bevy::prelude::*;
|
||||
//! use bevy_scriptum::prelude::*;
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
//!
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! App::new()
|
||||
//! .add_plugins(DefaultPlugins)
|
||||
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||
|
|
@ -209,8 +198,10 @@
|
|||
//! ```
|
||||
//! use bevy::prelude::*;
|
||||
//! use bevy_scriptum::prelude::*;
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! use bevy_scriptum::runtimes::lua::prelude::*;
|
||||
//!
|
||||
//! # #[cfg(feature = "lua")]
|
||||
//! App::new()
|
||||
//! .add_plugins(DefaultPlugins)
|
||||
//! .add_scripting::<LuaRuntime>(|runtime| {
|
||||
|
|
@ -259,7 +250,11 @@ use std::{
|
|||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use bevy::{app::MainScheduleOrder, ecs::{component::Mutable, schedule::ScheduleLabel}, prelude::*};
|
||||
use bevy::{
|
||||
app::MainScheduleOrder,
|
||||
ecs::{component::Mutable, schedule::ScheduleLabel},
|
||||
prelude::*,
|
||||
};
|
||||
use callback::{Callback, IntoCallbackSystem};
|
||||
use systems::{init_callbacks, log_errors, process_calls};
|
||||
use thiserror::Error;
|
||||
|
|
@ -275,10 +270,10 @@ const ENTITY_VAR_NAME: &str = "entity";
|
|||
/// An error that can occur when internal [ScriptingPlugin] systems are being executed
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ScriptingError {
|
||||
#[error("script runtime error: {0}")]
|
||||
RuntimeError(Box<dyn std::error::Error>),
|
||||
#[error("script compilation error: {0}")]
|
||||
CompileError(Box<dyn std::error::Error>),
|
||||
#[error("script runtime error:\n{0}")]
|
||||
RuntimeError(String),
|
||||
#[error("script compilation error:\n{0}")]
|
||||
CompileError(Box<dyn std::error::Error + Send>),
|
||||
#[error("no runtime resource present")]
|
||||
NoRuntimeResource,
|
||||
#[error("no settings resource present")]
|
||||
|
|
@ -299,11 +294,39 @@ pub trait Runtime: Resource + Default {
|
|||
/// Provides mutable reference to raw scripting engine instance.
|
||||
/// Can be used to directly interact with an interpreter to use interfaces
|
||||
/// that bevy_scriptum does not provided adapters for.
|
||||
/// Using this function make the closure be executed on another thread for
|
||||
/// some runtimes. If you need to operate on non-`'static` borrows and/or
|
||||
/// `!Send` data, you can use `with_engine_mut` - it may not be implemented
|
||||
/// for some of the runtimes though.
|
||||
fn with_engine_send_mut<T: Send + 'static>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T;
|
||||
|
||||
/// Provides immutable reference to raw scripting engine instance.
|
||||
/// Can be used to directly interact with an interpreter to use interfaces
|
||||
/// that bevy_scriptum does not provided adapters for.
|
||||
/// Using this function make the closure be executed on another thread for
|
||||
/// some runtimes. If you need to operate on non-`'static` borrows and/or
|
||||
/// `!Send` data, you can use `with_engine` - it may not be implemented
|
||||
/// for some of the runtimes though.
|
||||
fn with_engine_send<T: Send + 'static>(
|
||||
&self,
|
||||
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T;
|
||||
|
||||
/// Provides mutable reference to raw scripting engine instance.
|
||||
/// Can be used to directly interact with an interpreter to use interfaces
|
||||
/// that bevy_scriptum does not provided adapters for.
|
||||
/// May not be implemented for runtimes which require the closure to pass
|
||||
/// thread boundary - use `with_engine_send_mut` then.
|
||||
fn with_engine_mut<T>(&mut self, f: impl FnOnce(&mut Self::RawEngine) -> T) -> T;
|
||||
|
||||
/// Provides immutable reference to raw scripting engine instance.
|
||||
/// Can be used to directly interact with an interpreter to use interfaces
|
||||
/// that bevy_scriptum does not provided adapters for.
|
||||
/// May not be implemented for runtimes which require the closure to pass
|
||||
/// thread boundary - use `with_engine_send` then.
|
||||
fn with_engine<T>(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T;
|
||||
|
||||
fn eval(
|
||||
|
|
@ -320,12 +343,12 @@ pub trait Runtime: Resource + Default {
|
|||
name: String,
|
||||
arg_types: Vec<TypeId>,
|
||||
f: impl Fn(
|
||||
Self::CallContext,
|
||||
Vec<Self::Value>,
|
||||
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
Self::CallContext,
|
||||
Vec<Self::Value>,
|
||||
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Result<(), ScriptingError>;
|
||||
|
||||
/// Calls a function by name defined within the runtime in the context of the
|
||||
|
|
@ -336,7 +359,7 @@ pub trait Runtime: Resource + Default {
|
|||
name: &str,
|
||||
script_data: &mut Self::ScriptData,
|
||||
entity: Entity,
|
||||
args: impl for<'a> FuncArgs<'a, Self::Value, Self>,
|
||||
args: impl for<'a> FuncArgs<'a, Self::Value, Self> + Send + 'static,
|
||||
) -> Result<Self::Value, ScriptingError>;
|
||||
|
||||
/// Calls a function by value defined within the runtime in the context of the
|
||||
|
|
@ -348,6 +371,10 @@ pub trait Runtime: Resource + Default {
|
|||
context: &Self::CallContext,
|
||||
args: Vec<Self::Value>,
|
||||
) -> Result<Self::Value, ScriptingError>;
|
||||
|
||||
fn needs_rdynamic_linking() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FuncArgs<'a, V, R: Runtime> {
|
||||
|
|
@ -410,6 +437,17 @@ impl BuildScriptingRuntime for App {
|
|||
/// 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 {
|
||||
#[cfg(debug_assertions)]
|
||||
if R::needs_rdynamic_linking() && !is_rdynamic_linking() {
|
||||
panic!(
|
||||
"Missing `-rdynamic`: symbol resolution failed.\n\
|
||||
It is needed by {:?}.\n\
|
||||
Please add `println!(\"cargo:rustc-link-arg=-rdynamic\");` to your build.rs\n\
|
||||
or set `RUSTFLAGS=\"-C link-arg=-rdynamic\"`.",
|
||||
std::any::type_name::<R>()
|
||||
);
|
||||
}
|
||||
|
||||
self.world_mut()
|
||||
.resource_mut::<MainScheduleOrder>()
|
||||
.insert_after(Update, R::Schedule::default());
|
||||
|
|
@ -472,6 +510,20 @@ impl<R: Runtime> Default for Callbacks<R> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub extern "C" fn is_rdynamic_linking() -> bool {
|
||||
unsafe {
|
||||
// Get a function pointer to itself
|
||||
let addr = is_rdynamic_linking as *const libc::c_void;
|
||||
let mut info: libc::Dl_info = std::mem::zeroed();
|
||||
|
||||
// Try to resolve symbol info
|
||||
let result = libc::dladdr(addr, &mut info);
|
||||
|
||||
result != 0 && !info.dli_sname.is_null()
|
||||
}
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{BuildScriptingRuntime as _, Runtime as _, Script};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ impl<C: Clone + Send + 'static, V: Send + Clone> Promise<C, V> {
|
|||
}
|
||||
|
||||
/// Register a callback that will be called when the [Promise] is resolved.
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
pub(crate) fn then(&mut self, callback: V) -> Self {
|
||||
let mut inner = self
|
||||
.inner
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ use serde::Deserialize;
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::{
|
||||
ENTITY_VAR_NAME, FuncArgs, Runtime, ScriptingError,
|
||||
assets::GetExtensions,
|
||||
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
|
||||
promise::Promise,
|
||||
FuncArgs, Runtime, ScriptingError, ENTITY_VAR_NAME,
|
||||
};
|
||||
|
||||
type LuaEngine = Arc<Mutex<Lua>>;
|
||||
|
|
@ -41,6 +41,12 @@ pub struct LuaRuntime {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BevyEntity(pub Entity);
|
||||
|
||||
impl BevyEntity {
|
||||
pub fn index(&self) -> u32 {
|
||||
self.0.index()
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for BevyEntity {}
|
||||
|
||||
impl FromLua<'_> for BevyEntity {
|
||||
|
|
@ -58,6 +64,24 @@ impl FromLua<'_> for BevyEntity {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BevyVec3(pub Vec3);
|
||||
|
||||
impl BevyVec3 {
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
BevyVec3(Vec3 { x, y, z })
|
||||
}
|
||||
|
||||
pub fn x(&self) -> f32 {
|
||||
self.0.x
|
||||
}
|
||||
|
||||
pub fn y(&self) -> f32 {
|
||||
self.0.y
|
||||
}
|
||||
|
||||
pub fn z(&self) -> f32 {
|
||||
self.0.z
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for BevyVec3 {}
|
||||
|
||||
impl FromLua<'_> for BevyVec3 {
|
||||
|
|
@ -163,7 +187,7 @@ impl Runtime for LuaRuntime {
|
|||
.expect("Error clearing entity global variable");
|
||||
result
|
||||
})
|
||||
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?;
|
||||
Ok(LuaScriptData)
|
||||
}
|
||||
|
||||
|
|
@ -172,14 +196,14 @@ impl Runtime for LuaRuntime {
|
|||
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,
|
||||
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
|
||||
|
|
@ -212,14 +236,14 @@ impl Runtime for LuaRuntime {
|
|||
let func = engine
|
||||
.globals()
|
||||
.get::<_, Function>(name)
|
||||
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?;
|
||||
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)))?;
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?;
|
||||
engine
|
||||
.globals()
|
||||
.set(ENTITY_VAR_NAME, mlua::Value::Nil)
|
||||
|
|
@ -237,13 +261,13 @@ impl Runtime for LuaRuntime {
|
|||
self.with_engine(|engine| {
|
||||
let val = engine
|
||||
.registry_value::<Function>(&value.0)
|
||||
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?;
|
||||
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)))?;
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?;
|
||||
Ok(LuaValue::new(engine, result))
|
||||
})
|
||||
}
|
||||
|
|
@ -257,6 +281,20 @@ impl Runtime for LuaRuntime {
|
|||
let engine = self.engine.lock().unwrap();
|
||||
f(&engine)
|
||||
}
|
||||
|
||||
fn with_engine_send_mut<T: Send + 'static>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.with_engine_mut(f)
|
||||
}
|
||||
|
||||
fn with_engine_send<T: Send + 'static>(
|
||||
&self,
|
||||
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.with_engine(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: IntoLuaMulti<'a>> IntoRuntimeValueWithEngine<'a, T, LuaRuntime> for T {
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@
|
|||
pub mod lua;
|
||||
#[cfg(feature = "rhai")]
|
||||
pub mod rhai;
|
||||
#[cfg(feature = "ruby")]
|
||||
pub mod ruby;
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ use rhai::{CallFnOptions, Dynamic, Engine, FnPtr, Scope, Variant};
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
ENTITY_VAR_NAME, FuncArgs, Runtime, ScriptingError,
|
||||
assets::GetExtensions,
|
||||
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
|
||||
promise::Promise,
|
||||
FuncArgs, Runtime, ScriptingError, ENTITY_VAR_NAME,
|
||||
};
|
||||
|
||||
#[derive(Asset, Debug, Deserialize, TypePath)]
|
||||
|
|
@ -50,6 +50,36 @@ pub struct RhaiScriptData {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct RhaiValue(pub rhai::Dynamic);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BevyEntity(pub Entity);
|
||||
|
||||
impl BevyEntity {
|
||||
pub fn index(&self) -> u32 {
|
||||
self.0.index()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BevyVec3(pub Vec3);
|
||||
|
||||
impl BevyVec3 {
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(Vec3::new(x, y, z))
|
||||
}
|
||||
|
||||
pub fn x(&self) -> f32 {
|
||||
self.0.x
|
||||
}
|
||||
|
||||
pub fn y(&self) -> f32 {
|
||||
self.0.y
|
||||
}
|
||||
|
||||
pub fn z(&self) -> f32 {
|
||||
self.0.z
|
||||
}
|
||||
}
|
||||
|
||||
impl Runtime for RhaiRuntime {
|
||||
type Schedule = RhaiSchedule;
|
||||
type ScriptAsset = RhaiScript;
|
||||
|
|
@ -65,7 +95,7 @@ impl Runtime for RhaiRuntime {
|
|||
entity: Entity,
|
||||
) -> Result<Self::ScriptData, ScriptingError> {
|
||||
let mut scope = Scope::new();
|
||||
scope.push(ENTITY_VAR_NAME, entity);
|
||||
scope.push(ENTITY_VAR_NAME, BevyEntity(entity));
|
||||
|
||||
let engine = &self.engine;
|
||||
|
||||
|
|
@ -75,9 +105,9 @@ impl Runtime for RhaiRuntime {
|
|||
|
||||
engine
|
||||
.run_ast_with_scope(&mut scope, &ast)
|
||||
.map_err(|e| ScriptingError::RuntimeError(Box::new(e)))?;
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?;
|
||||
|
||||
scope.remove::<Entity>(ENTITY_VAR_NAME).unwrap();
|
||||
scope.remove::<BevyEntity>(ENTITY_VAR_NAME).unwrap();
|
||||
|
||||
Ok(Self::ScriptData { ast, scope })
|
||||
}
|
||||
|
|
@ -87,12 +117,12 @@ impl Runtime for RhaiRuntime {
|
|||
name: String,
|
||||
arg_types: Vec<std::any::TypeId>,
|
||||
f: impl Fn(
|
||||
Self::CallContext,
|
||||
Vec<Self::Value>,
|
||||
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
Self::CallContext,
|
||||
Vec<Self::Value>,
|
||||
) -> Result<Promise<Self::CallContext, Self::Value>, ScriptingError>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
) -> Result<(), ScriptingError> {
|
||||
self.engine
|
||||
.register_raw_fn(name, arg_types, move |context, args| {
|
||||
|
|
@ -113,7 +143,7 @@ impl Runtime for RhaiRuntime {
|
|||
) -> Result<RhaiValue, ScriptingError> {
|
||||
let ast = script_data.ast.clone();
|
||||
let scope = &mut script_data.scope;
|
||||
scope.push(ENTITY_VAR_NAME, entity);
|
||||
scope.push(ENTITY_VAR_NAME, BevyEntity(entity));
|
||||
let options = CallFnOptions::new().eval_ast(false);
|
||||
let args = args
|
||||
.parse(&self.engine)
|
||||
|
|
@ -123,10 +153,10 @@ impl Runtime for RhaiRuntime {
|
|||
let result = self
|
||||
.engine
|
||||
.call_fn_with_options::<Dynamic>(options, scope, &ast, name, args);
|
||||
scope.remove::<Entity>(ENTITY_VAR_NAME).unwrap();
|
||||
scope.remove::<BevyEntity>(ENTITY_VAR_NAME).unwrap();
|
||||
match result {
|
||||
Ok(val) => Ok(RhaiValue(val)),
|
||||
Err(e) => Err(ScriptingError::RuntimeError(Box::new(e))),
|
||||
Err(e) => Err(ScriptingError::RuntimeError(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -143,11 +173,11 @@ impl Runtime for RhaiRuntime {
|
|||
|
||||
let result = if args.len() == 1 && args.first().unwrap().0.is_unit() {
|
||||
f.call_raw(ctx, None, [])
|
||||
.map_err(|e| ScriptingError::RuntimeError(e))?
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?
|
||||
} else {
|
||||
let args = args.into_iter().map(|a| a.0).collect::<Vec<Dynamic>>();
|
||||
f.call_raw(ctx, None, args)
|
||||
.map_err(|e| ScriptingError::RuntimeError(e))?
|
||||
.map_err(|e| ScriptingError::RuntimeError(e.to_string()))?
|
||||
};
|
||||
|
||||
Ok(RhaiValue(result))
|
||||
|
|
@ -160,6 +190,20 @@ impl Runtime for RhaiRuntime {
|
|||
fn with_engine<T>(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T {
|
||||
f(&self.engine)
|
||||
}
|
||||
|
||||
fn with_engine_send_mut<T: Send + 'static>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.with_engine_mut(f)
|
||||
}
|
||||
|
||||
fn with_engine_send<T: Send + 'static>(
|
||||
&self,
|
||||
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.with_engine(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RhaiRuntime {
|
||||
|
|
@ -167,8 +211,8 @@ impl Default for RhaiRuntime {
|
|||
let mut engine = Engine::new();
|
||||
|
||||
engine
|
||||
.register_type_with_name::<Entity>("Entity")
|
||||
.register_get("index", |entity: &mut Entity| entity.index());
|
||||
.register_type_with_name::<BevyEntity>("Entity")
|
||||
.register_get("index", |entity: &mut BevyEntity| entity.index());
|
||||
#[allow(deprecated)]
|
||||
engine
|
||||
.register_type_with_name::<Promise<rhai::NativeCallContextStore, RhaiValue>>("Promise")
|
||||
|
|
@ -181,13 +225,13 @@ impl Default for RhaiRuntime {
|
|||
);
|
||||
|
||||
engine
|
||||
.register_type_with_name::<Vec3>("Vec3")
|
||||
.register_type_with_name::<BevyVec3>("Vec3")
|
||||
.register_fn("new_vec3", |x: f64, y: f64, z: f64| {
|
||||
Vec3::new(x as f32, y as f32, z as f32)
|
||||
BevyVec3(Vec3::new(x as f32, y as f32, z as f32))
|
||||
})
|
||||
.register_get("x", |vec: &mut Vec3| vec.x as f64)
|
||||
.register_get("y", |vec: &mut Vec3| vec.y as f64)
|
||||
.register_get("z", |vec: &mut Vec3| vec.z as f64);
|
||||
.register_get("x", |vec: &mut BevyVec3| vec.x() as f64)
|
||||
.register_get("y", |vec: &mut BevyVec3| vec.y() as f64)
|
||||
.register_get("z", |vec: &mut BevyVec3| vec.z() as f64);
|
||||
#[allow(deprecated)]
|
||||
engine.on_def_var(|_, info, _| Ok(info.name != "entity"));
|
||||
|
||||
|
|
@ -221,7 +265,7 @@ impl<T: Clone + 'static> FromRuntimeValueWithEngine<'_, RhaiRuntime> for T {
|
|||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{RhaiRuntime, RhaiScript, RhaiScriptData};
|
||||
pub use super::{BevyEntity, BevyVec3, RhaiRuntime, RhaiScript, RhaiScriptData};
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple {
|
||||
|
|
|
|||
599
src/runtimes/ruby.rs
Normal file
599
src/runtimes/ruby.rs
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
ffi::CString,
|
||||
sync::{Arc, Condvar, LazyLock, Mutex},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use ::magnus::{typed_data::Inspect, value::Opaque};
|
||||
use bevy::{
|
||||
asset::Asset,
|
||||
ecs::{component::Component, entity::Entity, resource::Resource, schedule::ScheduleLabel},
|
||||
math::Vec3,
|
||||
reflect::TypePath,
|
||||
};
|
||||
use magnus::{
|
||||
DataType, DataTypeFunctions, IntoValue, Object, RClass, RModule, Ruby, TryConvert, TypedData,
|
||||
block::Proc,
|
||||
data_type_builder, function,
|
||||
value::{Lazy, ReprValue},
|
||||
};
|
||||
use magnus::{method, prelude::*};
|
||||
use rb_sys::{VALUE, ruby_init_stack};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
FuncArgs, Runtime, ScriptingError,
|
||||
assets::GetExtensions,
|
||||
callback::{FromRuntimeValueWithEngine, IntoRuntimeValueWithEngine},
|
||||
promise::Promise,
|
||||
};
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct RubyRuntime {
|
||||
ruby_thread: Option<RubyThread>,
|
||||
}
|
||||
|
||||
#[derive(ScheduleLabel, Clone, PartialEq, Eq, Debug, Hash, Default)]
|
||||
pub struct RubySchedule;
|
||||
|
||||
#[derive(Asset, Debug, Deserialize, TypePath)]
|
||||
pub struct RubyScript(pub String);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct RubyScriptData;
|
||||
|
||||
impl GetExtensions for RubyScript {
|
||||
fn extensions() -> &'static [&'static str] {
|
||||
&["rb"]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for RubyScript {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
type RubyClosure = Box<dyn FnOnce(Ruby) + Send>;
|
||||
|
||||
struct RubyThread {
|
||||
sender: crossbeam_channel::Sender<RubyClosure>,
|
||||
handle: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
static RUBY_THREAD: LazyLock<Arc<(Mutex<Option<RubyThread>>, Condvar)>> =
|
||||
LazyLock::new(|| Arc::new((Mutex::new(Some(RubyThread::spawn())), Condvar::new())));
|
||||
|
||||
impl RubyThread {
|
||||
fn build_ruby_process_argv() -> anyhow::Result<Vec<*mut i8>> {
|
||||
Ok(vec![
|
||||
CString::new("ruby")?.into_raw(),
|
||||
CString::new("-e")?.into_raw(),
|
||||
CString::new("")?.into_raw(),
|
||||
])
|
||||
}
|
||||
|
||||
fn spawn() -> Self {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded::<Box<dyn FnOnce(Ruby) + Send>>();
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
unsafe {
|
||||
let mut variable_in_this_stack_frame: VALUE = 0;
|
||||
ruby_init_stack(&mut variable_in_this_stack_frame as *mut VALUE as *mut _);
|
||||
|
||||
rb_sys::ruby_init();
|
||||
|
||||
let mut argv =
|
||||
Self::build_ruby_process_argv().expect("Failed to build ruby process args");
|
||||
rb_sys::ruby_options(argv.len() as i32, argv.as_mut_ptr());
|
||||
};
|
||||
while let Ok(f) = receiver.recv() {
|
||||
let ruby = Ruby::get().expect("Failed to get a handle to Ruby API");
|
||||
f(ruby);
|
||||
}
|
||||
unsafe {
|
||||
rb_sys::ruby_finalize();
|
||||
}
|
||||
});
|
||||
|
||||
RubyThread {
|
||||
sender,
|
||||
handle: Some(handle),
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<T: Send + 'static>(&self, f: Box<dyn FnOnce(Ruby) -> T + Send>) -> T {
|
||||
let (return_sender, return_receiver) = crossbeam_channel::bounded(0);
|
||||
self.sender
|
||||
.send(Box::new(move |ruby| {
|
||||
return_sender
|
||||
.send(f(ruby))
|
||||
.expect("Failed to send callback return value");
|
||||
}))
|
||||
.expect("Faild to send execution unit to Ruby thread");
|
||||
return_receiver
|
||||
.recv()
|
||||
.expect("Failed to receive callback return value")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RubyThread {
|
||||
fn drop(&mut self) {
|
||||
let handle = self.handle.take().expect("No Ruby thread to join");
|
||||
handle.join().expect("Failed to join Ruby thread");
|
||||
}
|
||||
}
|
||||
|
||||
impl DataTypeFunctions for Promise<(), RubyValue> {}
|
||||
|
||||
unsafe impl TypedData for Promise<(), RubyValue> {
|
||||
fn class(ruby: &Ruby) -> magnus::RClass {
|
||||
static CLASS: Lazy<RClass> = Lazy::new(|ruby| {
|
||||
let class = ruby
|
||||
.define_module("Bevy")
|
||||
.expect("Failed to define Bevy module")
|
||||
.define_class("Promise", ruby.class_object())
|
||||
.expect("Failed to define Bevy::Promise class in Ruby");
|
||||
class.undef_default_alloc_func();
|
||||
class
|
||||
});
|
||||
ruby.get_inner(&CLASS)
|
||||
}
|
||||
|
||||
fn data_type() -> &'static magnus::DataType {
|
||||
static DATA_TYPE: DataType =
|
||||
data_type_builder!(Promise<(), RubyValue>, "Bevy::Promise").build();
|
||||
&DATA_TYPE
|
||||
}
|
||||
}
|
||||
|
||||
impl TryConvert for Promise<(), RubyValue> {
|
||||
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
|
||||
let result: Result<&Self, _> = TryConvert::try_convert(val);
|
||||
result.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
fn then(r_self: magnus::Value) -> magnus::Value {
|
||||
let promise: &Promise<(), RubyValue> =
|
||||
TryConvert::try_convert(r_self).expect("Couldn't convert self to Promise");
|
||||
let ruby =
|
||||
Ruby::get().expect("Failed to get a handle to Ruby API when registering Promise callback");
|
||||
promise
|
||||
.clone()
|
||||
.then(RubyValue::new(
|
||||
if ruby.block_given() {
|
||||
ruby.block_proc()
|
||||
.expect("Failed to create Proc for Promise")
|
||||
} else {
|
||||
ruby.proc_new(|ruby, _, _| ruby.qnil().as_value())
|
||||
}
|
||||
.as_value(),
|
||||
))
|
||||
.into_value()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[magnus::wrap(class = "Bevy::Entity")]
|
||||
pub struct BevyEntity(pub Entity);
|
||||
|
||||
impl BevyEntity {
|
||||
pub fn index(&self) -> u32 {
|
||||
self.0.index()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryConvert for BevyEntity {
|
||||
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
|
||||
let result: Result<&Self, _> = TryConvert::try_convert(val);
|
||||
result.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[magnus::wrap(class = "Bevy::Vec3")]
|
||||
pub struct BevyVec3(pub Vec3);
|
||||
|
||||
impl BevyVec3 {
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(Vec3::new(x, y, z))
|
||||
}
|
||||
|
||||
pub fn x(&self) -> f32 {
|
||||
self.0.x
|
||||
}
|
||||
|
||||
pub fn y(&self) -> f32 {
|
||||
self.0.y
|
||||
}
|
||||
|
||||
pub fn z(&self) -> f32 {
|
||||
self.0.z
|
||||
}
|
||||
}
|
||||
|
||||
impl TryConvert for BevyVec3 {
|
||||
fn try_convert(val: magnus::Value) -> Result<Self, magnus::Error> {
|
||||
let result: Result<&Self, _> = TryConvert::try_convert(val);
|
||||
result.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<magnus::Error> for ScriptingError {
|
||||
fn from(value: magnus::Error) -> Self {
|
||||
ScriptingError::RuntimeError(format!(
|
||||
"{}\nbacktrace:\n{}\n",
|
||||
value.inspect(),
|
||||
value
|
||||
.value()
|
||||
.expect("No error value")
|
||||
.funcall::<_, _, magnus::RArray>("backtrace", ())
|
||||
.expect("Failed to get backtrace")
|
||||
.to_vec::<String>()
|
||||
.expect("Failed to convert backtrace to vec of strings")
|
||||
.join("\n"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RubyRuntime {
|
||||
fn default() -> Self {
|
||||
let (lock, cvar) = &*Arc::clone(&RUBY_THREAD);
|
||||
let mut ruby_thread = lock.lock().expect("Failed to acquire lock on Ruby thread");
|
||||
|
||||
while ruby_thread.is_none() {
|
||||
ruby_thread = cvar
|
||||
.wait(ruby_thread)
|
||||
.expect("Failed to acquire lock on Ruby thread after waiting");
|
||||
}
|
||||
let ruby_thread = ruby_thread.take().expect("Ruby thread is not available");
|
||||
cvar.notify_all();
|
||||
|
||||
ruby_thread
|
||||
.execute(Box::new(|ruby| {
|
||||
let module = ruby.define_module("Bevy")?;
|
||||
|
||||
let entity = module.define_class("Entity", ruby.class_object())?;
|
||||
entity.class().define_method(
|
||||
"current",
|
||||
method!(
|
||||
|r_self: RClass| { r_self.ivar_get::<_, BevyEntity>("_current") },
|
||||
0
|
||||
),
|
||||
)?;
|
||||
entity.define_method("index", method!(BevyEntity::index, 0))?;
|
||||
|
||||
let promise = module.define_class("Promise", ruby.class_object())?;
|
||||
promise.define_method("and_then", magnus::method!(then, 0))?;
|
||||
|
||||
let vec3 = module.define_class("Vec3", ruby.class_object())?;
|
||||
vec3.define_singleton_method("new", function!(BevyVec3::new, 3))?;
|
||||
vec3.define_method("x", method!(BevyVec3::x, 0))?;
|
||||
vec3.define_method("y", method!(BevyVec3::y, 0))?;
|
||||
vec3.define_method("z", method!(BevyVec3::z, 0))?;
|
||||
Ok::<(), ScriptingError>(())
|
||||
}))
|
||||
.expect("Failed to define builtin types");
|
||||
Self {
|
||||
ruby_thread: Some(ruby_thread),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RubyRuntime {
|
||||
fn drop(&mut self) {
|
||||
let (lock, cvar) = &*Arc::clone(&RUBY_THREAD);
|
||||
let mut ruby_thread = lock
|
||||
.lock()
|
||||
.expect("Failed to lock ruby thread while dropping the runtime");
|
||||
*ruby_thread = self.ruby_thread.take();
|
||||
cvar.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RubyValue(pub magnus::value::Opaque<magnus::Value>);
|
||||
|
||||
impl RubyValue {
|
||||
fn nil(ruby: &Ruby) -> Self {
|
||||
Self::new(ruby.qnil().as_value())
|
||||
}
|
||||
|
||||
fn new(value: magnus::Value) -> Self {
|
||||
Self(magnus::value::Opaque::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl RubyRuntime {
|
||||
fn execute_in_thread<T: Send + 'static>(
|
||||
&self,
|
||||
f: impl FnOnce(&magnus::Ruby) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.ruby_thread
|
||||
.as_ref()
|
||||
.expect("No Ruby thread")
|
||||
.execute(Box::new(move |ruby| f(&ruby)))
|
||||
}
|
||||
|
||||
fn execute_in_thread_mut<T: Send + 'static>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut magnus::Ruby) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.ruby_thread
|
||||
.as_ref()
|
||||
.expect("No Ruby thread")
|
||||
.execute(Box::new(move |mut ruby| f(&mut ruby)))
|
||||
}
|
||||
|
||||
fn with_current_entity<T>(ruby: &Ruby, entity: Entity, f: impl FnOnce() -> T) -> T {
|
||||
let var = ruby
|
||||
.class_object()
|
||||
.const_get::<_, RModule>("Bevy")
|
||||
.expect("Failed to get Bevy module")
|
||||
.const_get::<_, RClass>("Entity")
|
||||
.expect("Failed to get Entity class");
|
||||
|
||||
var.ivar_set("_current", BevyEntity(entity))
|
||||
.expect("Failed to set current entity handle");
|
||||
|
||||
let result = f();
|
||||
|
||||
var.ivar_set("_current", ruby.qnil().as_value())
|
||||
.expect("Failed to unset current entity handle");
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Runtime for RubyRuntime {
|
||||
type Schedule = RubySchedule;
|
||||
|
||||
type ScriptAsset = RubyScript;
|
||||
|
||||
type ScriptData = RubyScriptData;
|
||||
|
||||
type CallContext = ();
|
||||
|
||||
type Value = RubyValue;
|
||||
|
||||
type RawEngine = magnus::Ruby;
|
||||
|
||||
fn with_engine_send_mut<T: Send + 'static>(
|
||||
&mut self,
|
||||
f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.execute_in_thread_mut(f)
|
||||
}
|
||||
|
||||
fn with_engine_send<T: Send + 'static>(
|
||||
&self,
|
||||
f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static,
|
||||
) -> T {
|
||||
self.execute_in_thread(f)
|
||||
}
|
||||
|
||||
fn with_engine_mut<T>(&mut self, _f: impl FnOnce(&mut Self::RawEngine) -> T) -> T {
|
||||
unimplemented!(
|
||||
"Ruby runtime requires sending execution to another thread, use `with_engine_mut_send`"
|
||||
);
|
||||
}
|
||||
|
||||
fn with_engine<T>(&self, _f: impl FnOnce(&Self::RawEngine) -> T) -> T {
|
||||
unimplemented!(
|
||||
"Ruby runtime requires sending execution to another thread, use `with_engine_send`"
|
||||
);
|
||||
}
|
||||
|
||||
fn eval(
|
||||
&self,
|
||||
script: &Self::ScriptAsset,
|
||||
entity: bevy::prelude::Entity,
|
||||
) -> Result<Self::ScriptData, crate::ScriptingError> {
|
||||
let script = script.0.clone();
|
||||
self.execute_in_thread(Box::new(move |ruby: &Ruby| {
|
||||
Self::with_current_entity(ruby, entity, || {
|
||||
ruby.eval::<magnus::value::Value>(&script)
|
||||
.map_err(<magnus::Error as Into<ScriptingError>>::into)
|
||||
})?;
|
||||
Ok::<Self::ScriptData, ScriptingError>(RubyScriptData)
|
||||
}))
|
||||
}
|
||||
|
||||
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> {
|
||||
type CallbackClosure = Box<
|
||||
dyn Fn(
|
||||
(),
|
||||
Vec<RubyValue>,
|
||||
)
|
||||
-> Result<crate::promise::Promise<(), RubyValue>, crate::ScriptingError>
|
||||
+ Send,
|
||||
>;
|
||||
static RUBY_CALLBACKS: LazyLock<Mutex<HashMap<String, CallbackClosure>>> =
|
||||
LazyLock::new(|| Mutex::new(HashMap::new()));
|
||||
let mut callbacks = RUBY_CALLBACKS
|
||||
.lock()
|
||||
.expect("Failed to lock callbacks static when registering a callback");
|
||||
callbacks.insert(name.clone(), Box::new(f));
|
||||
|
||||
fn callback(args: &[magnus::Value]) -> magnus::Value {
|
||||
let ruby = magnus::Ruby::get()
|
||||
.expect("Failed to get a handle to Ruby API while processing callback");
|
||||
let method_name: magnus::value::StaticSymbol = ruby
|
||||
.class_object()
|
||||
.funcall("__method__", ())
|
||||
.expect("Failed to get currently called method name symbol from Ruby");
|
||||
let method_name = method_name
|
||||
.name()
|
||||
.expect("Failed to convert method symbol to string");
|
||||
let callbacks = RUBY_CALLBACKS
|
||||
.lock()
|
||||
.expect("Failed to lock callbacks when executing a script callback");
|
||||
let f = callbacks
|
||||
.get(method_name)
|
||||
.expect("No callback found to execute with specified name");
|
||||
let result = f(
|
||||
(),
|
||||
args.iter()
|
||||
.map(|arg| RubyValue::new(arg.into_value()))
|
||||
.collect(),
|
||||
)
|
||||
.expect("failed to call callback");
|
||||
result.into_value()
|
||||
}
|
||||
|
||||
self.execute_in_thread(Box::new(move |ruby: &Ruby| {
|
||||
ruby.define_global_function(&name, function!(callback, -1));
|
||||
RubyValue::nil(ruby)
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn call_fn(
|
||||
&self,
|
||||
name: &str,
|
||||
_script_data: &mut Self::ScriptData,
|
||||
entity: bevy::prelude::Entity,
|
||||
args: impl for<'a> crate::FuncArgs<'a, Self::Value, Self> + Send + 'static,
|
||||
) -> Result<Self::Value, crate::ScriptingError> {
|
||||
let name = name.to_string();
|
||||
self.execute_in_thread(Box::new(move |ruby: &Ruby| {
|
||||
let return_value = Self::with_current_entity(ruby, entity, || {
|
||||
let args: Vec<_> = args
|
||||
.parse(ruby)
|
||||
.into_iter()
|
||||
.map(|a| ruby.get_inner(a.0))
|
||||
.collect();
|
||||
|
||||
ruby.class_object().funcall(name, args.as_slice())
|
||||
})?;
|
||||
|
||||
Ok(RubyValue::new(return_value))
|
||||
}))
|
||||
}
|
||||
|
||||
fn call_fn_from_value(
|
||||
&self,
|
||||
value: &Self::Value,
|
||||
_context: &Self::CallContext,
|
||||
args: Vec<Self::Value>,
|
||||
) -> Result<Self::Value, crate::ScriptingError> {
|
||||
let value = value.clone();
|
||||
|
||||
self.execute_in_thread(move |ruby| {
|
||||
let f: Proc = TryConvert::try_convert(ruby.get_inner(value.0))?;
|
||||
|
||||
let args: Vec<_> = args
|
||||
.into_iter()
|
||||
.map(|x| ruby.get_inner(x.0).as_value())
|
||||
.collect();
|
||||
let result: magnus::Value = f.funcall("call", args.as_slice())?;
|
||||
Ok(RubyValue::new(result))
|
||||
})
|
||||
}
|
||||
|
||||
fn needs_rdynamic_linking() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub mod magnus {
|
||||
pub use magnus::*;
|
||||
}
|
||||
|
||||
pub mod prelude {
|
||||
pub use super::{BevyEntity, BevyVec3, RubyRuntime, RubyScript, RubyScriptData};
|
||||
}
|
||||
|
||||
impl<T: TryConvert> FromRuntimeValueWithEngine<'_, RubyRuntime> for T {
|
||||
fn from_runtime_value_with_engine(value: RubyValue, engine: &magnus::Ruby) -> Self {
|
||||
let inner = engine.get_inner(value.0);
|
||||
T::try_convert(inner).expect("Failed to convert from Ruby value to native type")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoValue> IntoRuntimeValueWithEngine<'_, T, RubyRuntime> for T {
|
||||
fn into_runtime_value_with_engine(value: T, _engine: &magnus::Ruby) -> RubyValue {
|
||||
RubyValue::new(value.into_value())
|
||||
}
|
||||
}
|
||||
|
||||
impl FuncArgs<'_, RubyValue, RubyRuntime> for () {
|
||||
fn parse(self, _engine: &magnus::Ruby) -> Vec<RubyValue> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoValue> FuncArgs<'_, RubyValue, RubyRuntime> for Vec<T> {
|
||||
fn parse(self, _engine: &magnus::Ruby) -> Vec<RubyValue> {
|
||||
self.into_iter()
|
||||
.map(|x| RubyValue::new(x.into_value()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RArray(pub Opaque<magnus::RArray>);
|
||||
|
||||
impl FromRuntimeValueWithEngine<'_, RubyRuntime> for RArray {
|
||||
fn from_runtime_value_with_engine(value: RubyValue, engine: &magnus::Ruby) -> Self {
|
||||
let inner = engine.get_inner(value.0);
|
||||
let array =
|
||||
magnus::RArray::try_convert(inner).expect("Failed to convert Ruby value to RArray");
|
||||
RArray(Opaque::from(array))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_tuple {
|
||||
($($idx:tt $t:tt),+) => {
|
||||
impl<'a, $($t: IntoValue,)+> FuncArgs<'a, RubyValue, RubyRuntime>
|
||||
for ($($t,)+)
|
||||
{
|
||||
fn parse(self, _engine: &'a magnus::Ruby) -> Vec<RubyValue> {
|
||||
vec![
|
||||
$(RubyValue::new(self.$idx.into_value()), )+
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
use bevy::{prelude::*, log::tracing};
|
||||
use bevy::{log::tracing, prelude::*};
|
||||
use std::{
|
||||
fmt::Display,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Callback, Callbacks, Runtime, ScriptingError,
|
||||
callback::FunctionCallEvent,
|
||||
promise::{Promise, PromiseInner},
|
||||
Callback, Callbacks, Runtime, ScriptingError,
|
||||
};
|
||||
|
||||
use super::components::Script;
|
||||
|
|
@ -52,7 +52,7 @@ pub(crate) fn process_new_scripts<R: Runtime>(
|
|||
let path = asset_server
|
||||
.get_path(&script_component.script)
|
||||
.unwrap_or_default();
|
||||
tracing::error!("error running script {} {:?}", path, e);
|
||||
tracing::error!("error running script {} {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ pub(crate) fn init_callbacks<R: Runtime>(world: &mut World) -> Result<(), Script
|
|||
},
|
||||
);
|
||||
if let Err(e) = result {
|
||||
tracing::error!("error registering function: {:?}", e);
|
||||
tracing::error!("error registering function: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ pub(crate) fn process_calls<R: Runtime>(world: &mut World) -> Result<(), Scripti
|
|||
match result {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
tracing::error!("error resolving call: {} {:?}", callback.name, e);
|
||||
tracing::error!("error resolving call: {} {}", callback.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
332
tests/tests.rs
332
tests/tests.rs
|
|
@ -1,17 +1,35 @@
|
|||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
use std::sync::OnceLock;
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
use bevy::ecs::system::RunSystemOnce as _;
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
use bevy::prelude::*;
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
use bevy_scriptum::{prelude::*, FuncArgs, Runtime};
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
use bevy_scriptum::{FuncArgs, Runtime, prelude::*};
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
static TRACING_SUBSCRIBER: OnceLock<()> = OnceLock::new();
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
#[derive(Default, Resource)]
|
||||
struct TimesCalled {
|
||||
times_called: u8,
|
||||
}
|
||||
|
||||
macro_rules! assert_n_times_called {
|
||||
($app: expr, $count: expr) => {
|
||||
assert_eq!(
|
||||
$app.world()
|
||||
.get_resource::<TimesCalled>()
|
||||
.unwrap()
|
||||
.times_called,
|
||||
$count
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
fn build_test_app() -> App {
|
||||
let mut app = App::new();
|
||||
|
||||
|
|
@ -25,7 +43,7 @@ fn build_test_app() -> App {
|
|||
app
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
fn run_script<R: Runtime, Out, Marker>(
|
||||
app: &mut App,
|
||||
path: String,
|
||||
|
|
@ -42,7 +60,7 @@ fn run_script<R: Runtime, Out, Marker>(
|
|||
entity_id
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
fn call_script_on_update_from_rust<R: Runtime>(
|
||||
mut scripted_entities: Query<(Entity, &mut R::ScriptData)>,
|
||||
scripting_runtime: ResMut<R>,
|
||||
|
|
@ -55,7 +73,7 @@ fn call_script_on_update_from_rust<R: Runtime>(
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
trait AssertStateKeyValue {
|
||||
type ScriptData;
|
||||
fn assert_state_key_value_i64(world: &World, entity_id: Entity, key: &str, value: i64);
|
||||
|
|
@ -63,9 +81,9 @@ trait AssertStateKeyValue {
|
|||
fn assert_state_key_value_string(world: &World, entity_id: Entity, key: &str, value: &str);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "rhai", feature = "lua"))]
|
||||
#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))]
|
||||
macro_rules! scripting_tests {
|
||||
($runtime:ty, $script:literal, $extension:literal) => {
|
||||
($runtime:ty, $script:literal, $extension:literal, $entity_type: ty, $vec_type: ty) => {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
|
@ -189,7 +207,7 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_function_gets_called_from_rust_with_heterogenous_params() {
|
||||
fn test_script_function_gets_called_from_rust_with_multiple_params() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<$runtime>(|_| {});
|
||||
|
|
@ -225,33 +243,34 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_function_gets_called_from_rust_with_multiple_params() {
|
||||
fn eval_that_casues_runtime_error_doesnt_panic() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<$runtime>(|_| {});
|
||||
app.add_scripting::<$runtime>(|r| {
|
||||
r.add_function(
|
||||
String::from("mark_called"),
|
||||
|mut times_called: ResMut<TimesCalled>| {
|
||||
times_called.times_called += 1;
|
||||
},
|
||||
);
|
||||
})
|
||||
.init_resource::<TimesCalled>();
|
||||
|
||||
let entity_id = run_script::<$runtime, _, _>(
|
||||
run_script::<$runtime, _, _>(
|
||||
&mut app,
|
||||
format!(
|
||||
"tests/{}/script_function_gets_called_from_rust_with_multiple_params.{}",
|
||||
"tests/{}/eval_that_causes_runtime_error.{}",
|
||||
$script, $extension
|
||||
)
|
||||
.to_string(),
|
||||
|mut scripted_entities: Query<(Entity, &mut <$runtime as Runtime>::ScriptData)>,
|
||||
scripting_runtime: ResMut<$runtime>| {
|
||||
let (entity, mut script_data) = scripted_entities.single_mut().unwrap();
|
||||
scripting_runtime
|
||||
.call_fn("test_func", &mut script_data, entity, vec![1, 2])
|
||||
.unwrap();
|
||||
},
|
||||
|| {},
|
||||
);
|
||||
|
||||
<$runtime>::assert_state_key_value_i32(&app.world(), entity_id, "a_value", 1i32);
|
||||
<$runtime>::assert_state_key_value_i32(&app.world(), entity_id, "b_value", 2i32);
|
||||
assert_n_times_called!(app, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_script_function_that_casues_runtime_error() {
|
||||
fn call_script_function_that_casues_runtime_error() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<$runtime>(|_| {});
|
||||
|
|
@ -274,18 +293,14 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_call_script_function_that_does_not_exist() {
|
||||
fn call_script_function_that_does_not_exist() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<$runtime>(|_| {});
|
||||
|
||||
run_script::<$runtime, _, _>(
|
||||
&mut app,
|
||||
format!(
|
||||
"tests/{}/call_script_function_that_causes_runtime_error.{}",
|
||||
$script, $extension
|
||||
)
|
||||
.to_string(),
|
||||
format!("tests/{}/side_effects.{}", $script, $extension).to_string(),
|
||||
|mut scripted_entities: Query<(Entity, &mut <$runtime as Runtime>::ScriptData)>,
|
||||
scripting_runtime: ResMut<$runtime>| {
|
||||
let (entity, mut script_data) = scripted_entities.single_mut().unwrap();
|
||||
|
|
@ -297,7 +312,7 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_function_gets_called_from_rust() {
|
||||
fn script_function_gets_called_from_rust() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<$runtime>(|_| {});
|
||||
|
|
@ -316,7 +331,7 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_promise() {
|
||||
fn return_via_promise() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
|
|
@ -333,7 +348,7 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_promise_runtime_error_does_not_panic() {
|
||||
fn promise_runtime_error() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
|
|
@ -348,7 +363,7 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_side_effects() {
|
||||
fn side_effects() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
#[derive(Component)]
|
||||
|
|
@ -374,14 +389,9 @@ macro_rules! scripting_tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_rust_function_gets_called_from_script() {
|
||||
fn rust_function_gets_called_from_script() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
struct TimesCalled {
|
||||
times_called: u8,
|
||||
}
|
||||
|
||||
app.world_mut().init_resource::<TimesCalled>();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
|
|
@ -400,13 +410,172 @@ macro_rules! scripting_tests {
|
|||
call_script_on_update_from_rust::<$runtime>,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
app.world()
|
||||
.get_resource::<TimesCalled>()
|
||||
.unwrap()
|
||||
.times_called,
|
||||
1
|
||||
assert_n_times_called!(app, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_variable_index_is_available_in_callback() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
struct State {
|
||||
index: u32,
|
||||
}
|
||||
|
||||
app.world_mut().init_resource::<State>();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
runtime.add_function(
|
||||
String::from("rust_func"),
|
||||
|In((index,)): In<(u32,)>, mut res: ResMut<State>| {
|
||||
res.index = index;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let entity = run_script::<$runtime, _, _>(
|
||||
&mut app,
|
||||
format!("tests/{}/entity_variable.{}", $script, $extension).to_string(),
|
||||
call_script_on_update_from_rust::<$runtime>,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
app.world().get_resource::<State>().unwrap().index,
|
||||
entity.index()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_variable_index_is_available_in_eval() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
struct State {
|
||||
index: Option<u32>,
|
||||
}
|
||||
|
||||
app.world_mut().init_resource::<State>();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
runtime.add_function(
|
||||
String::from("rust_func"),
|
||||
|In((index,)): In<(u32,)>, mut res: ResMut<State>| {
|
||||
res.index = Some(index);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let entity = run_script::<$runtime, _, _>(
|
||||
&mut app,
|
||||
format!("tests/{}/entity_variable_eval.{}", $script, $extension).to_string(),
|
||||
call_script_on_update_from_rust::<$runtime>,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
app.world().get_resource::<State>().unwrap().index,
|
||||
Some(entity.index())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_entity_from_script() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
struct State {
|
||||
index: Option<u32>,
|
||||
}
|
||||
|
||||
app.world_mut().init_resource::<State>();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
runtime.add_function(
|
||||
String::from("rust_func"),
|
||||
|In((entity,)): In<($entity_type,)>, mut res: ResMut<State>| {
|
||||
res.index = Some(entity.index());
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let entity = run_script::<$runtime, _, _>(
|
||||
&mut app,
|
||||
format!("tests/{}/pass_entity_from_script.{}", $script, $extension).to_string(),
|
||||
call_script_on_update_from_rust::<$runtime>,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
app.world().get_resource::<State>().unwrap().index,
|
||||
Some(entity.index())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_vec3_from_script() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
struct State {
|
||||
success: bool,
|
||||
}
|
||||
|
||||
app.world_mut().init_resource::<State>();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
runtime.add_function(
|
||||
String::from("rust_func"),
|
||||
|In((v,)): In<($vec_type,)>, mut res: ResMut<State>| {
|
||||
assert_eq!(v.x(), 1.5);
|
||||
assert_eq!(v.y(), 2.5);
|
||||
assert_eq!(v.z(), -3.5);
|
||||
res.success = true
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
run_script::<$runtime, _, _>(
|
||||
&mut app,
|
||||
format!("tests/{}/pass_vec3_from_script.{}", $script, $extension).to_string(),
|
||||
call_script_on_update_from_rust::<$runtime>,
|
||||
);
|
||||
|
||||
assert!(app.world().get_resource::<State>().unwrap().success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_vec3_to_script() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
struct State {
|
||||
success: bool,
|
||||
}
|
||||
|
||||
app.world_mut().init_resource::<State>();
|
||||
|
||||
app.add_scripting::<$runtime>(|runtime| {
|
||||
runtime.add_function(String::from("mark_success"), |mut res: ResMut<State>| {
|
||||
res.success = true
|
||||
});
|
||||
});
|
||||
|
||||
run_script::<$runtime, _, _>(
|
||||
&mut app,
|
||||
format!("tests/{}/pass_vec3_to_script.{}", $script, $extension).to_string(),
|
||||
|mut scripted_entities: Query<(Entity, &mut <$runtime as Runtime>::ScriptData)>,
|
||||
scripting_runtime: ResMut<$runtime>| {
|
||||
let (entity, mut script_data) = scripted_entities.single_mut().unwrap();
|
||||
scripting_runtime
|
||||
.call_fn(
|
||||
"test_func",
|
||||
&mut script_data,
|
||||
entity,
|
||||
(<$vec_type>::new(1.5, 2.5, -3.5),),
|
||||
)
|
||||
.unwrap();
|
||||
},
|
||||
);
|
||||
|
||||
assert!(app.world().get_resource::<State>().unwrap().success);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -438,7 +607,7 @@ mod rhai_tests {
|
|||
}
|
||||
}
|
||||
|
||||
scripting_tests!(RhaiRuntime, "rhai", "rhai");
|
||||
scripting_tests!(RhaiRuntime, "rhai", "rhai", BevyEntity, BevyVec3);
|
||||
}
|
||||
|
||||
#[cfg(feature = "lua")]
|
||||
|
|
@ -480,5 +649,66 @@ mod lua_tests {
|
|||
}
|
||||
}
|
||||
|
||||
scripting_tests!(LuaRuntime, "lua", "lua");
|
||||
scripting_tests!(LuaRuntime, "lua", "lua", BevyEntity, BevyVec3);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ruby")]
|
||||
mod ruby_tests {
|
||||
use bevy::prelude::*;
|
||||
use bevy_scriptum::runtimes::ruby::{RubyScriptData, prelude::*};
|
||||
use magnus::value::ReprValue;
|
||||
|
||||
impl AssertStateKeyValue for RubyRuntime {
|
||||
type ScriptData = RubyScriptData;
|
||||
|
||||
fn assert_state_key_value_i64(world: &World, _entity_id: Entity, key: &str, value: i64) {
|
||||
let runtime = world.get_resource::<RubyRuntime>().unwrap();
|
||||
let key = key.to_string();
|
||||
runtime.with_engine_send(move |engine| {
|
||||
let state: magnus::value::Value = engine.eval("$state").unwrap();
|
||||
let res: i64 = state.funcall_public("[]", (key,)).unwrap();
|
||||
assert_eq!(res, value)
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_state_key_value_i32(world: &World, _entity_id: Entity, key: &str, value: i32) {
|
||||
let runtime = world.get_resource::<RubyRuntime>().unwrap();
|
||||
let key = key.to_string();
|
||||
runtime.with_engine_send(move |engine| {
|
||||
let state: magnus::value::Value = engine.eval("$state").unwrap();
|
||||
let res: i32 = state.funcall_public("[]", (key,)).unwrap();
|
||||
assert_eq!(res, value)
|
||||
})
|
||||
}
|
||||
|
||||
fn assert_state_key_value_string(
|
||||
world: &World,
|
||||
_entity_id: Entity,
|
||||
key: &str,
|
||||
value: &str,
|
||||
) {
|
||||
let runtime = world.get_resource::<RubyRuntime>().unwrap();
|
||||
let key = key.to_string();
|
||||
let value = value.to_string();
|
||||
runtime.with_engine_send(move |engine| {
|
||||
let state: magnus::value::Value = engine.eval("$state").unwrap();
|
||||
let res: String = state.funcall_public("[]", (key,)).unwrap();
|
||||
assert_eq!(res, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_symbol_inspection() {
|
||||
let mut app = build_test_app();
|
||||
|
||||
app.add_scripting::<RubyRuntime>(|_| {});
|
||||
let runtime = app.world().get_resource::<RubyRuntime>().unwrap();
|
||||
runtime.with_engine_send(|engine| {
|
||||
let symbol_string: String = engine.eval(":test_symbol.inspect").unwrap();
|
||||
assert_eq!(symbol_string, ":test_symbol")
|
||||
});
|
||||
}
|
||||
|
||||
scripting_tests!(RubyRuntime, "ruby", "rb", BevyEntity, BevyVec3);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue