Skip to content

Commit d03fc35

Browse files
authored
Merge pull request #27 from copper-project/gbin/doc-drift
doc drift, mainly about ctx
2 parents b7e5925 + 9669d09 commit d03fc35

14 files changed

Lines changed: 107 additions & 89 deletions

book/src/ch03-setup.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,11 @@ my_project/
4848
├── build.rs # Build script (required by Copper logging)
4949
├── Cargo.toml # Dependencies
5050
├── copperconfig.ron # Task graph definition
51+
├── justfile # Helper commands (logreader, replay, DAG rendering)
5152
└── src/
5253
├── main.rs # Runtime entry point
54+
├── logreader.rs # Offline log export utility
55+
├── resim.rs # Replay / remote-debug entry point
5356
└── tasks.rs # Your task implementations
5457
```
5558

book/src/ch04-project-structure.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ my_project/
88
├── build.rs # Build script (sets up logging index)
99
├── Cargo.toml # Dependencies and project metadata
1010
├── copperconfig.ron # Task graph definition
11+
├── justfile # Helper commands for logs, replay, and DAG rendering
1112
└── src/
1213
├── main.rs # Runtime entry point
1314
├── tasks.rs # Your task implementations
14-
└── logreader.rs # (optional) Log export utility
15+
├── logreader.rs # Log export utility
16+
└── resim.rs # Replay / remote-debug entry point
1517
```
1618

1719
## Which files do I actually work on?
@@ -30,6 +32,8 @@ The rest is scaffolding that you set up once and rarely change:
3032
| `main.rs` | Boilerplate: create logger, build runtime, call `run()` | Rarely |
3133
| `build.rs` | Sets an env var for Copper's logging macros | Never |
3234
| `logreader.rs` | CLI tool to decode and export Copper's binary logs | Rarely |
35+
| `resim.rs` | Replay target for deterministic re-simulation and remote debug | Rarely |
36+
| `justfile` | Shortcuts for common commands like `just log` and `just dag` | Occasionally |
3337
| `Cargo.toml` | Dependencies | When adding new hardware driver crates |
3438

3539
## The mental model

book/src/ch06-messages.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ then emit `NoChange` on later cycles until something changes:
8181
```rust
8282
type Output<'m> = output_msg!(CuLatchedStateUpdate<CameraCalibration>);
8383

84-
fn process(&mut self, _clock: &RobotClock, output: &mut Self::Output<'_>) -> CuResult<()> {
84+
fn process(&mut self, _ctx: &CuContext, output: &mut Self::Output<'_>) -> CuResult<()> {
8585
if let Some(calibration) = self.pending_calibration.take() {
8686
output.set_payload(CuLatchedStateUpdate::Set(calibration));
8787
} else {
@@ -102,7 +102,7 @@ pub struct DepthProjector {
102102
calibration: CuLatchedState<CameraCalibration>,
103103
}
104104

105-
fn process(&mut self, _clock: &RobotClock, input: &Self::Input<'_>,
105+
fn process(&mut self, _ctx: &CuContext, input: &Self::Input<'_>,
106106
output: &mut Self::Output<'_>) -> CuResult<()> {
107107
if let Some(update) = input.payload() {
108108
self.calibration.update(update);

book/src/ch07-task-traits.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ impl CuSrcTask for MySource {
3838
Ok(Self {})
3939
}
4040

41-
fn process(&mut self, _clock: &RobotClock, output: &mut Self::Output<'_>) -> CuResult<()> {
41+
fn process(&mut self, _ctx: &CuContext, output: &mut Self::Output<'_>) -> CuResult<()> {
4242
output.set_payload(MyPayload { value: 42 });
4343
Ok(())
4444
}
@@ -64,7 +64,7 @@ impl CuTask for MyTask {
6464

6565
fn process(
6666
&mut self,
67-
_clock: &RobotClock,
67+
_ctx: &CuContext,
6868
input: &Self::Input<'_>,
6969
output: &mut Self::Output<'_>,
7070
) -> CuResult<()> {
@@ -91,7 +91,7 @@ impl CuSinkTask for MySink {
9191
Ok(Self {})
9292
}
9393

94-
fn process(&mut self, _clock: &RobotClock, input: &Self::Input<'_>) -> CuResult<()> {
94+
fn process(&mut self, _ctx: &CuContext, input: &Self::Input<'_>) -> CuResult<()> {
9595
debug!("Sink Received message: {}", input.payload().unwrap().value);
9696
Ok(())
9797
}
@@ -109,7 +109,7 @@ use serde::{Deserialize, Serialize};
109109
```
110110

