diff --git a/Cargo.toml b/Cargo.toml index 41d08e564a..ce81a4f190 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -98,6 +98,7 @@ bevy_mod_scripting_lua = { workspace = true, optional = true } bevy_mod_scripting_rhai = { workspace = true, optional = true } bevy_mod_scripting_functions = { workspace = true } bevy_mod_scripting_derive = { workspace = true } +bevy_mod_scripting_asset = { workspace = true } [workspace.dependencies] # local crates @@ -110,6 +111,8 @@ ladfile = { path = "crates/ladfile", version = "0.5.0" } ladfile_builder = { path = "crates/ladfile_builder", version = "0.5.1" } bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.15.1", default-features = false } bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.15.1", default-features = false } +bevy_mod_scripting_asset = { path = "crates/bevy_mod_scripting_asset", version = "0.15.1", default-features = false } + # bevy bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.15.1" } @@ -241,6 +244,7 @@ members = [ "crates/ladfile_builder", "crates/bevy_system_reflection", "crates/bindings/*", + "crates/bevy_mod_scripting_asset", ] resolver = "2" exclude = ["codegen", "crates/macro_tests", "xtask"] diff --git a/assets/tests/add_system/added_systems_run_in_parallel.lua b/assets/tests/add_system/added_systems_run_in_parallel.lua index 8c8afb7f15..32ac6ee2a6 100644 --- a/assets/tests/add_system/added_systems_run_in_parallel.lua +++ b/assets/tests/add_system/added_systems_run_in_parallel.lua @@ -25,7 +25,7 @@ digraph { node_0 [label="bevy_asset::assets::Assets::asset_events"]; node_1 [label="bevy_asset::assets::Assets::asset_events"]; node_2 [label="bevy_asset::assets::Assets<()>::asset_events"]; - node_3 [label="bevy_asset::assets::Assets::asset_events"]; + node_3 [label="bevy_asset::assets::Assets::asset_events"]; node_4 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"]; node_5 [label="script_integration_test_harness::dummy_before_post_update_system"]; node_6 [label="script_integration_test_harness::dummy_post_update_system"]; diff --git a/assets/tests/add_system/added_systems_run_in_parallel.rhai b/assets/tests/add_system/added_systems_run_in_parallel.rhai index cc59990d73..cad12e36f5 100644 --- a/assets/tests/add_system/added_systems_run_in_parallel.rhai +++ b/assets/tests/add_system/added_systems_run_in_parallel.rhai @@ -24,7 +24,7 @@ digraph { node_0 [label="bevy_asset::assets::Assets::asset_events"]; node_1 [label="bevy_asset::assets::Assets::asset_events"]; node_2 [label="bevy_asset::assets::Assets<()>::asset_events"]; - node_3 [label="bevy_asset::assets::Assets::asset_events"]; + node_3 [label="bevy_asset::assets::Assets::asset_events"]; node_4 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"]; node_5 [label="script_integration_test_harness::dummy_before_post_update_system"]; node_6 [label="script_integration_test_harness::dummy_post_update_system"]; diff --git a/crates/bevy_mod_scripting_asset/Cargo.toml b/crates/bevy_mod_scripting_asset/Cargo.toml new file mode 100644 index 0000000000..0699b70fa6 --- /dev/null +++ b/crates/bevy_mod_scripting_asset/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "bevy_mod_scripting_asset" +version = "0.15.1" +authors = ["Maksymilian Mozolewski "] +edition = "2024" +license = "MIT OR Apache-2.0" +description = "Core traits and structures required for other parts of bevy_mod_scripting" +repository = "https://github.com/makspll/bevy_mod_scripting" +homepage = "https://github.com/makspll/bevy_mod_scripting" +categories = ["game-development"] +readme = "readme.md" + + +[dependencies] +bevy_reflect = { workspace = true } +bevy_asset = { workspace = true } +bevy_log = { workspace = true } +serde = { workspace = true, features = ["derive"] } +[lints] +workspace = true diff --git a/crates/bevy_mod_scripting_asset/readme.md b/crates/bevy_mod_scripting_asset/readme.md new file mode 100644 index 0000000000..8ab67ce5a9 --- /dev/null +++ b/crates/bevy_mod_scripting_asset/readme.md @@ -0,0 +1,3 @@ +# bevy_mod_scripting_core + +This crate is a part of the ["bevy_mod_scripting" workspace](https://github.com/makspll/bevy_mod_scripting). \ No newline at end of file diff --git a/crates/bevy_mod_scripting_asset/src/error.rs b/crates/bevy_mod_scripting_asset/src/error.rs new file mode 100644 index 0000000000..daf2e7ddde --- /dev/null +++ b/crates/bevy_mod_scripting_asset/src/error.rs @@ -0,0 +1,51 @@ +//! Error definitions for the scripting asset pipeline. + +use std::fmt::Display; + +use bevy_asset::AssetPath; + +#[derive(Debug)] +/// An error that can occur when loading or processing a script asset. +pub struct ScriptAssetError { + pub(crate) phrase: &'static str, + pub(crate) asset_path: Option>, + pub(crate) inner: Box, +} +impl ScriptAssetError { + /// Create a new script asset error + pub fn new( + phrase: &'static str, + asset_path: Option<&AssetPath<'static>>, + inner: Box, + ) -> Self { + Self { + phrase, + asset_path: asset_path.cloned(), + inner, + } + } +} + +impl Display for ScriptAssetError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(path) = &self.asset_path { + write!( + f, + "Error {}. while processing script asset '{}': {}", + self.phrase, path, self.inner + ) + } else { + write!( + f, + "Error {}. while processing script asset: {}", + self.phrase, self.inner + ) + } + } +} + +impl std::error::Error for ScriptAssetError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(self.inner.as_ref()) + } +} diff --git a/crates/bevy_mod_scripting_asset/src/language.rs b/crates/bevy_mod_scripting_asset/src/language.rs new file mode 100644 index 0000000000..cfa66e54e5 --- /dev/null +++ b/crates/bevy_mod_scripting_asset/src/language.rs @@ -0,0 +1,84 @@ +//! Defines supported scripting languages and their file extensions. + +use serde::{Deserialize, Serialize}; +use std::borrow::Cow; + +/// 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, Default, Serialize, Deserialize)] +pub enum Language { + /// The Rhai scripting language + Rhai, + /// The Lua scripting language + Lua, + /// The Rune scripting language + Rune, + /// An external scripting language + External(Cow<'static, str>), + /// Set if none of the asset path to language mappers match + #[default] + 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), + } + } +} + +/// Collect the language extensions supported during initialization. +/// +/// NOTE: This resource is removed after plugin setup. +#[derive(Debug)] +pub struct LanguageExtensions(Vec<&'static str>, Vec); + +impl LanguageExtensions { + /// Create a new language extensions mapping from an iterator of (extension, language) pairs. + pub fn new(iter: impl IntoIterator) -> Self { + let (extensions, languages): (Vec<&'static str>, Vec) = iter.into_iter().unzip(); + Self(extensions, languages) + } + + /// Retrieves the language for the given file extension, if it exists. + pub fn get(&self, extension: &str) -> Option<&Language> { + self.0 + .iter() + .position(|&ext| ext.eq_ignore_ascii_case(extension)) + .and_then(|index| self.1.get(index)) + } + + /// Inserts a new (extension, language) pair into the mapping. + pub fn insert(&mut self, extension: &'static str, language: Language) { + if let Some(pos) = self + .0 + .iter() + .position(|&ext| ext.eq_ignore_ascii_case(extension)) + { + self.1[pos] = language; + } else { + self.0.push(extension); + self.1.push(language); + } + } + + /// Returns a slice of all supported file extensions. + pub fn extensions(&self) -> &[&str] { + self.0.as_slice() + } +} + +impl Default for LanguageExtensions { + fn default() -> Self { + LanguageExtensions::new([ + ("lua", Language::Lua), + ("luau", Language::Lua), + ("rhai", Language::Rhai), + ("rn", Language::Rune), + ]) + } +} diff --git a/crates/bevy_mod_scripting_asset/src/lib.rs b/crates/bevy_mod_scripting_asset/src/lib.rs new file mode 100644 index 0000000000..ca9a8acbb4 --- /dev/null +++ b/crates/bevy_mod_scripting_asset/src/lib.rs @@ -0,0 +1,8 @@ +//! All things asset and scripting related. + +pub mod error; +pub mod language; +pub mod loader; +pub mod script_asset; + +pub use {error::*, language::*, loader::*, script_asset::*}; diff --git a/crates/bevy_mod_scripting_asset/src/loader.rs b/crates/bevy_mod_scripting_asset/src/loader.rs new file mode 100644 index 0000000000..c917d67b06 --- /dev/null +++ b/crates/bevy_mod_scripting_asset/src/loader.rs @@ -0,0 +1,105 @@ +//! A loader pipeline for script assets + +use bevy_asset::AssetLoader; +use bevy_log::warn; +use serde::{Deserialize, Serialize}; + +use crate::{Language, LanguageExtensions, ScriptAsset, ScriptAssetError}; + +/// Script settings +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct ScriptSettings { + /// Define the language for a script or use the extension if None. + pub language: Option, +} + +/// A loader for script assets +pub struct ScriptAssetLoader { + /// The file extensions this loader should handle + language_extensions: &'static LanguageExtensions, + /// preprocessor to run on the script before saving the content to an asset + pub preprocessor: Option Result<(), ScriptAssetError> + Send + Sync>>, +} + +impl ScriptAssetLoader { + /// Create a new script asset loader for the given extensions. + pub fn new(language_extensions: &'static LanguageExtensions) -> Self { + Self { + language_extensions, + preprocessor: None, + } + } + + /// Add a preprocessor + pub fn with_preprocessor( + mut self, + preprocessor: Box Result<(), ScriptAssetError> + Send + Sync>, + ) -> Self { + self.preprocessor = Some(preprocessor); + self + } +} + +impl AssetLoader for ScriptAssetLoader { + type Asset = ScriptAsset; + + type Settings = ScriptSettings; + + type Error = ScriptAssetError; + + async fn load( + &self, + reader: &mut dyn bevy_asset::io::Reader, + settings: &Self::Settings, + load_context: &mut bevy_asset::LoadContext<'_>, + ) -> Result { + let mut content = Vec::new(); + reader.read_to_end(&mut content).await.map_err(|e| { + ScriptAssetError::new( + "reading from disk", + Some(load_context.asset_path()), + Box::new(e), + ) + })?; + if let Some(processor) = &self.preprocessor { + processor(&mut content)?; + } + let language = settings.language.clone().unwrap_or_else(|| { + let ext = load_context + .path() + .extension() + .and_then(|e| e.to_str()) + .unwrap_or_default(); + self.language_extensions + .get(ext) + .cloned() + .unwrap_or_else(|| { + warn!("Unknown language for {:?}", load_context.path().display()); + Language::Unknown + }) + }); + // if language == Language::Lua && cfg!(not(feature = "mlua")) { + // warn_once!( + // "Script {:?} is a Lua script but the {:?} feature is not enabled; the script will not be evaluated.", + // load_context.path().display(), + // "mlua" + // ); + // } + // if language == Language::Rhai && cfg!(not(feature = "rhai")) { + // warn_once!( + // "Script {:?} is a Rhai script but the {:?} feature is not enabled; the script will not be evaluated.", + // load_context.path().display(), + // "rhai" + // ); + // } + let asset = ScriptAsset { + content: content.into_boxed_slice(), + language, + }; + Ok(asset) + } + + fn extensions(&self) -> &[&str] { + self.language_extensions.extensions() + } +} diff --git a/crates/bevy_mod_scripting_asset/src/script_asset.rs b/crates/bevy_mod_scripting_asset/src/script_asset.rs new file mode 100644 index 0000000000..5f4d91374d --- /dev/null +++ b/crates/bevy_mod_scripting_asset/src/script_asset.rs @@ -0,0 +1,32 @@ +//! Scripting asset definitions + +use bevy_asset::Asset; +use bevy_reflect::Reflect; + +use crate::Language; + +/// Represents a script loaded into memory as an asset +#[derive(Asset, Clone, Reflect)] +#[reflect(opaque)] +pub struct ScriptAsset { + /// The body of the script + pub content: Box<[u8]>, // Any chance a Cow<'static, ?> could work here? + /// The language of the script + pub language: Language, +} + +impl From for ScriptAsset { + fn from(s: String) -> ScriptAsset { + ScriptAsset { + content: s.into_bytes().into_boxed_slice(), + language: Language::default(), + } + } +} + +impl ScriptAsset { + /// Create a new script asset with an unknown language. + pub fn new(s: impl Into) -> Self { + s.into().into() + } +} diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index 1a88236531..5d532387bd 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -25,6 +25,11 @@ mlua_impls = ["mlua"] rhai_impls = ["rhai"] [dependencies] +bevy_mod_scripting_derive = { workspace = true } +bevy_mod_scripting_asset = { workspace = true } +bevy_system_reflection = { workspace = true } + + mlua = { workspace = true, optional = true } rhai = { workspace = true, features = ["sync"], optional = true } @@ -40,16 +45,13 @@ parking_lot = { workspace = true } smallvec = { workspace = true } itertools = { workspace = true } profiling = { workspace = true } -bevy_mod_scripting_derive = { workspace = true } fixedbitset = { workspace = true } -bevy_system_reflection = { workspace = true } serde = { workspace = true, features = ["derive"] } uuid = { workspace = true } variadics_please = { workspace = true } [dev-dependencies] test_utils = { workspace = true } -tokio = { workspace = true, features = ["rt", "macros"] } pretty_assertions = { workspace = true, features = ["alloc"] } [lints] diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index 96544bdc09..823b4aa83a 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -1,16 +1,17 @@ //! Systems and resources for handling script assets and events -use std::{borrow::Cow, collections::VecDeque}; +use std::collections::VecDeque; +use crate::{ + IntoScriptPluginParams, ScriptComponent, ScriptingSystemSet, + commands::{CreateOrUpdateScript, DeleteScript}, + event::ScriptEvent, + script::{ContextKey, DisplayProxy, ScriptAttachment, ScriptContext}, +}; use ::{ bevy_app::{App, Last}, - bevy_asset::{Asset, AssetEvent, AssetLoader, Assets, LoadState}, - bevy_log::{error, trace, warn, warn_once}, - // prelude::{ - // AssetServer, Commands, Entity, EventReader, EventWriter, IntoScheduleConfigs, Local, Query, - // Res, - // }, - bevy_reflect::Reflect, + bevy_asset::{AssetEvent, Assets, LoadState}, + bevy_log::{error, trace}, }; use bevy_asset::{AssetServer, Handle}; use bevy_ecs::{ @@ -20,173 +21,10 @@ use bevy_ecs::{ system::{Commands, Local, Query, Res}, world::WorldId, }; -use serde::{Deserialize, Serialize}; - -use crate::{ - IntoScriptPluginParams, LanguageExtensions, ScriptComponent, ScriptingSystemSet, - commands::{CreateOrUpdateScript, DeleteScript}, - error::ScriptError, - event::ScriptEvent, - script::{ContextKey, DisplayProxy, ScriptAttachment, ScriptContext}, -}; - -/// 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, Default, Serialize, Deserialize)] -pub enum Language { - /// The Rhai scripting language - Rhai, - /// The Lua scripting language - Lua, - /// The Rune scripting language - Rune, - /// An external scripting language - External(Cow<'static, str>), - /// Set if none of the asset path to language mappers match - #[default] - 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, Clone, Reflect)] -#[reflect(opaque)] -pub struct ScriptAsset { - /// The body of the script - pub content: Box<[u8]>, // Any chance a Cow<'static, ?> could work here? - /// The language of the script - pub language: Language, -} - -impl From for ScriptAsset { - fn from(s: String) -> ScriptAsset { - ScriptAsset { - content: s.into_bytes().into_boxed_slice(), - language: Language::default(), - } - } -} - -impl ScriptAsset { - /// Create a new script asset with an unknown language. - pub fn new(s: impl Into) -> Self { - s.into().into() - } -} +use bevy_mod_scripting_asset::ScriptAsset; /// The queue that evaluates scripts. type ScriptQueue = VecDeque; -/// Script settings -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct ScriptSettings { - /// Define the language for a script or use the extension if None. - pub language: Option, -} - -#[derive(Default)] -/// A loader for script assets -pub struct ScriptAssetLoader { - /// The file extensions this loader should handle - language_extensions: LanguageExtensions, - extensions: &'static [&'static str], - /// preprocessor to run on the script before saving the content to an asset - pub preprocessor: Option Result<(), ScriptError> + Send + Sync>>, -} - -impl ScriptAssetLoader { - /// Create a new script asset loader for the given extensions. - pub fn new(language_extensions: LanguageExtensions) -> Self { - let extensions: Vec<&'static str> = language_extensions.keys().copied().collect(); - let new_arr_static = Vec::leak(extensions); - Self { - language_extensions, - extensions: new_arr_static, - preprocessor: None, - } - } - - /// Add a preprocessor - pub fn with_preprocessor( - mut self, - preprocessor: Box Result<(), ScriptError> + Send + Sync>, - ) -> Self { - self.preprocessor = Some(preprocessor); - self - } -} - -#[profiling::all_functions] -impl AssetLoader for ScriptAssetLoader { - type Asset = ScriptAsset; - - type Settings = ScriptSettings; - - type Error = ScriptError; - - async fn load( - &self, - reader: &mut dyn bevy_asset::io::Reader, - settings: &Self::Settings, - load_context: &mut bevy_asset::LoadContext<'_>, - ) -> Result { - let mut content = Vec::new(); - reader - .read_to_end(&mut content) - .await - .map_err(|e| ScriptError::new_external(e).with_context(load_context.asset_path()))?; - if let Some(processor) = &self.preprocessor { - processor(&mut content)?; - } - let language = settings.language.clone().unwrap_or_else(|| { - let ext = load_context - .path() - .extension() - .and_then(|e| e.to_str()) - .unwrap_or_default(); - self.language_extensions - .0 - .get(ext) - .cloned() - .unwrap_or_else(|| { - warn!("Unknown language for {:?}", load_context.path().display()); - Language::Unknown - }) - }); - if language == Language::Lua && cfg!(not(feature = "mlua")) { - warn_once!( - "Script {:?} is a Lua script but the {:?} feature is not enabled; the script will not be evaluated.", - load_context.path().display(), - "mlua" - ); - } - if language == Language::Rhai && cfg!(not(feature = "rhai")) { - warn_once!( - "Script {:?} is a Rhai script but the {:?} feature is not enabled; the script will not be evaluated.", - load_context.path().display(), - "rhai" - ); - } - let asset = ScriptAsset { - content: content.into_boxed_slice(), - language, - }; - Ok(asset) - } - - fn extensions(&self) -> &[&str] { - self.extensions - } -} fn sync_assets( mut events: EventReader>, @@ -362,105 +200,3 @@ pub(crate) fn configure_asset_systems_for_plugin(app: handle_script_events::

