ResourceActor

Struct ResourceActor 

Source
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 Context is 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:

  1. Create: Call ResourceActor::new() to get the actor (server) and client (interface).
  2. Wire: Pass dependencies (other clients) into actor.run(context).
  3. 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:

    1. Generates a new ID using the internal next_id counter (incrementing it).
    2. Converts the u32 ID to T::Id.
    3. Calls T::from_create_params to instantiate the entity.
    4. Calls the on_create lifecycle hook.
    5. Inserts the new entity into the store.
    6. Returns the new ID.
  • Get:

    1. Looks up the entity in the store by ID.
    2. Returns a clone of the entity if found, or None.
  • Update:

    1. Looks up the entity in the store (mutable access).
    2. Calls the on_update lifecycle hook with the update DTO.
    3. The entity modifies its own state within the hook.
    4. Returns the updated entity state.
  • Delete:

    1. Looks up the entity in the store.
    2. Calls the on_delete lifecycle hook.
    3. Removes the entity from the store.
  • Action:

    1. Looks up the entity in the store (mutable access).
    2. Calls the handle_action hook with the custom action enum.
    3. Returns the result of the action.

Fields§

§receiver: Receiver<ResourceRequest<T>>§store: HashMap<T::Id, T>§next_id: u32

Implementations§

Source§

impl<T: ActorEntity> ResourceActor<T>

Source

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:

  1. The ResourceActor instance (the server), which must be run via .run().
  2. The ResourceClient instance, which can be cloned and shared to send requests.
Source

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.

Auto Trait Implementations§

§

impl<T> Freeze for ResourceActor<T>

§

impl<T> RefUnwindSafe for ResourceActor<T>

§

impl<T> Send for ResourceActor<T>

§

impl<T> Sync for ResourceActor<T>

§

impl<T> Unpin for ResourceActor<T>
where <T as ActorEntity>::Id: Unpin, T: Unpin,

§

impl<T> UnwindSafe for ResourceActor<T>
where <T as ActorEntity>::Id: UnwindSafe, T: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more