use std::sync::Arc;
use bevy::prelude::*;
use symbios_shape::{Interpreter, Scope};
use crate::cache::ShapeMeshCache;
use crate::registry::ShapeRegistry;
use crate::spawner::SpawnShapeExt;
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
pub struct SpawnShapeSystems;
#[derive(Event, Clone)]
pub struct SpawnShapeRequest {
pub interpreter: Arc<Interpreter>,
pub root_scope: Scope,
pub root_rule: String,
}
#[derive(Event, Clone, Copy, Debug)]
pub struct SpawnShapeSpawned(pub Entity);
#[derive(Event, Clone, Debug)]
pub struct SpawnShapeFailed(pub String);
pub fn handle_spawn_request(
request: On<SpawnShapeRequest>,
mut commands: Commands,
registry: Res<ShapeRegistry>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut cache: ResMut<ShapeMeshCache>,
) {
let req = request.event();
match commands.spawn_shape(
&req.interpreter,
req.root_scope,
&req.root_rule,
®istry,
&mut meshes,
&mut materials,
&mut cache,
) {
Ok(entity) => {
commands.trigger(SpawnShapeSpawned(entity));
}
Err(err) => {
error!("spawn_shape failed: {err}");
commands.trigger(SpawnShapeFailed(err.to_string()));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BevySymbiosShapePlugin;
use crate::query::LastDerivedModel;
use std::sync::{Arc, Mutex};
use symbios_shape::grammar::parse_ops;
use symbios_shape::{Quat as DQuat, Vec3 as DVec3};
fn make_app() -> App {
let mut app = App::new();
app.add_plugins(bevy::asset::AssetPlugin::default())
.init_resource::<Assets<Mesh>>()
.init_resource::<Assets<StandardMaterial>>()
.init_resource::<Assets<Scene>>()
.add_plugins(BevySymbiosShapePlugin);
app
}
fn simple_interp() -> Interpreter {
let mut interp = Interpreter::new();
interp.add_rule("Lot", parse_ops(r#"Extrude(2) I("Wall")"#).unwrap());
interp
}
#[test]
fn observer_spawns_entity_on_request() {
let mut app = make_app();
let captured: Arc<Mutex<Option<Entity>>> = Arc::new(Mutex::new(None));
let captured_obs = captured.clone();
app.add_observer(move |spawned: On<SpawnShapeSpawned>| {
*captured_obs.lock().unwrap() = Some(spawned.event().0);
});
app.world_mut().trigger(SpawnShapeRequest {
interpreter: Arc::new(simple_interp()),
root_scope: Scope::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::new(2.0, 0.0, 2.0)),
root_rule: "Lot".into(),
});
app.update();
let entity = captured.lock().unwrap();
assert!(entity.is_some(), "expected SpawnShapeSpawned to fire");
let model = app.world().resource::<LastDerivedModel>();
assert!(
!model.model().terminals.is_empty(),
"LastDerivedModel should be populated"
);
}
#[test]
fn observer_emits_failed_on_invalid_scope() {
let mut app = make_app();
let captured: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let captured_obs = captured.clone();
app.add_observer(move |failed: On<SpawnShapeFailed>| {
*captured_obs.lock().unwrap() = Some(failed.event().0.clone());
});
app.world_mut().trigger(SpawnShapeRequest {
interpreter: Arc::new(simple_interp()),
root_scope: Scope::new(DVec3::ZERO, DQuat::IDENTITY, DVec3::new(f64::NAN, 0.0, 1.0)),
root_rule: "Lot".into(),
});
app.update();
let msg = captured.lock().unwrap();
assert!(msg.is_some(), "expected SpawnShapeFailed to fire");
}
}