From 70ea3269fa09ac11fcaf430b0dcf13d1d9bc5f40 Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 9 May 2022 14:40:47 +0100 Subject: [PATCH 1/6] allow multiple identical scripts on same entity + script instance id's --- .../assets/scripts/console_integration2.lua | 12 -- .../examples/console_integration.rs | 8 +- bevy_scripting/src/hosts/mod.rs | 123 ++++++++++++------ bevy_scripting/src/hosts/rlua_host.rs | 52 ++++---- 4 files changed, 111 insertions(+), 84 deletions(-) delete mode 100644 bevy_scripting/assets/scripts/console_integration2.lua diff --git a/bevy_scripting/assets/scripts/console_integration2.lua b/bevy_scripting/assets/scripts/console_integration2.lua deleted file mode 100644 index a452c4059f..0000000000 --- a/bevy_scripting/assets/scripts/console_integration2.lua +++ /dev/null @@ -1,12 +0,0 @@ -local a = 0 - -function on_update() - - if (a+50) % 100 == 0 then - -- print_to_console()() is defined in console_integration.rs - -- by the api provider - print_to_console(a) - end - - a = a + 1 -end \ No newline at end of file diff --git a/bevy_scripting/examples/console_integration.rs b/bevy_scripting/examples/console_integration.rs index b097465831..9d40400e5d 100644 --- a/bevy_scripting/examples/console_integration.rs +++ b/bevy_scripting/examples/console_integration.rs @@ -110,23 +110,27 @@ pub fn run_script_cmd( >, ) { if let Some(RunScriptCmd { path, entity }) = log.take() { - info!("Running script: scripts/{}", path); let handle = server.load::(&format!("scripts/{}", &path)); match entity { Some(e) => { if let Ok(mut scripts) = existing_scripts.get_mut(Entity::from_raw(e)) { + info!("Creating script: scripts/{} {:?}",&path, &entity); + scripts.scripts.push(Script::< as ScriptHost>::ScriptAssetType, >::new::>( path, handle )); + } else { log.reply_failed(format!("Something went wrong")); }; } None => { + info!("Creating script: scripts/{}",&path); + commands.spawn().insert(ScriptCollection::< as ScriptHost>::ScriptAssetType, > { @@ -185,6 +189,6 @@ pub struct DeleteScriptCmd { /// the name of the script pub name: String, - /// the entity the script is attached to (only one script can be attached to an entitty as of now) + /// the entity the script is attached to pub entity_id: u32, } diff --git a/bevy_scripting/src/hosts/mod.rs b/bevy_scripting/src/hosts/mod.rs index 41495adc70..95f5d54ef4 100644 --- a/bevy_scripting/src/hosts/mod.rs +++ b/bevy_scripting/src/hosts/mod.rs @@ -2,7 +2,7 @@ pub mod rlua_host; use anyhow::Result; use bevy::{asset::Asset, ecs::system::SystemState, prelude::*}; pub use rlua_host::*; -use std::collections::{HashMap, HashSet}; +use std::{collections::{HashMap, HashSet}, sync::atomic::{AtomicU32, Ordering}}; pub trait AddScriptHost { fn add_script_host(&mut self) -> &mut Self; @@ -12,19 +12,42 @@ pub trait CodeAsset: Asset { fn bytes(&self) -> &[u8]; } +/// Implementers can modify a script context in order to enable +/// API access. ScriptHosts call `attach_api` when creating scripts pub trait APIProvider: 'static + Default { type Ctx; fn attach_api(ctx: &Self::Ctx); } +#[derive(Component)] +pub struct ScriptCollection { + pub scripts: Vec>, +} + +#[derive(Default)] +pub struct ScriptContexts { + /// holds script contexts for all scripts given their instance ids + pub contexts: HashMap, +} + + +/// A struct defining an instance of a script asset pub struct Script { + + /// a strong handle to the script asset handle: Handle, + + /// the name of the script, usually its file name + relative asset path name: String, + + /// uniquely identifies the script instance (scripts which use the same asset don't necessarily have the same ID) + id: u32, } impl Script { pub fn new(name: String, handle: Handle) -> Self { - Self { handle, name } + static COUNTER:AtomicU32 = AtomicU32::new(0); + Self { handle, name, id:COUNTER.fetch_add(1,Ordering::Relaxed)} } pub fn name(&self) -> &str { @@ -33,10 +56,24 @@ impl Script { pub fn handle(&self) -> &Handle { &self.handle } + pub fn id(&self) -> u32 { + self.id + } + + fn reload_script ( + script: &Script, + script_assets: &Res>, + contexts : &mut ResMut> + ){ + // remove old context + contexts.contexts.remove(&script.id); + + // insert new re-loaded context + Self::insert_new_script_context(script,script_assets,contexts) + } fn insert_new_script_context( new_script: &Script, - entity: &Entity, script_assets: &Res>, contexts: &mut ResMut>, ) { @@ -53,15 +90,7 @@ impl Script { // allow plugging in an API H::ScriptAPIProvider::attach_api(&ctx); - let name_map = contexts.contexts.entry(entity.id()).or_default(); - - // if the script already exists on an entity, panic - // not allowed at least for now - if name_map.contains_key(&new_script.name) { - panic!("Attempted to attach script: {} to entity which already has another copy of this script attached", &new_script.name); - } - - name_map.insert(new_script.name.clone(), ctx); + contexts.contexts.insert(new_script.id(), ctx); } Err(_e) => { warn! {"Failed to load script: {}", &new_script.name} @@ -71,17 +100,6 @@ impl Script { } } -#[derive(Component)] -pub struct ScriptCollection { - pub scripts: Vec>, -} - -#[derive(Default)] -pub struct ScriptContexts { - /// holds script contexts for all scripts - /// and keeps track of which entities they're attached to - pub contexts: HashMap>, -} pub struct CachedScriptEventState<'w, 's, S: Send + Sync + 'static> { event_state: SystemState>, @@ -98,7 +116,6 @@ impl<'w, 's, S: Send + Sync + 'static> FromWorld for CachedScriptEventState<'w, pub fn script_add_synchronizer( query: Query< ( - Entity, &ScriptCollection, ChangeTrackers>, ), @@ -107,12 +124,11 @@ pub fn script_add_synchronizer( mut contexts: ResMut>, script_assets: Res>, ) { - query.for_each(|(entity, new_scripts, tracker)| { + query.for_each(|(new_scripts, tracker)| { if tracker.is_added() { new_scripts.scripts.iter().for_each(|new_script| { Script::::insert_new_script_context( new_script, - &entity, &script_assets, &mut contexts, ) @@ -122,32 +138,26 @@ pub fn script_add_synchronizer( // find out what's changed // we only care about added or removed scripts here // if the script asset gets changed we deal with that elsewhere - // TODO: reduce allocations in this function the change detection here is kinda clunky - let name_map = contexts.contexts.entry(entity.id()).or_default(); + let context_ids = contexts.contexts.keys().cloned().collect::>(); + let script_ids = new_scripts.scripts.iter() + .map(|s| s.id()) + .collect::>(); - let previous_scripts = name_map.keys().cloned().collect::>(); - - let current_scripts = new_scripts - .scripts - .iter() - .map(|s| s.name.clone()) - .collect::>(); - - // find new/removed scripts - let removed_scripts = previous_scripts.difference(¤t_scripts); - let added_scripts = current_scripts.difference(&previous_scripts); + let removed_scripts = context_ids.difference(&script_ids); + let added_scripts = script_ids.difference(&context_ids); for r in removed_scripts { - name_map.remove(r); + contexts.contexts.remove(r); } for a in added_scripts { - let script = new_scripts.scripts.iter().find(|e| &e.name == a).unwrap(); + let script = new_scripts.scripts + .iter() + .find(|e| &e.id == a).unwrap(); Script::::insert_new_script_context( script, - &entity, &script_assets, &mut contexts, ) @@ -156,7 +166,7 @@ pub fn script_add_synchronizer( }) } -pub fn script_remove_synchronizer( +pub fn script_remove_synchronizer( query: RemovedComponents>, mut contexts: ResMut>, ) { @@ -170,6 +180,33 @@ pub fn script_remove_synchronizer( }) } +pub fn script_hot_reload_handler( + mut events : EventReader>, + scripts : Query<&ScriptCollection>, + script_assets: Res>, + mut contexts: ResMut> +) { + for e in events.iter() { + match e { + AssetEvent::Modified { handle } => { + // find script using this handle by handle id + 'outer : for scripts in scripts.iter() { + for script in &scripts.scripts { + if &script.handle == handle{ + // reload the script + Script::::reload_script(&script,&script_assets,&mut contexts); + + // update other changed assets + break 'outer + } + } + } + }, + _ => continue, + } + } +} + pub fn script_event_handler(world: &mut World) { world.resource_scope( |world, mut cached_state: Mut>| { diff --git a/bevy_scripting/src/hosts/rlua_host.rs b/bevy_scripting/src/hosts/rlua_host.rs index 952a75ea0c..358076d2c6 100644 --- a/bevy_scripting/src/hosts/rlua_host.rs +++ b/bevy_scripting/src/hosts/rlua_host.rs @@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex}; use crate::{ script_add_synchronizer, script_event_handler, script_remove_synchronizer, APIProvider, - CachedScriptEventState, CodeAsset, ScriptContexts, ScriptHost, + CachedScriptEventState, CodeAsset, ScriptContexts, ScriptHost, script_hot_reload_handler, }; use anyhow::{anyhow, Result}; use beau_collector::BeauCollector as _; @@ -94,8 +94,9 @@ impl>> ScriptHost for RLuaScriptHost { SystemSet::new() .with_system(script_add_synchronizer::) .with_system(script_remove_synchronizer::) + .with_system(script_hot_reload_handler::) .with_system(script_event_handler::.exclusive_system().at_end()), - ); + ); } fn load_script(script: &[u8], script_name: &str) -> Result { @@ -120,33 +121,30 @@ impl>> ScriptHost for RLuaScriptHost { world.resource_scope(|world, res: Mut>| { res.contexts .values() - .flat_map(|ctx| { + .map(|ctx| { let world_ptr = LuaLightUserData(world as *mut World as *mut c_void); - ctx.values().map(move |ctx| { - let lua_ctx = ctx.lock().unwrap(); - - lua_ctx.context::<_, Result<()>>(|lua_ctx| { - let globals = lua_ctx.globals(); - globals.set("world", world_ptr)?; - - // event order is preserved, but scripts can't rely on any temporal - // guarantees when it comes to other scripts callbacks, - // at least for now - for event in events.iter() { - let f: Function = match globals.get(event.hook_name.clone()) { - Ok(f) => f, - Err(_) => continue, // not subscribed to this event - }; - - f.call::(event.args.clone().to_lua_multi(lua_ctx)?) - .map_err(|e| anyhow!("Runtime LUA error: {}", e))?; - } - - Ok(()) - }) + let lua_ctx = ctx.lock().unwrap(); + + lua_ctx.context::<_, Result<()>>(|lua_ctx| { + let globals = lua_ctx.globals(); + globals.set("world", world_ptr)?; + + // event order is preserved, but scripts can't rely on any temporal + // guarantees when it comes to other scripts callbacks, + // at least for now + for event in events.iter() { + let f: Function = match globals.get(event.hook_name.clone()) { + Ok(f) => f, + Err(_) => continue, // not subscribed to this event + }; + + f.call::(event.args.clone().to_lua_multi(lua_ctx)?) + .map_err(|e| anyhow!("Runtime LUA error: {}", e))?; + } + + Ok(()) }) - }) - .bcollect() + }).bcollect() }) } } From b490f05a527e0f08d9d4c4b70422f7bdee40582e Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 9 May 2022 15:23:16 +0100 Subject: [PATCH 2/6] added some docstrings, fixed hot reload problem --- bevy_scripting/src/hosts/mod.rs | 89 +++++++++++++++++++++------ bevy_scripting/src/hosts/rlua_host.rs | 17 ++--- bevy_scripting/src/lib.rs | 4 ++ 3 files changed, 83 insertions(+), 27 deletions(-) diff --git a/bevy_scripting/src/hosts/mod.rs b/bevy_scripting/src/hosts/mod.rs index 95f5d54ef4..8fdc26ea23 100644 --- a/bevy_scripting/src/hosts/mod.rs +++ b/bevy_scripting/src/hosts/mod.rs @@ -4,10 +4,9 @@ use bevy::{asset::Asset, ecs::system::SystemState, prelude::*}; pub use rlua_host::*; use std::{collections::{HashMap, HashSet}, sync::atomic::{AtomicU32, Ordering}}; -pub trait AddScriptHost { - fn add_script_host(&mut self) -> &mut Self; -} - +/// All code assets share this common interface. +/// When adding a new code asset don't forget to implement asset loading +/// and inserting appropriate systems when registering with the app pub trait CodeAsset: Asset { fn bytes(&self) -> &[u8]; } @@ -15,23 +14,38 @@ pub trait CodeAsset: Asset { /// Implementers can modify a script context in order to enable /// API access. ScriptHosts call `attach_api` when creating scripts pub trait APIProvider: 'static + Default { + /// The type of script context this api provider handles type Ctx; + + /// provide the given script context with the API permamently fn attach_api(ctx: &Self::Ctx); } #[derive(Component)] +/// The component storing many scripts. +/// Scripts receive information about the entity they are attached to +/// Scripts have unique identifiers and hence multiple copies of the same script +/// can be attached to the same entity pub struct ScriptCollection { pub scripts: Vec>, } #[derive(Default)] +/// A resource storing the script contexts for each script instance. +/// The reason we need this is to split the world borrow in our handle event systems, but this +/// has the added benefit that users don't see the contexts at all, and we can provide +/// generic handling for each new/removed script in one place. +/// +/// We keep this public for now since there is no API for communicating with scripts +/// outside of events. Later this might change. pub struct ScriptContexts { /// holds script contexts for all scripts given their instance ids pub contexts: HashMap, } -/// A struct defining an instance of a script asset +/// A struct defining an instance of a script asset. +/// Multiple instances of the same script can exist on the same entity (unlike in Unity for example) pub struct Script { /// a strong handle to the script asset @@ -45,22 +59,37 @@ pub struct Script { } impl Script { + + /// creates a new script instance with the given name and asset handle + /// automatically gives this script instance a unique ID. + /// No two scripts instances ever share the same ID pub fn new(name: String, handle: Handle) -> Self { static COUNTER:AtomicU32 = AtomicU32::new(0); Self { handle, name, id:COUNTER.fetch_add(1,Ordering::Relaxed)} } + + #[inline(always)] + /// returns the name of the script pub fn name(&self) -> &str { &self.name } + + #[inline(always)] + /// returns the asset handle which this script is executing pub fn handle(&self) -> &Handle { &self.handle } + + #[inline(always)] + /// returns the unique ID of this script instance pub fn id(&self) -> u32 { self.id } - fn reload_script ( + /// reloads the script by deleting the old context and inserting a new one + /// if the script context never existed, it will after this call. + pub(crate) fn reload_script ( script: &Script, script_assets: &Res>, contexts : &mut ResMut> @@ -72,7 +101,8 @@ impl Script { Self::insert_new_script_context(script,script_assets,contexts) } - fn insert_new_script_context( + /// inserts a new script context for the given script + pub(crate) fn insert_new_script_context( new_script: &Script, script_assets: &Res>, contexts: &mut ResMut>, @@ -101,7 +131,8 @@ impl Script { } -pub struct CachedScriptEventState<'w, 's, S: Send + Sync + 'static> { +/// system state for exclusive systems dealing with script events +pub(crate) struct CachedScriptEventState<'w, 's, S: Send + Sync + 'static> { event_state: SystemState>, } @@ -113,7 +144,8 @@ impl<'w, 's, S: Send + Sync + 'static> FromWorld for CachedScriptEventState<'w, } } -pub fn script_add_synchronizer( +/// Handles creating contexts for new/modified scripts +pub(crate) fn script_add_synchronizer( query: Query< ( &ScriptCollection, @@ -166,7 +198,8 @@ pub fn script_add_synchronizer( }) } -pub fn script_remove_synchronizer( +/// Handles the removal of script components and their contexts +pub(crate) fn script_remove_synchronizer( query: RemovedComponents>, mut contexts: ResMut>, ) { @@ -180,7 +213,8 @@ pub fn script_remove_synchronizer( }) } -pub fn script_hot_reload_handler( +/// Reloads hot-reloaded scripts +pub(crate) fn script_hot_reload_handler( mut events : EventReader>, scripts : Query<&ScriptCollection>, script_assets: Res>, @@ -190,14 +224,10 @@ pub fn script_hot_reload_handler( match e { AssetEvent::Modified { handle } => { // find script using this handle by handle id - 'outer : for scripts in scripts.iter() { + for scripts in scripts.iter() { for script in &scripts.scripts { - if &script.handle == handle{ - // reload the script + if &script.handle == handle { Script::::reload_script(&script,&script_assets,&mut contexts); - - // update other changed assets - break 'outer } } } @@ -207,7 +237,8 @@ pub fn script_hot_reload_handler( } } -pub fn script_event_handler(world: &mut World) { +/// Lets the script host handle all script events +pub(crate) fn script_event_handler(world: &mut World) { world.resource_scope( |world, mut cached_state: Mut>| { // we need to clone the events otherwise we cannot perform the subsequent query for scripts @@ -227,16 +258,34 @@ pub fn script_event_handler(world: &mut World) { ); } + +/// A script host is the interface between your rust application +/// and the scripts in some interpreted language. pub trait ScriptHost: Send + Sync + 'static { type ScriptContext: Send + Sync + 'static; type ScriptEventType: Send + Sync + Clone + 'static; type ScriptAssetType: CodeAsset; type ScriptAPIProvider: APIProvider; + /// Loads a script in byte array format, the script name can be used + /// to send useful errors. fn load_script(path: &[u8], script_name: &str) -> Result; + + /// the main point of contact with the bevy world. + /// Scripts are called with appropriate events in the event order fn handle_events(world: &mut World, events: &[Self::ScriptEventType]) -> Result<()>; - /// registers the script host with the given app, and stage. - /// all script events generated will be handled at the end of this stage. Ideally place after update + /// Registers the script host with the given app, and stage. + /// all script events generated will be handled at the end of this stage. Ideally place after any game logic + /// which can spawn/remove/modify scripts to avoid frame lag. (typically `CoreStage::Post_Update`) fn register_with_app(app: &mut App, stage: impl StageLabel); } + + +/// Trait for app builder notation +pub trait AddScriptHost { + + /// registers the given script host with your app + fn add_script_host(&mut self) -> &mut Self; +} + diff --git a/bevy_scripting/src/hosts/rlua_host.rs b/bevy_scripting/src/hosts/rlua_host.rs index 358076d2c6..649093cbac 100644 --- a/bevy_scripting/src/hosts/rlua_host.rs +++ b/bevy_scripting/src/hosts/rlua_host.rs @@ -1,8 +1,6 @@ use std::ffi::c_void; use std::marker::PhantomData; - use std::sync::{Arc, Mutex}; - use crate::{ script_add_synchronizer, script_event_handler, script_remove_synchronizer, APIProvider, CachedScriptEventState, CodeAsset, ScriptContexts, ScriptHost, script_hot_reload_handler, @@ -10,17 +8,14 @@ use crate::{ use anyhow::{anyhow, Result}; use beau_collector::BeauCollector as _; use bevy::asset::{AssetLoader, LoadedAsset}; - -use bevy::prelude::{ - App, ExclusiveSystemDescriptorCoercion, IntoExclusiveSystem, Mut, StageLabel, SystemSet, World, -}; +use bevy::prelude::*; use bevy::reflect::TypeUuid; - use rlua::prelude::*; use rlua::{Context, Function, Lua, MultiValue, ToLua, ToLuaMulti}; #[derive(Debug, TypeUuid)] #[uuid = "39cadc56-aa9c-4543-8640-a018b74b5052"] +/// A lua code file in bytes pub struct LuaFile { pub bytes: Arc<[u8]>, } @@ -32,6 +27,7 @@ impl CodeAsset for LuaFile { } #[derive(Default)] +/// Asset loader for lua scripts pub struct LuaLoader; impl AssetLoader for LuaLoader { @@ -52,6 +48,7 @@ impl AssetLoader for LuaLoader { } /// defines a value allowed to be passed as lua script arguments for callbacks +/// TODO: expand this #[derive(Clone)] pub enum LuaCallbackArgument { Integer(usize), @@ -64,13 +61,18 @@ impl<'lua> ToLua<'lua> for LuaCallbackArgument { } } } + #[derive(Clone)] +/// A Lua Hook. The result of creating this event will be +/// a call to the lua script with the hook_name and the given arguments pub struct LuaEvent { pub hook_name: String, pub args: Vec, } + #[derive(Default)] +/// Rlua script host, enables Lua scripting provided by the Rlua library. pub struct RLuaScriptHost { _ph: PhantomData, } @@ -165,3 +167,4 @@ impl>> RLuaScriptHost { }); } } + diff --git a/bevy_scripting/src/lib.rs b/bevy_scripting/src/lib.rs index 8dd7d53f8f..b982f00a5f 100644 --- a/bevy_scripting/src/lib.rs +++ b/bevy_scripting/src/lib.rs @@ -1,8 +1,12 @@ use bevy::prelude::*; + +/// All script host related things pub mod hosts; + pub use hosts::*; #[derive(Default)] +/// Bevy plugin enabling run-time scripting pub struct ScriptingPlugin; impl Plugin for ScriptingPlugin { From 6519efc60d9390fabd69d7ecc5f7c0f2944ebc11 Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 9 May 2022 15:27:45 +0100 Subject: [PATCH 3/6] changed readme.md --- readme.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 8718dfdd62..0dceb21ace 100644 --- a/readme.md +++ b/readme.md @@ -15,14 +15,16 @@ The API will likely change in the future as more scripting support is rolled out ## State of this crate - [x] Script host interface +- [x] Hot re-loading scripts (on script asset changes, scripts using those assets are re-started) - [x] Rlua integration - [ ] Rhai integration - [x] Customisable Lua API - [x] Event based hooks (i.e. on_update) - [ ] Flexible event scheduling (i.e. allow handling events at different stages rather than a single stage based on the event) - [x] Multiple scripts per entity -- [ ] Multiple instances of the same script on one entity -- [ ] Improved Ergonomics +- [x] Multiple instances of the same script on one entity (unlike Unity) +- [ ] Improved Ergonomics (some types are cumbersome right now) +- [ ] General Bevy API for all script hosts (i.e. Add component, remove component etc.). Blocked by https://github.com/bevyengine/bevy/issues/4474 - [ ] More extensive callback argument type support - [ ] Tests From 857f29f29f7b543f4790f6bd320a4cd002f5d182 Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 9 May 2022 15:28:01 +0100 Subject: [PATCH 4/6] changed readme.md --- readme.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/readme.md b/readme.md index 0dceb21ace..95a7a0faeb 100644 --- a/readme.md +++ b/readme.md @@ -28,9 +28,6 @@ The API will likely change in the future as more scripting support is rolled out - [ ] More extensive callback argument type support - [ ] Tests - -As of now script component removals do not work properly just yet - ## Usage ### Installation From 6458271dd73e76235250bd9cc04c10c65f7f94b5 Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 9 May 2022 15:28:36 +0100 Subject: [PATCH 5/6] cargo fmt --- .../examples/console_integration.rs | 8 +- bevy_scripting/src/hosts/mod.rs | 84 ++++++++++--------- bevy_scripting/src/hosts/rlua_host.rs | 20 ++--- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/bevy_scripting/examples/console_integration.rs b/bevy_scripting/examples/console_integration.rs index 9d40400e5d..2b4e5f5742 100644 --- a/bevy_scripting/examples/console_integration.rs +++ b/bevy_scripting/examples/console_integration.rs @@ -110,26 +110,24 @@ pub fn run_script_cmd( >, ) { if let Some(RunScriptCmd { path, entity }) = log.take() { - let handle = server.load::(&format!("scripts/{}", &path)); match entity { Some(e) => { if let Ok(mut scripts) = existing_scripts.get_mut(Entity::from_raw(e)) { - info!("Creating script: scripts/{} {:?}",&path, &entity); + info!("Creating script: scripts/{} {:?}", &path, &entity); scripts.scripts.push(Script::< as ScriptHost>::ScriptAssetType, >::new::>( path, handle )); - } else { log.reply_failed(format!("Something went wrong")); }; } None => { - info!("Creating script: scripts/{}",&path); + info!("Creating script: scripts/{}", &path); commands.spawn().insert(ScriptCollection::< as ScriptHost>::ScriptAssetType, @@ -189,6 +187,6 @@ pub struct DeleteScriptCmd { /// the name of the script pub name: String, - /// the entity the script is attached to + /// the entity the script is attached to pub entity_id: u32, } diff --git a/bevy_scripting/src/hosts/mod.rs b/bevy_scripting/src/hosts/mod.rs index 8fdc26ea23..4a5eb0ce8a 100644 --- a/bevy_scripting/src/hosts/mod.rs +++ b/bevy_scripting/src/hosts/mod.rs @@ -2,10 +2,13 @@ pub mod rlua_host; use anyhow::Result; use bevy::{asset::Asset, ecs::system::SystemState, prelude::*}; pub use rlua_host::*; -use std::{collections::{HashMap, HashSet}, sync::atomic::{AtomicU32, Ordering}}; +use std::{ + collections::{HashMap, HashSet}, + sync::atomic::{AtomicU32, Ordering}, +}; /// All code assets share this common interface. -/// When adding a new code asset don't forget to implement asset loading +/// When adding a new code asset don't forget to implement asset loading /// and inserting appropriate systems when registering with the app pub trait CodeAsset: Asset { fn bytes(&self) -> &[u8]; @@ -32,23 +35,21 @@ pub struct ScriptCollection { #[derive(Default)] /// A resource storing the script contexts for each script instance. -/// The reason we need this is to split the world borrow in our handle event systems, but this -/// has the added benefit that users don't see the contexts at all, and we can provide +/// The reason we need this is to split the world borrow in our handle event systems, but this +/// has the added benefit that users don't see the contexts at all, and we can provide /// generic handling for each new/removed script in one place. -/// -/// We keep this public for now since there is no API for communicating with scripts +/// +/// We keep this public for now since there is no API for communicating with scripts /// outside of events. Later this might change. pub struct ScriptContexts { /// holds script contexts for all scripts given their instance ids pub contexts: HashMap, } - /// A struct defining an instance of a script asset. /// Multiple instances of the same script can exist on the same entity (unlike in Unity for example) pub struct Script { - - /// a strong handle to the script asset + /// a strong handle to the script asset handle: Handle, /// the name of the script, usually its file name + relative asset path @@ -59,16 +60,18 @@ pub struct Script { } impl Script { - /// creates a new script instance with the given name and asset handle /// automatically gives this script instance a unique ID. - /// No two scripts instances ever share the same ID + /// No two scripts instances ever share the same ID pub fn new(name: String, handle: Handle) -> Self { - static COUNTER:AtomicU32 = AtomicU32::new(0); - Self { handle, name, id:COUNTER.fetch_add(1,Ordering::Relaxed)} + static COUNTER: AtomicU32 = AtomicU32::new(0); + Self { + handle, + name, + id: COUNTER.fetch_add(1, Ordering::Relaxed), + } } - #[inline(always)] /// returns the name of the script pub fn name(&self) -> &str { @@ -89,19 +92,19 @@ impl Script { /// reloads the script by deleting the old context and inserting a new one /// if the script context never existed, it will after this call. - pub(crate) fn reload_script ( + pub(crate) fn reload_script( script: &Script, script_assets: &Res>, - contexts : &mut ResMut> - ){ - // remove old context + contexts: &mut ResMut>, + ) { + // remove old context contexts.contexts.remove(&script.id); // insert new re-loaded context - Self::insert_new_script_context(script,script_assets,contexts) + Self::insert_new_script_context(script, script_assets, contexts) } - /// inserts a new script context for the given script + /// inserts a new script context for the given script pub(crate) fn insert_new_script_context( new_script: &Script, script_assets: &Res>, @@ -130,7 +133,6 @@ impl Script { } } - /// system state for exclusive systems dealing with script events pub(crate) struct CachedScriptEventState<'w, 's, S: Send + Sync + 'static> { event_state: SystemState>, @@ -172,7 +174,9 @@ pub(crate) fn script_add_synchronizer( // if the script asset gets changed we deal with that elsewhere let context_ids = contexts.contexts.keys().cloned().collect::>(); - let script_ids = new_scripts.scripts.iter() + let script_ids = new_scripts + .scripts + .iter() .map(|s| s.id()) .collect::>(); @@ -184,9 +188,7 @@ pub(crate) fn script_add_synchronizer( } for a in added_scripts { - let script = new_scripts.scripts - .iter() - .find(|e| &e.id == a).unwrap(); + let script = new_scripts.scripts.iter().find(|e| &e.id == a).unwrap(); Script::::insert_new_script_context( script, @@ -213,31 +215,35 @@ pub(crate) fn script_remove_synchronizer( }) } -/// Reloads hot-reloaded scripts +/// Reloads hot-reloaded scripts pub(crate) fn script_hot_reload_handler( - mut events : EventReader>, - scripts : Query<&ScriptCollection>, + mut events: EventReader>, + scripts: Query<&ScriptCollection>, script_assets: Res>, - mut contexts: ResMut> + mut contexts: ResMut>, ) { for e in events.iter() { match e { AssetEvent::Modified { handle } => { - // find script using this handle by handle id + // find script using this handle by handle id for scripts in scripts.iter() { for script in &scripts.scripts { - if &script.handle == handle { - Script::::reload_script(&script,&script_assets,&mut contexts); + if &script.handle == handle { + Script::::reload_script( + &script, + &script_assets, + &mut contexts, + ); } } } - }, + } _ => continue, } } } -/// Lets the script host handle all script events +/// Lets the script host handle all script events pub(crate) fn script_event_handler(world: &mut World) { world.resource_scope( |world, mut cached_state: Mut>| { @@ -258,7 +264,6 @@ pub(crate) fn script_event_handler(world: &mut World) { ); } - /// A script host is the interface between your rust application /// and the scripts in some interpreted language. pub trait ScriptHost: Send + Sync + 'static { @@ -267,25 +272,22 @@ pub trait ScriptHost: Send + Sync + 'static { type ScriptAssetType: CodeAsset; type ScriptAPIProvider: APIProvider; - /// Loads a script in byte array format, the script name can be used + /// Loads a script in byte array format, the script name can be used /// to send useful errors. fn load_script(path: &[u8], script_name: &str) -> Result; - + /// the main point of contact with the bevy world. /// Scripts are called with appropriate events in the event order fn handle_events(world: &mut World, events: &[Self::ScriptEventType]) -> Result<()>; /// Registers the script host with the given app, and stage. - /// all script events generated will be handled at the end of this stage. Ideally place after any game logic + /// all script events generated will be handled at the end of this stage. Ideally place after any game logic /// which can spawn/remove/modify scripts to avoid frame lag. (typically `CoreStage::Post_Update`) fn register_with_app(app: &mut App, stage: impl StageLabel); } - /// Trait for app builder notation pub trait AddScriptHost { - /// registers the given script host with your app fn add_script_host(&mut self) -> &mut Self; } - diff --git a/bevy_scripting/src/hosts/rlua_host.rs b/bevy_scripting/src/hosts/rlua_host.rs index 649093cbac..f37c75ccd8 100644 --- a/bevy_scripting/src/hosts/rlua_host.rs +++ b/bevy_scripting/src/hosts/rlua_host.rs @@ -1,9 +1,7 @@ -use std::ffi::c_void; -use std::marker::PhantomData; -use std::sync::{Arc, Mutex}; use crate::{ - script_add_synchronizer, script_event_handler, script_remove_synchronizer, APIProvider, - CachedScriptEventState, CodeAsset, ScriptContexts, ScriptHost, script_hot_reload_handler, + script_add_synchronizer, script_event_handler, script_hot_reload_handler, + script_remove_synchronizer, APIProvider, CachedScriptEventState, CodeAsset, ScriptContexts, + ScriptHost, }; use anyhow::{anyhow, Result}; use beau_collector::BeauCollector as _; @@ -12,6 +10,9 @@ use bevy::prelude::*; use bevy::reflect::TypeUuid; use rlua::prelude::*; use rlua::{Context, Function, Lua, MultiValue, ToLua, ToLuaMulti}; +use std::ffi::c_void; +use std::marker::PhantomData; +use std::sync::{Arc, Mutex}; #[derive(Debug, TypeUuid)] #[uuid = "39cadc56-aa9c-4543-8640-a018b74b5052"] @@ -64,13 +65,12 @@ impl<'lua> ToLua<'lua> for LuaCallbackArgument { #[derive(Clone)] /// A Lua Hook. The result of creating this event will be -/// a call to the lua script with the hook_name and the given arguments +/// a call to the lua script with the hook_name and the given arguments pub struct LuaEvent { pub hook_name: String, pub args: Vec, } - #[derive(Default)] /// Rlua script host, enables Lua scripting provided by the Rlua library. pub struct RLuaScriptHost { @@ -98,7 +98,7 @@ impl>> ScriptHost for RLuaScriptHost { .with_system(script_remove_synchronizer::) .with_system(script_hot_reload_handler::) .with_system(script_event_handler::.exclusive_system().at_end()), - ); + ); } fn load_script(script: &[u8], script_name: &str) -> Result { @@ -146,7 +146,8 @@ impl>> ScriptHost for RLuaScriptHost { Ok(()) }) - }).bcollect() + }) + .bcollect() }) } } @@ -167,4 +168,3 @@ impl>> RLuaScriptHost { }); } } - From bb6570310abd38bbfdbd9d2592d5dd9f67bb4caa Mon Sep 17 00:00:00 2001 From: makspll Date: Mon, 9 May 2022 15:29:12 +0100 Subject: [PATCH 6/6] clippy fix --- bevy_scripting/src/hosts/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bevy_scripting/src/hosts/mod.rs b/bevy_scripting/src/hosts/mod.rs index 4a5eb0ce8a..d733ed39cf 100644 --- a/bevy_scripting/src/hosts/mod.rs +++ b/bevy_scripting/src/hosts/mod.rs @@ -230,7 +230,7 @@ pub(crate) fn script_hot_reload_handler( for script in &scripts.scripts { if &script.handle == handle { Script::::reload_script( - &script, + script, &script_assets, &mut contexts, );