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
7 changes: 6 additions & 1 deletion bevy_mod_scripting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ path = "examples/console_integration_rhai.rs"

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


[[example]]
name = "event_recipients"
path = "examples/event_recipients.rs"
6 changes: 6 additions & 0 deletions bevy_mod_scripting/assets/scripts/event_recipients.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@


function on_event(id)
print(string.format("on_event, script_id: %d, Handling:",script))
print(string.format("\t-> id: %d", id))
end
5 changes: 3 additions & 2 deletions bevy_mod_scripting/examples/complex_game_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use bevy::{core::FixedTimestep, prelude::*};
use bevy_console::ConsolePlugin;
use bevy_event_priority::PriorityEventWriter;
use bevy_mod_scripting::{
APIProvider, AddScriptHost, AddScriptHostHandler, LuaEvent, LuaFile, RLuaScriptHost, Script,
ScriptCollection, ScriptingPlugin,
APIProvider, AddScriptHost, AddScriptHostHandler, LuaEvent, LuaFile, RLuaScriptHost,
Recipients, Script, ScriptCollection, ScriptingPlugin,
};
use rand::prelude::SliceRandom;
use rlua::{Lua, ToLua};
Expand Down Expand Up @@ -55,6 +55,7 @@ fn fire_random_event(w: &mut PriorityEventWriter<LuaEvent<MyLuaArg>>, events: &[
LuaEvent {
hook_name: v.0.to_string(),
args: vec![arg],
recipients: Recipients::All,
},
v.1,
)
Expand Down
3 changes: 2 additions & 1 deletion bevy_mod_scripting/examples/console_integration_lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use bevy::{ecs::event::Events, prelude::*};
use bevy_console::{AddConsoleCommand, ConsoleCommand, ConsolePlugin, PrintConsoleLine};
use bevy_mod_scripting::{
events::PriorityEventWriter, APIProvider, AddScriptHost, AddScriptHostHandler, LuaEvent,
LuaFile, RLuaScriptHost, Script, ScriptCollection, ScriptingPlugin,
LuaFile, RLuaScriptHost, Recipients, Script, ScriptCollection, ScriptingPlugin,
};
use rlua::{Lua, ToLua};
use std::sync::Mutex;
Expand Down Expand Up @@ -51,6 +51,7 @@ pub fn trigger_on_update_lua(mut w: PriorityEventWriter<LuaEvent<MyLuaArg>>) {
let event = LuaEvent {
hook_name: "on_update".to_string(),
args: Vec::default(),
recipients: Recipients::All,
};

w.send(event, 0);
Expand Down
6 changes: 4 additions & 2 deletions bevy_mod_scripting/examples/console_integration_rhai.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use bevy::{ecs::event::Events, prelude::*};
use bevy_console::{AddConsoleCommand, ConsoleCommand, ConsolePlugin, PrintConsoleLine};
use bevy_mod_scripting::{
events::PriorityEventWriter, APIProvider, AddScriptHost, AddScriptHostHandler, RhaiAPIProvider,
RhaiContext, RhaiEvent, RhaiFile, RhaiScriptHost, Script, ScriptCollection, ScriptingPlugin,
events::PriorityEventWriter, APIProvider, AddScriptHost, AddScriptHostHandler, Recipients,
RhaiAPIProvider, RhaiContext, RhaiEvent, RhaiFile, RhaiScriptHost, Script, ScriptCollection,
ScriptingPlugin,
};
use rhai::FuncArgs;

Expand Down Expand Up @@ -49,6 +50,7 @@ pub fn trigger_on_update_rhai(mut w: PriorityEventWriter<RhaiEvent<RhaiEventArgs
let event = RhaiEvent {
hook_name: "on_update".to_string(),
args: RhaiEventArgs {},
recipients: Recipients::All,
};

w.send(event, 0);
Expand Down
126 changes: 126 additions & 0 deletions bevy_mod_scripting/examples/event_recipients.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use bevy::prelude::*;
use bevy_console::ConsolePlugin;
use bevy_event_priority::PriorityEventWriter;
use bevy_mod_scripting::{
APIProvider, AddScriptHost, AddScriptHostHandler, LuaEvent, LuaFile, RLuaScriptHost,
Recipients, Script, ScriptCollection, ScriptingPlugin,
};
use rand::prelude::SliceRandom;
use rlua::{Lua, ToLua};
use std::sync::atomic::Ordering::Relaxed;
use std::sync::{atomic::AtomicU32, Mutex};

#[derive(Clone)]
pub struct MyLuaArg(usize);

impl<'lua> ToLua<'lua> for MyLuaArg {
fn to_lua(self, lua: rlua::Context<'lua>) -> rlua::Result<rlua::Value<'lua>> {
self.0.to_lua(lua)
}
}

#[derive(Default)]
pub struct LuaAPIProvider {}

/// the custom Lua api, world is provided via a global pointer,
/// and callbacks are defined only once at script creation
impl APIProvider for LuaAPIProvider {
type Ctx = Mutex<Lua>;
fn attach_api(ctx: &mut Self::Ctx) {
// callbacks can receive any `ToLuaMulti` arguments, here '()' and
// return any `FromLuaMulti` arguments, here a `usize`
// check the Rlua documentation for more details
RLuaScriptHost::<MyLuaArg, Self>::register_api_callback(
"print",
|_ctx, msg: String| {
info!("{}", msg);
Ok(())
},
ctx,
)
}
}

static COUNTER: AtomicU32 = AtomicU32::new(0);

/// utility for generating random events from a list
fn fire_random_event(w: &mut PriorityEventWriter<LuaEvent<MyLuaArg>>, events: &[ScriptEventData]) {
let mut rng = rand::thread_rng();
let id = COUNTER.fetch_add(1, Relaxed);
let arg = MyLuaArg(id as usize);
let event = events
.choose(&mut rng)
.map(|v| LuaEvent {
hook_name: v.0.to_string(),
args: vec![arg],
recipients: v.1.clone(),
})
.unwrap();

info!(
"\t - event: {},\t recipients: {:?},\t id: {}",
event.hook_name, event.recipients, id
);
w.send(event, 0);
}

fn do_update(mut w: PriorityEventWriter<LuaEvent<MyLuaArg>>) {
info!("Update, firing:");

let all_events = [
ScriptEventData("on_event", Recipients::All),
ScriptEventData("on_event", Recipients::ScriptID(0)),
ScriptEventData("on_event", Recipients::ScriptID(1)),
ScriptEventData(
"on_event",
Recipients::ScriptName("scripts/event_recipients.lua".to_owned()),
),
];

// fire random event, for any stages
fire_random_event(&mut w, &all_events);
}

#[derive(Clone)]
pub struct ScriptEventData(&'static str, Recipients);

fn load_our_scripts(server: Res<AssetServer>, mut commands: Commands) {
// spawn two identical scripts
// id's will be 0 and 1
let path = "scripts/event_recipients.lua";
let handle = server.load::<LuaFile, &str>(path);
let scripts = (0..2)
.into_iter()
.map(|_| {
Script::<LuaFile>::new::<RLuaScriptHost<MyLuaArg, LuaAPIProvider>>(
path.to_string(),
handle.clone(),
)
})
.collect();

commands
.spawn()
.insert(ScriptCollection::<LuaFile> { scripts });
}

fn main() -> std::io::Result<()> {
let mut app = App::new();

app.add_plugins(DefaultPlugins)
.add_plugin(ScriptingPlugin)
.add_plugin(ConsolePlugin)
.add_startup_system(load_our_scripts)
// randomly fire events for either all scripts,
// the script with id 0
// or the script with id 1
.add_system(do_update)
.add_script_handler_stage::<RLuaScriptHost<MyLuaArg, LuaAPIProvider>, _, 0, 0>(
CoreStage::PostUpdate,
)
.add_script_host::<RLuaScriptHost<MyLuaArg, LuaAPIProvider>, _>(CoreStage::PostUpdate);

app.run();

Ok(())
}
111 changes: 93 additions & 18 deletions bevy_mod_scripting/src/hosts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,57 @@ use std::{
sync::atomic::{AtomicU32, Ordering},
};

/// Describes the target set of scripts this event should
/// be handled by
#[derive(Clone, Debug)]
pub enum Recipients {
/// Send to all scripts
All,
/// Send only to scripts on the given entity
Entity(Entity),
/// Send to script with the given ID
ScriptID(u32),
// Send to script with the given name
ScriptName(String),
}

#[derive(Debug)]
pub struct FlatScriptData<'a> {
sid: u32,
entity: Entity,
name: &'a str,
}

impl Recipients {
/// Returns true if the given script should
pub fn is_recipient(&self, c: &FlatScriptData) -> bool {
match self {
Recipients::All => true,
Recipients::Entity(e) => e == &c.entity,
Recipients::ScriptID(i) => i == &c.sid,
Recipients::ScriptName(n) => n == c.name,
}
}
}

impl Default for Recipients {
fn default() -> Self {
Self::All
}
}

pub trait ScriptEvent: Send + Sync + Clone + 'static {
/// Retrieves the recipient scripts for this event
fn recipients(&self) -> &Recipients;
}

/// A script host is the interface between your rust application
/// and the scripts in some interpreted language.
pub trait ScriptHost: Send + Sync + 'static {
/// the type of the persistent script context, representing the execution context of the script
type ScriptContext: Send + Sync + 'static;
/// the type of events picked up by lua callbacks
type ScriptEvent: Send + Sync + Clone + 'static;
type ScriptEvent: ScriptEvent;
/// the type of asset representing the script files for this host
type ScriptAsset: CodeAsset;

Expand All @@ -37,22 +81,31 @@ pub trait ScriptHost: Send + Sync + 'static {
fn handle_events<'a>(
world: &mut World,
events: &[Self::ScriptEvent],
ctxs: impl Iterator<Item = (&'a mut Entity, &'a mut Self::ScriptContext)>,
ctxs: impl Iterator<Item = (FlatScriptData<'a>, &'a mut Self::ScriptContext)>,
) -> Result<()>;

/// Loads and runs script instantaneously without storing any script data into the world.
/// The script receives the `world` global as normal, but `entity` is set to `u64::MAX`
/// The script receives the `world` global as normal, but `entity` is set to `u64::MAX`.
/// The script id is set to `u32::MAX`.
fn run_one_shot(
script: &[u8],
script_name: &str,
world: &mut World,
event: Self::ScriptEvent,
) -> Result<()> {
let mut ctx = Self::load_script(script, script_name)?;
let mut entity = Entity::from_bits(u64::MAX);
let entity = Entity::from_bits(u64::MAX);

let events = [event; 1];
let ctx_iter = [(&mut entity, &mut ctx); 1].into_iter();
let ctx_iter = [(
FlatScriptData {
name: script_name,
sid: u32::MAX,
entity,
},
&mut ctx,
); 1]
.into_iter();

Self::handle_events(world, &events, ctx_iter)
}
Expand Down Expand Up @@ -192,7 +245,7 @@ pub struct ScriptCollection<T: Asset> {
pub struct ScriptContexts<C> {
/// holds script contexts for all scripts given their instance ids.
/// This also stores contexts which are not fully loaded hence the Option
pub context_entities: HashMap<u32, (Entity, Option<C>)>,
pub context_entities: HashMap<u32, (Entity, Option<C>, String)>,
}

impl<C> Default for ScriptContexts<C> {
Expand All @@ -205,11 +258,12 @@ impl<C> Default for ScriptContexts<C> {

impl<C> ScriptContexts<C> {
pub fn script_owner(&self, script_id: u32) -> Option<Entity> {
self.context_entities.get(&script_id).map(|(e, _c)| *e)
self.context_entities.get(&script_id).map(|(e, _c, _n)| *e)
}

pub fn insert_context(&mut self, script_id: u32, entity: Entity, ctx: Option<C>) {
self.context_entities.insert(script_id, (entity, ctx));
pub fn insert_context(&mut self, fd: FlatScriptData, ctx: Option<C>) {
self.context_entities
.insert(fd.sid, (fd.entity, ctx, fd.name.to_owned()));
}

pub fn remove_context(&mut self, script_id: u32) {
Expand Down Expand Up @@ -286,24 +340,30 @@ impl<T: Asset> Script<T> {
script_assets: &Res<Assets<H::ScriptAsset>>,
contexts: &mut ResMut<ScriptContexts<H::ScriptContext>>,
) {
let fd = FlatScriptData {
sid: new_script.id(),
entity,
name: new_script.name(),
};

let script = match script_assets.get(&new_script.handle) {
Some(s) => s,
None => {
// not loaded yet
contexts.insert_context(new_script.id(), entity, None);
contexts.insert_context(fd, None);
return;
}
};

match H::load_script(script.bytes(), &new_script.name) {
Ok(ctx) => {
contexts.insert_context(new_script.id(), entity, Some(ctx));
contexts.insert_context(fd, Some(ctx));
}
Err(e) => {
warn! {"Error in loading script {}:\n{}", &new_script.name,e}
// this script will now never execute, unless manually reloaded
// but contexts are left in a valid state
contexts.insert_context(new_script.id(), entity, None)
contexts.insert_context(fd, None)
}
}
}
Expand Down Expand Up @@ -356,7 +416,7 @@ pub(crate) fn script_add_synchronizer<H: ScriptHost + 'static>(
let context_ids = contexts
.context_entities
.iter()
.filter_map(|(sid, (e, _))| if *e == entity { Some(sid) } else { None })
.filter_map(|(sid, (e, _, _))| if *e == entity { Some(sid) } else { None })
.cloned()
.collect::<HashSet<u32>>();
let script_ids = new_scripts
Expand Down Expand Up @@ -445,11 +505,26 @@ pub(crate) fn script_event_handler<H: ScriptHost, const MAX: u32, const MIN: u32
// as provide world access to scripts
// afaik there is not really a better way to do this in bevy just now
world.resource_scope(|world, mut ctxts: Mut<ScriptContexts<H::ScriptContext>>| {
let ctx_iter = ctxts
.as_mut()
.context_entities
.values_mut()
.filter_map(|(e, o)| o.as_mut().map(|v| (e, v)));
let ctx_iter =
ctxts
.as_mut()
.context_entities
.iter_mut()
.filter_map(|(sid, (entity, o, name))| {
let ctx = match o {
Some(v) => v,
None => return None,
};

Some((
FlatScriptData {
sid: *sid,
entity: *entity,
name,
},
ctx,
))
});

match H::handle_events(world, &events, ctx_iter) {
Ok(_) => {}
Expand Down
Loading