use std::collections::HashSet;
use bevy::{ecs::system::SystemState, prelude::*};
use bevy_event_priority::PriorityEventReader;
use crate::{
event::ScriptLoaded,
prelude::{APIProviders, Script, ScriptCollection, ScriptContexts, ScriptData, ScriptHost},
ScriptErrorEvent,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemSet)]
pub enum ScriptSystemSet {
EventHandling,
}
pub fn script_add_synchronizer<H: ScriptHost + 'static>(
query: Query<
(
Entity,
&ScriptCollection<H::ScriptAsset>,
Ref<ScriptCollection<H::ScriptAsset>>,
),
Changed<ScriptCollection<H::ScriptAsset>>,
>,
mut host: ResMut<H>,
mut providers: ResMut<APIProviders<H>>,
script_assets: Res<Assets<H::ScriptAsset>>,
mut contexts: ResMut<ScriptContexts<H::ScriptContext>>,
mut event_writer: EventWriter<ScriptLoaded>,
) {
debug!("Handling addition/modification of scripts");
query.iter().for_each(|(entity, new_scripts, tracker)| {
if tracker.is_added() {
new_scripts.scripts.iter().for_each(|new_script| {
Script::<H::ScriptAsset>::insert_new_script_context::<H>(
&mut host,
new_script,
entity,
&script_assets,
&mut providers,
&mut contexts,
&mut event_writer,
)
})
} else {
let context_ids = contexts
.context_entities
.iter()
.filter_map(|(sid, (e, _, _))| if *e == entity { Some(sid) } else { None })
.cloned()
.collect::<HashSet<u32>>();
let script_ids = new_scripts
.scripts
.iter()
.map(|s| s.id())
.collect::<HashSet<u32>>();
let removed_scripts = context_ids.difference(&script_ids);
let added_scripts = script_ids.difference(&context_ids);
for r in removed_scripts {
contexts.remove_context(*r);
}
for a in added_scripts {
let script = new_scripts.scripts.iter().find(|e| &e.id() == a).unwrap();
Script::<H::ScriptAsset>::insert_new_script_context::<H>(
&mut host,
script,
entity,
&script_assets,
&mut providers,
&mut contexts,
&mut event_writer,
)
}
}
})
}
pub fn script_remove_synchronizer<H: ScriptHost>(
mut query: RemovedComponents<ScriptCollection<H::ScriptAsset>>,
mut contexts: ResMut<ScriptContexts<H::ScriptContext>>,
) {
for v in query.read() {
let script_ids = contexts
.context_entities
.iter()
.filter_map(|(script_id, (entity, ..))| {
(entity.index() == v.index()).then_some(*script_id)
})
.collect::<Vec<_>>();
for script_id in script_ids {
contexts.remove_context(script_id);
}
}
}
pub fn script_hot_reload_handler<H: ScriptHost>(
mut events: EventReader<AssetEvent<H::ScriptAsset>>,
mut host: ResMut<H>,
scripts: Query<&ScriptCollection<H::ScriptAsset>>,
script_assets: Res<Assets<H::ScriptAsset>>,
mut providers: ResMut<APIProviders<H>>,
mut contexts: ResMut<ScriptContexts<H::ScriptContext>>,
mut event_writer: EventWriter<ScriptLoaded>,
) {
for e in events.read() {
let (handle, created) = match e {
AssetEvent::Modified { id } => (id, false),
AssetEvent::Added { id } => (id, true),
_ => continue,
};
for scripts in scripts.iter() {
for script in &scripts.scripts {
if script.handle().id() == *handle
&& !(contexts.has_context(script.id()) && created)
{
Script::<H::ScriptAsset>::reload_script::<H>(
&mut host,
script,
&script_assets,
&mut providers,
&mut contexts,
&mut event_writer,
);
}
}
}
}
}
pub fn script_event_handler<H: ScriptHost, const MAX: u32, const MIN: u32>(world: &mut World) {
let mut state: CachedScriptState<H> = world.remove_resource().unwrap();
let events = state
.event_state
.get_mut(world)
.0
.iter_prio_range(MAX, MIN)
.collect::<Vec<H::ScriptEvent>>();
world.insert_resource(state);
if events.is_empty() {
return;
}
let mut ctxts: ScriptContexts<H::ScriptContext> = world.remove_resource().unwrap();
let mut host: H = world.remove_resource().unwrap();
let mut providers: APIProviders<H> = world.remove_resource().unwrap();
let ctx_iter = ctxts
.context_entities
.iter_mut()
.filter_map(|(sid, (entity, o, name))| {
let ctx = match o {
Some(v) => v,
None => return None,
};
Some((
ScriptData {
sid: *sid,
entity: *entity,
name,
},
ctx,
))
});
host.handle_events(world, &events, ctx_iter, &mut providers);
world.insert_resource(ctxts);
world.insert_resource(host);
world.insert_resource(providers);
}
#[derive(Resource)]
pub struct CachedScriptState<H: ScriptHost> {
pub event_state: SystemState<(
PriorityEventReader<'static, 'static, H::ScriptEvent>,
EventWriter<'static, ScriptErrorEvent>,
EventReader<'static, 'static, ScriptLoaded>,
)>,
}
impl<H: ScriptHost> FromWorld for CachedScriptState<H> {
fn from_world(world: &mut World) -> Self {
Self {
event_state: SystemState::new(world),
}
}
}