1use crate::scene::{Body, Object, Shape};
22use nightshade::prelude::{Entity, KeyCode, MouseButton, TextAlignment, Vec3, World, vec3};
23use serde::{Deserialize, Serialize};
24
25#[derive(Serialize, Deserialize, Clone, Copy, Debug, enum2schema::Schema)]
30pub enum Ref {
31 Entity(#[schema(with = entity_schema)] Entity),
32 Result(u32),
33 Existing(u32),
38}
39
40#[derive(Serialize, Deserialize, Clone, Debug, enum2schema::Schema)]
44pub enum CommandReply {
45 None,
46 Entity(#[schema(with = entity_schema)] Entity),
47 Bool(bool),
48 Float(f32),
49 Int(i64),
50 Text(String),
51 Vector([f32; 3]),
52 Entities(#[schema(with = entities_schema)] Vec<Entity>),
53 Strings(Vec<String>),
54 Bytes(Vec<u8>),
55 Json(#[schema(with = any_schema)] enum2schema::serde_json::Value),
60 Error(String),
61}
62
63#[derive(Serialize, Clone, Debug)]
68pub struct FieldSpec {
69 pub name: &'static str,
70 pub type_name: &'static str,
71 pub role: &'static str,
72}
73
74#[derive(Serialize, Clone, Debug)]
78pub struct CommandSpec {
79 pub name: &'static str,
80 pub fields: Vec<FieldSpec>,
81 pub reply: &'static str,
82 pub description: &'static str,
83}
84
85pub fn command_manifest_json() -> String {
88 enum2schema::serde_json::to_string(&command_manifest()).unwrap_or_default()
89}
90
91fn entity_schema() -> enum2schema::serde_json::Value {
92 enum2schema::serde_json::json!({
93 "type": "object",
94 "properties": {
95 "id": { "type": "integer" },
96 "generation": { "type": "integer" }
97 },
98 "required": ["id", "generation"]
99 })
100}
101
102fn entities_schema() -> enum2schema::serde_json::Value {
103 enum2schema::serde_json::json!({ "type": "array", "items": entity_schema() })
104}
105
106fn any_schema() -> enum2schema::serde_json::Value {
107 enum2schema::serde_json::json!({})
108}
109
110pub fn command_schema() -> enum2schema::serde_json::Value {
114 <Command as enum2schema::Schema>::schema()
115}
116
117pub fn command_reply_schema() -> enum2schema::serde_json::Value {
119 <CommandReply as enum2schema::Schema>::schema()
120}
121
122pub fn submit_command(world: &mut World, command: &Command) -> CommandReply {
124 dispatch(world, command, &[])
125}
126
127pub fn submit_commands(world: &mut World, commands: &[Command]) -> Vec<CommandReply> {
131 let mut produced: Vec<Option<Entity>> = Vec::with_capacity(commands.len());
132 let mut replies = Vec::with_capacity(commands.len());
133 for command in commands {
134 let reply = dispatch(world, command, &produced);
135 produced.push(match &reply {
136 CommandReply::Entity(entity) => Some(*entity),
137 _ => None,
138 });
139 replies.push(reply);
140 }
141 replies
142}
143
144fn resolve(world: &World, reference: Ref, produced: &[Option<Entity>]) -> Option<Entity> {
145 match reference {
146 Ref::Entity(entity) => Some(entity),
147 Ref::Result(index) => produced.get(index as usize).copied().flatten(),
148 Ref::Existing(id) => world
149 .core
150 .entity_locations
151 .get(id)
152 .filter(|location| location.allocated)
153 .map(|location| Entity {
154 id,
155 generation: location.generation,
156 }),
157 }
158}
159
160fn array_to_vec3(values: [f32; 3]) -> Vec3 {
161 vec3(values[0], values[1], values[2])
162}
163
164fn array_to_vec2(values: [f32; 2]) -> nightshade::prelude::Vec2 {
165 nightshade::prelude::vec2(values[0], values[1])
166}
167
168fn spawn_object_command(
173 world: &mut World,
174 shape: Shape,
175 position: Vec3,
176 scale: Vec3,
177 color: [f32; 4],
178 body: Body,
179) -> Entity {
180 crate::scene::spawn_object(
181 world,
182 Object {
183 shape,
184 position,
185 scale,
186 color,
187 body,
188 },
189 )
190}
191
192#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, enum2schema::Schema)]
196pub struct InstanceWire {
197 pub position: [f32; 3],
198 pub rotation: [f32; 4],
199 pub scale: [f32; 3],
200}
201
202fn instance_from_wire(wire: &InstanceWire) -> nightshade::prelude::InstanceTransform {
203 use nightshade::prelude::nalgebra_glm::Quat;
204 nightshade::prelude::InstanceTransform::new(
205 array_to_vec3(wire.position),
206 Quat::new(
207 wire.rotation[3],
208 wire.rotation[0],
209 wire.rotation[1],
210 wire.rotation[2],
211 ),
212 array_to_vec3(wire.scale),
213 )
214}
215
216fn register_material_command(
217 world: &mut World,
218 name: &str,
219 base_color: [f32; 4],
220 metallic: f32,
221 roughness: f32,
222 emissive: [f32; 3],
223) -> String {
224 crate::materials::register_material(
225 world,
226 name,
227 nightshade::ecs::material::components::Material {
228 base_color,
229 metallic,
230 roughness,
231 emissive_factor: emissive,
232 ..Default::default()
233 },
234 )
235}
236
237fn spawn_objects_command(
238 world: &mut World,
239 shape: Shape,
240 scale: Vec3,
241 color: [f32; 4],
242 body: Body,
243 positions: &[Vec3],
244) -> Vec<Entity> {
245 crate::scene::spawn_objects(
246 world,
247 Object {
248 shape,
249 position: vec3(0.0, 0.0, 0.0),
250 scale,
251 color,
252 body,
253 },
254 positions,
255 )
256}
257
258fn spawn_custom_mesh_command(
264 world: &mut World,
265 name: &str,
266 positions: &[f32],
267 normals: &[f32],
268 uvs: &[f32],
269 indices: &[usize],
270 position: Vec3,
271) -> Entity {
272 let count = positions.len() / 3;
273 let vertices: Vec<([f32; 3], [f32; 3], [f32; 2])> = (0..count)
274 .map(|index| {
275 let point = index * 3;
276 let coord = index * 2;
277 (
278 [positions[point], positions[point + 1], positions[point + 2]],
279 [
280 normals.get(point).copied().unwrap_or(0.0),
281 normals.get(point + 1).copied().unwrap_or(1.0),
282 normals.get(point + 2).copied().unwrap_or(0.0),
283 ],
284 [
285 uvs.get(coord).copied().unwrap_or(0.0),
286 uvs.get(coord + 1).copied().unwrap_or(0.0),
287 ],
288 )
289 })
290 .collect();
291 let indices: Vec<u32> = indices.iter().map(|value| *value as u32).collect();
292 crate::mesh::spawn_custom_mesh(world, name, &vertices, &indices, position)
293}
294
295#[cfg(feature = "navmesh")]
296#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, enum2schema::Schema)]
297pub struct RecastConfigWire {
298 pub agent_radius: f32,
299 pub agent_height: f32,
300 pub cell_size_fraction: f32,
301 pub cell_height_fraction: f32,
302 pub walkable_climb: f32,
303 pub walkable_slope_angle: f32,
304 pub min_region_size: u32,
305 pub merge_region_size: u32,
306 pub max_simplification_error: f32,
307 pub edge_max_len_factor: u32,
308 pub max_vertices_per_polygon: u32,
309 pub detail_sample_dist: f32,
310 pub detail_sample_max_error: f32,
311}
312
313#[cfg(feature = "navmesh")]
314fn bake_navmesh_with_command(world: &mut World, config: RecastConfigWire) {
315 crate::navigation::bake_navmesh_with(
316 world,
317 &nightshade::prelude::RecastNavMeshConfig {
318 agent_radius: config.agent_radius,
319 agent_height: config.agent_height,
320 cell_size_fraction: config.cell_size_fraction,
321 cell_height_fraction: config.cell_height_fraction,
322 walkable_climb: config.walkable_climb,
323 walkable_slope_angle: config.walkable_slope_angle,
324 min_region_size: config.min_region_size as u16,
325 merge_region_size: config.merge_region_size as u16,
326 max_simplification_error: config.max_simplification_error,
327 edge_max_len_factor: config.edge_max_len_factor as u16,
328 max_vertices_per_polygon: config.max_vertices_per_polygon as u16,
329 detail_sample_dist: config.detail_sample_dist,
330 detail_sample_max_error: config.detail_sample_max_error,
331 },
332 );
333}
334
335#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, enum2schema::Schema)]
340pub struct EmitterWire {
341 pub position: [f32; 3],
342 pub color: [f32; 3],
343 pub spawn_rate: f32,
344 pub burst_count: u32,
345 pub lifetime: [f32; 2],
346 pub size: [f32; 2],
347 pub gravity: [f32; 3],
348 pub drag: f32,
349 pub one_shot: bool,
350}
351
352fn emitter_from_wire(wire: &EmitterWire) -> nightshade::prelude::ParticleEmitter {
353 let mut emitter = nightshade::prelude::ParticleEmitter::firework_explosion(
354 array_to_vec3(wire.position),
355 array_to_vec3(wire.color),
356 wire.burst_count,
357 );
358 emitter.spawn_rate = wire.spawn_rate;
359 emitter.particle_lifetime_min = wire.lifetime[0];
360 emitter.particle_lifetime_max = wire.lifetime[1];
361 emitter.size_start = wire.size[0];
362 emitter.size_end = wire.size[1];
363 emitter.gravity = array_to_vec3(wire.gravity);
364 emitter.drag = wire.drag;
365 emitter.one_shot = wire.one_shot;
366 emitter.enabled = true;
367 emitter
368}
369
370fn spawn_particle_emitter_command(world: &mut World, emitter: EmitterWire) -> Entity {
371 crate::effects::spawn_particle_emitter(world, emitter_from_wire(&emitter))
372}
373
374fn set_emitter_command(world: &mut World, emitter_entity: Entity, emitter: EmitterWire) {
375 crate::effects::set_emitter(world, emitter_entity, emitter_from_wire(&emitter));
376}
377
378fn update_material_command(
379 world: &mut World,
380 name: &str,
381 base_color: [f32; 4],
382 metallic: f32,
383 roughness: f32,
384 emissive: [f32; 3],
385) {
386 crate::materials::update_material(
387 world,
388 name,
389 nightshade::ecs::material::components::Material {
390 base_color,
391 metallic,
392 roughness,
393 emissive_factor: emissive,
394 ..Default::default()
395 },
396 );
397}
398
399fn set_material_variant_command(world: &mut World, variant: &str) -> usize {
400 let variant = if variant.is_empty() {
401 None
402 } else {
403 Some(variant)
404 };
405 crate::materials::set_material_variant(world, variant)
406}
407
408fn set_fog_command(world: &mut World, enabled: bool, color: [f32; 3], start: f32, end: f32) {
409 let fog = if enabled {
410 Some(nightshade::ecs::graphics::resources::Fog { color, start, end })
411 } else {
412 None
413 };
414 crate::environment::set_fog(world, fog);
415}
416
417fn set_depth_of_field_command(
418 world: &mut World,
419 enabled: bool,
420 focus_distance: f32,
421 focus_range: f32,
422 max_blur_radius: f32,
423 bokeh_threshold: f32,
424) {
425 crate::environment::set_depth_of_field(
426 world,
427 nightshade::ecs::graphics::resources::DepthOfField {
428 enabled,
429 focus_distance,
430 focus_range,
431 max_blur_radius,
432 bokeh_threshold,
433 ..Default::default()
434 },
435 );
436}
437
438fn panel_data_grid_command(
439 world: &mut World,
440 panel: Entity,
441 headers: &[&str],
442 widths: &[f32],
443 pool_size: usize,
444) -> Entity {
445 let columns: Vec<(&str, f32)> = headers
446 .iter()
447 .zip(widths.iter())
448 .map(|(header, width)| (*header, *width))
449 .collect();
450 crate::ui::panel_data_grid(world, panel, &columns, pool_size)
451}
452
453fn panel_selectable_command(
454 world: &mut World,
455 panel: Entity,
456 text: &str,
457 group: u32,
458 grouped: bool,
459) -> Entity {
460 crate::ui::panel_selectable(world, panel, text, grouped.then_some(group))
461}
462
463fn panel_splitter_command(
464 world: &mut World,
465 panel: Entity,
466 horizontal: bool,
467 ratio: f32,
468) -> Entity {
469 let direction = if horizontal {
470 nightshade::prelude::SplitDirection::Horizontal
471 } else {
472 nightshade::prelude::SplitDirection::Vertical
473 };
474 crate::ui::panel_splitter(world, panel, direction, ratio)
475}
476
477fn screenshot_command(world: &mut World, path: &str) {
478 crate::environment::screenshot(world, std::path::PathBuf::from(path));
479}
480
481fn easing_from_name(name: &str) -> nightshade::prelude::EasingFunction {
482 use nightshade::prelude::EasingFunction::*;
483 match name.to_ascii_lowercase().as_str() {
484 "quadin" => QuadIn,
485 "quadout" => QuadOut,
486 "quadinout" => QuadInOut,
487 "cubicin" => CubicIn,
488 "cubicout" => CubicOut,
489 "cubicinout" => CubicInOut,
490 "quartin" => QuartIn,
491 "quartout" => QuartOut,
492 "quartinout" => QuartInOut,
493 "quintin" => QuintIn,
494 "quintout" => QuintOut,
495 "quintinout" => QuintInOut,
496 "sinein" => SineIn,
497 "sineout" => SineOut,
498 "sineinout" => SineInOut,
499 "expoin" => ExpoIn,
500 "expoout" => ExpoOut,
501 "expoinout" => ExpoInOut,
502 _ => Linear,
503 }
504}
505
506fn animate_position_command(
507 world: &mut World,
508 entity: Entity,
509 to: Vec3,
510 seconds: f32,
511 easing: &str,
512) {
513 crate::animate::animate_position(world, entity, to, seconds, easing_from_name(easing));
514}
515
516fn animate_scale_command(world: &mut World, entity: Entity, to: Vec3, seconds: f32, easing: &str) {
517 crate::animate::animate_scale(world, entity, to, seconds, easing_from_name(easing));
518}
519
520fn animate_color_command(
521 world: &mut World,
522 entity: Entity,
523 to: [f32; 4],
524 seconds: f32,
525 easing: &str,
526) {
527 crate::animate::animate_color(world, entity, to, seconds, easing_from_name(easing));
528}
529
530fn set_shading_mode_command(world: &mut World, mode: &str) {
531 use nightshade::prelude::ShadingMode;
532 let mode = match mode.to_ascii_lowercase().as_str() {
533 "wireframe" => ShadingMode::Wireframe,
534 "flat" => ShadingMode::Flat,
535 "rendered" => ShadingMode::Rendered,
536 _ => ShadingMode::Solid,
537 };
538 crate::camera::set_shading_mode(world, mode);
539}
540
541#[cfg(feature = "physics")]
542#[derive(Serialize)]
543struct RaycastResultWire {
544 entity_id: u32,
545 distance: f32,
546 point: [f32; 3],
547 normal: [f32; 3],
548}
549
550#[cfg(feature = "physics")]
551fn raycast_command(
552 world: &mut World,
553 origin: Vec3,
554 direction: Vec3,
555 max_distance: f32,
556) -> Option<RaycastResultWire> {
557 crate::physics::raycast(world, origin, direction, max_distance).map(|hit| RaycastResultWire {
558 entity_id: hit.entity.id,
559 distance: hit.distance,
560 point: [hit.point.x, hit.point.y, hit.point.z],
561 normal: [hit.normal.x, hit.normal.y, hit.normal.z],
562 })
563}
564
565#[cfg(feature = "physics")]
566fn attach_fixed_command(world: &mut World, parent: Entity, child: Entity) -> bool {
567 crate::physics::attach_fixed(world, parent, child).is_some()
568}
569
570#[cfg(feature = "physics")]
571fn attach_hinge_command(world: &mut World, parent: Entity, child: Entity, axis: &str) -> bool {
572 use nightshade::ecs::physics::joints::JointAxisDirection;
573 let axis = match axis.to_ascii_lowercase().as_str() {
574 "y" => JointAxisDirection::Y,
575 "z" => JointAxisDirection::Z,
576 _ => JointAxisDirection::X,
577 };
578 crate::physics::attach_hinge(world, parent, child, axis).is_some()
579}
580
581#[cfg(feature = "physics")]
582fn attach_spring_command(
583 world: &mut World,
584 parent: Entity,
585 child: Entity,
586 rest_length: f32,
587 stiffness: f32,
588 damping: f32,
589) -> bool {
590 crate::physics::attach_spring(world, parent, child, rest_length, stiffness, damping).is_some()
591}
592
593#[cfg(feature = "physics")]
594fn attach_rope_command(
595 world: &mut World,
596 parent: Entity,
597 child: Entity,
598 max_distance: f32,
599) -> bool {
600 crate::physics::attach_rope(world, parent, child, max_distance).is_some()
601}
602
603#[cfg(feature = "picking")]
604#[derive(Serialize)]
605struct SurfacePickWire {
606 world_position: [f32; 3],
607 world_normal: [f32; 3],
608 depth: f32,
609 entity_id: Option<u32>,
610}
611
612#[cfg(feature = "picking")]
613fn take_surface_pick_command(world: &mut World) -> Option<SurfacePickWire> {
614 crate::picking::take_surface_pick(world).map(|result| SurfacePickWire {
615 world_position: [
616 result.world_position.x,
617 result.world_position.y,
618 result.world_position.z,
619 ],
620 world_normal: [
621 result.world_normal.x,
622 result.world_normal.y,
623 result.world_normal.z,
624 ],
625 depth: result.depth,
626 entity_id: result.entity_id,
627 })
628}
629
630fn save_scene_command(world: &mut World, name: &str) -> Vec<u8> {
631 crate::serialize::save_scene(world, name).unwrap_or_default()
632}
633
634fn load_scene_command(world: &mut World, bytes: &[u8]) -> Vec<Entity> {
635 crate::serialize::load_scene(world, bytes).unwrap_or_default()
636}
637
638fn key_from_name(name: &str) -> Option<KeyCode> {
642 let lower = name.to_ascii_lowercase();
643 Some(match lower.as_str() {
644 "a" => KeyCode::KeyA,
645 "b" => KeyCode::KeyB,
646 "c" => KeyCode::KeyC,
647 "d" => KeyCode::KeyD,
648 "e" => KeyCode::KeyE,
649 "f" => KeyCode::KeyF,
650 "g" => KeyCode::KeyG,
651 "h" => KeyCode::KeyH,
652 "i" => KeyCode::KeyI,
653 "j" => KeyCode::KeyJ,
654 "k" => KeyCode::KeyK,
655 "l" => KeyCode::KeyL,
656 "m" => KeyCode::KeyM,
657 "n" => KeyCode::KeyN,
658 "o" => KeyCode::KeyO,
659 "p" => KeyCode::KeyP,
660 "q" => KeyCode::KeyQ,
661 "r" => KeyCode::KeyR,
662 "s" => KeyCode::KeyS,
663 "t" => KeyCode::KeyT,
664 "u" => KeyCode::KeyU,
665 "v" => KeyCode::KeyV,
666 "w" => KeyCode::KeyW,
667 "x" => KeyCode::KeyX,
668 "y" => KeyCode::KeyY,
669 "z" => KeyCode::KeyZ,
670 "0" => KeyCode::Digit0,
671 "1" => KeyCode::Digit1,
672 "2" => KeyCode::Digit2,
673 "3" => KeyCode::Digit3,
674 "4" => KeyCode::Digit4,
675 "5" => KeyCode::Digit5,
676 "6" => KeyCode::Digit6,
677 "7" => KeyCode::Digit7,
678 "8" => KeyCode::Digit8,
679 "9" => KeyCode::Digit9,
680 "space" => KeyCode::Space,
681 "enter" | "return" => KeyCode::Enter,
682 "escape" | "esc" => KeyCode::Escape,
683 "tab" => KeyCode::Tab,
684 "backspace" => KeyCode::Backspace,
685 "delete" => KeyCode::Delete,
686 "left" => KeyCode::ArrowLeft,
687 "right" => KeyCode::ArrowRight,
688 "up" => KeyCode::ArrowUp,
689 "down" => KeyCode::ArrowDown,
690 "shift" | "lshift" => KeyCode::ShiftLeft,
691 "rshift" => KeyCode::ShiftRight,
692 "ctrl" | "control" | "lctrl" => KeyCode::ControlLeft,
693 "rctrl" => KeyCode::ControlRight,
694 "alt" | "lalt" => KeyCode::AltLeft,
695 "ralt" => KeyCode::AltRight,
696 _ => return None,
697 })
698}
699
700fn mouse_button_from_index(index: u8) -> MouseButton {
702 match index {
703 1 => MouseButton::Middle,
704 2 => MouseButton::Right,
705 _ => MouseButton::Left,
706 }
707}
708
709fn key_down_command(world: &World, key: &str) -> bool {
710 key_from_name(key)
711 .map(|key| crate::input::key_down(world, key))
712 .unwrap_or(false)
713}
714
715fn key_pressed_command(world: &World, key: &str) -> bool {
716 key_from_name(key)
717 .map(|key| crate::input::key_pressed(world, key))
718 .unwrap_or(false)
719}
720
721fn mouse_down_command(world: &World, button: u8) -> bool {
722 crate::input::mouse_down(world, mouse_button_from_index(button))
723}
724
725fn mouse_clicked_command(world: &World, button: u8) -> bool {
726 crate::input::mouse_clicked(world, mouse_button_from_index(button))
727}
728
729macro_rules! bind_argument {
730 ($field:ident, entity, $produced:ident, $world:ident) => {
731 let $field = match resolve($world, *$field, $produced) {
732 Some(entity) => entity,
733 None => {
734 return CommandReply::Error(
735 concat!(stringify!($field), ": unresolved entity reference").to_string(),
736 );
737 }
738 };
739 };
740 ($field:ident, opt_entity, $produced:ident, $world:ident) => {
741 let $field = match $field {
742 Some(reference) => match resolve($world, *reference, $produced) {
743 Some(entity) => Some(entity),
744 None => {
745 return CommandReply::Error(
746 concat!(stringify!($field), ": unresolved entity reference").to_string(),
747 );
748 }
749 },
750 None => None,
751 };
752 };
753 ($field:ident, vec3, $produced:ident, $world:ident) => {
754 let $field = array_to_vec3(*$field);
755 };
756 ($field:ident, vec2, $produced:ident, $world:ident) => {
757 let $field = array_to_vec2(*$field);
758 };
759 ($field:ident, copy, $produced:ident, $world:ident) => {
760 let $field = *$field;
761 };
762 ($field:ident, owned, $produced:ident, $world:ident) => {
763 let $field = $field.clone();
764 };
765 ($field:ident, text, $produced:ident, $world:ident) => {
766 let $field = $field.as_str();
767 };
768 ($field:ident, bytes, $produced:ident, $world:ident) => {
769 let $field = $field.as_slice();
770 };
771 ($field:ident, strs, $produced:ident, $world:ident) => {
772 let $field: Vec<&str> = $field.iter().map(|value| value.as_str()).collect();
773 let $field = $field.as_slice();
774 };
775 ($field:ident, vec3_list, $produced:ident, $world:ident) => {
776 let $field: Vec<Vec3> = $field.iter().map(|value| array_to_vec3(*value)).collect();
777 let $field = $field.as_slice();
778 };
779 ($field:ident, floats, $produced:ident, $world:ident) => {
780 let $field = $field.as_slice();
781 };
782 ($field:ident, indices, $produced:ident, $world:ident) => {
783 let $field: Vec<usize> = $field.iter().map(|value| *value as usize).collect();
784 let $field = $field.as_slice();
785 };
786 ($field:ident, opt_vec3, $produced:ident, $world:ident) => {
787 let $field = (*$field).map(array_to_vec3);
788 };
789 ($field:ident, transforms, $produced:ident, $world:ident) => {
790 let $field: Vec<nightshade::prelude::InstanceTransform> =
791 $field.iter().map(instance_from_wire).collect();
792 };
793 ($field:ident, refs, $produced:ident, $world:ident) => {
794 let mut resolved = Vec::with_capacity($field.len());
795 for reference in $field.iter() {
796 match resolve($world, *reference, $produced) {
797 Some(entity) => resolved.push(entity),
798 None => {
799 return CommandReply::Error(
800 concat!(stringify!($field), ": unresolved entity reference").to_string(),
801 );
802 }
803 }
804 }
805 let $field = resolved.as_slice();
806 };
807}
808
809macro_rules! wrap_reply {
810 (none, $call:expr) => {{
811 $call;
812 CommandReply::None
813 }};
814 (entity, $call:expr) => {
815 CommandReply::Entity($call)
816 };
817 (opt_entity, $call:expr) => {
818 match $call {
819 Some(entity) => CommandReply::Entity(entity),
820 None => CommandReply::None,
821 }
822 };
823 (bool, $call:expr) => {
824 CommandReply::Bool($call)
825 };
826 (float, $call:expr) => {
827 CommandReply::Float($call)
828 };
829 (vector, $call:expr) => {{
830 let value = $call;
831 CommandReply::Vector([value.x, value.y, value.z])
832 }};
833 (opt_vector, $call:expr) => {
834 match $call {
835 Some(value) => CommandReply::Vector([value.x, value.y, value.z]),
836 None => CommandReply::None,
837 }
838 };
839 (entities, $call:expr) => {
840 CommandReply::Entities($call)
841 };
842 (strings, $call:expr) => {
843 CommandReply::Strings($call)
844 };
845 (int, $call:expr) => {
846 CommandReply::Int($call as i64)
847 };
848 (text, $call:expr) => {
849 CommandReply::Text($call)
850 };
851 (bytes, $call:expr) => {
852 CommandReply::Bytes($call)
853 };
854 (json, $call:expr) => {
855 CommandReply::Json(
856 enum2schema::serde_json::to_value($call)
857 .unwrap_or(enum2schema::serde_json::Value::Null),
858 )
859 };
860}
861
862#[cfg(feature = "scripting")]
869pub fn command_method_name(variant: &str) -> String {
870 let characters: Vec<char> = variant.chars().collect();
871 let mut name = String::new();
872 for index in 0..characters.len() {
873 let character = characters[index];
874 if character.is_uppercase() {
875 let previous_lower = index > 0
876 && (characters[index - 1].is_lowercase() || characters[index - 1].is_ascii_digit());
877 let previous_upper = index > 0 && characters[index - 1].is_uppercase();
878 let next_lower = index + 1 < characters.len() && characters[index + 1].is_lowercase();
879 if index != 0 && (previous_lower || (previous_upper && next_lower)) {
880 name.push('_');
881 }
882 name.extend(character.to_lowercase());
883 } else if character.is_ascii_digit() {
884 if index > 0 && characters[index - 1].is_alphabetic() {
885 name.push('_');
886 }
887 name.push(character);
888 } else {
889 name.push(character);
890 }
891 }
892 name
893}
894
895#[cfg(feature = "scripting")]
899fn command_method_map(name: &str, pairs: Vec<(&'static str, rhai::Dynamic)>) -> rhai::Dynamic {
900 let mut fields = rhai::Map::new();
901 for (key, value) in pairs {
902 fields.insert(key.into(), value);
903 }
904 let mut outer = rhai::Map::new();
905 outer.insert(name.into(), rhai::Dynamic::from_map(fields));
906 rhai::Dynamic::from_map(outer)
907}
908
909fn command_description(variant: &str) -> &'static str {
914 match variant {
915 "SpawnCube" => "Spawn a cube at the given position",
916 "SpawnSphere" => "Spawn a sphere at the given position",
917 "SpawnCylinder" => "Spawn a cylinder at the given position",
918 "SpawnCone" => "Spawn a cone at the given position",
919 "SpawnPlane" => "Spawn a plane at the given position",
920 "SpawnTorus" => "Spawn a torus at the given position",
921 "SpawnFloor" => "Spawn a flat ground plane reaching the half extent in each direction",
922 "SpawnGroup" => "Spawn an invisible group at a position for building hierarchies",
923 "SpawnModel" => "Spawn a glb model with its textures, materials, skins, and animations",
924 "SpawnCustomMesh" => "Spawn an entity rendering a mesh built from raw vertex data",
925 "RegisterPrefab" => "Capture an entity subtree as a named reusable prefab",
926 "SpawnPrefabNamed" => "Stamp a copy of a named prefab at a position",
927 "SpawnObject" => "Spawn an object with mesh, color, and optional physics body in one call",
928 "SetColor" => "Set the entity's base color as linear RGBA",
929 "SetMetallicRoughness" => "Set the entity's metallic and roughness factors",
930 "SetEmissive" => "Make the entity glow with the given color and strength",
931 "SetUnlit" => "Disable lighting on the entity so its color renders as is",
932 "SetTexture" => "Set the entity's base color texture by name",
933 "SetTextureTiling" => {
934 "Tile the entity's base color texture the given number of times per axis"
935 }
936 "SetNormalTexture" => "Set the entity's normal map by texture name",
937 "SetMetallicRoughnessTexture" => {
938 "Set the entity's metallic and roughness map by texture name"
939 }
940 "SetEmissiveTexture" => "Set the entity's emissive map by texture name",
941 "SetOcclusionTexture" => "Set the entity's ambient occlusion map by texture name",
942 "SetPosition" => "Set the entity's position in its parent's space",
943 "SetScale" => "Set the entity's scale",
944 "SetRotation" => "Replace the entity's rotation with the given angle around an axis",
945 "Rotate" => "Rotate the entity around an axis on top of its current rotation",
946 "Position" => "Get the entity's position in world space",
947 "SetParent" => "Parent a child to a parent or unparent it, keeping its world position",
948 "SetVisible" => "Show or hide the entity without despawning it",
949 "Despawn" => "Despawn the entity and its descendants",
950 "Tag" => "Tag the entity with a label",
951 "Untag" => "Remove a label from the entity",
952 "HasTag" => "Check whether the entity carries the label",
953 "QueryTagged" => "Get every entity carrying the label",
954 "PointLight" => "Add a point light at the given position",
955 "SpotLight" => "Add a shadow casting spot light aimed at a target",
956 "SetSun" => "Adjust the default sun's color and intensity",
957 "SetBackground" => "Set the scene background",
958 "ShowGrid" => "Show or hide the reference grid",
959 "SetAmbient" => "Set the ambient light color as linear RGBA",
960 "SetClearColor" => "Set the background clear color as linear RGBA",
961 "SetBloom" => "Toggle bloom",
962 "SetBloomIntensity" => "Set the bloom strength",
963 "SetSsao" => "Toggle screen-space ambient occlusion",
964 "SetSsr" => "Toggle screen-space reflections on glossy surfaces",
965 "SetSsgi" => "Toggle screen-space global illumination",
966 "SetTaa" => "Toggle temporal antialiasing",
967 "SetExposure" => "Set the manual exposure multiplier",
968 "SetColorGrading" => "Set saturation, contrast, and brightness color grading",
969 "SetTimeOfDay" => "Set the hour of the day from 0 to 24",
970 "SetTimeScale" => "Set the global time scale: 0.5 for slow motion, 2.0 for fast forward",
971 "TimeScale" => "Get the current global time scale",
972 "Pause" => "Pause game time so scaled delta time reports zero",
973 "Unpause" => "Resume game time after a pause",
974 "SetPaused" => "Set whether game time is paused",
975 "IsPaused" => "Whether game time is currently paused",
976 "EmitFire" => "Emit a continuous fire at the given position",
977 "EmitSmoke" => "Emit a continuous smoke column at the given position",
978 "EmitBurst" => "Emit a one-shot burst of colored particles at the given position",
979 "DrawCube" => "Draw a cube with the given size and color for one frame",
980 "DrawSphere" => "Draw a sphere with the given radius and color for one frame",
981 "DrawCylinder" => "Draw an upright cylinder with the given size and color for one frame",
982 "DrawCone" => "Draw an upright cone with the given size and color for one frame",
983 "DrawTorus" => "Draw a flat torus with the given size and color for one frame",
984 "DrawLine" => "Draw a line from start to end for one frame",
985 "DrawText3d" => "Draw billboard text at a 3d position for one frame",
986 "SpawnLabel" => "Spawn 3d text at a position that always faces the camera",
987 "SpawnText" => "Spawn screen text at the given anchor",
988 "SetText" => "Replace the content of a text entity",
989 "SetTextColor" => "Set a text entity's color as linear RGBA",
990 "SetTextSize" => "Set a text entity's font size",
991 "SpawnPanel" => {
992 "Spawn an empty panel anchored to a window corner or center, sized in pixels"
993 }
994 "PanelLabel" => "Add a line of text to a panel",
995 "PanelButton" => "Add a themed button to a panel",
996 "ButtonClicked" => "Check whether the button was clicked this frame",
997 "ButtonHovered" => "Check whether the pointer is over the button",
998 "DespawnPanel" => "Remove a panel and everything in it",
999 "PanelRow" => "Add a horizontal row to a panel",
1000 "PanelGrid" => "Add a fixed-column grid to a panel",
1001 "PanelScroll" => "Add a scrollable region to a panel and return its content container",
1002 "SetScrollOffset" => "Scroll a panel-scroll region to a pixel offset from the top",
1003 "SetFocusOrder" => "Set a widget's keyboard focus order",
1004 "FocusWidget" => "Give keyboard focus to a widget immediately",
1005 "SpawnPanelAt" => {
1006 "Spawn a panel at any of the nine screen positions with a pixel offset and size"
1007 }
1008 "PanelText" => "Add a text label to a parent in a pixel rectangle with alignment",
1009 "PanelBox" => "Add a solid colored rectangle to a parent at a pixel offset and size",
1010 "PanelButtonAt" => "Add an interactive button to a parent at a pixel offset and size",
1011 "SetPanelRect" => "Reposition and resize a UI node within its parent, in pixels",
1012 "SetPanelColor" => "Set a UI node's background color as linear RGBA",
1013 "SetPanelText" => "Replace a panel text label's content",
1014 "SetPanelTextColor" => "Recolor a panel text label",
1015 "SetPanelSelected" => "Toggle a button's selected highlight with an accent tint",
1016 "SetPanelVisible" => "Show or hide a UI node and its children",
1017 "PlayAnimation" => "Start playing the model's animation clip at the given index",
1018 "PlayAnimationNamed" => "Play the animation clip with the given name",
1019 "SetAnimationLooping" => "Set whether the entity's current animation repeats",
1020 "SetAnimationSpeed" => "Set the playback speed of the entity's animation",
1021 "BlendToAnimation" => "Cross-fade from the current clip to another over the given seconds",
1022 "PauseAnimation" => "Pause the entity's animation at the current frame",
1023 "ResumeAnimation" => "Resume a paused animation from where it left off",
1024 "StopAnimation" => "Stop the entity's animation and reset it to the first frame",
1025 "AnimationClips" => "Get the names of the entity's animation clips in index order",
1026 "AddAnimationEvent" => "Add a named marker at a time on the clip at the given index",
1027 "AddAnimationEventNamed" => "Add a named marker at a time on the clip with the given name",
1028 "SetAnimationLayerWeight" => "Set the blend weight of an animation layer",
1029 "ClearAnimationLayers" => "Remove every animation layer, leaving only the base animation",
1030 "AimAt" => "Point a bone's forward axis at a world target, recomputed each frame",
1031 "OrbitCamera" => "Add an orbit camera focused on a point at the given radius",
1032 "FlyCamera" => "Add a free-flying camera at the given position",
1033 "FixedCamera" => "Add a stationary camera at an eye looking at a target",
1034 "LookAt" => "Repoint the active camera to look at a target from an eye",
1035 "SetOrbitFocus" => "Move the orbit camera's focus point",
1036 "SetOrbitView" => "Set the orbit camera's focus, distance, yaw, and pitch at once",
1037 "SetOrbitZoom" => "Enable or disable scroll-wheel zoom on the orbit camera",
1038 "SetOrbitModifier" => {
1039 "Set the modifier key the orbit camera requires before drag orbits, or clear it"
1040 }
1041 "SetFieldOfView" => "Set the active perspective camera's vertical field of view in degrees",
1042 "SetOrthographic" => "Switch the active camera to an orthographic projection",
1043 "SetPerspective" => "Switch the active camera to a perspective projection",
1044 "CameraPosition" => "Get the active camera's world position",
1045 "CameraForward" => "Get the active camera's forward direction as a unit vector",
1046 "FirstPerson" => {
1047 "Add a walking first-person player with mouse look, WASD, sprint, and jump"
1048 }
1049 "DeltaTime" => "Get the seconds the previous frame took",
1050 "ElapsedSeconds" => "Get the seconds since the app started",
1051 "KeyDown" => "Check whether a key is held down",
1052 "KeyPressed" => "Check whether a key went down this frame",
1053 "MouseDown" => "Check whether a mouse button is held down",
1054 "MouseClicked" => "Check whether a mouse button went down this frame",
1055 "Wasd" => "Get the WASD movement direction on the ground plane",
1056 "PointerOverUi" => "Check whether the pointer is over a UI element this frame",
1057 "MouseScroll" => "Get the scroll wheel delta this frame",
1058 "Push" => "Apply an instant impulse to a dynamic entity",
1059 "SetVelocity" => "Set a dynamic body's linear velocity directly",
1060 "ApplyForce" => "Apply a continuous force to a dynamic entity for this step",
1061 "ApplyTorque" => "Apply a continuous torque to a dynamic entity for this step",
1062 "SetAngularVelocity" => "Set a dynamic body's angular velocity directly",
1063 "Velocity" => "Get the entity's current linear velocity if it has a body",
1064 "AngularVelocity" => "Get the entity's current angular velocity if it has a body",
1065 "MakeSensor" => "Turn the entity's collider into an overlap-reporting sensor",
1066 "OverlapSphere" => "Get every entity whose collider overlaps a sphere",
1067 "SetCollisionGroups" => "Set the entity collider's membership and filter collision masks",
1068 "SetFriction" => "Set an entity collider's friction",
1069 "SetRestitution" => "Set an entity collider's restitution or bounciness",
1070 "SetLinearDamping" => "Set a dynamic body's linear damping",
1071 "SetAngularDamping" => "Set a dynamic body's angular damping",
1072 "SetMass" => "Set a dynamic body's mass in kilograms",
1073 "SetGravityScale" => "Set a body's per-body gravity multiplier",
1074 "BakeNavmesh" => "Bake a navmesh over the current static geometry",
1075 "SpawnWalker" => "Spawn a navmesh agent that walks to ordered destinations",
1076 "WalkTo" => "Order an agent to walk to a destination along the navmesh",
1077 "SetWalkSpeed" => "Set an agent's walk speed in units per second",
1078 "StopWalking" => "Stop an agent where it stands and clear its path",
1079 "ClickedEntity" => "Get the entity clicked this frame, if any",
1080 "EntityUnderCursor" => "Get the entity currently under the cursor, if any",
1081 "CursorOnGround" => "Get where the cursor ray meets the ground plane, if it does",
1082 "SpawnWorldPanel" => "Spawn a flat panel in the 3d world facing the camera",
1083 "WorldPanelButton" => "Add a button to a world panel at local coordinates",
1084 "WorldPanelLabel" => "Add a text label to a world panel at local coordinates",
1085 "WorldButtonClicked" => "Check whether a world-panel button was clicked this frame",
1086 "PauseSound" => "Pause a playing sound, keeping its position",
1087 "ResumeSound" => "Resume a paused sound from where it stopped",
1088 "FadeVolume" => "Fade a playing sound to a target volume over the given seconds",
1089 "Crossfade" => "Crossfade between two sounds over the given seconds",
1090 "SetBusVolume" => "Set an audio bus's volume in decibels, fading over seconds",
1091 "DuckVoice" => "Duck the music and ambient buses under the voice bus",
1092 "DirectionalLight" => {
1093 "Add a directional light shining along a direction, like a second sun"
1094 }
1095 "AreaLight" => "Add a rectangular area light, a glowing panel facing a target",
1096 "SetLightShadows" => "Turn shadow casting on or off for a light entity",
1097 "EmitSparks" => "Emit a continuous fountain of bright sparks at the given position",
1098 "EmitFirework" => "Launch a firework shell that arcs and bursts on its own",
1099 "EmitParticles" => "Spawn a configurable continuous emitter at the given position",
1100 "SetAlphaBlend" => "Turn alpha blending on or off for the entity",
1101 "SetAlphaCutoff" => "Switch the entity to alpha cutout, discarding texels below the cutoff",
1102 "SetDoubleSided" => "Render both faces of the entity's triangles",
1103 "SetIor" => "Set the index of refraction for the entity's surface",
1104 "SetTransmission" => "Set how much light passes through the entity",
1105 "SetClearcoat" => "Add a clearcoat layer over the entity with its own factor and roughness",
1106 "SetAnisotropy" => "Set anisotropic reflection stretching highlights along a rotation",
1107 "SetUvTransform" => "Transform the entity's base color texture coordinates",
1108 "SetSheen" => "Add a soft retroreflective sheen tint to the entity",
1109 "SetIridescence" => "Add a thin-film iridescence to the entity",
1110 "SetSpecular" => "Set the entity's specular reflectance factor and tint",
1111 "SetNormalScale" => "Scale the strength of the entity's normal map",
1112 "SetOcclusionStrength" => {
1113 "Scale how strongly the entity's ambient occlusion map darkens it"
1114 }
1115 "SetEmissiveStrength" => "Set the entity's emissive strength on its own",
1116 "SetThickness" => "Set the volume thickness of a transmissive entity",
1117 "SetTextOutline" => "Set a 3d text or label entity's outline width and color",
1118 "SetMorphWeight" => "Set one morph target's weight on an entity by index",
1119 "SetWindowTitle" => "Set the OS window title",
1120 "LockCursor" => "Lock the cursor to the window for mouse-look, or release it",
1121 "RequestExit" => "Ask the app to exit at the end of the frame",
1122 "SetRenderLayer" => "Put an entity on a render layer cameras can selectively show",
1123 "SetCameraLayers" => "Set which render layers a camera sees as a bitmask",
1124 "ThirdPersonCamera" => "Add a third-person camera trailing a target at a distance",
1125 "SpawnCloth" => "Spawn a cloth grid hanging from a position, pinned along its top edge",
1126 "ResetCloth" => "Reset a cloth back to its spawned shape, clearing all motion",
1127 "SetWind" => "Set a global wind force on all cloth",
1128 "PauseCutscene" => "Pause the running cutscene timeline",
1129 "ResumeCutscene" => "Resume a paused cutscene",
1130 "StopCutscene" => "Stop the cutscene and clear it",
1131 "SeekCutscene" => "Jump the cutscene to the given time along its timeline",
1132 "SetCutsceneCamera" => "Set the camera the cutscene drives",
1133 "BindCutsceneActor" => "Bind a named cutscene track to an entity it animates",
1134 "SpawnCylinderBody" => "Spawn a dynamic cylinder physics body at the given position",
1135 "SpawnCapsuleBody" => "Spawn a dynamic capsule physics body at the given position",
1136 "SetControllerSpeed" => "Set a character controller's move speed in units per second",
1137 "SetControllerJump" => "Set a character controller's jump impulse",
1138 "IsGrounded" => "Check whether a character controller is grounded this frame",
1139 "EnableTerrain" => "Turn on procedural terrain seeded by the given value",
1140 "DisableTerrain" => "Turn off procedural terrain",
1141 "SetTerrainHeightRange" => "Set the terrain's minimum and maximum height in world units",
1142 "SetTerrainSnowHeight" => "Set the elevation above which terrain turns to snow",
1143 "EnableGrass" => "Turn on procedural grass over the terrain",
1144 "DisableGrass" => "Turn off procedural grass",
1145 "LoadTexture" => "Register a texture from encoded png or jpeg bytes",
1146 "LoadTextureLinear" => {
1147 "Register a linear-space texture for normal, metallic-roughness, or occlusion data"
1148 }
1149 "RegisterTexture" => "Register a texture from raw RGBA8 pixels",
1150 "ListMaterials" => "Get every registered material name",
1151 "Material" => "Get a registered material's properties by name",
1152 "RegisterMaterial" => "Register a named material in the shared registry",
1153 "UpdateMaterial" => "Replace the material registered under a name and reupload it",
1154 "SetMaterialVariant" => "Activate a material variant by name across the scene",
1155 "SpawnObjects" => "Spawn one object at each position, all sharing one material",
1156 "SpawnInstanced" => {
1157 "Spawn one entity rendering many copies of a shape in a single draw call"
1158 }
1159 "SpawnInstancedWithMaterial" => {
1160 "Spawn an instanced mesh drawing every transform with a named material"
1161 }
1162 "SetInstances" => {
1163 "Replace an instanced batch's transforms and reupload its instance buffer"
1164 }
1165 "SpawnClothSheet" => "Spawn a simulated cloth sheet hanging from a position, pinned on top",
1166 "BlendToAnimationNamed" => "Cross-fade to a named clip over the given seconds",
1167 "AddAnimationLayer" => "Play an extra clip on top of the base animation at a weight",
1168 "NameEntity" => "Register an entity under a name so it can be looked up",
1169 "Name" => "Get the entity's name, or a stable fallback when it has none",
1170 "SetEntityName" => "Rename the entity",
1171 "Children" => "Get the entity's direct children in id order",
1172 "Descendants" => "Get the entity's whole subtree below it",
1173 "Roots" => "Get every parentless transform entity in id order",
1174 "SceneTree" => "Get the whole transform hierarchy flattened depth-first",
1175 "MaterialOf" => "Get the entity's resolved material as json",
1176 "Color" => "Get the entity's base color as linear RGBA",
1177 "MetallicRoughness" => "Get the entity's metallic and roughness factors",
1178 "Emissive" => "Get the entity's emissive color and strength",
1179 "Unlit" => "Check whether the entity renders unlit",
1180 "Texture" => "Get the entity's base color texture name, if any",
1181 "DescribeEntity" => "Get a summary of the entity as json",
1182 "SetTextAlignment" => "Set the horizontal alignment of a 3d text or label entity",
1183 "SetMorphWeights" => "Set every morph weight at once in target order",
1184 "MorphWeight" => "Get one morph target's weight by index",
1185 "MorphTargetCount" => "Get the number of morph targets the entity has",
1186 "SetFog" => "Enable distance fog between a start and end, or disable it",
1187 "SetDepthOfField" => "Set depth of field focus and blur parameters",
1188 "Screenshot" => "Save a screenshot of the next rendered frame to a path as png",
1189 "SetShadingMode" => "Set the active camera's shading mode",
1190 "AnimatePosition" => "Glide the entity to a target position over the given seconds",
1191 "AnimateScale" => "Grow or shrink the entity to a target scale over the given seconds",
1192 "AnimateColor" => "Fade the entity's base color to a target over the given seconds",
1193 "ShakeCamera" => "Shake the camera for a duration at the given strength",
1194 "ReachTo" => "Bend a three-joint chain so the tip reaches a world target",
1195 "Bounds" => "Get an entity's world-space bounding box as json",
1196 "BoundsOf" => "Get the combined world-space bounding box of several entities",
1197 "FrameEntities" => "Frame the given entities in view, moving the camera to fit them",
1198 "SpawnParticleEmitter" => "Spawn a fully configured particle emitter",
1199 "SetEmitter" => "Replace a spawned emitter's configuration",
1200 "SpawnDecal" => "Project a texture onto the surface at a position facing a normal",
1201 "SaveScene" => "Capture the world to a self-contained compressed binary scene",
1202 "LoadScene" => "Spawn a saved scene into the world and return its root entities",
1203 "WindowSize" => "Get the window's inner size in physical pixels",
1204 "CursorLocked" => "Check whether the cursor is currently locked",
1205 "FramesPerSecond" => "Get the current frames per second",
1206 "FrameCount" => "Get the number of frames rendered since startup",
1207 "UptimeMilliseconds" => "Get the milliseconds since the app started",
1208 "BakeNavmeshWith" => "Bake the navmesh with an explicit Recast configuration",
1209 "Raycast" => "Cast a ray against all physics colliders and return the closest hit",
1210 "AttachFixed" => "Weld two bodies together rigidly",
1211 "AttachHinge" => "Hinge two bodies around an axis",
1212 "AttachSpring" => "Connect two bodies with a spring",
1213 "AttachRope" => "Tether two bodies with a maximum separation",
1214 "ControllerVelocity" => "Get a character controller's current velocity",
1215 "MoveCharacter" => "Move a character controller this frame with input and a jump flag",
1216 "RequestSurfacePick" => "Request a precise GPU surface pick at a screen position",
1217 "TakeSurfacePick" => "Take the result of a requested surface pick, if ready",
1218 "WorldButtonHovered" => "Check whether the pointer is over a world-panel button this frame",
1219 "LoadSound" => "Register a sound from encoded audio file bytes",
1220 "PlaySound" => "Play a loaded sound once and return its voice entity",
1221 "PlaySoundLooping" => "Play a loaded sound on a loop and return its voice entity",
1222 "PlaySoundAt" => "Play a loaded sound once at a world position, attenuating with distance",
1223 "SetVolume" => "Set a playing sound's volume",
1224 "StopSound" => "Stop a sound and free its voice",
1225 "SetPitch" => "Set a sound's pitch and speed multiplier",
1226 "SetSpatialDistance" => "Set the distance range over which a spatial sound fades",
1227 "PanelCheckbox" => "Add a labeled checkbox to a panel",
1228 "CheckboxValue" => "Get the checkbox's current on or off value",
1229 "PanelSlider" => "Add a slider from min to max starting at an initial value to a panel",
1230 "SliderValue" => "Get the slider's current value",
1231 "SetSliderValue" => "Set the slider's value",
1232 "PanelTextInput" => "Add a single-line text input with placeholder text to a panel",
1233 "TextInputChanged" => "Get the input's new contents if it changed this frame",
1234 "PanelDropdown" => "Add a dropdown of options with an initial selection to a panel",
1235 "DropdownSelected" => "Get the newly chosen index if the dropdown changed this frame",
1236 "PanelProgressBar" => "Add a progress bar filled to an initial value to a panel",
1237 "SetProgress" => "Set a progress bar's fill from 0 to 1",
1238 "PanelToggle" => "Add an on or off toggle to a panel",
1239 "ToggleValue" => "Get the toggle's current on or off value",
1240 "PanelRadio" => "Add a radio button to a panel as one option of a group",
1241 "RadioSelected" => "Get the selected option index of a radio group, if any",
1242 "PanelRangeSlider" => "Add a dual-handle range slider to a panel",
1243 "SetRange" => "Set both handles of a range slider",
1244 "PanelTabs" => "Add a tab bar of labels to a panel with an initial selection",
1245 "SetTab" => "Select a tab bar's active tab by index",
1246 "PanelCollapsing" => "Add a collapsing section to a panel and return its content container",
1247 "PanelColorPicker" => "Add a color picker to a panel starting at a linear RGBA color",
1248 "ColorValue" => "Get the color picker's current color as linear RGBA",
1249 "PanelTextArea" => "Add a multi-line text area with placeholder text to a panel",
1250 "PanelTextAreaWithValue" => {
1251 "Add a multi-line text area pre-filled with an initial value to a panel"
1252 }
1253 "SetTextArea" => "Replace a text area's contents",
1254 "PanelMultiSelect" => "Add a multi-select chip list of options to a panel",
1255 "SetMultiSelect" => "Set a multi-select's chosen option indices",
1256 "PanelDatePicker" => "Add a date picker starting at a given date to a panel",
1257 "SetDate" => "Set a date picker's value",
1258 "PanelMenu" => "Add a dropdown menu listing items to a panel",
1259 "PanelColorPickerHsv" => {
1260 "Add an HSV color picker starting at a linear RGBA color to a panel"
1261 }
1262 "PanelSplitter" => "Add a draggable splitter dividing a panel into two resizable panes",
1263 "PanelBreadcrumb" => "Add a breadcrumb trail of segments to a panel",
1264 "PanelVirtualList" => "Add a virtual list that recycles a pool of rows to a panel",
1265 "PanelTable" => "Add a simple table with column headers and widths to a panel",
1266 "PanelDataGrid" => "Add a sortable, selectable data grid to a panel",
1267 "SetDataGridRows" => "Set a data grid's total row count",
1268 "SetDataGridCell" => "Set the text of a single data grid cell",
1269 "DataGridSelectionChanged" => {
1270 "Check whether the data grid's row selection changed this frame"
1271 }
1272 "PanelCommandPalette" => "Add a searchable command palette overlay to a panel",
1273 "PanelPropertyGrid" => "Add a two-column label and value property grid to a panel",
1274 "PanelPropertyRow" => "Add a labeled row to a property grid and return its value cell",
1275 "PanelTreeView" => "Add a tree view to a panel",
1276 "TreeContent" => "Get the root container of a tree view for adding top-level nodes",
1277 "TreeNode" => "Add a node at a depth under a container in a tree view",
1278 "TreeNodeChildren" => "Get the container a node's children go into for nesting",
1279 "SetTreeNodeExpanded" => "Expand or collapse a tree node",
1280 "TreeViewSelected" => "Get the entities of the currently selected tree nodes",
1281 "PanelDragValue" => "Add a numeric drag value field that scrubs as you drag to a panel",
1282 "DragValue" => "Get the drag value's current value",
1283 "PanelSelectable" => "Add a selectable label to a panel",
1284 "PanelModal" => "Add a centered modal dialog to a panel and return its content container",
1285 "PanelSpinner" => "Add an animated loading spinner to a panel",
1286 "PanelSeparator" => "Add a thin horizontal divider line to a panel",
1287 "PanelHeading" => "Add a larger heading-styled line of text to a panel",
1288 "SaveSceneToFile" => "Serialize the scene and write it to a file path (native only)",
1289 "LoadSceneFromFile" => {
1290 "Read a scene file from a path and spawn it, replying its root entities (native only)"
1291 }
1292 _ => "",
1293 }
1294}
1295
1296macro_rules! commands {
1297 (
1298 $(
1299 $(#[$meta:meta])*
1300 $variant:ident { $( $field:ident : $field_type:ty [$role:ident] ),* $(,)? }
1301 => $func:path , $reply:ident ;
1302 )*
1303 ) => {
1304 #[derive(Serialize, Deserialize, Clone, Debug, enum2schema::Schema)]
1308 pub enum Command {
1309 $(
1310 $(#[$meta])*
1311 $variant { $( $field : $field_type ),* },
1312 )*
1313 }
1314
1315 impl Command {
1316 pub fn name(&self) -> &'static str {
1319 match self {
1320 $(
1321 $(#[$meta])*
1322 Command::$variant { .. } => stringify!($variant),
1323 )*
1324 }
1325 }
1326 }
1327
1328 fn dispatch(
1329 world: &mut World,
1330 command: &Command,
1331 produced: &[Option<Entity>],
1332 ) -> CommandReply {
1333 match command {
1334 $(
1335 $(#[$meta])*
1336 Command::$variant { $( $field ),* } => {
1337 $( bind_argument!($field, $role, produced, world); )*
1338 wrap_reply!($reply, $func(world $(, $field)*))
1339 }
1340 )*
1341 }
1342 }
1343
1344 pub fn command_manifest() -> Vec<CommandSpec> {
1349 let mut specs = Vec::new();
1350 $(
1351 $(#[$meta])*
1352 specs.extend([CommandSpec {
1353 name: stringify!($variant),
1354 fields: vec![
1355 $( FieldSpec {
1356 name: stringify!($field),
1357 type_name: stringify!($field_type),
1358 role: stringify!($role),
1359 } ),*
1360 ],
1361 reply: stringify!($reply),
1362 description: command_description(stringify!($variant)),
1363 }]);
1364 )*
1365 specs
1366 }
1367
1368 #[cfg(feature = "scripting")]
1375 pub fn register_command_methods(engine: &mut rhai::Engine) {
1376 $(
1377 $(#[$meta])*
1378 engine.register_fn(
1379 command_method_name(stringify!($variant)),
1380 |commands: &mut rhai::Array $(, $field: rhai::Dynamic )*| {
1381 commands.push(command_method_map(
1382 stringify!($variant),
1383 vec![ $( (stringify!($field), $field) ),* ],
1384 ));
1385 },
1386 );
1387 )*
1388 }
1389 };
1390}
1391
1392commands! {
1393 SpawnCube { position: [f32; 3] [vec3] } => crate::scene::spawn_cube, entity;
1394 SpawnSphere { position: [f32; 3] [vec3] } => crate::scene::spawn_sphere, entity;
1395 SpawnCylinder { position: [f32; 3] [vec3] } => crate::scene::spawn_cylinder, entity;
1396 SpawnCone { position: [f32; 3] [vec3] } => crate::scene::spawn_cone, entity;
1397 SpawnPlane { position: [f32; 3] [vec3] } => crate::scene::spawn_plane, entity;
1398 SpawnTorus { position: [f32; 3] [vec3] } => crate::scene::spawn_torus, entity;
1399 SpawnFloor { half_extent: f32 [copy] } => crate::scene::spawn_floor, entity;
1400 SpawnGroup { position: [f32; 3] [vec3] } => crate::scene::spawn_group, entity;
1401 SpawnModel { glb: Vec<u8> [bytes], position: [f32; 3] [vec3] } => crate::scene::spawn_model, entity;
1402 SpawnCustomMesh { name: String [text], positions: Vec<f32> [floats], normals: Vec<f32> [floats], uvs: Vec<f32> [floats], indices: Vec<u32> [indices], position: [f32; 3] [vec3] } => spawn_custom_mesh_command, entity;
1403 RegisterPrefab { name: String [text], root: Ref [entity] } => crate::prefab::register_prefab_command, bool;
1404 SpawnPrefabNamed { name: String [text], position: [f32; 3] [vec3] } => crate::prefab::spawn_prefab_named_command, entities;
1405 SpawnObject { shape: Shape [copy], position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy], body: Body [copy] } => spawn_object_command, entity;
1406
1407 SetColor { entity: Ref [entity], color: [f32; 4] [copy] } => crate::appearance::set_color, none;
1408 SetMetallicRoughness { entity: Ref [entity], metallic: f32 [copy], roughness: f32 [copy] } => crate::appearance::set_metallic_roughness, none;
1409 SetEmissive { entity: Ref [entity], color: [f32; 3] [copy], strength: f32 [copy] } => crate::appearance::set_emissive, none;
1410 SetUnlit { entity: Ref [entity], unlit: bool [copy] } => crate::appearance::set_unlit, none;
1411 SetTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_texture, none;
1412 SetTextureTiling { entity: Ref [entity], repeats: f32 [copy] } => crate::appearance::set_texture_tiling, none;
1413 SetNormalTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_normal_texture, none;
1414 SetMetallicRoughnessTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_metallic_roughness_texture, none;
1415 SetEmissiveTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_emissive_texture, none;
1416 SetOcclusionTexture { entity: Ref [entity], texture: String [text] } => crate::appearance::set_occlusion_texture, none;
1417
1418 SetPosition { entity: Ref [entity], position: [f32; 3] [vec3] } => crate::placement::set_position, none;
1419 SetScale { entity: Ref [entity], scale: [f32; 3] [vec3] } => crate::placement::set_scale, none;
1420 SetRotation { entity: Ref [entity], axis: [f32; 3] [vec3], radians: f32 [copy] } => crate::placement::set_rotation, none;
1421 Rotate { entity: Ref [entity], axis: [f32; 3] [vec3], radians: f32 [copy] } => crate::placement::rotate, none;
1422 Position { entity: Ref [entity] } => crate::placement::position, vector;
1423
1424 SetParent { child: Ref [entity], parent: Option<Ref> [opt_entity] } => crate::scene::set_parent, none;
1425 SetVisible { entity: Ref [entity], visible: bool [copy] } => crate::scene::set_visible, none;
1426 Despawn { entity: Ref [entity] } => crate::scene::despawn, none;
1427
1428 Tag { entity: Ref [entity], label: String [text] } => crate::groups::tag, none;
1429 Untag { entity: Ref [entity], label: String [text] } => crate::groups::untag, none;
1430 HasTag { entity: Ref [entity], label: String [text] } => crate::groups::has_tag, bool;
1431 QueryTagged { label: String [text] } => crate::groups::tagged, entities;
1432
1433 PointLight { position: [f32; 3] [vec3], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::point_light, entity;
1434 SpotLight { position: [f32; 3] [vec3], target: [f32; 3] [vec3], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::spot_light, entity;
1435 SetSun { color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::set_sun, none;
1436
1437 SetBackground { background: crate::environment::Background [owned] } => crate::environment::set_background, none;
1438 ShowGrid { enabled: bool [copy] } => crate::environment::show_grid, none;
1439 SetAmbient { color: [f32; 4] [copy] } => crate::environment::set_ambient, none;
1440 SetClearColor { color: [f32; 4] [copy] } => crate::environment::set_clear_color, none;
1441 SetBloom { enabled: bool [copy] } => crate::environment::set_bloom, none;
1442 SetBloomIntensity { intensity: f32 [copy] } => crate::environment::set_bloom_intensity, none;
1443 SetSsao { enabled: bool [copy] } => crate::environment::set_ssao, none;
1444 SetSsr { enabled: bool [copy] } => crate::environment::set_ssr, none;
1445 SetSsgi { enabled: bool [copy] } => crate::environment::set_ssgi, none;
1446 SetTaa { enabled: bool [copy] } => crate::environment::set_taa, none;
1447 SetExposure { exposure: f32 [copy] } => crate::environment::set_exposure, none;
1448 SetColorGrading { saturation: f32 [copy], contrast: f32 [copy], brightness: f32 [copy] } => crate::environment::set_color_grading, none;
1449 SetTimeOfDay { hour: f32 [copy] } => crate::environment::set_time_of_day, none;
1450
1451 SetTimeScale { scale: f32 [copy] } => crate::clock::set_time_scale, none;
1452 TimeScale { } => crate::clock::time_scale, float;
1453 Pause { } => crate::clock::pause, none;
1454 Unpause { } => crate::clock::unpause, none;
1455 SetPaused { paused: bool [copy] } => crate::clock::set_paused, none;
1456 IsPaused { } => crate::clock::is_paused, bool;
1457
1458 EmitFire { position: [f32; 3] [vec3] } => crate::effects::emit_fire, entity;
1459 EmitSmoke { position: [f32; 3] [vec3] } => crate::effects::emit_smoke, entity;
1460 EmitBurst { position: [f32; 3] [vec3], color: [f32; 4] [copy], count: u32 [copy] } => crate::effects::emit_burst, entity;
1461
1462 DrawCube { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_cube, none;
1463 DrawSphere { position: [f32; 3] [vec3], radius: f32 [copy], color: [f32; 4] [copy] } => crate::draw::draw_sphere, none;
1464 DrawCylinder { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_cylinder, none;
1465 DrawCone { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_cone, none;
1466 DrawTorus { position: [f32; 3] [vec3], scale: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_torus, none;
1467 DrawLine { start: [f32; 3] [vec3], end: [f32; 3] [vec3], color: [f32; 4] [copy] } => crate::draw::draw_line, none;
1468 DrawText3d { text: String [text], position: [f32; 3] [vec3] } => crate::draw::draw_text_3d, none;
1469
1470 SpawnLabel { text: String [text], position: [f32; 3] [vec3] } => crate::text::spawn_label, entity;
1471 SpawnText { text: String [text], anchor: crate::text::ScreenAnchor [copy] } => crate::text::spawn_text, entity;
1472 SetText { entity: Ref [entity], text: String [text] } => crate::text::set_text, none;
1473 SetTextColor { entity: Ref [entity], color: [f32; 4] [copy] } => crate::text::set_text_color, none;
1474 SetTextSize { entity: Ref [entity], size: f32 [copy] } => crate::text::set_text_size, none;
1475
1476 SpawnPanel { anchor: crate::text::ScreenAnchor [copy], width: f32 [copy], height: f32 [copy] } => crate::ui::spawn_panel, entity;
1477 PanelLabel { panel: Ref [entity], text: String [text] } => crate::ui::panel_label, entity;
1478 PanelButton { panel: Ref [entity], text: String [text] } => crate::ui::panel_button, entity;
1479 ButtonClicked { button: Ref [entity] } => crate::ui::button_clicked, bool;
1480 ButtonHovered { button: Ref [entity] } => crate::ui::button_hovered, bool;
1481 DespawnPanel { panel: Ref [entity] } => crate::ui::despawn_panel, none;
1482 PanelRow { panel: Ref [entity], height: f32 [copy] } => crate::ui::panel_row, entity;
1483 PanelGrid { panel: Ref [entity], columns: usize [copy], row_height: f32 [copy], height: f32 [copy] } => crate::ui::panel_grid, entity;
1484 PanelScroll { panel: Ref [entity], height: f32 [copy] } => crate::ui::panel_scroll, entity;
1485 SetScrollOffset { scroll_area: Ref [entity], offset: f32 [copy] } => crate::ui::set_scroll_offset, none;
1486 SetFocusOrder { entity: Ref [entity], order: i32 [copy] } => crate::ui::set_focus_order, none;
1487 FocusWidget { entity: Ref [entity] } => crate::ui::focus_widget, none;
1488 SpawnPanelAt { anchor: crate::text::ScreenAnchor [copy], offset: [f32; 2] [vec2], size: [f32; 2] [vec2], color: [f32; 4] [copy] } => crate::ui::spawn_panel_at, entity;
1489 PanelText { parent: Ref [entity], text: String [text], rect: [f32; 4] [copy], font_size: f32 [copy], color: [f32; 4] [copy], align: TextAlignment [copy] } => crate::ui::panel_text, entity;
1490 PanelBox { parent: Ref [entity], offset: [f32; 2] [vec2], size: [f32; 2] [vec2], color: [f32; 4] [copy] } => crate::ui::panel_box, entity;
1491 PanelButtonAt { parent: Ref [entity], label: String [text], offset: [f32; 2] [vec2], size: [f32; 2] [vec2] } => crate::ui::panel_button_at, entity;
1492 SetPanelRect { node: Ref [entity], offset: [f32; 2] [vec2], size: [f32; 2] [vec2] } => crate::ui::set_panel_rect, none;
1493 SetPanelColor { node: Ref [entity], color: [f32; 4] [copy] } => crate::ui::set_panel_color, none;
1494 SetPanelText { label: Ref [entity], text: String [text] } => crate::ui::set_panel_text, none;
1495 SetPanelTextColor { label: Ref [entity], color: [f32; 4] [copy] } => crate::ui::set_panel_text_color, none;
1496 SetPanelSelected { button: Ref [entity], selected: bool [copy], accent: [f32; 4] [copy] } => crate::ui::set_panel_selected, none;
1497 SetPanelVisible { node: Ref [entity], visible: bool [copy] } => crate::ui::set_panel_visible, none;
1498
1499 PlayAnimation { entity: Ref [entity], clip: usize [copy] } => crate::scene::play_animation, none;
1500 PlayAnimationNamed { entity: Ref [entity], name: String [text] } => crate::scene::play_animation_named, bool;
1501 SetAnimationLooping { entity: Ref [entity], looping: bool [copy] } => crate::scene::set_animation_looping, none;
1502 SetAnimationSpeed { entity: Ref [entity], speed: f32 [copy] } => crate::scene::set_animation_speed, none;
1503 BlendToAnimation { entity: Ref [entity], clip: usize [copy], seconds: f32 [copy] } => crate::scene::blend_to_animation, none;
1504 PauseAnimation { entity: Ref [entity] } => crate::scene::pause_animation, none;
1505 ResumeAnimation { entity: Ref [entity] } => crate::scene::resume_animation, none;
1506 StopAnimation { entity: Ref [entity] } => crate::scene::stop_animation, none;
1507 AnimationClips { entity: Ref [entity] } => crate::scene::animation_clips, strings;
1508 AddAnimationEvent { entity: Ref [entity], clip_index: usize [copy], time: f32 [copy], name: String [text] } => crate::scene::add_animation_event, bool;
1509 AddAnimationEventNamed { entity: Ref [entity], clip_name: String [text], time: f32 [copy], name: String [text] } => crate::scene::add_animation_event_named, bool;
1510 SetAnimationLayerWeight { entity: Ref [entity], layer_index: usize [copy], weight: f32 [copy] } => crate::scene::set_animation_layer_weight, none;
1511 ClearAnimationLayers { entity: Ref [entity] } => crate::scene::clear_animation_layers, none;
1512 AimAt { bone: Ref [entity], target: [f32; 3] [vec3], forward: [f32; 3] [vec3] } => crate::animate::aim_at, none;
1513
1514 OrbitCamera { focus: [f32; 3] [vec3], radius: f32 [copy] } => crate::camera::orbit_camera, entity;
1515 FlyCamera { position: [f32; 3] [vec3] } => crate::camera::fly_camera, entity;
1516 FixedCamera { eye: [f32; 3] [vec3], target: [f32; 3] [vec3] } => crate::camera::fixed_camera, entity;
1517 LookAt { eye: [f32; 3] [vec3], target: [f32; 3] [vec3] } => crate::camera::look_at, none;
1518 SetOrbitFocus { focus: [f32; 3] [vec3] } => crate::camera::set_orbit_focus, none;
1519 SetOrbitView { focus: [f32; 3] [vec3], radius: f32 [copy], yaw: f32 [copy], pitch: f32 [copy] } => crate::camera::set_orbit_view, none;
1520 SetOrbitZoom { enabled: bool [copy] } => crate::camera::set_orbit_zoom, none;
1521 SetOrbitModifier { modifier: String [text] } => crate::camera::set_orbit_modifier, none;
1522 SetFieldOfView { degrees: f32 [copy] } => crate::camera::set_field_of_view, none;
1523 SetOrthographic { half_height: f32 [copy] } => crate::camera::set_orthographic, none;
1524 SetPerspective { degrees: f32 [copy] } => crate::camera::set_perspective, none;
1525 CameraPosition {} => crate::camera::camera_position, vector;
1526 CameraForward {} => crate::camera::camera_forward, vector;
1527 #[cfg(feature = "physics")]
1528 FirstPerson { position: [f32; 3] [vec3] } => crate::camera::first_person, entity;
1529
1530 DeltaTime {} => crate::input::delta_time, float;
1531 ElapsedSeconds {} => crate::input::elapsed_seconds, float;
1532 KeyDown { key: String [text] } => key_down_command, bool;
1533 KeyPressed { key: String [text] } => key_pressed_command, bool;
1534 MouseDown { button: u8 [copy] } => mouse_down_command, bool;
1535 MouseClicked { button: u8 [copy] } => mouse_clicked_command, bool;
1536 Wasd {} => crate::input::wasd, vector;
1537 PointerOverUi {} => crate::input::pointer_over_ui, bool;
1538 MouseScroll {} => crate::input::mouse_scroll, float;
1539
1540 #[cfg(feature = "physics")]
1541 Push { entity: Ref [entity], impulse: [f32; 3] [vec3] } => crate::physics::push, none;
1542 #[cfg(feature = "physics")]
1543 SetVelocity { entity: Ref [entity], velocity: [f32; 3] [vec3] } => crate::physics::set_velocity, none;
1544 #[cfg(feature = "physics")]
1545 ApplyForce { entity: Ref [entity], force: [f32; 3] [vec3] } => crate::physics::apply_force, none;
1546 #[cfg(feature = "physics")]
1547 ApplyTorque { entity: Ref [entity], torque: [f32; 3] [vec3] } => crate::physics::apply_torque, none;
1548 #[cfg(feature = "physics")]
1549 SetAngularVelocity { entity: Ref [entity], velocity: [f32; 3] [vec3] } => crate::physics::set_angular_velocity, none;
1550 #[cfg(feature = "physics")]
1551 Velocity { entity: Ref [entity] } => crate::physics::velocity, opt_vector;
1552 #[cfg(feature = "physics")]
1553 AngularVelocity { entity: Ref [entity] } => crate::physics::angular_velocity, opt_vector;
1554 #[cfg(feature = "physics")]
1555 MakeSensor { entity: Ref [entity] } => crate::physics::make_sensor, none;
1556 #[cfg(feature = "physics")]
1557 OverlapSphere { center: [f32; 3] [vec3], radius: f32 [copy] } => crate::physics::overlap_sphere, entities;
1558 #[cfg(feature = "physics")]
1559 SetCollisionGroups { entity: Ref [entity], membership: u32 [copy], filter: u32 [copy] } => crate::physics::set_collision_groups, none;
1560 #[cfg(feature = "physics")]
1561 SetFriction { entity: Ref [entity], friction: f32 [copy] } => crate::physics::set_friction, none;
1562 #[cfg(feature = "physics")]
1563 SetRestitution { entity: Ref [entity], restitution: f32 [copy] } => crate::physics::set_restitution, none;
1564 #[cfg(feature = "physics")]
1565 SetLinearDamping { entity: Ref [entity], damping: f32 [copy] } => crate::physics::set_linear_damping, none;
1566 #[cfg(feature = "physics")]
1567 SetAngularDamping { entity: Ref [entity], damping: f32 [copy] } => crate::physics::set_angular_damping, none;
1568 #[cfg(feature = "physics")]
1569 SetMass { entity: Ref [entity], mass: f32 [copy] } => crate::physics::set_mass, none;
1570 #[cfg(feature = "physics")]
1571 SetGravityScale { entity: Ref [entity], scale: f32 [copy] } => crate::physics::set_gravity_scale, none;
1572
1573 #[cfg(feature = "navmesh")]
1574 BakeNavmesh {} => crate::navigation::bake_navmesh, none;
1575 #[cfg(feature = "navmesh")]
1576 SpawnWalker { position: [f32; 3] [vec3] } => crate::navigation::spawn_walker, entity;
1577 #[cfg(feature = "navmesh")]
1578 WalkTo { agent: Ref [entity], destination: [f32; 3] [vec3] } => crate::navigation::walk_to, none;
1579 #[cfg(feature = "navmesh")]
1580 SetWalkSpeed { agent: Ref [entity], speed: f32 [copy] } => crate::navigation::set_walk_speed, none;
1581 #[cfg(feature = "navmesh")]
1582 StopWalking { agent: Ref [entity] } => crate::navigation::stop_walking, none;
1583
1584 #[cfg(feature = "picking")]
1585 ClickedEntity {} => crate::picking::clicked_entity, opt_entity;
1586 #[cfg(feature = "picking")]
1587 EntityUnderCursor {} => crate::picking::entity_under_cursor, opt_entity;
1588 #[cfg(feature = "picking")]
1589 CursorOnGround {} => crate::picking::cursor_on_ground, opt_vector;
1590 #[cfg(feature = "picking")]
1591 SpawnWorldPanel { position: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy], color: [f32; 4] [copy] } => crate::world_ui::spawn_world_panel, entity;
1592 #[cfg(feature = "picking")]
1593 WorldPanelButton { panel: Ref [entity], x: f32 [copy], y: f32 [copy], width: f32 [copy], height: f32 [copy], color: [f32; 4] [copy] } => crate::world_ui::world_panel_button, entity;
1594 #[cfg(feature = "picking")]
1595 WorldPanelLabel { panel: Ref [entity], text: String [text], x: f32 [copy], y: f32 [copy] } => crate::world_ui::world_panel_label, entity;
1596 #[cfg(feature = "picking")]
1597 WorldButtonClicked { button: Ref [entity] } => crate::world_ui::world_button_clicked, bool;
1598
1599 #[cfg(feature = "audio")]
1600 PauseSound { entity: Ref [entity] } => crate::audio::pause_sound, none;
1601 #[cfg(feature = "audio")]
1602 ResumeSound { entity: Ref [entity] } => crate::audio::resume_sound, none;
1603 #[cfg(feature = "audio")]
1604 FadeVolume { entity: Ref [entity], volume: f32 [copy], seconds: f32 [copy] } => crate::audio::fade_volume, none;
1605 #[cfg(feature = "audio")]
1606 Crossfade { fade_out: Ref [entity], fade_in: Ref [entity], volume: f32 [copy], seconds: f32 [copy] } => crate::audio::crossfade, none;
1607 #[cfg(feature = "audio")]
1608 SetBusVolume { bus: nightshade::prelude::AudioBus [copy], decibels: f32 [copy], fade_seconds: f32 [copy] } => crate::audio::set_bus_volume, none;
1609 #[cfg(feature = "audio")]
1610 DuckVoice { amount: f32 [copy], fade_seconds: f32 [copy] } => crate::audio::duck_voice, none;
1611
1612 DirectionalLight { direction: [f32; 3] [vec3], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::directional_light, entity;
1613 AreaLight { position: [f32; 3] [vec3], target: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy], color: [f32; 3] [copy], intensity: f32 [copy] } => crate::lighting::area_light, entity;
1614 SetLightShadows { light: Ref [entity], enabled: bool [copy] } => crate::lighting::set_light_shadows, none;
1615
1616 EmitSparks { position: [f32; 3] [vec3] } => crate::effects::emit_sparks, entity;
1617 EmitFirework { position: [f32; 3] [vec3], velocity: [f32; 3] [vec3] } => crate::effects::emit_firework, entity;
1618 EmitParticles { position: [f32; 3] [vec3], rate: f32 [copy], lifetime: f32 [copy], size: f32 [copy], gravity: [f32; 3] [vec3] } => crate::effects::emit_particles, entity;
1619
1620 SetAlphaBlend { entity: Ref [entity], enabled: bool [copy] } => crate::appearance::set_alpha_blend, none;
1621 SetAlphaCutoff { entity: Ref [entity], cutoff: f32 [copy] } => crate::appearance::set_alpha_cutoff, none;
1622 SetDoubleSided { entity: Ref [entity], double_sided: bool [copy] } => crate::appearance::set_double_sided, none;
1623 SetIor { entity: Ref [entity], ior: f32 [copy] } => crate::appearance::set_ior, none;
1624 SetTransmission { entity: Ref [entity], factor: f32 [copy] } => crate::appearance::set_transmission, none;
1625 SetClearcoat { entity: Ref [entity], factor: f32 [copy], roughness: f32 [copy] } => crate::appearance::set_clearcoat, none;
1626 SetAnisotropy { entity: Ref [entity], strength: f32 [copy], rotation: f32 [copy] } => crate::appearance::set_anisotropy, none;
1627 SetUvTransform { entity: Ref [entity], offset: [f32; 2] [copy], scale: [f32; 2] [copy], rotation: f32 [copy] } => crate::appearance::set_uv_transform, none;
1628 SetSheen { entity: Ref [entity], color: [f32; 3] [copy], roughness: f32 [copy] } => crate::appearance::set_sheen, none;
1629 SetIridescence { entity: Ref [entity], factor: f32 [copy], ior: f32 [copy] } => crate::appearance::set_iridescence, none;
1630 SetSpecular { entity: Ref [entity], factor: f32 [copy], color: [f32; 3] [copy] } => crate::appearance::set_specular, none;
1631 SetNormalScale { entity: Ref [entity], scale: f32 [copy] } => crate::appearance::set_normal_scale, none;
1632 SetOcclusionStrength { entity: Ref [entity], strength: f32 [copy] } => crate::appearance::set_occlusion_strength, none;
1633 SetEmissiveStrength { entity: Ref [entity], strength: f32 [copy] } => crate::appearance::set_emissive_strength, none;
1634 SetThickness { entity: Ref [entity], thickness: f32 [copy] } => crate::appearance::set_thickness, none;
1635
1636 SetTextOutline { entity: Ref [entity], width: f32 [copy], color: [f32; 4] [copy] } => crate::text::set_text_outline, none;
1637
1638 SetMorphWeight { entity: Ref [entity], index: u32 [copy], weight: f32 [copy] } => crate::morph::set_morph_weight, none;
1639
1640 SetWindowTitle { title: String [text] } => crate::window::set_window_title, none;
1641 LockCursor { locked: bool [copy] } => crate::window::lock_cursor, none;
1642 RequestExit {} => crate::window::request_exit, none;
1643
1644 SetRenderLayer { entity: Ref [entity], layer: u32 [copy] } => crate::render::set_render_layer, none;
1645 SetCameraLayers { camera: Ref [entity], mask: u32 [copy] } => crate::render::set_camera_layers, none;
1646
1647 ThirdPersonCamera { target: Ref [entity], distance: f32 [copy] } => crate::camera::third_person_camera, entity;
1648
1649 SpawnCloth { position: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy], columns: u32 [copy], rows: u32 [copy] } => crate::cloth::spawn_cloth, entity;
1650 ResetCloth { entity: Ref [entity] } => crate::cloth::reset_cloth, none;
1651 SetWind { direction: [f32; 3] [vec3], strength: f32 [copy] } => crate::cloth::set_wind, none;
1652
1653 PauseCutscene {} => crate::cutscene::pause_cutscene, none;
1654 ResumeCutscene {} => crate::cutscene::resume_cutscene, none;
1655 StopCutscene {} => crate::cutscene::stop_cutscene, none;
1656 SeekCutscene { seconds: f32 [copy] } => crate::cutscene::seek_cutscene, none;
1657 SetCutsceneCamera { camera: Ref [entity] } => crate::cutscene::set_cutscene_camera, none;
1658 BindCutsceneActor { name: String [text], entity: Ref [entity] } => crate::cutscene::bind_cutscene_actor, none;
1659
1660 #[cfg(feature = "physics")]
1661 SpawnCylinderBody { position: [f32; 3] [vec3], half_height: f32 [copy], radius: f32 [copy], mass: f32 [copy], color: [f32; 4] [copy] } => crate::physics::spawn_cylinder_body, entity;
1662 #[cfg(feature = "physics")]
1663 SpawnCapsuleBody { position: [f32; 3] [vec3], half_height: f32 [copy], radius: f32 [copy], mass: f32 [copy], color: [f32; 4] [copy] } => crate::scene::spawn_capsule_body, entity;
1664 #[cfg(feature = "physics")]
1665 SetControllerSpeed { entity: Ref [entity], speed: f32 [copy] } => crate::character::set_controller_speed, none;
1666 #[cfg(feature = "physics")]
1667 SetControllerJump { entity: Ref [entity], impulse: f32 [copy] } => crate::character::set_controller_jump, none;
1668 #[cfg(feature = "physics")]
1669 IsGrounded { entity: Ref [entity] } => crate::character::is_grounded, bool;
1670
1671 #[cfg(feature = "terrain")]
1672 EnableTerrain { seed: u32 [copy] } => crate::terrain::enable_terrain, none;
1673 #[cfg(feature = "terrain")]
1674 DisableTerrain {} => crate::terrain::disable_terrain, none;
1675 #[cfg(feature = "terrain")]
1676 SetTerrainHeightRange { min: f32 [copy], max: f32 [copy] } => crate::terrain::set_terrain_height_range, none;
1677 #[cfg(feature = "terrain")]
1678 SetTerrainSnowHeight { height: f32 [copy] } => crate::terrain::set_terrain_snow_height, none;
1679 #[cfg(feature = "grass")]
1680 EnableGrass {} => crate::terrain::enable_grass, none;
1681 #[cfg(feature = "grass")]
1682 DisableGrass {} => crate::terrain::disable_grass, none;
1683
1684 LoadTexture { name: String [text], image_bytes: Vec<u8> [bytes] } => crate::appearance::load_texture, none;
1685 LoadTextureLinear { name: String [text], image_bytes: Vec<u8> [bytes] } => crate::appearance::load_texture_linear, none;
1686 RegisterTexture { name: String [text], width: u32 [copy], height: u32 [copy], rgba: Vec<u8> [bytes] } => crate::appearance::register_texture, none;
1687
1688 ListMaterials {} => crate::materials::list_materials, json;
1689 Material { name: String [text] } => crate::materials::material, json;
1690 RegisterMaterial { name: String [text], base_color: [f32; 4] [copy], metallic: f32 [copy], roughness: f32 [copy], emissive: [f32; 3] [copy] } => register_material_command, text;
1691 UpdateMaterial { name: String [text], base_color: [f32; 4] [copy], metallic: f32 [copy], roughness: f32 [copy], emissive: [f32; 3] [copy] } => update_material_command, none;
1692 SetMaterialVariant { variant: String [text] } => set_material_variant_command, int;
1693
1694 SpawnObjects { shape: Shape [copy], scale: [f32; 3] [vec3], color: [f32; 4] [copy], body: Body [copy], positions: Vec<[f32; 3]> [vec3_list] } => spawn_objects_command, entities;
1695 SpawnInstanced { shape: Shape [copy], transforms: Vec<InstanceWire> [transforms], color: [f32; 4] [copy] } => crate::scene::spawn_instanced, entity;
1696 SpawnInstancedWithMaterial { shape: Shape [copy], transforms: Vec<InstanceWire> [transforms], material: String [text] } => crate::scene::spawn_instanced_with_material, entity;
1697 SetInstances { batch: Ref [entity], transforms: Vec<InstanceWire> [transforms] } => crate::scene::set_instances, none;
1698 SpawnClothSheet { position: [f32; 3] [vec3], width: f32 [copy], height: f32 [copy] } => crate::scene::spawn_cloth_sheet, entity;
1699 BlendToAnimationNamed { entity: Ref [entity], name: String [text], seconds: f32 [copy] } => crate::scene::blend_to_animation_named, bool;
1700 AddAnimationLayer { entity: Ref [entity], clip_index: usize [copy], weight: f32 [copy] } => crate::scene::add_animation_layer, none;
1701 NameEntity { name: String [text], entity: Ref [entity] } => crate::scene::name_entity, none;
1702
1703 Name { entity: Ref [entity] } => crate::hierarchy::name, text;
1704 SetEntityName { entity: Ref [entity], name: String [text] } => crate::hierarchy::set_name, none;
1705 Children { entity: Ref [entity] } => crate::hierarchy::children, entities;
1706 Descendants { entity: Ref [entity] } => crate::hierarchy::descendants, entities;
1707 Roots {} => crate::hierarchy::roots, entities;
1708 SceneTree {} => crate::hierarchy::scene_tree, json;
1709
1710 MaterialOf { entity: Ref [entity] } => crate::inspect::material_of, json;
1711 Color { entity: Ref [entity] } => crate::inspect::color, json;
1712 MetallicRoughness { entity: Ref [entity] } => crate::inspect::metallic_roughness, json;
1713 Emissive { entity: Ref [entity] } => crate::inspect::emissive, json;
1714 Unlit { entity: Ref [entity] } => crate::inspect::unlit, json;
1715 Texture { entity: Ref [entity] } => crate::inspect::texture, json;
1716 DescribeEntity { entity: Ref [entity] } => crate::inspect::describe_entity, json;
1717
1718 SetTextAlignment { entity: Ref [entity], alignment: TextAlignment [copy] } => crate::text::set_text_alignment, none;
1719
1720 SetMorphWeights { entity: Ref [entity], weights: Vec<f32> [floats] } => crate::morph::set_morph_weights, none;
1721 MorphWeight { entity: Ref [entity], index: u32 [copy] } => crate::morph::morph_weight, float;
1722 MorphTargetCount { entity: Ref [entity] } => crate::morph::morph_target_count, int;
1723
1724 SetFog { enabled: bool [copy], color: [f32; 3] [copy], start: f32 [copy], end: f32 [copy] } => set_fog_command, none;
1725 SetDepthOfField { enabled: bool [copy], focus_distance: f32 [copy], focus_range: f32 [copy], max_blur_radius: f32 [copy], bokeh_threshold: f32 [copy] } => set_depth_of_field_command, none;
1726 Screenshot { path: String [text] } => screenshot_command, none;
1727
1728 SetShadingMode { mode: String [text] } => set_shading_mode_command, none;
1729
1730 AnimatePosition { entity: Ref [entity], to: [f32; 3] [vec3], seconds: f32 [copy], easing: String [text] } => animate_position_command, none;
1731 AnimateScale { entity: Ref [entity], to: [f32; 3] [vec3], seconds: f32 [copy], easing: String [text] } => animate_scale_command, none;
1732 AnimateColor { entity: Ref [entity], to: [f32; 4] [copy], seconds: f32 [copy], easing: String [text] } => animate_color_command, none;
1733 ShakeCamera { strength: f32 [copy], seconds: f32 [copy] } => crate::animate::shake_camera, none;
1734 ReachTo { root: Ref [entity], mid: Ref [entity], tip: Ref [entity], target: [f32; 3] [vec3], pole: Option<[f32; 3]> [opt_vec3] } => crate::animate::reach_to, none;
1735
1736 Bounds { entity: Ref [entity] } => crate::bounds::bounds, json;
1737 BoundsOf { entities: Vec<Ref> [refs] } => crate::bounds::bounds_of, json;
1738 FrameEntities { entities: Vec<Ref> [refs] } => crate::bounds::frame_entities, none;
1739
1740 SpawnParticleEmitter { emitter: EmitterWire [owned] } => spawn_particle_emitter_command, entity;
1741 SetEmitter { emitter_entity: Ref [entity], emitter: EmitterWire [owned] } => set_emitter_command, none;
1742
1743 SpawnDecal { texture: String [text], position: [f32; 3] [vec3], normal: [f32; 3] [vec3], size: f32 [copy] } => crate::decals::spawn_decal, entity;
1744
1745 SaveScene { name: String [text] } => save_scene_command, bytes;
1746 LoadScene { bytes: Vec<u8> [bytes] } => load_scene_command, entities;
1747
1748 WindowSize {} => crate::window::window_size, json;
1749 CursorLocked {} => crate::window::cursor_locked, bool;
1750 FramesPerSecond {} => crate::window::frames_per_second, float;
1751 FrameCount {} => crate::window::frame_count, int;
1752 UptimeMilliseconds {} => crate::window::uptime_milliseconds, int;
1753
1754 #[cfg(feature = "navmesh")]
1755 BakeNavmeshWith { config: RecastConfigWire [owned] } => bake_navmesh_with_command, none;
1756
1757 #[cfg(feature = "physics")]
1758 Raycast { origin: [f32; 3] [vec3], direction: [f32; 3] [vec3], max_distance: f32 [copy] } => raycast_command, json;
1759 #[cfg(feature = "physics")]
1760 AttachFixed { parent: Ref [entity], child: Ref [entity] } => attach_fixed_command, bool;
1761 #[cfg(feature = "physics")]
1762 AttachHinge { parent: Ref [entity], child: Ref [entity], axis: String [text] } => attach_hinge_command, bool;
1763 #[cfg(feature = "physics")]
1764 AttachSpring { parent: Ref [entity], child: Ref [entity], rest_length: f32 [copy], stiffness: f32 [copy], damping: f32 [copy] } => attach_spring_command, bool;
1765 #[cfg(feature = "physics")]
1766 AttachRope { parent: Ref [entity], child: Ref [entity], max_distance: f32 [copy] } => attach_rope_command, bool;
1767 #[cfg(feature = "physics")]
1768 ControllerVelocity { entity: Ref [entity] } => crate::character::controller_velocity, vector;
1769 #[cfg(feature = "physics")]
1770 MoveCharacter { entity: Ref [entity], movement: [f32; 2] [vec2], jump: bool [copy] } => crate::character::move_character, none;
1771
1772 #[cfg(feature = "picking")]
1773 RequestSurfacePick { screen_pos: [f32; 2] [vec2] } => crate::picking::request_surface_pick, none;
1774 #[cfg(feature = "picking")]
1775 TakeSurfacePick {} => take_surface_pick_command, json;
1776 #[cfg(feature = "picking")]
1777 WorldButtonHovered { button: Ref [entity] } => crate::world_ui::world_button_hovered, bool;
1778
1779 #[cfg(feature = "audio")]
1780 LoadSound { name: String [text], bytes: Vec<u8> [bytes] } => crate::audio::load_sound, none;
1781 #[cfg(feature = "audio")]
1782 PlaySound { name: String [text] } => crate::audio::play_sound, entity;
1783 #[cfg(feature = "audio")]
1784 PlaySoundLooping { name: String [text] } => crate::audio::play_sound_looping, entity;
1785 #[cfg(feature = "audio")]
1786 PlaySoundAt { name: String [text], position: [f32; 3] [vec3] } => crate::audio::play_sound_at, entity;
1787 #[cfg(feature = "audio")]
1788 SetVolume { entity: Ref [entity], volume: f32 [copy] } => crate::audio::set_volume, none;
1789 #[cfg(feature = "audio")]
1790 StopSound { entity: Ref [entity] } => crate::audio::stop_sound, none;
1791 #[cfg(feature = "audio")]
1792 SetPitch { entity: Ref [entity], rate: f32 [copy] } => crate::audio::set_pitch, none;
1793 #[cfg(feature = "audio")]
1794 SetSpatialDistance { entity: Ref [entity], min: f32 [copy], max: f32 [copy] } => crate::audio::set_spatial_distance, none;
1795
1796 PanelCheckbox { panel: Ref [entity], label: String [text], initial: bool [copy] } => crate::ui::panel_checkbox, entity;
1797 CheckboxValue { checkbox: Ref [entity] } => crate::ui::checkbox_value, bool;
1798 PanelSlider { panel: Ref [entity], min: f32 [copy], max: f32 [copy], initial: f32 [copy] } => crate::ui::panel_slider, entity;
1799 SliderValue { slider: Ref [entity] } => crate::ui::slider_value, float;
1800 SetSliderValue { slider: Ref [entity], value: f32 [copy] } => crate::ui::set_slider_value, none;
1801 PanelTextInput { panel: Ref [entity], placeholder: String [text] } => crate::ui::panel_text_input, entity;
1802 TextInputChanged { input: Ref [entity] } => crate::ui::text_input_changed, json;
1803 PanelDropdown { panel: Ref [entity], options: Vec<String> [strs], initial: usize [copy] } => crate::ui::panel_dropdown, entity;
1804 DropdownSelected { dropdown: Ref [entity] } => crate::ui::dropdown_selected, json;
1805 PanelProgressBar { panel: Ref [entity], initial: f32 [copy] } => crate::ui::panel_progress_bar, entity;
1806 SetProgress { bar: Ref [entity], value: f32 [copy] } => crate::ui::set_progress, none;
1807 PanelToggle { panel: Ref [entity], initial: bool [copy] } => crate::ui::panel_toggle, entity;
1808 ToggleValue { toggle: Ref [entity] } => crate::ui::toggle_value, bool;
1809 PanelRadio { panel: Ref [entity], label: String [text], group_id: u32 [copy], option_index: usize [copy] } => crate::ui::panel_radio, entity;
1810 RadioSelected { group_id: u32 [copy] } => crate::ui::radio_selected, json;
1811 PanelRangeSlider { panel: Ref [entity], min: f32 [copy], max: f32 [copy], low: f32 [copy], high: f32 [copy] } => crate::ui::panel_range_slider, entity;
1812 SetRange { slider: Ref [entity], low: f32 [copy], high: f32 [copy] } => crate::ui::set_range, none;
1813 PanelTabs { panel: Ref [entity], labels: Vec<String> [strs], initial: usize [copy] } => crate::ui::panel_tabs, entity;
1814 SetTab { tabs: Ref [entity], index: usize [copy] } => crate::ui::set_tab, none;
1815 PanelCollapsing { panel: Ref [entity], label: String [text], open: bool [copy] } => crate::ui::panel_collapsing, entity;
1816 PanelColorPicker { panel: Ref [entity], initial: [f32; 4] [copy] } => crate::ui::panel_color_picker, entity;
1817 ColorValue { picker: Ref [entity] } => crate::ui::color_value, json;
1818 PanelTextArea { panel: Ref [entity], placeholder: String [text], rows: usize [copy] } => crate::ui::panel_text_area, entity;
1819 PanelTextAreaWithValue { panel: Ref [entity], placeholder: String [text], rows: usize [copy], initial: String [text] } => crate::ui::panel_text_area_with_value, entity;
1820 SetTextArea { area: Ref [entity], text: String [text] } => crate::ui::set_text_area, none;
1821 PanelMultiSelect { panel: Ref [entity], options: Vec<String> [strs] } => crate::ui::panel_multi_select, entity;
1822 SetMultiSelect { widget: Ref [entity], indices: Vec<u32> [indices] } => crate::ui::set_multi_select, none;
1823 PanelDatePicker { panel: Ref [entity], year: i32 [copy], month: u32 [copy], day: u32 [copy] } => crate::ui::panel_date_picker, entity;
1824 SetDate { picker: Ref [entity], year: i32 [copy], month: u32 [copy], day: u32 [copy] } => crate::ui::set_date, none;
1825 PanelMenu { panel: Ref [entity], label: String [text], items: Vec<String> [strs] } => crate::ui::panel_menu, entity;
1826 PanelColorPickerHsv { panel: Ref [entity], initial: [f32; 4] [copy] } => crate::ui::panel_color_picker_hsv, entity;
1827 PanelSplitter { panel: Ref [entity], horizontal: bool [copy], ratio: f32 [copy] } => panel_splitter_command, entity;
1828 PanelBreadcrumb { panel: Ref [entity], segments: Vec<String> [strs] } => crate::ui::panel_breadcrumb, entity;
1829 PanelVirtualList { panel: Ref [entity], item_height: f32 [copy], pool_size: usize [copy] } => crate::ui::panel_virtual_list, entity;
1830 PanelTable { panel: Ref [entity], headers: Vec<String> [strs], widths: Vec<f32> [floats] } => crate::ui::panel_table, entity;
1831 PanelDataGrid { panel: Ref [entity], headers: Vec<String> [strs], widths: Vec<f32> [floats], pool_size: usize [copy] } => panel_data_grid_command, entity;
1832 SetDataGridRows { grid: Ref [entity], count: usize [copy] } => crate::ui::set_data_grid_rows, none;
1833 SetDataGridCell { grid: Ref [entity], row: usize [copy], column: usize [copy], text: String [text] } => crate::ui::set_data_grid_cell, none;
1834 DataGridSelectionChanged { grid: Ref [entity] } => crate::ui::data_grid_selection_changed, bool;
1835 PanelCommandPalette { panel: Ref [entity], pool_size: usize [copy] } => crate::ui::panel_command_palette, entity;
1836 PanelPropertyGrid { panel: Ref [entity], label_width: f32 [copy] } => crate::ui::panel_property_grid, entity;
1837 PanelPropertyRow { grid: Ref [entity], label: String [text] } => crate::ui::panel_property_row, entity;
1838 PanelTreeView { panel: Ref [entity], multi_select: bool [copy] } => crate::ui::panel_tree_view, entity;
1839 TreeContent { tree_view: Ref [entity] } => crate::ui::tree_content, entity;
1840 TreeNode { tree_view: Ref [entity], parent_container: Ref [entity], label: String [text], depth: usize [copy], user_data: u64 [copy] } => crate::ui::tree_node, entity;
1841 TreeNodeChildren { node: Ref [entity] } => crate::ui::tree_node_children, entity;
1842 SetTreeNodeExpanded { node: Ref [entity], expanded: bool [copy] } => crate::ui::set_tree_node_expanded, none;
1843 TreeViewSelected { tree_view: Ref [entity] } => crate::ui::tree_view_selected, entities;
1844 PanelDragValue { panel: Ref [entity], min: f32 [copy], max: f32 [copy], initial: f32 [copy] } => crate::ui::panel_drag_value, entity;
1845 DragValue { widget: Ref [entity] } => crate::ui::drag_value, float;
1846 PanelSelectable { panel: Ref [entity], text: String [text], group: u32 [copy], grouped: bool [copy] } => panel_selectable_command, entity;
1847 PanelModal { panel: Ref [entity], title: String [text], width: f32 [copy], height: f32 [copy] } => crate::ui::panel_modal, entity;
1848 PanelSpinner { panel: Ref [entity] } => crate::ui::panel_spinner, entity;
1849 PanelSeparator { panel: Ref [entity] } => crate::ui::panel_separator, entity;
1850 PanelHeading { panel: Ref [entity], text: String [text] } => crate::ui::panel_heading, entity;
1851
1852 SaveSceneToFile { path: String [text] } => crate::filesystem::save_scene_to_path, bool;
1853 LoadSceneFromFile { path: String [text] } => crate::filesystem::load_scene_from_path, entities;
1854}
1855
1856#[cfg(test)]
1857mod tests {
1858 use super::*;
1859
1860 #[test]
1861 fn command_schema_covers_the_surface() {
1862 let schema = command_schema();
1863 assert!(schema.get("oneOf").is_some());
1864 let text = enum2schema::serde_json::to_string(&schema).unwrap();
1865 for variant in [
1866 "SpawnCube",
1867 "SpawnObject",
1868 "SetColor",
1869 "Rotate",
1870 "QueryTagged",
1871 ] {
1872 assert!(text.contains(variant), "schema missing {variant}");
1873 }
1874 }
1875
1876 #[test]
1877 fn reply_schema_is_generated() {
1878 assert!(command_reply_schema().get("oneOf").is_some());
1879 }
1880
1881 #[test]
1882 fn manifest_covers_the_surface() {
1883 let manifest = command_manifest();
1884 assert!(!manifest.is_empty());
1885 let names: Vec<&str> = manifest.iter().map(|spec| spec.name).collect();
1886 for variant in [
1887 "SpawnCube",
1888 "SpawnObject",
1889 "SetColor",
1890 "Rotate",
1891 "QueryTagged",
1892 ] {
1893 assert!(names.contains(&variant), "manifest missing {variant}");
1894 }
1895 let replies = [
1896 "none",
1897 "entity",
1898 "opt_entity",
1899 "bool",
1900 "float",
1901 "int",
1902 "text",
1903 "vector",
1904 "opt_vector",
1905 "entities",
1906 "strings",
1907 "bytes",
1908 "json",
1909 ];
1910 for spec in &manifest {
1911 assert!(
1912 replies.contains(&spec.reply),
1913 "unknown reply {}",
1914 spec.reply
1915 );
1916 }
1917 }
1918
1919 #[test]
1920 fn every_command_has_a_description() {
1921 for spec in command_manifest() {
1922 assert!(
1923 !spec.description.is_empty(),
1924 "command {} has no description; add one to command_description",
1925 spec.name
1926 );
1927 }
1928 }
1929
1930 #[test]
1931 fn entity_reference_serializes_as_its_schema_claims() {
1932 let entity = Entity {
1933 id: 3,
1934 generation: 1,
1935 };
1936 let value = enum2schema::serde_json::to_value(Ref::Entity(entity)).unwrap();
1937 let inner = value
1938 .get("Entity")
1939 .and_then(|tagged| tagged.as_object())
1940 .expect("Ref::Entity serializes as an externally tagged object");
1941 assert!(inner.contains_key("id"));
1942 assert!(inner.contains_key("generation"));
1943 assert_eq!(inner.len(), 2);
1944 }
1945}