diff --git a/.gitignore b/.gitignore index f8a150654c..b8efa21749 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ assets/scripts/tlconfig.lua **.log **build/ .html +.idea/ /assets/**/*.lad.json -/docs/src/ladfiles/*.lad.json \ No newline at end of file +/docs/src/ladfiles/*.lad.json diff --git a/Cargo.toml b/Cargo.toml index 61bf0bfe3f..73fd20824b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting" -version = "0.12.0" +version = "0.13.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" @@ -76,37 +76,38 @@ profile_with_tracy = ["bevy/trace_tracy"] [dependencies] bevy = { workspace = true } bevy_mod_scripting_core = { workspace = true } -bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.12.0", optional = true } -bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.12.0", optional = true } +bevy_mod_scripting_lua = { path = "crates/languages/bevy_mod_scripting_lua", version = "0.13.0", optional = true } +bevy_mod_scripting_rhai = { path = "crates/languages/bevy_mod_scripting_rhai", version = "0.13.0", 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 } bevy_mod_scripting_derive = { workspace = true } [workspace.dependencies] profiling = { version = "1.0" } -bevy = { version = "0.15.3", default-features = false } -bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.12.0" } -bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.12.0", default-features = false } -bevy_mod_scripting_derive = { path = "crates/bevy_mod_scripting_derive", version = "0.12.0" } +bevy_math = { version = "0.16.0", default-features = false } +bevy_reflect = { version = "0.16.0", default-features = false } +bevy_input = { version = "0.16.0", default-features = false } +bevy = { version = "0.16.0", default-features = false } +bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.13.0" } +bevy_mod_scripting_functions = { path = "crates/bevy_mod_scripting_functions", version = "0.13.0", default-features = false } +bevy_mod_scripting_derive = { path = "crates/bevy_mod_scripting_derive", version = "0.13.0" } # test utilities script_integration_test_harness = { path = "crates/testing_crates/script_integration_test_harness" } test_utils = { path = "crates/testing_crates/test_utils" } [dev-dependencies] -bevy = { workspace = true, default-features = true } +bevy = { workspace = true, default-features = true, features = ["std"] } clap = { version = "4.1", features = ["derive"] } -rand = "0.8.5" -bevy_console = "0.13" -# rhai-rand = "0.1" +rand = "0.9.1" criterion = { version = "0.5" } -ansi-parser = "0.9" ladfile_builder = { path = "crates/ladfile_builder", version = "0.3.2" } script_integration_test_harness = { workspace = true } test_utils = { workspace = true } libtest-mimic = "0.8" tracing-tracy = "0.11" regex = "1.11" +bevy_console = "0.14" [workspace] members = [ @@ -160,7 +161,7 @@ name = "game_of_life" path = "examples/game_of_life.rs" required-features = [ "lua54", - "rhai", + # "rhai", "bevy/file_watcher", "bevy/multi_threaded", ] 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 2adf8104d0..bd7951c729 100644 --- a/assets/tests/add_system/added_systems_run_in_parallel.lua +++ b/assets/tests/add_system/added_systems_run_in_parallel.lua @@ -21,21 +21,30 @@ function on_test() local expected_dot_graph = [[ digraph { - node_0 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"]; - node_1 [label="on_test_post_update"]; - node_2 [label="script_integration_test_harness::dummy_before_post_update_system"]; - node_3 [label="script_integration_test_harness::dummy_post_update_system"]; - node_4 [label="custom_system_a"]; - node_5 [label="custom_system_b"]; - node_6 [label="SystemSet GarbageCollection"]; - node_7 [label="SystemSet ScriptSystem(custom_system_a)"]; - node_8 [label="SystemSet ScriptSystem(custom_system_b)"]; - node_0 -> node_6 [color=red, label="child of", arrowhead=diamond]; - node_4 -> node_7 [color=red, label="child of", arrowhead=diamond]; - node_5 -> node_8 [color=red, label="child of", arrowhead=diamond]; - node_1 -> node_4 [color=blue, label="runs before", arrowhead=normal]; - node_1 -> node_5 [color=blue, label="runs before", arrowhead=normal]; - node_2 -> node_3 [color=blue, label="runs before", arrowhead=normal]; + 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_4 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"]; + node_5 [label="on_test_post_update"]; + node_6 [label="script_integration_test_harness::dummy_before_post_update_system"]; + node_7 [label="script_integration_test_harness::dummy_post_update_system"]; + node_8 [label="custom_system_a"]; + node_9 [label="custom_system_b"]; + node_10 [label="SystemSet AssetEvents"]; + node_11 [label="SystemSet GarbageCollection"]; + node_12 [label="SystemSet ScriptSystem(custom_system_a)"]; + node_13 [label="SystemSet ScriptSystem(custom_system_b)"]; + node_0 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_1 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_2 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_3 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_4 -> node_11 [color=red, label="child of", arrowhead=diamond]; + node_8 -> node_12 [color=red, label="child of", arrowhead=diamond]; + node_9 -> node_13 [color=red, label="child of", arrowhead=diamond]; + node_5 -> node_8 [color=blue, label="runs before", arrowhead=normal]; + node_5 -> node_9 [color=blue, label="runs before", arrowhead=normal]; + node_6 -> node_7 [color=blue, label="runs before", arrowhead=normal]; } ]] 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 4ab2ddc17a..b49d41b9b3 100644 --- a/assets/tests/add_system/added_systems_run_in_parallel.rhai +++ b/assets/tests/add_system/added_systems_run_in_parallel.rhai @@ -20,21 +20,30 @@ fn on_test() { let expected_dot_graph = ` digraph { - node_0 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"]; - node_1 [label="on_test_post_update"]; - node_2 [label="script_integration_test_harness::dummy_before_post_update_system"]; - node_3 [label="script_integration_test_harness::dummy_post_update_system"]; - node_4 [label="custom_system_a"]; - node_5 [label="custom_system_b"]; - node_6 [label="SystemSet GarbageCollection"]; - node_7 [label="SystemSet ScriptSystem(custom_system_a)"]; - node_8 [label="SystemSet ScriptSystem(custom_system_b)"]; - node_0 -> node_6 [color=red, label="child of", arrowhead=diamond]; - node_4 -> node_7 [color=red, label="child of", arrowhead=diamond]; - node_5 -> node_8 [color=red, label="child of", arrowhead=diamond]; - node_1 -> node_4 [color=blue, label="runs before", arrowhead=normal]; - node_1 -> node_5 [color=blue, label="runs before", arrowhead=normal]; - node_2 -> node_3 [color=blue, label="runs before", arrowhead=normal]; + 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_4 [label="bevy_mod_scripting_core::bindings::allocator::garbage_collector"]; + node_5 [label="on_test_post_update"]; + node_6 [label="script_integration_test_harness::dummy_before_post_update_system"]; + node_7 [label="script_integration_test_harness::dummy_post_update_system"]; + node_8 [label="custom_system_a"]; + node_9 [label="custom_system_b"]; + node_10 [label="SystemSet AssetEvents"]; + node_11 [label="SystemSet GarbageCollection"]; + node_12 [label="SystemSet ScriptSystem(custom_system_a)"]; + node_13 [label="SystemSet ScriptSystem(custom_system_b)"]; + node_0 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_1 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_2 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_3 -> node_10 [color=red, label="child of", arrowhead=diamond]; + node_4 -> node_11 [color=red, label="child of", arrowhead=diamond]; + node_8 -> node_12 [color=red, label="child of", arrowhead=diamond]; + node_9 -> node_13 [color=red, label="child of", arrowhead=diamond]; + node_5 -> node_8 [color=blue, label="runs before", arrowhead=normal]; + node_5 -> node_9 [color=blue, label="runs before", arrowhead=normal]; + node_6 -> node_7 [color=blue, label="runs before", arrowhead=normal]; }`; assert_str_eq.call(dot_graph, expected_dot_graph, "Expected the schedule graph to match the expected graph"); diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 82b3745d46..087538ae90 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -1,27 +1,28 @@ -use bevy::log::tracing_subscriber::layer::SubscriberExt; -use bevy::log::{tracing_subscriber, Level}; -use bevy::reflect::Reflect; -use bevy::utils::tracing; -use bevy::utils::tracing::span; +extern crate bevy_mod_scripting; +extern crate script_integration_test_harness; +extern crate test_utils; +use std::{collections::HashMap, path::PathBuf, sync::LazyLock, time::Duration}; + +use bevy::{ + log::{ + tracing, tracing::span, tracing_subscriber, tracing_subscriber::layer::SubscriberExt, Level, + }, + reflect::Reflect, +}; use bevy_mod_scripting_core::bindings::{ FromScript, IntoScript, Mut, Ref, ReflectReference, ScriptValue, Val, }; -use criterion::{criterion_main, measurement::Measurement, BenchmarkGroup, Criterion}; -use criterion::{BatchSize, BenchmarkFilter}; +use criterion::{ + criterion_main, measurement::Measurement, BatchSize, BenchmarkFilter, BenchmarkGroup, Criterion, +}; use regex::Regex; -use script_integration_test_harness::test_functions::rand::Rng; use script_integration_test_harness::{ make_test_lua_plugin, make_test_rhai_plugin, perform_benchmark_with_generator, run_lua_benchmark, run_plugin_script_load_benchmark, run_rhai_benchmark, + test_functions::rand::Rng, }; -use std::collections::HashMap; -use std::{path::PathBuf, sync::LazyLock, time::Duration}; use test_utils::{discover_all_tests, Test}; -extern crate bevy_mod_scripting; -extern crate script_integration_test_harness; -extern crate test_utils; - static ENABLE_PROFILING: LazyLock = LazyLock::new(|| std::env::var("ENABLE_PROFILING").is_ok()); diff --git a/crates/bevy_api_gen/Cargo.toml b/crates/bevy_api_gen/Cargo.toml index 5773d66271..e84c96e17d 100644 --- a/crates/bevy_api_gen/Cargo.toml +++ b/crates/bevy_api_gen/Cargo.toml @@ -41,7 +41,6 @@ log = "0.4" env_logger = "0.11" rustc_plugin = "0.12.0-nightly-2024-12-15" indexmap = "2" -tempdir = "0.3" cargo_metadata = "0.18" serde_json = "1" serde = "1" @@ -52,7 +51,6 @@ include_dir = "0.7" prettyplease = "0.2" convert_case = "0.6" syn = { version = "2", features = ["parsing"], no-default-features = true } -clap-verbosity-flag = "2.2" itertools = "0.12" chrono = "0.4" diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index b94c1a88a3..b53a4b2a4f 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_core" -version = "0.12.0" +version = "0.13.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" @@ -29,19 +29,16 @@ mlua = { version = "0.10", default-features = false, optional = true } rhai = { version = "1.21", default-features = false, features = [ "sync", ], optional = true } -bevy = { workspace = true, default-features = false, features = ["bevy_asset"] } -thiserror = "1.0.31" +bevy = { workspace = true, default-features = false, features = ["bevy_asset", "std"] } parking_lot = "0.12.1" -dashmap = "6" smallvec = "1.11" -itertools = "0.13" -derivative = "2.2" +itertools = "0.14" profiling = { workspace = true } bevy_mod_scripting_derive = { workspace = true } fixedbitset = "0.5" -petgraph = "0.6" -bevy_mod_debugdump = "0.12" bevy_system_reflection = { path = "../bevy_system_reflection", version = "0.1.1" } +serde = { version = "1.0", features = ["derive"] } +variadics_please = "1.1.0" [dev-dependencies] test_utils = { workspace = true } diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index f3135f55b4..df5e678765 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -1,27 +1,29 @@ //! Systems and resources for handling script assets and events use crate::{ + StaticScripts, + ScriptComponent, commands::{CreateOrUpdateScript, DeleteScript}, error::ScriptError, - script::ScriptId, + script::{DisplayProxy, ScriptId, Domain, ScriptDomain}, IntoScriptPluginParams, ScriptingSystemSet, }; use bevy::{ app::{App, PreUpdate}, - asset::{Asset, AssetEvent, AssetId, AssetLoader, AssetPath, Assets}, - ecs::system::Resource, + asset::{Asset, AssetEvent, AssetId, AssetLoader, AssetPath, Assets, LoadState}, log::{debug, info, trace, warn}, prelude::{ - Commands, Event, EventReader, EventWriter, IntoSystemConfigs, IntoSystemSetConfigs, Res, - ResMut, + Commands, Event, EventReader, EventWriter, IntoScheduleConfigs, Res, + ResMut, Added, Query, Local, Handle, AssetServer, Entity, Resource }, reflect::TypePath, - utils::hashbrown::HashMap, + platform::collections::HashMap, }; -use std::borrow::Cow; +use std::{borrow::Cow, collections::VecDeque}; +use serde::{Deserialize, Serialize}; /// 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)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)] pub enum Language { /// The Rhai scripting language Rhai, @@ -52,16 +54,25 @@ impl std::fmt::Display for Language { #[derive(Asset, TypePath, Clone)] pub struct ScriptAsset { /// The body of the script - 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: AssetPath<'static>, + 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(), + } + } } -#[derive(Event, Debug, Clone)] -pub(crate) enum ScriptAssetEvent { - Added(ScriptMetadata), - Removed(ScriptMetadata), - Modified(ScriptMetadata), +/// 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)] @@ -77,14 +88,14 @@ pub struct ScriptAssetLoader { impl AssetLoader for ScriptAssetLoader { type Asset = ScriptAsset; - type Settings = (); + type Settings = ScriptSettings; type Error = ScriptError; async fn load( &self, reader: &mut dyn bevy::asset::io::Reader, - _settings: &Self::Settings, + settings: &Self::Settings, load_context: &mut bevy::asset::LoadContext<'_>, ) -> Result { let mut content = Vec::new(); @@ -95,9 +106,22 @@ impl AssetLoader for ScriptAssetLoader { if let Some(processor) = &self.preprocessor { processor(&mut content)?; } + let language = settings + .language + .clone() + .unwrap_or_else(|| + match load_context.path().extension().and_then(|e| e.to_str()).unwrap_or_default() { + "lua" => Language::Lua, + "rhai" => Language::Rhai, + "rn" => Language::Rune, + x => { + warn!("Unknown language for {:?}", load_context.path().display()); + Language::Unknown + } + }); let asset = ScriptAsset { content: content.into_boxed_slice(), - asset_path: load_context.asset_path().to_owned(), + language, }; Ok(asset) } @@ -107,230 +131,112 @@ impl AssetLoader for ScriptAssetLoader { } } -#[derive(Clone, Resource)] -/// Settings to do with script assets and how they are handled -pub struct ScriptAssetSettings { - /// Strategy for mapping asset paths to script ids, by default this is the identity function - pub script_id_mapper: AssetPathToScriptIdMapper, - /// Mapping from extension to script language - pub extension_to_language_map: HashMap<&'static str, Language>, - - /// The currently supported asset extensions - /// Should be updated by each scripting plugin to include the extensions it supports. - /// - /// Will be used to populate the script asset loader with the supported extensions - pub supported_extensions: &'static [&'static str], -} - -#[profiling::all_functions] -impl ScriptAssetSettings { - /// Selects the language for a given asset path - pub fn select_script_language(&self, path: &AssetPath) -> Language { - let extension = path - .path() - .extension() - .and_then(|ext| ext.to_str()) - .unwrap_or_default(); - self.extension_to_language_map - .get(extension) - .cloned() - .unwrap_or_default() - } -} - -impl Default for ScriptAssetSettings { - fn default() -> Self { - Self { - script_id_mapper: AssetPathToScriptIdMapper { - map: (|path: &AssetPath| path.path().to_string_lossy().into_owned().into()), - }, - extension_to_language_map: HashMap::from_iter(vec![ - ("lua", Language::Lua), - ("luau", Language::Lua), - ("rhai", Language::Rhai), - ("rn", Language::Rune), - ]), - supported_extensions: &["lua", "luau", "rhai", "rn"], - } - } -} - -/// Strategy for mapping asset paths to script ids, by default this is the identity function -#[derive(Clone, Copy)] -pub struct AssetPathToScriptIdMapper { - /// The mapping function - pub map: fn(&AssetPath) -> ScriptId, -} - -/// 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 ScriptMetadataStore { - /// The map of asset id's to their metadata - pub map: HashMap, ScriptMetadata>, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -/// Metadata for a script asset -pub struct ScriptMetadata { - /// The asset id of the script - pub asset_id: AssetId, - /// The script id of the script - pub script_id: ScriptId, - /// The language of the script - pub language: Language, -} - -#[profiling::all_functions] -impl ScriptMetadataStore { - /// Inserts a new metadata entry - pub fn insert(&mut self, id: AssetId, meta: ScriptMetadata) { - // TODO: new generations of assets are not going to have the same ID as the old one - self.map.insert(id, meta); - } - - /// Gets a metadata entry - pub fn get(&self, id: AssetId) -> Option<&ScriptMetadata> { - self.map.get(&id) - } - - /// Removes a metadata entry - pub fn remove(&mut self, id: AssetId) -> Option { - self.map.remove(&id) - } - - /// Checks if the store contains a metadata entry - pub fn contains(&self, id: AssetId) -> bool { - self.map.contains_key(&id) - } -} - -/// Converts incoming asset events, into internal script asset events, also loads and inserts metadata for newly added scripts +/// +/// Allows for hot-reloading of scripts. #[profiling::function] -pub(crate) fn dispatch_script_asset_events( +pub(crate) fn sync_script_data( mut events: EventReader>, - mut script_asset_events: EventWriter, - assets: Res>, - mut metadata_store: ResMut, - settings: Res, + script_assets: Res>, + mut static_scripts: ResMut, + asset_server: Res, + mut commands: Commands, ) { for event in events.read() { + + trace!("{}: Received script asset event: {:?}", P::LANGUAGE, event); match event { - AssetEvent::LoadedWithDependencies { id } | AssetEvent::Added { id } => { - // these can occur multiple times, we only send one added event though - if !metadata_store.contains(*id) { - let asset = assets.get(*id); - if let Some(asset) = asset { - let path = &asset.asset_path; - let converter = settings.script_id_mapper.map; - let script_id = converter(path); - - let language = settings.select_script_language(path); - if language == Language::Unknown { - let extension = path - .path() - .extension() - .and_then(|ext| ext.to_str()) - .unwrap_or_default(); - warn!("A script {:?} was added but its language is unknown. Consider adding the {:?} extension to the `ScriptAssetSettings`.", &script_id, extension); + // emitted when a new script asset is loaded for the first time + AssetEvent::LoadedWithDependencies { id } | AssetEvent::Added { id } | AssetEvent::Modified{ id } => { + if let Some(asset) = script_assets.get(*id) { + if asset.language != P::LANGUAGE { + match asset_server.get_path(*id) { + Some(path) => { + trace!( + "{}: Script path {} is for a different language than this sync system. Skipping.", + P::LANGUAGE, + path); + } + None => { + trace!( + "{}: Script id {} is for a different language than this sync system. Skipping.", + P::LANGUAGE, + id); + } } - let metadata = ScriptMetadata { - asset_id: *id, - script_id, - language, - }; - debug!("Script loaded, populating metadata: {:?}:", metadata); - script_asset_events.send(ScriptAssetEvent::Added(metadata.clone())); - metadata_store.insert(*id, metadata); - } else { - warn!("A script was added but it's asset was not found, failed to compute metadata. This script will not be loaded. Did you forget to store `Handle` somewhere?. {}", id); + continue; + } + + if static_scripts.iter().any(|handle| handle.id() == *id) { + info!("{}: Loading static script: {:?}", P::LANGUAGE, id); + commands.queue(CreateOrUpdateScript::

::new( + Handle::Weak(*id), + asset.content.clone(), // Cloning seems bad! + None, // No domain for static scripts. + )); } } } - AssetEvent::Removed { id } => { - if let Some(metadata) = metadata_store.get(*id) { - debug!("Script removed: {:?}", metadata); - script_asset_events.send(ScriptAssetEvent::Removed(metadata.clone())); - } else { - warn!("Script metadata not found for removed script asset: {}. Cannot properly clean up script", id); + AssetEvent::Removed{ id } => { + if static_scripts.remove(id) { + info!("{}: Removing static script {:?}", P::LANGUAGE, id); } } - AssetEvent::Modified { id } => { - if let Some(metadata) = metadata_store.get(*id) { - debug!("Script modified: {:?}", metadata); - script_asset_events.send(ScriptAssetEvent::Modified(metadata.clone())); - } else { - warn!("Script metadata not found for modified script asset: {}. Cannot properly update script", id); - } + AssetEvent::Unused { id } => { } - _ => {} - } - } -} - -/// Listens to [`ScriptAssetEvent::Removed`] events and removes the corresponding script metadata. -#[profiling::function] -pub(crate) fn remove_script_metadata( - mut events: EventReader, - mut asset_path_map: ResMut, -) { - for event in events.read() { - if let ScriptAssetEvent::Removed(metadata) = event { - let previous = asset_path_map.remove(metadata.asset_id); - if let Some(previous) = previous { - debug!("Removed script metadata: {:?}", previous); - } - } + }; } } -/// Listens to [`ScriptAssetEvent`] events and dispatches [`CreateOrUpdateScript`] and [`DeleteScript`] commands accordingly. -/// -/// Allows for hot-reloading of scripts. -#[profiling::function] -pub(crate) fn sync_script_data( - mut events: EventReader, +pub(crate) fn eval_script( + script_comps: Query<(Entity, &ScriptComponent, Option<&ScriptDomain>), Added>, + mut script_queue: Local, Option)>>, script_assets: Res>, + asset_server: Res, mut commands: Commands, -) { - for event in events.read() { - let metadata = match event { - ScriptAssetEvent::Added(script_metadata) - | ScriptAssetEvent::Removed(script_metadata) - | ScriptAssetEvent::Modified(script_metadata) => script_metadata, - }; - - if metadata.language != P::LANGUAGE { - continue; + ) { + for (id, script_comp, domain_maybe) in &script_comps { + for handle in &script_comp.0 { + script_queue.push_back((id, handle.clone_weak(), domain_maybe.map(|x| x.0.clone()))); } - - trace!("{}: Received script asset event: {:?}", P::LANGUAGE, event); - match event { - // emitted when a new script asset is loaded for the first time - ScriptAssetEvent::Added(_) | ScriptAssetEvent::Modified(_) => { - if metadata.language != P::LANGUAGE { - trace!( - "{}: Script asset with id: {} is for a different langauge than this sync system. Skipping.", - P::LANGUAGE, - metadata.script_id - ); - continue; - } - - info!("{}: Loading Script: {:?}", P::LANGUAGE, metadata.script_id,); - - if let Some(asset) = script_assets.get(metadata.asset_id) { - commands.queue(CreateOrUpdateScript::

::new( - metadata.script_id.clone(), + } + while ! script_queue.is_empty() { + let script_ready = script_queue.front().map(|(_, script_id, _)| match asset_server.load_state(&*script_id) { + LoadState::Failed(e) => { + warn!("Failed to load script {}", script_id.display()); + true + } + LoadState::Loaded => true, + _ => false + }).unwrap_or(false); + if ! script_ready { + break; + } + // NOTE: Maybe once pop_front_if is stabalized. + // if let Some(script_id) = script_queue.pop_front_if(|script_id| match asset_server.load_state(script_id) { + // LoadState::Failed(e) => { + // warn!("Failed to load script {}", &script_id); + // true + // } + // LoadState::Loaded => true, + // _ => false + // }) { + if let Some((id, script_id, domain_maybe)) = script_queue.pop_front() { + if let Some(asset) = script_assets.get(&script_id) { + if asset.language == P::LANGUAGE { + commands.entity(id).queue(CreateOrUpdateScript::

::new( + script_id, asset.content.clone(), - Some(script_assets.reserve_handle().clone_weak()), + domain_maybe )); } + } else { + // This is probably a load failure. What to do? We've already + // provided a warning on failure. Doing nothing is fine then we + // process the next one. } - ScriptAssetEvent::Removed(_) => { - info!("{}: Deleting Script: {:?}", P::LANGUAGE, metadata.script_id,); - commands.queue(DeleteScript::

::new(metadata.script_id.clone())); - } - }; + } else { + break; + } } } @@ -339,13 +245,14 @@ pub(crate) fn sync_script_data( pub(crate) fn configure_asset_systems(app: &mut App) -> &mut App { // these should be in the same set as bevy's asset systems // currently this is in the PreUpdate set - app.add_systems( - PreUpdate, - ( - dispatch_script_asset_events.in_set(ScriptingSystemSet::ScriptAssetDispatch), - remove_script_metadata.in_set(ScriptingSystemSet::ScriptMetadataRemoval), - ), - ) + app + // .add_systems( + // PreUpdate, + // ( + // dispatch_script_asset_events.in_set(ScriptingSystemSet::ScriptAssetDispatch), + // remove_script_metadata.in_set(ScriptingSystemSet::ScriptMetadataRemoval), + // ), + // ) .configure_sets( PreUpdate, ( @@ -354,10 +261,7 @@ pub(crate) fn configure_asset_systems(app: &mut App) -> &mut App { .after(ScriptingSystemSet::ScriptAssetDispatch) .before(ScriptingSystemSet::ScriptMetadataRemoval), ), - ) - .init_resource::() - .init_resource::() - .add_event::(); + ); app } @@ -369,7 +273,7 @@ pub(crate) fn configure_asset_systems_for_plugin( ) -> &mut App { app.add_systems( PreUpdate, - sync_script_data::

.in_set(ScriptingSystemSet::ScriptCommandDispatch), + (eval_script::

, sync_script_data::

).in_set(ScriptingSystemSet::ScriptCommandDispatch), ); app } @@ -580,9 +484,9 @@ mod tests { struct DummyPlugin; impl IntoScriptPluginParams for DummyPlugin { - type R = (); - type C = (); const LANGUAGE: Language = Language::Lua; + type C = (); + type R = (); fn build_runtime() -> Self::R {} } diff --git a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs index cf91e5bced..d4a290fab0 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs @@ -4,8 +4,8 @@ use std::hash::{BuildHasherDefault, Hasher}; use bevy::{ ecs::{component::ComponentId, world::unsafe_world_cell::UnsafeWorldCell}, + platform::collections::{HashMap, HashSet}, prelude::Resource, - utils::hashbrown::{HashMap, HashSet}, }; use parking_lot::Mutex; use smallvec::SmallVec; diff --git a/crates/bevy_mod_scripting_core/src/bindings/allocator.rs b/crates/bevy_mod_scripting_core/src/bindings/allocator.rs index c37acdb943..9feb3c2454 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/allocator.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/allocator.rs @@ -1,12 +1,13 @@ //! An allocator used to control the lifetime of allocations +use bevy::prelude::Resource; use bevy::{ app::{App, Plugin, PostUpdate}, diagnostic::{Diagnostic, DiagnosticPath, Diagnostics, RegisterDiagnostic}, - ecs::system::{Res, Resource}, + ecs::system::Res, + platform::collections::HashMap, prelude::ResMut, reflect::PartialReflect, - utils::hashbrown::HashMap, }; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{ diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs index 0852840708..c07b25921c 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/from.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/from.rs @@ -585,4 +585,4 @@ macro_rules! impl_from_script_tuple { }; } -bevy::utils::all_tuples!(impl_from_script_tuple, 1, 14, T); +variadics_please::all_tuples!(impl_from_script_tuple, 1, 14, T); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs index fd1fb88e9b..fd48053c1e 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/into.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/into.rs @@ -199,4 +199,4 @@ macro_rules! impl_into_script_tuple { } } -bevy::utils::all_tuples!(impl_into_script_tuple, 1, 14, T); +variadics_please::all_tuples!(impl_into_script_tuple, 1, 14, T); 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 aeaaa5b081..dbcfad6640 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 @@ -10,8 +10,8 @@ use crate::{ error::InteropError, ScriptValue, }; +use bevy::platform::collections::HashMap; use bevy::prelude::{Reflect, Resource}; -use bevy::utils::hashbrown::HashMap; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::borrow::Cow; use std::collections::VecDeque; @@ -622,13 +622,13 @@ macro_rules! impl_script_function { }; } -bevy::utils::all_tuples!(impl_script_function, 0, 13, T); +variadics_please::all_tuples!(impl_script_function, 0, 13, T); #[cfg(test)] mod test { - use super::*; + use super::*; - fn with_local_world(f: F) { + fn with_local_world(f: F) { let mut world = bevy::prelude::World::default(); WorldGuard::with_static_guard(&mut world, |world| { ThreadWorldContainer.set_world(world).unwrap(); diff --git a/crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs b/crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs index 0bc1bf0a3f..e02dc601af 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/function/type_dependencies.rs @@ -1,11 +1,11 @@ //! This module contains the [`GetTypeDependencies`] trait and its implementations for various types. use super::{ - from::{Mut, Ref, Union, Val}, - script_function::FunctionCallContext, DynamicScriptFunction, DynamicScriptFunctionMut, + from::{Mut, Ref, Union, Val}, + script_function::FunctionCallContext, DynamicScriptFunction, DynamicScriptFunctionMut, }; use crate::{ - bindings::{ReflectReference, ScriptValue}, error::InteropError} + bindings::{ReflectReference, ScriptValue}, error::InteropError} ; use bevy::reflect::{FromReflect, GetTypeRegistration, TypeRegistry, Typed}; use bevy_mod_scripting_derive::impl_get_type_dependencies; @@ -156,7 +156,7 @@ macro_rules! register_tuple_dependencies { } -bevy::utils::all_tuples!(register_tuple_dependencies, 1, 14, T); +variadics_please::all_tuples!(register_tuple_dependencies, 1, 14, T); /// A trait collecting type dependency information for a whole function. Used to register everything used by a function with the type registry pub trait GetFunctionTypeDependencies { @@ -184,4 +184,4 @@ macro_rules! impl_script_function_type_dependencies{ }; } -bevy::utils::all_tuples!(impl_script_function_type_dependencies, 0, 13, T); +variadics_please::all_tuples!(impl_script_function_type_dependencies, 0, 13, T); 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 44e85bd38d..8e31b4bc26 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/core.rs @@ -53,7 +53,7 @@ impl Plugin for CoreScriptGlobalsPlugin { app.init_resource::(); } fn finish(&self, app: &mut bevy::app::App) { - profiling::function_scope!("app finish"); + // profiling::function_scope!("app finish"); if self.register_static_references { register_static_core_globals(app.world_mut(), self.filter); @@ -121,7 +121,7 @@ impl CoreGlobals { >, InteropError, > { - profiling::function_scope!("registering core globals"); + // profiling::function_scope!("registering core globals"); let type_registry = guard.type_registry(); let type_registry = type_registry.read(); let mut type_cache = HashMap::::default(); diff --git a/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs b/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs index 42f4d2de80..49e329e5ba 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/globals/mod.rs @@ -1,15 +1,15 @@ //! Contains abstractions for exposing "globals" to scripts, in a language-agnostic way. use super::{ - function::arg_meta::{ScriptReturn, TypedScriptReturn}, - script_value::ScriptValue, - WorldGuard, + function::arg_meta::{ScriptReturn, TypedScriptReturn}, + script_value::ScriptValue, + WorldGuard, }; use crate::{ - docgen::{into_through_type_info, typed_through::ThroughTypeInfo}, - error::InteropError, + docgen::{into_through_type_info, typed_through::ThroughTypeInfo}, + error::InteropError, }; -use bevy::{ecs::system::Resource, reflect::Typed, utils::hashbrown::HashMap}; +use bevy::{platform::collections::HashMap, prelude::Resource, reflect::Typed}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::{any::TypeId, borrow::Cow, sync::Arc}; @@ -235,11 +235,11 @@ impl ScriptGlobalsRegistry { #[cfg(test)] mod test { - use bevy::ecs::world::World; + use bevy::ecs::world::World; - use super::*; + use super::*; - #[test] + #[test] fn test_script_globals_registry() { let mut registry = ScriptGlobalsRegistry::default(); diff --git a/crates/bevy_mod_scripting_core/src/bindings/reference.rs b/crates/bevy_mod_scripting_core/src/bindings/reference.rs index 9df1e2fbdc..d573154d7b 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/reference.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/reference.rs @@ -6,19 +6,19 @@ //! we need wrapper types which have owned and ref variants. use super::{access_map::ReflectAccessId, WorldGuard}; use crate::{ - bindings::{with_access_read, with_access_write, ReflectAllocationId}, - error::InteropError, - reflection_extensions::{PartialReflectExt, TypeIdExtensions}, - ReflectAllocator, + bindings::{with_access_read, with_access_write, ReflectAllocationId}, + error::InteropError, + reflection_extensions::{PartialReflectExt, TypeIdExtensions}, + ReflectAllocator, }; use bevy::{ - ecs::{ - change_detection::MutUntyped, component::ComponentId, entity::Entity, - world::unsafe_world_cell::UnsafeWorldCell, - }, - prelude::{Component, ReflectDefault, Resource}, - ptr::Ptr, - reflect::{ParsedPath, PartialReflect, Reflect, ReflectFromPtr, ReflectPath}, + ecs::{ + change_detection::MutUntyped, component::ComponentId, entity::Entity, + world::unsafe_world_cell::UnsafeWorldCell, + }, + prelude::{Component, ReflectDefault, Resource}, + ptr::Ptr, + reflect::{ParsedPath, PartialReflect, Reflect, ReflectFromPtr, ReflectPath}, }; use std::{any::TypeId, fmt::Debug}; @@ -214,7 +214,7 @@ impl ReflectReference { self.with_reflect(world.clone(), |r| { ::from_reflect_or_clone(r, world.clone()) - }) + })? } /// The way to access the value of the reference, that is the pointed-to value. @@ -503,7 +503,11 @@ impl ReflectBase { match self { ReflectBase::Component(entity, component_id) => { // Safety: the caller ensures invariants hold - world.get_entity(entity)?.get_by_id(component_id) + if let Ok(entity) = world.get_entity(entity) { + entity.get_by_id(component_id) + } else { + None + } } ReflectBase::Resource(component_id) => { // Safety: the caller ensures invariants hold @@ -522,7 +526,11 @@ impl ReflectBase { match self { ReflectBase::Component(entity, component_id) => { // Safety: the caller ensures invariants hold - world.get_entity(entity)?.get_mut_by_id(component_id) + if let Ok(entity) = world.get_entity(entity) { + entity.get_mut_by_id(component_id).ok() + } else { + None + } } ReflectBase::Resource(component_id) => { // Safety: the caller ensures invariants hold @@ -638,15 +646,15 @@ impl Iterator for ReflectRefIter { #[cfg(test)] mod test { - use bevy::prelude::{AppTypeRegistry, World}; + use bevy::prelude::{AppTypeRegistry, World}; - use crate::bindings::{ - function::script_function::AppScriptFunctionRegistry, AppReflectAllocator, - }; + use crate::bindings::{ + function::script_function::AppScriptFunctionRegistry, AppReflectAllocator, + }; - use super::*; + use super::*; - #[derive(Reflect, Component, Debug, Clone, PartialEq)] + #[derive(Reflect, Component, Debug, Clone, PartialEq)] struct Component(Vec); #[derive(Reflect, Resource, Debug, Clone, PartialEq)] diff --git a/crates/bevy_mod_scripting_core/src/bindings/schedule.rs b/crates/bevy_mod_scripting_core/src/bindings/schedule.rs index 061029a46a..8feff1cc98 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/schedule.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/schedule.rs @@ -3,15 +3,15 @@ use super::{script_system::ScriptSystemBuilder, WorldAccessGuard}; use crate::{error::InteropError, IntoScriptPluginParams}; use bevy::{ - app::{ - First, FixedFirst, FixedLast, FixedMain, FixedPostUpdate, FixedPreUpdate, FixedUpdate, - Last, PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop, Startup, Update, - }, - ecs::{ - schedule::{Schedule, ScheduleLabel, Schedules}, - system::Resource, - world::World, - }, + app::{ + First, FixedFirst, FixedLast, FixedMain, FixedPostUpdate, FixedPreUpdate, FixedUpdate, + Last, PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop, Startup, Update, + }, + ecs::{ + schedule::{Schedule, ScheduleLabel, Schedules}, + world::World, + }, + prelude::Resource, }; use bevy_system_reflection::{ReflectSchedule, ReflectSystem}; use parking_lot::RwLock; @@ -191,19 +191,18 @@ impl WorldAccessGuard<'_> { reason = "tests are there but not working currently" )] mod tests { - - use bevy::{ - app::{App, Update}, - ecs::{ - schedule::{NodeId, Schedules}, - system::IntoSystem, - }, - }; - use test_utils::make_test_plugin; - - use super::*; - - #[test] + use bevy::{ + app::Update, + ecs::{ + schedule::{NodeId, Schedules}, + system::IntoSystem, + }, + }; + use test_utils::make_test_plugin; + + use super::*; + + #[test] fn test_schedule_registry() { let mut registry = ScheduleRegistry::default(); registry.register(Update); @@ -342,7 +341,7 @@ mod tests { // Collect all edges as (from, to) name pairs. let mut found_edges = Vec::new(); - for (from, to, _) in graph.dependency().graph().all_edges() { + for (from, to) in graph.dependency().graph().all_edges() { let name_from = resolve_name(from); let name_to = resolve_name(to); found_edges.push((name_from, name_to)); diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs index 74243b29a3..b1a0c650f0 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_component.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_component.rs @@ -3,13 +3,11 @@ use super::{ScriptComponentRegistration, ScriptTypeRegistration, ScriptValue, WorldAccessGuard}; use crate::error::InteropError; use bevy::{ - app::{App, Plugin}, - ecs::{ - component::{Component, ComponentDescriptor, StorageType}, - system::Resource, - }, - reflect::{prelude::ReflectDefault, GetTypeRegistration, Reflect}, - utils::HashMap, + app::{App, Plugin}, + ecs::component::{Component, ComponentDescriptor, StorageType, ComponentCloneBehavior, Mutable}, + platform::collections::HashMap, + prelude::Resource, + reflect::{prelude::ReflectDefault, GetTypeRegistration, Reflect}, }; use parking_lot::RwLock; use std::{alloc::Layout, mem::needs_drop, sync::Arc}; @@ -31,6 +29,7 @@ pub struct DynamicComponentInfo { impl Component for DynamicComponent { const STORAGE_TYPE: StorageType = StorageType::Table; + type Mutability = Mutable; } /// A registry of dynamically registered script components @@ -96,6 +95,8 @@ impl WorldAccessGuard<'_> { DynamicComponent::STORAGE_TYPE, Layout::new::(), needs_drop::().then_some(|x| x.drop_as::()), + true, + ComponentCloneBehavior::Default, ) }; w.register_component_with_descriptor(descriptor) @@ -135,10 +136,10 @@ impl Plugin for DynamicScriptComponentPlugin { #[cfg(test)] mod test { - use super::*; - use bevy::ecs::world::World; + use super::*; + use bevy::ecs::world::World; - #[test] + #[test] fn test_script_component() { let mut world = World::new(); let registration = { diff --git a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs index c523b0d231..e5648eb486 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/script_system.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/script_system.rs @@ -10,6 +10,7 @@ use super::{ WorldGuard, }; use crate::{ + ScriptAsset, bindings::pretty_print::DisplayWithWorld, context::ContextLoadingSettings, error::{InteropError, ScriptError}, @@ -17,25 +18,31 @@ use crate::{ extractors::get_all_access_ids, handler::CallbackSettings, runtime::RuntimeContainer, - script::{ScriptId, Scripts}, + script::{ScriptId, ScriptContextProvider, ScriptContext, Domain}, IntoScriptPluginParams, }; use bevy::{ + asset::Handle, + prelude::{Query, AssetServer}, ecs::{ archetype::{ArchetypeComponentId, ArchetypeGeneration}, component::{ComponentId, Tick}, entity::Entity, query::{Access, FilteredAccess, FilteredAccessSet, QueryState}, reflect::AppTypeRegistry, - schedule::{IntoSystemConfigs, SystemSet}, - system::{IntoSystem, System}, + schedule::{ + SystemSet, Infallible + }, + system::{IntoSystem, System, SystemParamValidationError}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }, + platform::collections::HashSet, + prelude::{BevyError, IntoScheduleConfigs}, reflect::{OffsetAccess, ParsedPath, Reflect}, - utils::hashbrown::HashSet, }; use bevy_system_reflection::{ReflectSchedule, ReflectSystem}; use std::{any::TypeId, borrow::Cow, hash::Hash, marker::PhantomData, ops::Deref}; + #[derive(Clone, Hash, PartialEq, Eq)] /// a system set for script systems. pub struct ScriptSystemSet(Cow<'static, str>); @@ -78,12 +85,15 @@ enum ScriptSystemParamDescriptor { EntityQuery(ScriptQueryBuilder), } +type ScriptPath = Cow<'static, str>; + /// A builder for systems living in scripts #[derive(Reflect, Clone)] #[reflect(opaque)] pub struct ScriptSystemBuilder { pub(crate) name: CallbackLabel, - pub(crate) script_id: ScriptId, + pub(crate) script_id: ScriptPath, + domain: Option, before: Vec, after: Vec, system_params: Vec, @@ -93,12 +103,13 @@ pub struct ScriptSystemBuilder { #[profiling::all_functions] impl ScriptSystemBuilder { /// Creates a new script system builder - pub fn new(name: CallbackLabel, script_id: ScriptId) -> Self { + pub fn new(name: CallbackLabel, script_id: ScriptPath, domain: Option) -> Self { Self { before: vec![], after: vec![], name, script_id, + domain, system_params: vec![], is_exclusive: false, } @@ -160,7 +171,7 @@ impl ScriptSystemBuilder { // this is quite important, by default systems are placed in a set defined by their TYPE, i.e. in this case // all script systems would be the same // let set = ScriptSystemSet::next(); - let mut system_config = IntoSystemConfigs::>::into_configs(self); // apply ordering + let mut system_config = > + 'static)>, (Infallible, IsDynamicScriptSystem

)>>::into_configs(self); // apply ordering for (other, is_before) in before_systems .into_iter() .map(|b| (b, true)) @@ -170,7 +181,8 @@ impl ScriptSystemBuilder { if is_before { bevy::log::info!("before {default_set:?}"); system_config = system_config.before(*default_set); - } else { bevy::log::info!("before {default_set:?}"); + } else { + bevy::log::info!("before {default_set:?}"); bevy::log::info!("after {default_set:?}"); system_config = system_config.after(*default_set); } @@ -194,7 +206,7 @@ impl ScriptSystemBuilder { } struct DynamicHandlerContext<'w, P: IntoScriptPluginParams> { - scripts: &'w Scripts

, + script_context: &'w ScriptContext

, callback_settings: &'w CallbackSettings

, context_loading_settings: &'w ContextLoadingSettings

, runtime_container: &'w RuntimeContainer

, @@ -208,9 +220,8 @@ impl<'w, P: IntoScriptPluginParams> DynamicHandlerContext<'w, P> { )] pub fn init_param(world: &mut World, system: &mut FilteredAccessSet) { let mut access = FilteredAccess::::matches_nothing(); - let scripts_res_id = world - .resource_id::>() - .expect("Scripts resource not found"); + // let scripts_res_id = world + // .query::<&Script

>(); let callback_settings_res_id = world .resource_id::>() .expect("CallbackSettings resource not found"); @@ -221,7 +232,6 @@ impl<'w, P: IntoScriptPluginParams> DynamicHandlerContext<'w, P> { .resource_id::>() .expect("RuntimeContainer resource not found"); - access.add_resource_read(scripts_res_id); access.add_resource_read(callback_settings_res_id); access.add_resource_read(context_loading_settings_res_id); access.add_resource_read(runtime_container_res_id); @@ -236,7 +246,9 @@ impl<'w, P: IntoScriptPluginParams> DynamicHandlerContext<'w, P> { pub fn get_param(system: &UnsafeWorldCell<'w>) -> Self { unsafe { Self { - scripts: system.get_resource().expect("Scripts resource not found"), + script_context: system + .get_resource() + .expect("Scripts resource not found"), callback_settings: system .get_resource() .expect("CallbackSettings resource not found"), @@ -254,15 +266,15 @@ impl<'w, P: IntoScriptPluginParams> DynamicHandlerContext<'w, P> { pub fn call_dynamic_label( &self, label: &CallbackLabel, - script_id: &ScriptId, + script_id: &Handle, entity: Entity, + domain: &Option, payload: Vec, guard: WorldGuard<'_>, ) -> Result { // find script - let script = match self.scripts.scripts.get(script_id) { - Some(script) => script, - None => return Err(InteropError::missing_script(script_id.clone()).into()), + let Some(context) = self.script_context.get(Some(entity), &script_id.id(), domain) else { + return Err(InteropError::missing_context(script_id.clone()).into()); }; // call the script @@ -272,7 +284,7 @@ impl<'w, P: IntoScriptPluginParams> DynamicHandlerContext<'w, P> { .context_pre_handling_initializers; let runtime = &self.runtime_container.runtime; - let mut context = script.context.lock(); + let mut context = context.lock(); CallbackSettings::

::call( handler, @@ -339,10 +351,11 @@ pub struct DynamicScriptSystem { /// cause a conflict pub(crate) archetype_component_access: Access, pub(crate) last_run: Tick, - target_script: ScriptId, + target_script: ScriptPath, archetype_generation: ArchetypeGeneration, system_param_descriptors: Vec, state: Option, + domain: Option, _marker: std::marker::PhantomData P>, } @@ -364,6 +377,7 @@ impl IntoSystem<(), (), IsDynamicScriptSystem

> last_run: Default::default(), target_script: builder.script_id, state: None, + domain: None, component_access_set: Default::default(), archetype_component_access: Default::default(), _marker: Default::default(), @@ -420,6 +434,10 @@ impl System for DynamicScriptSystem

{ }; let mut payload = Vec::with_capacity(state.system_params.len()); + let script_id = { + let asset_server = world.world().resource::(); + asset_server.load(&*self.target_script) + }; let guard = if self.exclusive { // safety: we are an exclusive system, therefore the cell allows us to do this let world = unsafe { world.world_mut() }; @@ -489,8 +507,10 @@ impl System for DynamicScriptSystem

{ let result = handler_ctxt.call_dynamic_label( &state.callback_label, - &self.target_script, + // &self.target_script, + &script_id, Entity::from_raw(0), + &self.domain, payload, guard.clone(), ); @@ -642,9 +662,9 @@ impl System for DynamicScriptSystem

{ unsafe fn validate_param_unsafe( &mut self, - _world: bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell, - ) -> bool { - true + _world: UnsafeWorldCell, + ) -> Result<(), SystemParamValidationError> { + Ok(()) } fn default_system_sets(&self) -> Vec { @@ -655,7 +675,7 @@ impl System for DynamicScriptSystem

{ TypeId::of::() } - fn validate_param(&mut self, world: &World) -> bool { + fn validate_param(&mut self, world: &World) -> Result<(), SystemParamValidationError> { let world_cell = world.as_unsafe_world_cell_readonly(); self.update_archetype_component_access(world_cell); // SAFETY: diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index c88dc84f61..7c061d2d19 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -29,16 +29,18 @@ use crate::{ error::InteropError, reflection_extensions::PartialReflectExt, }; +use bevy::ecs::component::Mutable; +use bevy::prelude::{ChildOf, Children}; use bevy::{ app::AppExit, ecs::{ component::{Component, ComponentId}, entity::Entity, + prelude::Resource, reflect::{AppTypeRegistry, ReflectFromWorld, ReflectResource}, - system::{Commands, Resource}, + system::Commands, world::{unsafe_world_cell::UnsafeWorldCell, CommandQueue, Mut, World}, }, - hierarchy::{BuildChildren, Children, DespawnRecursiveExt, Parent}, reflect::{ std_traits::ReflectDefault, DynamicEnum, DynamicStruct, DynamicTuple, DynamicTupleStruct, DynamicVariant, ParsedPath, PartialReflect, TypeRegistryArc, @@ -446,7 +448,7 @@ impl<'w> WorldAccessGuard<'w> { format!("Could not access component: {}", std::any::type_name::()), { // Safety: we have acquired access for the duration of the closure - f(unsafe { cell.get_entity(entity).and_then(|e| e.get::()) }) + f(unsafe { cell.get_entity(entity).map(|e| e.get::()) }.ok().unwrap_or(None)) } ) } @@ -454,7 +456,7 @@ impl<'w> WorldAccessGuard<'w> { /// Safely accesses the component by claiming and releasing access to it. pub fn with_component_mut(&self, entity: Entity, f: F) -> Result where - T: Component, + T: Component, F: FnOnce(Option>) -> O, { let cell = self.as_unsafe_world_cell()?; @@ -466,7 +468,7 @@ impl<'w> WorldAccessGuard<'w> { format!("Could not access component: {}", std::any::type_name::()), { // Safety: we have acquired access for the duration of the closure - f(unsafe { cell.get_entity(entity).and_then(|e| e.get_mut::()) }) + f(unsafe { cell.get_entity(entity).map(|e| e.get_mut::()) }.ok().unwrap_or(None)) } ) } @@ -478,7 +480,7 @@ impl<'w> WorldAccessGuard<'w> { f: F, ) -> Result where - T: Component + Default, + T: Component + Default, F: FnOnce(&mut T) -> O, { self.with_global_access(|world| match world.get_mut::(entity) { @@ -536,7 +538,7 @@ impl<'w> WorldAccessGuard<'w> { /// checks if a given entity exists and is valid pub fn is_valid_entity(&self, entity: Entity) -> Result { let cell = self.as_unsafe_world_cell()?; - Ok(cell.get_entity(entity).is_some() && entity.index() != 0) + Ok(cell.get_entity(entity).is_ok() && entity.index() != 0) } /// Tries to call a fitting overload of the function with the given name and in the type id's namespace based on the arguments provided. @@ -816,10 +818,10 @@ impl WorldAccessGuard<'_> { // try to construct type from reflect // TODO: it would be nice to have a ::from_reflect_with_fallback equivalent, that does exactly that // only using this as it's already there and convenient, the clone variant hitting will be confusing to end users - Ok(::from_reflect_or_clone( + ::from_reflect_or_clone( dynamic.as_ref(), self.clone(), - )) + ) } /// Spawns a new entity in the world @@ -989,7 +991,7 @@ impl WorldAccessGuard<'_> { let cell = self.as_unsafe_world_cell()?; let entity = cell .get_entity(entity) - .ok_or_else(|| InteropError::missing_entity(entity))?; + .map_err(|_| InteropError::missing_entity(entity))?; if entity.contains_id(component_registration.component_id) { Ok(Some(ReflectReference { @@ -1016,7 +1018,7 @@ impl WorldAccessGuard<'_> { let cell = self.as_unsafe_world_cell()?; let entity = cell .get_entity(entity) - .ok_or_else(|| InteropError::missing_entity(entity))?; + .map_err(|_| InteropError::missing_entity(entity))?; Ok(entity.contains_id(component_id)) } @@ -1111,7 +1113,7 @@ impl WorldAccessGuard<'_> { return Err(InteropError::missing_entity(entity)); } - self.with_component(entity, |c: Option<&Parent>| c.map(|c| c.get())) + self.with_component(entity, |c: Option<&ChildOf>| c.map(|c| c.parent())) } /// insert children into the given entity @@ -1185,7 +1187,7 @@ impl WorldAccessGuard<'_> { self.with_global_access(|world| { let mut queue = CommandQueue::default(); let mut commands = Commands::new(&mut queue, world); - commands.entity(parent).despawn_recursive(); + commands.entity(parent).despawn(); queue.apply(world); }) } @@ -1199,7 +1201,7 @@ impl WorldAccessGuard<'_> { self.with_global_access(|world| { let mut queue = CommandQueue::default(); let mut commands = Commands::new(&mut queue, world); - commands.entity(entity).despawn(); + commands.entity(entity).remove::().despawn(); queue.apply(world); }) } @@ -1213,7 +1215,7 @@ impl WorldAccessGuard<'_> { self.with_global_access(|world| { let mut queue = CommandQueue::default(); let mut commands = Commands::new(&mut queue, world); - commands.entity(parent).despawn_descendants(); + commands.entity(parent).despawn_related::(); queue.apply(world); }) } diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index 95ea651b03..c9bf807994 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -1,6 +1,7 @@ //! Commands for creating, updating and deleting scripts use crate::{ + AssetId, asset::ScriptAsset, bindings::{ScriptValue, WorldGuard}, context::ContextBuilder, @@ -9,28 +10,30 @@ use crate::{ CallbackLabel, IntoCallbackLabel, OnScriptLoaded, OnScriptUnloaded, ScriptCallbackResponseEvent, }, - extractors::{with_handler_system_state, HandlerContext}, + extractors::{with_handler_system_state, HandlerContext, WithWorldGuard}, handler::{handle_script_errors, send_callback_response}, - script::{Script, ScriptId, Scripts, StaticScripts}, + script::{ScriptId, StaticScripts, DisplayProxy, ScriptContextProvider, Domain}, IntoScriptPluginParams, }; -use bevy::{asset::Handle, ecs::entity::Entity, log::debug, prelude::Command}; +use bevy::{asset::{Assets, Handle}, ecs::entity::Entity, log::{warn, debug}, prelude::{EntityCommand, Command, EntityWorldMut}}; use parking_lot::Mutex; use std::{marker::PhantomData, sync::Arc}; /// Deletes a script with the given ID pub struct DeleteScript { /// The ID of the script to delete - pub id: ScriptId, + pub id: AssetId, + domain: Option, /// hack to make this Send, C does not need to be Send since it is not stored in the command pub _ph: PhantomData, } impl DeleteScript

{ /// Creates a new DeleteScript command with the given ID - pub fn new(id: ScriptId) -> Self { + pub fn new(id: AssetId, domain: Option) -> Self { Self { id, + domain, _ph: PhantomData, } } @@ -40,16 +43,17 @@ impl Command for DeleteScript

{ fn apply(self, world: &mut bevy::prelude::World) { // first apply unload callback RunScriptCallback::

::new( - self.id.clone(), + Handle::Weak(self.id.clone()), Entity::from_raw(0), + self.domain, OnScriptUnloaded::into_callback_label(), vec![], false, ) .apply(world); - let mut scripts = world.get_resource_or_init::>(); - if scripts.remove(self.id.clone()) { + let mut scripts = world.get_resource_or_init::(); + if scripts.remove(&self.id) { debug!("Deleted script with id: {}", self.id); } else { bevy::log::error!( @@ -64,9 +68,10 @@ impl Command for DeleteScript

{ /// /// If script comes from an asset, expects it to be loaded, otherwise this command will fail to process the script. pub struct CreateOrUpdateScript { - id: ScriptId, + id: Handle, + // It feels like we're using a Box, which requires a clone merely to satisfy the Command trait. content: Box<[u8]>, - asset: Option>, + domain: Option, // Hack to make this Send, C does not need to be Send since it is not stored in the command _ph: std::marker::PhantomData, } @@ -74,57 +79,54 @@ pub struct CreateOrUpdateScript { #[profiling::all_functions] impl CreateOrUpdateScript

{ /// Creates a new CreateOrUpdateScript command with the given ID, content and asset - pub fn new(id: ScriptId, content: Box<[u8]>, asset: Option>) -> Self { + pub fn new(id: Handle, content: Box<[u8]>, domain: Option) -> Self { Self { id, content, - asset, + domain, _ph: std::marker::PhantomData, } } fn reload_context( - &self, + id: &Handle, + content: &[u8], + context: &mut P::C, guard: WorldGuard, handler_ctxt: &HandlerContext

, ) -> Result<(), ScriptError> { - let existing_script = match handler_ctxt.scripts.scripts.get(&self.id) { - Some(script) => script, - None => { - return Err( - InteropError::invariant("Tried to reload script which doesn't exist").into(), - ) - } - }; + // let mut context = script + // .contexts + // .get_mut(&id.id()) + // .ok_or_else(|| InteropError::invariant("Tried to reload script which doesn't have a context"))?; // reload context - let mut context = existing_script.context.lock(); + // let mut context = context.lock(); (ContextBuilder::

::reload)( handler_ctxt.context_loading_settings.loader.reload, - &self.id, - &self.content, - &mut context, + &id, + content, + context, &handler_ctxt.context_loading_settings.context_initializers, &handler_ctxt .context_loading_settings .context_pre_handling_initializers, guard.clone(), &handler_ctxt.runtime_container.runtime, - )?; - - Ok(()) + ) } fn load_context( - &self, + id: &Handle, + content: &[u8], guard: WorldGuard, handler_ctxt: &mut HandlerContext

, - ) -> Result<(), ScriptError> { + ) -> Result { let context = (ContextBuilder::

::load)( handler_ctxt.context_loading_settings.loader.load, - &self.id, - &self.content, + &id, + content, &handler_ctxt.context_loading_settings.context_initializers, &handler_ctxt .context_loading_settings @@ -133,125 +135,138 @@ impl CreateOrUpdateScript

{ &handler_ctxt.runtime_container.runtime, )?; - let context = Arc::new(Mutex::new(context)); + // Ok(Arc::new(Mutex::new(context))) + Ok(context) + } - handler_ctxt.scripts.scripts.insert( - self.id.clone(), - Script { - id: self.id.clone(), - asset: self.asset.clone(), - context, - }, - ); - Ok(()) + pub(crate) fn create_or_update_script( + entity: Option, + id: &Handle, + content: &[u8], + domain: &Option, + guard: WorldGuard, + handler_ctxt: &mut HandlerContext

) -> Result<(), ScriptError> { + let assignment_strategy = handler_ctxt.context_loading_settings.assignment_strategy; + + let phrase; + let result = match handler_ctxt.script_context.get(entity, &id.id(), domain) { + Some(context) => { + bevy::log::debug!("{}: reloading script {}", P::LANGUAGE, id.display()); + let mut lcontext = context.lock(); + phrase = "reloading"; + Self::reload_context(id, content, &mut lcontext, guard.clone(), handler_ctxt) + .map(|_| None) + } + None => { + bevy::log::debug!("{}: loading script {}", P::LANGUAGE, id.display()); + phrase = "loading"; + Self::load_context(id, content, guard.clone(), handler_ctxt) + .map(Some) + } + }; + + match result { + Ok(maybe_context) => { + if let Some(context) = maybe_context { + if handler_ctxt.script_context.insert(entity, &id.id(), &None, context).is_err() { + warn!("Unable to insert script context for entity {:?} with script {}.", entity, id.display()); + } + } + + bevy::log::debug!( + "{}: script {} successfully created or updated", + P::LANGUAGE, + id.display() + ); + Ok(())// none until individual context support added. + } + Err(err) => { + handle_script_errors( + guard, + vec![err.clone() + .with_script(id.display()) + .with_context(P::LANGUAGE) + .with_context(phrase)] + .into_iter(), + ); + Err(err) + } + } } + } + #[profiling::all_functions] impl Command for CreateOrUpdateScript

{ fn apply(self, world: &mut bevy::prelude::World) { - let success = with_handler_system_state( + // todo!() + let result = with_handler_system_state( world, |guard, handler_ctxt: &mut HandlerContext

| { - let is_new_script = !handler_ctxt.scripts.scripts.contains_key(&self.id); - - let assigned_shared_context = - match handler_ctxt.context_loading_settings.assignment_strategy { - crate::context::ContextAssignmentStrategy::Individual => None, - crate::context::ContextAssignmentStrategy::Global => { - let is_new_context = handler_ctxt.scripts.scripts.is_empty(); - if !is_new_context { - handler_ctxt - .scripts - .scripts - .values() - .next() - .map(|s| s.context.clone()) - } else { - None - } - } - }; - - debug!( - "{}: CreateOrUpdateScript command applying (script_id: {}, new context?: {}, new script?: {})", - P::LANGUAGE, - self.id, - assigned_shared_context.is_none(), - is_new_script - ); - - let result = match &assigned_shared_context { - Some(assigned_shared_context) => { - if is_new_script { - // this will happen when sharing contexts - // make a new script with the shared context - let script = Script { - id: self.id.clone(), - asset: self.asset.clone(), - context: assigned_shared_context.clone(), - }; - // it can potentially be loaded but without a successful script reload but that - // leaves us in an okay state - handler_ctxt.scripts.scripts.insert(self.id.clone(), script); - } - bevy::log::debug!("{}: reloading script with id: {}", P::LANGUAGE, self.id); - self.reload_context(guard.clone(), handler_ctxt) - } - None => { - bevy::log::debug!("{}: loading script with id: {}", P::LANGUAGE, self.id); - self.load_context(guard.clone(), handler_ctxt) - } - }; - - let phrase = if assigned_shared_context.is_some() { - "reloading" - } else { - "loading" - }; - - if let Err(err) = result { - handle_script_errors( - guard, - vec![err - .with_script(self.id.clone()) - .with_context(P::LANGUAGE) - .with_context(phrase)] - .into_iter(), - ); - return false; - } + Self::create_or_update_script(None, &self.id, &self.content, &self.domain, + guard, handler_ctxt) + }); - bevy::log::debug!( - "{}: script with id: {} successfully created or updated", - P::LANGUAGE, - self.id - ); + // immediately run command for callback, but only if loading went fine + match result { + Ok(_) => { + + RunScriptCallback::

::new( + self.id, + Entity::from_raw(0), + self.domain, + OnScriptLoaded::into_callback_label(), + vec![], + false, + ) + .apply(world) + } + Err(_) => () + } + } +} - true - }, - ); +#[profiling::all_functions] +impl EntityCommand for CreateOrUpdateScript

{ + fn apply(self, entity_world: EntityWorldMut<'_>) { + let entity = entity_world.id(); + let world = entity_world.into_world_mut(); + let result = with_handler_system_state( + world, + |guard, handler_ctxt: &mut HandlerContext

| { + Self::create_or_update_script(Some(entity), &self.id, &self.content, &self.domain, + guard, handler_ctxt) + }); // immediately run command for callback, but only if loading went fine - if success { - RunScriptCallback::

::new( - self.id, - Entity::from_raw(0), - OnScriptLoaded::into_callback_label(), - vec![], - false, - ) - .apply(world) + match result { + Ok(maybe_script) => { + + RunScriptCallback::

::new( + self.id, + entity, + self.domain, + OnScriptLoaded::into_callback_label(), + vec![], + false, + ) + .apply(world) + } + Err(_) => () } } } + /// Runs a callback on the script with the given ID if it exists pub struct RunScriptCallback { /// The ID of the script to run the callback on - pub id: ScriptId, + pub id: Handle, /// The entity to use for the callback pub entity: Entity, + /// The domain if any + pub domain: Option, /// The callback to run pub callback: CallbackLabel, /// optional context passed down to errors @@ -267,8 +282,9 @@ pub struct RunScriptCallback { impl RunScriptCallback

{ /// Creates a new RunCallbackCommand with the given ID, callback and arguments pub fn new( - id: ScriptId, + id: Handle, entity: Entity, + domain: Option, callback: CallbackLabel, args: Vec, trigger_response: bool, @@ -276,6 +292,7 @@ impl RunScriptCallback

{ Self { id, entity, + domain, context: None, callback, args, @@ -294,19 +311,20 @@ impl RunScriptCallback

{ impl Command for RunScriptCallback

{ fn apply(self, world: &mut bevy::prelude::World) { with_handler_system_state(world, |guard, handler_ctxt: &mut HandlerContext

| { - if !handler_ctxt.is_script_fully_loaded(self.id.clone()) { - bevy::log::error!( - "{}: Cannot apply callback command, as script does not exist: {}. Ignoring.", - P::LANGUAGE, - self.id - ); - return; - } + // if !handler_ctxt.is_script_fully_loaded(self.id.id()) { + // bevy::log::error!( + // "{}: Cannot apply callback command, as script {} does not exist. Ignoring.", + // P::LANGUAGE, + // self.id.display() + // ); + // return; + // } let result = handler_ctxt.call_dynamic_label( &self.callback, &self.id, self.entity, + &self.domain, self.args, guard.clone(), ); @@ -316,14 +334,14 @@ impl Command for RunScriptCallback

{ guard.clone(), ScriptCallbackResponseEvent::new( self.callback, - self.id.clone(), + self.id.id(), result.clone(), ), ); } if let Err(err) = result { - let mut error_with_context = err.with_script(self.id).with_context(P::LANGUAGE); + let mut error_with_context = err.with_script(self.id.display()).with_context(P::LANGUAGE); if let Some(ctxt) = self.context { error_with_context = error_with_context.with_context(ctxt); } @@ -337,12 +355,12 @@ impl Command for RunScriptCallback

{ /// Adds a static script to the collection of static scripts pub struct AddStaticScript { /// The ID of the script to add - id: ScriptId, + id: Handle, } impl AddStaticScript { /// Creates a new AddStaticScript command with the given ID - pub fn new(id: impl Into) -> Self { + pub fn new(id: impl Into>) -> Self { Self { id: id.into() } } } @@ -357,12 +375,12 @@ impl Command for AddStaticScript { /// Removes a static script from the collection of static scripts pub struct RemoveStaticScript { /// The ID of the script to remove - id: ScriptId, + id: Handle, } impl RemoveStaticScript { /// Creates a new RemoveStaticScript command with the given ID - pub fn new(id: ScriptId) -> Self { + pub fn new(id: Handle) -> Self { Self { id } } } @@ -371,7 +389,7 @@ impl RemoveStaticScript { impl Command for RemoveStaticScript { fn apply(self, world: &mut bevy::prelude::World) { let mut static_scripts = world.get_resource_or_init::(); - static_scripts.remove(self.id); + static_scripts.remove(&self.id.id()); } } diff --git a/crates/bevy_mod_scripting_core/src/context.rs b/crates/bevy_mod_scripting_core/src/context.rs index dec993f7be..ebf5c2af94 100644 --- a/crates/bevy_mod_scripting_core/src/context.rs +++ b/crates/bevy_mod_scripting_core/src/context.rs @@ -1,12 +1,16 @@ //! Traits and types for managing script contexts. use crate::{ + ScriptAsset, bindings::{ThreadWorldContainer, WorldContainer, WorldGuard}, error::{InteropError, ScriptError}, script::ScriptId, IntoScriptPluginParams, }; -use bevy::ecs::{entity::Entity, system::Resource}; +use bevy::{ + ecs::entity::Entity, + prelude::{Handle, Resource}, +}; /// A trait that all script contexts must implement. /// @@ -17,11 +21,11 @@ impl Context for T {} /// Initializer run once after creating a context but before executing it for the first time as well as after re-loading the script pub type ContextInitializer

= - fn(&str, &mut

::C) -> Result<(), ScriptError>; + fn(&Handle, &mut

::C) -> Result<(), ScriptError>; /// Initializer run every time before executing or loading/re-loading a script pub type ContextPreHandlingInitializer

= - fn(&str, Entity, &mut

::C) -> Result<(), ScriptError>; + fn(&Handle, Entity, &mut

::C) -> Result<(), ScriptError>; /// Settings concerning the creation and assignment of script contexts as well as their initialization. #[derive(Resource)] @@ -59,7 +63,7 @@ impl Clone for ContextLoadingSettings { } /// A strategy for loading contexts pub type ContextLoadFn

= fn( - script_id: &ScriptId, + script_id: &Handle, content: &[u8], context_initializers: &[ContextInitializer

], pre_handling_initializers: &[ContextPreHandlingInitializer

], @@ -68,7 +72,7 @@ pub type ContextLoadFn

= fn( /// A strategy for reloading contexts pub type ContextReloadFn

= fn( - script_id: &ScriptId, + script_id: &Handle, content: &[u8], previous_context: &mut

::C, context_initializers: &[ContextInitializer

], @@ -99,7 +103,7 @@ impl ContextBuilder

{ /// load a context pub fn load( loader: ContextLoadFn

, - script: &ScriptId, + script: &Handle, content: &[u8], context_initializers: &[ContextInitializer

], pre_handling_initializers: &[ContextPreHandlingInitializer

], @@ -121,7 +125,7 @@ impl ContextBuilder

{ /// reload a context pub fn reload( reloader: ContextReloadFn

, - script: &ScriptId, + script: &Handle, content: &[u8], previous_context: &mut P::C, context_initializers: &[ContextInitializer

], @@ -161,3 +165,10 @@ pub enum ContextAssignmentStrategy { /// Share contexts with all other scripts Global, } + +impl ContextAssignmentStrategy { + /// Returns true if there is one global context. + pub fn is_global(&self) -> bool { + matches!(self, ContextAssignmentStrategy::Global) + } +} diff --git a/crates/bevy_mod_scripting_core/src/docgen/info.rs b/crates/bevy_mod_scripting_core/src/docgen/info.rs index b6a9ce5ae1..949286c0d3 100644 --- a/crates/bevy_mod_scripting_core/src/docgen/info.rs +++ b/crates/bevy_mod_scripting_core/src/docgen/info.rs @@ -191,18 +191,18 @@ macro_rules! impl_documentable { }; } -bevy::utils::all_tuples!(impl_documentable, 0, 13, T); +variadics_please::all_tuples!(impl_documentable, 0, 13, T); #[cfg(test)] mod test { - use crate::{ - bindings::function::from::{Mut, Ref, Val}, - docgen::typed_through::UntypedWrapperKind, - }; + use crate::{ + bindings::function::from::{Mut, Ref, Val}, + docgen::typed_through::UntypedWrapperKind, + }; - use super::*; + use super::*; - #[test] + #[test] fn test_get_function_info() { fn test_fn(a: i32, b: f32) -> f64 { (a as f64) + (b as f64) diff --git a/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs b/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs index 7e467b6180..694711af35 100644 --- a/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs +++ b/crates/bevy_mod_scripting_core/src/docgen/typed_through.rs @@ -6,18 +6,18 @@ use std::{ffi::OsString, path::PathBuf}; use bevy::reflect::{TypeInfo, Typed}; use crate::{ - bindings::{ - function::{ - from::{Mut, Ref, Union, Val}, - script_function::{ - DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext, - }, - }, - script_value::ScriptValue, - ReflectReference, - }, - error::InteropError, - reflection_extensions::TypeInfoExtensions, + bindings::{ + function::{ + from::{Mut, Ref, Union, Val}, + script_function::{ + DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext, + }, + }, + script_value::ScriptValue, + ReflectReference, + }, + error::InteropError, + reflection_extensions::TypeInfoExtensions, }; /// All Through types follow one rule: @@ -265,13 +265,13 @@ macro_rules! impl_through_typed_tuple { }; } -bevy::utils::all_tuples!(impl_through_typed_tuple, 0, 13, T); +variadics_please::all_tuples!(impl_through_typed_tuple, 0, 13, T); #[cfg(test)] mod test { - use super::*; + use super::*; - fn assert_type_info_is_through() { + fn assert_type_info_is_through() { let type_info = T::type_info(); let through_type_info = T::through_type_info(); diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index 8b2bd2a967..c5d3ee4c85 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -1,6 +1,7 @@ //! Errors that can occur when interacting with the scripting system use crate::{ + ScriptAsset, bindings::{ access_map::{DisplayCodeLocation, ReflectAccessId}, function::namespace::Namespace, @@ -8,9 +9,10 @@ use crate::{ script_value::ScriptValue, ReflectBaseType, ReflectReference, }, - script::ScriptId, + script::{DisplayProxy, ScriptId}, }; use bevy::{ + asset::Handle, ecs::{ component::ComponentId, schedule::{ScheduleBuildError, ScheduleNotInitialized}, @@ -592,14 +594,14 @@ impl InteropError { } /// Thrown if a script could not be found when trying to call a synchronous callback or otherwise - pub fn missing_script(script_id: impl Into) -> Self { + pub fn missing_script(script_id: impl Into>) -> Self { Self(Arc::new(InteropErrorInner::MissingScript { script_id: script_id.into(), })) } /// Thrown if the required context for an operation is missing. - pub fn missing_context(script_id: impl Into) -> Self { + pub fn missing_context(script_id: impl Into>) -> Self { Self(Arc::new(InteropErrorInner::MissingContext { script_id: script_id.into(), })) @@ -628,7 +630,7 @@ pub enum InteropErrorInner { /// Thrown if a script could not be found when trying to call a synchronous callback. MissingScript { /// The script id that was not found. - script_id: ScriptId, + script_id: Handle, }, /// Thrown if a base type is not registered with the reflection system UnregisteredBase { @@ -812,7 +814,7 @@ pub enum InteropErrorInner { /// Thrown if the required context for an operation is missing. MissingContext { /// The script that was attempting to access the context - script_id: ScriptId, + script_id: Handle, }, /// Thrown when a schedule is missing from the registry. MissingSchedule { @@ -1254,8 +1256,8 @@ macro_rules! unregistered_component_or_resource_type { macro_rules! missing_script_for_callback { ($script_id:expr) => { format!( - "Could not find script with id: {}. Is the script loaded?", - $script_id + "Could not find script {}. Is the script loaded?", + $script_id.display() ) }; } @@ -1283,8 +1285,8 @@ macro_rules! argument_count_mismatch_msg { macro_rules! missing_context_for_callback { ($script_id:expr) => { format!( - "Missing context for script with id: {}. Was the script loaded?.", - $script_id + "Missing context for script {}. Was the script loaded?.", + $script_id.display() ) }; } diff --git a/crates/bevy_mod_scripting_core/src/event.rs b/crates/bevy_mod_scripting_core/src/event.rs index de9002ce85..5334b2fcfb 100644 --- a/crates/bevy_mod_scripting_core/src/event.rs +++ b/crates/bevy_mod_scripting_core/src/event.rs @@ -1,6 +1,6 @@ //! Event handlers and event types for scripting. -use crate::{bindings::script_value::ScriptValue, error::ScriptError, script::ScriptId}; +use crate::{bindings::script_value::ScriptValue, error::ScriptError, script::{Domain, ScriptId}}; use bevy::{ecs::entity::Entity, prelude::Event, reflect::Reflect}; /// An error coming from a script @@ -122,6 +122,8 @@ pub enum Recipients { Script(ScriptId), /// The event is to be handled by all the scripts on the specified entity Entity(Entity), + /// The event is to be handled by a specific domain + Domain(Domain), /// The event is to be handled by all the scripts of one language Language(crate::asset::Language), } diff --git a/crates/bevy_mod_scripting_core/src/extractors.rs b/crates/bevy_mod_scripting_core/src/extractors.rs index d105cca7b5..1a82ae97ee 100644 --- a/crates/bevy_mod_scripting_core/src/extractors.rs +++ b/crates/bevy_mod_scripting_core/src/extractors.rs @@ -4,18 +4,23 @@ #![allow(deprecated)] use std::ops::{Deref, DerefMut}; -use bevy::ecs::{ - component::ComponentId, - entity::Entity, - event::{Event, EventCursor, EventIterator, Events}, - query::{Access, AccessConflicts}, - storage::SparseSetIndex, - system::{Local, Resource, SystemParam, SystemState}, - world::World, +use bevy::{ + asset::{LoadState, Handle}, + ecs::{ + component::ComponentId, + entity::Entity, + event::{Event, EventCursor, EventIterator, Events}, + query::{Access, AccessConflicts}, + storage::SparseSetIndex, + system::{Local, SystemParam, SystemState, SystemParamValidationError}, + world::World, + }, + prelude::{AssetServer, Query, Res, Resource}, }; use fixedbitset::FixedBitSet; use crate::{ + ScriptAsset, bindings::{ access_map::ReflectAccessId, pretty_print::DisplayWithWorld, script_value::ScriptValue, WorldAccessGuard, WorldGuard, @@ -25,7 +30,7 @@ use crate::{ event::{CallbackLabel, IntoCallbackLabel}, handler::CallbackSettings, runtime::RuntimeContainer, - script::{ScriptId, Scripts, StaticScripts}, + script::{ScriptId, StaticScripts, ScriptContext, DisplayProxy, ScriptContextProvider, Domain}, IntoScriptPluginParams, }; @@ -47,16 +52,24 @@ pub fn with_handler_system_state< o } -/// Semantics of [`bevy::ecs::change_detection::Res`] but doesn't claim read or write on the world by removing the resource from it ahead of time. +/// Semantics of [`bevy::ecs::change_detection::Res`] but doesn't claim read or +/// write on the world by removing the resource from it ahead of time. /// /// Similar to using [`World::resource_scope`]. /// -/// This is useful for interacting with scripts, since [`WithWorldGuard`] will ensure scripts cannot gain exclusive access to the world if *any* reads or writes -/// are claimed on the world. Removing the resource from the world lets you access it in the context of running scripts without blocking exclusive world access. +/// This is useful for interacting with scripts, since [`WithWorldGuard`] will +/// ensure scripts cannot gain exclusive access to the world if *any* reads or +/// writes are claimed on the world. Removing the resource from the world lets +/// you access it in the context of running scripts without blocking exclusive +/// world access. /// /// # Safety -/// - Because the resource is removed during the `get_param` call, if there is a conflicting resource access, this will be unsafe -/// - You must ensure you're only using this in combination with system parameters which will not read or write to this resource in `get_param` +/// +/// - Because the resource is removed during the `get_param` call, if there is a +/// conflicting resource access, this will be unsafe +/// +/// - You must ensure you're only using this in combination with system +/// parameters which will not read or write to this resource in `get_param` pub(crate) struct ResScope<'state, T: Resource + Default>(pub &'state mut T); impl Deref for ResScope<'_, T> { @@ -133,6 +146,7 @@ impl EventReaderScope<'_, T> { } } + /// Context for systems which handle events for scripts #[derive(SystemParam)] pub struct HandlerContext<'s, P: IntoScriptPluginParams> { @@ -140,15 +154,15 @@ pub struct HandlerContext<'s, P: IntoScriptPluginParams> { pub(crate) callback_settings: ResScope<'s, CallbackSettings

>, /// Settings for loading contexts pub(crate) context_loading_settings: ResScope<'s, ContextLoadingSettings

>, - /// Scripts - pub(crate) scripts: ResScope<'s, Scripts

>, /// The runtime container pub(crate) runtime_container: ResScope<'s, RuntimeContainer

>, /// List of static scripts pub(crate) static_scripts: ResScope<'s, StaticScripts>, + /// Script context + pub(crate) script_context: ResScope<'s, ScriptContext

>, } -impl HandlerContext<'_, P> { +impl<'s, P: IntoScriptPluginParams> HandlerContext<'s, P> { /// Splits the handler context into its individual components. /// /// Useful if you are needing multiple resources from the handler context. @@ -158,14 +172,12 @@ impl HandlerContext<'_, P> { ) -> ( &mut CallbackSettings

, &mut ContextLoadingSettings

, - &mut Scripts

, &mut RuntimeContainer

, &mut StaticScripts, ) { ( &mut self.callback_settings, &mut self.context_loading_settings, - &mut self.scripts, &mut self.runtime_container, &mut self.static_scripts, ) @@ -181,11 +193,6 @@ impl HandlerContext<'_, P> { &mut self.context_loading_settings } - /// Get the scripts - pub fn scripts(&mut self) -> &mut Scripts

{ - &mut self.scripts - } - /// Get the runtime container pub fn runtime_container(&mut self) -> &mut RuntimeContainer

{ &mut self.runtime_container @@ -196,24 +203,30 @@ impl HandlerContext<'_, P> { &mut self.static_scripts } - /// checks if the script is loaded such that it can be executed. - pub fn is_script_fully_loaded(&self, script_id: ScriptId) -> bool { - self.scripts.scripts.contains_key(&script_id) + /// Get the static scripts + pub fn script_context(&mut self) -> &mut ScriptContext

{ + &mut self.script_context } + // /// checks if the script is loaded such that it can be executed. + // pub fn is_script_fully_loaded(&self, script_id: ScriptId) -> bool { + // todo!() + // // matches!(self.asset_server.load_state(script_id), LoadState::Loaded) + // } + /// Equivalent to [`Self::call`] but with a dynamically passed in label pub fn call_dynamic_label( &self, label: &CallbackLabel, - script_id: &ScriptId, + script_id: &Handle, entity: Entity, + domain: &Option, payload: Vec, guard: WorldGuard<'_>, ) -> Result { // find script - let script = match self.scripts.scripts.get(script_id) { - Some(script) => script, - None => return Err(InteropError::missing_script(script_id.clone()).into()), + let Some(context) = self.script_context.get(Some(entity), &script_id.id(), domain) else { + return Err(InteropError::missing_context(script_id.clone()).into()); }; // call the script @@ -223,7 +236,7 @@ impl HandlerContext<'_, P> { .context_pre_handling_initializers; let runtime = &self.runtime_container.runtime; - let mut context = script.context.lock(); + let mut context = context.lock(); CallbackSettings::

::call( handler, @@ -244,12 +257,13 @@ impl HandlerContext<'_, P> { /// Run [`Self::is_script_fully_loaded`] before calling the script to ensure the script and context were loaded ahead of time. pub fn call( &self, - script_id: &ScriptId, + script_id: &Handle, entity: Entity, + domain: &Option, payload: Vec, guard: WorldGuard<'_>, ) -> Result { - self.call_dynamic_label(&C::into_callback_label(), script_id, entity, payload, guard) + self.call_dynamic_label(&C::into_callback_label(), script_id, entity, domain, payload, guard) } } @@ -365,7 +379,7 @@ unsafe impl SystemParam for WithWorldGuard<'_, '_, T> { state: &Self::State, system_meta: &bevy::ecs::system::SystemMeta, world: bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell, - ) -> bool { + ) -> Result<(), SystemParamValidationError> { T::validate_param(&state.0, system_meta, world) } } @@ -417,9 +431,10 @@ mod test { ecs::{ component::Component, event::{Event, EventReader}, - system::{Query, ResMut, Resource}, + system::{Query, ResMut}, world::FromWorld, }, + prelude::Resource, }; use test_utils::make_test_plugin; diff --git a/crates/bevy_mod_scripting_core/src/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs index 34f259c4c7..eeb82e031c 100644 --- a/crates/bevy_mod_scripting_core/src/handler.rs +++ b/crates/bevy_mod_scripting_core/src/handler.rs @@ -1,5 +1,6 @@ //! Contains the logic for handling script callback events use crate::{ + ScriptAsset, bindings::{ pretty_print::DisplayWithWorld, script_value::ScriptValue, ThreadWorldContainer, WorldContainer, WorldGuard, @@ -11,25 +12,27 @@ use crate::{ ScriptErrorEvent, }, extractors::{HandlerContext, WithWorldGuard}, - script::{ScriptComponent, ScriptId}, + script::{ScriptComponent, ScriptId, ScriptDomain, Domain, ScriptContextProvider}, IntoScriptPluginParams, }; use bevy::{ + asset::Handle, ecs::{ entity::Entity, query::QueryState, - system::{Local, Resource, SystemState}, + system::{Local, SystemState}, world::{Mut, World}, }, log::trace_once, - prelude::{Events, Ref}, + prelude::{Events, Ref, Resource}, + platform::collections::HashSet, }; /// A function that handles a callback event pub type HandlerFn

= fn( args: Vec, entity: Entity, - script_id: &ScriptId, + script_id: &Handle, callback: &CallbackLabel, context: &mut

::C, pre_handling_initializers: &[ContextPreHandlingInitializer

], @@ -71,7 +74,7 @@ impl CallbackSettings

{ handler: HandlerFn

, args: Vec, entity: Entity, - script_id: &ScriptId, + script_id: &Handle, callback: &CallbackLabel, script_ctxt: &mut P::C, pre_handling_initializers: &[ContextPreHandlingInitializer

], @@ -129,7 +132,7 @@ pub fn event_handler( #[allow(deprecated)] pub(crate) type EventHandlerSystemState<'w, 's, P> = SystemState<( - Local<'s, QueryState<(Entity, Ref<'w, ScriptComponent>)>>, + Local<'s, QueryState<(Entity, Ref<'w, ScriptComponent>, Option>)>>, crate::extractors::EventReaderScope<'s, ScriptCallbackEvent>, WithWorldGuard<'w, 's, HandlerContext<'s, P>>, )>; @@ -138,27 +141,27 @@ pub(crate) type EventHandlerSystemState<'w, 's, P> = SystemState<( #[allow(deprecated)] pub(crate) fn event_handler_inner( callback_label: CallbackLabel, - mut entity_query_state: Local)>>, + mut entity_query_state: Local, Option>)>>, mut script_events: crate::extractors::EventReaderScope, mut handler_ctxt: WithWorldGuard>, ) { + const NO_ENTITY: Entity = Entity::from_raw(0); let (guard, handler_ctxt) = handler_ctxt.get_mut(); let mut errors = Vec::default(); - let events = script_events.read().cloned().collect::>(); // query entities + chain static scripts let entity_and_static_scripts = guard.with_global_access(|world| { entity_query_state .iter(world) - .map(|(e, s)| (e, s.0.clone())) + .map(|(e, s, d)| (e, s.0.clone(), d.map(|x| x.0.clone()))) .chain( handler_ctxt .static_scripts .scripts .iter() - .map(|s| (Entity::from_raw(0), vec![s.clone()])), + .map(|s| (NO_ENTITY, vec![s.clone()], None)), ) .collect::>() }); @@ -166,7 +169,7 @@ pub(crate) fn event_handler_inner( let entity_and_static_scripts = match entity_and_static_scripts { Ok(entity_and_static_scripts) => entity_and_static_scripts, Err(e) => { - bevy::log::error!( + bevy::log::error_once!( "{}: Failed to query entities with scripts: {}", P::LANGUAGE, e.display_with_world(guard.clone()) @@ -175,12 +178,23 @@ pub(crate) fn event_handler_inner( } }; + // Keep track of the contexts that have been called. Don't duplicate the + // calls on account of multiple matches. + // + // If I have five scripts all in one shared context, and I fire a call to + // `Recipients::All`, then I want that call to go to the shared context + // once. + // + // If I have four scripts in three different contexts, and I fire a call to + // `Recipients::All`, then I want that call to be evaluated three times, + // once in each context. + let mut called_contexts: HashSet = HashSet::new(); for event in events.into_iter().filter(|e| e.label == callback_label) { - for (entity, entity_scripts) in entity_and_static_scripts.iter() { + for (entity, entity_scripts, domain) in entity_and_static_scripts.iter() { for script_id in entity_scripts.iter() { match &event.recipients { crate::event::Recipients::Script(target_script_id) - if target_script_id != script_id => + if *target_script_id != script_id.id() => { continue } @@ -192,13 +206,30 @@ pub(crate) fn event_handler_inner( { continue } - _ => {} + crate::event::Recipients::Domain(target_domain) + if domain.as_ref().map(|x| *x != *target_domain).unwrap_or(false) => + { + continue + } + crate::event::Recipients::All => (), + _ => () + + } + let context_hash = handler_ctxt.script_context.hash((*entity != NO_ENTITY).then_some(*entity), + &script_id.id(), + domain); + if let Some(hash) = context_hash { + if !called_contexts.insert(hash) { + // Only call a context once, not once per script. + continue; + } } let call_result = handler_ctxt.call_dynamic_label( &callback_label, - script_id, + &script_id, *entity, + domain, event.args.clone(), guard.clone(), ); @@ -208,7 +239,7 @@ pub(crate) fn event_handler_inner( guard.clone(), ScriptCallbackResponseEvent::new( callback_label.clone(), - script_id.clone(), + script_id.id(), call_result.clone(), ), ); @@ -219,11 +250,19 @@ pub(crate) fn event_handler_inner( Err(e) => { match e.downcast_interop_inner() { Some(InteropErrorInner::MissingScript { script_id }) => { - trace_once!( - "{}: Script `{}` on entity `{:?}` is either still loading, doesn't exist, or is for another language, ignoring until the corresponding script is loaded.", - P::LANGUAGE, - script_id, entity - ); + if let Some(path) = script_id.path() { + trace_once!( + "{}: Script path `{}` on entity `{:?}` is either still loading, doesn't exist, or is for another language, ignoring until the corresponding script is loaded.", + P::LANGUAGE, + path, entity + ); + } else { + trace_once!( + "{}: Script id `{}` on entity `{:?}` is either still loading, doesn't exist, or is for another language, ignoring until the corresponding script is loaded.", + P::LANGUAGE, + script_id.id(), entity + ); + } continue; } Some(InteropErrorInner::MissingContext { .. }) => { @@ -234,9 +273,15 @@ pub(crate) fn event_handler_inner( } _ => {} } - let e = e - .with_script(script_id.clone()) - .with_context(format!("Event handling for: Language: {}", P::LANGUAGE)); + let e = { + + if let Some(path) = script_id.path() { + e.with_script(path) + } else { + e + } + .with_context(format!("Event handling for: Language: {}", P::LANGUAGE)) + }; push_err_and_continue!(errors, Err(e)); } }; diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 15faf31ff4..ce4f3eb0e4 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -5,7 +5,7 @@ use crate::event::ScriptErrorEvent; use asset::{ configure_asset_systems, configure_asset_systems_for_plugin, Language, ScriptAsset, - ScriptAssetLoader, ScriptAssetSettings, + ScriptAssetLoader, //ScriptAssetSettings, }; use bevy::prelude::*; use bindings::{ @@ -22,7 +22,7 @@ use error::ScriptError; use event::{ScriptCallbackEvent, ScriptCallbackResponseEvent}; use handler::{CallbackSettings, HandlerFn}; use runtime::{initialize_runtime, Runtime, RuntimeContainer, RuntimeInitializer, RuntimeSettings}; -use script::{ScriptComponent, ScriptId, Scripts, StaticScripts}; +use script::{ScriptComponent, ScriptId, StaticScripts, ScriptContext, EntityContext}; pub mod asset; pub mod bindings; @@ -129,7 +129,12 @@ impl Plugin for ScriptingPlugin

{ context_initializers: self.context_initializers.clone(), context_pre_handling_initializers: self.context_pre_handling_initializers.clone(), }) - .init_resource::>(); + .insert_resource( + if self.context_assignment_strategy.is_global() { + ScriptContext::

::shared() + } else { + ScriptContext::

::per_entity() + }); register_script_plugin_systems::

(app); @@ -277,17 +282,17 @@ impl Plugin for BMSScriptingInfrastructurePlugin { fn finish(&self, app: &mut App) { // read extensions from asset settings - let asset_settings_extensions = app - .world_mut() - .get_resource_or_init::() - .supported_extensions; - - // convert extensions to static array - bevy::log::info!( - "Initializing BMS with Supported extensions: {:?}", - asset_settings_extensions - ); - + // let asset_settings_extensions = app + // .world_mut() + // // .get_resource_or_init::() + // .supported_extensions; + + // // convert extensions to static array + // bevy::log::info!( + // "Initializing BMS with Supported extensions: {:?}", + // asset_settings_extensions + // ); + let asset_settings_extensions = &["lua", "luau", "rhai", "rn"]; app.register_asset_loader(ScriptAssetLoader { extensions: asset_settings_extensions, preprocessor: None, @@ -353,21 +358,21 @@ pub trait ManageStaticScripts { /// Registers a script id as a static script. /// /// Event handlers will run these scripts on top of the entity scripts. - fn add_static_script(&mut self, script_id: impl Into) -> &mut Self; + fn add_static_script(&mut self, script_id: impl Into>) -> &mut Self; /// Removes a script id from the list of static scripts. /// /// Does nothing if the script id is not in the list. - fn remove_static_script(&mut self, script_id: impl Into) -> &mut Self; + fn remove_static_script(&mut self, script_id: impl Into>) -> &mut Self; } impl ManageStaticScripts for App { - fn add_static_script(&mut self, script_id: impl Into) -> &mut Self { + fn add_static_script(&mut self, script_id: impl Into>) -> &mut Self { AddStaticScript::new(script_id.into()).apply(self.world_mut()); self } - fn remove_static_script(&mut self, script_id: impl Into) -> &mut Self { + fn remove_static_script(&mut self, script_id: impl Into>) -> &mut Self { RemoveStaticScript::new(script_id.into()).apply(self.world_mut()); self } @@ -394,24 +399,25 @@ impl ConfigureScriptAssetSettings for App { extensions: &[&'static str], language: Language, ) -> &mut Self { - let mut asset_settings = self - .world_mut() - .get_resource_or_init::(); + todo!() + // let mut asset_settings = self + // .world_mut() + // .get_resource_or_init::(); - let mut new_arr = Vec::from(asset_settings.supported_extensions); + // let mut new_arr = Vec::from(asset_settings.supported_extensions); - new_arr.extend(extensions); + // new_arr.extend(extensions); - let new_arr_static = Vec::leak(new_arr); + // let new_arr_static = Vec::leak(new_arr); - asset_settings.supported_extensions = new_arr_static; - for extension in extensions { - asset_settings - .extension_to_language_map - .insert(*extension, language.clone()); - } + // asset_settings.supported_extensions = new_arr_static; + // for extension in extensions { + // asset_settings + // .extension_to_language_map + // .insert(*extension, language.clone()); + // } - self + // self } } diff --git a/crates/bevy_mod_scripting_core/src/reflection_extensions.rs b/crates/bevy_mod_scripting_core/src/reflection_extensions.rs index e923c33b3f..1ff32fb425 100644 --- a/crates/bevy_mod_scripting_core/src/reflection_extensions.rs +++ b/crates/bevy_mod_scripting_core/src/reflection_extensions.rs @@ -4,11 +4,14 @@ use crate::{ bindings::{ReflectReference, WorldGuard}, error::InteropError, }; -use bevy::reflect::{PartialReflect, Reflect, ReflectFromReflect, ReflectMut, TypeInfo}; +use bevy::reflect::{ + PartialReflect, Reflect, ReflectFromReflect, ReflectMut, TypeInfo, +}; use std::{ any::{Any, TypeId}, cmp::max, }; + /// Extension trait for [`PartialReflect`] providing additional functionality for working with specific types. pub trait PartialReflectExt { /// Try to get a reference to the given key in an underyling map, if the type is a map. @@ -30,7 +33,7 @@ pub trait PartialReflectExt { fn from_reflect_or_clone( reflect: &dyn PartialReflect, world: WorldGuard, - ) -> Box; + ) -> Result, InteropError>; /// Allocate a new boxed reflect reference from a boxed reflect. fn allocate(boxed: Box, world: WorldGuard) -> ReflectReference; @@ -424,11 +427,19 @@ impl PartialReflectExt for T { fn from_reflect_or_clone( reflect: &dyn PartialReflect, world: WorldGuard, - ) -> Box { + ) -> Result, InteropError> { // try from reflect match ::from_reflect(reflect, world.clone()) { - Ok(v) => v.into_partial_reflect(), - Err(_) => reflect.clone_value(), + Ok(v) => Ok(v.into_partial_reflect()), + Err(_) => reflect + .reflect_clone() + .map(|v| v.into_partial_reflect()) + .map_err(|e| { + InteropError::failed_from_reflect( + reflect.get_represented_type_info().map(|ti| ti.type_id()), + e.to_string(), + ) + }), } } @@ -667,7 +678,7 @@ mod test { let mut map = std::collections::HashMap::>::default(); let value = DynamicMap::from_iter(vec![(1, 2), (2, 3), (3, 4)]); - let value_ref: Box = Box::new(value.clone_dynamic()); + let value_ref: Box = Box::new(value.to_dynamic_map()); map.insert(1, std::collections::HashMap::::default()); map.insert(2, std::collections::HashMap::::default()); map.insert(3, std::collections::HashMap::::default()); diff --git a/crates/bevy_mod_scripting_core/src/runtime.rs b/crates/bevy_mod_scripting_core/src/runtime.rs index 359a7a0c97..0f69158fed 100644 --- a/crates/bevy_mod_scripting_core/src/runtime.rs +++ b/crates/bevy_mod_scripting_core/src/runtime.rs @@ -3,8 +3,8 @@ use crate::{error::ScriptError, IntoScriptPluginParams}; use bevy::{ - ecs::system::{ResMut, Resource}, - prelude::Res, + ecs::system::{ResMut}, + prelude::{Res, Resource}, }; /// A trait that all script runtimes must implement. diff --git a/crates/bevy_mod_scripting_core/src/script.rs b/crates/bevy_mod_scripting_core/src/script.rs deleted file mode 100644 index d5b0fe46ae..0000000000 --- a/crates/bevy_mod_scripting_core/src/script.rs +++ /dev/null @@ -1,171 +0,0 @@ -//! Script related types, functions and components - -use crate::{asset::ScriptAsset, IntoScriptPluginParams}; -use bevy::prelude::ReflectComponent; -use bevy::{asset::Handle, ecs::system::Resource, reflect::Reflect, utils::HashSet}; -use parking_lot::Mutex; -use std::{borrow::Cow, collections::HashMap, ops::Deref, sync::Arc}; - -/// A unique identifier for a script, by default corresponds to the path of the asset excluding the asset source. -/// -/// I.e. an asset with the path `path/to/asset.ext` will have the script id `path/to/asset.ext` -pub type ScriptId = Cow<'static, str>; - -#[derive(bevy::ecs::component::Component, Reflect, Clone)] -#[reflect(Component)] -/// A component which identifies the scripts existing on an entity. -/// -/// Event handlers search for components with this component to figure out which scripts to run and on which entities. -pub struct ScriptComponent(pub Vec); - -impl Deref for ScriptComponent { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl ScriptComponent { - /// Creates a new [`ScriptComponent`] with the given ScriptID's - pub fn new, I: IntoIterator>(components: I) -> Self { - Self(components.into_iter().map(Into::into).collect()) - } -} - -/// All the scripts which are currently loaded or loading and their mapping to contexts -#[derive(Resource)] -pub struct Scripts { - pub(crate) scripts: HashMap>, -} - -#[profiling::all_functions] -impl Scripts

{ - /// Inserts a script into the collection - pub fn insert(&mut self, script: Script

) { - self.scripts.insert(script.id.clone(), script); - } - - /// Removes a script from the collection, returning `true` if the script was in the collection, `false` otherwise - pub fn remove>(&mut self, script: S) -> bool { - self.scripts.remove(&script.into()).is_some() - } - - /// Checks if a script is in the collection - /// Returns `true` if the script is in the collection, `false` otherwise - pub fn contains>(&self, script: S) -> bool { - self.scripts.contains_key(&script.into()) - } - - /// Returns a reference to the script with the given id - pub fn get>(&self, script: S) -> Option<&Script

> { - self.scripts.get(&script.into()) - } - - /// Returns a mutable reference to the script with the given id - pub fn get_mut>(&mut self, script: S) -> Option<&mut Script

> { - self.scripts.get_mut(&script.into()) - } - - /// Returns an iterator over the scripts - pub fn iter(&self) -> impl Iterator> { - self.scripts.values() - } - - /// Returns a mutable iterator over the scripts - pub fn iter_mut(&mut self) -> impl Iterator> { - self.scripts.values_mut() - } -} - -impl Default for Scripts

{ - fn default() -> Self { - Self { - scripts: Default::default(), - } - } -} - -/// A script -pub struct Script { - /// The id of the script - pub id: ScriptId, - /// the asset holding the content of the script if it comes from an asset - pub asset: Option>, - /// The context of the script, possibly shared with other scripts - pub context: Arc>, -} - -impl Clone for Script

{ - fn clone(&self) -> Self { - Self { - id: self.id.clone(), - asset: self.asset.clone(), - context: self.context.clone(), - } - } -} - -/// A collection of scripts, not associated with any entity. -/// -/// Useful for `global` or `static` scripts which operate over a larger scope than a single entity. -#[derive(Default, Resource)] -pub struct StaticScripts { - pub(crate) scripts: HashSet, -} - -#[profiling::all_functions] -impl StaticScripts { - /// Inserts a static script into the collection - pub fn insert>(&mut self, script: S) { - self.scripts.insert(script.into()); - } - - /// Removes a static script from the collection, returning `true` if the script was in the collection, `false` otherwise - pub fn remove>(&mut self, script: S) -> bool { - self.scripts.remove(&script.into()) - } - - /// Checks if a static script is in the collection - /// Returns `true` if the script is in the collection, `false` otherwise - pub fn contains>(&self, script: S) -> bool { - self.scripts.contains(&script.into()) - } - - /// Returns an iterator over the static scripts - pub fn iter(&self) -> impl Iterator { - self.scripts.iter() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn static_scripts_insert() { - let mut static_scripts = StaticScripts::default(); - static_scripts.insert("script1"); - assert_eq!(static_scripts.scripts.len(), 1); - assert!(static_scripts.scripts.contains("script1")); - } - - #[test] - fn static_scripts_remove() { - let mut static_scripts = StaticScripts::default(); - static_scripts.insert("script1"); - assert_eq!(static_scripts.scripts.len(), 1); - assert!(static_scripts.scripts.contains("script1")); - assert!(static_scripts.remove("script1")); - assert_eq!(static_scripts.scripts.len(), 0); - assert!(!static_scripts.scripts.contains("script1")); - } - - #[test] - fn static_scripts_contains() { - let mut static_scripts = StaticScripts::default(); - static_scripts.insert("script1"); - assert!(static_scripts.contains("script1")); - assert!(!static_scripts.contains("script2")); - } -} diff --git a/crates/bevy_mod_scripting_core/src/script/domain_context.rs b/crates/bevy_mod_scripting_core/src/script/domain_context.rs new file mode 100644 index 0000000000..f6e2a8148b --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/script/domain_context.rs @@ -0,0 +1,34 @@ +use super::*; + +/// Stores the script context by entity. +pub struct DomainContext(HashMap, Arc>>); + +impl Default for DomainContext

{ + fn default() -> Self { + Self(HashMap::new()) + } +} + +impl ScriptContextProvider

for DomainContext

{ + fn hash(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option { + domain.as_ref().map(|d| { + let mut hasher = FixedState::default().build_hasher(); + d.hash(&mut hasher); + hasher.finish() + }) + } + fn get(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option<&Arc>> { + domain.as_ref().and_then(|id| self.0.get(id)) + } + fn insert(&mut self, id: Option, script_id: &ScriptId, domain: &Option, context: P::C) -> Result<(), P::C> { + if let Some(id) = domain { + self.0.insert(id.clone(), Arc::new(Mutex::new(context))); + Ok(()) + } else { + Err(context) + } + } + fn contains(&self, id: Option, script_id: &ScriptId, domain: &Option) -> bool { + domain.as_ref().map(|id| self.0.contains_key(id)).unwrap_or(false) + } +} diff --git a/crates/bevy_mod_scripting_core/src/script/entity_context.rs b/crates/bevy_mod_scripting_core/src/script/entity_context.rs new file mode 100644 index 0000000000..c87c384ab3 --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/script/entity_context.rs @@ -0,0 +1,34 @@ +use super::*; + +/// Stores the script context by entity. +pub struct EntityContext(HashMap>>); + +impl Default for EntityContext

{ + fn default() -> Self { + Self(HashMap::new()) + } +} + +impl ScriptContextProvider

for EntityContext

{ + fn hash(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option { + id.map(|id| { + let mut hasher = FixedState::default().build_hasher(); + id.hash(&mut hasher); + hasher.finish() + }) + } + fn get(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option<&Arc>> { + id.and_then(|id| self.0.get(&id)) + } + fn insert(&mut self, id: Option, script_id: &ScriptId, domain: &Option, context: P::C) -> Result<(), P::C> { + if let Some(id) = id { + self.0.insert(id, Arc::new(Mutex::new(context))); + Ok(()) + } else { + Err(context) + } + } + fn contains(&self, id: Option, script_id: &ScriptId, domain: &Option) -> bool { + id.map(|id| self.0.contains_key(&id)).unwrap_or(false) + } +} diff --git a/crates/bevy_mod_scripting_core/src/script/mod.rs b/crates/bevy_mod_scripting_core/src/script/mod.rs new file mode 100644 index 0000000000..7ebc688b2a --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/script/mod.rs @@ -0,0 +1,159 @@ +//! Script related types, functions and components + +use crate::{asset::ScriptAsset, IntoScriptPluginParams}; +use bevy::prelude::{Component, ReflectComponent, Deref, DerefMut, Entity, Resource}; +use bevy::{asset::{Asset, AssetId, Handle}, + reflect::Reflect, +}; +use parking_lot::Mutex; +use std::{borrow::Cow, collections::HashMap, ops::Deref, sync::Arc, fmt, hash::{Hash, Hasher, BuildHasher}}; +use bevy::platform::{ + collections::HashSet, + hash::FixedState, +}; + +mod script_context; +mod shared_context; +mod entity_context; +mod domain_context; +mod scriptid_context; +pub use script_context::*; +pub use shared_context::*; +pub use entity_context::*; +pub use domain_context::*; +pub use scriptid_context::*; + +/// A unique identifier for a script, by default corresponds to the path of the asset excluding the asset source. +/// +/// I.e. an asset with the path `path/to/asset.ext` will have the script id `path/to/asset.ext` +pub type ScriptId = AssetId; + +/// Display the path of a script or its asset ID. +#[doc(hidden)] +pub struct HandleDisplay<'a, T: Asset>(&'a Handle); + +impl<'a, A: Asset> fmt::Display for HandleDisplay<'a, A> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(path) = self.0.path() { + write!(f, "path {}", path) + } else { + write!(f, "id {}", self.0.id()) + } + } +} + +impl<'a, A: Asset> fmt::Debug for HandleDisplay<'a, A> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(path) = self.0.path() { + write!(f, "path {:?}", path) + } else { + write!(f, "id {:?}", self.0.id()) + } + } +} + +/// Make a type display-able. +pub trait DisplayProxy { + /// The type that does the displaying. + type D<'a>: fmt::Display + fmt::Debug where Self: 'a; + /// Return a display-able reference. + fn display<'a>(&'a self) -> Self::D<'a>; +} + +impl DisplayProxy for Handle { + type D<'a> = HandleDisplay<'a, A>; + + fn display<'a>(&'a self) -> Self::D<'a> { + HandleDisplay(self) + } +} + +/// Defines the domain of a script +#[derive(Component)] +pub struct ScriptDomain(pub Domain); + +#[derive(bevy::ecs::component::Component, Reflect, Clone)] +#[reflect(Component)] +/// A component which identifies the scripts existing on an entity. +/// +/// Event handlers search for components with this component to figure out which scripts to run and on which entities. +pub struct ScriptComponent(pub Vec>); + +impl Deref for ScriptComponent { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl ScriptComponent { + /// Creates a new [`ScriptComponent`] with the given ScriptID's + pub fn new>, I: IntoIterator>(components: I) -> Self { + Self(components.into_iter().map(Into::into).collect()) + } +} + +/// A collection of scripts, not associated with any entity. +/// +/// Useful for `global` or `static` scripts which operate over a larger scope than a single entity. +#[derive(Default, Resource)] +pub struct StaticScripts { + pub(crate) scripts: HashSet>, +} + +#[profiling::all_functions] +impl StaticScripts { + /// Inserts a static script into the collection + pub fn insert>>(&mut self, script: S) { + self.scripts.insert(script.into()); + } + + /// Removes a static script from the collection, returning `true` if the script was in the collection, `false` otherwise + pub fn remove(&mut self, script_id: &ScriptId) -> bool { + self.scripts.extract_if(|handle| handle.id() == *script_id).next().is_some() + } + + /// Checks if a static script is in the collection + /// Returns `true` if the script is in the collection, `false` otherwise + pub fn contains(&self, script_id: &ScriptId) -> bool { + self.scripts.iter().any(|handle| handle.id() == *script_id) + } + + /// Returns an iterator over the static scripts + pub fn iter(&self) -> impl Iterator> { + self.scripts.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn static_scripts_insert() { + let mut static_scripts = StaticScripts::default(); + static_scripts.insert("script1"); + assert_eq!(static_scripts.scripts.len(), 1); + assert!(static_scripts.scripts.contains("script1")); + } + + #[test] + fn static_scripts_remove() { + let mut static_scripts = StaticScripts::default(); + static_scripts.insert("script1"); + assert_eq!(static_scripts.scripts.len(), 1); + assert!(static_scripts.scripts.contains("script1")); + assert!(static_scripts.remove("script1")); + assert_eq!(static_scripts.scripts.len(), 0); + assert!(!static_scripts.scripts.contains("script1")); + } + + #[test] + fn static_scripts_contains() { + let mut static_scripts = StaticScripts::default(); + static_scripts.insert("script1"); + assert!(static_scripts.contains("script1")); + assert!(!static_scripts.contains("script2")); + } +} diff --git a/crates/bevy_mod_scripting_core/src/script/script_context.rs b/crates/bevy_mod_scripting_core/src/script/script_context.rs new file mode 100644 index 0000000000..7ba396081d --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/script/script_context.rs @@ -0,0 +1,128 @@ +use super::*; + +use crate::{asset::ScriptAsset, IntoScriptPluginParams}; +use bevy::prelude::{Component, ReflectComponent, Deref, DerefMut, Entity, Resource}; +use bevy::{asset::{Asset, AssetId, Handle}, reflect::Reflect}; +use parking_lot::Mutex; +use std::{borrow::Cow, collections::HashMap, ops::Deref, sync::Arc, fmt, hash::{Hash, Hasher, BuildHasher}}; +use bevy::platform::{ + collections::HashSet, + hash::FixedState, +}; +/// A kind of catch all type for script context selection +/// +/// I believe this is what the original ScriptId was intended to be. +pub type Domain = Cow<'static, str>; + +/// A generic script context provider +pub trait ScriptContextProvider { + /// Get the context. + fn get(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option<&Arc>>; + /// Insert a context. + /// + /// If the context cannot be inserted, it is returned as an `Err`. + fn insert(&mut self, id: Option, script_id: &ScriptId, domain: &Option, context: P::C) -> Result<(), P::C>; + /// Returns true if there is a context. + fn contains(&self, id: Option, script_id: &ScriptId, domain: &Option) -> bool; + /// Hash for context. + /// + /// Useful for tracking what context will be returned by `get()` without + /// requiring that `P::C` impl `Hash` and cheaper too. + fn hash(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option; +} + +#[derive(Resource)] +/// Keeps track of script contexts +pub enum ScriptContext { + /// One shared script context + Shared(SharedContext

), + /// One script context per entity + /// + /// Stores context by entity with a shared context as a last resort when no + /// entity is provided. + /// + /// TODO: Should check for entity despawns and remove from this + /// EntityContext. + Entity(EntityContext

, SharedContext

), + /// Domain contexts or shared context + /// + /// Stores context by domain with a shared context as a last resort when no + /// domain is provided. + Domain(DomainContext

, SharedContext

), + /// A script context per script + ScriptId(ScriptIdContext

), + // NOTE: We could also have the following which would support domains; + // failing that entities; failing that a shared context. + // DomainEntity(DomainContext

, EntityContext

, SharedContext

), +} + +impl ScriptContext

{ + /// Use a shared script context + pub fn shared() -> Self { + Self::Shared(SharedContext::default()) + } + /// Domain contexts or a shared context + pub fn with_domains() -> Self { + Self::Domain(DomainContext::default(), SharedContext::default()) + } + /// Use one script ontext per entity + pub fn per_entity() -> Self { + Self::Entity(EntityContext::default(), SharedContext::default()) + } + /// Use one script ontext per entity + pub fn per_script() -> Self { + Self::ScriptId(ScriptIdContext::default()) + } +} + +impl Default for ScriptContext

{ + fn default() -> Self { + Self::shared() + } +} + +impl ScriptContextProvider

for ScriptContext

{ + fn get(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option<&Arc>> { + match self { + ScriptContext::Shared(a) => a.get(id, script_id, domain), + ScriptContext::ScriptId(a) => a.get(id, script_id, domain), + ScriptContext::Entity(a, b) => a.get(id, script_id, domain) + .or_else(|| b.get(id, script_id, domain)), + ScriptContext::Domain(a, b) => a.get(id, script_id, domain) + .or_else(|| b.get(id, script_id, domain)), + } + } + fn insert(&mut self, id: Option, script_id: &ScriptId, domain: &Option, context: P::C) -> Result<(), P::C> { + match self { + ScriptContext::Shared(a) => a.insert(id, script_id, domain, context), + ScriptContext::ScriptId(a) => a.insert(id, script_id, domain, context), + ScriptContext::Entity(a, b) => match a.insert(id, script_id, domain, context) { + Ok(()) => Ok(()), + Err(context) => b.insert(id, script_id, domain, context) + } + ScriptContext::Domain(a, b) => match a.insert(id, script_id, domain, context) { + Ok(()) => Ok(()), + Err(context) => b.insert(id, script_id, domain, context) + } + } + } + fn contains(&self, id: Option, script_id: &ScriptId, domain: &Option) -> bool { + match self { + ScriptContext::Shared(a) => a.contains(id, script_id, domain), + ScriptContext::ScriptId(a) => a.contains(id, script_id, domain), + ScriptContext::Entity(a, b) => a.contains(id, script_id, domain) || b.contains(id, script_id, domain), + ScriptContext::Domain(a, b) => a.contains(id, script_id, domain) || b.contains(id, script_id, domain), + } + } + fn hash(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option { + match self { + ScriptContext::Shared(a) => a.hash(id, script_id, domain), + ScriptContext::ScriptId(a) => a.hash(id, script_id, domain), + ScriptContext::Entity(a, b) => a.hash(id, script_id, domain) + .or_else(|| b.hash(id, script_id, domain)), + ScriptContext::Domain(a, b) => a.hash(id, script_id, domain) + .or_else(|| b.hash(id, script_id, domain)), + } + } +} + diff --git a/crates/bevy_mod_scripting_core/src/script/scriptid_context.rs b/crates/bevy_mod_scripting_core/src/script/scriptid_context.rs new file mode 100644 index 0000000000..9b342f1fae --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/script/scriptid_context.rs @@ -0,0 +1,28 @@ +use super::*; + +/// Stores the script context by entity. +pub struct ScriptIdContext(HashMap>>); + +impl Default for ScriptIdContext

{ + fn default() -> Self { + Self(HashMap::new()) + } +} + +impl ScriptContextProvider

for ScriptIdContext

{ + fn hash(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option { + let mut hasher = FixedState::default().build_hasher(); + script_id.hash(&mut hasher); + Some(hasher.finish()) + } + fn get(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option<&Arc>> { + self.0.get(script_id) + } + fn insert(&mut self, id: Option, script_id: &ScriptId, domain: &Option, context: P::C) -> Result<(), P::C> { + self.0.insert(script_id.clone(), Arc::new(Mutex::new(context))); + Ok(()) + } + fn contains(&self, id: Option, script_id: &ScriptId, domain: &Option) -> bool { + self.0.contains_key(&script_id) + } +} diff --git a/crates/bevy_mod_scripting_core/src/script/shared_context.rs b/crates/bevy_mod_scripting_core/src/script/shared_context.rs new file mode 100644 index 0000000000..b47c099f7a --- /dev/null +++ b/crates/bevy_mod_scripting_core/src/script/shared_context.rs @@ -0,0 +1,27 @@ +use super::*; + +/// Contains the shared context. +pub struct SharedContext(pub Option>>); + +impl ScriptContextProvider

for SharedContext

{ + fn hash(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option { + self.0.is_some().then_some(0) + } + + fn get(&self, id: Option, script_id: &ScriptId, domain: &Option) -> Option<&Arc>> { + self.0.as_ref() + } + fn insert(&mut self, id: Option, script_id: &ScriptId, domain: &Option, context: P::C) -> Result<(), P::C> { + self.0 = Some(Arc::new(Mutex::new(context))); + Ok(()) + } + fn contains(&self, id: Option, script_id: &ScriptId, domain: &Option) -> bool { + self.0.is_some() + } +} + +impl Default for SharedContext

{ + fn default() -> Self { + Self(None) + } +} diff --git a/crates/bevy_mod_scripting_derive/Cargo.toml b/crates/bevy_mod_scripting_derive/Cargo.toml index 0f867cbf67..cd77066a16 100644 --- a/crates/bevy_mod_scripting_derive/Cargo.toml +++ b/crates/bevy_mod_scripting_derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_derive" -version = "0.12.0" +version = "0.13.0" edition = "2021" authors = ["Maksymilian Mozolewski "] license = "MIT OR Apache-2.0" diff --git a/crates/bevy_mod_scripting_derive/src/derive/mod.rs b/crates/bevy_mod_scripting_derive/src/derive/mod.rs index b0363a2455..b2b1171376 100644 --- a/crates/bevy_mod_scripting_derive/src/derive/mod.rs +++ b/crates/bevy_mod_scripting_derive/src/derive/mod.rs @@ -9,8 +9,8 @@ use quote::{quote_spanned, ToTokens}; use syn::{Ident, ImplItemFn, ItemImpl}; pub use self::{ - get_type_dependencies::get_type_dependencies, into_script::into_script, - script_bindings::script_bindings, script_globals::script_globals, typed_through::typed_through, + get_type_dependencies::get_type_dependencies, into_script::into_script, + script_bindings::script_bindings, script_globals::script_globals, typed_through::typed_through, }; pub(crate) fn impl_fn_to_namespace_builder_registration(fun: &ImplItemFn) -> TokenStream { diff --git a/crates/bevy_mod_scripting_functions/Cargo.toml b/crates/bevy_mod_scripting_functions/Cargo.toml index e2ad1971a3..18433d6297 100644 --- a/crates/bevy_mod_scripting_functions/Cargo.toml +++ b/crates/bevy_mod_scripting_functions/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_functions" -version = "0.12.0" +version = "0.13.0" edition = "2021" authors = ["Maksymilian Mozolewski "] license = "MIT OR Apache-2.0" @@ -28,13 +28,16 @@ rhai_bindings = ["bevy_mod_scripting_rhai"] [dependencies] bevy = { workspace = true } +bevy_math = { workspace = true, features = ["curve"]} +bevy_reflect = { workspace = true, features = ["smol_str"]} +bevy_input = { workspace = true, features = ["smol_str"]} profiling = { workspace = true } uuid = "1.11" smol_str = "0.2.2" bevy_mod_scripting_core = { workspace = true } bevy_mod_scripting_derive = { workspace = true } -bevy_mod_scripting_lua = { path = "../languages/bevy_mod_scripting_lua", optional = true, version = "0.12.0" } -bevy_mod_scripting_rhai = { path = "../languages/bevy_mod_scripting_rhai", optional = true, version = "0.12.0" } +bevy_mod_scripting_lua = { path = "../languages/bevy_mod_scripting_lua", optional = true, version = "0.13.0" } +bevy_mod_scripting_rhai = { path = "../languages/bevy_mod_scripting_rhai", optional = true, version = "0.13.0" } bevy_system_reflection = { path = "../bevy_system_reflection", version = "0.1.1" } [lints] diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs index 2d2e6ee754..f6466929c0 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_core.rs @@ -2,15 +2,8 @@ #![allow(clippy::all)] #![allow(unused, deprecated, dead_code)] #![cfg_attr(rustfmt, rustfmt_skip)] -use bevy_mod_scripting_core::bindings::{ - ReflectReference, - function::{ - from::{Ref, Mut, Val}, - namespace::NamespaceBuilder, - }, -}; +use bevy_mod_scripting_core::bindings::function::from::{Ref, Val}; use bevy_mod_scripting_derive::script_bindings; -use crate::*; pub struct BevyCoreScriptingPlugin; #[script_bindings( remote, @@ -18,20 +11,20 @@ pub struct BevyCoreScriptingPlugin; bms_core_path = "bevy_mod_scripting_core", generated )] -impl bevy::core::prelude::Name { - fn clone(_self: Ref) -> Val { - let output: Val = ::clone( +impl bevy::prelude::Name { + fn clone(_self: Ref) -> Val { + let output: Val = ::clone( &_self, ) .into(); output } fn eq( - _self: Ref, - other: Ref, + _self: Ref, + other: Ref, ) -> bool { - let output: bool = >::eq(&_self, &other) .into(); output diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs index 529c8c7f5d..373bff58bc 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_hierarchy.rs @@ -18,14 +18,14 @@ pub struct BevyHierarchyScriptingPlugin; bms_core_path = "bevy_mod_scripting_core", generated )] -impl bevy::hierarchy::prelude::Children { +impl bevy::prelude::Children { /// Swaps the child at `a_index` with the child at `b_index`. fn swap( - mut _self: Mut, + mut _self: Mut, a_index: usize, b_index: usize, ) -> () { - let output: () = bevy::hierarchy::prelude::Children::swap( + let output: () = bevy::prelude::Children::swap( &mut _self, a_index, b_index, @@ -40,74 +40,39 @@ impl bevy::hierarchy::prelude::Children { bms_core_path = "bevy_mod_scripting_core", generated )] -impl bevy::hierarchy::prelude::Parent { - fn assert_receiver_is_total_eq(_self: Ref) -> () { - let output: () = ::assert_receiver_is_total_eq( +impl bevy::prelude::ChildOf { + fn assert_receiver_is_total_eq(_self: Ref) -> () { + let output: () = ::assert_receiver_is_total_eq( &_self, ) .into(); output } fn eq( - _self: Ref, - other: Ref, + _self: Ref, + other: Ref, ) -> bool { - let output: bool = >::eq(&_self, &other) .into(); output } /// Gets the [`Entity`] ID of the parent. fn get( - _self: Ref, + _self: Ref, ) -> Val { - let output: Val = bevy::hierarchy::prelude::Parent::get( + let output: Val = bevy::prelude::ChildOf::get( &_self, ) .into(); output } } -#[script_bindings( - remote, - name = "hierarchy_event_functions", - bms_core_path = "bevy_mod_scripting_core", - generated -)] -impl bevy::hierarchy::HierarchyEvent { - fn assert_receiver_is_total_eq(_self: Ref) -> () { - let output: () = ::assert_receiver_is_total_eq( - &_self, - ) - .into(); - output - } - fn clone( - _self: Ref, - ) -> Val { - let output: Val = ::clone( - &_self, - ) - .into(); - output - } - fn eq( - _self: Ref, - other: Ref, - ) -> bool { - let output: bool = >::eq(&_self, &other) - .into(); - output - } -} impl ::bevy::app::Plugin for BevyHierarchyScriptingPlugin { fn build(&self, app: &mut ::bevy::prelude::App) { let mut world = app.world_mut(); register_children_functions(&mut world); register_parent_functions(&mut world); - register_hierarchy_event_functions(&mut world); } } diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs index 712fa729dc..592fd54354 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_input.rs @@ -1191,22 +1191,6 @@ impl bevy::input::gamepad::AxisSettings { .into(); output } - /// Filters the `new_value` based on the `old_value`, according to the [`AxisSettings`]. - /// Returns the clamped `new_value` if the change exceeds the settings threshold, - /// and `None` otherwise. - fn filter( - _self: Ref, - new_value: f32, - old_value: std::option::Option, - ) -> std::option::Option { - let output: std::option::Option = bevy::input::gamepad::AxisSettings::filter( - &_self, - new_value, - old_value, - ) - .into(); - output - } /// Get the value below which negative inputs will be rounded down to -1.0. fn livezone_lowerbound(_self: Ref) -> f32 { let output: f32 = bevy::input::gamepad::AxisSettings::livezone_lowerbound(&_self) @@ -1315,22 +1299,6 @@ impl bevy::input::gamepad::ButtonAxisSettings { .into(); output } - /// Filters the `new_value` based on the `old_value`, according to the [`ButtonAxisSettings`]. - /// Returns the clamped `new_value`, according to the [`ButtonAxisSettings`], if the change - /// exceeds the settings threshold, and `None` otherwise. - fn filter( - _self: Ref, - new_value: f32, - old_value: std::option::Option, - ) -> std::option::Option { - let output: std::option::Option = bevy::input::gamepad::ButtonAxisSettings::filter( - &_self, - new_value, - old_value, - ) - .into(); - output - } } #[script_bindings( remote, diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs index 6a65dc0527..5d0a60944c 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_math.rs @@ -501,15 +501,6 @@ impl bevy::math::Ray3d { generated )] impl bevy::math::Rot2 { - /// Returns the angle in radians needed to make `self` and `other` coincide. - fn angle_between(_self: Val, other: Val) -> f32 { - let output: f32 = bevy::math::Rot2::angle_between( - _self.into_inner(), - other.into_inner(), - ) - .into(); - output - } /// Returns the angle in radians needed to make `self` and `other` coincide. fn angle_to(_self: Val, other: Val) -> f32 { let output: f32 = bevy::math::Rot2::angle_to( @@ -3390,11 +3381,11 @@ impl bevy::math::primitives::Segment2d { output } /// Create a new `Segment2d` from a direction and full length of the segment - fn new( + fn from_direction_and_length( direction: Val, length: f32, ) -> Val { - let output: Val = bevy::math::primitives::Segment2d::new( + let output: Val = bevy::math::primitives::Segment2d::from_direction_and_length( direction.into_inner(), length, ) @@ -4039,11 +4030,11 @@ impl bevy::math::primitives::Segment3d { output } /// Create a new `Segment3d` from a direction and full length of the segment - fn new( + fn from_direction_and_length( direction: Val, length: f32, ) -> Val { - let output: Val = bevy::math::primitives::Segment3d::new( + let output: Val = bevy::math::primitives::Segment3d::from_direction_and_length( direction.into_inner(), length, ) @@ -4569,7 +4560,7 @@ impl bevy::math::bounding::BoundingSphereCast { bms_core_path = "bevy_mod_scripting_core", generated )] -impl bevy::math::curve::interval::Interval { +impl bevy::math::curve::Interval { /// Clamp the given `value` to lie within this interval. fn clamp(_self: Val, value: f32) -> f32 { let output: f32 = bevy::math::curve::interval::Interval::clamp( diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs index 303da037a7..b981719c12 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_reflect.rs @@ -408,7 +408,7 @@ impl std::sync::atomic::AtomicUsize { bms_core_path = "bevy_mod_scripting_core", generated )] -impl bevy::utils::Duration { +impl std::time::Duration { /// Computes the absolute difference between `self` and `other`. /// # Examples /// ``` @@ -417,10 +417,10 @@ impl bevy::utils::Duration { /// assert_eq!(Duration::new(100, 400_000_000).abs_diff(Duration::new(110, 0)), Duration::new(9, 600_000_000)); /// ``` fn abs_diff( - _self: Val, - other: Val, - ) -> Val { - let output: Val = bevy::utils::Duration::abs_diff( + _self: Val, + other: Val, + ) -> Val { + let output: Val = std::time::Duration::abs_diff( _self.into_inner(), other.into_inner(), ) @@ -428,11 +428,11 @@ impl bevy::utils::Duration { output } fn add( - _self: Val, - rhs: Val, - ) -> Val { - let output: Val = , + rhs: Val, + ) -> Val { + let output: Val = >::add(_self.into_inner(), rhs.into_inner()) .into(); output @@ -444,8 +444,8 @@ impl bevy::utils::Duration { /// let duration = Duration::new(5, 730_023_852); /// assert_eq!(duration.as_micros(), 5_730_023); /// ``` - fn as_micros(_self: Ref) -> u128 { - let output: u128 = bevy::utils::Duration::as_micros(&_self).into(); + fn as_micros(_self: Ref) -> u128 { + let output: u128 = std::time::Duration::as_micros(&_self).into(); output } /// Returns the total number of whole milliseconds contained by this `Duration`. @@ -455,8 +455,8 @@ impl bevy::utils::Duration { /// let duration = Duration::new(5, 730_023_852); /// assert_eq!(duration.as_millis(), 5_730); /// ``` - fn as_millis(_self: Ref) -> u128 { - let output: u128 = bevy::utils::Duration::as_millis(&_self).into(); + fn as_millis(_self: Ref) -> u128 { + let output: u128 = std::time::Duration::as_millis(&_self).into(); output } /// Returns the total number of nanoseconds contained by this `Duration`. @@ -466,8 +466,8 @@ impl bevy::utils::Duration { /// let duration = Duration::new(5, 730_023_852); /// assert_eq!(duration.as_nanos(), 5_730_023_852); /// ``` - fn as_nanos(_self: Ref) -> u128 { - let output: u128 = bevy::utils::Duration::as_nanos(&_self).into(); + fn as_nanos(_self: Ref) -> u128 { + let output: u128 = std::time::Duration::as_nanos(&_self).into(); output } /// Returns the number of _whole_ seconds contained by this `Duration`. @@ -484,8 +484,8 @@ impl bevy::utils::Duration { /// [`as_secs_f64`]: Duration::as_secs_f64 /// [`as_secs_f32`]: Duration::as_secs_f32 /// [`subsec_nanos`]: Duration::subsec_nanos - fn as_secs(_self: Ref) -> u64 { - let output: u64 = bevy::utils::Duration::as_secs(&_self).into(); + fn as_secs(_self: Ref) -> u64 { + let output: u64 = std::time::Duration::as_secs(&_self).into(); output } /// Returns the number of seconds contained by this `Duration` as `f32`. @@ -496,8 +496,8 @@ impl bevy::utils::Duration { /// let dur = Duration::new(2, 700_000_000); /// assert_eq!(dur.as_secs_f32(), 2.7); /// ``` - fn as_secs_f32(_self: Ref) -> f32 { - let output: f32 = bevy::utils::Duration::as_secs_f32(&_self).into(); + fn as_secs_f32(_self: Ref) -> f32 { + let output: f32 = std::time::Duration::as_secs_f32(&_self).into(); output } /// Returns the number of seconds contained by this `Duration` as `f64`. @@ -508,26 +508,26 @@ impl bevy::utils::Duration { /// let dur = Duration::new(2, 700_000_000); /// assert_eq!(dur.as_secs_f64(), 2.7); /// ``` - fn as_secs_f64(_self: Ref) -> f64 { - let output: f64 = bevy::utils::Duration::as_secs_f64(&_self).into(); + fn as_secs_f64(_self: Ref) -> f64 { + let output: f64 = std::time::Duration::as_secs_f64(&_self).into(); output } - fn assert_receiver_is_total_eq(_self: Ref) -> () { - let output: () = ::assert_receiver_is_total_eq( + fn assert_receiver_is_total_eq(_self: Ref) -> () { + let output: () = ::assert_receiver_is_total_eq( &_self, ) .into(); output } - fn clone(_self: Ref) -> Val { - let output: Val = ::clone( + fn clone(_self: Ref) -> Val { + let output: Val = ::clone( &_self, ) .into(); output } - fn div(_self: Val, rhs: u32) -> Val { - let output: Val = , rhs: u32) -> Val { + let output: Val = >::div(_self.into_inner(), rhs) .into(); @@ -542,10 +542,10 @@ impl bevy::utils::Duration { /// assert_eq!(dur1.div_duration_f32(dur2), 0.5); /// ``` fn div_duration_f32( - _self: Val, - rhs: Val, + _self: Val, + rhs: Val, ) -> f32 { - let output: f32 = bevy::utils::Duration::div_duration_f32( + let output: f32 = std::time::Duration::div_duration_f32( _self.into_inner(), rhs.into_inner(), ) @@ -561,10 +561,10 @@ impl bevy::utils::Duration { /// assert_eq!(dur1.div_duration_f64(dur2), 0.5); /// ``` fn div_duration_f64( - _self: Val, - rhs: Val, + _self: Val, + rhs: Val, ) -> f64 { - let output: f64 = bevy::utils::Duration::div_duration_f64( + let output: f64 = std::time::Duration::div_duration_f64( _self.into_inner(), rhs.into_inner(), ) @@ -584,10 +584,10 @@ impl bevy::utils::Duration { /// assert_eq!(dur.div_f32(3.14e5), Duration::new(0, 8_599)); /// ``` fn div_f32( - _self: Val, + _self: Val, rhs: f32, - ) -> Val { - let output: Val = bevy::utils::Duration::div_f32( + ) -> Val { + let output: Val = std::time::Duration::div_f32( _self.into_inner(), rhs, ) @@ -605,19 +605,19 @@ impl bevy::utils::Duration { /// assert_eq!(dur.div_f64(3.14e5), Duration::new(0, 8_599)); /// ``` fn div_f64( - _self: Val, + _self: Val, rhs: f64, - ) -> Val { - let output: Val = bevy::utils::Duration::div_f64( + ) -> Val { + let output: Val = std::time::Duration::div_f64( _self.into_inner(), rhs, ) .into(); output } - fn eq(_self: Ref, other: Ref) -> bool { - let output: bool = , other: Ref) -> bool { + let output: bool = >::eq(&_self, &other) .into(); output @@ -630,8 +630,8 @@ impl bevy::utils::Duration { /// assert_eq!(1, duration.as_secs()); /// assert_eq!(2_000, duration.subsec_nanos()); /// ``` - fn from_micros(micros: u64) -> Val { - let output: Val = bevy::utils::Duration::from_micros( + fn from_micros(micros: u64) -> Val { + let output: Val = std::time::Duration::from_micros( micros, ) .into(); @@ -645,8 +645,8 @@ impl bevy::utils::Duration { /// assert_eq!(2, duration.as_secs()); /// assert_eq!(569_000_000, duration.subsec_nanos()); /// ``` - fn from_millis(millis: u64) -> Val { - let output: Val = bevy::utils::Duration::from_millis( + fn from_millis(millis: u64) -> Val { + let output: Val = std::time::Duration::from_millis( millis, ) .into(); @@ -664,8 +664,8 @@ impl bevy::utils::Duration { /// assert_eq!(1, duration.as_secs()); /// assert_eq!(123, duration.subsec_nanos()); /// ``` - fn from_nanos(nanos: u64) -> Val { - let output: Val = bevy::utils::Duration::from_nanos(nanos) + fn from_nanos(nanos: u64) -> Val { + let output: Val = std::time::Duration::from_nanos(nanos) .into(); output } @@ -677,8 +677,8 @@ impl bevy::utils::Duration { /// assert_eq!(5, duration.as_secs()); /// assert_eq!(0, duration.subsec_nanos()); /// ``` - fn from_secs(secs: u64) -> Val { - let output: Val = bevy::utils::Duration::from_secs(secs) + fn from_secs(secs: u64) -> Val { + let output: Val = std::time::Duration::from_secs(secs) .into(); output } @@ -706,8 +706,8 @@ impl bevy::utils::Duration { /// let res = Duration::from_secs_f32(0.999e-9); /// assert_eq!(res, Duration::new(0, 1)); /// ``` - fn from_secs_f32(secs: f32) -> Val { - let output: Val = bevy::utils::Duration::from_secs_f32( + fn from_secs_f32(secs: f32) -> Val { + let output: Val = std::time::Duration::from_secs_f32( secs, ) .into(); @@ -737,8 +737,8 @@ impl bevy::utils::Duration { /// let res = Duration::from_secs_f64(0.999e-9); /// assert_eq!(res, Duration::new(0, 1)); /// ``` - fn from_secs_f64(secs: f64) -> Val { - let output: Val = bevy::utils::Duration::from_secs_f64( + fn from_secs_f64(secs: f64) -> Val { + let output: Val = std::time::Duration::from_secs_f64( secs, ) .into(); @@ -756,12 +756,12 @@ impl bevy::utils::Duration { /// assert!(!Duration::from_nanos(1).is_zero()); /// assert!(!Duration::from_secs(1).is_zero()); /// ``` - fn is_zero(_self: Ref) -> bool { - let output: bool = bevy::utils::Duration::is_zero(&_self).into(); + fn is_zero(_self: Ref) -> bool { + let output: bool = std::time::Duration::is_zero(&_self).into(); output } - fn mul(_self: Val, rhs: u32) -> Val { - let output: Val = , rhs: u32) -> Val { + let output: Val = >::mul(_self.into_inner(), rhs) .into(); @@ -778,10 +778,10 @@ impl bevy::utils::Duration { /// assert_eq!(dur.mul_f32(3.14e5), Duration::new(847_800, 0)); /// ``` fn mul_f32( - _self: Val, + _self: Val, rhs: f32, - ) -> Val { - let output: Val = bevy::utils::Duration::mul_f32( + ) -> Val { + let output: Val = std::time::Duration::mul_f32( _self.into_inner(), rhs, ) @@ -799,10 +799,10 @@ impl bevy::utils::Duration { /// assert_eq!(dur.mul_f64(3.14e5), Duration::new(847_800, 0)); /// ``` fn mul_f64( - _self: Val, + _self: Val, rhs: f64, - ) -> Val { - let output: Val = bevy::utils::Duration::mul_f64( + ) -> Val { + let output: Val = std::time::Duration::mul_f64( _self.into_inner(), rhs, ) @@ -821,8 +821,8 @@ impl bevy::utils::Duration { /// use std::time::Duration; /// let five_seconds = Duration::new(5, 0); /// ``` - fn new(secs: u64, nanos: u32) -> Val { - let output: Val = bevy::utils::Duration::new(secs, nanos) + fn new(secs: u64, nanos: u32) -> Val { + let output: Val = std::time::Duration::new(secs, nanos) .into(); output } @@ -836,10 +836,10 @@ impl bevy::utils::Duration { /// assert_eq!(Duration::new(1, 0).saturating_add(Duration::new(u64::MAX, 0)), Duration::MAX); /// ``` fn saturating_add( - _self: Val, - rhs: Val, - ) -> Val { - let output: Val = bevy::utils::Duration::saturating_add( + _self: Val, + rhs: Val, + ) -> Val { + let output: Val = std::time::Duration::saturating_add( _self.into_inner(), rhs.into_inner(), ) @@ -856,10 +856,10 @@ impl bevy::utils::Duration { /// assert_eq!(Duration::new(u64::MAX - 1, 0).saturating_mul(2), Duration::MAX); /// ``` fn saturating_mul( - _self: Val, + _self: Val, rhs: u32, - ) -> Val { - let output: Val = bevy::utils::Duration::saturating_mul( + ) -> Val { + let output: Val = std::time::Duration::saturating_mul( _self.into_inner(), rhs, ) @@ -875,10 +875,10 @@ impl bevy::utils::Duration { /// assert_eq!(Duration::new(0, 0).saturating_sub(Duration::new(0, 1)), Duration::ZERO); /// ``` fn saturating_sub( - _self: Val, - rhs: Val, - ) -> Val { - let output: Val = bevy::utils::Duration::saturating_sub( + _self: Val, + rhs: Val, + ) -> Val { + let output: Val = std::time::Duration::saturating_sub( _self.into_inner(), rhs.into_inner(), ) @@ -886,11 +886,11 @@ impl bevy::utils::Duration { output } fn sub( - _self: Val, - rhs: Val, - ) -> Val { - let output: Val = , + rhs: Val, + ) -> Val { + let output: Val = >::sub(_self.into_inner(), rhs.into_inner()) .into(); output @@ -906,8 +906,8 @@ impl bevy::utils::Duration { /// assert_eq!(duration.as_secs(), 1); /// assert_eq!(duration.subsec_micros(), 234_567); /// ``` - fn subsec_micros(_self: Ref) -> u32 { - let output: u32 = bevy::utils::Duration::subsec_micros(&_self).into(); + fn subsec_micros(_self: Ref) -> u32 { + let output: u32 = std::time::Duration::subsec_micros(&_self).into(); output } /// Returns the fractional part of this `Duration`, in whole milliseconds. @@ -921,8 +921,8 @@ impl bevy::utils::Duration { /// assert_eq!(duration.as_secs(), 5); /// assert_eq!(duration.subsec_millis(), 432); /// ``` - fn subsec_millis(_self: Ref) -> u32 { - let output: u32 = bevy::utils::Duration::subsec_millis(&_self).into(); + fn subsec_millis(_self: Ref) -> u32 { + let output: u32 = std::time::Duration::subsec_millis(&_self).into(); output } /// Returns the fractional part of this `Duration`, in nanoseconds. @@ -936,8 +936,8 @@ impl bevy::utils::Duration { /// assert_eq!(duration.as_secs(), 5); /// assert_eq!(duration.subsec_nanos(), 10_000_000); /// ``` - fn subsec_nanos(_self: Ref) -> u32 { - let output: u32 = bevy::utils::Duration::subsec_nanos(&_self).into(); + fn subsec_nanos(_self: Ref) -> u32 { + let output: u32 = std::time::Duration::subsec_nanos(&_self).into(); output } } @@ -947,29 +947,29 @@ impl bevy::utils::Duration { bms_core_path = "bevy_mod_scripting_core", generated )] -impl bevy::utils::Instant { +impl bevy::platform::time::Instant { /// # Panics /// This function may panic if the resulting point in time cannot be represented by the /// underlying data structure. See [`Instant::checked_add`] for a version without panic. fn add( - _self: Val, - other: Val, - ) -> Val { - let output: Val = , + other: Val, + ) -> Val { + let output: Val = >::add(_self.into_inner(), other.into_inner()) .into(); output } - fn assert_receiver_is_total_eq(_self: Ref) -> () { - let output: () = ::assert_receiver_is_total_eq( + fn assert_receiver_is_total_eq(_self: Ref) -> () { + let output: () = ::assert_receiver_is_total_eq( &_self, ) .into(); output } - fn clone(_self: Ref) -> Val { - let output: Val = ::clone( + fn clone(_self: Ref) -> Val { + let output: Val = ::clone( &_self, ) .into(); @@ -993,10 +993,10 @@ impl bevy::utils::Instant { /// println!("{:?}", now.duration_since(new_now)); // 0ns /// ``` fn duration_since( - _self: Ref, - earlier: Val, - ) -> Val { - let output: Val = bevy::utils::Instant::duration_since( + _self: Ref, + earlier: Val, + ) -> Val { + let output: Val = bevy::platform::time::Instant::duration_since( &_self, earlier.into_inner(), ) @@ -1018,14 +1018,14 @@ impl bevy::utils::Instant { /// sleep(three_secs); /// assert!(instant.elapsed() >= three_secs); /// ``` - fn elapsed(_self: Ref) -> Val { - let output: Val = bevy::utils::Instant::elapsed(&_self) + fn elapsed(_self: Ref) -> Val { + let output: Val = bevy::platform::time::Instant::elapsed(&_self) .into(); output } - fn eq(_self: Ref, other: Ref) -> bool { - let output: bool = , other: Ref) -> bool { + let output: bool = >::eq(&_self, &other) .into(); output @@ -1036,8 +1036,8 @@ impl bevy::utils::Instant { /// use std::time::Instant; /// let now = Instant::now(); /// ``` - fn now() -> Val { - let output: Val = bevy::utils::Instant::now().into(); + fn now() -> Val { + let output: Val = bevy::platform::time::Instant::now().into(); output } /// Returns the amount of time elapsed from another instant to this one, @@ -1053,10 +1053,10 @@ impl bevy::utils::Instant { /// println!("{:?}", now.saturating_duration_since(new_now)); // 0ns /// ``` fn saturating_duration_since( - _self: Ref, - earlier: Val, - ) -> Val { - let output: Val = bevy::utils::Instant::saturating_duration_since( + _self: Ref, + earlier: Val, + ) -> Val { + let output: Val = bevy::platform::time::Instant::saturating_duration_since( &_self, earlier.into_inner(), ) @@ -1064,11 +1064,11 @@ impl bevy::utils::Instant { output } fn sub( - _self: Val, - other: Val, - ) -> Val { - let output: Val = , + other: Val, + ) -> Val { + let output: Val = >::sub(_self.into_inner(), other.into_inner()) .into(); output @@ -1081,11 +1081,11 @@ impl bevy::utils::Instant { /// See [Monotonicity]. /// [Monotonicity]: Instant#monotonicity fn sub( - _self: Val, - other: Val, - ) -> Val { - let output: Val = , + other: Val, + ) -> Val { + let output: Val = >::sub(_self.into_inner(), other.into_inner()) .into(); output diff --git a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs index 4802139580..2e7a937b67 100644 --- a/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs +++ b/crates/bevy_mod_scripting_functions/src/bevy_bindings/bevy_time.rs @@ -71,8 +71,8 @@ impl bevy::time::prelude::Timer { /// let timer = Timer::new(Duration::from_secs(1), TimerMode::Once); /// assert_eq!(timer.duration(), Duration::from_secs(1)); /// ``` - fn duration(_self: Ref) -> Val { - let output: Val = bevy::time::prelude::Timer::duration( + fn duration(_self: Ref) -> Val { + let output: Val = bevy::time::prelude::Timer::duration( &_self, ) .into(); @@ -89,8 +89,8 @@ impl bevy::time::prelude::Timer { /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.elapsed(), Duration::from_secs_f32(0.5)); /// ``` - fn elapsed(_self: Ref) -> Val { - let output: Val = bevy::time::prelude::Timer::elapsed( + fn elapsed(_self: Ref) -> Val { + let output: Val = bevy::time::prelude::Timer::elapsed( &_self, ) .into(); @@ -218,7 +218,7 @@ impl bevy::time::prelude::Timer { /// Creates a new timer with a given duration. /// See also [`Timer::from_seconds`](Timer::from_seconds). fn new( - duration: Val, + duration: Val, mode: Val, ) -> Val { let output: Val = bevy::time::prelude::Timer::new( @@ -268,8 +268,8 @@ impl bevy::time::prelude::Timer { /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.remaining(), Duration::from_secs_f32(1.5)); /// ``` - fn remaining(_self: Ref) -> Val { - let output: Val = bevy::time::prelude::Timer::remaining( + fn remaining(_self: Ref) -> Val { + let output: Val = bevy::time::prelude::Timer::remaining( &_self, ) .into(); @@ -318,7 +318,7 @@ impl bevy::time::prelude::Timer { /// ``` fn set_duration( mut _self: Mut, - duration: Val, + duration: Val, ) -> () { let output: () = bevy::time::prelude::Timer::set_duration( &mut _self, @@ -341,7 +341,7 @@ impl bevy::time::prelude::Timer { /// ``` fn set_elapsed( mut _self: Mut, - time: Val, + time: Val, ) -> () { let output: () = bevy::time::prelude::Timer::set_elapsed( &mut _self, @@ -493,8 +493,8 @@ impl bevy::time::Stopwatch { /// # See Also /// [`elapsed_secs`](Stopwatch::elapsed_secs) - if an `f32` value is desirable instead. /// [`elapsed_secs_f64`](Stopwatch::elapsed_secs_f64) - if an `f64` is desirable instead. - fn elapsed(_self: Ref) -> Val { - let output: Val = bevy::time::Stopwatch::elapsed(&_self) + fn elapsed(_self: Ref) -> Val { + let output: Val = bevy::time::Stopwatch::elapsed(&_self) .into(); output } @@ -599,7 +599,7 @@ impl bevy::time::Stopwatch { /// ``` fn set_elapsed( mut _self: Mut, - time: Val, + time: Val, ) -> () { let output: () = bevy::time::Stopwatch::set_elapsed( &mut _self, diff --git a/crates/bevy_mod_scripting_functions/src/core.rs b/crates/bevy_mod_scripting_functions/src/core.rs index 08963a02d3..a4d0d9d81c 100644 --- a/crates/bevy_mod_scripting_functions/src/core.rs +++ b/crates/bevy_mod_scripting_functions/src/core.rs @@ -4,28 +4,28 @@ use std::{collections::HashMap, ops::Deref}; use bevy::prelude::*; use bevy_mod_scripting_core::{ - bindings::{ - function::{ - from::Union, namespace::GlobalNamespace, script_function::DynamicScriptFunctionMut, - }, - script_system::ScriptSystemBuilder, - }, - docgen::info::FunctionInfo, - *, + bindings::{ + function::{ + from::Union, namespace::GlobalNamespace, script_function::DynamicScriptFunctionMut, + }, + script_system::ScriptSystemBuilder, + }, + docgen::info::FunctionInfo, + *, }; use bevy_mod_scripting_derive::script_bindings; use bevy_system_reflection::{ReflectSchedule, ReflectSystem}; use bindings::{ - function::{ - from::{Ref, Val}, - from_ref::FromScriptRef, - into_ref::IntoScriptRef, - script_function::{FunctionCallContext, ScriptFunctionMut}, - }, - pretty_print::DisplayWithWorld, - script_value::ScriptValue, - ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, ScriptQueryResult, - ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, WorldContainer, + function::{ + from::{Ref, Val}, + from_ref::FromScriptRef, + into_ref::IntoScriptRef, + script_function::{FunctionCallContext, ScriptFunctionMut}, + }, + pretty_print::DisplayWithWorld, + script_value::ScriptValue, + ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, ScriptQueryResult, + ScriptResourceRegistration, ScriptTypeRegistration, ThreadWorldContainer, WorldContainer, }; use error::InteropError; use reflection_extensions::{PartialReflectExt, TypeIdExtensions}; @@ -1261,8 +1261,9 @@ impl GlobalNamespace { fn system_builder( callback: String, script_id: String, + domain: Option, ) -> Result, InteropError> { - Ok(ScriptSystemBuilder::new(callback.into(), script_id.into()).into()) + Ok(ScriptSystemBuilder::new(callback.into(), script_id.into(), domain.map(|x| x.into())).into()) } } diff --git a/crates/bevy_mod_scripting_functions/src/lib.rs b/crates/bevy_mod_scripting_functions/src/lib.rs index 5f998f4cb4..5d48dc58a6 100644 --- a/crates/bevy_mod_scripting_functions/src/lib.rs +++ b/crates/bevy_mod_scripting_functions/src/lib.rs @@ -15,7 +15,7 @@ impl Plugin for ScriptFunctionsPlugin { register_core_functions(app); // TODO: if bevy ever does this itself we should remove this - app.world_mut().register_component::(); + app.world_mut().register_component::(); app.world_mut().register_component::(); } } diff --git a/crates/bevy_system_reflection/Cargo.toml b/crates/bevy_system_reflection/Cargo.toml index 95921576f8..675d8929f3 100644 --- a/crates/bevy_system_reflection/Cargo.toml +++ b/crates/bevy_system_reflection/Cargo.toml @@ -12,9 +12,8 @@ categories = ["game-development"] readme = "readme.md" [dependencies] -bevy = { workspace = true, default-features = false } +bevy = { workspace = true, default-features = false, features = ["bevy_log"]} dot-writer = "0.1.4" -petgraph = "0.6" [dev-dependencies] diff --git a/crates/bevy_system_reflection/src/lib.rs b/crates/bevy_system_reflection/src/lib.rs index b176d96c2a..473ab74ad6 100644 --- a/crates/bevy_system_reflection/src/lib.rs +++ b/crates/bevy_system_reflection/src/lib.rs @@ -1,13 +1,16 @@ //! A visualiser for bevy system schedules, as well as utilities for querying them via reflection -use std::ops::Deref; -use std::{any::TypeId, borrow::Cow}; - -use bevy::ecs::schedule::{ - InternedScheduleLabel, InternedSystemSet, NodeId, Schedule, ScheduleLabel, SystemSet, +use std::{any::TypeId, borrow::Cow, ops::Deref}; + +use bevy::{ + ecs::{ + schedule::{ + InternedScheduleLabel, InternedSystemSet, NodeId, Schedule, ScheduleLabel, SystemSet, + }, + system::{System, SystemInput}, + }, + platform::collections::{HashMap, HashSet}, + reflect::Reflect, }; -use bevy::ecs::system::{System, SystemInput}; -use bevy::reflect::Reflect; -use bevy::utils::hashbrown::{HashMap, HashSet}; use dot_writer::{Attributes, DotWriter}; #[derive(Reflect, Debug, Clone)] @@ -283,7 +286,7 @@ pub fn schedule_to_reflect_graph(schedule: &Schedule) -> ReflectSystemGraph { let dependencies = dependency .all_edges() - .map(|(from, to, _)| Edge { + .map(|(from, to)| Edge { from: ReflectNodeId(from), to: ReflectNodeId(to), }) @@ -291,7 +294,7 @@ pub fn schedule_to_reflect_graph(schedule: &Schedule) -> ReflectSystemGraph { let hierarchy = hierarchy .all_edges() - .map(|(from, to, _)| Edge { + .map(|(from, to)| Edge { from: ReflectNodeId(from), to: ReflectNodeId(to), }) @@ -466,17 +469,11 @@ pub struct Edge { #[cfg(test)] mod test { - use bevy::{ - app::Update, - ecs::{ - schedule::{IntoSystemConfigs, IntoSystemSetConfigs}, - world::World, - }, - }; + use bevy::{app::Update, ecs::world::World, prelude::IntoScheduleConfigs}; - use super::*; + use super::*; - fn system_a() {} + fn system_a() {} fn system_b() {} diff --git a/crates/ladfile_builder/Cargo.toml b/crates/ladfile_builder/Cargo.toml index 6b78ed7b8d..adcc25e745 100644 --- a/crates/ladfile_builder/Cargo.toml +++ b/crates/ladfile_builder/Cargo.toml @@ -16,7 +16,7 @@ readme = "readme.md" bevy_mod_scripting_core = { workspace = true } # I don't think bevy has a top level feature for this :C bevy = { workspace = true } -bevy_reflect = { version = "0.15.3", features = ["documentation"] } +bevy_reflect = { version = "0.16.0", features = ["documentation"] } ladfile = { version = "0.5.0", path = "../ladfile" } regex = "1.11" diff --git a/crates/ladfile_builder/src/lib.rs b/crates/ladfile_builder/src/lib.rs index 3c6e6de46d..f4e057f7a0 100644 --- a/crates/ladfile_builder/src/lib.rs +++ b/crates/ladfile_builder/src/lib.rs @@ -1,34 +1,35 @@ //! Parsing definitions for the LAD (Language Agnostic Decleration) file format. pub mod plugin; -use bevy::{ecs::world::World, utils::HashSet}; +use std::{ + any::TypeId, + borrow::Cow, + cmp::{max, min}, + collections::HashMap, + ffi::OsString, + path::PathBuf, +}; + +use bevy::{ecs::world::World, platform::collections::HashSet}; use bevy_mod_scripting_core::{ - bindings::{ - function::{ - namespace::Namespace, - script_function::{ - DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext, - }, - }, - ReflectReference, - }, - docgen::{ - info::FunctionInfo, - typed_through::{ThroughTypeInfo, TypedWrapperKind, UntypedWrapperKind}, - TypedThrough, - }, - match_by_type, + bindings::{ + function::{ + namespace::Namespace, + script_function::{ + DynamicScriptFunction, DynamicScriptFunctionMut, FunctionCallContext, + }, + }, + ReflectReference, + }, + docgen::{ + info::FunctionInfo, + typed_through::{ThroughTypeInfo, TypedWrapperKind, UntypedWrapperKind}, + TypedThrough, + }, + match_by_type, }; use bevy_reflect::{NamedField, TypeInfo, TypeRegistry, Typed, UnnamedField}; use ladfile::*; -use std::{ - any::TypeId, - borrow::Cow, - cmp::{max, min}, - collections::HashMap, - ffi::OsString, - path::PathBuf, -}; /// We can assume that the types here will be either primitives /// or reflect types, as the rest will be covered by typed wrappers @@ -787,21 +788,21 @@ impl<'t> LadFileBuilder<'t> { #[cfg(test)] mod test { - use bevy_mod_scripting_core::{ - bindings::{ - function::{ - from::Ref, - namespace::{GlobalNamespace, IntoNamespace}, - }, - Union, Val, - }, - docgen::info::GetFunctionInfo, - }; - use bevy_reflect::Reflect; - - use super::*; - - /// normalize line endings etc.. + use bevy_mod_scripting_core::{ + bindings::{ + function::{ + from::Ref, + namespace::{GlobalNamespace, IntoNamespace}, + }, + Union, Val, + }, + docgen::info::GetFunctionInfo, + }; + use bevy_reflect::Reflect; + + use super::*; + + /// normalize line endings etc.. fn normalize_file(file: &mut String) { *file = file.replace("\r\n", "\n"); } diff --git a/crates/ladfile_builder/src/plugin.rs b/crates/ladfile_builder/src/plugin.rs index ef6a3a6773..89942ae47a 100644 --- a/crates/ladfile_builder/src/plugin.rs +++ b/crates/ladfile_builder/src/plugin.rs @@ -3,17 +3,13 @@ use std::path::PathBuf; use bevy::{ - app::{App, Plugin, Startup}, - ecs::{ - reflect::AppTypeRegistry, - system::{Res, Resource}, - world::World, - }, + app::{App, Plugin, Startup}, + ecs::{prelude::Resource, reflect::AppTypeRegistry, system::Res, world::World}, }; use bevy_mod_scripting_core::bindings::{ - function::{namespace::Namespace, script_function::AppScriptFunctionRegistry}, - globals::AppScriptGlobalsRegistry, - IntoNamespace, MarkAsCore, MarkAsGenerated, MarkAsSignificant, + function::{namespace::Namespace, script_function::AppScriptFunctionRegistry}, + globals::AppScriptGlobalsRegistry, + IntoNamespace, MarkAsCore, MarkAsGenerated, MarkAsSignificant, }; use ladfile::{default_importance, LadTypeKind}; diff --git a/crates/languages/bevy_mod_scripting_lua/Cargo.toml b/crates/languages/bevy_mod_scripting_lua/Cargo.toml index 9ca8352a0a..45aa8aee47 100644 --- a/crates/languages/bevy_mod_scripting_lua/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_lua/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_lua" -version = "0.12.0" +version = "0.13.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" @@ -40,10 +40,6 @@ path = "src/lib.rs" bevy = { workspace = true, default-features = false } bevy_mod_scripting_core = { workspace = true, features = ["mlua_impls"] } mlua = { version = "0.10", features = ["vendored", "send", "macros"] } -parking_lot = "0.12.1" -uuid = "1.1" -smol_str = "0.2.2" -smallvec = "1.13" profiling = { workspace = true } [lints] diff --git a/crates/languages/bevy_mod_scripting_lua/src/lib.rs b/crates/languages/bevy_mod_scripting_lua/src/lib.rs index 473c98d24c..a58b8b57d3 100644 --- a/crates/languages/bevy_mod_scripting_lua/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_lua/src/lib.rs @@ -1,10 +1,11 @@ //! Lua integration for the bevy_mod_scripting system. use bevy::{ app::Plugin, + asset::Handle, ecs::{entity::Entity, world::World}, }; use bevy_mod_scripting_core::{ - asset::Language, + asset::{ScriptAsset, Language}, bindings::{ function::namespace::Namespace, globals::AppScriptGlobalsRegistry, script_value::ScriptValue, ThreadWorldContainer, WorldContainer, @@ -72,7 +73,7 @@ impl Default for LuaScriptingPlugin { Ok(()) }, - |_script_id, context: &mut Lua| { + |_script_id, context| { // set static globals let world = ThreadWorldContainer.try_get_world()?; let globals_registry = @@ -124,9 +125,10 @@ impl Default for LuaScriptingPlugin { LuaReflectReference(::allocate(Box::new(entity), world)), ) .map_err(ScriptError::from_mlua_error)?; + let path = script_id.path().map(|p| p.to_string()).unwrap_or_else(|| script_id.id().to_string()); context .globals() - .set("script_id", script_id) + .set("script_id", path) .map_err(ScriptError::from_mlua_error)?; Ok(()) }], @@ -149,7 +151,7 @@ impl Plugin for LuaScriptingPlugin { fn load_lua_content_into_context( context: &mut Lua, - script_id: &ScriptId, + script_id: &Handle, content: &[u8], initializers: &[ContextInitializer], pre_handling_initializers: &[ContextPreHandlingInitializer], @@ -173,7 +175,7 @@ fn load_lua_content_into_context( #[profiling::function] /// Load a lua context from a script pub fn lua_context_load( - script_id: &ScriptId, + script_id: &Handle, content: &[u8], initializers: &[ContextInitializer], pre_handling_initializers: &[ContextPreHandlingInitializer], @@ -197,7 +199,7 @@ pub fn lua_context_load( #[profiling::function] /// Reload a lua context from a script pub fn lua_context_reload( - script: &ScriptId, + script: &Handle, content: &[u8], old_ctxt: &mut Lua, initializers: &[ContextInitializer], @@ -220,7 +222,7 @@ pub fn lua_context_reload( pub fn lua_handler( args: Vec, entity: bevy::ecs::entity::Entity, - script_id: &ScriptId, + script_id: &Handle, callback_label: &CallbackLabel, context: &mut Lua, pre_handling_initializers: &[ContextPreHandlingInitializer], @@ -234,11 +236,22 @@ pub fn lua_handler( Ok(handler) => handler, // not subscribed to this event type Err(_) => { - bevy::log::trace!( - "Script {} is not subscribed to callback {}", - script_id, - callback_label.as_ref() - ); + match script_id.path() { + Some(path) => { + bevy::log::trace!( + "Script path {} is not subscribed to callback {}", + path, + callback_label.as_ref() + ); + } + None => { + bevy::log::trace!( + "Script id {} is not subscribed to callback {}", + script_id.id(), + callback_label.as_ref() + ); + } + } return Ok(ScriptValue::Unit); } }; @@ -262,13 +275,14 @@ mod test { #[test] fn test_reload_doesnt_overwrite_old_context() { let lua = Lua::new(); - let script_id = ScriptId::from("asd.lua"); + let script_id: ScriptId = ScriptId::from(uuid::Uuid::new_v4()); let initializers = vec![]; let pre_handling_initializers = vec![]; let mut old_ctxt = lua.clone(); + let handle = Handle::Weak(script_id); lua_context_load( - &script_id, + &handle, "function hello_world_from_first_load() end" @@ -280,7 +294,7 @@ mod test { .unwrap(); lua_context_reload( - &script_id, + &handle, "function hello_world_from_second_load() end" diff --git a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml index 2c2789922d..64e10ee089 100644 --- a/crates/languages/bevy_mod_scripting_rhai/Cargo.toml +++ b/crates/languages/bevy_mod_scripting_rhai/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bevy_mod_scripting_rhai" -version = "0.12.0" +version = "0.13.0" authors = ["Maksymilian Mozolewski "] edition = "2021" license = "MIT OR Apache-2.0" diff --git a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs index 6f72de7362..4bdc26d5ea 100644 --- a/crates/languages/bevy_mod_scripting_rhai/src/lib.rs +++ b/crates/languages/bevy_mod_scripting_rhai/src/lib.rs @@ -3,11 +3,12 @@ use std::ops::Deref; use bevy::{ + asset::Handle, app::Plugin, ecs::{entity::Entity, world::World}, }; use bevy_mod_scripting_core::{ - asset::Language, + asset::{Language, ScriptAsset}, bindings::{ function::namespace::Namespace, globals::AppScriptGlobalsRegistry, script_value::ScriptValue, ThreadWorldContainer, WorldContainer, @@ -17,17 +18,16 @@ use bevy_mod_scripting_core::{ event::CallbackLabel, reflection_extensions::PartialReflectExt, runtime::RuntimeSettings, - script::ScriptId, + script::{DisplayProxy, ScriptId}, IntoScriptPluginParams, ScriptingPlugin, }; use bindings::{ - reference::{ReservedKeyword, RhaiReflectReference, RhaiStaticReflectReference}, - script_value::{FromDynamic, IntoDynamic}, + reference::{ReservedKeyword, RhaiReflectReference, RhaiStaticReflectReference}, + script_value::{FromDynamic, IntoDynamic}, }; use parking_lot::RwLock; -use rhai::{CallFnOptions, Dynamic, Engine, EvalAltResult, Scope, AST}; - pub use rhai; +use rhai::{CallFnOptions, Dynamic, Engine, EvalAltResult, Scope, AST}; /// Bindings for rhai. pub mod bindings; @@ -71,7 +71,7 @@ impl Default for RhaiScriptingPlugin { scripting_plugin: ScriptingPlugin { context_assignment_strategy: Default::default(), runtime_settings: RuntimeSettings { - initializers: vec![|runtime: &RhaiRuntime| { + initializers: vec![|runtime| { let mut engine = runtime.write(); engine.set_max_expr_depths(999, 999); engine.build_type::(); @@ -86,14 +86,14 @@ impl Default for RhaiScriptingPlugin { reload: rhai_context_reload, }, context_initializers: vec![ - |_, context: &mut RhaiScriptContext| { + |_, context| { context.scope.set_or_push( "world", RhaiStaticReflectReference(std::any::TypeId::of::()), ); Ok(()) }, - |_, context: &mut RhaiScriptContext| { + |_, context| { // initialize global functions let world = ThreadWorldContainer.try_get_world()?; let globals_registry = @@ -180,7 +180,7 @@ impl Plugin for RhaiScriptingPlugin { // NEW helper function to load content into an existing context without clearing previous definitions. fn load_rhai_content_into_context( context: &mut RhaiScriptContext, - script: &ScriptId, + script: &Handle, content: &[u8], initializers: &[ContextInitializer], pre_handling_initializers: &[ContextPreHandlingInitializer], @@ -189,7 +189,7 @@ fn load_rhai_content_into_context( let runtime = runtime.read(); context.ast = runtime.compile(std::str::from_utf8(content)?)?; - context.ast.set_source(script.to_string()); + context.ast.set_source(script.display().to_string()); initializers .iter() @@ -205,7 +205,7 @@ fn load_rhai_content_into_context( /// Load a rhai context from a script. pub fn rhai_context_load( - script: &ScriptId, + script: &Handle, content: &[u8], initializers: &[ContextInitializer], pre_handling_initializers: &[ContextPreHandlingInitializer], @@ -229,7 +229,7 @@ pub fn rhai_context_load( /// Reload a rhai context from a script. New content is appended to the existing context. pub fn rhai_context_reload( - script: &ScriptId, + script: &Handle, content: &[u8], context: &mut RhaiScriptContext, initializers: &[ContextInitializer], @@ -251,7 +251,7 @@ pub fn rhai_context_reload( pub fn rhai_callback_handler( args: Vec, entity: Entity, - script_id: &ScriptId, + script_id: &Handle, callback: &CallbackLabel, context: &mut RhaiScriptContext, pre_handling_initializers: &[ContextPreHandlingInitializer], @@ -271,7 +271,7 @@ pub fn rhai_callback_handler( bevy::log::trace!( "Calling callback {} in script {} with args: {:?}", callback, - script_id, + script_id.display(), args ); let runtime = runtime.read(); @@ -288,7 +288,7 @@ pub fn rhai_callback_handler( if let EvalAltResult::ErrorFunctionNotFound(_, _) = e.unwrap_inner() { bevy::log::trace!( "Script {} is not subscribed to callback {} with the provided arguments.", - script_id, + script_id.display(), callback ); Ok(ScriptValue::Unit) @@ -301,9 +301,9 @@ pub fn rhai_callback_handler( #[cfg(test)] mod test { - use super::*; + use super::*; - #[test] + #[test] fn test_reload_doesnt_overwrite_old_context() { let runtime = RhaiRuntime::new(Engine::new()); let script_id = ScriptId::from("asd.rhai"); diff --git a/crates/testing_crates/script_integration_test_harness/Cargo.toml b/crates/testing_crates/script_integration_test_harness/Cargo.toml index 723e8a8cae..49b849043e 100644 --- a/crates/testing_crates/script_integration_test_harness/Cargo.toml +++ b/crates/testing_crates/script_integration_test_harness/Cargo.toml @@ -6,7 +6,7 @@ publish = false [features] default = ["lua", "rhai"] -lua = ["bevy_mod_scripting_lua", "bevy_mod_scripting_functions/lua_bindings"] +lua = ["bevy_mod_scripting_lua", "bevy_mod_scripting_functions/lua_bindings", "bevy_mod_scripting_lua/lua54"] rhai = ["bevy_mod_scripting_rhai", "bevy_mod_scripting_functions/rhai_bindings"] [dependencies] @@ -23,3 +23,4 @@ bevy_mod_scripting_rhai = { path = "../../languages/bevy_mod_scripting_rhai", op criterion = "0.5" rand = "0.9" rand_chacha = "0.9" +uuid = "1.11" diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index 17956e9c62..8ea7ef28f9 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -4,22 +4,22 @@ use std::{ marker::PhantomData, path::PathBuf, time::{Duration, Instant}, + sync::{Arc,Mutex}, }; use bevy::{ app::{Last, Plugin, PostUpdate, Startup, Update}, - asset::{AssetServer, Handle}, + asset::{AssetServer, Handle, AssetPath, AssetId, LoadState}, ecs::{ component::Component, event::{Event, Events}, - schedule::{IntoSystemConfigs, SystemConfigs}, - system::{IntoSystem, Local, Res, Resource, SystemState}, - world::{Command, FromWorld, Mut}, + schedule::{IntoScheduleConfigs, ScheduleConfigs}, + system::{IntoSystem, Local, Res, SystemState, InfallibleSystemWrapper, BoxedSystem}, + world::{FromWorld, Mut}, }, - log::Level, - prelude::{Entity, World}, + log::{tracing, Level}, + prelude::{Entity, World, Commands, Command, BevyError, Resource}, reflect::{Reflect, TypeRegistry}, - utils::tracing, }; use bevy_mod_scripting_core::{ asset::ScriptAsset, @@ -33,7 +33,7 @@ use bevy_mod_scripting_core::{ event::{IntoCallbackLabel, ScriptErrorEvent}, extractors::{HandlerContext, WithWorldGuard}, handler::handle_script_errors, - script::ScriptId, + script::{ScriptComponent, ScriptId, DisplayProxy, ScriptContextProvider}, BMSScriptingInfrastructurePlugin, IntoScriptPluginParams, ScriptingPlugin, }; use bevy_mod_scripting_functions::ScriptFunctionsPlugin; @@ -55,19 +55,24 @@ struct TestCallbackBuilder { } impl TestCallbackBuilder { - fn build(script_id: impl Into, expect_response: bool) -> SystemConfigs { - let script_id = script_id.into(); - IntoSystem::into_system( + fn build<'a>( + script_path: impl Into>, + expect_response: bool, + ) -> ScheduleConfigs>> { + let script_path = script_path.into().clone_owned(); + let system = Box::new(InfallibleSystemWrapper::new(IntoSystem::into_system( move |world: &mut World, system_state: &mut SystemState>>| { + let script_id = world.resource::().load(&script_path).id(); + let with_guard = system_state.get_mut(world); - let _ = run_test_callback::(&script_id.clone(), with_guard, expect_response); + let _ = run_test_callback::(&script_id, with_guard, expect_response); system_state.apply(world); }, - ) - .with_name(L::into_callback_label().to_string()) - .into_configs() + ).with_name(L::into_callback_label().to_string()))); + + system.into_configs() } } @@ -92,7 +97,7 @@ pub fn make_test_lua_plugin() -> bevy_mod_scripting_lua::LuaScriptingPlugin { use bevy_mod_scripting_core::{bindings::WorldContainer, ConfigureScriptPlugin}; use bevy_mod_scripting_lua::{mlua, LuaScriptingPlugin}; - LuaScriptingPlugin::default().add_context_initializer( + LuaScriptingPlugin::default().enable_context_sharing().add_context_initializer( |_, ctxt: &mut bevy_mod_scripting_lua::mlua::Lua| { let globals = ctxt.globals(); globals.set( @@ -130,12 +135,12 @@ pub fn make_test_lua_plugin() -> bevy_mod_scripting_lua::LuaScriptingPlugin { #[cfg(feature = "rhai")] pub fn make_test_rhai_plugin() -> bevy_mod_scripting_rhai::RhaiScriptingPlugin { use bevy_mod_scripting_core::{ - bindings::{ThreadWorldContainer, WorldContainer}, - ConfigureScriptPlugin, + bindings::{ThreadWorldContainer, WorldContainer}, + ConfigureScriptPlugin, }; use bevy_mod_scripting_rhai::{ - rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}, - RhaiScriptingPlugin, + rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}, + RhaiScriptingPlugin, }; RhaiScriptingPlugin::default().add_runtime_initializer(|runtime| { @@ -199,14 +204,15 @@ pub fn execute_rhai_integration_test(script_id: &str) -> Result<(), String> { execute_integration_test(plugin, |_, _| {}, script_id) } -pub fn execute_integration_test< +pub fn execute_integration_test<'a, P: IntoScriptPluginParams + Plugin + AsMut>, F: FnOnce(&mut World, &mut TypeRegistry), >( plugin: P, init: F, - script_id: &str, + script_id: impl Into>, ) -> Result<(), String> { + let script_id = script_id.into(); // set "BEVY_ASSET_ROOT" to the global assets folder, i.e. CARGO_MANIFEST_DIR/../../../assets let mut manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); @@ -232,28 +238,27 @@ pub fn execute_integration_test< OnTestLast => "on_test_last", ); - let script_id = script_id.to_owned(); - let script_id: &'static str = Box::leak(script_id.into_boxed_str()); + let script_path = script_id.clone_owned(); - let load_system = |server: Res, mut handle: Local>| { - *handle = server.load(script_id.to_owned()); + // tests can opt in to this via "__RETURN" + let expect_callback_response = script_id.path().to_str().map(|s| s.contains("__RETURN")).unwrap_or(false); + let load_system = move |server: Res, mut commands: Commands| { + commands.spawn(ScriptComponent::new([server.load(script_path.clone())])); }; - // tests can opt in to this via "__RETURN" - let expect_callback_response = script_id.contains("__RETURN"); app.add_systems(Startup, load_system); app.add_systems( Update, - TestCallbackBuilder::::build(script_id, expect_callback_response), + TestCallbackBuilder::::build(&script_id, expect_callback_response), ); app.add_systems( PostUpdate, - TestCallbackBuilder::::build(script_id, expect_callback_response), + TestCallbackBuilder::::build(&script_id, expect_callback_response), ); app.add_systems( Last, - TestCallbackBuilder::::build(script_id, expect_callback_response), + TestCallbackBuilder::::build(&script_id, expect_callback_response), ); app.add_systems(Update, dummy_update_system); app.add_systems(Startup, dummy_startup_system::); @@ -296,19 +301,20 @@ pub fn execute_integration_test< } fn run_test_callback( - script_id: &str, + script_id: &ScriptId, mut with_guard: WithWorldGuard<'_, '_, HandlerContext<'_, P>>, expect_response: bool, ) -> Result { let (guard, handler_ctxt) = with_guard.get_mut(); - if !handler_ctxt.is_script_fully_loaded(script_id.to_string().into()) { - return Ok(ScriptValue::Unit); - } + // if !handler_ctxt.is_script_fully_loaded(*script_id) { + // return Ok(ScriptValue::Unit); + // } let res = handler_ctxt.call::( - &script_id.to_string().into(), + &Handle::Weak(*script_id), Entity::from_raw(0), + &None, vec![], guard.clone(), ); @@ -341,7 +347,7 @@ pub fn run_lua_benchmark( label: &str, criterion: &mut criterion::BenchmarkGroup, ) -> Result<(), String> { - use bevy::{log::Level, utils::tracing}; + use bevy::log::Level; use bevy_mod_scripting_lua::mlua::Function; let plugin = make_test_lua_plugin(); @@ -373,7 +379,7 @@ pub fn run_rhai_benchmark( label: &str, criterion: &mut criterion::BenchmarkGroup, ) -> Result<(), String> { - use bevy::{log::Level, utils::tracing}; + use bevy::log::Level; use bevy_mod_scripting_rhai::rhai::Dynamic; let plugin = make_test_rhai_plugin(); @@ -406,9 +412,9 @@ pub fn run_rhai_benchmark( ) } -pub fn run_plugin_benchmark( +pub fn run_plugin_benchmark<'a, P, F, M: criterion::measurement::Measurement>( plugin: P, - script_id: &str, + script_path: impl Into>, label: &str, criterion: &mut criterion::BenchmarkGroup, bench_fn: F, @@ -418,21 +424,17 @@ where F: Fn(&mut P::C, &P::R, &str, &mut criterion::BenchmarkGroup) -> Result<(), String>, { use bevy_mod_scripting_core::bindings::{ - ThreadWorldContainer, WorldAccessGuard, WorldContainer, + ThreadWorldContainer, WorldAccessGuard, WorldContainer, }; let mut app = setup_integration_test(|_, _| {}); install_test_plugin(&mut app, plugin, true); - let script_id = script_id.to_owned(); - let script_id_clone = script_id.clone(); - app.add_systems( - Startup, - move |server: Res, mut handle: Local>| { - *handle = server.load(script_id_clone.to_owned()); - }, - ); + let script_path = script_path.into(); + let script_handle = app.world().resource::().load(script_path); + let script_id = script_handle.id(); + let entity = app.world_mut().spawn(ScriptComponent(vec![script_handle.clone()])).id(); // finalize app.cleanup(); @@ -442,31 +444,37 @@ where let mut state = SystemState::>>::from_world(app.world_mut()); + /// Wait until script is loaded. + loop { + app.update(); + match app.world().resource::().load_state(script_id) { + _ => continue, + LoadState::Loaded => break, + LoadState::Failed(e) => { + return Err(format!("Failed to load script {}: {e}", script_handle.display())); + } + } + } + loop { app.update(); let mut handler_ctxt = state.get_mut(app.world_mut()); let (guard, context) = handler_ctxt.get_mut(); - if context.is_script_fully_loaded(script_id.clone().into()) { - let script = context - .scripts() - .get_mut(script_id.to_owned()) - .ok_or_else(|| String::from("Could not find scripts resource"))?; - let ctxt_arc = script.context.clone(); - let mut ctxt_locked = ctxt_arc.lock(); - - let runtime = &context.runtime_container().runtime; - - return WorldAccessGuard::with_existing_static_guard(guard, |guard| { - // Ensure the world is available via ThreadWorldContainer - ThreadWorldContainer - .set_world(guard.clone()) - .map_err(|e| e.display_with_world(guard))?; - // Pass the locked context to the closure for benchmarking its Lua (or generic) part - bench_fn(&mut ctxt_locked, runtime, label, criterion) - }); - } + let ctxt_arc = context.script_context().get(Some(entity), &script_id, &None).cloned().unwrap(); + let mut ctxt_locked = ctxt_arc.lock(); + + let runtime = &context.runtime_container().runtime; + + return WorldAccessGuard::with_existing_static_guard(guard, |guard| { + // Ensure the world is available via ThreadWorldContainer + ThreadWorldContainer + .set_world(guard.clone()) + .map_err(|e| e.display_with_world(guard))?; + // Pass the locked context to the closure for benchmarking its Lua (or generic) part + bench_fn(&mut ctxt_locked, runtime, label, criterion) + }); state.apply(app.world_mut()); if timer.elapsed() > Duration::from_secs(30) { return Err("Timeout after 30 seconds, could not load script".into()); @@ -482,7 +490,7 @@ pub fn run_plugin_script_load_benchmark< benchmark_id: &str, content: &str, criterion: &mut criterion::BenchmarkGroup, - script_id_generator: impl Fn(u64) -> String, + script_id_generator: impl Fn(u64) -> AssetId, reload_probability: f32, ) { let mut app = setup_integration_test(|_, _| {}); @@ -495,14 +503,15 @@ pub fn run_plugin_script_load_benchmark< || { let mut rng = RNG.lock().unwrap(); let is_reload = rng.random_range(0f32..=1f32) < reload_probability; - let random_id = if is_reload { 0 } else { rng.random::() }; + let random_id = if is_reload { 0 } else { rng.random::() }; - let random_script_id = script_id_generator(random_id); + // let random_script_id = script_id_generator(random_id); + let random_script_id: ScriptId = ScriptId::from(uuid::Builder::from_random_bytes(random_id.to_le_bytes()).into_uuid()); // we manually load the script inside a command let content = content.to_string().into_boxed_str(); ( CreateOrUpdateScript::

::new( - random_script_id.into(), + Handle::Weak(random_script_id), content.clone().into(), None, ), @@ -587,7 +596,7 @@ pub fn perform_benchmark_with_generator< ) }, |(i, w)| { - bevy::utils::tracing::event!(bevy::log::Level::TRACE, "profiling_iter {}", label); + tracing::event!(bevy::log::Level::TRACE, "profiling_iter {}", label); bench_fn(w, i) }, batch_size, diff --git a/crates/testing_crates/test_utils/src/test_data.rs b/crates/testing_crates/test_utils/src/test_data.rs index 030f7dc020..1fa707524a 100644 --- a/crates/testing_crates/test_utils/src/test_data.rs +++ b/crates/testing_crates/test_utils/src/test_data.rs @@ -1,12 +1,13 @@ -use std::alloc::Layout; -use std::collections::HashMap; +use std::{alloc::Layout, collections::HashMap}; -use bevy::asset::AssetPlugin; -use bevy::diagnostic::DiagnosticsPlugin; -use bevy::ecs::{component::*, world::World}; -use bevy::log::LogPlugin; -use bevy::prelude::*; -use bevy::reflect::*; +use bevy::{ + asset::AssetPlugin, + diagnostic::DiagnosticsPlugin, + ecs::{component::*, world::World}, + log::LogPlugin, + prelude::*, + reflect::*, +}; /// Test component with Reflect and ReflectComponent registered #[derive(Component, Reflect, PartialEq, Eq, Debug)] @@ -312,6 +313,8 @@ fn init_world(world: &mut World, init: StorageType::Table, Layout::new::(), None, + true, + ComponentCloneBehavior::Default, )) }; } @@ -346,21 +349,20 @@ pub fn setup_integration_test(init: F) app.add_plugins(( MinimalPlugins, AssetPlugin::default(), - HierarchyPlugin, DiagnosticsPlugin, - LogPlugin { - filter: log_level, - ..Default::default() - }, + // LogPlugin { + // filter: log_level, + // ..Default::default() + // }, )); app } #[cfg(test)] mod test { - use super::*; + use super::*; - #[test] + #[test] fn setup_works() { setup_world(|_, _| {}); } diff --git a/docs/src/ScriptSystems/introduction.md b/docs/src/ScriptSystems/introduction.md index fe53c76dfc..5a148d3097 100644 --- a/docs/src/ScriptSystems/introduction.md +++ b/docs/src/ScriptSystems/introduction.md @@ -13,7 +13,7 @@ BMS also provides utilities for visualising schedules using dot graphs, allowing ## Schedules -Bevy doesn't support reflecting schedules, so BMS rolls it's own schedule registry resource: `AppScheduleRegistry`, which can be used to add any custom schedules you want to interact with. The default Bevy schedules will be pre-populated for you. +Bevy doesn't support reflecting schedules, so BMS rolls its own schedule registry resource: `AppScheduleRegistry`, which can be used to add any custom schedules you want to interact with. The default Bevy schedules will be pre-populated for you. Once you've registered your schedule you will be able to interact with it in scripts like below: diff --git a/examples/game_of_life.rs b/examples/game_of_life.rs index 325b4a4db9..67ee2e23cf 100644 --- a/examples/game_of_life.rs +++ b/examples/game_of_life.rs @@ -17,6 +17,7 @@ use bevy::{ use bevy_console::{make_layer, AddConsoleCommand, ConsoleCommand, ConsoleOpen, ConsolePlugin}; use bevy_mod_scripting::{BMSPlugin, ScriptFunctionsPlugin}; use bevy_mod_scripting_core::{ + ConfigureScriptPlugin, asset::ScriptAsset, bindings::{ function::namespace::{GlobalNamespace, NamespaceBuilder}, @@ -24,12 +25,13 @@ use bevy_mod_scripting_core::{ AllocatorDiagnosticPlugin, CoreScriptGlobalsPlugin, }, callback_labels, - commands::AddStaticScript, + commands::{DeleteScript, AddStaticScript}, event::ScriptCallbackEvent, handler::event_handler, - script::ScriptComponent, + script::{ScriptId, ScriptComponent}, }; use bevy_mod_scripting_lua::LuaScriptingPlugin; +#[cfg(feature = "rhai")] use bevy_mod_scripting_rhai::RhaiScriptingPlugin; use clap::Parser; @@ -54,7 +56,11 @@ fn console_app(app: &mut App) -> &mut App { fn run_script_cmd( mut log: ConsoleCommand, mut commands: Commands, - mut loaded_scripts: ResMut, + asset_server: Res, + mut script_handle: Local>>, + script_comps: Query>, + mut static_lua_scripts: Local>, + mut static_rhai_scripts: Local>, ) { if let Some(Ok(command)) = log.take() { match command { @@ -63,32 +69,46 @@ fn run_script_cmd( use_static_script, } => { // create an entity with the script component - bevy::log::info!( - "Starting game of life spawning entity with the game_of_life.{} script", - language - ); + bevy::log::info!("Using game of life script game_of_life.{}", language); let script_path = format!("scripts/game_of_life.{}", language); if !use_static_script { bevy::log::info!("Spawning an entity with ScriptComponent"); - commands.spawn(ScriptComponent::new(vec![script_path])); + commands.spawn(ScriptComponent::new(vec![asset_server.load(script_path)])); } else { bevy::log::info!("Using static script instead of spawning an entity"); - commands.queue(AddStaticScript::new(script_path)) + let handle = asset_server.load(script_path); + if language == "lua" { + static_lua_scripts.push(handle.id()); + } else { + static_rhai_scripts.push(handle.id()); + } + commands.queue(AddStaticScript::new(handle)) } } GameOfLifeCommand::Stop => { // we can simply drop the handle, or manually delete, I'll just drop the handle bevy::log::info!("Stopping game of life by dropping the handles to all scripts"); - // I am not mapping the handles to the script names, so I'll just clear the entire list - loaded_scripts.0.clear(); - // you could also do - // commands.queue(DeleteScript::::new( - // "scripts/game_of_life.lua".into(), - // )); - // as this will retain your script asset and handle + for id in &script_comps { + commands.entity(id).despawn(); + } + + for script_id in static_lua_scripts.drain(..) { + commands.queue(DeleteScript::::new( + script_id, + None, + )); + } + + #[cfg(feature = "rhai")] + for script_id in static_rhai_scripts.drain(..) { + commands.queue(DeleteScript::::new( + script_id, + None, + )); + } } } } @@ -115,12 +135,12 @@ pub enum GameOfLifeCommand { // ------------- GAME OF LIFE fn game_of_life_app(app: &mut App) -> &mut App { app.insert_resource(Time::::from_seconds(UPDATE_FREQUENCY.into())) + // .add_plugins(BMSPlugin.set(LuaScriptingPlugin::default().enable_context_sharing())) .add_plugins(BMSPlugin) .register_type::() .register_type::() .init_resource::() - .init_resource::() - .add_systems(Startup, (init_game_of_life_state, load_script_assets)) + .add_systems(Startup, init_game_of_life_state) .add_systems(Update, (sync_window_size, send_on_click)) .add_systems( FixedUpdate, @@ -129,8 +149,10 @@ fn game_of_life_app(app: &mut App) -> &mut App { send_on_update.after(update_rendered_state), ( event_handler::, + #[cfg(feature = "rhai")] event_handler::, event_handler::, + #[cfg(feature = "rhai")] event_handler::, ) .after(send_on_update), @@ -145,9 +167,6 @@ pub struct LifeState { pub cells: Vec, } -#[derive(Debug, Resource, Default)] -pub struct LoadedScripts(pub Vec>); - #[derive(Reflect, Resource)] #[reflect(Resource)] pub struct Settings { @@ -170,17 +189,6 @@ impl Default for Settings { } } -/// Prepares any scripts by loading them and storing the handles. -pub fn load_script_assets( - asset_server: Res, - mut loaded_scripts: ResMut, -) { - loaded_scripts.0.extend(vec![ - asset_server.load("scripts/game_of_life.lua"), - asset_server.load("scripts/game_of_life.rhai"), - ]); -} - pub fn register_script_functions(app: &mut App) -> &mut App { let world = app.world_mut(); NamespaceBuilder::::new_unregistered(world) @@ -280,7 +288,7 @@ pub fn update_rendered_state( let old_rendered_state = assets .get_mut(&old_rendered_state.image) .expect("World is not setup correctly"); - old_rendered_state.data = new_state.cells.clone(); + old_rendered_state.data = Some(new_state.cells.clone()); } } @@ -301,7 +309,7 @@ pub fn send_on_click( ) { if buttons.just_pressed(MouseButton::Left) { let window = q_windows.single(); - let pos = window.cursor_position().unwrap_or_default(); + let pos = window.unwrap().cursor_position().unwrap_or_default(); let x = pos.x as u32; let y = pos.y as u32; events.send(ScriptCallbackEvent::new_for_all( diff --git a/src/lib.rs b/src/lib.rs index a880540a76..5860ae083c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,17 @@ #![doc=include_str!("../readme.md")] pub mod core { - pub use bevy_mod_scripting_core::*; + pub use bevy_mod_scripting_core::*; } #[cfg(feature = "lua")] pub mod lua { - pub use bevy_mod_scripting_lua::*; + pub use bevy_mod_scripting_lua::*; } #[cfg(feature = "rhai")] pub mod rhai { - pub use bevy_mod_scripting_rhai::*; + pub use bevy_mod_scripting_rhai::*; } // #[cfg(feature = "rune")] @@ -21,7 +21,7 @@ pub mod rhai { use bevy::app::plugin_group; use bevy_mod_scripting_core::{ - bindings::CoreScriptGlobalsPlugin, BMSScriptingInfrastructurePlugin, + bindings::CoreScriptGlobalsPlugin, BMSScriptingInfrastructurePlugin, }; pub use bevy_mod_scripting_derive::*; pub use bevy_mod_scripting_functions::*; diff --git a/tests/script_tests.rs b/tests/script_tests.rs index cbb451abd6..06d93fb5c9 100644 --- a/tests/script_tests.rs +++ b/tests/script_tests.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use libtest_mimic::{Arguments, Failed, Trial}; use script_integration_test_harness::{ - execute_lua_integration_test, execute_rhai_integration_test, + execute_lua_integration_test, }; use test_utils::{discover_all_tests, Test, TestKind}; @@ -16,11 +16,22 @@ trait TestExecutor { impl TestExecutor for Test { fn execute(self) -> Result<(), Failed> { - println!("Running test: {:?}", self.path); match self.kind { - TestKind::Lua => execute_lua_integration_test(&self.path.to_string_lossy())?, - TestKind::Rhai => execute_rhai_integration_test(&self.path.to_string_lossy())?, + TestKind::Lua => { + println!("Running test: {:?}", self.path); + execute_lua_integration_test(&self.path.to_string_lossy())? + }, + TestKind::Rhai => { + if cfg!(feature = "rhai") { + println!("Running test: {:?}", self.path); + #[cfg(feature = "rhai")] + script_integration_test_harness::execute_rhai_integration_test(&self.path.to_string_lossy())? + } else { + println!("Skipping test: {:?}", self.path); + } + }, + } Ok(())