pub struct ResourceActor<T: ActorEntity> {
receiver: Receiver<ResourceRequest<T>>,
store: HashMap<T::Id, T>,
next_id: u32,
}Expand description
The generic actor that manages a collection of entities.
§Architecture Note
This struct is the “Server” half of the actor. It owns the state (store) and
the receiver end of the channel.
Concurrency Model:
Even though we might have 1000 ResourceActor instances running, each one
processes its own messages sequentially in a loop. This means we don’t need
Mutex or RwLock for the store! The “Actor Model” gives us safety through
exclusive ownership of state within the task.
§ResourceActor
The ResourceActor<T> struct is the server side of the framework. It owns the in‑memory store for a given entity type T: ActorEntity and processes all incoming ResourceRequest<T> messages sequentially. Each actor runs in its own Tokio task, guaranteeing exclusive access to its state without any locking.
- Concurrency model – each actor processes one message at a time, eliminating data races.
- Context injection – a user‑provided
Contextis passed to every lifecycle hook, enabling dependency injection. - Uniform API – works with any entity that implements
ActorEntity, providing a generic CRUD + Action implementation.
§Usage Pattern
The canonical way to create and wire actors is:
- Create: Call
ResourceActor::new()to get theactor(server) andclient(interface). - Wire: Pass dependencies (other clients) into
actor.run(context). - Run: Spawn the actor’s run loop in a background task.
use actor_framework::{ActorEntity, ResourceActor};
use async_trait::async_trait;
// Minimal Entity Definition
#[derive(Clone, Debug)] struct MyEntity { id: u32 }
#[derive(Debug)] struct MyCreate;
#[derive(Debug)] struct MyUpdate;
#[derive(Debug)] enum MyAction {}
#[derive(Debug)] struct MyError(String);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }
}
impl std::error::Error for MyError {}
impl From<String> for MyError { fn from(s: String) -> Self { MyError(s) } }
#[async_trait]
impl ActorEntity for MyEntity {
type Id = u32;
type Create = MyCreate;
type Update = MyUpdate;
type Action = MyAction;
type ActionResult = ();
type Context = (); // No dependencies in this example
type Error = MyError;
fn from_create_params(id: u32, _: MyCreate) -> Result<Self, Self::Error> { Ok(Self { id }) }
async fn on_update(&mut self, _: MyUpdate, _: &()) -> Result<(), Self::Error> { Ok(()) }
async fn handle_action(&mut self, _: MyAction, _: &()) -> Result<(), Self::Error> { Ok(()) }
}
#[tokio::main]
async fn main() {
// 1. Create
let (actor, client) = ResourceActor::<MyEntity>::new(10);
// 2. Wire & Run
tokio::spawn(actor.run(()));
// 3. Use
let _ = client.create(MyCreate).await;
}§Implementation Details
The actor maintains an internal HashMap (store) mapping IDs to entities and a u32 counter (next_id) for ID generation.
§Operations
-
Create:
- Generates a new ID using the internal
next_idcounter (incrementing it). - Converts the
u32ID toT::Id. - Calls
T::from_create_paramsto instantiate the entity. - Calls the
on_createlifecycle hook. - Inserts the new entity into the
store. - Returns the new ID.
- Generates a new ID using the internal
-
Get:
- Looks up the entity in the
storeby ID. - Returns a clone of the entity if found, or
None.
- Looks up the entity in the
-
Update:
- Looks up the entity in the
store(mutable access). - Calls the
on_updatelifecycle hook with the update DTO. - The entity modifies its own state within the hook.
- Returns the updated entity state.
- Looks up the entity in the
-
Delete:
- Looks up the entity in the
store. - Calls the
on_deletelifecycle hook. - Removes the entity from the
store.
- Looks up the entity in the
-
Action:
- Looks up the entity in the
store(mutable access). - Calls the
handle_actionhook with the custom action enum. - Returns the result of the action.
- Looks up the entity in the
Fields§
§receiver: Receiver<ResourceRequest<T>>§store: HashMap<T::Id, T>§next_id: u32Implementations§
Source§impl<T: ActorEntity> ResourceActor<T>
impl<T: ActorEntity> ResourceActor<T>
Sourcepub fn new(buffer_size: usize) -> (Self, ResourceClient<T>)
pub fn new(buffer_size: usize) -> (Self, ResourceClient<T>)
Creates a new ResourceActor and its associated ResourceClient.
§Arguments
buffer_size- The capacity of the MPSC channel. If the channel is full, calls to the client will wait until there is space.
§Returns
A tuple containing:
- The
ResourceActorinstance (the server), which must be run via.run(). - The
ResourceClientinstance, which can be cloned and shared to send requests.
Sourcepub async fn run(self, context: T::Context)
pub async fn run(self, context: T::Context)
Runs the actor’s event loop, processing messages until the channel closes.
§Context Injection
The context argument is injected into every entity hook. This allows entities
to access external dependencies (like other clients) that were created after
the actor was instantiated but before the loop started.