use std::cell::Cell as StdCell;
use std::rc::Rc;
use lazily::{Context, Effect, QueueCell};
#[derive(Clone, PartialEq)]
enum Request {
Add { id: u64, n: i64 },
Get { id: u64 },
}
#[derive(Clone, PartialEq, Debug)]
struct Reply {
id: u64,
total: i64,
}
#[derive(Clone)]
struct CounterActor {
mailbox: QueueCell<Request>,
outbox: QueueCell<Reply>,
_drain: Rc<Effect>,
}
impl CounterActor {
fn spawn(ctx: &Context, start: i64) -> Self {
let mailbox: QueueCell<Request> = QueueCell::new(ctx);
let outbox: QueueCell<Reply> = QueueCell::new(ctx);
let total = Rc::new(StdCell::new(start));
let drain = {
let mailbox = mailbox.clone();
let outbox = outbox.clone();
let total = Rc::clone(&total);
ctx.effect(move |ctx| {
while !mailbox.is_empty(ctx) {
let Ok(request) = mailbox.try_pop(ctx.untracked()) else {
break;
};
match request {
Request::Add { n, .. } => {
total.set(total.get() + n);
}
Request::Get { id } => {
let reply = Reply {
id,
total: total.get(),
};
let _ = outbox.try_push(ctx.untracked(), reply);
}
}
}
})
};
Self {
mailbox,
outbox,
_drain: Rc::new(drain),
}
}
fn send(&self, ctx: &Context, request: Request) {
let _ = self.mailbox.try_push(ctx, request);
}
fn get(&self, ctx: &Context, id: u64) -> i64 {
self.send(ctx, Request::Get { id });
loop {
match self.outbox.try_pop(ctx) {
Ok(reply) if reply.id == id => return reply.total,
Ok(other) => {
let _ = self.outbox.try_push(ctx, other);
}
Err(_) => panic!("no reply for request {id}"),
}
}
}
}
fn main() {
let ctx = Context::new();
let actor = CounterActor::spawn(&ctx, 0);
actor.send(&ctx, Request::Add { id: 1, n: 5 });
actor.send(&ctx, Request::Add { id: 2, n: 3 });
let total = actor.get(&ctx, 100);
println!("after Add(5), Add(3): total = {total}"); assert_eq!(total, 8);
actor.send(&ctx, Request::Add { id: 3, n: -2 });
let total = actor.get(&ctx, 101);
println!("after Add(-2): total = {total}"); assert_eq!(total, 6);
println!("actor recipe ok");
}