phoxal_bundle/
participant.rs1use phoxal_model::identity::ComponentInstanceId;
4use phoxal_model::{Clock, Robot};
5use phoxal_runtime_contract::identity::{ParticipantArtifactId, ParticipantId};
6use phoxal_runtime_contract::metadata::ParticipantKind;
7use serde::{Deserialize, Serialize};
8
9use crate::{BinaryReference, DocumentError, ParticipantClock};
10
11#[derive(phoxal_macros::DescribeWire, Clone, Debug, Deserialize, Serialize)]
13#[serde(deny_unknown_fields)]
14pub struct RuntimeParticipant {
15 pub(crate) id: ParticipantId,
16 pub(crate) artifact: ParticipantArtifactId,
17 pub(crate) config: Option<serde_json::Value>,
18 pub(crate) component: Option<ComponentInstanceId>,
19 pub(crate) clock: ParticipantClock,
20}
21
22impl RuntimeParticipant {
23 #[must_use]
24 pub fn new(
25 id: ParticipantId,
26 artifact: ParticipantArtifactId,
27 config: Option<serde_json::Value>,
28 component: Option<ComponentInstanceId>,
29 clock: ParticipantClock,
30 ) -> Self {
31 Self {
32 id,
33 artifact,
34 config,
35 component,
36 clock,
37 }
38 }
39 #[must_use]
40 pub const fn id(&self) -> &ParticipantId {
41 &self.id
42 }
43 #[must_use]
44 pub const fn artifact(&self) -> &ParticipantArtifactId {
45 &self.artifact
46 }
47 #[must_use]
48 pub fn config(&self) -> Option<&serde_json::Value> {
49 self.config.as_ref()
50 }
51 #[must_use]
52 pub const fn component(&self) -> Option<&ComponentInstanceId> {
53 self.component.as_ref()
54 }
55 #[must_use]
56 pub const fn clock(&self) -> ParticipantClock {
57 self.clock
58 }
59
60 pub(crate) fn validate(
61 &self,
62 robot: &Robot,
63 artifact: &BinaryReference,
64 config_validator: &jsonschema::Validator,
65 ) -> Result<(), DocumentError> {
66 if !artifact.path().starts_with_directory(crate::BIN_DIR) {
67 return Err(DocumentError::ArtifactOutsideBin {
68 artifact: self.artifact.clone(),
69 path: artifact.path().clone(),
70 });
71 }
72 if let Some(component) = &self.component
73 && robot.component_instance(component.as_str()).is_none()
74 {
75 return Err(DocumentError::UnknownComponent {
76 participant: self.id.clone(),
77 component_instance: component.clone(),
78 });
79 }
80 match (artifact.contract().kind, &self.component) {
81 (ParticipantKind::Driver, None) => {
82 return Err(DocumentError::MissingDriverComponent {
83 participant: self.id.clone(),
84 });
85 }
86 (ParticipantKind::Driver, Some(_)) | (_, None) => {}
87 (kind, Some(component_instance)) => {
88 return Err(DocumentError::UnexpectedComponent {
89 participant: self.id.clone(),
90 kind,
91 component_instance: component_instance.clone(),
92 });
93 }
94 }
95 let kind = artifact.contract().kind;
96 let execution_mode_matches = match kind {
97 ParticipantKind::Driver => {
98 robot.clock() == Clock::Real && self.clock != ParticipantClock::Simulation
99 }
100 ParticipantKind::Simulator => {
101 robot.clock() == Clock::Simulated && self.clock == ParticipantClock::Simulation
102 }
103 ParticipantKind::Brain | ParticipantKind::Service => true,
104 };
105 if !execution_mode_matches {
106 return Err(DocumentError::ExecutionModeMismatch {
107 participant: self.id.clone(),
108 kind,
109 robot: robot.clock(),
110 participant_clock: self.clock,
111 });
112 }
113 match (robot.clock(), self.clock) {
114 (Clock::Real, ParticipantClock::Simulation) => {
115 return Err(DocumentError::ClockMismatch {
116 participant: self.id.clone(),
117 robot: Clock::Real,
118 participant_clock: self.clock,
119 });
120 }
121 (Clock::Simulated, ParticipantClock::Real) => {
122 return Err(DocumentError::ClockMismatch {
123 participant: self.id.clone(),
124 robot: Clock::Simulated,
125 participant_clock: self.clock,
126 });
127 }
128 _ => {}
129 }
130 let null = serde_json::Value::Null;
131 let config = self.config.as_ref().unwrap_or(&null);
132 if let Err(error) = config_validator.validate(config) {
133 return Err(DocumentError::InvalidConfig {
134 participant: self.id.clone(),
135 error: error.to_string(),
136 });
137 }
138 Ok(())
139 }
140}