Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Remaining Files and Running

We’ve covered copperconfig.ron and tasks.rs – the two files you’ll edit most. Now let’s look at the three remaining files: main.rs, build.rs, and Cargo.toml. These are mostly boilerplate that you write once and rarely touch.

main.rs – the entry point

pub mod tasks;

use cu29::prelude::*;
use cu29_helpers::basic_copper_setup;
use std::path::{Path, PathBuf};
use std::thread::sleep;
use std::time::Duration;

const PREALLOCATED_STORAGE_SIZE: Option<usize> = Some(1024 * 1024 * 100);

#[copper_runtime(config = "copperconfig.ron")]
struct MyProjectApplication {}

fn main() {
    let logger_path = "logs/my-project.copper";
    if let Some(parent) = Path::new(logger_path).parent() {
        if !parent.exists() {
            std::fs::create_dir_all(parent).expect("Failed to create logs directory");
        }
    }
    let copper_ctx = basic_copper_setup(
        &PathBuf::from(&logger_path),
        PREALLOCATED_STORAGE_SIZE,
        true,
        None,
    )
    .expect("Failed to setup logger.");
    debug!("Logger created at {}.", logger_path);
    debug!("Creating application... ");
    let mut application = MyProjectApplicationBuilder::new()
        .with_context(&copper_ctx)
        .build()
        .expect("Failed to create application.");
    let clock = copper_ctx.clock.clone();
    debug!("Running... starting clock: {}.", clock.now());

    application.run().expect("Failed to run application.");
    debug!("End of program: {}.", clock.now());
    sleep(Duration::from_secs(1));
}

Here’s what each part does:

pub mod tasks; – Brings in your tasks.rs. The task types you defined there (e.g., tasks::MySource) are what copperconfig.ron references.

#[copper_runtime(config = "copperconfig.ron")] – The key macro. At compile time, it reads your config file, parses the task graph, computes a topological execution order, and generates a custom runtime struct with a deterministic scheduler. It also creates a builder struct named MyProjectApplicationBuilder. The struct itself is empty – all the generated code is injected by the macro.

PREALLOCATED_STORAGE_SIZE – How much memory (in bytes) to pre-allocate for the structured log. 100 MB is a reasonable default.

basic_copper_setup() – Initializes the unified logger, the robot clock, and returns a copper_ctx that holds references to both. The parameters are: log file path, pre-allocated size, whether to also print to console, and an optional custom monitor.

MyProjectApplicationBuilder::new().with_context(&copper_ctx).build() – Wires everything together: creates each task by calling their new() constructors, pre-allocates all message buffers, and sets up the scheduler.

application.run() – Starts the deterministic execution loop. Calls start() on all tasks, then enters the cycle loop (preprocess -> process -> postprocess for each task, in topological order), and continues until you stop the application (Ctrl+C).

copper_ctx.clock.clone() – Note that we clone the clock after passing copper_ctx to the builder, to avoid a partial-move error.

build.rs – log index setup

fn main() {
    println!(
        "cargo:rustc-env=LOG_INDEX_DIR={}",
        std::env::var("OUT_DIR").unwrap()
    );
}

This sets the LOG_INDEX_DIR environment variable at compile time. Copper’s logging macros (debug!, info!, etc.) need it to generate a string index for log messages. Without it, you’ll get:

no LOG_INDEX_DIR system variable set, be sure build.rs sets it

You never need to change this file. Just make sure it exists.