2026-02-06 11:25:37 +00:00
|
|
|
use std::any::Any;
|
|
|
|
|
|
2026-02-07 10:36:45 +00:00
|
|
|
use crate::Error;
|
2026-01-25 13:38:34 +00:00
|
|
|
|
|
|
|
|
/// The primary trait defining data that can be passed to and from actor processes
|
|
|
|
|
pub trait Message: 'static + Sized + Clone + Send + Sync {}
|
|
|
|
|
impl<T: 'static + Sized + Clone + Send + Sync> Message for T {}
|
|
|
|
|
|
|
|
|
|
pub trait ActorInterface: 'static + Send {
|
|
|
|
|
type Incoming: Message;
|
|
|
|
|
type Response: Message;
|
2026-02-06 11:25:37 +00:00
|
|
|
fn handle(&mut self, ctx: &Ctx, msg: Self::Incoming);
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A unique address for this actor. 32 bytes is overkill for a small application,
|
|
|
|
|
/// but most systems are powerful, and this allows us to create a global map of
|
|
|
|
|
/// actor processes in the future, without worrying about collision.
|
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
2026-02-08 16:18:39 +00:00
|
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
2026-01-25 13:38:34 +00:00
|
|
|
pub struct ActorAddress(pub [u8; 32]);
|
2026-02-08 16:18:39 +00:00
|
|
|
|
|
|
|
|
impl std::fmt::Display for ActorAddress {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
for b in &self.0[..8] {
|
|
|
|
|
write!(f, "{:02x}", b)?;
|
|
|
|
|
}
|
|
|
|
|
write!(f, "\u{2026}")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-25 13:38:34 +00:00
|
|
|
impl ActorAddress {
|
|
|
|
|
pub fn new_random() -> Self {
|
|
|
|
|
let mut bytes = [0u8; 32];
|
2026-02-06 14:45:19 +00:00
|
|
|
crate::get_random(&mut bytes);
|
2026-01-25 13:38:34 +00:00
|
|
|
Self(bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 14:45:19 +00:00
|
|
|
/// The actor process as represented in the Runtime — thin wrapper around user state.
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub struct Actor<A: ActorInterface>(A);
|
2026-01-25 13:38:34 +00:00
|
|
|
|
|
|
|
|
impl<A: ActorInterface> Actor<A> {
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub fn new(inner: A) -> Self {
|
2026-02-06 14:45:19 +00:00
|
|
|
Self(inner)
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 14:45:19 +00:00
|
|
|
/// Trait for type-erased actors — single-message handler.
|
2026-02-10 07:35:58 +00:00
|
|
|
///
|
2026-02-11 15:23:26 +00:00
|
|
|
/// Returns `Some(type_name)` if handled, `None` on type mismatch.
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub trait AnyActor: Send {
|
2026-02-11 15:23:26 +00:00
|
|
|
fn handle_any(&mut self, ctx: &Ctx, msg: Box<dyn Any + Send>) -> Option<&'static str>;
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<A> AnyActor for Actor<A>
|
|
|
|
|
where
|
|
|
|
|
A: ActorInterface,
|
|
|
|
|
{
|
2026-02-11 15:23:26 +00:00
|
|
|
fn handle_any(&mut self, ctx: &Ctx, msg: Box<dyn Any + Send>) -> Option<&'static str> {
|
2026-02-06 11:25:37 +00:00
|
|
|
if let Ok(typed) = msg.downcast::<A::Incoming>() {
|
2026-02-06 14:45:19 +00:00
|
|
|
self.0.handle(ctx, *typed);
|
2026-02-11 15:23:26 +00:00
|
|
|
Some(std::any::type_name::<A::Incoming>())
|
2026-02-10 07:35:58 +00:00
|
|
|
} else {
|
2026-02-11 15:23:26 +00:00
|
|
|
None
|
2026-02-06 11:25:37 +00:00
|
|
|
}
|
2026-01-25 13:38:34 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-07 10:36:45 +00:00
|
|
|
|
|
|
|
|
/// Object-safe inner trait for sending type-erased messages.
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub trait ContextInner {
|
2026-02-07 10:36:45 +00:00
|
|
|
fn send_any(&self, addr: ActorAddress, msg: Box<dyn Any + Send>) -> Result<(), Error>;
|
2026-02-10 07:35:58 +00:00
|
|
|
fn spawn_any(&self, addr: ActorAddress, actor: Box<dyn AnyActor>);
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Actor syscall interface — passed to `ActorInterface::handle()`.
|
|
|
|
|
///
|
|
|
|
|
/// Wraps a `&dyn ContextInner` to solve the object-safety problem while
|
|
|
|
|
/// providing a typed public API.
|
|
|
|
|
pub struct Ctx<'a> {
|
|
|
|
|
inner: &'a dyn ContextInner,
|
|
|
|
|
self_addr: ActorAddress,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Ctx<'a> {
|
|
|
|
|
pub(crate) fn new(inner: &'a dyn ContextInner, self_addr: ActorAddress) -> Self {
|
|
|
|
|
Self { inner, self_addr }
|
|
|
|
|
}
|
|
|
|
|
|
refactor: repack external bindings into their own crates (#19)
Split the monolithic crate into a Cargo workspace with the Python and Wasm bindings as separate member crates.
- Cargo.toml: declare a `[workspace]` with members `.`/`crates/swactor-python`/`crates/swactor-wasm`, remove the `python` feature and pyo3 dependency, and change root crate-type from `["cdylib","rlib"]` to `["rlib"]`
- crates/swactor-python: new cdylib crate re-exporting the PyO3 bindings (Runtime/RuntimeConfig/RuntimeHandle/Inbox/Ctx/ActorAddress/RuntimeStats), depending on `swactor` + pyo3; pyproject.toml and uv.lock relocated here from the root
- crates/swactor-wasm: new cdylib crate moved from top-level `wasm/`, depending on `swactor` with `no_random` features
- src/actor.rs: widen `Actor::new`, `AnyActor`, `ContextInner`, and `Ctx::raw_inner` to `pub` so the separate binding crates can drive the runtime
- src/lib.rs: delete the in-tree `python` module and `#[pymodule]`, and gate the `no_random` RNG behind `all(feature = "no_random", not(feature = "getrandom"))`
- tools/: relocate package.json/package-lock.json; drop the now-duplicate `wasm/Cargo.lock`
Signed-off-by: Zachery Aaron Shores-Chmielewski <zacheryasc@gmail.com>
2026-02-07 17:39:02 +00:00
|
|
|
pub fn raw_inner(&self) -> &dyn ContextInner {
|
2026-02-07 10:36:45 +00:00
|
|
|
self.inner
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns the address of the actor currently being ticked.
|
|
|
|
|
pub fn self_addr(&self) -> ActorAddress {
|
|
|
|
|
self.self_addr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Send a typed message to an actor address.
|
|
|
|
|
pub fn send<M: Message>(&self, addr: ActorAddress, msg: M) -> Result<(), Error> {
|
|
|
|
|
self.inner.send_any(addr, Box::new(msg))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Spawn a new actor, returning its address.
|
|
|
|
|
pub fn spawn<A: ActorInterface>(&self, actor: A) -> Result<ActorAddress, Error> {
|
|
|
|
|
let addr = ActorAddress::new_random();
|
|
|
|
|
let boxed: Box<dyn AnyActor> = Box::new(Actor::new(actor));
|
2026-02-10 07:35:58 +00:00
|
|
|
self.inner.spawn_any(addr, boxed);
|
2026-02-07 10:36:45 +00:00
|
|
|
Ok(addr)
|
|
|
|
|
}
|
|
|
|
|
}
|