cu29 1.0.0

Copper Runtime prelude crate. Copper is a Rust engine for robotics.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
//! # Copper Runtime & SDK
//!
//! Think of Copper as a robotics game engine: define a task graph, compile once,
//! and get deterministic execution, unified logging, and sub-microsecond
//! latency from Linux workstations all the way down to bare-metal MPU builds.
//!
//! ## Quick start
//!
//! ```bash
//! cargo install cargo-cunew
//! cargo cunew /path/to/my_robot
//! cd /path/to/my_robot
//! cargo run
//! ```
//!
//! It will generate a minimal Copper robot project at `/path/to/my_robot` using the latest
//! stable Copper crates from crates.io by default.
//!
//! ## Feature flags
//!
//! - `default` = `["std", "signal-handler", "textlogs", "units"]`
//! - `units`: exposes `cu29::units` (re-export of `cu29-units`)
//! - `std`: host/runtime support that is also safe to compile for browser targets
//! - `signal-handler`: desktop Ctrl-C integration for generated `run()` loops
//! - `reflect`: reflection support for runtime and units types
//! - `textlogs`: text logging derive support
//! - `remote-debug`: remote debug transport support
//! - `sysclock-perf`: use a host/system clock for runtime perf timing while keeping robot time for `tov` and `rate_target_hz`
//! - `high-precision-limiter`: std-only hybrid sleep/spin loop limiter for tighter `rate_target_hz` cadence
//! - `async-cl-io`: offload CopperList serialization/logging to a dedicated std thread
//! - `parallel-rt`: prepare the runtime for a future multi-threaded deterministic executor
//! - `safety-ids`: std-only safety-case metadata collection and JSON export helpers
//!
//! ## Concepts behind Copper
//!
//! Check out the [Copper Wiki](https://github.com/copper-project/copper-rs/wiki) to understand the
//! deployments concepts, task lifecycle, available components, etc ...
//!
//! ## More examples to get you started
//!
//! - `examples/cu_caterpillar`: a minimal running example passing around booleans.
//! - `examples/cu_rp_balancebot`: a more complete example try Copper without hardware via
//!   `cargo install cu-rp-balancebot` + `balancebot-sim` (Bevy + Avian3d).
//!
//! ## Key traits and structs to check out
//!
//! - `cu29_runtime::app::CuApp`: the main trait the copper runtime will expose to run your application. (when run() etc .. is coming from)
//! - `cu29_runtime::config::CuConfig`: the configuration of your runtime
//! - `cu29_runtime::cutask::CuTask`: the core trait and helpers to implement your own tasks.
//! - `cu29_runtime::cubridge::CuBridge`: the trait to implement bridges to hardware or other software.
//! - `cu29_runtime::curuntime::CuRuntime`: the runtime that manages task execution.
//! - `cu29_runtime::simulation`: This will explain how to hook up your tasks to a simulation environment.
//!
//! ## V1 API status
//!
//! The V1 public contract is defined in `doc/v1-api-surface.md`. The prelude is the
//! canonical application import surface; lower-level modules remain addressable by
//! module path when needed, but are not implicitly part of the prelude contract.
//!
//! Need help or want to show what you're building? Join
//! [Discord](https://discord.gg/VkCG7Sb9Kw) and hop into the #general channel.
//!

#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(all(feature = "parallel-rt", not(feature = "std")))]
compile_error!("feature `parallel-rt` requires `std`");
#[cfg(not(feature = "std"))]
extern crate alloc;
extern crate self as cu29;

pub use cu29_derive::{bundle_resources, resources, safety_case};
pub use cu29_runtime::app;
pub use cu29_runtime::config;
pub use cu29_runtime::context;
pub use cu29_runtime::copperlist;
#[cfg(feature = "std")]
pub use cu29_runtime::cuasynctask;
pub use cu29_runtime::cubridge;
pub use cu29_runtime::curuntime;
pub use cu29_runtime::cutask;
#[cfg(feature = "std")]
pub use cu29_runtime::debug;
#[cfg(feature = "std")]
pub use cu29_runtime::distributed_replay;
pub use cu29_runtime::input_msg;
pub use cu29_runtime::logcodec;
pub use cu29_runtime::monitoring;
pub use cu29_runtime::output_msg;
#[cfg(all(feature = "std", feature = "parallel-rt"))]
pub use cu29_runtime::parallel_queue;
#[cfg(all(feature = "std", feature = "parallel-rt"))]
pub use cu29_runtime::parallel_rt;
pub use cu29_runtime::payload;
#[cfg(feature = "std")]
pub use cu29_runtime::pool;
pub use cu29_runtime::reflect;
pub use cu29_runtime::reflect as bevy_reflect;
#[cfg(feature = "remote-debug")]
pub use cu29_runtime::remote_debug;
#[cfg(feature = "std")]
pub use cu29_runtime::replay;
pub use cu29_runtime::resource;
pub use cu29_runtime::rx_channels;
#[cfg(feature = "std")]
pub use cu29_runtime::simulation;
#[cfg(feature = "std")]
pub use cu29_runtime::thread_pool;
pub use cu29_runtime::tx_channels;
#[cfg(feature = "safety-ids")]
pub mod safety;
#[cfg(all(feature = "std", any(test, feature = "safety-ids")))]
mod safety_runtime_cases;

#[cfg(feature = "safety-ids")]
#[doc(hidden)]
pub fn link_safety_ids() {
    safety_runtime_cases::link_safety_ids();
}

#[cfg(feature = "rtsan")]
pub mod rtsan {
    pub use rtsan_standalone::*;
}

#[cfg(not(feature = "rtsan"))]
pub mod rtsan {
    use core::ffi::CStr;

    #[derive(Default)]
    pub struct ScopedSanitizeRealtime;

    #[derive(Default)]
    pub struct ScopedDisabler;

    #[inline]
    pub fn realtime_enter() {}

    #[inline]
    pub fn realtime_exit() {}

    #[inline]
    pub fn disable() {}

    #[inline]
    pub fn enable() {}

    #[inline]
    pub fn ensure_initialized() {}

    #[allow(unused_variables)]
    pub fn notify_blocking_call(_function_name: &'static CStr) {}
}

pub use bincode;
pub use cu29_clock as clock;
#[cfg(feature = "units")]
pub use cu29_units as units;
#[doc(hidden)]
pub use serde;
#[cfg(feature = "defmt")]
pub mod defmt {
    pub use defmt::{debug, error, info, warn};
}
#[cfg(feature = "std")]
pub use cu29_runtime::config::read_configuration;
#[cfg(feature = "std")]
pub use cu29_runtime::config::read_multi_configuration;
pub use cu29_traits::*;

#[cfg(feature = "std")]
pub use rayon;

#[doc(hidden)]
pub mod __private {
    #[doc(hidden)]
    pub mod sync {
        #[cfg(not(feature = "std"))]
        pub use alloc::sync::Arc;
        #[cfg(not(feature = "std"))]
        pub use spin::Mutex;
        #[cfg(feature = "std")]
        pub use std::sync::{Arc, Mutex};
    }
}

// defmt shims re-exported for proc-macro call sites
#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! defmt_debug {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::defmt::debug!($fmt $(, $arg)*);
    }
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! defmt_debug {
    ($($tt:tt)*) => {{}};
}

#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! defmt_info {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::defmt::info!($fmt $(, $arg)*);
    }
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! defmt_info {
    ($($tt:tt)*) => {{}};
}

#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! defmt_warn {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::defmt::warn!($fmt $(, $arg)*);
    }
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! defmt_warn {
    ($($tt:tt)*) => {{}};
}

#[cfg(all(feature = "defmt", not(feature = "std")))]
#[macro_export]
macro_rules! defmt_error {
    ($fmt:literal $(, $arg:expr)* $(,)?) => {
        $crate::defmt::error!($fmt $(, $arg)*);
    }
}
#[cfg(not(all(feature = "defmt", not(feature = "std"))))]
#[macro_export]
macro_rules! defmt_error {
    ($($tt:tt)*) => {{}};
}

#[macro_export]
macro_rules! safety_check {
    ($check_id:literal, $requirement_id:literal, $condition:expr $(,)?) => {
        assert!(
            $condition,
            "safety check {} for requirement {} failed",
            $check_id, $requirement_id
        );
    };
}

#[macro_export]
macro_rules! safety_check_eq {
    ($check_id:literal, $requirement_id:literal, $left:expr, $right:expr $(,)?) => {
        assert_eq!(
            $left, $right,
            "safety check {} for requirement {} failed",
            $check_id, $requirement_id
        );
    };
}

