Skip to content

Commit

Permalink
feat: Improvements to BMS in multi-language context (#194)
Browse files Browse the repository at this point in the history
* fix: bugs to do with multiple languages being initialized

* rhai re-implementation WIP

* feat: switch vscode to xtask

* re-write lua tests to use common integration test framework

* setup rhai tests

* add assertions

* start writing marshalling code for rhai

* move world pointers to thread locals

* move namespace stuff into core

* chore(codegen): update bevy bindings (#195)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* refactor function interface, reduce amount of string cloning, happy cows

* remove extra import, fix lua to_string

* clean up comments

* remove rhai and rune support for now

* correct for changed features in xtasks

* Add note in readme about removal of features

* start adding documentation for new languages

* format

* fix failing test utils test

* generate matrix from xtask

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
makspll and github-actions[bot] authored Jan 13, 2025
1 parent fc03230 commit 7595742
Show file tree
Hide file tree
Showing 85 changed files with 6,165 additions and 5,959 deletions.
1 change: 0 additions & 1 deletion .github/workflows/bevy_mod_scripting.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ jobs:
{label: Windows, os: windows-latest },
{label: MacOS, os: macOS-latest },
{label: Ubuntu, os: ubuntu-latest },
{label: Ubuntu Aarch64, os: ubuntu-latest }
]
steps:
- name: Checkout
Expand Down
8 changes: 5 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@
},
"rust-analyzer.rustc.source": "discover",
"rust-analyzer.linkedProjects": [
"./crates/bevy_api_gen/Cargo.toml",
// "./crates/bevy_api_gen/Cargo.toml",
"Cargo.toml",
],
"rust-analyzer.check.invocationStrategy": "per_workspace",
"rust-analyzer.check.invocationLocation": "workspace",
"rust-analyzer.check.invocationStrategy": "once",
"rust-analyzer.check.overrideCommand": [
"/home/makspll/git/bevy_mod_scripting/check.sh"
],
Expand All @@ -28,5 +27,8 @@
"rust-analyzer.runnables.extraArgs": [
"--profile=release-with-debug",
],
// "rust-analyzer.cargo.features": [
// "bevy_mod_scripting_functions/test_functions"
// ]
// "rust-analyzer.semanticHighlighting.operator.enable": false
}
3 changes: 1 addition & 2 deletions CONTRIBUTING.MD
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,7 @@ Enhancement suggestions are tracked as [GitHub issues](https://github.com/makspl

### Your First Code Contribution

This project uses xtask to manage the build and test process. You can run `cargo xtask` to see the available commands. Run `xtask init` to setup your local development environment.

Before contributing see the [book](https://makspll.github.io/bevy_mod_scripting) for helpful guides on how to get started with the project.


### Improving The Documentation
Expand Down
35 changes: 17 additions & 18 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ name = "bevy_mod_scripting"
path = "src/lib.rs"

[package.metadata."docs.rs"]
features = ["lua54", "rhai", "rune"]
features = ["lua54"]

[features]
default = ["core_functions", "bevy_bindings", "unsafe_lua_modules"]
Expand All @@ -44,44 +44,48 @@ mlua_macros = ["bevy_mod_scripting_lua/mlua_macros"]
mlua_async = ["bevy_mod_scripting_lua/mlua_async"]

## rhai
rhai = ["bevy_mod_scripting_rhai"]
# rhai = ["bevy_mod_scripting_rhai"]

## rune
rune = ["bevy_mod_scripting_rune"]
# rune = ["bevy_mod_scripting_rune"]

[dependencies]
bevy = { workspace = true }
bevy_mod_scripting_core = { workspace = true }
bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.9.0-alpha.2", optional = true }
bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.9.0-alpha.2", optional = true }
bevy_mod_scripting_rune = { path = "crates/languages/bevy_mod_scripting_rune", version = "0.9.0-alpha.2", optional = true }
# bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.9.0-alpha.2", optional = true }
# bevy_mod_scripting_rune = { path = "crates/languages/bevy_mod_scripting_rune", version = "0.9.0-alpha.2", optional = true }
bevy_mod_scripting_functions = { workspace = true }

[workspace.dependencies]
bevy = { version = "0.15.0", default-features = false }
bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.9.0-alpha.2" }
bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.9.0-alpha.2" }
test_utils = { path = "crates/test_utils" }
bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.9.0-alpha.2", default-features = false }
mlua = { version = "0.10" }
rhai = { version = "1.20.1" }
# rhai = { version = "1.20.1" }

# test utilities
script_integration_test_harness = { path = "crates/script_integration_test_harness" }
test_utils = { path = "crates/test_utils" }

[dev-dependencies]
bevy = { workspace = true, default-features = true }
clap = { version = "4.1", features = ["derive"] }
rand = "0.8.5"
bevy_console = "0.13"
rhai-rand = "0.1"
# rhai-rand = "0.1"
ansi-parser = "0.9"

[workspace]
members = [
"crates/bevy_mod_scripting_core",
"crates/languages/bevy_mod_scripting_lua",
"crates/languages/bevy_mod_scripting_rhai",
"crates/languages/bevy_mod_scripting_rune",
# "crates/languages/bevy_mod_scripting_rhai",
# "crates/languages/bevy_mod_scripting_rune",
"crates/test_utils",
"crates/bevy_mod_scripting_functions",
"crates/xtask",
"crates/script_integration_test_harness",
]
resolver = "2"
exclude = ["crates/bevy_api_gen", "crates/macro_tests"]
Expand All @@ -106,11 +110,6 @@ inherits = "release"
debug = true

[[example]]
name = "game_of_life_lua"
path = "examples/lua/game_of_life.rs"
name = "game_of_life"
path = "examples/game_of_life.rs"
required-features = ["lua54", "bevy/file_watcher", "bevy/multi_threaded"]

# [[example]]
# required-features = ["rhai", "bevy/file_watcher", "bevy/multi_threaded"]
# name = "game_of_life_rhai"
# path = "examples/rhai/game_of_life.rs"
1 change: 1 addition & 0 deletions assets/scripts/game_of_life.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ end
function on_click(x,y)
-- get the settings
world.info("Lua: Clicked at x: " .. x .. " y: " .. y)
print(entity)
local life_state = fetch_life_state()
local cells = life_state.cells

Expand Down
19 changes: 10 additions & 9 deletions assets/scripts/game_of_life.rhai
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
fn init() {
let LifeState = world.get_type_by_name("LifeState");
let life_state = world.get_component(entity,LifeState);
let cells = life_state.cells;
fn on_script_loaded() {
world.info("Game of Life script loaded");
// let LifeState = world.get_type_by_name("LifeState");
// let life_state = world.get_component(entity,LifeState);
// let cells = life_state.cells;

// set some cells alive
for x in 1..10000 {
let index = rand(0..cells.len());
cells[index] = 255;
}
// // set some cells alive
// for x in 1..10000 {
// let index = rand(0..cells.len());
// cells[index] = 255;
// }
}

fn on_update() {
Expand Down
11 changes: 2 additions & 9 deletions check.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
#!/bin/bash
unset RUSTUP_TOOLCHAIN
CURRENT_DIR=$(basename "$PWD")


if [[ "$CURRENT_DIR" == "bevy_api_gen" ]]; then
cargo +nightly-2024-11-05 clippy --all-targets --message-format=json
else
cargo clippy --workspace --all-targets --message-format=json --features="lua54 rhai rune bevy/file_watcher bevy/multi_threaded"
fi
cd "$(dirname "$0")"
cargo xtask check --ide-mode
2 changes: 1 addition & 1 deletion crates/bevy_api_gen/templates/header.tera
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use bevy_mod_scripting_core::{
StoreDocumentation,
bindings::{
ReflectReference,
function::from::{Ref, Mut, Val}
function::{from::{Ref, Mut, Val}, namespace::{NamespaceBuilder}}
}
};
{% if args.self_is_bms_lua %}
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_mod_scripting_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ doc_always = []

# if enabled enables some common mlua trait implementations
mlua_impls = ["mlua"]
rhai_impls = ["rhai"]
# rhai_impls = ["rhai"]

[dependencies]
mlua = { optional = true, workspace = true }
rhai = { optional = true, workspace = true }
# rhai = { optional = true, workspace = true }

bevy = { workspace = true, default-features = false, features = [
"bevy_asset",
Expand Down
72 changes: 60 additions & 12 deletions crates/bevy_mod_scripting_core/src/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,39 @@ use std::{
path::{Path, PathBuf},
};

/// Represents a scripting language. Languages which compile into another language should use the target language as their language.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Language {
Rhai,
Lua,
Rune,
External(Cow<'static, str>),
/// Set if none of the asset path to language mappers match
Unknown,
}

impl std::fmt::Display for Language {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Language::Rhai => "Rhai".fmt(f),
Language::Lua => "Lua".fmt(f),
Language::Rune => "Rune".fmt(f),
Language::External(cow) => cow.fmt(f),
Language::Unknown => "Unknown".fmt(f),
}
}
}

/// Represents a script loaded into memory as an asset
#[derive(Asset, TypePath, Clone)]
pub struct ScriptAsset {
pub content: Box<[u8]>,
/// The virtual filesystem path of the asset, used to map to the script Id for asset backed scripts
pub asset_path: PathBuf,
pub language: Cow<'static, str>,
}

#[derive(Default)]
pub struct ScriptAssetLoader {
/// Used to set the language of the script
pub language: Cow<'static, str>,
/// The file extensions this loader should handle
pub extensions: &'static [&'static str],
/// preprocessor to run on the script before saving the content to an asset
Expand Down Expand Up @@ -52,7 +73,6 @@ impl AssetLoader for ScriptAssetLoader {
let asset = ScriptAsset {
content: content.into_boxed_slice(),
asset_path: load_context.path().to_owned(),
language: self.language.clone(),
};
Ok(asset)
}
Expand All @@ -62,9 +82,24 @@ impl AssetLoader for ScriptAssetLoader {
}
}

#[derive(Clone, Copy, Resource)]
#[derive(Clone, Resource)]
pub struct ScriptAssetSettings {
pub script_id_mapper: AssetPathToScriptIdMapper,
pub script_language_mappers: Vec<AssetPathToLanguageMapper>,
}

impl ScriptAssetSettings {
pub fn select_script_language(&self, path: &Path) -> Language {
for mapper in &self.script_language_mappers {
let language = (mapper.map)(path);
match language {
Language::Unknown => continue,
_ => return language,
}
}

Language::Unknown
}
}

impl Default for ScriptAssetSettings {
Expand All @@ -73,6 +108,7 @@ impl Default for ScriptAssetSettings {
script_id_mapper: AssetPathToScriptIdMapper {
map: (|path: &Path| path.to_string_lossy().into_owned().into()),
},
script_language_mappers: vec![],
}
}
}
Expand All @@ -83,22 +119,34 @@ pub struct AssetPathToScriptIdMapper {
pub map: fn(&Path) -> ScriptId,
}

#[derive(Clone, Copy)]
pub struct AssetPathToLanguageMapper {
pub map: fn(&Path) -> Language,
}

/// A cache of asset id's to their script id's. Necessary since when we drop an asset we won't have the ability to get the path from the asset.
#[derive(Default, Debug, Resource)]
pub struct AssetIdToScriptIdMap {
pub map: HashMap<AssetId<ScriptAsset>, ScriptId>,
pub struct ScriptMetadataStore {
pub map: HashMap<AssetId<ScriptAsset>, ScriptMetadata>,
}

#[derive(Debug, Clone)]
pub struct ScriptMetadata {
pub script_id: ScriptId,
pub language: Language,
}

impl AssetIdToScriptIdMap {
pub fn insert(&mut self, id: AssetId<ScriptAsset>, path: ScriptId) {
self.map.insert(id, path);
impl ScriptMetadataStore {
pub fn insert(&mut self, id: AssetId<ScriptAsset>, meta: ScriptMetadata) {
// TODO: new generations of assets are not going to have the same ID as the old one
self.map.insert(id, meta);
}

pub fn get(&self, id: AssetId<ScriptAsset>) -> Option<&ScriptId> {
pub fn get(&self, id: AssetId<ScriptAsset>) -> Option<&ScriptMetadata> {
self.map.get(&id)
}

pub fn remove(&mut self, id: AssetId<ScriptAsset>) -> Option<ScriptId> {
pub fn remove(&mut self, id: AssetId<ScriptAsset>) -> Option<ScriptMetadata> {
self.map.remove(&id)
}
}
1 change: 0 additions & 1 deletion crates/bevy_mod_scripting_core/src/bindings/allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,6 @@ impl ReflectAllocator {
/// Runs a garbage collection pass on the allocations, removing any allocations which have no more strong references
/// Needs to be run periodically to prevent memory leaks
pub fn clean_garbage_allocations(&mut self) {
bevy::log::trace!("Cleaning garbage allocations");
self.allocations.retain(|k, _| Arc::strong_count(&k.0) > 1);
}

Expand Down
36 changes: 36 additions & 0 deletions crates/bevy_mod_scripting_core/src/bindings/function/from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use std::{
path::PathBuf,
};

use super::script_function::{DynamicScriptFunction, DynamicScriptFunctionMut};

/// Describes the procedure for constructing a value of type `T` from a [`ScriptValue`].
///
/// The [`FromScript::This`] associated type is used to allow for the implementation of this trait to return
Expand Down Expand Up @@ -387,3 +389,37 @@ where
}
}
}

impl FromScript for DynamicScriptFunctionMut {
type This<'w> = Self;

fn from_script(value: ScriptValue, _: WorldGuard<'_>) -> Result<Self::This<'_>, InteropError>
where
Self: Sized,
{
match value {
ScriptValue::FunctionMut(f) => Ok(f),
_ => Err(InteropError::value_mismatch(
std::any::TypeId::of::<Self>(),
value,
)),
}
}
}

impl FromScript for DynamicScriptFunction {
type This<'w> = Self;

fn from_script(value: ScriptValue, _: WorldGuard<'_>) -> Result<Self::This<'_>, InteropError>
where
Self: Sized,
{
match value {
ScriptValue::Function(f) => Ok(f),
_ => Err(InteropError::value_mismatch(
std::any::TypeId::of::<Self>(),
value,
)),
}
}
}
6 changes: 6 additions & 0 deletions crates/bevy_mod_scripting_core/src/bindings/function/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ impl IntoScript for () {
self_type_dependency_only!(());

impl IntoScript for DynamicScriptFunctionMut {
fn into_script(self, _world: WorldGuard) -> Result<ScriptValue, InteropError> {
Ok(ScriptValue::FunctionMut(self))
}
}

impl IntoScript for DynamicScriptFunction {
fn into_script(self, _world: WorldGuard) -> Result<ScriptValue, InteropError> {
Ok(ScriptValue::Function(self))
}
Expand Down
Loading

0 comments on commit 7595742

Please sign in to comment.