diff --git a/Cargo.lock b/Cargo.lock index 2419f4b..70a0f01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -479,6 +479,19 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "muninn-contracts" +version = "0.1.0" +dependencies = [ + "anyhow", + "cultcache-rs", + "cultmesh-rs", + "cultnet-rs", + "rmp-serde", + "serde", + "serde_json", +] + [[package]] name = "muninn-daemon" version = "0.1.0" @@ -488,6 +501,7 @@ dependencies = [ "cultmesh-rs", "cultnet-rs", "libc", + "muninn-contracts", "muninn-move-tracker", "muninn-psmoveapi-tracker", "odin-core", @@ -799,6 +813,7 @@ dependencies = [ "cultcache-rs", "cultmesh-rs", "cultnet-rs", + "muninn-contracts", "odin-core", "rmp-serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 12add68..b9d770a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "crates/muninn-contracts", "crates/muninn-daemon", "crates/muninn-move-tracker", "crates/muninn-psmoveapi-tracker", diff --git a/crates/muninn-contracts/Cargo.toml b/crates/muninn-contracts/Cargo.toml new file mode 100644 index 0000000..8b674b5 --- /dev/null +++ b/crates/muninn-contracts/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "muninn-contracts" +version = "0.1.0" +edition.workspace = true +license.workspace = true +publish.workspace = true + +[lib] +name = "muninn_contracts" +path = "src/lib.rs" + +[dependencies] +anyhow = "1" +cultcache-rs = { workspace = true } +cultmesh-rs = { workspace = true } +cultnet-rs = { workspace = true } +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[dev-dependencies] +rmp-serde = "1.3" diff --git a/crates/muninn-contracts/src/lib.rs b/crates/muninn-contracts/src/lib.rs new file mode 100644 index 0000000..0ec0605 --- /dev/null +++ b/crates/muninn-contracts/src/lib.rs @@ -0,0 +1,176 @@ +//! Muninn's typed state records: the wire and store contract that `muninn` and +//! `sleipnir` share. Library only: no sockets, threads, devices or Odin. + +mod records; + +pub use records::*; + +cultmesh_rs::cultmesh_documents!(MuninnRecords { + MuninnTelemetrySurfaceRecord => MUNINN_TELEMETRY_SURFACE_SCHEMA, + MuninnCaptureStreamRecord => MUNINN_CAPTURE_STREAM_SCHEMA, + MuninnCaptureStreamCommandRecord => MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA, + MuninnObsStreamCatalogRecord => MUNINN_OBS_STREAM_CATALOG_SCHEMA, + MuninnMoveMarkerCandidateRecord => MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA, + MuninnMoveControllerStateRecord => MUNINN_MOVE_CONTROLLER_STATE_SCHEMA, + MuninnHidControllerStateRecord => MUNINN_HID_CONTROLLER_STATE_SCHEMA, + MuninnMoveIdentityRecord => MUNINN_MOVE_IDENTITY_SCHEMA, + MuninnMoveLightCommandRecord => MUNINN_MOVE_LIGHT_COMMAND_SCHEMA, + MuninnMoveHueProgramRecord => MUNINN_MOVE_HUE_PROGRAM_SCHEMA, + MuninnMoveTrackerHealthRecord => MUNINN_MOVE_TRACKER_HEALTH_SCHEMA, + MuninnMoveEvidenceTransportHealthRecord => MUNINN_MOVE_EVIDENCE_TRANSPORT_HEALTH_SCHEMA, + MuninnQuestAccessRecord => MUNINN_QUEST_ACCESS_SCHEMA, + MuninnCommandBoundaryCompatRecord => MUNINN_COMMAND_BOUNDARY_SCHEMA, + MuninnTransportProfileCompatRecord => MUNINN_TRANSPORT_PROFILE_SCHEMA, +}); + +#[cfg(test)] +mod tests { + use super::*; + use cultcache_rs::{CultCache, DatabaseEntry}; + use cultmesh_rs::CultMeshDocumentSet; + use cultnet_rs::CultNetDocumentRegistry; + + /// Every Muninn record: its type name, its schema id, the schema constant + /// the document set binds it under, and its Rust name. + fn contract() -> Vec<(&'static str, &'static str, &'static str, &'static str)> { + macro_rules! row { + ($record:ty, $constant:expr) => { + ( + <$record as DatabaseEntry>::TYPE, + <$record as DatabaseEntry>::SCHEMA_NAME, + $constant, + stringify!($record), + ) + }; + } + vec![ + row!(MuninnTelemetrySurfaceRecord, MUNINN_TELEMETRY_SURFACE_SCHEMA), + row!(MuninnCaptureStreamRecord, MUNINN_CAPTURE_STREAM_SCHEMA), + row!(MuninnCaptureStreamCommandRecord, MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA), + row!(MuninnObsStreamCatalogRecord, MUNINN_OBS_STREAM_CATALOG_SCHEMA), + row!(MuninnMoveMarkerCandidateRecord, MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA), + row!(MuninnMoveControllerStateRecord, MUNINN_MOVE_CONTROLLER_STATE_SCHEMA), + row!(MuninnHidControllerStateRecord, MUNINN_HID_CONTROLLER_STATE_SCHEMA), + row!(MuninnMoveIdentityRecord, MUNINN_MOVE_IDENTITY_SCHEMA), + row!(MuninnMoveLightCommandRecord, MUNINN_MOVE_LIGHT_COMMAND_SCHEMA), + row!(MuninnMoveHueProgramRecord, MUNINN_MOVE_HUE_PROGRAM_SCHEMA), + row!(MuninnMoveTrackerHealthRecord, MUNINN_MOVE_TRACKER_HEALTH_SCHEMA), + row!( + MuninnMoveEvidenceTransportHealthRecord, + MUNINN_MOVE_EVIDENCE_TRANSPORT_HEALTH_SCHEMA + ), + row!(MuninnQuestAccessRecord, MUNINN_QUEST_ACCESS_SCHEMA), + row!(MuninnCommandBoundaryCompatRecord, MUNINN_COMMAND_BOUNDARY_SCHEMA), + row!(MuninnTransportProfileCompatRecord, MUNINN_TRANSPORT_PROFILE_SCHEMA), + ] + } + + #[test] + fn record_names_are_the_published_schema_ids() { + let expected = [ + ("muninn.telemetry_surface", "muninn.telemetry_surface.v1"), + ("muninn.capture_stream", "muninn.capture_stream.v1"), + ("muninn.capture_stream_command", "muninn.capture_stream_command.v1"), + ("muninn.obs_stream_catalog", "muninn.obs_stream_catalog.v1"), + ("muninn.move_marker_candidate", "muninn.move_marker_candidate.v1"), + ("muninn.move_controller_state", "muninn.move_controller_state.v1"), + ("muninn.hid_controller_state", "muninn.hid_controller_state.v1"), + ("muninn.move_identity", "muninn.move_identity.v1"), + ("muninn.move_light_command", "muninn.move_light_command.v1"), + ("muninn.move_hue_program", "muninn.move_hue_program.v1"), + ("muninn.move_tracker_health", "muninn.move_tracker_health.v1"), + ( + "muninn.move_evidence_transport_health", + "muninn.move_evidence_transport_health.v1", + ), + ("muninn.quest_access", "muninn.quest_access.v1"), + ("muninn.command_boundary", "muninn.command_boundary.v1"), + ("muninn.transport_profile", "muninn.transport_profile.v1"), + ]; + let actual: Vec<_> = contract() + .into_iter() + .map(|(record_type, schema, constant, _)| { + assert_eq!(schema, constant, "schema constant and derive disagree"); + (record_type, schema) + }) + .collect(); + assert_eq!(actual, expected); + } + + #[test] + fn document_set_binds_every_record_under_its_schema() { + let mut cache = CultCache::new(); + MuninnRecords.register_cache(&mut cache).unwrap(); + let mut registry = CultNetDocumentRegistry::new(); + MuninnRecords.register_documents(&mut registry).unwrap(); + + let rows = contract(); + let mut registered = cache.registered_entry_types(); + registered.sort(); + let mut expected: Vec = rows.iter().map(|row| row.0.to_string()).collect(); + expected.sort(); + assert_eq!(registered, expected); + for (record_type, schema, _, record) in rows { + let binding = registry + .binding_by_schema_id(schema) + .unwrap_or_else(|| panic!("{record} is not bound under {schema}")); + assert_eq!(binding.document_type, record_type, "{record}"); + } + } + + #[test] + fn hid_controller_state_wire_layout_is_positional_by_key() { + let record = MuninnHidControllerStateRecord { + stream_id: "muninn.raven.hid".into(), + host_id: "raven".into(), + device_id: "xbox-0".into(), + device_kind: "xinput".into(), + sequence: 7, + source_timestamp_ns: -3, + axes: vec![0.5, -1.0], + buttons: vec!["a".into(), "b".into()], + battery01: 1.0, + observed_at: "unix:100".into(), + source_path: "xinput://0".into(), + }; + let bytes = rmp_serde::to_vec(&record).unwrap(); + assert_eq!( + hex(&bytes), + "9bb06d756e696e6e2e726176656e2e686964a5726176656ea678626f782d30a678696e70757407fd92ca3f000000cabf80000092a161a162ca3f800000a8756e69783a313030aa78696e7075743a2f2f30" + ); + assert_eq!( + rmp_serde::from_slice::(&bytes).unwrap(), + record + ); + } + + #[test] + fn hue_program_wire_layout_carries_its_defaulted_tail() { + let record = MuninnMoveHueProgramRecord { + program_id: "prog".into(), + host_id: "raven".into(), + mode: "cycle".into(), + cycle_ms: 20, + epoch_ns: -1, + hold_at_ns: 0, + requested_by: "mimir".into(), + updated_at: "unix:1".into(), + order_mode: "ring".into(), + transition_percent: 50, + transition_percent_explicit: true, + }; + let bytes = rmp_serde::to_vec(&record).unwrap(); + assert_eq!( + hex(&bytes), + "9ba470726f67a5726176656ea56379636c6514ff00a56d696d6972a6756e69783a31a472696e6732c3" + ); + assert_eq!( + rmp_serde::from_slice::(&bytes).unwrap(), + record + ); + } + + fn hex(bytes: &[u8]) -> String { + bytes.iter().map(|byte| format!("{byte:02x}")).collect() + } +} diff --git a/crates/muninn-contracts/src/records.rs b/crates/muninn-contracts/src/records.rs new file mode 100644 index 0000000..7742843 --- /dev/null +++ b/crates/muninn-contracts/src/records.rs @@ -0,0 +1,508 @@ +use cultcache_rs::DatabaseEntry; +use serde_json::Value; + +pub const MUNINN_TELEMETRY_SURFACE_SCHEMA: &str = "muninn.telemetry_surface.v1"; +pub const MUNINN_CAPTURE_STREAM_SCHEMA: &str = "muninn.capture_stream.v1"; +pub const MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA: &str = "muninn.capture_stream_command.v1"; +pub const MUNINN_OBS_STREAM_CATALOG_SCHEMA: &str = "muninn.obs_stream_catalog.v1"; +pub const MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA: &str = "muninn.move_marker_candidate.v1"; +pub const MUNINN_MOVE_CONTROLLER_STATE_SCHEMA: &str = "muninn.move_controller_state.v1"; +pub const MUNINN_HID_CONTROLLER_STATE_SCHEMA: &str = "muninn.hid_controller_state.v1"; +pub const MUNINN_MOVE_IDENTITY_SCHEMA: &str = "muninn.move_identity.v1"; +pub const MUNINN_MOVE_LIGHT_COMMAND_SCHEMA: &str = "muninn.move_light_command.v1"; +pub const MUNINN_MOVE_HUE_PROGRAM_SCHEMA: &str = "muninn.move_hue_program.v1"; +pub const MUNINN_MOVE_TRACKER_HEALTH_SCHEMA: &str = "muninn.move_tracker_health.v1"; +pub const MUNINN_MOVE_EVIDENCE_TRANSPORT_HEALTH_SCHEMA: &str = + "muninn.move_evidence_transport_health.v1"; +pub const MUNINN_QUEST_ACCESS_SCHEMA: &str = "muninn.quest_access.v1"; +pub const MUNINN_COMMAND_BOUNDARY_SCHEMA: &str = "muninn.command_boundary.v1"; +pub const MUNINN_TRANSPORT_PROFILE_SCHEMA: &str = "muninn.transport_profile.v1"; + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "muninn.capture_stream", schema = "muninn.capture_stream.v1")] +pub struct MuninnCaptureStreamRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub state: String, + #[cultcache(key = 3)] + pub video_source: String, + #[cultcache(key = 4)] + pub audio_source: String, + #[cultcache(key = 5)] + pub transport: String, + #[cultcache(key = 6)] + pub targets: Vec, + #[cultcache(key = 7)] + pub command_witness: String, + #[cultcache(key = 8)] + pub supervisor_pid: Option, + #[cultcache(key = 9)] + pub mux_pid: Option, + #[cultcache(key = 10)] + pub restart_count: u32, + #[cultcache(key = 11)] + pub detail: String, + #[cultcache(key = 12)] + pub updated_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "muninn.capture_stream_command", + schema = "muninn.capture_stream_command.v1" +)] +pub struct MuninnCaptureStreamCommandRecord { + #[cultcache(key = 0)] + pub command_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub stream_id: String, + #[cultcache(key = 3)] + pub state: String, + #[cultcache(key = 4)] + pub action: String, + #[cultcache(key = 5)] + pub target_host: String, + #[cultcache(key = 6)] + pub port: u16, + #[cultcache(key = 7)] + pub obs_target_host: Option, + #[cultcache(key = 8)] + pub obs_port: u16, + #[cultcache(key = 9)] + pub media_transport: String, + #[cultcache(key = 10)] + pub media_packet_bytes: u32, + #[cultcache(key = 11)] + pub requested_by: String, + #[cultcache(key = 12)] + pub detail: String, + #[cultcache(key = 13)] + pub updated_at: String, + #[cultcache(key = 14, default)] + pub rudp_video_bitrate_kbps: u32, + #[cultcache(key = 15, default)] + pub rudp_latency_budget_ms: u32, + #[cultcache(key = 16, default)] + pub video_source_id: String, + #[cultcache(key = 17, default)] + pub audio_source_id: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "muninn.telemetry_surface", + schema = "muninn.telemetry_surface.v1" +)] +pub struct MuninnTelemetrySurfaceRecord { + #[cultcache(key = 0)] + pub surface_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub state: String, + #[cultcache(key = 3)] + pub available_sources: Vec, + #[cultcache(key = 4)] + pub stream_affordances: Vec, + #[cultcache(key = 5)] + pub active_streams: Vec, + #[cultcache(key = 6)] + pub activation_authority: String, + #[cultcache(key = 7)] + pub detail: String, + #[cultcache(key = 8)] + pub updated_at: String, + #[cultcache(key = 9, default)] + pub primary_stream_id: String, + #[cultcache(key = 10, default)] + pub primary_stream_label: String, + #[cultcache(key = 11, default)] + pub command_rudp_target: String, + #[cultcache(key = 12, default)] + pub media_target_host: String, + #[cultcache(key = 13, default)] + pub media_port: u16, + #[cultcache(key = 14, default)] + pub media_packet_bytes: u32, + #[cultcache(key = 15, default)] + pub rudp_video_bitrate_kbps: u32, + #[cultcache(key = 16, default)] + pub rudp_latency_budget_ms: u32, + #[cultcache(key = 17, default)] + pub video_source_ids: Vec, + #[cultcache(key = 18, default)] + pub video_source_labels: Vec, + #[cultcache(key = 19, default)] + pub audio_source_ids: Vec, + #[cultcache(key = 20, default)] + pub audio_source_labels: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "muninn.obs_stream_catalog", + schema = "muninn.obs_stream_catalog.v1" +)] +pub struct MuninnObsStreamCatalogRecord { + #[cultcache(key = 0)] + pub catalog_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub stream_ids: Vec, + #[cultcache(key = 3)] + pub labels: Vec, + #[cultcache(key = 4)] + pub urls: Vec, + #[cultcache(key = 5)] + pub states: Vec, + #[cultcache(key = 6)] + pub updated_at: String, + #[cultcache(key = 7, default)] + pub command_rudp_target: String, + #[cultcache(key = 8, default)] + pub media_target_host: String, + #[cultcache(key = 9, default)] + pub media_port: u16, + #[cultcache(key = 10, default)] + pub media_packet_bytes: u32, + #[cultcache(key = 11, default)] + pub rudp_video_bitrate_kbps: u32, + #[cultcache(key = 12, default)] + pub rudp_latency_budget_ms: u32, + #[cultcache(key = 13, default)] + pub video_source_ids: Vec, + #[cultcache(key = 14, default)] + pub video_source_labels: Vec, + #[cultcache(key = 15, default)] + pub audio_source_ids: Vec, + #[cultcache(key = 16, default)] + pub audio_source_labels: Vec, +} + +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache( + type = "muninn.move_marker_candidate", + schema = "muninn.move_marker_candidate.v1" +)] +pub struct MuninnMoveMarkerCandidateRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub camera_id: String, + #[cultcache(key = 3)] + pub frame_sequence: u64, + #[cultcache(key = 4)] + pub source_id_hash: u64, + #[cultcache(key = 5)] + pub tile_x: u32, + #[cultcache(key = 6)] + pub tile_y: u32, + #[cultcache(key = 7)] + pub center_x_px: f32, + #[cultcache(key = 8)] + pub center_y_px: f32, + #[cultcache(key = 9)] + pub radius_px: f32, + #[cultcache(key = 10)] + pub area_px: u32, + #[cultcache(key = 11)] + pub mean_luma: f32, + #[cultcache(key = 12)] + pub peak_luma: u32, + #[cultcache(key = 13)] + pub score: f32, + #[cultcache(key = 14)] + pub observed_at: String, + #[cultcache(key = 15, default)] + pub move_id: String, +} + +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache( + type = "muninn.move_controller_state", + schema = "muninn.move_controller_state.v1" +)] +pub struct MuninnMoveControllerStateRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub move_id: String, + #[cultcache(key = 3)] + pub sequence: u64, + #[cultcache(key = 4)] + pub source_timestamp_ns: i64, + #[cultcache(key = 5)] + pub accelerometer_xyz: Vec, + #[cultcache(key = 6)] + pub gyroscope_xyz: Vec, + #[cultcache(key = 7)] + pub magnetometer_xyz: Vec, + #[cultcache(key = 8)] + pub trigger_value: f32, + #[cultcache(key = 9)] + pub buttons: Vec, + #[cultcache(key = 10)] + pub battery01: f32, + #[cultcache(key = 11)] + pub observed_at: String, + #[cultcache(key = 12, default)] + pub source_path: String, +} + +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache( + type = "muninn.hid_controller_state", + schema = "muninn.hid_controller_state.v1" +)] +pub struct MuninnHidControllerStateRecord { + #[cultcache(key = 0)] + pub stream_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub device_id: String, + #[cultcache(key = 3)] + pub device_kind: String, + #[cultcache(key = 4)] + pub sequence: u64, + #[cultcache(key = 5)] + pub source_timestamp_ns: i64, + #[cultcache(key = 6)] + pub axes: Vec, + #[cultcache(key = 7)] + pub buttons: Vec, + #[cultcache(key = 8)] + pub battery01: f32, + #[cultcache(key = 9)] + pub observed_at: String, + #[cultcache(key = 10)] + pub source_path: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "muninn.move_identity", schema = "muninn.move_identity.v1")] +pub struct MuninnMoveIdentityRecord { + #[cultcache(key = 0)] + pub identity_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub move_id: String, + #[cultcache(key = 3)] + pub source_path: String, + #[cultcache(key = 4)] + pub bluetooth_host_address: String, + #[cultcache(key = 5)] + pub state: String, + #[cultcache(key = 6)] + pub detail: String, + #[cultcache(key = 7)] + pub observed_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "muninn.move_light_command", + schema = "muninn.move_light_command.v1" +)] +pub struct MuninnMoveLightCommandRecord { + #[cultcache(key = 0)] + pub command_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub move_id: String, + #[cultcache(key = 3)] + pub hidraw_path: String, + #[cultcache(key = 4)] + pub colors: Vec, + #[cultcache(key = 5)] + pub durations_ms: Vec, + #[cultcache(key = 6)] + pub repeat_count: u32, + #[cultcache(key = 7)] + pub authority: String, + #[cultcache(key = 8)] + pub state: String, + #[cultcache(key = 9)] + pub detail: String, + #[cultcache(key = 10)] + pub updated_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "muninn.move_hue_program", + schema = "muninn.move_hue_program.v1" +)] +pub struct MuninnMoveHueProgramRecord { + #[cultcache(key = 0)] + pub program_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub mode: String, + #[cultcache(key = 3)] + pub cycle_ms: u64, + #[cultcache(key = 4)] + pub epoch_ns: i64, + #[cultcache(key = 5)] + pub hold_at_ns: i64, + #[cultcache(key = 6)] + pub requested_by: String, + #[cultcache(key = 7)] + pub updated_at: String, + #[cultcache(key = 8, default)] + pub order_mode: String, + #[cultcache(key = 9, default)] + pub transition_percent: u8, + #[cultcache(key = 10, default)] + pub transition_percent_explicit: bool, +} + +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache( + type = "muninn.move_tracker_health", + schema = "muninn.move_tracker_health.v1" +)] +pub struct MuninnMoveTrackerHealthRecord { + #[cultcache(key = 0)] + pub health_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub camera_id: String, + #[cultcache(key = 3)] + pub camera_index: i32, + #[cultcache(key = 4)] + pub state: String, + #[cultcache(key = 5)] + pub camera_name: String, + #[cultcache(key = 6)] + pub camera_api: String, + #[cultcache(key = 7)] + pub width: u32, + #[cultcache(key = 8)] + pub height: u32, + #[cultcache(key = 9)] + pub exposure: f32, + #[cultcache(key = 10)] + pub calibrated_controller_count: u32, + #[cultcache(key = 11)] + pub update_count: u64, + #[cultcache(key = 12)] + pub observation_count: u64, + #[cultcache(key = 13)] + pub latest_observation_count: u32, + #[cultcache(key = 14)] + pub last_observation_at: String, + #[cultcache(key = 15)] + pub detail: String, + #[cultcache(key = 16)] + pub updated_at: String, + #[cultcache(key = 17, default)] + pub image_mean_rgb: Vec, + #[cultcache(key = 18, default)] + pub image_peak_rgb: Vec, + #[cultcache(key = 19, default)] + pub color_evidence_move_ids: Vec, + #[cultcache(key = 20, default)] + pub color_evidence_pixel_counts: Vec, + #[cultcache(key = 21, default)] + pub rejected_stale_count: u64, + #[cultcache(key = 22, default)] + pub rejected_radius_count: u64, + #[cultcache(key = 23, default)] + pub rejected_bounds_count: u64, + #[cultcache(key = 24, default)] + pub rejected_continuity_count: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache( + type = "muninn.move_evidence_transport_health", + schema = "muninn.move_evidence_transport_health.v1" +)] +pub struct MuninnMoveEvidenceTransportHealthRecord { + #[cultcache(key = 0)] + pub health_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub stream_id: String, + #[cultcache(key = 3)] + pub produced_frames: u64, + #[cultcache(key = 4)] + pub local_ring_admissions: u64, + #[cultcache(key = 5)] + pub remote_handoffs: u64, + #[cultcache(key = 6)] + pub remote_sends: u64, + #[cultcache(key = 7)] + pub updated_at: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] +#[cultcache(type = "muninn.quest_access", schema = "muninn.quest_access.v1")] +pub struct MuninnQuestAccessRecord { + #[cultcache(key = 0)] + pub access_id: String, + #[cultcache(key = 1)] + pub host_id: String, + #[cultcache(key = 2)] + pub serial: String, + #[cultcache(key = 3)] + pub connection_state: String, + #[cultcache(key = 4)] + pub product: String, + #[cultcache(key = 5)] + pub model: String, + #[cultcache(key = 6)] + pub device: String, + #[cultcache(key = 7)] + pub transport_id: String, + #[cultcache(key = 8)] + pub input_stream_id: String, + #[cultcache(key = 9)] + pub pose_stream_id: String, + #[cultcache(key = 10)] + pub video_input_stream_id: String, + #[cultcache(key = 11)] + pub video_input_transport: String, + #[cultcache(key = 12)] + pub state: String, + #[cultcache(key = 13)] + pub detail: String, + #[cultcache(key = 14)] + pub observed_at: String, +} + +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache( + type = "muninn.command_boundary", + schema = "muninn.command_boundary.v1" +)] +pub struct MuninnCommandBoundaryCompatRecord { + #[cultcache(key = 0)] + pub value: Value, +} + +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache( + type = "muninn.transport_profile", + schema = "muninn.transport_profile.v1" +)] +pub struct MuninnTransportProfileCompatRecord { + #[cultcache(key = 0)] + pub value: Value, +} diff --git a/crates/muninn-daemon/Cargo.toml b/crates/muninn-daemon/Cargo.toml index 5b495ee..c77d6c5 100644 --- a/crates/muninn-daemon/Cargo.toml +++ b/crates/muninn-daemon/Cargo.toml @@ -19,6 +19,7 @@ cultcache-rs = { workspace = true } cultmesh-rs = { workspace = true } cultnet-rs = { workspace = true } libc = "0.2" +muninn-contracts = { path = "../muninn-contracts" } muninn-move-tracker = { path = "../muninn-move-tracker" } muninn-psmoveapi-tracker = { path = "../muninn-psmoveapi-tracker", optional = true } odin-core = { workspace = true } diff --git a/crates/muninn-daemon/src/main.rs b/crates/muninn-daemon/src/main.rs index c3b310c..cf8e119 100644 --- a/crates/muninn-daemon/src/main.rs +++ b/crates/muninn-daemon/src/main.rs @@ -23,18 +23,18 @@ use cultnet_rs::{ GameCultRuntimePresenceHealthRecord, decode_cultnet_message_from_slice, media_stream_request_key, validate_media_stream_request, }; -#[cfg(feature = "psmoveapi-tracker")] -use odin_core::MuninnMoveTrackerHealthRecord; -use odin_core::{ - EveProviderAdvertisementRecord, EveSurfaceStateRecord, +use muninn_contracts::{ MUNINN_MOVE_HUE_PROGRAM_SCHEMA, MUNINN_OBS_STREAM_CATALOG_SCHEMA, MuninnCaptureStreamCommandRecord, MuninnCaptureStreamRecord, MuninnCommandBoundaryCompatRecord, MuninnHidControllerStateRecord, MuninnMoveControllerStateRecord, MuninnMoveEvidenceTransportHealthRecord, MuninnMoveHueProgramRecord, MuninnMoveIdentityRecord, MuninnMoveLightCommandRecord, MuninnMoveMarkerCandidateRecord, MuninnObsStreamCatalogRecord, - MuninnQuestAccessRecord, MuninnTelemetrySurfaceRecord, MuninnTransportProfileCompatRecord, - OdinDocuments, + MuninnQuestAccessRecord, MuninnRecords, MuninnTelemetrySurfaceRecord, + MuninnTransportProfileCompatRecord, }; +#[cfg(feature = "psmoveapi-tracker")] +use muninn_contracts::MuninnMoveTrackerHealthRecord; +use odin_core::{EveProviderAdvertisementRecord, EveSurfaceStateRecord, OdinDocuments}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::collections::{HashMap, HashSet}; @@ -9526,15 +9526,18 @@ fn ensure_state_dirs(options: &Options) -> Result<()> { Ok(()) } -/// Odin's document set plus the producer-agnostic media stream contract. -/// Muninn advertises through the latter and takes requests through it; the -/// former is what the rest of its state still speaks. +/// Odin's document set, Muninn's own records, and the producer-agnostic media +/// stream contract. Muninn's records are registered after Odin's, so +/// `muninn-contracts` decides every `muninn.*` binding; Odin's document set +/// still carries stale copies of the same schema ids (and the store may hold +/// legacy Odin records), which is why it stays registered. #[derive(Clone, Copy, Debug, Default)] struct MuninnDocuments; impl cultmesh_rs::CultMeshDocumentSet for MuninnDocuments { fn register_cache(&self, cache: &mut cultcache_rs::CultCache) -> Result<()> { OdinDocuments.register_cache(cache)?; + MuninnRecords.register_cache(cache)?; cache.register_entry_type::()?; cache.register_entry_type::()?; cache.register_entry_type::()?; @@ -9543,6 +9546,7 @@ impl cultmesh_rs::CultMeshDocumentSet for MuninnDocuments { fn register_documents(&self, registry: &mut cultnet_rs::CultNetDocumentRegistry) -> Result<()> { OdinDocuments.register_documents(registry)?; + MuninnRecords.register_documents(registry)?; registry.register( cultnet_rs::CultNetDocumentBinding::for_entry_with_schema_id::< GameCultMediaStreamAdvertisementRecord, diff --git a/crates/sleipnir-daemon/Cargo.toml b/crates/sleipnir-daemon/Cargo.toml index b10f048..09e4533 100644 --- a/crates/sleipnir-daemon/Cargo.toml +++ b/crates/sleipnir-daemon/Cargo.toml @@ -14,6 +14,7 @@ anyhow = "1" cultmesh-rs = { workspace = true } cultnet-rs = { workspace = true } cultcache-rs = { workspace = true } +muninn-contracts = { path = "../muninn-contracts" } odin-core = { workspace = true } rmp-serde = "1.3" serde_json = "1" diff --git a/crates/sleipnir-daemon/src/main.rs b/crates/sleipnir-daemon/src/main.rs index 1be0193..cccedfd 100644 --- a/crates/sleipnir-daemon/src/main.rs +++ b/crates/sleipnir-daemon/src/main.rs @@ -7,10 +7,11 @@ use cultnet_rs::{ CultNetRudpSocketTransportConnection, CultNetRudpSocketTransportOptions, CultNetWireContract, encode_cultnet_message_to_vec, }; +use muninn_contracts::{MUNINN_HID_CONTROLLER_STATE_SCHEMA, MuninnHidControllerStateRecord}; use odin_core::{ EVE_PROVIDER_ADVERTISEMENT_SCHEMA, EveProviderAdvertisementRecord, EveSurfaceStateRecord, - IdunnDaemonHealthRecord, MUNINN_HID_CONTROLLER_STATE_SCHEMA, MuninnHidControllerStateRecord, - OdinDocuments, OdinEndpointQuery, SleipnirInputMappingRecord, discover_provider_endpoints, + IdunnDaemonHealthRecord, OdinDocuments, OdinEndpointQuery, SleipnirInputMappingRecord, + discover_provider_endpoints, }; use std::collections::HashMap; use std::env;