diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml new file mode 100644 index 0000000..0ea529d --- /dev/null +++ b/.github/workflows/book.yml @@ -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/ diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b7bd998..85c2931 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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 diff --git a/.gitignore b/.gitignore index a7c83b7..3c51940 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target /Cargo.lock .vscode -rust-analyzer.json +.nvim.lua diff --git a/Cargo.toml b/Cargo.toml index 5eb7042..ad3fc0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,17 +2,18 @@ name = "bevy_scriptum" authors = ["Jaroslaw Konik "] 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"] } diff --git a/README.md b/README.md index 6e55837..5dbb0d0 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,18 @@ ![demo](demo.gif) -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::(|runtime| { - runtime.add_function(String::from("hello_from_my_plugin"), || { - info!("Hello from MyPlugin"); - }); - }); - } -} - -App::new() - .add_plugins(DefaultPlugins) - .add_scripting::(|_| { - // 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. diff --git a/assets/examples/ruby/call_function_from_rust.rb b/assets/examples/ruby/call_function_from_rust.rb new file mode 100644 index 0000000..a5adb3b --- /dev/null +++ b/assets/examples/ruby/call_function_from_rust.rb @@ -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 diff --git a/assets/examples/ruby/current_entity.rb b/assets/examples/ruby/current_entity.rb new file mode 100644 index 0000000..172b3e9 --- /dev/null +++ b/assets/examples/ruby/current_entity.rb @@ -0,0 +1,3 @@ +get_name(Bevy::Entity.current).and_then do |name| + puts(name) +end diff --git a/assets/examples/ruby/custom_type.rb b/assets/examples/ruby/custom_type.rb new file mode 100644 index 0000000..0ab76a2 --- /dev/null +++ b/assets/examples/ruby/custom_type.rb @@ -0,0 +1,4 @@ +# Create a new instance of MyType +my_type = MyType.new() +# Call registered method +puts(my_type.my_method) diff --git a/assets/examples/ruby/ecs.rb b/assets/examples/ruby/ecs.rb new file mode 100644 index 0000000..63e06a7 --- /dev/null +++ b/assets/examples/ruby/ecs.rb @@ -0,0 +1 @@ +print_player_names diff --git a/assets/examples/ruby/entity_variable.rb b/assets/examples/ruby/entity_variable.rb new file mode 100644 index 0000000..d54a372 --- /dev/null +++ b/assets/examples/ruby/entity_variable.rb @@ -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}") diff --git a/assets/examples/ruby/function_params.rb b/assets/examples/ruby/function_params.rb new file mode 100644 index 0000000..10cf48d --- /dev/null +++ b/assets/examples/ruby/function_params.rb @@ -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"]) diff --git a/assets/examples/ruby/function_return_value.rb b/assets/examples/ruby/function_return_value.rb new file mode 100644 index 0000000..522e7f3 --- /dev/null +++ b/assets/examples/ruby/function_return_value.rb @@ -0,0 +1,3 @@ +def get_value + 42 +end diff --git a/assets/examples/ruby/hello_world.rb b/assets/examples/ruby/hello_world.rb new file mode 100644 index 0000000..7f4cbfd --- /dev/null +++ b/assets/examples/ruby/hello_world.rb @@ -0,0 +1 @@ +hello_bevy() diff --git a/assets/examples/ruby/multiple_plugins_plugin_a.rb b/assets/examples/ruby/multiple_plugins_plugin_a.rb new file mode 100644 index 0000000..1ae9ec9 --- /dev/null +++ b/assets/examples/ruby/multiple_plugins_plugin_a.rb @@ -0,0 +1 @@ +hello_from_plugin_a diff --git a/assets/examples/ruby/multiple_plugins_plugin_b.rb b/assets/examples/ruby/multiple_plugins_plugin_b.rb new file mode 100644 index 0000000..ded6dc2 --- /dev/null +++ b/assets/examples/ruby/multiple_plugins_plugin_b.rb @@ -0,0 +1 @@ +hello_from_plugin_b_with_parameters("hello", 42) diff --git a/assets/examples/ruby/promises.rb b/assets/examples/ruby/promises.rb new file mode 100644 index 0000000..dd0ecbe --- /dev/null +++ b/assets/examples/ruby/promises.rb @@ -0,0 +1,3 @@ +get_player_name.and_then do |name| + puts name +end diff --git a/assets/examples/ruby/side_effects.rb b/assets/examples/ruby/side_effects.rb new file mode 100644 index 0000000..63642de --- /dev/null +++ b/assets/examples/ruby/side_effects.rb @@ -0,0 +1 @@ +spawn_entity() diff --git a/assets/tests/lua/entity_variable.lua b/assets/tests/lua/entity_variable.lua new file mode 100644 index 0000000..4bf3745 --- /dev/null +++ b/assets/tests/lua/entity_variable.lua @@ -0,0 +1,3 @@ +function test_func() + rust_func(entity.index) +end diff --git a/assets/tests/lua/entity_variable_eval.lua b/assets/tests/lua/entity_variable_eval.lua new file mode 100644 index 0000000..fdc8f94 --- /dev/null +++ b/assets/tests/lua/entity_variable_eval.lua @@ -0,0 +1,5 @@ +index = entity.index + +function test_func() + rust_func(index) +end diff --git a/assets/tests/lua/eval_that_causes_runtime_error.lua b/assets/tests/lua/eval_that_causes_runtime_error.lua new file mode 100644 index 0000000..f4713a8 --- /dev/null +++ b/assets/tests/lua/eval_that_causes_runtime_error.lua @@ -0,0 +1,2 @@ +mark_called() +error() diff --git a/assets/tests/lua/pass_entity_from_script.lua b/assets/tests/lua/pass_entity_from_script.lua new file mode 100644 index 0000000..355ef3a --- /dev/null +++ b/assets/tests/lua/pass_entity_from_script.lua @@ -0,0 +1,3 @@ +function test_func() + rust_func(entity) +end diff --git a/assets/tests/lua/pass_vec3_from_script.lua b/assets/tests/lua/pass_vec3_from_script.lua new file mode 100644 index 0000000..bd2a772 --- /dev/null +++ b/assets/tests/lua/pass_vec3_from_script.lua @@ -0,0 +1,3 @@ +function test_func() + rust_func(Vec3(1.5, 2.5, -3.5)) +end diff --git a/assets/tests/lua/pass_vec3_to_script.lua b/assets/tests/lua/pass_vec3_to_script.lua new file mode 100644 index 0000000..0b50c5c --- /dev/null +++ b/assets/tests/lua/pass_vec3_to_script.lua @@ -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 diff --git a/assets/tests/rhai/entity_variable.rhai b/assets/tests/rhai/entity_variable.rhai new file mode 100644 index 0000000..b93b116 --- /dev/null +++ b/assets/tests/rhai/entity_variable.rhai @@ -0,0 +1,3 @@ +fn test_func() { + rust_func(entity.index); +} diff --git a/assets/tests/rhai/entity_variable_eval.rhai b/assets/tests/rhai/entity_variable_eval.rhai new file mode 100644 index 0000000..3c94277 --- /dev/null +++ b/assets/tests/rhai/entity_variable_eval.rhai @@ -0,0 +1,5 @@ +let index = entity.index; + +fn test_func() { + rust_func(index); +} diff --git a/assets/tests/rhai/eval_that_causes_runtime_error.rhai b/assets/tests/rhai/eval_that_causes_runtime_error.rhai new file mode 100644 index 0000000..68c56f0 --- /dev/null +++ b/assets/tests/rhai/eval_that_causes_runtime_error.rhai @@ -0,0 +1,2 @@ +mark_called(); +throw(); diff --git a/assets/tests/rhai/pass_entity_from_script.rhai b/assets/tests/rhai/pass_entity_from_script.rhai new file mode 100644 index 0000000..ad4f318 --- /dev/null +++ b/assets/tests/rhai/pass_entity_from_script.rhai @@ -0,0 +1,3 @@ +fn test_func() { + rust_func(entity); +} diff --git a/assets/tests/rhai/pass_vec3_from_script.rhai b/assets/tests/rhai/pass_vec3_from_script.rhai new file mode 100644 index 0000000..cc137f7 --- /dev/null +++ b/assets/tests/rhai/pass_vec3_from_script.rhai @@ -0,0 +1,3 @@ +fn test_func() { + rust_func(new_vec3(1.5, 2.5, -3.5)); +} diff --git a/assets/tests/rhai/pass_vec3_to_script.rhai b/assets/tests/rhai/pass_vec3_to_script.rhai new file mode 100644 index 0000000..0afb1c2 --- /dev/null +++ b/assets/tests/rhai/pass_vec3_to_script.rhai @@ -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(); +} diff --git a/assets/tests/ruby/call_script_function_that_causes_runtime_error.rb b/assets/tests/ruby/call_script_function_that_causes_runtime_error.rb new file mode 100644 index 0000000..e6200c0 --- /dev/null +++ b/assets/tests/ruby/call_script_function_that_causes_runtime_error.rb @@ -0,0 +1,3 @@ +def test_func + raise +end diff --git a/assets/tests/ruby/call_script_function_with_params.rb b/assets/tests/ruby/call_script_function_with_params.rb new file mode 100644 index 0000000..ba6c271 --- /dev/null +++ b/assets/tests/ruby/call_script_function_with_params.rb @@ -0,0 +1,7 @@ +$state = { + 'called_with' => nil +} + +def test_func(val) + $state['called_with'] = val +end diff --git a/assets/tests/ruby/entity_variable.rb b/assets/tests/ruby/entity_variable.rb new file mode 100644 index 0000000..2c3925b --- /dev/null +++ b/assets/tests/ruby/entity_variable.rb @@ -0,0 +1,3 @@ +def test_func + rust_func(Bevy::Entity.current.index) +end diff --git a/assets/tests/ruby/entity_variable_eval.rb b/assets/tests/ruby/entity_variable_eval.rb new file mode 100644 index 0000000..a07bd59 --- /dev/null +++ b/assets/tests/ruby/entity_variable_eval.rb @@ -0,0 +1,5 @@ +$index = Bevy::Entity.current.index + +def test_func + rust_func($index) +end diff --git a/assets/tests/ruby/eval_that_causes_runtime_error.rb b/assets/tests/ruby/eval_that_causes_runtime_error.rb new file mode 100644 index 0000000..7b460ab --- /dev/null +++ b/assets/tests/ruby/eval_that_causes_runtime_error.rb @@ -0,0 +1,2 @@ +mark_called +raise diff --git a/assets/tests/ruby/pass_entity_from_script.rb b/assets/tests/ruby/pass_entity_from_script.rb new file mode 100644 index 0000000..3f14b64 --- /dev/null +++ b/assets/tests/ruby/pass_entity_from_script.rb @@ -0,0 +1,3 @@ +def test_func + rust_func(Bevy::Entity.current) +end diff --git a/assets/tests/ruby/pass_vec3_from_script.rb b/assets/tests/ruby/pass_vec3_from_script.rb new file mode 100644 index 0000000..e6f117b --- /dev/null +++ b/assets/tests/ruby/pass_vec3_from_script.rb @@ -0,0 +1,3 @@ +def test_func + rust_func(Bevy::Vec3.new(1.5, 2.5, -3.5)) +end diff --git a/assets/tests/ruby/pass_vec3_to_script.rb b/assets/tests/ruby/pass_vec3_to_script.rb new file mode 100644 index 0000000..477ba1a --- /dev/null +++ b/assets/tests/ruby/pass_vec3_to_script.rb @@ -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 diff --git a/assets/tests/ruby/promise_runtime_error.rb b/assets/tests/ruby/promise_runtime_error.rb new file mode 100644 index 0000000..8c3e31a --- /dev/null +++ b/assets/tests/ruby/promise_runtime_error.rb @@ -0,0 +1,5 @@ +def test_func + rust_func.and_then do |x| + raise + end +end diff --git a/assets/tests/ruby/return_via_promise.rb b/assets/tests/ruby/return_via_promise.rb new file mode 100644 index 0000000..c1ec94b --- /dev/null +++ b/assets/tests/ruby/return_via_promise.rb @@ -0,0 +1,9 @@ +$state = { + 'x' => nil +} + +def test_func + rust_func.and_then do |x| + $state['x'] = x + end +end diff --git a/assets/tests/ruby/rust_function_gets_called_from_script.rb b/assets/tests/ruby/rust_function_gets_called_from_script.rb new file mode 100644 index 0000000..07a5876 --- /dev/null +++ b/assets/tests/ruby/rust_function_gets_called_from_script.rb @@ -0,0 +1,3 @@ +def test_func + rust_func +end diff --git a/assets/tests/ruby/rust_function_gets_called_from_script_with_multiple_params.rb b/assets/tests/ruby/rust_function_gets_called_from_script_with_multiple_params.rb new file mode 100644 index 0000000..fea8542 --- /dev/null +++ b/assets/tests/ruby/rust_function_gets_called_from_script_with_multiple_params.rb @@ -0,0 +1,3 @@ +def test_func + rust_func(5, 'test') +end diff --git a/assets/tests/ruby/rust_function_gets_called_from_script_with_param.rb b/assets/tests/ruby/rust_function_gets_called_from_script_with_param.rb new file mode 100644 index 0000000..3238e08 --- /dev/null +++ b/assets/tests/ruby/rust_function_gets_called_from_script_with_param.rb @@ -0,0 +1,3 @@ +def test_func + rust_func(5) +end diff --git a/assets/tests/ruby/script_function_gets_called_from_rust.rb b/assets/tests/ruby/script_function_gets_called_from_rust.rb new file mode 100644 index 0000000..0fae6da --- /dev/null +++ b/assets/tests/ruby/script_function_gets_called_from_rust.rb @@ -0,0 +1,7 @@ +$state = { + 'times_called' => 0 +} + +def test_func + $state['times_called'] += 1 +end diff --git a/assets/tests/ruby/script_function_gets_called_from_rust_with_multiple_params.rb b/assets/tests/ruby/script_function_gets_called_from_rust_with_multiple_params.rb new file mode 100644 index 0000000..4cc49e7 --- /dev/null +++ b/assets/tests/ruby/script_function_gets_called_from_rust_with_multiple_params.rb @@ -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 diff --git a/assets/tests/ruby/script_function_gets_called_from_rust_with_single_param.rb b/assets/tests/ruby/script_function_gets_called_from_rust_with_single_param.rb new file mode 100644 index 0000000..769ea1c --- /dev/null +++ b/assets/tests/ruby/script_function_gets_called_from_rust_with_single_param.rb @@ -0,0 +1,7 @@ +$state = { + 'a_value' => nil +} + +def test_func(a) + $state['a_value'] = a +end diff --git a/assets/tests/ruby/side_effects.rb b/assets/tests/ruby/side_effects.rb new file mode 100644 index 0000000..8b6177d --- /dev/null +++ b/assets/tests/ruby/side_effects.rb @@ -0,0 +1,3 @@ +def test_func + spawn_entity +end diff --git a/book/.gitignore b/book/.gitignore index 517ed82..32b4ea1 100644 --- a/book/.gitignore +++ b/book/.gitignore @@ -1,2 +1,3 @@ book doctest_cache +target diff --git a/book/Cargo.lock b/book/Cargo.lock new file mode 100644 index 0000000..13a5e66 --- /dev/null +++ b/book/Cargo.lock @@ -0,0 +1,2498 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-activity" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" +dependencies = [ + "android-properties", + "bitflags", + "cc", + "cesu8", + "jni", + "jni-sys", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "assert_type_match" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f548ad2c4031f2902e3edc1f29c29e835829437de49562d8eb5dc5584d3a1043" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-lock" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "atomicow" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52e8890bb9844440d0c412fa74b67fd2f14e85248b6e00708059b6da9e5f8bf" +dependencies = [ + "portable-atomic", + "portable-atomic-util", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bevy" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a5cd3b24a5adb7c7378da7b3eea47639877643d11b6b087fc8a8094f2528615" +dependencies = [ + "bevy_internal", +] + +[[package]] +name = "bevy_app" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b6267ac23a9947d5b2725ff047a1e1add70076d85fa9fb73d044ab9bea1f3c" +dependencies = [ + "bevy_derive", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "cfg-if", + "console_error_panic_hook", + "ctrlc", + "downcast-rs", + "log", + "thiserror 2.0.12", + "variadics_please", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "bevy_asset" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0698040d63199391ea77fd02e039630748e3e335c3070c6d932fd96cbf80f5d6" +dependencies = [ + "async-broadcast", + "async-fs", + "async-lock", + "atomicow", + "bevy_app", + "bevy_asset_macros", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bevy_window", + "bitflags", + "blake3", + "crossbeam-channel", + "derive_more", + "disqualified", + "downcast-rs", + "either", + "futures-io", + "futures-lite", + "js-sys", + "parking_lot", + "ron", + "serde", + "stackfuture", + "thiserror 2.0.12", + "tracing", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "bevy_asset_macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf8c00b5d532f8e5ac7b49af10602f9f7774a2d522cf0638323b5dfeee7b31c" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f626531b9c05c25a758ede228727bd11c2c2c8498ecbed9925044386d525a2a3" +dependencies = [ + "bevy_macro_utils", + "quote", + "syn", +] + +[[package]] +name = "bevy_diagnostic" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048a1ff3944a534b8472516866284181eef0a75b6dd4d39b6e5925715e350766" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_tasks", + "bevy_time", + "bevy_utils", + "const-fnv1a-hash", + "log", +] + +[[package]] +name = "bevy_ecs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9e807b5d9aab3bb8dfe47e7a44c9ff088bad2ceefe299b80ac77609a87fe9d4" +dependencies = [ + "arrayvec", + "bevy_ecs_macros", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_utils", + "bitflags", + "bumpalo", + "concurrent-queue", + "derive_more", + "disqualified", + "fixedbitset", + "indexmap", + "log", + "nonmax", + "serde", + "smallvec", + "thiserror 2.0.12", + "variadics_please", +] + +[[package]] +name = "bevy_ecs_macros" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d7bb98aeb8dd30f36e6a773000c12a891d4f1bee2adc3841ec89cc8eaf54e" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bevy_input" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763410715714f3d4d2dcdf077af276e2e4ea93fd8081b183d446d060ea95baaa" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "derive_more", + "log", + "thiserror 2.0.12", +] + +[[package]] +name = "bevy_internal" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "526ffd64c58004cb97308826e896c07d0e23dc056c243b97492e31cdf72e2830" +dependencies = [ + "bevy_app", + "bevy_asset", + "bevy_derive", + "bevy_diagnostic", + "bevy_ecs", + "bevy_input", + "bevy_log", + "bevy_math", + "bevy_platform", + "bevy_ptr", + "bevy_reflect", + "bevy_tasks", + "bevy_time", + "bevy_transform", + "bevy_utils", +] + +[[package]] +name = "bevy_log" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7156df8d2f11135cf71c03eb4c11132b65201fd4f51648571e59e39c9c9ee2f6" +dependencies = [ + "android_log-sys", + "bevy_app", + "bevy_ecs", + "bevy_utils", + "tracing", + "tracing-log", + "tracing-oslog", + "tracing-subscriber", + "tracing-wasm", +] + +[[package]] +name = "bevy_macro_utils" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2473db70d8785b5c75d6dd951a2e51e9be2c2311122db9692c79c9d887517b" +dependencies = [ + "parking_lot", + "proc-macro2", + "quote", + "syn", + "toml_edit", +] + +[[package]] +name = "bevy_math" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a3a926d02dc501c6156a047510bdb538dcb1fa744eeba13c824b73ba88de55" +dependencies = [ + "approx", + "bevy_reflect", + "derive_more", + "glam", + "itertools 0.14.0", + "libm", + "rand", + "rand_distr", + "serde", + "smallvec", + "thiserror 2.0.12", + "variadics_please", +] + +[[package]] +name = "bevy_platform" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704db2c11b7bc31093df4fbbdd3769f9606a6a5287149f4b51f2680f25834ebc" +dependencies = [ + "cfg-if", + "critical-section", + "foldhash", + "getrandom 0.2.16", + "hashbrown", + "portable-atomic", + "portable-atomic-util", + "serde", + "spin 0.9.8", + "web-time", +] + +[[package]] +name = "bevy_ptr" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f1275dfb4cfef4ffc90c3fa75408964864facf833acc932413d52aa5364ba4" + +[[package]] +name = "bevy_reflect" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "607ebacc31029cf2f39ac330eabf1d4bc411b159528ec08dbe6b0593eaccfd41" +dependencies = [ + "assert_type_match", + "bevy_platform", + "bevy_ptr", + "bevy_reflect_derive", + "bevy_utils", + "derive_more", + "disqualified", + "downcast-rs", + "erased-serde", + "foldhash", + "glam", + "serde", + "smallvec", + "smol_str", + "thiserror 2.0.12", + "uuid", + "variadics_please", + "wgpu-types", +] + +[[package]] +name = "bevy_reflect_derive" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf35e45e4eb239018369f63f2adc2107a54c329f9276d020e01eee1625b0238b" +dependencies = [ + "bevy_macro_utils", + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "bevy_scriptum" +version = "0.8.1" +dependencies = [ + "anyhow", + "bevy", + "crossbeam-channel", + "magnus", + "mlua", + "rb-sys", + "rhai", + "serde", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "bevy_scriptum_book" +version = "0.0.0" +dependencies = [ + "bevy_scriptum", +] + +[[package]] +name = "bevy_tasks" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444c450b65e108855f42ecb6db0c041a56ea7d7f10cc6222f0ca95e9536a7d19" +dependencies = [ + "async-executor", + "async-task", + "atomic-waker", + "bevy_platform", + "cfg-if", + "crossbeam-queue", + "derive_more", + "futures-channel", + "futures-lite", + "heapless", + "pin-project", + "wasm-bindgen-futures", +] + +[[package]] +name = "bevy_time" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456369ca10f8e039aaf273332744674844827854833ee29e28f9e161702f2f55" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_platform", + "bevy_reflect", + "log", +] + +[[package]] +name = "bevy_transform" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8479cdd5461246943956a7c8347e4e5d6ff857e57add889fb50eee0b5c26ab48" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_math", + "bevy_reflect", + "bevy_tasks", + "derive_more", + "serde", + "thiserror 2.0.12", +] + +[[package]] +name = "bevy_utils" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2da3b3c1f94dadefcbe837aaa4aa119fcea37f7bdc5307eb05b4ede1921e24" +dependencies = [ + "bevy_platform", + "thread_local", +] + +[[package]] +name = "bevy_window" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d83327cc5584da463d12b7a88ddb97f9e006828832287e1564531171fffdeb4" +dependencies = [ + "android-activity", + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_math", + "bevy_platform", + "bevy_reflect", + "bevy_utils", + "log", + "raw-window-handle", + "serde", + "smol_str", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", +] + +[[package]] +name = "bindgen" +version = "0.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +dependencies = [ + "serde", +] + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "blocking" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", + "portable-atomic", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-fnv1a-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b13ea120a812beba79e34316b3942a857c86ec1593cb34f27bb28272ce2cca" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix", + "windows-sys 0.59.0", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "disqualified" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" + +[[package]] +name = "downcast-rs" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "glam" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" +dependencies = [ + "bytemuck", + "libm", + "serde", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "equivalent", + "serde", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "portable-atomic", + "stable_deref_trait", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "libloading" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a793df0d7afeac54f95b471d3af7f0d4fb975699f972341a4b76988d49cdf0c" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lua-src" +version = "547.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edaf29e3517b49b8b746701e5648ccb5785cde1c119062cbabbc5d5cd115e42" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.5.12+a4f56a4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8e7962a5368d5f264d045a5a255e90f9aa3fc1941ae15a8d2940d42cac671" +dependencies = [ + "cc", + "which", +] + +[[package]] +name = "magnus" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d87ae53030f3a22e83879e666cb94e58a7bdf31706878a0ba48752994146dab" +dependencies = [ + "magnus-macros", + "rb-sys", + "rb-sys-env", + "seq-macro", +] + +[[package]] +name = "magnus-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5968c820e2960565f647819f5928a42d6e874551cab9d88d75e3e0660d7f71e3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mlua" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +dependencies = [ + "bstr", + "mlua-sys", + "num-traits", + "once_cell", + "rustc-hash 2.1.1", +] + +[[package]] +name = "mlua-sys" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "rb-sys" +version = "0.9.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ca6726be0eca74687047fed7dcbc2d509571f3962e190c343ac1eb40e482b3" +dependencies = [ + "rb-sys-build", +] + +[[package]] +name = "rb-sys-build" +version = "0.9.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f2390cfc87b7513656656faad6567291e581542d3ec41dd0a2bf381896e0880" +dependencies = [ + "bindgen 0.69.5", + "lazy_static", + "proc-macro2", + "quote", + "regex", + "shell-words", + "syn", +] + +[[package]] +name = "rb-sys-env" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a35802679f07360454b418a5d1735c89716bde01d35b1560fc953c1415a0b3bb" + +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rhai" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6" +dependencies = [ + "ahash", + "bitflags", + "instant", + "no-std-compat", + "num-traits", + "once_cell", + "rhai_codegen", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64", + "bitflags", + "serde", + "serde_derive", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "seq-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stackfuture" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eae92052b72ef70dafa16eddbabffc77e5ca3574be2f7bc1127b36f0a7ad7f2" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528bdd1f0e27b5dd9a4ededf154e824b0532731e4af73bb531de46276e0aab1e" +dependencies = [ + "bindgen 0.70.1", + "cc", + "cfg-if", + "once_cell", + "parking_lot", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "variadics_please" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b6d82be61465f97d42bd1d15bf20f3b0a3a0905018f38f9d6f6962055b0b5c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wgpu-types" +version = "24.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50ac044c0e76c03a0378e7786ac505d010a873665e2d51383dcff8dd227dc69c" +dependencies = [ + "bitflags", + "js-sys", + "log", + "serde", + "web-sys", +] + +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/book/Cargo.toml b/book/Cargo.toml new file mode 100644 index 0000000..d2afdf9 --- /dev/null +++ b/book/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bevy_scriptum_book" +publish = false +edition = "2024" + +[dependencies] +bevy_scriptum = { path = "../", features = ["ruby", "lua", "rhai"] } diff --git a/book/book.toml b/book/book.toml index 35d684f..8903447 100644 --- a/book/book.toml +++ b/book/book.toml @@ -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"] diff --git a/book/justfile b/book/justfile new file mode 100644 index 0000000..a221a90 --- /dev/null +++ b/book/justfile @@ -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 diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index cfaddce..348dea7 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -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) diff --git a/book/src/introduction.md b/book/src/introduction.md index 63a6a32..54975f7 100644 --- a/book/src/introduction.md +++ b/book/src/introduction.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::(|runtime| { - runtime.add_function(String::from("hello_from_my_plugin"), || { - info!("Hello from MyPlugin"); - }); - }); - } -} - -// Main -fn main() { - App::new() - .add_plugins(DefaultPlugins) - .add_scripting::(|_| { - // 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::*; diff --git a/book/src/lib.rs b/book/src/lib.rs new file mode 100644 index 0000000..e69de29 diff --git a/book/src/lua/builtin_types.md b/book/src/lua/builtin_types.md index e376a33..1a136ae 100644 --- a/book/src/lua/builtin_types.md +++ b/book/src/lua/builtin_types.md @@ -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::*; diff --git a/book/src/lua/calling_rust_from_script.md b/book/src/lua/calling_rust_from_script.md index 3b7321e..de90227 100644 --- a/book/src/lua/calling_rust_from_script.md +++ b/book/src/lua/calling_rust_from_script.md @@ -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::*; diff --git a/book/src/lua/calling_script_from_rust.md b/book/src/lua/calling_script_from_rust.md index b543c2c..c42fb08 100644 --- a/book/src/lua/calling_script_from_rust.md +++ b/book/src/lua/calling_script_from_rust.md @@ -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 diff --git a/book/src/lua/hello_world.md b/book/src/lua/hello_world.md index 469f551..a271b2e 100644 --- a/book/src/lua/hello_world.md +++ b/book/src/lua/hello_world.md @@ -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::*; diff --git a/book/src/lua/interacting_with_bevy.md b/book/src/lua/interacting_with_bevy.md index 6e7637d..96aad07 100644 --- a/book/src/lua/interacting_with_bevy.md +++ b/book/src/lua/interacting_with_bevy.md @@ -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; }, ); diff --git a/book/src/lua/spawning_scripts.md b/book/src/lua/spawning_scripts.md index 41a49b4..8acd82d 100644 --- a/book/src/lua/spawning_scripts.md +++ b/book/src/lua/spawning_scripts.md @@ -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) { 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::` 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() {} ``` diff --git a/book/src/multiple_plugins.md b/book/src/multiple_plugins.md new file mode 100644 index 0000000..db15636 --- /dev/null +++ b/book/src/multiple_plugins.md @@ -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::(|runtime| { + runtime.add_function(String::from("hello_from_my_plugin"), || { + info!("Hello from MyPlugin"); + }); + }); + } +} + +// Main +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_scripting::(|_| { + // nice and clean + }) + .add_plugins(MyPlugin) + .run(); +} +``` diff --git a/book/src/ruby/builtin_types.md b/book/src/ruby/builtin_types.md new file mode 100644 index 0000000..dd48962 --- /dev/null +++ b/book/src/ruby/builtin_types.md @@ -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::(|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::(|runtime| { + runtime.add_function(String::from("pass_to_rust"), |In((entity,)): In<(BevyEntity,)>| { + println!("pass_to_rust called with entity: {:?}", entity); + }); + }) + .run(); +} +``` diff --git a/book/src/ruby/calling_rust_from_script.md b/book/src/ruby/calling_rust_from_script.md new file mode 100644 index 0000000..f4520fb --- /dev/null +++ b/book/src/ruby/calling_rust_from_script.md @@ -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::(|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::(|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::(|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::(|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::(|runtime| { + runtime.add_function(String::from("returns_value"), || { + 123 + }); + }) + .run(); +} +``` + +```ruby +returns_value.and_then do |value| + puts(value) # 123 +end +``` diff --git a/book/src/ruby/calling_script_from_rust.md b/book/src/ruby/calling_script_from_rust.md new file mode 100644 index 0000000..6fcd068 --- /dev/null +++ b/book/src/ruby/calling_script_from_rust.md @@ -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, +) { + 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, +) { + 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 +``` diff --git a/book/src/ruby/hello_world.md b/book/src/ruby/hello_world.md new file mode 100644 index 0000000..b16f780 --- /dev/null +++ b/book/src/ruby/hello_world.md @@ -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::(|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::(|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| { + commands.spawn(Script::::new(asset_server.load("script.rb"))); + }) + .run(); +} +``` + +You should then see `my_print: 'Hello world!'` printed in your console. diff --git a/book/src/ruby/installation.md b/book/src/ruby/installation.md new file mode 100644 index 0000000..1dc0a82 --- /dev/null +++ b/book/src/ruby/installation.md @@ -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"); +} +``` diff --git a/book/src/ruby/interacting_with_bevy.md b/book/src/ruby/interacting_with_bevy.md new file mode 100644 index 0000000..0fe5e9a --- /dev/null +++ b/book/src/ruby/interacting_with_bevy.md @@ -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::(|runtime| { + runtime.add_function( + String::from("print_player_names"), + |players: Query<&Name, With>| { + 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::(|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) +``` diff --git a/book/src/ruby/ruby.md b/book/src/ruby/ruby.md new file mode 100644 index 0000000..1eb429f --- /dev/null +++ b/book/src/ruby/ruby.md @@ -0,0 +1,3 @@ +# Ruby + +This chapter demonstrates how to work with bevy_scriptum when using Ruby language runtime. diff --git a/book/src/ruby/spawning_scripts.md b/book/src/ruby/spawning_scripts.md new file mode 100644 index 0000000..3191f6f --- /dev/null +++ b/book/src/ruby/spawning_scripts.md @@ -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) { + commands.spawn(Script::::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::` 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 + } +} +``` diff --git a/book/src/workflow/live_reload.md b/book/src/workflow/live_reload.md index 447e84c..5bd90bf 100644 --- a/book/src/workflow/live_reload.md +++ b/book/src/workflow/live_reload.md @@ -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. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..989fef5 --- /dev/null +++ b/build.rs @@ -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"); + } +} diff --git a/examples/lua/side_effects.rs b/examples/lua/side_effects.rs index e3567ff..2bf4a86 100644 --- a/examples/lua/side_effects.rs +++ b/examples/lua/side_effects.rs @@ -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 { diff --git a/examples/rhai/side_effects.rs b/examples/rhai/side_effects.rs index 28a6770..f91ca3b 100644 --- a/examples/rhai/side_effects.rs +++ b/examples/rhai/side_effects.rs @@ -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) diff --git a/examples/ruby/call_function_from_rust.rs b/examples/ruby/call_function_from_rust.rs new file mode 100644 index 0000000..039170e --- /dev/null +++ b/examples/ruby/call_function_from_rust.rs @@ -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::(|runtime| { + runtime.add_function(String::from("quit"), |mut exit: EventWriter| { + exit.write(AppExit::Success); + }); + }) + .run(); +} + +fn startup(mut commands: Commands, assets_server: Res) { + commands.spawn(Script::::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, +) { + for (entity, mut script_data) in &mut scripted_entities { + scripting_runtime + .call_fn("on_update", &mut script_data, entity, ()) + .unwrap(); + } +} diff --git a/examples/ruby/current_entity.rs b/examples/ruby/current_entity.rs new file mode 100644 index 0000000..6e1c777 --- /dev/null +++ b/examples/ruby/current_entity.rs @@ -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::(|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) { + commands.spawn(( + Name::from("MyEntityName"), + Script::::new(assets_server.load("examples/ruby/current_entity.rb")), + )); +} diff --git a/examples/ruby/custom_type.rs b/examples/ruby/custom_type.rs new file mode 100644 index 0000000..2877b8d --- /dev/null +++ b/examples/ruby/custom_type.rs @@ -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::(|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, + assets_server: Res, +) { + 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::::new( + assets_server.load("examples/ruby/custom_type.rb"), + )); +} diff --git a/examples/ruby/ecs.rs b/examples/ruby/ecs.rs new file mode 100644 index 0000000..a48c4c6 --- /dev/null +++ b/examples/ruby/ecs.rs @@ -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::(|runtime| { + runtime.add_function( + String::from("print_player_names"), + |players: Query<&Name, With>| { + for player in &players { + println!("player name: {}", player); + } + }, + ); + }) + .add_systems(Startup, startup) + .run(); +} + +fn startup(mut commands: Commands, assets_server: Res) { + commands.spawn((Player, Name::new("John"))); + commands.spawn((Player, Name::new("Mary"))); + commands.spawn((Player, Name::new("Alice"))); + + commands.spawn(Script::::new( + assets_server.load("examples/ruby/ecs.rb"), + )); +} diff --git a/examples/ruby/entity_variable.rs b/examples/ruby/entity_variable.rs new file mode 100644 index 0000000..e9e25ee --- /dev/null +++ b/examples/ruby/entity_variable.rs @@ -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::(|_| {}) + .add_systems(Startup, startup) + .run(); +} + +fn startup(mut commands: Commands, assets_server: Res) { + commands.spawn(Script::::new( + assets_server.load("examples/ruby/entity_variable.rb"), + )); +} diff --git a/examples/ruby/function_params.rs b/examples/ruby/function_params.rs new file mode 100644 index 0000000..65fb3b6 --- /dev/null +++ b/examples/ruby/function_params.rs @@ -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::(|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| { + 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) { + commands.spawn(Script::::new( + assets_server.load("examples/ruby/function_params.rb"), + )); +} diff --git a/examples/ruby/function_return_value.rs b/examples/ruby/function_return_value.rs new file mode 100644 index 0000000..088779a --- /dev/null +++ b/examples/ruby/function_return_value.rs @@ -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::(|runtime| { + runtime.add_function(String::from("quit"), |mut exit: EventWriter| { + exit.write(AppExit::Success); + }); + }) + .run(); +} + +fn startup(mut commands: Commands, assets_server: Res) { + commands.spawn(Script::::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, + mut exit: EventWriter, +) { + 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); + } +} diff --git a/examples/ruby/hello_world.rs b/examples/ruby/hello_world.rs new file mode 100644 index 0000000..218067c --- /dev/null +++ b/examples/ruby/hello_world.rs @@ -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::(|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) { + commands.spawn(Script::::new( + assets_server.load("examples/ruby/hello_world.rb"), + )); +} diff --git a/examples/ruby/multiple_plugins.rs b/examples/ruby/multiple_plugins.rs new file mode 100644 index 0000000..59438fd --- /dev/null +++ b/examples/ruby/multiple_plugins.rs @@ -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::(|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) { + commands.spawn(Script::::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::(|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) { + commands.spawn(Script::::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::(|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) { + commands.spawn(Script::::new( + assets_server.load("examples/ruby/hello_world.rb"), + )); +} diff --git a/examples/ruby/promises.rs b/examples/ruby/promises.rs new file mode 100644 index 0000000..1b3ed62 --- /dev/null +++ b/examples/ruby/promises.rs @@ -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::(|builder| { + builder.add_function( + String::from("get_player_name"), + |player_names: Query<&Name, With>| { + player_names + .single() + .expect("Missing player_names") + .to_string() + }, + ); + }) + .add_systems(Startup, startup) + .run(); +} + +fn startup(mut commands: Commands, assets_server: Res) { + commands.spawn((Player, Name::new("John"))); + commands.spawn(Script::::new( + assets_server.load("examples/ruby/promises.rb"), + )); +} diff --git a/examples/ruby/side_effects.rs b/examples/ruby/side_effects.rs new file mode 100644 index 0000000..85c8564 --- /dev/null +++ b/examples/ruby/side_effects.rs @@ -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::(|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) { + commands.spawn((Script::::new( + assets_server.load("examples/ruby/side_effects.rb"), + ),)); +} + +fn print_entity_names_and_quit(query: Query<&Name>, mut exit: EventWriter) { + if !query.is_empty() { + for e in &query { + println!("{}", e); + } + exit.write(AppExit::Success); + } +} diff --git a/src/callback.rs b/src/callback.rs index 0174950..f19a898 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -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 { @@ -65,7 +65,7 @@ where fn into_callback_system(self, world: &mut World) -> CallbackSystem; } -impl IntoCallbackSystem for FN +impl IntoCallbackSystem 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::().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 IntoCallbackSystem, Out, Marker> + impl IntoCallbackSystem, Out, Marker> for FN where FN: IntoSystem, 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 { let mut inner_system = IntoSystem::into_system(self); inner_system.initialize(world); let system_fn = move |args: In>, world: &mut World| { let mut runtime = world.get_resource_mut::().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::().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) }) }; diff --git a/src/lib.rs b/src/lib.rs index 5f23c69..85f41ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,18 @@ //! ![demo](demo.gif) //! -//! 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::(|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::(|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::(|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::(|runtime| { -//! runtime.add_function(String::from("hello_from_my_plugin"), || { -//! info!("Hello from MyPlugin"); -//! }); -//! }); -//! } -//! } -//! -//! App::new() -//! .add_plugins(DefaultPlugins) -//! .add_scripting::(|_| { -//! // 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::(|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::(|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::(|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), - #[error("script compilation error: {0}")] - CompileError(Box), + #[error("script runtime error:\n{0}")] + RuntimeError(String), + #[error("script compilation error:\n{0}")] + CompileError(Box), #[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( + &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( + &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(&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(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T; fn eval( @@ -320,12 +343,12 @@ pub trait Runtime: Resource + Default { name: String, arg_types: Vec, f: impl Fn( - Self::CallContext, - Vec, - ) -> Result, ScriptingError> - + Send - + Sync - + 'static, + Self::CallContext, + Vec, + ) -> Result, 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; /// 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, ) -> Result; + + 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(&mut self, f: impl Fn(ScriptingRuntimeBuilder)) -> &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::() + ); + } + self.world_mut() .resource_mut::() .insert_after(Update, R::Schedule::default()); @@ -472,6 +510,20 @@ impl Default for Callbacks { } } +#[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}; } diff --git a/src/promise.rs b/src/promise.rs index 6b84a63..038cc0c 100644 --- a/src/promise.rs +++ b/src/promise.rs @@ -58,7 +58,7 @@ impl Promise { } /// 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 diff --git a/src/runtimes/lua.rs b/src/runtimes/lua.rs index 5613ba2..a049fbb 100644 --- a/src/runtimes/lua.rs +++ b/src/runtimes/lua.rs @@ -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>; @@ -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, f: impl Fn( - Self::CallContext, - Vec, - ) -> Result< - crate::promise::Promise, - crate::ScriptingError, - > + Send - + Sync - + 'static, + Self::CallContext, + Vec, + ) -> Result< + crate::promise::Promise, + 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::(&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::(&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::(&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( + &mut self, + f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static, + ) -> T { + self.with_engine_mut(f) + } + + fn with_engine_send( + &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 { diff --git a/src/runtimes/mod.rs b/src/runtimes/mod.rs index c01166f..96bf325 100644 --- a/src/runtimes/mod.rs +++ b/src/runtimes/mod.rs @@ -2,3 +2,5 @@ pub mod lua; #[cfg(feature = "rhai")] pub mod rhai; +#[cfg(feature = "ruby")] +pub mod ruby; diff --git a/src/runtimes/rhai.rs b/src/runtimes/rhai.rs index c293364..57dd83a 100644 --- a/src/runtimes/rhai.rs +++ b/src/runtimes/rhai.rs @@ -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 { 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_VAR_NAME).unwrap(); + scope.remove::(ENTITY_VAR_NAME).unwrap(); Ok(Self::ScriptData { ast, scope }) } @@ -87,12 +117,12 @@ impl Runtime for RhaiRuntime { name: String, arg_types: Vec, f: impl Fn( - Self::CallContext, - Vec, - ) -> Result, ScriptingError> - + Send - + Sync - + 'static, + Self::CallContext, + Vec, + ) -> Result, 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 { 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::(options, scope, &ast, name, args); - scope.remove::(ENTITY_VAR_NAME).unwrap(); + scope.remove::(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::>(); 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(&self, f: impl FnOnce(&Self::RawEngine) -> T) -> T { f(&self.engine) } + + fn with_engine_send_mut( + &mut self, + f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static, + ) -> T { + self.with_engine_mut(f) + } + + fn with_engine_send( + &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") - .register_get("index", |entity: &mut Entity| entity.index()); + .register_type_with_name::("Entity") + .register_get("index", |entity: &mut BevyEntity| entity.index()); #[allow(deprecated)] engine .register_type_with_name::>("Promise") @@ -181,13 +225,13 @@ impl Default for RhaiRuntime { ); engine - .register_type_with_name::("Vec3") + .register_type_with_name::("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 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 { diff --git a/src/runtimes/ruby.rs b/src/runtimes/ruby.rs new file mode 100644 index 0000000..1ccccdd --- /dev/null +++ b/src/runtimes/ruby.rs @@ -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, +} + +#[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 for RubyScript { + fn from(value: String) -> Self { + Self(value) + } +} + +type RubyClosure = Box; + +struct RubyThread { + sender: crossbeam_channel::Sender, + handle: Option>, +} + +static RUBY_THREAD: LazyLock>, Condvar)>> = + LazyLock::new(|| Arc::new((Mutex::new(Some(RubyThread::spawn())), Condvar::new()))); + +impl RubyThread { + fn build_ruby_process_argv() -> anyhow::Result> { + 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::>(); + + 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(&self, f: Box 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 = 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 { + 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 { + 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 { + let result: Result<&Self, _> = TryConvert::try_convert(val); + result.cloned() + } +} + +impl From 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::() + .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); + +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( + &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( + &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(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( + &mut self, + f: impl FnOnce(&mut Self::RawEngine) -> T + Send + 'static, + ) -> T { + self.execute_in_thread_mut(f) + } + + fn with_engine_send( + &self, + f: impl FnOnce(&Self::RawEngine) -> T + Send + 'static, + ) -> T { + self.execute_in_thread(f) + } + + fn with_engine_mut(&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(&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 { + let script = script.0.clone(); + self.execute_in_thread(Box::new(move |ruby: &Ruby| { + Self::with_current_entity(ruby, entity, || { + ruby.eval::(&script) + .map_err(>::into) + })?; + Ok::(RubyScriptData) + })) + } + + fn register_fn( + &mut self, + name: String, + _arg_types: Vec, + f: impl Fn( + Self::CallContext, + Vec, + ) -> Result< + crate::promise::Promise, + crate::ScriptingError, + > + Send + + Sync + + 'static, + ) -> Result<(), crate::ScriptingError> { + type CallbackClosure = Box< + dyn Fn( + (), + Vec, + ) + -> Result, crate::ScriptingError> + + Send, + >; + static RUBY_CALLBACKS: LazyLock>> = + 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 { + 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, + ) -> Result { + 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 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 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 { + Vec::new() + } +} + +impl FuncArgs<'_, RubyValue, RubyRuntime> for Vec { + fn parse(self, _engine: &magnus::Ruby) -> Vec { + self.into_iter() + .map(|x| RubyValue::new(x.into_value())) + .collect() + } +} + +pub struct RArray(pub Opaque); + +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 { + 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); diff --git a/src/systems.rs b/src/systems.rs index a259fe9..7cbf81b 100644 --- a/src/systems.rs +++ b/src/systems.rs @@ -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( 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(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(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); } } } diff --git a/tests/tests.rs b/tests/tests.rs index 7dcf443..cad660b 100644 --- a/tests/tests.rs +++ b/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::() + .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( app: &mut App, path: String, @@ -42,7 +60,7 @@ fn run_script( entity_id } -#[cfg(any(feature = "rhai", feature = "lua"))] +#[cfg(any(feature = "rhai", feature = "lua", feature = "ruby"))] fn call_script_on_update_from_rust( mut scripted_entities: Query<(Entity, &mut R::ScriptData)>, scripting_runtime: ResMut, @@ -55,7 +73,7 @@ fn call_script_on_update_from_rust( .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| { + times_called.times_called += 1; + }, + ); + }) + .init_resource::(); - 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::(); 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::() - .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::(); + + app.add_scripting::<$runtime>(|runtime| { + runtime.add_function( + String::from("rust_func"), + |In((index,)): In<(u32,)>, mut res: ResMut| { + 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::().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, + } + + app.world_mut().init_resource::(); + + app.add_scripting::<$runtime>(|runtime| { + runtime.add_function( + String::from("rust_func"), + |In((index,)): In<(u32,)>, mut res: ResMut| { + 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::().unwrap().index, + Some(entity.index()) + ); + } + + #[test] + fn pass_entity_from_script() { + let mut app = build_test_app(); + + #[derive(Default, Resource)] + struct State { + index: Option, + } + + app.world_mut().init_resource::(); + + app.add_scripting::<$runtime>(|runtime| { + runtime.add_function( + String::from("rust_func"), + |In((entity,)): In<($entity_type,)>, mut res: ResMut| { + 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::().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::(); + + app.add_scripting::<$runtime>(|runtime| { + runtime.add_function( + String::from("rust_func"), + |In((v,)): In<($vec_type,)>, mut res: ResMut| { + 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::().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::(); + + app.add_scripting::<$runtime>(|runtime| { + runtime.add_function(String::from("mark_success"), |mut res: ResMut| { + 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::().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::().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::().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::().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::(|_| {}); + let runtime = app.world().get_resource::().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); }