1#[cfg(feature = "protocol-agent")]
5mod agent;
6#[cfg(feature = "protocol-agent")]
7pub use agent::*;
8
9use crate::command::Command;
10use nightshade::ecs::audio::components::AudioSource;
11use nightshade::ecs::camera::components::Camera;
12use nightshade::ecs::decal::components::Decal;
13use nightshade::ecs::event::Event;
14use nightshade::ecs::gizmos::GizmoMode;
15use nightshade::ecs::light::components::Light;
16use nightshade::ecs::scene::{SceneChunkConfig, SceneLayerConfig};
17use nightshade::ecs::text::components::TextProperties;
18use nightshade::ecs::vfx::components::{Beam, LightningBolt, Trail, VfxAnimator};
19use nightshade::ecs::water::components::Water;
20pub use nightshade::prelude::{
21 Atmosphere, CharacterControllerComponent, ColliderComponent, NavMeshAgent, RigidBodyComponent,
22};
23use nightshade::render::material::Material;
24use nightshade::render::particles::ParticleEmitter;
25use serde::{Deserialize, Serialize};
26
27pub use nightshade::ecs::audio::components::AudioSource as AudioSourceComponent;
28pub use nightshade::ecs::camera::components::{
29 Camera as CameraComponent, OrthographicCamera, PerspectiveCamera, Projection,
30};
31pub use nightshade::ecs::decal::components::Decal as DecalComponent;
32pub use nightshade::ecs::gizmos::GizmoMode as GizmoModeKind;
33pub use nightshade::ecs::light::components::{AreaLightShape, Light as LightComponent, LightType};
34pub use nightshade::ecs::physics::components::ColliderShape;
35pub use nightshade::ecs::physics::types::RigidBodyType;
36pub use nightshade::ecs::primitives::{CameraCullingMask, CullingMask};
37pub use nightshade::ecs::text::components::TextProperties as TextPropertiesComponent;
38pub use nightshade::ecs::vfx::components::{
39 Beam as BeamComponent, LightningBolt as LightningBoltComponent, Trail as TrailComponent,
40 VfxAnimator as VfxAnimatorComponent,
41};
42pub use nightshade::ecs::water::components::Water as WaterComponent;
43pub use nightshade::prelude::{Atmosphere as AtmosphereKind, Vec2, Vec3, Vec4};
44pub use nightshade::render::material::AlphaMode;
45pub use nightshade::render::material::Material as MaterialComponent;
46pub use nightshade::render::particles::ParticleEmitter as ParticleEmitterComponent;
47pub use nightshade::render::render_layer::RenderLayer;
48
49pub const MESSAGE_KEY: &str = "message";
51pub const CANVAS_KEY: &str = "canvas";
53pub const BYTES_KEY: &str = "bytes";
55pub const GLTF_KEY: &str = "gltf";
57pub const RESOURCES_KEY: &str = "resources";
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
62pub enum TouchPhase {
63 Started,
64 Moved,
65 Ended,
66 Cancelled,
67}
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
71#[cfg_attr(
72 feature = "protocol-agent",
73 derive(enum2schema::Schema),
74 schema(string_enum)
75)]
76pub enum ShapeKind {
77 Cube,
78 Plane,
79 Cylinder,
80 Cone,
81 Sphere,
82 Torus,
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
87#[cfg_attr(
88 feature = "protocol-agent",
89 derive(enum2schema::Schema),
90 schema(string_enum)
91)]
92pub enum LightKind {
93 Point,
94 Spot,
95 Directional,
96}
97
98#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
100#[cfg_attr(
101 feature = "protocol-agent",
102 derive(enum2schema::Schema),
103 schema(string_enum)
104)]
105pub enum GeneratorKind {
106 Building,
107 Room,
108 Tower,
109 Stairs,
110 Columns,
111 Perimeter,
112 CityBlock,
113 Courtyard,
114 Wfc,
115 WfcCity,
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
120#[cfg_attr(
121 feature = "protocol-agent",
122 derive(enum2schema::Schema),
123 schema(string_enum)
124)]
125pub enum PolyhavenCategory {
126 Hdris,
127 Models,
128}
129
130pub use crate::reflect::{ComponentKind, ComponentPatch};
131
132#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
134#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
135pub enum AnimationCommand {
136 Play { index: u32 },
137 PlayAll,
138 Pause,
139 Resume,
140 Stop,
141 Seek { time: f32 },
142 SetSpeed { speed: f32 },
143 SetLooping { looping: bool },
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
149#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
150pub struct GenerationSettings {
151 pub footprint_x: f32,
152 pub footprint_z: f32,
153 pub stories: u32,
154 pub story_height: f32,
155 pub wall_thickness: f32,
156 pub floor_thickness: f32,
157 pub window_spacing: f32,
158 pub seed: u32,
159 pub variation: f32,
160}
161
162#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
165pub struct RenderSettingsState {
166 pub ambient_light: [f32; 4],
167 pub bloom_enabled: bool,
168 pub bloom_intensity: f32,
169 pub bloom_threshold: f32,
170 pub bloom_knee: f32,
171 pub bloom_filter_radius: f32,
172 pub ssao_enabled: bool,
173 pub ssao_radius: f32,
174 pub ssao_intensity: f32,
175 pub ssao_bias: f32,
176 pub ssao_sample_count: u32,
177 pub ssao_visualization: bool,
178 pub ssgi_enabled: bool,
179 pub ssgi_radius: f32,
180 pub ssgi_intensity: f32,
181 pub ssgi_max_steps: u32,
182 pub ssr_enabled: bool,
183 pub ssr_max_steps: u32,
184 pub ssr_thickness: f32,
185 pub ssr_max_distance: f32,
186 pub ssr_stride: f32,
187 pub ssr_fade_start: f32,
188 pub ssr_fade_end: f32,
189 pub ssr_intensity: f32,
190 pub fog_enabled: bool,
191 pub fog_color: [f32; 3],
192 pub fog_start: f32,
193 pub fog_end: f32,
194 pub dof_enabled: bool,
195 pub dof_focus_distance: f32,
196 pub dof_focus_range: f32,
197 pub dof_max_blur_radius: f32,
198 pub dof_bokeh_threshold: f32,
199 pub dof_bokeh_intensity: f32,
200 pub dof_quality: u32,
201 pub taa_enabled: bool,
202 pub render_scale: f32,
203 pub ibl_blend_factor: f32,
204 pub pbr_debug_index: u32,
205 pub unlit_mode: bool,
206 pub selection_outline_enabled: bool,
207 pub selection_outline_color: [f32; 4],
208 pub exposure: f32,
209 pub saturation: f32,
210 pub contrast: f32,
211 pub brightness: f32,
212 pub gamma: f32,
213 pub show_grid: bool,
214 pub show_sky: bool,
215 pub show_normals: bool,
216 pub navmesh_debug: bool,
217 pub atmosphere: Atmosphere,
218}
219
220#[derive(Clone, PartialEq, Serialize, Deserialize)]
222pub struct EditorFlags {
223 pub gizmo_mode: GizmoMode,
224 pub snap_enabled: bool,
225 pub snap_translation_step: f32,
226 pub snap_rotation_step_degrees: f32,
227 pub snap_scale_step: f32,
228 pub greybox_enabled: bool,
229 pub greybox_show_final: bool,
230 pub placement_active: bool,
231 pub placement_shape: ShapeKind,
232 pub placement_size: [f32; 3],
233 pub placement_orient: bool,
234 pub placement_status: String,
235 pub push_pull_active: bool,
236 pub scripting_active: bool,
237 pub playground_active: bool,
238 pub fly_mode: bool,
239 pub viewer_mode: bool,
240 pub frame_on_load: bool,
241 pub day_night_hour: f32,
242 pub day_night_auto: bool,
243 pub skeleton_view: bool,
244 pub rotation_speed: f32,
245 pub active_palette: usize,
246 pub edit_mode_target: Option<u32>,
247 pub generation: GenerationSettings,
248 pub global_scripts: Vec<nightshade::ecs::script::components::GlobalScript>,
249}
250
251#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
253#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
254pub enum GlobalScriptCommand {
255 Add,
256 AddTemplate { name: String, source: String },
257 Remove(u32),
258 MoveUp(u32),
259 MoveDown(u32),
260 SetEnabled { index: u32, enabled: bool },
261 SetSource { index: u32, source: String },
262 Rename { index: u32, name: String },
263}
264
265#[derive(Clone, PartialEq, Serialize, Deserialize)]
267pub struct TreeRow {
268 pub id: u32,
269 pub name: String,
270 pub depth: u32,
271 pub has_children: bool,
272 pub camera: bool,
273 pub light: bool,
274 pub mesh: bool,
275}
276
277#[derive(Clone, PartialEq, Serialize, Deserialize)]
279pub struct PaletteEntry {
280 pub label: String,
281 pub color: [f32; 3],
282}
283
284#[derive(Clone, PartialEq, Serialize, Deserialize)]
286pub struct PrefabLink {
287 pub name: String,
288 pub source_path: Option<String>,
289}
290
291#[derive(Clone, PartialEq, Serialize, Deserialize)]
294pub struct MorphMeshInfo {
295 pub id: u32,
296 pub name: String,
297 pub weights: Vec<f32>,
298}
299
300#[derive(Clone, PartialEq, Serialize, Deserialize)]
302pub struct AnimationInfo {
303 pub clips: Vec<String>,
304 pub current: Option<u32>,
305 pub playing: bool,
306 pub time: f32,
307 pub duration: f32,
308 pub speed: f32,
309 pub looping: bool,
310}
311
312#[derive(Clone, Serialize, Deserialize)]
314pub struct SelectedEntity {
315 pub id: u32,
316 pub revision: u32,
319 pub name: String,
320 pub translation: [f32; 3],
321 pub rotation: [f32; 3],
322 pub scale: [f32; 3],
323 pub mesh: Option<String>,
324 pub material_name: Option<String>,
325 pub tags: Vec<String>,
326 pub layer: Option<u32>,
327 pub chunk: Option<u32>,
328 pub prefab: Option<PrefabLink>,
329 pub animation: Option<AnimationInfo>,
330 pub morph_weights: Option<Vec<f32>>,
331 pub selected_count: u32,
332 pub visibility: Option<bool>,
333 pub casts_shadow: bool,
334 pub light: Option<Light>,
335 pub camera: Option<Camera>,
336 pub is_active_camera: bool,
337 pub rigid_body: Option<RigidBodyComponent>,
338 pub collider: Option<ColliderComponent>,
339 pub character_controller: Option<CharacterControllerComponent>,
340 pub navmesh_agent: Option<NavMeshAgent>,
341 pub particle_emitter: Option<Box<ParticleEmitter>>,
342 pub decal: Option<Decal>,
343 pub water: Option<Water>,
344 pub audio_source: Option<AudioSource>,
345 pub render_layer: Option<RenderLayer>,
346 pub culling_mask: Option<CullingMask>,
347 pub camera_culling_mask: Option<CameraCullingMask>,
348 pub ignore_parent_scale: bool,
349 pub text: Option<(String, TextProperties)>,
350 pub script: Option<String>,
351 pub beam: Option<Box<Beam>>,
352 pub lightning_bolt: Option<Box<LightningBolt>>,
353 pub trail: Option<Box<Trail>>,
354 pub vfx_animator: Option<Box<VfxAnimator>>,
355 pub present: Vec<ComponentKind>,
356 pub addable: Vec<ComponentKind>,
357}
358
359#[derive(Clone, Serialize, Deserialize)]
361pub struct MaterialEntry {
362 pub name: String,
363 pub material: Material,
364}
365
366#[derive(Clone, PartialEq, Serialize, Deserialize)]
368pub struct KhronosEntry {
369 pub label: String,
370 pub thumbnail: Option<String>,
371}
372
373#[derive(Clone, PartialEq, Serialize, Deserialize)]
375pub struct PolyhavenEntry {
376 pub slug: String,
377 pub name: String,
378 pub thumbnail: String,
379}
380
381#[derive(Clone, PartialEq, Serialize, Deserialize)]
383pub struct SceneStructure {
384 pub layers: Vec<SceneLayerConfig>,
385 pub chunks: Vec<SceneChunkConfig>,
386}
387
388#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
391#[cfg_attr(feature = "protocol-agent", derive(enum2schema::Schema))]
392pub enum EditorAction {
393 ToggleGroundGrid,
394 ToggleSky,
395 ToggleLitUnlit,
396 ToggleViewerMode,
397 SetViewerMode(bool),
398 FrameScene,
399 ToggleFrameOnLoad,
400 ClearScene,
401 SnapSelectionToFloor,
402 BakeNavmesh,
403 ToggleNavmeshDebug,
404 SetAtmosphere(Atmosphere),
405 SetGizmoMode(GizmoMode),
406 RandomizeBoth,
407 SelectEntity(u32),
408 ToggleSelectEntity(u32),
409 ClearSelection,
410 AddEntityChild(u32),
411 ViewCamera(u32),
412 ToggleSnap,
413 ToggleSkeletonView,
414 SetMaterialVariant(Option<String>),
415 ToggleCameraGizmos,
416 ToggleShowNormals,
417 ToggleDayNightCycle,
418 SetDayNightHour(f32),
419 LoadKhronosByIndex(usize),
420 LoadPolyhavenByIndex {
421 category: PolyhavenCategory,
422 index: usize,
423 },
424 SetPolyhavenResolution(u32),
425 RefreshBrowsers,
426 DeepLink(String),
427 SpawnLines,
428 SpawnMeshes,
429 Spawn3DText,
430 SpawnTextLattice,
431 ClothSponza,
432 NewProject,
433 LoadExampleProject,
434 SaveProject,
435 ExportGlb,
436 SaveSelectedAsPrefab,
437 RefreshSelectedPrefab,
438 BreakSelectedPrefabLink,
439 SpawnPrefab(String),
440 Undo,
441 Redo,
442 DeleteEntity(u32),
443 DeleteSelectedEntity,
444 DuplicateEntity(u32),
445 DuplicateSelectedEntity,
446 AddShape(ShapeKind),
447 AddInstancedShape(ShapeKind),
448 ConvertSimilarToInstanced,
449 AddEmpty,
450 AddCamera,
451 AddPlayerSpawn,
452 AddSavePoint,
453 AddPatrolPoint,
454 AddTriggerVolume,
455 StampDecalAtCursor,
456 AddLight(LightKind),
457 AddTagToSelected(String),
458 RemoveTagFromSelected(String),
459 ToggleGreyboxMode,
460 ApplyGreyboxToSelection,
461 SnapSelectionToGrid,
462 MarkSelectionAsBrush,
463 ToggleShowFinal,
464 ToggleScripting,
465 SetScripting(bool),
468 SetPlayground(bool),
472 LoadLesson(String),
474 GlobalScript(GlobalScriptCommand),
475 TogglePushPull,
476 SetPlacementActive(bool),
477 SetPlacementShape(ShapeKind),
478 SetPlacementSize([f32; 3]),
479 SetOrientToNormal(bool),
480 RunGenerator(GeneratorKind),
481 SetGenerationSettings(GenerationSettings),
482 SetPaletteIndex(usize),
483 SetSnapEnabled(bool),
484 SetSnapSteps {
485 translation: f32,
486 rotation_degrees: f32,
487 scale: f32,
488 },
489 SetRotationSpeed(f32),
490 SetEntityLayer {
491 id: u32,
492 layer: Option<u32>,
493 },
494 SetEntityChunk {
495 id: u32,
496 chunk: Option<u32>,
497 },
498 AddSceneLayer,
499 RemoveSceneLayer(u32),
500 UpdateSceneLayer(SceneLayerConfig),
501 AddSceneChunk,
502 RemoveSceneChunk(u32),
503 UpdateSceneChunk(SceneChunkConfig),
504}
505
506#[derive(Clone, Serialize, Deserialize)]
508pub struct CameraEntry {
509 pub id: u32,
510 pub name: String,
511}
512
513#[derive(Clone, Copy, Serialize, Deserialize)]
516pub struct ViewportTile {
517 pub camera: u32,
518 pub x: f32,
519 pub y: f32,
520 pub width: f32,
521 pub height: f32,
522}
523
524#[derive(Clone, Serialize, Deserialize)]
527pub enum ClientMessage {
528 Init {
530 width: f32,
531 height: f32,
532 },
533 Resize {
534 width: f32,
535 height: f32,
536 },
537 SetViewportTiles(Vec<ViewportTile>),
540 PointerMove {
541 x: f32,
542 y: f32,
543 },
544 PointerMotion {
547 dx: f32,
548 dy: f32,
549 },
550 PointerButton {
552 button: u8,
553 pressed: bool,
554 },
555 Wheel {
556 delta: f32,
557 },
558 Touch {
559 id: u64,
560 phase: TouchPhase,
561 x: f32,
562 y: f32,
563 },
564 Key {
567 code: String,
568 pressed: bool,
569 text: Option<String>,
570 },
571 Action(Box<EditorAction>),
572 SetTransform {
573 id: u32,
574 translation: [f32; 3],
575 rotation: [f32; 3],
576 scale: [f32; 3],
577 committed: bool,
578 },
579 SetEntityName {
580 id: u32,
581 name: String,
582 },
583 SetComponent {
584 id: u32,
585 payload: Box<ComponentPatch>,
586 committed: bool,
587 },
588 AddComponent {
589 id: u32,
590 kind: ComponentKind,
591 },
592 RemoveComponent {
593 id: u32,
594 kind: ComponentKind,
595 },
596 Animation {
597 id: u32,
598 command: AnimationCommand,
599 },
600 UpdateMaterial {
601 name: String,
602 material: Box<Material>,
603 committed: bool,
604 },
605 SetRenderSettings(Box<RenderSettingsState>),
606 DropAsset {
610 name: String,
611 },
612 LoadGltfBundle {
615 name: String,
616 },
617 UpgradeBrush {
619 name: String,
620 },
621 SubmitCommand {
626 command: String,
627 },
628 LoadScriptAsset {
634 name: String,
635 url: String,
636 },
637 #[cfg(feature = "protocol-agent")]
640 Agent(Box<AgentRequest>),
641}
642
643#[derive(Clone, Serialize, Deserialize)]
645pub enum WorkerMessage {
646 Ready {
647 adapter: String,
648 },
649 Stats {
650 fps: f32,
651 entity_count: u32,
652 },
653 Camera {
655 right: [f32; 3],
656 up: [f32; 3],
657 forward: [f32; 3],
658 },
659 ScriptAssetLoaded {
662 name: String,
663 },
664 ScriptAssetFailed {
666 name: String,
667 error: String,
668 },
669 Scene {
670 rows: Vec<TreeRow>,
671 },
672 Selected {
673 detail: Option<Box<SelectedEntity>>,
674 },
675 Cameras {
678 entries: Vec<CameraEntry>,
679 },
680 Animation {
682 time: f32,
683 duration: f32,
684 playing: bool,
685 clip: Option<u32>,
686 },
687 MorphWeights {
690 id: u32,
691 weights: Vec<f32>,
692 },
693 MorphMeshes {
696 entries: Vec<MorphMeshInfo>,
697 },
698 MaterialVariants {
701 names: Vec<String>,
702 active: Option<String>,
703 },
704 RenderSettings(Box<RenderSettingsState>),
705 Flags(Box<EditorFlags>),
706 Materials {
707 entries: Vec<MaterialEntry>,
708 },
709 Tags {
710 entries: Vec<(String, u32)>,
711 },
712 Structure(SceneStructure),
713 Palette {
714 entries: Vec<PaletteEntry>,
715 },
716 KhronosList {
717 entries: Vec<KhronosEntry>,
718 },
719 PolyhavenList {
720 category: PolyhavenCategory,
721 entries: Vec<PolyhavenEntry>,
722 },
723 Loading {
724 active: bool,
725 label: String,
726 },
727 Project {
729 name: String,
730 modified: bool,
731 model: Option<String>,
732 mode: String,
733 },
734 SaveFile {
736 name: String,
737 },
738 PointerLock {
740 locked: bool,
741 },
742 FocusEntity {
744 id: u32,
745 },
746 UndoState {
747 can_undo: bool,
748 can_redo: bool,
749 },
750 Toast {
751 message: String,
752 error: bool,
753 },
754 CommandLog {
757 entries: Vec<CommandLogEntry>,
758 },
759 #[cfg(feature = "protocol-agent")]
762 Agent(Box<AgentResponse>),
763}
764
765#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
768pub enum CommandLogKind {
769 Command,
770 Event,
771 Error,
772}
773
774#[derive(Clone, Debug, Serialize, Deserialize)]
777pub struct CommandLogEntry {
778 pub cycle: u64,
779 pub kind: CommandLogKind,
780 pub label: String,
781 pub detail: String,
782 pub entities: Vec<u32>,
785}
786
787impl CommandLogEntry {
788 pub fn from_event(cycle: u64, event: &Event) -> Self {
791 Self {
792 cycle,
793 kind: CommandLogKind::Event,
794 label: event_label(event).to_string(),
795 detail: event_detail(event),
796 entities: event_entities(event),
797 }
798 }
799
800 pub fn from_command(cycle: u64, command: &Command) -> Self {
805 use enum2schema::serde_json::Value;
806 let value: Value = enum2schema::serde_json::to_value(command).unwrap_or(Value::Null);
807 let detail = value
808 .as_object()
809 .and_then(|object| object.values().next())
810 .map(|inner| inner.to_string())
811 .unwrap_or_else(|| value.to_string());
812 let mut entities = Vec::new();
813 collect_entity_ids(&value, &mut entities);
814 Self {
815 cycle,
816 kind: CommandLogKind::Command,
817 label: command.name().to_string(),
818 detail,
819 entities,
820 }
821 }
822
823 pub fn from_error(cycle: u64, message: String) -> Self {
827 Self {
828 cycle,
829 kind: CommandLogKind::Error,
830 label: "error".to_string(),
831 detail: message,
832 entities: Vec::new(),
833 }
834 }
835}
836
837fn event_label(event: &Event) -> &'static str {
838 match event {
839 Event::Collision { .. } => "Collision",
840 Event::Despawned { .. } => "Despawned",
841 Event::AnimationFinished { .. } => "AnimationFinished",
842 Event::AnimationEvent { .. } => "AnimationEvent",
843 Event::NavigationArrived { .. } => "NavigationArrived",
844 }
845}
846
847fn event_detail(event: &Event) -> String {
848 match event {
849 Event::Collision {
850 a,
851 b,
852 sensor,
853 started,
854 } => format!(
855 "#{} and #{}, {}{}",
856 a.id,
857 b.id,
858 if *started { "started" } else { "ended" },
859 if *sensor { ", sensor" } else { "" }
860 ),
861 Event::AnimationEvent { entity, name } => format!("#{} {}", entity.id, name),
862 Event::Despawned { entity }
863 | Event::AnimationFinished { entity }
864 | Event::NavigationArrived { entity } => format!("#{}", entity.id),
865 }
866}
867
868fn event_entities(event: &Event) -> Vec<u32> {
869 match event {
870 Event::Collision { a, b, .. } => vec![a.id, b.id],
871 Event::AnimationEvent { entity, .. } => vec![entity.id],
872 Event::Despawned { entity }
873 | Event::AnimationFinished { entity }
874 | Event::NavigationArrived { entity } => vec![entity.id],
875 }
876}
877
878fn collect_entity_ids(value: &enum2schema::serde_json::Value, ids: &mut Vec<u32>) {
881 use enum2schema::serde_json::Value;
882 match value {
883 Value::Object(map) => {
884 if let Some(id) = map.get("Existing").and_then(Value::as_u64) {
885 ids.push(id as u32);
886 }
887 if let Some(id) = map
888 .get("Entity")
889 .and_then(Value::as_object)
890 .and_then(|entity| entity.get("id"))
891 .and_then(Value::as_u64)
892 {
893 ids.push(id as u32);
894 }
895 for nested in map.values() {
896 collect_entity_ids(nested, ids);
897 }
898 }
899 Value::Array(array) => {
900 for nested in array {
901 collect_entity_ids(nested, ids);
902 }
903 }
904 _ => {}
905 }
906}
907
908#[cfg(all(test, feature = "protocol-agent"))]
909mod schema_tests {
910 use super::EditorAction;
911 use enum2schema::Schema;
912 use enum2schema::serde_json::Value;
913
914 fn assert_no_array_items(value: &Value) {
919 match value {
920 Value::Object(object) => {
921 if let Some(items) = object.get("items") {
922 assert!(
923 !items.is_array(),
924 "array-valued `items` is invalid in draft 2020-12, use `prefixItems`: {object:?}"
925 );
926 }
927 for nested in object.values() {
928 assert_no_array_items(nested);
929 }
930 }
931 Value::Array(array) => {
932 for nested in array {
933 assert_no_array_items(nested);
934 }
935 }
936 _ => {}
937 }
938 }
939
940 #[test]
941 fn editor_action_schema_is_draft_2020_12() {
942 assert_no_array_items(&EditorAction::schema());
943 }
944}