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

Getting started

This document describes how to create and run a minimal Veecle OS application.

Setting up a package

Create an empty package with cargo new and change to the package directory:

cargo new foo
cd foo

By structuring Veecle OS programs in a specific way, you can write Veecle OS programs that run on multiple platforms. Because this example runs only on platforms that provide the Rust std library, the example uses a smaller structure that is not suitable for Veecle OS programs that run on multiple platforms. (Refer to other examples to learn the structure for Veecle OS programs that can run on multiple platforms.)

Run the following commands to add the Veecle OS framework crate with the std operating system abstraction layer (OSAL):

cargo add veecle-os --features osal-std

Writing Veecle OS code

Veecle OS programs contain actors. Actors read inputs and write outputs using runtime channels. The runtime stores marked Rust types. Actors run concurrently by using async programming.

This example contains two actors:

  • sender_actor writes a Value, then loops.
  • receiver_actor waits to read a Value, prints the Value, then loops.

Defining the storable types

First, define the Value type by copying the following code into src/main.rs:

//! Getting started example.
use core::convert::Infallible;
use core::fmt::Debug;

use veecle_os::runtime::{InitializedReader, Storable, Writer};

#[derive(Debug, Clone, PartialEq, Eq, Default, Storable)]
#[storable(data_type = "u32")]
pub struct Value;

By implementing Storable with the data_type u32, Value becomes an identifier for readers and writers. Any reader or writer that uses Value will read or write the data_type, in this case u32. The data_type must implement the Debug trait so that logs can show its debug representation.

Storable can also be implemented manually. See the veecle_os::runtime::Storable documentation for more information.

Implementing actors

You can implement actors in multiple ways. This example uses the actor macro, that defines an actor from a Rust function.

In this example, the actors use InitializedReaders and Writers to communicate with other actors.

Copy the following actors into src/main.rs:

/// An actor that writes `Value { i++ }` continuously.
#[veecle_os::runtime::actor]
async fn sender_actor(mut writer: Writer<'_, Value>) -> Infallible {
    let mut value = 0;
    loop {
        println!("[sender] Sending {:?}", value);
        writer.write(value).await;
        value += 1;
    }
}
/// An actor that reads `Value` and terminates when the value is 5.
#[veecle_os::runtime::actor]
async fn receiver_actor(mut reader: InitializedReader<'_, Value>) -> Infallible {
    loop {
        println!("[receiver] Waiting for value");
        reader.wait_for_update().await.read(|value| {
            println!("[receiver] Received: {value:?}");
            if *value == 5 {
                println!("[receiver] Exiting because value is 5");
                // Actors should never terminate. This program ends so it always generates the same short output that is
                // easy to verify.
                std::process::exit(0);
            }
        });
    }
}

receiver_actor uses std::process::exit to exit the program when it receives 5.

Actors should never terminate. In case of unrecoverable errors, actors should return an error. This program ends so it always generates the same short output that is easy to verify.

Implementing the Veecle OS program

Currently, Veecle OS programs have an entry point implemented according to the platform that the Veecle OS program runs on. std Veecle OS programs require Tokio as their async runtime. This is set up by the veecle_os::osal::std::main macro.

Copy the following code into src/main.rs to implement a Veecle OS program entry point:

#[veecle_os::osal::std::main]
async fn main() {
    veecle_os::runtime::execute! {
        store: [Value],
        actors: [ReceiverActor, SenderActor],
    }
    .await;
}

The main function uses veecle_os::runtime::execute! to create and execute a runtime instance that knows about the Value datatype and both actors.

Running the Veecle OS program

(See the end of this document for a complete listing of src/main.rs.)

To execute the Veecle OS program, run the following command:

cargo run

Appendix: the complete Veecle OS program

//! Getting started example.
use core::convert::Infallible;
use core::fmt::Debug;

use veecle_os::runtime::{InitializedReader, Storable, Writer};

#[derive(Debug, Clone, PartialEq, Eq, Default, Storable)]
#[storable(data_type = "u32")]
pub struct Value;

/// An actor that writes `Value { i++ }` continuously.
#[veecle_os::runtime::actor]
async fn sender_actor(mut writer: Writer<'_, Value>) -> Infallible {
    let mut value = 0;
    loop {
        println!("[sender] Sending {:?}", value);
        writer.write(value).await;
        value += 1;
    }
}

/// An actor that reads `Value` and terminates when the value is 5.
#[veecle_os::runtime::actor]
async fn receiver_actor(mut reader: InitializedReader<'_, Value>) -> Infallible {
    loop {
        println!("[receiver] Waiting for value");
        reader.wait_for_update().await.read(|value| {
            println!("[receiver] Received: {value:?}");
            if *value == 5 {
                println!("[receiver] Exiting because value is 5");
                // Actors should never terminate. This program ends so it always generates the same short output that is
                // easy to verify.
                std::process::exit(0);
            }
        });
    }
}

#[veecle_os::osal::std::main]
async fn main() {
    veecle_os::runtime::execute! {
        store: [Value],
        actors: [ReceiverActor, SenderActor],
    }
    .await;
}