/// Canonical imports for Copper applications.
///
/// This module intentionally re-exports each stable application-facing group once.
/// Runtime internals, remote-debug plumbing, and experimental executor APIs should
/// be imported from their explicit module paths instead of from the prelude.
pub mod prelude {
    pub use crate::bevy_reflect;
    #[cfg(feature = "units")]
    pub use crate::units;
    pub use crate::{defmt_debug, defmt_error, defmt_info, defmt_warn};
    pub use crate::{safety_case, safety_check, safety_check_eq};
    #[cfg(feature = "reflect")]
    pub use bevy_reflect_derive::Reflect;
    #[cfg(feature = "signal-handler")]
    pub use ctrlc;
    pub use cu29_clock::*;
    pub use cu29_derive::*;
    pub use cu29_log::*;
    pub use cu29_log_derive::*;
    pub use cu29_log_runtime::*;
    #[cfg(not(feature = "reflect"))]
    pub use cu29_reflect_derive::Reflect;
    pub use cu29_runtime::app;
    pub use cu29_runtime::app::*;
    pub use cu29_runtime::config::*;
    pub use cu29_runtime::context::*;
    pub use cu29_runtime::copperlist::*;
    pub use cu29_runtime::cubridge::*;
    pub use cu29_runtime::curuntime::{
        CuRuntime, KeyFrame, RuntimeLifecycleConfigSource, RuntimeLifecycleEvent,
        RuntimeLifecycleRecord, RuntimeLifecycleStackInfo,
    };
    pub use cu29_runtime::cutask::*;
    #[cfg(feature = "std")]
    pub use cu29_runtime::debug::*;
    pub use cu29_runtime::input_msg;
    pub use cu29_runtime::monitoring::*;
    pub use cu29_runtime::output_msg;
    pub use cu29_runtime::payload::*;
    #[cfg(feature = "std")]
    pub use cu29_runtime::pool::*;
    #[cfg(feature = "reflect")]
    pub use cu29_runtime::reflect::serde as reflect_serde;
    #[cfg(feature = "reflect")]
    pub use cu29_runtime::reflect::serde::{
        ReflectSerializer, SerializationData, TypedReflectSerializer,
    };
    pub use cu29_runtime::reflect::{
        GetTypeRegistration, ReflectTaskIntrospection, ReflectTypePath, TypeInfo, TypePath,
        TypeRegistry, dump_type_registry_schema,
    };
    pub use cu29_runtime::resource::*;
    pub use cu29_runtime::rx_channels;
    #[cfg(feature = "std")]
    pub use cu29_runtime::simulation::*;
    pub use cu29_runtime::tx_channels;
    pub use cu29_traits::{
        COMPACT_STRING_CAPACITY, CopperListTuple, CuCompactString, CuError, CuMsgMetadataTrait,
        CuMsgOrigin, CuPayloadRawBytes, CuResult, DebugFieldDescriptor, DebugFieldKind,
        DebugFieldSemantics, DebugScalarKind, DebugScalarRegistration, DebugScalarType,
        ErasedCuStampedData, ErasedCuStampedDataSet, MatchingTasks, Metadata, ObservedWriter,
        PayloadSchemas, TaskOutputSpec, UnifiedLogType, WriteStream, abort_observed_encode,
        begin_observed_encode, finish_observed_encode, observed_encode_bytes,
        record_observed_encode_bytes, with_cause,
    };
    #[cfg(feature = "std")]
    pub use cu29_unifiedlog::memmap;
    pub use cu29_unifiedlog::*;
    pub use cu29_value::Value;
    pub use cu29_value::to_value;
    pub use serde_derive::{Deserialize, Serialize};
}

#[cfg(all(test, feature = "std"))]
mod tests {
    use super::prelude::*;
    use std::sync::{Arc, Mutex, OnceLock};

    #[derive(Debug)]
    struct CaptureStream;

    impl WriteStream<CuLogEntry> for CaptureStream {
        fn log(&mut self, _obj: &CuLogEntry) -> CuResult<()> {
            Ok(())
        }
    }

    fn logger_test_lock() -> std::sync::MutexGuard<'static, ()> {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        LOCK.get_or_init(|| Mutex::new(()))
            .lock()
            .unwrap_or_else(|poison| poison.into_inner())
    }

    fn capture_one_log<F>(emit: F) -> CuLogEntry
    where
        F: FnOnce(),
    {
        let _guard = logger_test_lock();
        let runtime = LoggerRuntime::init(RobotClock::default(), CaptureStream, None::<NullLog>);
        let captured = Arc::new(Mutex::new(Vec::new()));
        let sink = captured.clone();
        let _listener = scoped_live_log_listener(move |entry, _, _| {
            sink.lock()
                .unwrap_or_else(|poison| poison.into_inner())
                .push(entry.clone());
        });

        emit();

        drop(runtime);

        let entries = captured.lock().unwrap_or_else(|poison| poison.into_inner());
        assert_eq!(entries.len(), 1, "expected exactly one captured log entry");
        entries[0].clone()
    }

    #[test]
    fn explicit_context_logs_capture_task_origin() {
        let mut ctx = CuContext::builder(RobotClock::default())
            .cl_id(77)
            .task_ids(&["task-0"])
            .build();
        ctx.set_current_task(0);

        let entry = capture_one_log(|| {
            debug!(ctx, "task log {}", 7);
        });

        assert_eq!(entry.origin.culistid, Some(77));
        assert_eq!(entry.origin.component_id, Some(0));
        assert_eq!(entry.origin.task_index, Some(0));
    }

    #[test]
    fn explicit_context_logs_capture_bridge_component_origin() {
        let mut ctx = CuContext::builder(RobotClock::default()).cl_id(88).build();
        ctx.set_current_component(5);
        ctx.clear_current_task();

        let entry = capture_one_log(|| {
            info!(ctx, "bridge log {}", 3);
        });

        assert_eq!(entry.origin.culistid, Some(88));
        assert_eq!(entry.origin.component_id, Some(5));
        assert_eq!(entry.origin.task_index, None);
    }

    #[test]
    fn context_free_logs_leave_origin_empty() {
        let entry = capture_one_log(|| {
            warning!("context free {}", 1);
        });

        assert_eq!(entry.origin, CuLogOrigin::default());
    }
}