use super::{BuilderError, BuilderResult};
use crate::types::{
basic::{OSString, ParameterDeclaration, ParameterDeclarations, UnsignedShort},
catalogs::locations::CatalogLocations,
entities::Entities,
enums::ParameterType,
road::RoadNetwork,
scenario::storyboard::{FileHeader, OpenScenario, Storyboard},
};
use std::marker::PhantomData;
#[derive(Debug)]
pub struct Empty;
#[derive(Debug)]
pub struct HasHeader;
#[derive(Debug)]
pub struct HasEntities;
#[derive(Debug)]
pub struct Complete;
pub struct ScenarioBuilder<S> {
_state: PhantomData<S>,
pub(crate) data: PartialScenarioData,
}
#[derive(Debug, Default)]
pub(crate) struct PartialScenarioData {
pub(crate) file_header: Option<FileHeader>,
pub(crate) parameter_declarations: Option<ParameterDeclarations>,
pub(crate) catalog_locations: Option<CatalogLocations>,
pub(crate) road_network: Option<RoadNetwork>,
pub(crate) entities: Option<Entities>,
pub(crate) storyboard: Option<Storyboard>,
}
impl ScenarioBuilder<Empty> {
pub fn new() -> Self {
Self {
_state: PhantomData,
data: PartialScenarioData::default(),
}
}
pub fn with_header(mut self, description: &str, author: &str) -> ScenarioBuilder<HasHeader> {
let now = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%S").to_string();
self.data.file_header = Some(FileHeader {
rev_major: UnsignedShort::literal(1),
rev_minor: UnsignedShort::literal(0),
date: OSString::literal(now),
description: OSString::literal(description.to_string()),
author: OSString::literal(author.to_string()),
});
ScenarioBuilder {
_state: PhantomData,
data: self.data,
}
}
}
impl ScenarioBuilder<HasHeader> {
pub fn with_parameters(mut self, params: ParameterDeclarations) -> Self {
self.data.parameter_declarations = Some(params);
self
}
pub fn add_parameter(mut self, name: &str, param_type: ParameterType, value: &str) -> Self {
let mut params = self.data.parameter_declarations.take().unwrap_or_default();
params.parameter_declarations.push(ParameterDeclaration {
name: OSString::literal(name.to_string()),
parameter_type: param_type,
value: OSString::literal(value.to_string()),
constraint_groups: Vec::new(),
});
self.data.parameter_declarations = Some(params);
self
}
pub fn with_catalog_locations(mut self, locations: CatalogLocations) -> Self {
self.data.catalog_locations = Some(locations);
self
}
pub fn with_road_network(mut self, network: RoadNetwork) -> Self {
self.data.road_network = Some(network);
self
}
pub fn with_road_file(mut self, file_path: &str) -> Self {
self.data.road_network = Some(RoadNetwork {
logic_file: Some(crate::types::road::LogicFile {
filepath: OSString::literal(file_path.to_string()),
}),
scene_graph_file: None,
});
self
}
pub fn with_entities(mut self) -> ScenarioBuilder<HasEntities> {
self.data.entities = Some(Entities::new());
ScenarioBuilder {
_state: PhantomData,
data: self.data,
}
}
}
impl ScenarioBuilder<HasEntities> {
pub fn add_vehicle<F>(mut self, name: &str, config: F) -> Self
where
F: FnOnce(
crate::builder::entities::DetachedVehicleBuilder,
) -> crate::builder::entities::DetachedVehicleBuilder,
{
let vehicle_builder = crate::builder::entities::DetachedVehicleBuilder::new(name);
let configured_builder = config(vehicle_builder);
let vehicle_object = configured_builder.build();
if let Some(ref mut entities) = self.data.entities {
entities.add_object(vehicle_object);
}
self
}
pub fn add_vehicle_mut(&mut self, name: &str) -> crate::builder::entities::VehicleBuilder<'_> {
crate::builder::entities::VehicleBuilder::new(self, name)
}
pub fn add_catalog_vehicle(
&mut self,
name: &str,
) -> crate::builder::entities::catalog::CatalogVehicleBuilder<'_> {
crate::builder::entities::catalog::CatalogVehicleBuilder::new(self, name)
}
pub fn add_pedestrian<F>(mut self, name: &str, config: F) -> Self
where
F: FnOnce(
crate::builder::entities::DetachedPedestrianBuilder,
) -> crate::builder::entities::DetachedPedestrianBuilder,
{
let pedestrian_builder = crate::builder::entities::DetachedPedestrianBuilder::new(name);
let configured_builder = config(pedestrian_builder);
let pedestrian_object = configured_builder.build();
if let Some(ref mut entities) = self.data.entities {
entities.add_object(pedestrian_object);
}
self
}
pub fn add_catalog_pedestrian(
&mut self,
name: &str,
) -> crate::builder::entities::catalog::CatalogPedestrianBuilder<'_> {
crate::builder::entities::catalog::CatalogPedestrianBuilder::new(self, name)
}
pub fn with_storyboard<F>(self, config: F) -> ScenarioBuilder<Complete>
where
F: FnOnce(
crate::builder::storyboard::StoryboardBuilder,
) -> crate::builder::storyboard::StoryboardBuilder,
{
let storyboard_builder = crate::builder::storyboard::StoryboardBuilder::new(self);
let configured_builder = config(storyboard_builder);
configured_builder.finish()
}
pub fn with_storyboard_mut(self) -> crate::builder::storyboard::StoryboardBuilder {
crate::builder::storyboard::StoryboardBuilder::new(self)
}
pub fn create_storyboard(self) -> crate::builder::storyboard::StoryboardBuilder {
crate::builder::storyboard::StoryboardBuilder::new(self)
}
pub fn build(self) -> BuilderResult<OpenScenario> {
let file_header = self
.data
.file_header
.ok_or_else(|| BuilderError::missing_field("file_header", ".with_header()"))?;
let entities = self
.data
.entities
.ok_or_else(|| BuilderError::missing_field("entities", ".with_entities()"))?;
let storyboard = self
.data
.storyboard
.ok_or_else(|| BuilderError::missing_field("storyboard", ".with_storyboard()"))?;
Ok(OpenScenario {
file_header,
parameter_declarations: self.data.parameter_declarations,
variable_declarations: None,
monitor_declarations: None,
catalog_locations: self.data.catalog_locations,
road_network: self.data.road_network,
entities: Some(entities),
storyboard: Some(storyboard),
parameter_value_distribution: None,
catalog: None,
})
}
}
impl ScenarioBuilder<Complete> {
pub(crate) fn from_data_complete(data: PartialScenarioData) -> Self {
Self {
_state: PhantomData,
data,
}
}
pub fn build(self) -> BuilderResult<OpenScenario> {
let file_header = self
.data
.file_header
.ok_or_else(|| BuilderError::missing_field("file_header", ".with_header()"))?;
let entities = self
.data
.entities
.ok_or_else(|| BuilderError::missing_field("entities", ".with_entities()"))?;
let storyboard = self
.data
.storyboard
.ok_or_else(|| BuilderError::missing_field("storyboard", ".with_storyboard()"))?;
Ok(OpenScenario {
file_header,
parameter_declarations: self.data.parameter_declarations,
variable_declarations: None,
monitor_declarations: None,
catalog_locations: self.data.catalog_locations,
road_network: self.data.road_network,
entities: Some(entities),
storyboard: Some(storyboard),
parameter_value_distribution: None,
catalog: None,
})
}
}
impl Default for ScenarioBuilder<Empty> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_minimal_scenario_builder() {
let scenario = ScenarioBuilder::new()
.with_header("Test Scenario", "Test Author")
.with_entities()
.with_storyboard(|storyboard| {
storyboard
})
.build()
.unwrap();
if let crate::types::basic::Value::Literal(desc) = &scenario.file_header.description {
assert_eq!(desc, "Test Scenario");
} else {
panic!("Description should be literal");
}
assert!(scenario.entities.is_some());
assert!(scenario.storyboard.is_some());
}
}