1use crate::CuJointPlanPath;
4use crate::CuJointPlanRequest;
5use crate::CuJointPlanner;
6use crate::CuPlanStatus;
7use bincode::Decode;
8use bincode::Encode;
9use bincode::de::Decoder;
10use bincode::enc::Encoder;
11use bincode::error::DecodeError;
12use bincode::error::EncodeError;
13use cu_rng::prelude::*;
14use cu29::cutask_anytime::AnytimeStatus;
15use cu29::cutask_anytime::CuAnytimeTask;
16use cu29::cutask_anytime::Quality;
17use cu29::cutask_anytime::quality_from_f32;
18use cu29::prelude::*;
19use cu29::resources;
20use repose_collision::CuCollisionModelSpec;
21use repose_kinematics::CuChainSpec;
22use serde::Deserialize;
23use serde::Serialize;
24
25pub mod planner_resources {
27 use super::*;
28 resources!({ rng => Owned<CuRng> });
29}
30
31#[derive(Default, Debug, Reflect)]
33pub struct CuJointPlannerDebugState {
34 pub status: CuPlanStatus,
35 pub effort: u32,
36 pub published_cost: f64,
37 pub published_quality: Quality,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
56#[serde(
58 deny_unknown_fields,
59 bound(
60 serialize = "P: Serialize",
61 deserialize = "P: Deserialize<'de> + Default"
62 )
63)]
64pub struct CuJointPlannerParams<P, const N: usize, const M: usize = N> {
65 #[serde(default)]
67 pub chain: Option<CuChainSpec<N>>,
68 #[serde(default)]
70 pub collision_model: Option<CuCollisionModelSpec<M>>,
71 #[serde(default)]
73 pub params: P,
74 #[serde(default)]
76 pub base_budget: Option<u32>,
77 #[serde(default)]
79 pub refine_budget: Option<u32>,
80}
81
82impl<P: Default, const N: usize, const M: usize> Default for CuJointPlannerParams<P, N, M> {
83 fn default() -> Self {
84 Self {
85 chain: None,
86 collision_model: None,
87 params: P::default(),
88 base_budget: None,
89 refine_budget: None,
90 }
91 }
92}
93
94impl<P, const N: usize, const M: usize> CuJointPlannerParams<P, N, M> {
95 pub fn validate(&self) -> CuResult<()> {
99 self.resolve_models()?;
100 if self.base_budget == Some(0) || self.refine_budget == Some(0) {
101 return Err(CuError::from(
102 "repose-motion-planning: 'base_budget' and 'refine_budget' must be positive",
103 ));
104 }
105 Ok(())
106 }
107
108 fn resolve_models(
109 &self,
110 ) -> CuResult<(
111 repose_kinematics::CuKinematicChain<N>,
112 repose_collision::CuRobotCollisionModel<N, M>,
113 )> {
114 let chain = self
115 .chain
116 .as_ref()
117 .ok_or_else(|| CuError::from("repose-motion-planning: missing required 'chain'"))?
118 .resolve()?;
119 let model = self
120 .collision_model
121 .as_ref()
122 .ok_or_else(|| {
123 CuError::from("repose-motion-planning: missing required 'collision_model'")
124 })?
125 .resolve::<N>()?;
126 Ok((chain, model))
127 }
128}
129
130#[derive(Reflect)]
143#[reflect(no_field_bounds, from_reflect = false)]
144pub struct CuJointPlannerTask<P, const N: usize, const M: usize = N> {
145 #[reflect(ignore)]
146 planner: P,
147 base_budget: u32,
148 refine_budget: u32,
149 base_seed: u64,
150 job_counter: u64,
151 #[reflect(ignore)]
152 status: CuPlanStatus,
153 #[reflect(ignore)]
154 effort: u32,
155 published_cost: f64,
156 published_quality: Quality,
157}
158
159impl<P: CuJointPlanner<N, M>, const N: usize, const M: usize> CuJointPlannerTask<P, N, M> {
160 pub fn from_planner(
162 planner: P,
163 base_budget: u32,
164 refine_budget: u32,
165 base_seed: u64,
166 ) -> CuResult<Self> {
167 if base_budget == 0 || refine_budget == 0 {
168 return Err(CuError::from(
169 "repose-motion-planning: 'base_budget' and 'refine_budget' must be positive",
170 ));
171 }
172 Ok(Self {
173 planner,
174 base_budget,
175 refine_budget,
176 base_seed,
177 job_counter: 0,
178 status: CuPlanStatus::default(),
179 effort: 0,
180 published_cost: f64::INFINITY,
181 published_quality: quality_from_f32(0.0),
182 })
183 }
184
185 pub fn from_params(
188 params: CuJointPlannerParams<P::Params, N, M>,
189 base_seed: u64,
190 ) -> CuResult<Self> {
191 params.validate()?;
192 let (chain, model) = params.resolve_models()?;
193 let planner = P::new(chain, model, params.params)?;
194 let (base, refine) = P::DEFAULT_BUDGETS;
195 Self::from_planner(
196 planner,
197 params.base_budget.unwrap_or(base),
198 params.refine_budget.unwrap_or(refine),
199 base_seed,
200 )
201 }
202
203 pub const fn planner(&self) -> &P {
205 &self.planner
206 }
207
208 pub const fn status(&self) -> CuPlanStatus {
210 self.status
211 }
212
213 pub fn debug_state(&self) -> CuJointPlannerDebugState {
215 CuJointPlannerDebugState {
216 status: self.status,
217 effort: self.effort,
218 published_cost: self.published_cost,
219 published_quality: self.published_quality,
220 }
221 }
222
223 fn reset_published(&mut self, output: &mut CuMsg<CuJointPlanPath<N>>) {
224 output.clear_payload();
225 self.published_cost = f64::INFINITY;
226 self.published_quality = quality_from_f32(0.0);
227 }
228
229 fn publish(&mut self, output: &mut CuMsg<CuJointPlanPath<N>>) -> AnytimeStatus<Quality> {
231 let mut path = CuJointPlanPath::<N>::default();
232 let status = self.planner.write_best(&mut path);
233 self.status = status;
234 self.effort = path.effort;
235 if status.has_path() {
236 if path.cost < self.published_cost {
237 self.published_cost = path.cost;
238 self.published_quality = self.planner.quality();
239 output.set_payload(path);
240 } else if let Some(published) = output.payload_mut() {
241 published.status = status;
243 published.effort = path.effort;
244 }
245 return if status == CuPlanStatus::Converged {
246 AnytimeStatus::Converged(self.published_quality)
247 } else {
248 AnytimeStatus::Improved(self.published_quality)
249 };
250 }
251 if status.is_final() {
252 self.published_cost = f64::INFINITY;
253 self.published_quality = quality_from_f32(0.0);
254 output.set_payload(path);
255 return AnytimeStatus::Aborted;
256 }
257 AnytimeStatus::Improved(self.published_quality)
258 }
259}
260
261impl<P, const N: usize, const M: usize> Freezable for CuJointPlannerTask<P, N, M> {
262 fn freeze<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
263 Encode::encode(&self.base_seed, encoder)?;
264 Encode::encode(&self.job_counter, encoder)
265 }
266
267 fn thaw<D: Decoder>(&mut self, decoder: &mut D) -> Result<(), DecodeError> {
268 self.base_seed = Decode::decode(decoder)?;
269 self.job_counter = Decode::decode(decoder)?;
270 Ok(())
271 }
272}
273
274impl<P, const N: usize, const M: usize> CuAnytimeTask for CuJointPlannerTask<P, N, M>
275where
276 P: CuJointPlanner<N, M> + Send + Sync + 'static,
277{
278 type Input<'m> = input_msg!(CuJointPlanRequest<N>);
279 type Output<'m> = output_msg!(CuJointPlanPath<N>);
280 type Resources<'r> = planner_resources::Resources;
281 type Quality = Quality;
282
283 fn register_debug_state_types(registry: &mut TypeRegistry) {
284 registry.register::<CuJointPlannerDebugState>();
285 }
286
287 fn debug_state_type_path() -> &'static str {
288 CuJointPlannerDebugState::type_path()
289 }
290
291 fn with_debug_state<R>(&self, f: impl FnOnce(&dyn bevy_reflect::Reflect) -> R) -> R {
292 f(&self.debug_state())
293 }
294
295 fn new(config: Option<&ComponentConfig>, resources: Self::Resources<'_>) -> CuResult<Self> {
296 let params = config
297 .map(ComponentConfig::deserialize_into::<CuJointPlannerParams<P::Params, N, M>>)
298 .transpose()?
299 .unwrap_or_default();
300 let Owned(mut rng) = resources.rng;
301 Self::from_params(params, rng.random::<u64>())
302 }
303
304 fn base<'i, 'o>(
305 &mut self,
306 _ctx: &CuContext,
307 input: &Self::Input<'i>,
308 output: &mut Self::Output<'o>,
309 ) -> CuResult<AnytimeStatus<Quality>> {
310 output.tov = input.tov;
311 self.reset_published(output);
312 let Some(request) = input.payload() else {
313 self.status = CuPlanStatus::default();
314 return Ok(AnytimeStatus::Aborted);
315 };
316 self.job_counter = self.job_counter.wrapping_add(1);
317 let seed = self.base_seed.wrapping_add(self.job_counter);
318 let status = self.planner.begin(request, seed)?;
319 if !status.is_final() {
320 self.planner.step(self.base_budget)?;
321 }
322 Ok(self.publish(output))
323 }
324
325 fn refine<'o>(
326 &mut self,
327 _ctx: &CuContext,
328 output: &mut Self::Output<'o>,
329 ) -> CuResult<AnytimeStatus<Quality>> {
330 if self.status.is_final() {
331 return Ok(if self.status.has_path() {
332 AnytimeStatus::Converged(self.published_quality)
333 } else {
334 AnytimeStatus::Aborted
335 });
336 }
337 self.planner.step(self.refine_budget)?;
338 Ok(self.publish(output))
339 }
340}