use bevy::{asset::Asset, ecs::schedule::ScheduleLabel, prelude::*};
use std::{
collections::HashMap,
iter::once,
sync::atomic::{AtomicU32, Ordering},
};
use crate::{
asset::CodeAsset,
docs::DocFragment,
error::ScriptError,
event::{ScriptEvent, ScriptLoaded},
world::WorldPointer,
};
#[derive(Clone, Debug)]
pub enum Recipients {
All,
Entity(Entity),
ScriptID(u32),
ScriptName(String),
}
#[derive(Debug)]
pub struct ScriptData<'a> {
pub sid: u32,
pub entity: Entity,
pub name: &'a str,
}
impl Recipients {
pub fn is_recipient(&self, c: &ScriptData) -> 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 ScriptHost: Send + Sync + 'static + Default + Resource {
type ScriptContext: Send + Sync + 'static;
type ScriptEvent: ScriptEvent;
type ScriptAsset: CodeAsset;
type APITarget: Send + Sync + 'static;
type DocTarget: DocFragment;
fn load_script(
&mut self,
script: &[u8],
script_data: &ScriptData,
providers: &mut APIProviders<Self>,
) -> Result<Self::ScriptContext, ScriptError>;
fn setup_script(
&mut self,
script_data: &ScriptData,
ctx: &mut Self::ScriptContext,
providers: &mut APIProviders<Self>,
) -> Result<(), ScriptError>;
fn handle_events<'a>(
&mut self,
world_ptr: &mut World,
events: &[Self::ScriptEvent],
ctxs: impl Iterator<Item = (ScriptData<'a>, &'a mut Self::ScriptContext)>,
providers: &mut APIProviders<Self>,
);
fn run_one_shot(
&mut self,
script: &[u8],
script_name: &str,
entity: Entity,
world: &mut World,
event: Self::ScriptEvent,
) -> Result<(), ScriptError> {
let fd = ScriptData {
name: script_name,
sid: u32::MAX,
entity,
};
let mut providers: APIProviders<Self> = world.remove_resource().unwrap();
let mut ctx = self.load_script(script, &fd, &mut providers).unwrap();
self.setup_script(&fd, &mut ctx, &mut providers)?;
let events = [event; 1];
self.handle_events(world, &events, once((fd, &mut ctx)), &mut providers);
world.insert_resource(providers);
Ok(())
}
fn register_with_app(app: &mut App, schedule: impl ScheduleLabel) {
#[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone, Copy)]
struct DummySet;
Self::register_with_app_in_set(app, schedule, DummySet);
}
fn register_with_app_in_set(app: &mut App, schedule: impl ScheduleLabel, set: impl SystemSet);
}
pub trait APIProvider: 'static + Send + Sync {
type APITarget: Send + Sync + 'static;
type ScriptContext: Send + Sync + 'static;
type DocTarget: DocFragment;
fn attach_api(&mut self, api: &mut Self::APITarget) -> Result<(), ScriptError>;
fn setup_script_runtime(
&mut self,
_world_ptr: WorldPointer,
_script_data: &ScriptData,
_ctx: &mut Self::ScriptContext,
) -> Result<(), ScriptError> {
Ok(())
}
fn setup_script(
&mut self,
_script_data: &ScriptData,
_ctx: &mut Self::ScriptContext,
) -> Result<(), ScriptError> {
Ok(())
}
fn get_doc_fragment(&self) -> Option<Self::DocTarget> {
None
}
fn register_with_app(&self, _app: &mut App) {}
}
#[derive(Resource)]
pub struct APIProviders<T: ScriptHost> {
pub providers: Vec<
Box<
dyn APIProvider<
APITarget = T::APITarget,
DocTarget = T::DocTarget,
ScriptContext = T::ScriptContext,
>,
>,
>,
}
impl<T: ScriptHost> Default for APIProviders<T> {
fn default() -> Self {
Self {
providers: Default::default(),
}
}
}
impl<T: ScriptHost> APIProviders<T> {
pub fn attach_all(&mut self, ctx: &mut T::APITarget) -> Result<(), ScriptError> {
for p in self.providers.iter_mut() {
p.attach_api(ctx)?;
}
Ok(())
}
pub fn setup_runtime_all(
&mut self,
world_ptr: WorldPointer,
script_data: &ScriptData,
ctx: &mut T::ScriptContext,
) -> Result<(), ScriptError> {
for p in self.providers.iter_mut() {
p.setup_script_runtime(world_ptr.clone(), script_data, ctx)?;
}
Ok(())
}
pub fn setup_all(
&mut self,
script_data: &ScriptData,
ctx: &mut T::ScriptContext,
) -> Result<(), ScriptError> {
for p in self.providers.iter_mut() {
p.setup_script(script_data, ctx)?;
}
Ok(())
}
pub fn gen_all(&self) -> Result<(), ScriptError> {
let mut d: Option<T::DocTarget> = None;
for p in self.providers.iter() {
if let Some(f) = p.get_doc_fragment() {
if let Some(prev) = d {
d = Some(prev.merge(f))
} else {
d = Some(f)
}
}
}
d.map(|d| d.gen_docs()).unwrap_or_else(|| Ok(()))
}
}
#[derive(Resource)]
pub struct ScriptContexts<C> {
pub context_entities: HashMap<u32, (Entity, Option<C>, String)>,
}
impl<C> Default for ScriptContexts<C> {
fn default() -> Self {
Self {
context_entities: Default::default(),
}
}
}
impl<C> ScriptContexts<C> {
pub fn script_owner(&self, script_id: u32) -> Option<Entity> {
self.context_entities.get(&script_id).map(|(e, _c, _n)| *e)
}
pub fn insert_context(&mut self, fd: ScriptData, 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) {
self.context_entities.remove(&script_id);
}
pub fn has_context(&self, script_id: u32) -> bool {
self.context_entities
.get(&script_id)
.map_or(false, |(_, c, _)| c.is_some())
}
pub fn is_empty(&self) -> bool {
self.context_entities.is_empty()
}
}
#[derive(Debug, Reflect)]
pub struct Script<T: Asset> {
handle: Handle<T>,
name: String,
id: u32,
}
static COUNTER: AtomicU32 = AtomicU32::new(0);
impl<T: Asset> Script<T> {
pub fn new(name: String, handle: Handle<T>) -> Self {
Self {
handle,
name,
id: COUNTER.fetch_add(1, Ordering::Relaxed),
}
}
#[inline(always)]
pub fn name(&self) -> &str {
&self.name
}
#[inline(always)]
pub fn handle(&self) -> &Handle<T> {
&self.handle
}
#[inline(always)]
pub fn id(&self) -> u32 {
self.id
}
pub(crate) fn reload_script<H: ScriptHost>(
host: &mut H,
script: &Script<H::ScriptAsset>,
script_assets: &Assets<H::ScriptAsset>,
providers: &mut APIProviders<H>,
contexts: &mut ScriptContexts<H::ScriptContext>,
event_writer: &mut EventWriter<ScriptLoaded>,
) {
debug!("reloading script {}", script.id);
if let Some(entity) = contexts.script_owner(script.id()) {
contexts.remove_context(script.id());
Self::insert_new_script_context::<H>(
host,
script,
entity,
script_assets,
providers,
contexts,
event_writer,
);
} else {
contexts.remove_context(script.id());
}
}
pub(crate) fn insert_new_script_context<H: ScriptHost>(
host: &mut H,
new_script: &Script<H::ScriptAsset>,
entity: Entity,
script_assets: &Assets<H::ScriptAsset>,
providers: &mut APIProviders<H>,
contexts: &mut ScriptContexts<H::ScriptContext>,
event_writer: &mut EventWriter<ScriptLoaded>,
) {
let fd = ScriptData {
sid: new_script.id(),
entity,
name: new_script.name(),
};
let script = match script_assets.get(&new_script.handle) {
Some(s) => s,
None => {
debug!("Inserted script which hasn't loaded yet {:?}", fd);
contexts.insert_context(fd, None);
return;
}
};
debug!("Inserted script {:?}", fd);
match host.load_script(script.bytes(), &fd, providers) {
Ok(mut ctx) => {
host.setup_script(&fd, &mut ctx, providers)
.expect("Failed to setup script");
contexts.insert_context(fd, Some(ctx));
event_writer.send(ScriptLoaded {
sid: new_script.id(),
});
}
Err(e) => {
warn! {"Error in loading script {}:\n{}", &new_script.name,e}
contexts.insert_context(fd, None);
}
}
}
}
#[derive(Component, Debug, Reflect)]
#[reflect(Component, Default)]
pub struct ScriptCollection<T: Asset> {
pub scripts: Vec<Script<T>>,
}
impl<T: Asset> Default for ScriptCollection<T> {
fn default() -> Self {
Self {
scripts: Default::default(),
}
}
}