use crate::types::basic::{Double, OSString};
use crate::types::enums::ObjectType;
use crate::types::scenario::triggers::EntityRef;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EntitySelection {
#[serde(rename = "ByType", skip_serializing_if = "Option::is_none")]
pub by_type: Option<ByObjectType>,
#[serde(rename = "ByName", skip_serializing_if = "Option::is_none")]
pub by_name: Option<ByName>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SelectedEntities {
#[serde(rename = "EntityRef")]
pub entity_refs: Vec<EntityRef>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EntityDistribution {
#[serde(rename = "EntityDistributionEntry")]
pub entries: Vec<EntityDistributionEntry>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EntityDistributionEntry {
#[serde(rename = "@entityRef")]
pub entity_ref: OSString,
#[serde(rename = "@weight")]
pub weight: Double,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ScenarioObjectTemplate {
#[serde(rename = "@name")]
pub name: OSString,
#[serde(rename = "@objectType")]
pub object_type: ObjectType,
#[serde(rename = "Properties", skip_serializing_if = "Option::is_none")]
pub properties: Option<TemplateProperties>,
#[serde(
rename = "ExternalObjectReference",
skip_serializing_if = "Option::is_none"
)]
pub external_object_reference: Option<ExternalObjectReference>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ExternalObjectReference {
#[serde(rename = "@file")]
pub file: OSString,
#[serde(rename = "@name")]
pub name: OSString,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ByObjectType {
#[serde(rename = "@objectType")]
pub object_type: ObjectType,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ByType {
#[serde(rename = "@type")]
pub type_spec: OSString,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ByName {
#[serde(rename = "@name")]
pub name: OSString,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct TemplateProperties {
#[serde(rename = "Property", default)]
pub properties: Vec<TemplateProperty>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TemplateProperty {
#[serde(rename = "@name")]
pub name: OSString,
#[serde(rename = "@value")]
pub value: OSString,
}
impl Default for EntitySelection {
fn default() -> Self {
Self {
by_type: Some(ByObjectType::default()),
by_name: None,
}
}
}
impl Default for SelectedEntities {
fn default() -> Self {
Self {
entity_refs: vec![EntityRef::default()],
}
}
}
impl Default for EntityDistribution {
fn default() -> Self {
Self {
entries: vec![EntityDistributionEntry::default()],
}
}
}
impl Default for EntityDistributionEntry {
fn default() -> Self {
Self {
entity_ref: OSString::literal("DefaultEntity".to_string()),
weight: Double::literal(1.0),
}
}
}
impl Default for ScenarioObjectTemplate {
fn default() -> Self {
Self {
name: OSString::literal("DefaultTemplate".to_string()),
object_type: ObjectType::Vehicle,
properties: None,
external_object_reference: None,
}
}
}
impl Default for ExternalObjectReference {
fn default() -> Self {
Self {
file: OSString::literal("objects.xml".to_string()),
name: OSString::literal("DefaultObject".to_string()),
}
}
}
impl Default for ByObjectType {
fn default() -> Self {
Self {
object_type: ObjectType::Vehicle,
}
}
}
impl Default for ByType {
fn default() -> Self {
Self {
type_spec: OSString::literal("vehicle".to_string()),
}
}
}
impl Default for ByName {
fn default() -> Self {
Self {
name: OSString::literal("*".to_string()),
}
}
}
impl Default for TemplateProperty {
fn default() -> Self {
Self {
name: OSString::literal("property".to_string()),
value: OSString::literal("value".to_string()),
}
}
}
impl EntitySelection {
pub fn by_object_type(object_type: ObjectType) -> Self {
Self {
by_type: Some(ByObjectType { object_type }),
by_name: None,
}
}
pub fn by_name(name: impl Into<String>) -> Self {
Self {
by_type: None,
by_name: Some(ByName {
name: OSString::literal(name.into()),
}),
}
}
pub fn by_type_and_name(object_type: ObjectType, name: impl Into<String>) -> Self {
Self {
by_type: Some(ByObjectType { object_type }),
by_name: Some(ByName {
name: OSString::literal(name.into()),
}),
}
}
}
impl SelectedEntities {
pub fn new() -> Self {
Self {
entity_refs: Vec::new(),
}
}
pub fn from_names(names: Vec<impl Into<String>>) -> Self {
Self {
entity_refs: names
.into_iter()
.map(|name| EntityRef::new(name.into()))
.collect(),
}
}
pub fn add_entity(&mut self, entity_name: impl Into<String>) {
self.entity_refs.push(EntityRef::new(entity_name.into()));
}
pub fn count(&self) -> usize {
self.entity_refs.len()
}
}
impl EntityDistribution {
pub fn new() -> Self {
Self {
entries: Vec::new(),
}
}
pub fn add_entry(&mut self, entity_ref: impl Into<String>, weight: f64) {
self.entries.push(EntityDistributionEntry {
entity_ref: OSString::literal(entity_ref.into()),
weight: Double::literal(weight),
});
}
pub fn uniform(entity_names: Vec<impl Into<String>>) -> Self {
let weight = 1.0 / entity_names.len() as f64;
let entries = entity_names
.into_iter()
.map(|name| EntityDistributionEntry {
entity_ref: OSString::literal(name.into()),
weight: Double::literal(weight),
})
.collect();
Self { entries }
}
pub fn total_weight(&self) -> f64 {
self.entries
.iter()
.filter_map(|entry| entry.weight.as_literal())
.sum()
}
}
impl EntityDistributionEntry {
pub fn new(entity_ref: impl Into<String>, weight: f64) -> Self {
Self {
entity_ref: OSString::literal(entity_ref.into()),
weight: Double::literal(weight),
}
}
}
impl ScenarioObjectTemplate {
pub fn new(name: impl Into<String>, object_type: ObjectType) -> Self {
Self {
name: OSString::literal(name.into()),
object_type,
properties: None,
external_object_reference: None,
}
}
pub fn with_external_reference(
name: impl Into<String>,
object_type: ObjectType,
file: impl Into<String>,
object_name: impl Into<String>,
) -> Self {
Self {
name: OSString::literal(name.into()),
object_type,
properties: None,
external_object_reference: Some(ExternalObjectReference {
file: OSString::literal(file.into()),
name: OSString::literal(object_name.into()),
}),
}
}
pub fn add_property(&mut self, name: impl Into<String>, value: impl Into<String>) {
if self.properties.is_none() {
self.properties = Some(TemplateProperties::default());
}
if let Some(ref mut props) = self.properties {
props.properties.push(TemplateProperty {
name: OSString::literal(name.into()),
value: OSString::literal(value.into()),
});
}
}
}
impl ExternalObjectReference {
pub fn new(file: impl Into<String>, name: impl Into<String>) -> Self {
Self {
file: OSString::literal(file.into()),
name: OSString::literal(name.into()),
}
}
}
impl ByObjectType {
pub fn new(object_type: ObjectType) -> Self {
Self { object_type }
}
pub fn vehicle() -> Self {
Self::new(ObjectType::Vehicle)
}
pub fn pedestrian() -> Self {
Self::new(ObjectType::Pedestrian)
}
pub fn miscellaneous_object() -> Self {
Self::new(ObjectType::MiscellaneousObject)
}
}
impl ByType {
pub fn new(type_spec: impl Into<String>) -> Self {
Self {
type_spec: OSString::literal(type_spec.into()),
}
}
}
impl ByName {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: OSString::literal(name.into()),
}
}
pub fn wildcard() -> Self {
Self::new("*")
}
pub fn prefix(prefix: impl Into<String>) -> Self {
Self::new(format!("{}*", prefix.into()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::enums::ObjectType;
#[test]
fn test_entity_selection_creation() {
let selection = EntitySelection::by_object_type(ObjectType::Vehicle);
assert!(selection.by_type.is_some());
assert_eq!(selection.by_type.unwrap().object_type, ObjectType::Vehicle);
assert!(selection.by_name.is_none());
let selection = EntitySelection::by_name("Ego*");
assert!(selection.by_type.is_none());
assert!(selection.by_name.is_some());
assert_eq!(
selection.by_name.unwrap().name.as_literal().unwrap(),
"Ego*"
);
let selection = EntitySelection::by_type_and_name(ObjectType::Pedestrian, "Walker*");
assert!(selection.by_type.is_some());
assert!(selection.by_name.is_some());
assert_eq!(
selection.by_type.unwrap().object_type,
ObjectType::Pedestrian
);
assert_eq!(
selection.by_name.unwrap().name.as_literal().unwrap(),
"Walker*"
);
}
#[test]
fn test_selected_entities() {
let mut entities = SelectedEntities::new();
assert_eq!(entities.count(), 0);
entities.add_entity("Ego");
entities.add_entity("Target1");
assert_eq!(entities.count(), 2);
let entities_from_names = SelectedEntities::from_names(vec!["Car1", "Car2", "Car3"]);
assert_eq!(entities_from_names.count(), 3);
}
#[test]
fn test_entity_distribution() {
let mut distribution = EntityDistribution::new();
distribution.add_entry("Car1", 0.6);
distribution.add_entry("Car2", 0.4);
assert_eq!(distribution.entries.len(), 2);
assert_eq!(distribution.total_weight(), 1.0);
let uniform_dist = EntityDistribution::uniform(vec!["A", "B", "C", "D"]);
assert_eq!(uniform_dist.entries.len(), 4);
assert!((uniform_dist.total_weight() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn test_entity_distribution_entry() {
let entry = EntityDistributionEntry::new("TestEntity", 0.75);
assert_eq!(entry.entity_ref.as_literal().unwrap(), "TestEntity");
assert_eq!(entry.weight.as_literal().unwrap(), &0.75);
}
#[test]
fn test_scenario_object_template() {
let mut template = ScenarioObjectTemplate::new("VehicleTemplate", ObjectType::Vehicle);
assert_eq!(template.name.as_literal().unwrap(), "VehicleTemplate");
assert_eq!(template.object_type, ObjectType::Vehicle);
assert!(template.properties.is_none());
template.add_property("color", "red");
template.add_property("mass", "1500");
assert!(template.properties.is_some());
assert_eq!(template.properties.as_ref().unwrap().properties.len(), 2);
let external_template = ScenarioObjectTemplate::with_external_reference(
"ExternalVehicle",
ObjectType::Vehicle,
"vehicles.xml",
"SportsCar",
);
assert!(external_template.external_object_reference.is_some());
let ext_ref = external_template.external_object_reference.unwrap();
assert_eq!(ext_ref.file.as_literal().unwrap(), "vehicles.xml");
assert_eq!(ext_ref.name.as_literal().unwrap(), "SportsCar");
}
#[test]
fn test_external_object_reference() {
let ext_ref = ExternalObjectReference::new("objects/vehicles.xml", "Sedan");
assert_eq!(ext_ref.file.as_literal().unwrap(), "objects/vehicles.xml");
assert_eq!(ext_ref.name.as_literal().unwrap(), "Sedan");
}
#[test]
fn test_by_object_type() {
let vehicle_selector = ByObjectType::vehicle();
assert_eq!(vehicle_selector.object_type, ObjectType::Vehicle);
let pedestrian_selector = ByObjectType::pedestrian();
assert_eq!(pedestrian_selector.object_type, ObjectType::Pedestrian);
let misc_selector = ByObjectType::miscellaneous_object();
assert_eq!(misc_selector.object_type, ObjectType::MiscellaneousObject);
}
#[test]
fn test_by_type() {
let type_selector = ByType::new("custom_vehicle_type");
assert_eq!(
type_selector.type_spec.as_literal().unwrap(),
"custom_vehicle_type"
);
}
#[test]
fn test_by_name() {
let wildcard_selector = ByName::wildcard();
assert_eq!(wildcard_selector.name.as_literal().unwrap(), "*");
let prefix_selector = ByName::prefix("Ego");
assert_eq!(prefix_selector.name.as_literal().unwrap(), "Ego*");
let exact_selector = ByName::new("SpecificEntity");
assert_eq!(exact_selector.name.as_literal().unwrap(), "SpecificEntity");
}
#[test]
fn test_serialization() {
let selection = EntitySelection::by_object_type(ObjectType::Vehicle);
let xml = quick_xml::se::to_string(&selection).unwrap();
assert!(xml.contains("ByType"));
assert!(xml.contains("objectType=\"vehicle\""));
let entities = SelectedEntities::from_names(vec!["Ego", "Target"]);
let xml = quick_xml::se::to_string(&entities).unwrap();
assert!(xml.contains("EntityRef"));
assert!(xml.contains("entityRef=\"Ego\""));
assert!(xml.contains("entityRef=\"Target\""));
let distribution = EntityDistribution::uniform(vec!["Car1", "Car2"]);
let xml = quick_xml::se::to_string(&distribution).unwrap();
assert!(xml.contains("EntityDistributionEntry"));
assert!(xml.contains("entityRef=\"Car1\""));
assert!(xml.contains("weight=\"0.5\""));
}
}