use bevy::ecs::reflect::ReflectComponent;
use bevy::prelude::Component;
use bevy::prelude::Reflect;
use bevy::reflect::ReflectDeserialize;
use bevy::reflect::ReflectSerialize;
use serde::Deserialize;
use serde::Serialize;
use super::scheme::AuthoredId;
use super::scheme::Digest;
use super::scheme::ReportedId;
use super::scheme::SchemeName;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Component, Reflect)]
#[reflect(opaque)]
#[reflect(Component, PartialEq)]
pub struct DeviceId(u64);
impl DeviceId {
pub(crate) const fn new(value: u64) -> Self { Self(value) }
#[must_use]
pub const fn get(self) -> u64 { self.0 }
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Component, Serialize, Deserialize, Reflect)]
#[reflect(Component, PartialEq, Serialize, Deserialize)]
pub struct DeviceKey {
pub kind: DeviceKind,
pub id: DeviceIdSource,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Reflect)]
#[reflect(Serialize, Deserialize)]
pub enum DeviceIdSource {
Reported {
scheme: SchemeName,
value: ReportedId,
},
Synthesized {
digest: Digest,
},
Authored {
value: AuthoredId,
},
}
#[non_exhaustive]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, Reflect)]
#[reflect(Serialize, Deserialize)]
pub enum DeviceKind {
Display,
Camera,
AudioInterface,
DmxUniverse,
HidPanel,
}
#[cfg(test)]
mod tests {
use std::any::TypeId;
use std::error::Error;
use bevy::app::App;
use bevy::ecs::reflect::AppTypeRegistry;
use bevy::ecs::reflect::ReflectComponent;
use bevy::prelude::Component;
use bevy::prelude::Reflect;
use bevy::reflect::FromReflect;
use bevy::reflect::ReflectSerialize;
use bevy::reflect::tuple_struct::DynamicTupleStruct;
use bevy::world_serialization::DynamicWorldBuilder;
use ron::Options;
use ron::extensions::Extensions;
use ron::ser::PrettyConfig;
use serde::Serialize;
use super::DeviceId;
use super::DeviceIdSource;
use super::DeviceKey;
use super::DeviceKind;
use crate::AuthoredId;
use crate::Digest;
use crate::ReportedId;
use crate::SchemeName;
#[derive(Component, Reflect, Serialize)]
#[reflect(Component, Serialize)]
struct PersistedDevice(DeviceKey);
#[test]
fn device_key_round_trips_from_each_ron_form() -> Result<(), Box<dyn Error>> {
let options = Options::default().with_default_extension(Extensions::UNWRAP_NEWTYPES);
let pretty = PrettyConfig::new().struct_names(true).compact_structs(true);
let display = reported_key("edid-serial", "DELL-U2723QE-9J4K2H3", DeviceKind::Display)?;
let audio_interface = reported_key(
"coreaudio-uid",
"Scarlett18i20:D4E5",
DeviceKind::AudioInterface,
)?;
let dmx_universe = reported_key("patch", "artnet/10.0.0.7/u1", DeviceKind::DmxUniverse)?;
let hid_panel = reported_key("usb-serial", "CL15K1A00080", DeviceKind::HidPanel)?;
let dock_child = reported_key("net-dock-node", "dock:AB12/child/2", DeviceKind::HidPanel)?;
let camera = DeviceKey {
kind: DeviceKind::Camera,
id: DeviceIdSource::Synthesized {
digest: Digest::new(14_695_981_039_346_656_037),
},
};
let authored_display = DeviceKey {
kind: DeviceKind::Display,
id: DeviceIdSource::Authored {
value: AuthoredId::new("studio-display-a")?,
},
};
for (ron, expected) in [
(
r#"DeviceKey(kind: Display, id: Reported(scheme: "edid-serial", value: "DELL-U2723QE-9J4K2H3"))"#,
display,
),
(
r#"DeviceKey(kind: AudioInterface, id: Reported(scheme: "coreaudio-uid", value: "Scarlett18i20:D4E5"))"#,
audio_interface,
),
(
r#"DeviceKey(kind: DmxUniverse, id: Reported(scheme: "patch", value: "artnet/10.0.0.7/u1"))"#,
dmx_universe,
),
(
r#"DeviceKey(kind: HidPanel, id: Reported(scheme: "usb-serial", value: "CL15K1A00080"))"#,
hid_panel,
),
(
r#"DeviceKey(kind: HidPanel, id: Reported(scheme: "net-dock-node", value: "dock:AB12/child/2"))"#,
dock_child,
),
(
r"DeviceKey(kind: Camera, id: Synthesized(digest: 14695981039346656037))",
camera,
),
(
r#"DeviceKey(kind: Display, id: Authored(value: "studio-display-a"))"#,
authored_display,
),
] {
let parsed = options.from_str::<DeviceKey>(ron)?;
assert_eq!(parsed, expected);
let serialized = options.to_string_pretty(&expected, pretty.clone())?;
assert_eq!(serialized, ron);
}
Ok(())
}
#[test]
fn device_id_is_excluded_from_persisted_device_entities() -> Result<(), Box<dyn Error>> {
let key = reported_key("edid-serial", "DELL-U2723QE-9J4K2H3", DeviceKind::Display)?;
let mut app = App::new();
let entity = app
.world_mut()
.spawn((DeviceId::new(7), PersistedDevice(key)))
.id();
let serialized = {
let world = app.world();
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let type_registry = app_type_registry.read();
let dynamic_world = DynamicWorldBuilder::from_world(world, &type_registry)
.deny_component::<DeviceId>()
.extract_entity(entity)
.build();
let serialized = dynamic_world.serialize(&type_registry)?;
drop(type_registry);
serialized
};
assert!(serialized.contains("PersistedDevice"));
assert!(!serialized.contains("DeviceId"));
Ok(())
}
#[test]
fn reflection_cannot_construct_device_id() {
let mut dynamic_device_id = DynamicTupleStruct::default();
dynamic_device_id.insert(0_u64);
assert!(DeviceId::from_reflect(&dynamic_device_id).is_none());
}
#[test]
fn device_id_registers_component_reflection_metadata() {
let app = App::new();
let type_registry = app.world().resource::<AppTypeRegistry>().read();
assert!(
type_registry
.get_type_data::<ReflectComponent>(TypeId::of::<DeviceId>())
.is_some()
);
drop(type_registry);
}
fn reported_key(
scheme: &str,
value: &str,
kind: DeviceKind,
) -> Result<DeviceKey, Box<dyn Error>> {
Ok(DeviceKey {
kind,
id: DeviceIdSource::Reported {
scheme: SchemeName::new(scheme)?,
value: ReportedId::new(value)?,
},
})
}
}