diff --git a/crates/unavi-scripting/src/api/wired/scene/mesh.rs b/crates/unavi-scripting/src/api/wired/scene/mesh.rs index dba6a9c98..e2a45acac 100644 --- a/crates/unavi-scripting/src/api/wired/scene/mesh.rs +++ b/crates/unavi-scripting/src/api/wired/scene/mesh.rs @@ -165,7 +165,7 @@ pub fn try_create_primitive( let material = primitive .material .as_ref() - .map(|m| m.read().handle.get().unwrap().clone()) + .and_then(|m| m.read().handle.get().cloned()) .unwrap_or_else(|| default_material.clone()); let entity = world diff --git a/crates/unavi-scripting/src/api/wired/scene/nodes/base.rs b/crates/unavi-scripting/src/api/wired/scene/nodes/base.rs index d3aa01ce5..edb1b25e4 100644 --- a/crates/unavi-scripting/src/api/wired/scene/nodes/base.rs +++ b/crates/unavi-scripting/src/api/wired/scene/nodes/base.rs @@ -15,12 +15,13 @@ use crate::{ scene::{composition::CompositionRes, document::DocumentRes, mesh::MeshRes}, }, }, - data::{CommandSender, ScriptData}, + data::{CommandSender, ControlSender, ScriptControl, ScriptData}, }; #[derive(Debug, Clone)] pub struct NodeRes { command_send: CommandSender, + control_send: ControlSender, data: Arc>, } @@ -83,6 +84,7 @@ impl NodeRes { pub fn new(data: &mut ScriptData) -> Self { let node = Self { command_send: data.command_send.clone(), + control_send: data.control_send.clone(), data: Arc::new(RwLock::new(NodeData::default())), }; @@ -90,7 +92,6 @@ impl NodeRes { let node = node.clone(); data.command_send .try_send(Box::new(move |world: &mut World| { - println!("new NodeRes command"); let node_data = node.write(); let entity = world.spawn(WiredNodeBundle::new(node_data.id.into())).id(); node_data.entity.set(entity).unwrap(); @@ -167,6 +168,10 @@ impl NodeRes { Ok(res) } pub fn add_child(data: &mut ScriptData, parent: Self, child: Self) { + if parent.read().id == child.read().id { + return; + } + // Add to children. parent.write().children.push(child.clone()); @@ -190,12 +195,18 @@ impl NodeRes { } pub fn remove_child(data: &mut ScriptData, parent: Self, child: Self) { // Remove from children. - let parent_read = parent.read(); - parent_read + let mut parent_write = parent.write(); + let child_id = child.read().id; + + let found = parent_write .children .iter() - .position(|n| n.read().id == parent_read.id) - .map(|index| parent.write().children.remove(index)); + .position(|c| c.read().id == child_id) + .map(|index| parent_write.children.remove(index)); + + if found.is_none() { + return; + } // Remove parent. child.write().parent = None; @@ -227,12 +238,17 @@ impl Drop for NodeRes { if Arc::strong_count(&self.data) == 1 { let data = self.data.clone(); - self.command_send + if let Err(e) = self + .command_send .try_send(Box::new(move |world: &mut World| { let entity = *data.read().unwrap().entity.get().unwrap(); world.entity_mut(entity).despawn(); })) - .unwrap() + { + // Should only error when the entire script environment is being + // dropped, in which case we do not matter. + let _ = self.control_send.send(ScriptControl::Exit(e.to_string())); + } } } } diff --git a/crates/unavi-scripting/src/api/wired/scene/primitive.rs b/crates/unavi-scripting/src/api/wired/scene/primitive.rs index b13b7a321..8a8811380 100644 --- a/crates/unavi-scripting/src/api/wired/scene/primitive.rs +++ b/crates/unavi-scripting/src/api/wired/scene/primitive.rs @@ -137,12 +137,12 @@ impl HostPrimitive for ScriptData { self_: Resource, value: Vec, ) -> wasm_bridge::Result<()> { - let handle = self.table.get(&self_)?.read().handle.clone(); + let data = self.table.get(&self_)?.clone(); self.command_send .try_send(Box::new(move |world: &mut World| { let mut assets = world.resource_mut::>(); - let mesh = assets.get_mut(handle.get().unwrap()).unwrap(); + let mesh = assets.get_mut(data.read().handle.get().unwrap()).unwrap(); mesh.insert_indices(Indices::U32(value)); })) .unwrap(); @@ -154,12 +154,12 @@ impl HostPrimitive for ScriptData { self_: Resource, value: Vec, ) -> wasm_bridge::Result<()> { - let handle = self.table.get(&self_)?.read().handle.clone(); + let data = self.table.get(&self_)?.clone(); self.command_send .try_send(Box::new(move |world: &mut World| { let mut assets = world.resource_mut::>(); - let mesh = assets.get_mut(handle.get().unwrap()).unwrap(); + let mesh = assets.get_mut(data.read().handle.get().unwrap()).unwrap(); let value = value.chunks(3).map(|x| [x[0], x[1], x[2]]).collect(); @@ -177,12 +177,12 @@ impl HostPrimitive for ScriptData { self_: Resource, value: Vec, ) -> wasm_bridge::Result<()> { - let handle = self.table.get(&self_)?.read().handle.clone(); + let data = self.table.get(&self_)?.clone(); self.command_send .try_send(Box::new(move |world: &mut World| { let mut assets = world.resource_mut::>(); - let mesh = assets.get_mut(handle.get().unwrap()).unwrap(); + let mesh = assets.get_mut(data.read().handle.get().unwrap()).unwrap(); let value = value.chunks(3).map(|x| [x[0], x[1], x[2]]).collect(); @@ -200,12 +200,12 @@ impl HostPrimitive for ScriptData { self_: Resource, value: Vec, ) -> wasm_bridge::Result<()> { - let handle = self.table.get(&self_)?.read().handle.clone(); + let data = self.table.get(&self_)?.clone(); self.command_send .try_send(Box::new(move |world: &mut World| { let mut assets = world.resource_mut::>(); - let mesh = assets.get_mut(handle.get().unwrap()).unwrap(); + let mesh = assets.get_mut(data.read().handle.get().unwrap()).unwrap(); if value.len() % 2 != 0 { warn!("UVs do not have an even length! Got: {}", value.len()); diff --git a/crates/unavi-scripting/src/data.rs b/crates/unavi-scripting/src/data.rs index 63ed41a4c..aac44e8fe 100644 --- a/crates/unavi-scripting/src/data.rs +++ b/crates/unavi-scripting/src/data.rs @@ -1,4 +1,4 @@ -use std::sync::mpsc::{Receiver, SyncSender}; +use std::sync::mpsc::{Receiver, Sender, SyncSender}; use bevy::prelude::*; use wasm_bridge::component::{Resource, ResourceTable, ResourceTableError}; @@ -9,10 +9,19 @@ use crate::api::{wired::scene::nodes::base::NodeRes, ApiData}; pub type ScriptCommand = Box; pub type CommandSender = SyncSender; +pub enum ScriptControl { + /// Unrecoverable error. + /// Stops script execution. + Exit(String), +} +pub type ControlSender = Sender; + pub struct ScriptData { pub api: ApiData, pub command_recv: Receiver, pub command_send: CommandSender, + pub control_recv: Receiver, + pub control_send: ControlSender, pub table: ResourceTable, pub wasi: WasiCtx, pub wasi_table: wasm_bridge_wasi::ResourceTable, @@ -40,11 +49,14 @@ impl Default for ScriptData { // // TODO: Test that script ECS data is indeed within the script scene let (command_send, command_recv) = std::sync::mpsc::sync_channel(10_000); + let (control_send, control_recv) = std::sync::mpsc::channel(); Self { api: ApiData::default(), command_recv, command_send, + control_recv, + control_send, table: ResourceTable::default(), wasi: WasiCtxBuilder::new().build(), wasi_table: wasm_bridge_wasi::ResourceTable::default(), @@ -97,13 +109,3 @@ impl ScriptData { self.node_insert_option::(node, None) } } - -impl Drop for ScriptData { - fn drop(&mut self) { - // Command queue must be cleared before the reciever is dropped. - // Commands in the queue may hold references to resource data that will - // attempt to send cleanup commands on drop. If the reciever is not open - // this will cause a panic. - while self.command_recv.try_recv().is_ok() {} - } -} diff --git a/crates/unavi-scripting/src/execution.rs b/crates/unavi-scripting/src/execution.rs index 885d9de95..e672583e9 100644 --- a/crates/unavi-scripting/src/execution.rs +++ b/crates/unavi-scripting/src/execution.rs @@ -1,7 +1,7 @@ use bevy::{prelude::*, tasks::block_on}; use wasm_bridge::component::ResourceAny; -use crate::load::LoadedScript; +use crate::{data::ScriptControl, load::LoadedScript}; use super::load::ScriptMap; @@ -60,7 +60,7 @@ pub fn update_scripts( #[allow(clippy::await_holding_lock)] let result: anyhow::Result<_> = block_on(async { - let span = trace_span!("ScriptUpdate", name = name.to_string(), tickrate.delta); + let span = trace_span!("ScriptUpdate", name = name.to_string()); let span = span.enter(); trace!("Updating script"); @@ -77,6 +77,15 @@ pub fn update_scripts( script.store.data().push_commands(&mut commands); + while let Ok(control) = script.store.data().control_recv.try_recv() { + match control { + ScriptControl::Exit(e) => { + error!("Controlled exit: {}", e); + todo!("stop script execution"); + } + } + } + trace!("Done"); drop(span); @@ -93,8 +102,9 @@ pub fn update_scripts( pub struct ScriptTickrate { delta: f32, last: f32, + /// Ticks per second. + tps: f32, pub ready_for_update: bool, - tickrate: f32, } impl Default for ScriptTickrate { @@ -102,8 +112,8 @@ impl Default for ScriptTickrate { Self { delta: 0.0, last: 0.0, + tps: 1.0 / 20.0, ready_for_update: true, - tickrate: 1.0 / 20.0, } } } @@ -119,7 +129,7 @@ pub fn tick_scripts(time: Res