Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ bevy_mod_scripting_asset = { path = "crates/bevy_mod_scripting_asset", version =
bevy_mod_scripting_bindings = { path = "crates/bevy_mod_scripting_bindings", version = "0.16.0", default-features = false }
bevy_mod_scripting_display = { path = "crates/bevy_mod_scripting_display", version = "0.16.0", default-features = false }
bevy_mod_scripting_script = { path = "crates/bevy_mod_scripting_script", version = "0.16.0", default-features = false }

# bevy

bevy_mod_scripting_core = { path = "crates/bevy_mod_scripting_core", version = "0.16.0" }
Expand Down Expand Up @@ -230,7 +231,10 @@ bevy = { workspace = true, features = [
"bevy_asset",
"bevy_core_pipeline",
"bevy_sprite",
"bevy_state",
"x11",
"bevy_ui",
"default_font",
] }
bevy_platform = { workspace = true }
clap = { workspace = true, features = ["derive"] }
Expand Down Expand Up @@ -308,7 +312,11 @@ required-features = []

[[example]]
name = "runscript"
path = "examples/run-script.rs"
path = "examples/run_script.rs"

[[example]]
name = "script_loading"
path = "examples/script_loading.rs"

[workspace.lints.clippy]
panic = "deny"
Expand Down
Empty file added assets/scripts/dummy.lua
Empty file.
4 changes: 2 additions & 2 deletions crates/bevy_mod_scripting_bindings/src/globals/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use ::{
bevy_reflect::TypeRegistration,
};
use bevy_app::App;
use bevy_log::warn;
use bevy_log::{warn, warn_once};
use bevy_mod_scripting_asset::ScriptAsset;
use bevy_mod_scripting_derive::script_globals;
use bevy_platform::collections::HashMap;
Expand Down Expand Up @@ -155,7 +155,7 @@ impl CoreGlobals {
.insert(type_path.to_owned(), registration)
.is_some()
{
warn!(
warn_once!(
"duplicate entry inside `types` global for type: {}. {MSG_DUPLICATE_GLOBAL}",
type_path
)
Expand Down
8 changes: 6 additions & 2 deletions crates/bevy_mod_scripting_core/src/pipeline/machines.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ pub trait TransitionListener<State>: 'static + Send + Sync {
}

impl<P: IntoScriptPluginParams> ActiveMachines<P> {
/// Returns the currently processing machine
pub fn current_machine(&self) -> Option<&ScriptMachine<P>> {
self.machines.front()
}

/// Adds a listener to the back of the listener list for the state
pub fn push_listener<S: 'static>(&mut self, listener: impl TransitionListener<S> + 'static) {
let erased = listener.erased::<P>();
Expand Down Expand Up @@ -229,10 +234,9 @@ impl<P: IntoScriptPluginParams> ScriptMachine<P> {
match &mut self.internal_state {
MachineExecutionState::Initialized(machine_state) => {
debug!(
"State '{}' entered. For script: {}, {:?}",
"State '{}' entered. For script: {}",
machine_state.state_name(),
self.context.attachment,
self.context.attachment.script(),
);

if let Some(listeners) = listeners.get(&machine_state.as_ref().type_id()) {
Expand Down
64 changes: 61 additions & 3 deletions crates/bevy_mod_scripting_core/src/pipeline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ use bevy_ecs::{
system::{Command, Local, Res, ResMut, SystemParam},
world::World,
};
use bevy_log::debug;
use bevy_mod_scripting_asset::ScriptAsset;
use bevy_mod_scripting_bindings::WorldGuard;
use bevy_mod_scripting_display::DisplayProxy;
use bevy_platform::collections::HashSet;
use parking_lot::Mutex;
use smallvec::SmallVec;
Expand All @@ -22,7 +24,7 @@ use crate::{
context::ScriptingLoader,
error::ScriptError,
event::{
ForPlugin, ScriptAssetModifiedEvent, ScriptAttachedEvent, ScriptDetachedEvent,
ForPlugin, Recipients, ScriptAssetModifiedEvent, ScriptAttachedEvent, ScriptDetachedEvent,
ScriptErrorEvent,
},
pipeline::hooks::{
Expand Down Expand Up @@ -175,15 +177,22 @@ impl<T: GetScriptHandle + Event + Clone> LoadedWithHandles<'_, '_, T> {
self.loading.retain(|e| {
let handle = e.get_script_handle();
match self.asset_server.get_load_state(&handle) {
Some(LoadState::Loaded) => {
Some(LoadState::Loaded) | None => { // none in case this is added in memory and not through asset server
let strong = StrongScriptHandle::from_assets(handle, &mut self.assets);
if let Some(strong) = strong {
self.loaded_with_handles.push_front((e.clone(), strong));
}
false
}
Some(LoadState::Loading) => true,
_ => false,
state => {

debug!(
"discarding script lifecycle triggers with handle: {} due to asset load state: {state:?}",
handle.display()
);
false
}
}
});

Expand Down Expand Up @@ -345,6 +354,55 @@ impl PipelineRun for App {
}
}

#[derive(SystemParam)]
/// System parameter composing resources related to script loading, exposing utility methods for checking on your script pipeline status
pub struct ScriptPipelineState<'w, P: IntoScriptPluginParams> {
contexts: Res<'w, ScriptContext<P>>,
machines: Res<'w, ActiveMachines<P>>,
}

impl<'w, P: IntoScriptPluginParams> ScriptPipelineState<'w, P> {
/// Returns the handle to the currently processing script, if the handle came from an asset server and a path,
/// it can be used to display the currently loading script
pub fn currently_loading_script(&self) -> Option<Handle<ScriptAsset>> {
self.machines
.current_machine()
.map(|machine| machine.context.attachment.script())
}

/// Returns the number of scripts currently being processed,
/// this includes loads, reloads and removals, when this is zero, no processing is happening at the moment
pub fn num_processing_scripts(&self) -> usize {
self.machines.active_machines()
}

/// returns true if the current processing batch is completed,
/// a batch is completed when the last active processing machine is finished.
/// If new machines are added during the processing of a batch, that batch is "extended".
pub fn processing_batch_completed(&self) -> bool {
self.num_processing_scripts() == 0
}

/// Returns the number of scripts currently existing in contexts.
/// This corresponds to [`Recipients::AllScripts`], i.e. it counts 'residents' within contexts as a script
pub fn num_loaded_scripts(&self) -> usize {
Recipients::AllScripts
.get_recipients(self.contexts.clone())
.len()
}

/// returns a number between 0 and 100.0 to represent the current script pipeline progress,
/// 0 representing no progress made, and 100 all processing completed, together with the numbers used for the fraction loaded and total.
pub fn progress(&self) -> (f32, usize, usize) {
let fraction = self.num_loaded_scripts();
let total = self.num_processing_scripts() + fraction;
if total == 0 {
return (0.0, 0, 0);
}
((fraction as f32 / total as f32) * 100.0, fraction, total)
}
}

#[cfg(test)]
mod test {
use bevy_asset::{AssetApp, AssetId, AssetPlugin};
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_mod_scripting_core/src/pipeline/start.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::*;
use bevy_asset::AssetEvent;
use bevy_log::{debug, trace};

/// A handle to a script asset which can only be made from a strong handle
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -83,6 +84,7 @@ pub fn filter_script_attachments<P: IntoScriptPluginParams>(
mut filtered: EventWriter<ForPlugin<ScriptAttachedEvent, P>>,
) {
let mut batch = events.get_loaded().map(|(mut a, b)| {
trace!("dispatching script attachment event for: {a:?}");
*a.0.script_mut() = b.0;
ForPlugin::new(a)
});
Expand All @@ -106,6 +108,7 @@ pub fn filter_script_detachments<P: IntoScriptPluginParams>(
.map(ForPlugin::new);

if let Some(next) = batch.next() {
trace!("dispatching script dettachments for plugin");
filtered.write_batch(std::iter::once(next).chain(batch));
}
}
Expand All @@ -120,6 +123,7 @@ pub fn process_attachments<P: IntoScriptPluginParams>(
let contexts = contexts.read();
events.read().for_each(|wrapper| {
let attachment_event = wrapper.event();
debug!("received attachment event: {attachment_event:?}");
let id = attachment_event.0.script();
let mut context = Context {
attachment: attachment_event.0.clone(),
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_mod_scripting_core/src/script/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use ::{
mod context_key;
mod script_context;
use bevy_ecs::component::Component;
use bevy_log::trace;
use bevy_mod_scripting_asset::ScriptAsset;
use bevy_mod_scripting_script::ScriptAttachment;
pub use context_key::*;
Expand Down Expand Up @@ -65,13 +66,15 @@ impl ScriptComponent {
/// the removal of the script.
pub fn on_remove(mut world: DeferredWorld, context: HookContext) {
let context_keys = Self::get_context_keys_present(&world, context.entity);
trace!("on remove hook for script components: {context_keys:?}");
world.send_event_batch(context_keys.into_iter().map(ScriptDetachedEvent));
}

/// the lifecycle hook called when a script component is added to an entity, emits an appropriate event so we can handle
/// the addition of the script.
pub fn on_add(mut world: DeferredWorld, context: HookContext) {
let context_keys = Self::get_context_keys_present(&world, context.entity);
trace!("on add hook for script components: {context_keys:?}");
world.send_event_batch(context_keys.into_iter().map(ScriptAttachedEvent));
}
}
Expand Down
5 changes: 5 additions & 0 deletions crates/bevy_mod_scripting_core/src/script/script_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ impl<P: IntoScriptPluginParams> ScriptContextInner<P> {
})
}

/// Returns the count of residents as would be returned by [`Self::all_residents`]
pub fn all_residents_len(&self) -> usize {
self.map.values().map(|entry| entry.residents.len()).sum()
}

/// Retrieves the first resident from each context.
///
/// For example if using a single global context, and with 2 scripts:
Expand Down
6 changes: 5 additions & 1 deletion docs/src/ScriptPipeline/pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,8 @@ The script loading/unloading order will look as follows:
- the order in which components are attached/detached, will determing what order scripts will be processed
- scripts are processed one-by-one, i.e. each machine is ticked to completion before the next one is started
- meaning for example if two scripts are loaded, their `on_script_loaded` hooks will not run at the same "lockstep".
- loading/unloading might happen over multiple frames, depending on the pipeline's settings.
- loading/unloading might happen over multiple frames, depending on the pipeline's settings.

## Waiting for scripts to load

In order to check on the pipeline and figure out when everything is ready, you can use the `ScriptPipelineState<P>` system parameter in a system as shown in the `script_loading` [example](https://github.com/makspll/bevy_mod_scripting/blob/main/examples/script_loading.rs).
12 changes: 12 additions & 0 deletions docs/src/Summary/managing-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ fn load_script(asset_server: Res<AssetServer>, mut commands: Commands) {
commands.spawn(ScriptComponent(vec![handle]));
}
```

<div class="warning">

Prefer using strong asset handles, internal references will only persist weak versions of the handle, leaving you in control of the asset handle via the container component.

</div>

### Create `ScriptAsset` and Add It
```rust
# extern crate bevy;
Expand All @@ -64,6 +71,11 @@ fn add_script(mut script_assets: ResMut<Assets<ScriptAsset>>, mut commands: Comm
commands.spawn(ScriptComponent(vec![handle]));
}
```
<div class="warning">

Scripts added directly through assets, or via asset server without an asset path, will not contain path information, and logs will be slightly less useful.

</div>

## Static Scripts
You can use attach and detach commands, which will run the [script pipeline](../ScriptPipeline/pipeline.md), repeatedly until the requested script attachments are processed. If you don't want to incur big frametime slowdowns, you can instead send `ScriptAttachedEvent` and `ScriptDetachedEvent` manually, and let the pipeline pick these up as normal.
Expand Down
File renamed without changes.
Loading
Loading