Actor System
Actors work as an abstraction over data storage and messaging. It allows for all systems (GUI, Programs, etc.) to work together, and rely on the same features. It reduces work of implementation, and all implementations can use the functions.
Features
- Petnames
- OCAP security
- HMAC message verification
Format
#![allow(unused)] fn main() { // Different possible types of actors (more to be added) enum ActorType { GUI(photon::Widget), ProgramInterface, } // Possible states an actor can be in enum ActorState { Receive, Send, Work, Idle, } // Cryptographic keypair struct KeyPair { privkey: u128, pubkey: u128, } // The actor itself struct Actor<D: DataInterface> { petname: Option<String>, // Human-meaningful petname (explored further down) uuid: Uuid, // Unique identifier namespace: Uuid, // Parent namespace of this actor actor_type: ActorType, state: ActorState, keys: Option<KeyPair>, // Cryptographic keypair creation_date: DateTime, modified_date: DateTime, data: Option<D>, // Optional data of the generic D type } impl Actor { fn new(namespace: Uuid, a_type: ActorType) -> Self { Actor { petname: None, uuid: Uuid::new(), namespace: namespace, actor_type: a_type, state: ActorState::Idle, keys: None, creation_date:: now(), modified_date: now(), data: None, } }; } impl KeyPair { async fn generate_keypair(&mut self) -> Self; // Generate a public/private keypair (threaded) fn get_pubkey(&self) -> u128; // Return the keypair of an Actor async fn sign(&self, &[u8]) -> Result<&[u8], Error>; // Sign some data with a private key (threaded) async fn verify_signature(&[u8], u128) -> Result<(), Error>; // Verify signed data (threaded) } trait FilesystemInterface { // Interfacing with the filesystem async fn read(&mut self) -> Result<(), Error>; // Read the data from the disk into the Actor using the Uuid as a search key async fn write(&self) -> Result<(), Error>; // Write the data to the disk using the Uuid as a key } trait DataInterface { // Necessary data functions async fn to_bytes(&self) -> Result<&[u8], Error>; // Convert the data into a byte array } trait MessageInterface { // Sending & receiving messages async fn send_message(&self, MessageType, Uuid) -> Result<(), Error>; // Send a message to a recipient async fn receive_message(&self, Channel) -> Message; // Asynchronously wait for an incoming message, and deal with the first one we get } }
OCAP
TODO
Messages
- postcard for message passing
- Priority Queue for processing multiple messages, while dealing with higher-priority ones first
Messages will be fully modelled so an actor can know exactly what they have to deal with, and what they can send.
Different channels are used to make each one less clogged up, and used only for a specific purpose.
Actors can read from/write to a specific channel, allowing them to ignore the others.
They can then also deal with channels in different ways, maybe deprioritizing the Test
channel.
#![allow(unused)] fn main() { enum Channel { // Channels for sending/receiving messages on Graphics, // Low-latency graphics updates Test, // Designated channel for testing messages Filesystem, // Batch filesystem operations Print, // Printing text Executable, // Executable-related messages } enum ProcessCode { Exit, // Exit the process Save, // Save data Clear, // Clear data Restart, // Restart process } enum MessageType { Ping(String), // Simple test if we can send/recieve a message FilesystemUpdate(gravitas::FileOperation), // We want to operate on the filesystem GraphicsUpdate(photon::GraphicsOperation), // Update a graphics window TextUpdate(String), // Send some text (text mode only) ProcessUpdate(ProcessCode), // Send some info about an operation to be done on the current process. Usually kernel -> exe } struct Message { id: Uuid, // UUID of the message itself m_type: MessageType, // Message type & content priority: u8, // For priority queueing sender: Uuid, // Who is sending the message recipient: Uuid, // Who the message is meant for } }
An example message handling loop may look like this:
#![allow(unused)] fn main() { loop { // Continuously loop through message sending & receiving actor.send_message(MessageType::Ping("hello!".to_string())).await; // Block and await until we can send the test message. match actor.receive_message(&self, Channel::Test).await.m_type { // Match on a message type Ping(s) => println!("We got pinged! {}", s), // Print if we got pinged _ => {}, // Ignore other states } } }