111111
- **`cu29::prelude::*`** -- Brings in everything you need from Copper: task traits,
112-
`RobotClock`, `ComponentConfig`, `CuResult`, `Freezable`, `Reflect`, the `input_msg!` /
112+
`CuContext`, `ComponentConfig`, `CuResult`, `Freezable`, `Reflect`, the `input_msg!` /
113113
`output_msg!` macros, and the `debug!` logging macro.
114114
- **`bincode`** and **`serde`** -- For the serialization derives on `MyPayload` (covered in
115115
the [Defining Messages](./ch06-messages.md) chapter).
@@ -158,7 +158,7 @@ impl CuSrcTask for MySource {
158158
Ok(Self {})
159159
}
160160

161-
fn process(&mut self, _clock: &RobotClock, output: &mut Self::Output<'_>) -> CuResult<()> {
161+
fn process(&mut self, _ctx: &CuContext, output: &mut Self::Output<'_>) -> CuResult<()> {
162162
output.set_payload(MyPayload { value: 42 });
163163
Ok(())
164164
}
@@ -196,7 +196,7 @@ impl CuTask for MyTask {
196196

197197
fn process(
198198
&mut self,
199-
_clock: &RobotClock,
199+
_ctx: &CuContext,
200200
input: &Self::Input<'_>,
201201
output: &mut Self::Output<'_>,
202202
) -> CuResult<()> {
@@ -235,7 +235,7 @@ impl CuSinkTask for MySink {
235235
Ok(Self {})
236236
}
237237

238-
fn process(&mut self, _clock: &RobotClock, input: &Self::Input<'_>) -> CuResult<()> {
238+
fn process(&mut self, _ctx: &CuContext, input: &Self::Input<'_>) -> CuResult<()> {
239239
debug!("Sink Received message: {}", input.payload().unwrap().value);
240240
Ok(())
241241
}

book/src/ch08-task-anatomy.md

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type Input<'m> = input_msg!(SensorA, SensorB);
3535
In `process()`, the `input` parameter becomes a tuple that you can destructure:
3636

3737
```rust
38-
fn process(&mut self, _clock: &RobotClock, input: &Self::Input<'_>, ...) -> CuResult<()> {
38+
fn process(&mut self, _ctx: &CuContext, input: &Self::Input<'_>, ...) -> CuResult<()> {
3939
let (sensor_a_msg, sensor_b_msg) = *input;
4040
// Use sensor_a_msg.payload() and sensor_b_msg.payload()
4141
Ok(())
@@ -86,8 +86,12 @@ You can read the values in `new()`:
8686
```rust
8787
fn new(config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self> {
8888
let cfg = config.ok_or("MotorDriver requires a config block")?;
89-
let pin: u8 = cfg.get("pin").unwrap().clone().into();
90-
let max_speed: f64 = cfg.get("max_speed").unwrap().clone().into();
89+
let pin: u8 = cfg
90+
.get::<u8>("pin")?
91+
.ok_or_else(|| CuError::from("MotorDriver missing `pin`"))?;
92+
let max_speed: f64 = cfg
93+
.get::<f64>("max_speed")?
94+
.ok_or_else(|| CuError::from("MotorDriver missing `max_speed`"))?;
9195
Ok(Self { pin, max_speed })
9296
}
9397
```
@@ -101,14 +105,14 @@ depends on the trait:
101105

102106
| Trait | Signature |
103107
|---|---|
104-
| `CuSrcTask` | `process(&mut self, clock, output)` |
105-
| `CuTask` | `process(&mut self, clock, input, output)` |
106-
| `CuSinkTask` | `process(&mut self, clock, input)` |
108+
| `CuSrcTask` | `process(&mut self, ctx, output)` |
109+
| `CuTask` | `process(&mut self, ctx, input, output)` |
110+
| `CuSinkTask` | `process(&mut self, ctx, input)` |
107111

108112
In our simple example, the source ignores most parameters and just writes a value:
109113

110114
```rust
111-
fn process(&mut self, _clock: &RobotClock, output: &mut Self::Output<'_>) -> CuResult<()> {
115+
fn process(&mut self, _ctx: &CuContext, output: &mut Self::Output<'_>) -> CuResult<()> {
112116
output.set_payload(MyPayload { value: 42 });
113117
Ok(())
114118
}
@@ -143,32 +147,35 @@ arrive. This is a good fit for calibration bundles, static transforms, lookup ta
143147
other low-rate metadata that should not be rebuilt every cycle. The full producer and
144148
consumer pattern is covered in [Defining Messages](./ch06-messages.md#latched-state-updates).
145149

146-
#### The clock: `&RobotClock`
150+
#### The context: `&CuContext`
147151

148-
Every `process()` receives a `clock` parameter. This is Copper's **only clock** -- a
149-
monotonic clock that starts at zero when your program launches and ticks forward in
150-
nanoseconds. There is no UTC or wall-clock in Copper; tasks should never call
151-
`std::time::SystemTime::now()` or `std::time::Instant::now()`.
152+
Every lifecycle callback receives a `ctx` parameter of type `&CuContext`. It dereferences
153+
to Copper's monotonic `RobotClock`, so `ctx.now()` gives you the current Copper time, and
154+
it also carries runtime metadata such as `ctx.cl_id()`, `ctx.instance_id()`, and the
155+
currently executing task ID.
152156

153-
`clock.now()` returns a `CuTime` (a `u64` of nanoseconds since startup). In our simple
154-
project we prefix the parameter with `_` because we don't use it. But on a real robot
155-
you'd use it like this:
157+
`ctx.now()` returns a `CuTime` (a `u64` of nanoseconds since startup). There is no UTC or
158+
wall-clock in Copper; tasks should never call `std::time::SystemTime::now()` or
159+
`std::time::Instant::now()`.
160+
161+
In our simple project we prefix the parameter with `_` because we don't use it. But on a
162+
real robot you'd use it like this:
156163

157164
**Timestamp your output** (typical for source tasks):
158165

159166
```rust
160-
fn process(&mut self, clock: &RobotClock, output: &mut Self::Output<'_>) -> CuResult<()> {
167+
fn process(&mut self, ctx: &CuContext, output: &mut Self::Output<'_>) -> CuResult<()> {
161168
output.set_payload(MyPayload { value: read_sensor() });
162-
output.tov = Tov::Time(clock.now());
169+
output.tov = Tov::Time(ctx.now());
163170
Ok(())
164171
}
165172
```
166173

167174
**Compute a time delta** (e.g., for a PID controller):
168175

169176
```rust
170-
fn process(&mut self, clock: &RobotClock, input: &Self::Input<'_>, output: &mut Self::Output<'_>) -> CuResult<()> {
171-
let now = clock.now();
177+
fn process(&mut self, ctx: &CuContext, input: &Self::Input<'_>, output: &mut Self::Output<'_>) -> CuResult<()> {
178+
let now = ctx.now();
172179
let dt = now - self.last_time; // CuDuration in nanoseconds
173180
self.last_time = now;
174181

@@ -182,9 +189,9 @@ fn process(&mut self, clock: &RobotClock, input: &Self::Input<'_>, output: &mut
182189
**Detect a timeout**:
183190

184191
```rust
185-
fn process(&mut self, clock: &RobotClock, input: &Self::Input<'_>) -> CuResult<()> {
192+
fn process(&mut self, ctx: &CuContext, input: &Self::Input<'_>) -> CuResult<()> {
186193
if input.payload().is_none() {
187-
let elapsed = clock.now() - self.last_seen;
194+
let elapsed = ctx.now() - self.last_seen;
188195
if elapsed > CuDuration::from_millis(100) {
189196
debug!("Sensor timeout! No data for {}ms", elapsed.as_millis());
190197
}
@@ -196,8 +203,8 @@ fn process(&mut self, clock: &RobotClock, input: &Self::Input<'_>) -> CuResult<(
196203
**Why not use the system clock?** Because Copper supports **deterministic replay**. When
197204
you replay a recorded run, the runtime feeds your tasks the exact same clock values from
198205
the original recording. If you used `std::time`, the replay would have different
199-
timestamps and your tasks would behave differently. With `RobotClock`, same clock + same
200-
inputs = same outputs, every time.
206+
timestamps and your tasks would behave differently. With `CuContext` (and its underlying
207+
`RobotClock`), same clock + same inputs = same outputs, every time.
201208

202209
### The golden rule of `process()`
203210

book/src/ch09-task-lifecycle.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ to run your control loop on slightly stale data than to miss a deadline. Your `p
4848
should handle this gracefully:
4949

5050
```rust
51-
fn preprocess(&mut self, _clock: &RobotClock) -> CuResult<()> {
51+
fn preprocess(&mut self, _ctx: &CuContext) -> CuResult<()> {
5252
// Heavy work on the best-effort thread -- might be slow
5353
self.decoded_image = Some(decode_jpeg(&self.raw_buffer));
5454
Ok(())
5555
}
5656

57-
fn process(&mut self, _clock: &RobotClock, input: &Self::Input<'_>,
57+
fn process(&mut self, _ctx: &CuContext, input: &Self::Input<'_>,
5858
output: &mut Self::Output<'_>) -> CuResult<()> {
5959
// Use whatever is ready. If preprocess was late, decoded_image
6060
// still holds the previous cycle's result (or None on first cycle).
@@ -110,17 +110,17 @@ impl CuTask for MyTask {
110110
}
111111

112112
// Required: core logic
113-
fn process(&mut self, _clock: &RobotClock, input: &Self::Input<'_>,
113+
fn process(&mut self, _ctx: &CuContext, input: &Self::Input<'_>,
114114
output: &mut Self::Output<'_>) -> CuResult<()> {
115115
// your core logic
116116
Ok(())
117117
}
118118

119119
// Optionally implement any of these:
120-
// fn start(&mut self, clock: &RobotClock) -> CuResult<()> { ... }
121-
// fn stop(&mut self, clock: &RobotClock) -> CuResult<()> { ... }
122-
// fn preprocess(&mut self, clock: &RobotClock) -> CuResult<()> { ... }
123-
// fn postprocess(&mut self, clock: &RobotClock) -> CuResult<()> { ... }
120+
// fn start(&mut self, ctx: &CuContext) -> CuResult<()> { ... }
121+
// fn stop(&mut self, ctx: &CuContext) -> CuResult<()> { ... }
122+
// fn preprocess(&mut self, ctx: &CuContext) -> CuResult<()> { ... }
123+
// fn postprocess(&mut self, ctx: &CuContext) -> CuResult<()> { ... }
124124
}
125125
```
126126

book/src/ch10-main-rs.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# The Remaining Files and Running
22

33
We've covered `copperconfig.ron` and `tasks.rs` -- the two files you'll edit most. Now
4-
let's look at the three remaining files: `main.rs`, `build.rs`, and `Cargo.toml`. These
5-
are mostly boilerplate that you write once and rarely touch.
4+
let's look at `main.rs`, `build.rs`, and the relevant `Cargo.toml` bits. These are mostly
5+
boilerplate that you write once and rarely touch. We'll come back to `logreader.rs` in
6+
Chapter 13 and replay via `resim.rs` later.
67

78
## main.rs -- the entry point
89

book/src/ch11-frequency.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ Let's modify `MySource` in `tasks.rs` to print the time at each cycle. Replace t
1313
`process()` method:
1414

1515
```rust
16-
fn process(&mut self, clock: &RobotClock, output: &mut Self::Output<'_>) -> CuResult<()> {
17-
debug!("Source at {}µs", clock.now().as_micros());
16+
fn process(&mut self, ctx: &CuContext, output: &mut Self::Output<'_>) -> CuResult<()> {
17+
debug!("Source at {}µs", ctx.now().as_micros());
1818
output.set_payload(MyPayload { value: 42 });
1919
Ok(())
2020
}

book/src/ch12-monitoring.md

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,12 @@ standard deviation, min, max, and more.
99

1010
## Step 1: Add the dependency
1111

12-
Add `cu-consolemon` to your `Cargo.toml`:
13-
14-
```toml
15-
[dependencies]
16-
cu29 = { git = "https://github.com/copper-project/copper-rs" }
17-
cu-consolemon = { git = "https://github.com/copper-project/copper-rs" }
18-
bincode = { package = "cu-bincode", version = "2.0", default-features = false,
19-
features = ["derive", "alloc"] }
20-
serde = { version = "1", features = ["derive"] }
21-
```
12+
Add `cu-consolemon` to your `Cargo.toml` next to your existing Copper dependencies.
13+
The only new dependency is `cu-consolemon`.
2214

23-
The only new line is `cu-consolemon`.
15+
If your project already uses Copper through `git` or local `path` dependencies, keep that
16+
same source style and add `cu-consolemon` from the same Copper release. If you use
17+
crates.io, use the same published Copper release as your existing `cu29` dependency.
2418

2519
## Step 2: Enable it in copperconfig.ron
2620

@@ -155,4 +149,3 @@ A scrollable view of all `debug!()` log output from your tasks -- the same messa
155149
see without the monitor, but captured inside the TUI. You can scroll through the history
156150
with `hjkl` or arrow keys. The bottom bar also shows keyboard shortcuts: `r` to reset
157151
latency statistics, `q` to quit.
158-

book/src/ch15-workspace.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ my_workspace/
3333
│ └── src/
3434
│ ├── main.rs
3535
│ ├── logreader.rs
36+
│ ├── resim.rs
3637
│ ├── messages.rs
3738
│ └── tasks/
3839
│ ├── mod.rs
@@ -79,7 +80,7 @@ When you add a new application or component, you add it to the `members` list.
7980
## The apps/ directory
8081

8182
This is where your **application crates** live. Each app is a standalone binary that owns
82-
its own runtime configuration, log storage, and logreader.
83+
its own runtime configuration, log storage, logreader, and replay target.
8384

8485
The example app (`cu_example_app`) looks very similar to the `my_project` we built in
8586
earlier chapters, but with two key differences.

0 commit comments

Comments
 (0)