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 aValue
, then loops.receiver_actor
waits to read aValue
, prints theValue
, 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 InitializedReader
s and Writer
s 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;
}