.in_set(ScriptingSystemSet::ScriptCommandDispatch), ); } - -// #[cfg(test)] -// mod tests { - -// use ::{ -// bevy_app::App, -// bevy_asset::{AssetServer, Handle, LoadState}, -// }; - -// use super::*; - -// // fn init_loader_test(loader: ScriptAssetLoader) -> App { -// // let mut app = App::new(); -// // app.add_plugins((MinimalPlugins, AssetPlugin::default())); -// // app.init_asset::(); -// // app.register_asset_loader(loader); -// // app -// // } - -// // fn for_extension(extension: &'static str) -> ScriptAssetLoader { -// // let mut language_extensions = LanguageExtensions::default(); -// // language_extensions.insert(extension, Language::Unknown); -// // ScriptAssetLoader::new(language_extensions) -// // } - -// // fn load_asset(app: &mut App, path: &str) -> Handle { -// // let handle = app.world_mut().resource::().load(path); - -// // loop { -// // let state = app -// // .world() -// // .resource::() -// // .get_load_state(&handle) -// // .unwrap(); -// // if !matches!(state, LoadState::Loading) { -// // break; -// // } -// // app.update(); -// // } - -// // match app -// // .world() -// // .resource::() -// // .get_load_state(&handle) -// // .unwrap() -// // { -// // LoadState::NotLoaded => panic!("Asset not loaded"), -// // LoadState::Loaded => {} -// // LoadState::Failed(asset_load_error) => { -// // panic!("Asset load failed: {asset_load_error:?}") -// // } -// // _ => panic!("Unexpected load state"), -// // } - -// // handle -// // } - -// // #[test] -// // fn test_asset_loader_loads() { -// // let loader = for_extension("script"); -// // let mut app = init_loader_test(loader); - -// // let handle = load_asset(&mut app, "test_assets/test_script.script"); -// // let asset = app -// // .world() -// // .get_resource::>() -// // .unwrap() -// // .get(&handle) -// // .unwrap(); - -// // assert_eq!( -// // String::from_utf8(asset.content.clone().to_vec()).unwrap(), -// // "test script".to_string() -// // ); -// // } - -// // #[test] -// // fn test_asset_loader_applies_preprocessor() { -// // let loader = for_extension("script").with_preprocessor(Box::new(|content| { -// // content[0] = b'p'; -// // Ok(()) -// // })); -// // let mut app = init_loader_test(loader); - -// // let handle = load_asset(&mut app, "test_assets/test_script.script"); -// // let asset = app -// // .world() -// // .get_resource::>() -// // .unwrap() -// // .get(&handle) -// // .unwrap(); - -// // assert_eq!( -// // handle.path().unwrap(), -// // &AssetPath::from(PathBuf::from("test_assets/test_script.script")) -// // ); -// // assert_eq!( -// // String::from_utf8(asset.content.clone().to_vec()).unwrap(), -// // "pest script".to_string() -// // ); -// // } -// } diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs index b243294b79..eac4f4606a 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/script_function.rs @@ -2,7 +2,6 @@ use super::MagicFunctions; use super::{from::FromScript, into::IntoScript, namespace::Namespace}; -use crate::asset::Language; use crate::bindings::function::arg_meta::ArgMeta; use crate::docgen::info::{FunctionInfo, GetFunctionInfo}; use crate::{ @@ -11,6 +10,7 @@ use crate::{ error::InteropError, }; use bevy_ecs::prelude::Resource; +use bevy_mod_scripting_asset::Language; use bevy_platform::collections::HashMap; use bevy_reflect::Reflect; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs index 72cd9cdd49..ae542af382 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs @@ -7,12 +7,12 @@ use ::{ bevy_reflect::TypeRegistration, }; use bevy_app::App; +use bevy_mod_scripting_asset::ScriptAsset; use bevy_mod_scripting_derive::script_globals; use bevy_platform::collections::HashMap; use std::{cell::RefCell, sync::Arc}; use crate::{ - asset::ScriptAsset, bindings::{ ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, WorldGuard, diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index 6ab9512bd4..4232e9cfda 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -23,7 +23,6 @@ use super::{ with_global_access, }; use crate::{ - asset::ScriptAsset, bindings::{ function::{from::FromScript, from_ref::FromScriptRef}, with_access_read, with_access_write, @@ -55,6 +54,7 @@ use bevy_ecs::{ system::Command, world::WorldId, }; +use bevy_mod_scripting_asset::ScriptAsset; use bevy_platform::collections::HashMap; use bevy_reflect::{TypeInfo, VariantInfo}; use bevy_system_reflection::ReflectSchedule; diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index 5630322f1e..3a734b2503 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -4,7 +4,6 @@ use std::{marker::PhantomData, sync::Arc}; use crate::{ IntoScriptPluginParams, ScriptContext, - asset::ScriptAsset, bindings::{ScriptValue, WorldGuard}, context::ScriptingLoader, error::{InteropError, ScriptError}, @@ -18,6 +17,7 @@ use crate::{ }; use bevy_ecs::{system::Command, world::World}; use bevy_log::{error, info, trace}; +use bevy_mod_scripting_asset::ScriptAsset; use parking_lot::Mutex; use { bevy_asset::{Assets, Handle}, diff --git a/crates/bevy_mod_scripting_core/src/config.rs b/crates/bevy_mod_scripting_core/src/config.rs index b327edb5af..25eca1d238 100644 --- a/crates/bevy_mod_scripting_core/src/config.rs +++ b/crates/bevy_mod_scripting_core/src/config.rs @@ -24,6 +24,8 @@ pub struct ScriptingPluginConfiguration { pub emit_responses: bool, /// The configured runtime for the plugin pub runtime: &'static P::R, + /// The language extensions this plugin supports + pub language_extensions: &'static crate::LanguageExtensions, } impl Clone for ScriptingPluginConfiguration

