init
Skeletal actor framework. Somewhat unweildy, needs a message box, a better runtime, and different channels. However, hello world example works
This commit is contained in:
commit
120ba0ec71
7 changed files with 260 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
104
Cargo.lock
generated
Normal file
104
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swactor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.111"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tokio-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "swactor"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.48.0", features = ["rt", "macros"] }
|
||||
tokio-util = "0.7.17"
|
||||
2
README.md
Normal file
2
README.md
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# about
|
||||
Small wasm-compatible actor library
|
||||
46
examples/hello.rs
Normal file
46
examples/hello.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
use swactor::Actor;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
pub enum GreeterMessage {
|
||||
Name(String),
|
||||
}
|
||||
|
||||
pub enum GreeterResponse {
|
||||
Hello(String),
|
||||
}
|
||||
|
||||
pub struct Greeter;
|
||||
|
||||
impl Actor for Greeter {
|
||||
type Message = GreeterMessage;
|
||||
type Response = GreeterResponse;
|
||||
|
||||
fn handle_message(&self, msg: Self::Message, tx: oneshot::Sender<Self::Response>) {
|
||||
let rep = match msg {
|
||||
GreeterMessage::Name(name) => GreeterResponse::Hello(format!("Hello, {name}!")),
|
||||
};
|
||||
|
||||
if let Err(_) = tx.send(rep) {
|
||||
// Greeter is not responsible for a dropped Receiver
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.build()
|
||||
.expect("failed to build runtime");
|
||||
let greeter = Greeter::spawn(Greeter, &rt);
|
||||
|
||||
let response = rt
|
||||
.block_on(async move {
|
||||
greeter
|
||||
.send(GreeterMessage::Name("world".to_string()))
|
||||
.await
|
||||
})
|
||||
.expect("failed to get respose");
|
||||
|
||||
match response {
|
||||
GreeterResponse::Hello(hello) => println!("{hello}"),
|
||||
}
|
||||
}
|
||||
5
src/error.rs
Normal file
5
src/error.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
pub fn convert_err<E: std::fmt::Debug>(e: E) -> Error {
|
||||
format!("{e:?}").into()
|
||||
}
|
||||
91
src/lib.rs
Normal file
91
src/lib.rs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
pub mod error;
|
||||
|
||||
/// Public export as the oneshot channel is in the `Actor` trait signature
|
||||
pub use tokio::sync::oneshot;
|
||||
|
||||
use tokio::{
|
||||
sync::{mpsc},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::error::{Result, convert_err};
|
||||
|
||||
|
||||
const DEFAULT_CHANNEL_SIZE: usize = 100;
|
||||
|
||||
/// Combined 'JoinHandle' to await the actor process and 'Sender' for communication
|
||||
pub struct Handle<A>
|
||||
where
|
||||
A: Actor,
|
||||
{
|
||||
cancel_token: CancellationToken,
|
||||
tx: mpsc::Sender<(A::Message, oneshot::Sender<A::Response>)>,
|
||||
_handle: JoinHandle<Result<()>>,
|
||||
// to prevent accidental swaps, strongly type the handle
|
||||
_type: std::marker::PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<A: Actor> Handle<A> {
|
||||
|
||||
/// Send a message to the spawned `Actor` task and get a response corresponding to the `Actor::Response` type
|
||||
pub async fn send(&self, msg: A::Message) -> Result<A::Response> {
|
||||
let (tx, rx) = oneshot::channel::<A::Response>();
|
||||
self.tx.send((msg, tx)).await.map_err(convert_err)?;
|
||||
|
||||
rx.await.map_err(convert_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Actor> Drop for Handle<A> {
|
||||
fn drop(&mut self) {
|
||||
self.cancel_token.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/// Primary trait defining an `Actor` capable of receiving, processing, and transmitting messages
|
||||
pub trait Actor: Send + Sized + Unpin + 'static {
|
||||
/// The type for messages received by this `Actor`
|
||||
type Message: Send;
|
||||
/// The type for responses given by this actor when called from `Handle::send(..)`
|
||||
type Response: Send;
|
||||
|
||||
/// Inner method that defines actor behavior
|
||||
fn handle_message(&self, msg: Self::Message, tx: oneshot::Sender<Self::Response>);
|
||||
|
||||
/// Spawns the `Actor` utilizing the given runtime context
|
||||
/// Only `tokio` runtime is accepted for now
|
||||
fn spawn(self, ctx: &tokio::runtime::Runtime) -> Handle<Self> {
|
||||
let cancel_token = CancellationToken::new();
|
||||
let cancel = cancel_token.clone();
|
||||
|
||||
let (tx, mut rx) =
|
||||
mpsc::channel::<(Self::Message, oneshot::Sender<Self::Response>)>(DEFAULT_CHANNEL_SIZE);
|
||||
let handle = ctx.spawn(async move {
|
||||
let mut res = Ok(());
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => {
|
||||
break;
|
||||
},
|
||||
|
||||
msg = rx.recv() => {
|
||||
match msg {
|
||||
Some(m) => { self.handle_message(m.0, m.1); },
|
||||
None => {res = Err(format!("Sender handle was dropped without calling cancel!").into()); break; },
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
res
|
||||
});
|
||||
|
||||
Handle {
|
||||
cancel_token,
|
||||
_handle: handle,
|
||||
tx,
|
||||
_type: std::marker::PhantomData::<Self>,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue