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