{ diff --git a/crates/bevy_mod_scripting_core/src/event.rs b/crates/bevy_mod_scripting_core/src/event.rs index 60da689fa5..954b56438c 100644 --- a/crates/bevy_mod_scripting_core/src/event.rs +++ b/crates/bevy_mod_scripting_core/src/event.rs @@ -4,11 +4,11 @@ use std::sync::Arc; use ::{bevy_asset::Handle, bevy_ecs::entity::Entity, bevy_reflect::Reflect}; use bevy_ecs::event::Event; +use bevy_mod_scripting_asset::Language; use parking_lot::Mutex; use crate::{ IntoScriptPluginParams, - asset::Language, bindings::script_value::ScriptValue, error::ScriptError, script::{ScriptAttachment, ScriptContext, ScriptId}, diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 45303ed9cd..a65a844c73 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -8,21 +8,17 @@ use crate::{ context::{ContextLoadFn, ContextReloadFn}, event::ScriptErrorEvent, }; -use asset::{ - Language, ScriptAsset, ScriptAssetLoader, configure_asset_systems, - configure_asset_systems_for_plugin, -}; +use asset::{configure_asset_systems, configure_asset_systems_for_plugin}; use bevy_app::{App, Plugin, PostUpdate}; use bevy_asset::{AssetApp, Handle}; use bevy_ecs::schedule::IntoScheduleConfigs; use bevy_ecs::{ reflect::{AppTypeRegistry, ReflectComponent}, - resource::Resource, schedule::SystemSet, system::Command, }; use bevy_log::error; -use bevy_platform::collections::HashMap; +use bevy_mod_scripting_asset::{Language, LanguageExtensions, ScriptAsset, ScriptAssetLoader}; use bindings::{ AppReflectAllocator, DynamicScriptComponentPlugin, ReflectAllocator, ReflectReference, ScriptTypeRegistration, function::script_function::AppScriptFunctionRegistry, @@ -34,7 +30,6 @@ use event::{ScriptCallbackEvent, ScriptCallbackResponseEvent, ScriptEvent}; use handler::HandlerFn; use runtime::{Runtime, RuntimeInitializer}; use script::{ContextPolicy, ScriptComponent, ScriptContext}; -use std::ops::{Deref, DerefMut}; pub mod asset; pub mod bindings; @@ -105,6 +100,9 @@ pub struct ScriptingPlugin { /// The language this plugin declares pub language: Language, + /// Declares the file extensions this plugin supports + pub supported_extensions: Vec<&'static str>, + /// initializers for the contexts, run when loading the script pub context_initializers: Vec>, @@ -139,6 +137,7 @@ impl Default for ScriptingPlugin

{ runtime_initializers: Default::default(), context_policy: ContextPolicy::default(), language: Default::default(), + supported_extensions: Default::default(), context_initializers: Default::default(), context_pre_handling_initializers: Default::default(), emit_responses: false, @@ -163,11 +162,17 @@ impl Plugin for ScriptingPlugin

{ context_initialization_callbacks: Vec::leak(self.context_initializers.clone()), emit_responses: self.emit_responses, runtime: Box::leak(Box::new(runtime)), + language_extensions: Box::leak(Box::new(LanguageExtensions::new( + self.supported_extensions + .iter() + .map(|&ext| (ext, P::LANGUAGE.clone())), + ))), }; P::set_world_local_config(app.world().id(), config); app.insert_resource(ScriptContext::

::new(self.context_policy.clone())); + app.register_asset_loader(ScriptAssetLoader::new(config.language_extensions)); register_script_plugin_systems::

(app); @@ -233,6 +238,12 @@ pub trait ConfigureScriptPlugin { /// You won't be able to react to these events until after contexts are fully loaded, /// but they might be useful for other purposes, such as debugging or logging. fn emit_core_callback_responses(self, emit_responses: bool) -> Self; + + /// Adds a supported file extension for the plugin's language. + fn add_supported_extension(self, extension: &'static str) -> Self; + + /// removes a supported file extension for the plugin's language. + fn remove_supported_extension(self, extension: &'static str) -> Self; } impl>> ConfigureScriptPlugin for P { @@ -266,6 +277,18 @@ impl>> ConfigureScriptPlugi self.as_mut().emit_responses = emit_responses; self } + + fn add_supported_extension(mut self, extension: &'static str) -> Self { + self.as_mut().supported_extensions.push(extension); + self + } + + fn remove_supported_extension(mut self, extension: &'static str) -> Self { + self.as_mut() + .supported_extensions + .retain(|&ext| ext != extension); + self + } } /// Ensures all types with `ReflectComponent` type data are pre-registered with component ID's @@ -313,12 +336,6 @@ impl Plugin for BMSScriptingInfrastructurePlugin { } fn finish(&self, app: &mut App) { - // Read extensions. - let language_extensions = app - .world_mut() - .remove_resource::() - .unwrap_or_default(); - app.register_asset_loader(ScriptAssetLoader::new(language_extensions)); // Pre-register component IDs. pre_register_components(app); DynamicScriptComponentPlugin.finish(app); @@ -363,104 +380,14 @@ impl ManageStaticScripts for App { } } -/// Trait for adding a supported extension to the script asset settings. -/// -/// This is only valid in the plugin building phase, as the asset loader will be created in the `finalize` phase. -/// Any changes to the asset settings after that will not be reflected in the asset loader. -pub trait ConfigureScriptAssetSettings { - /// Adds a supported extension to the asset settings - /// - /// This is only valid to call in the plugin building phase, as the asset loader will be created in the `finalize` phase. - fn add_supported_script_extensions( - &mut self, - extensions: &[&'static str], - language: Language, - ) -> &mut Self; -} - -/// Collect the language extensions supported during initialization. -/// -/// NOTE: This resource is removed after plugin setup. -#[derive(Debug, Resource)] -pub struct LanguageExtensions(HashMap<&'static str, Language>); - -impl Deref for LanguageExtensions { - type Target = HashMap<&'static str, Language>; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for LanguageExtensions { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Default for LanguageExtensions { - fn default() -> Self { - LanguageExtensions( - [ - ("lua", Language::Lua), - ("luau", Language::Lua), - ("rhai", Language::Rhai), - ("rn", Language::Rune), - ] - .into_iter() - .collect(), - ) - } -} - -impl ConfigureScriptAssetSettings for App { - fn add_supported_script_extensions( - &mut self, - extensions: &[&'static str], - language: Language, - ) -> &mut Self { - let mut language_extensions = self - .world_mut() - .get_resource_or_init::(); - - for extension in extensions { - language_extensions.insert(extension, language.clone()); - } - self - } -} - #[cfg(test)] mod test { - use bevy_asset::{AssetPlugin, AssetServer}; + use bevy_asset::AssetPlugin; use bevy_ecs::prelude::*; use bevy_reflect::Reflect; use super::*; - #[tokio::test] - async fn test_asset_extensions_correctly_accumulate() { - let mut app = App::new(); - app.add_plugins(AssetPlugin::default()); - - BMSScriptingInfrastructurePlugin.finish(&mut app); - - let asset_loader = app - .world() - .get_resource::() - .expect("Asset loader not found"); - - asset_loader - .get_asset_loader_with_extension("lua") - .await - .expect("Lua loader not found"); - - asset_loader - .get_asset_loader_with_extension("rhai") - .await - .expect("Rhai loader not found"); - } - #[test] fn test_reflect_component_is_preregistered_in_app_finalize() { let mut app = App::new(); diff --git a/crates/bevy_mod_scripting_core/src/script/mod.rs b/crates/bevy_mod_scripting_core/src/script/mod.rs index 30a56dc404..247ab49898 100644 --- a/crates/bevy_mod_scripting_core/src/script/mod.rs +++ b/crates/bevy_mod_scripting_core/src/script/mod.rs @@ -15,11 +15,12 @@ use ::{ bevy_reflect::Reflect, }; -use crate::{asset::ScriptAsset, event::ScriptEvent}; +use crate::event::ScriptEvent; mod context_key; mod script_context; use bevy_ecs::component::Component; +use bevy_mod_scripting_asset::ScriptAsset; pub use context_key::*; pub use script_context::*; diff --git a/crates/bevy_mod_scripting_functions/Cargo.toml b/crates/bevy_mod_scripting_functions/Cargo.toml index 1f9265755a..47b3d81b8c 100644 --- a/crates/bevy_mod_scripting_functions/Cargo.toml +++ b/crates/bevy_mod_scripting_functions/Cargo.toml @@ -43,6 +43,7 @@ rhai_bindings = ["bevy_mod_scripting_rhai"] [dependencies] profiling = { workspace = true } bevy_mod_scripting_core = { workspace = true } +bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_derive = { workspace = true } bevy_mod_scripting_lua = { path = "../languages/bevy_mod_scripting_lua", optional = true, version = "0.15.1" } bevy_mod_scripting_rhai = { path = "../languages/bevy_mod_scripting_rhai", optional = true, version = "0.15.1" } diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index ef8867db49..daf16b806c 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -1,5 +1,6 @@ //! Contains functions defined by the [`bevy_mod_scripting_core`] crate +use bevy_mod_scripting_asset::ScriptAsset; use bevy_platform::collections::HashMap; use std::ops::Deref; @@ -7,7 +8,6 @@ use bevy_app::App; use bevy_asset::{AssetServer, Handle}; use bevy_ecs::{entity::Entity, prelude::AppTypeRegistry, schedule::Schedules, world::World}; use bevy_mod_scripting_core::{ - asset::ScriptAsset, bindings::{ function::{ from::Union, namespace::GlobalNamespace, script_function::DynamicScriptFunctionMut, @@ -458,17 +458,18 @@ impl World { let _world = ctxt.world()?; let _system = match ctxt.language() { #[cfg(feature = "lua_bindings")] - asset::Language::Lua => _world + bevy_mod_scripting_asset::Language::Lua => _world .add_system::( - &schedule, - builder.into_inner(), - )?, + &schedule, + builder.into_inner(), + )?, #[cfg(feature = "rhai_bindings")] - asset::Language::Rhai => _world - .add_system::( + bevy_mod_scripting_asset::Language::Rhai => { + _world.add_system::( &schedule, builder.into_inner(), - )?, + )? + } _ => { return Err(InteropError::unsupported_operation( None, diff --git a/crates/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index 53b5e69f0a..e544426bca 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -43,6 +43,7 @@ bevy_asset = { workspace = true, default-features = false, features = [] } bevy_log = { workspace = true, default-features = false, features = [] } bevy_platform = { workspace = true, default-features = false, features = [] } bevy_mod_scripting_core = { workspace = true, features = ["mlua_impls"] } +bevy_mod_scripting_asset = { workspace = true } mlua = { workspace = true, features = ["vendored", "send", "macros"] } profiling = { workspace = true } diff --git a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs index 11a326054b..abdb02ba22 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/bindings/script_value.rs @@ -3,8 +3,8 @@ use std::{ ops::{Deref, DerefMut}, }; +use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_core::{ - asset::Language, bindings::{function::script_function::FunctionCallContext, script_value::ScriptValue}, error::InteropError, }; diff --git a/crates/languages/bevy_mod_scripting_lua/src/lib.rs b/crates/languages/bevy_mod_scripting_lua/src/lib.rs index 3ea7b51af4..1c35c417b7 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/lib.rs @@ -9,9 +9,9 @@ use ::{ use bevy_app::App; use bevy_ecs::world::WorldId; use bevy_log::trace; +use bevy_mod_scripting_asset::{Language, ScriptAsset}; use bevy_mod_scripting_core::{ IntoScriptPluginParams, ScriptingPlugin, - asset::{Language, ScriptAsset}, bindings::{ ThreadWorldContainer, WorldContainer, function::namespace::Namespace, globals::AppScriptGlobalsRegistry, script_value::ScriptValue, @@ -96,6 +96,7 @@ impl Default for LuaScriptingPlugin { LuaScriptingPlugin { scripting_plugin: ScriptingPlugin { runtime_initializers: Vec::default(), + supported_extensions: vec!["lua", "luau"], context_initializers: vec![ |_script_id, context| { // set the world global @@ -294,6 +295,7 @@ pub fn lua_handler( #[cfg(test)] mod test { use ::bevy_asset::{AssetId, AssetIndex, Handle}; + use bevy_mod_scripting_asset::LanguageExtensions; use mlua::Value; use super::*; @@ -312,6 +314,7 @@ mod test { context_initialization_callbacks: &[], emit_responses: false, runtime: &(), + language_extensions: Box::leak(Box::new(LanguageExtensions::default())), }, ); lua_context_load( diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index ceb249a506..0977e2a0df 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -23,6 +23,7 @@ bevy_log = { workspace = true, default-features = false, features = [] } bevy_platform = { workspace = true, default-features = false, features = [] } rhai = { workspace = true, features = ["std"] } bevy_mod_scripting_core = { workspace = true, features = ["rhai_impls"] } +bevy_mod_scripting_asset = { workspace = true } strum = { workspace = true, features = ["derive"] } parking_lot = { workspace = true } diff --git a/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs b/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs index 2a79bab06e..6ce94244d7 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/bindings/script_value.rs @@ -1,7 +1,7 @@ use std::str::FromStr; +use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_core::{ - asset::Language, bindings::{ function::script_function::{DynamicScriptFunction, FunctionCallContext}, script_value::ScriptValue, diff --git a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs index 1ff56cab44..612e4e216f 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs @@ -10,9 +10,9 @@ use ::{ use bevy_app::App; use bevy_ecs::world::WorldId; use bevy_log::trace; +use bevy_mod_scripting_asset::{Language, ScriptAsset}; use bevy_mod_scripting_core::{ IntoScriptPluginParams, ScriptingPlugin, - asset::{Language, ScriptAsset}, bindings::{ ThreadWorldContainer, WorldContainer, function::namespace::Namespace, globals::AppScriptGlobalsRegistry, script_value::ScriptValue, @@ -92,6 +92,7 @@ impl Default for RhaiScriptingPlugin { fn default() -> Self { RhaiScriptingPlugin { scripting_plugin: ScriptingPlugin { + supported_extensions: vec!["rhai"], runtime_initializers: vec![|runtime| { let mut engine = runtime.write(); engine.set_max_expr_depths(999, 999); diff --git a/crates/testing_crates/script_integration_test_harness/Cargo.toml b/crates/testing_crates/script_integration_test_harness/Cargo.toml index ad959930d0..cb9469e159 100644 --- a/crates/testing_crates/script_integration_test_harness/Cargo.toml +++ b/crates/testing_crates/script_integration_test_harness/Cargo.toml @@ -21,6 +21,7 @@ test_utils = { workspace = true } bevy_reflect = { workspace = true } bevy_log = { workspace = true } bevy_mod_scripting_core = { workspace = true } +bevy_mod_scripting_asset = { workspace = true } bevy_mod_scripting_functions = { workspace = true, features = [ "core_functions", ] } diff --git a/crates/testing_crates/script_integration_test_harness/src/parse.rs b/crates/testing_crates/script_integration_test_harness/src/parse.rs index d68946ec09..f7ce011202 100644 --- a/crates/testing_crates/script_integration_test_harness/src/parse.rs +++ b/crates/testing_crates/script_integration_test_harness/src/parse.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; use anyhow::Error; +use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_core::{ - asset::Language, bindings::ScriptValue, callback_labels, event::{ diff --git a/crates/testing_crates/script_integration_test_harness/src/scenario.rs b/crates/testing_crates/script_integration_test_harness/src/scenario.rs index 3c49ce1a93..9ffd26b932 100644 --- a/crates/testing_crates/script_integration_test_harness/src/scenario.rs +++ b/crates/testing_crates/script_integration_test_harness/src/scenario.rs @@ -21,9 +21,9 @@ use anyhow::{Context, Error, anyhow}; use bevy_app::{DynEq, FixedUpdate, Last, PostUpdate, Startup, Update}; use bevy_asset::{AssetServer, Assets}; use bevy_log::info; +use bevy_mod_scripting_asset::{Language, LanguageExtensions, ScriptAsset}; use bevy_mod_scripting_core::{ - ConfigureScriptPlugin, LanguageExtensions, - asset::{Language, ScriptAsset}, + ConfigureScriptPlugin, bindings::{DisplayWithWorld, ScriptValue, WorldGuard}, commands::{AddStaticScript, RemoveStaticScript}, event::{ diff --git a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs index d7204671d3..0a3f8fc7a1 100644 --- a/crates/testing_crates/script_integration_test_harness/src/test_functions.rs +++ b/crates/testing_crates/script_integration_test_harness/src/test_functions.rs @@ -8,8 +8,8 @@ use ::{ bevy_ecs::{component::ComponentId, entity::Entity, world::World}, bevy_reflect::{Reflect, TypeRegistration}, }; +use bevy_mod_scripting_asset::Language; use bevy_mod_scripting_core::{ - asset::Language, bindings::{ DynamicScriptFunction, ReflectReference, ScriptComponentRegistration, ScriptResourceRegistration, ScriptTypeRegistration, ScriptValue, diff --git a/src/lib.rs b/src/lib.rs index 6ea28406fe..111e42c911 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,10 @@ pub mod core { pub use bevy_mod_scripting_core::*; } +pub mod asset { + pub use bevy_mod_scripting_asset::*; +} + pub mod prelude; #[cfg(feature = "lua")] diff --git a/src/prelude.rs b/src/prelude.rs index 775d7d6580..e00adcc0be 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,5 @@ pub use bevy_mod_scripting_core::{ - ConfigureScriptAssetSettings, ConfigureScriptPlugin, IntoScriptPluginParams, - asset::{Language, ScriptAsset}, + ConfigureScriptPlugin, IntoScriptPluginParams, bindings::{ CoreScriptGlobalsPlugin, function::namespace::{GlobalNamespace, NamespaceBuilder}, @@ -12,6 +11,9 @@ pub use bevy_mod_scripting_core::{ handler::event_handler, script::{ScriptComponent, ScriptId}, }; + +pub use bevy_mod_scripting_asset::*; + #[cfg(feature = "lua")] pub use bevy_mod_scripting_lua::LuaScriptingPlugin; #[cfg(feature = "rhai")]