use std::cell::RefCell;
use std::rc::Rc;
use logicaffeine_runtime::{RtPayload, Task, TaskCtx, TaskStep};
use crate::interpreter::RuntimeValue;
use crate::vm::{Value, Vm, VmBlock, VmStep};
use super::driver::ErrSink;
use super::marshal;
enum OutputMode {
Shared(Rc<RefCell<Vec<String>>>),
Buffered(Vec<String>),
}
pub struct VmTask<'p> {
vm: Vm<'p>,
output: OutputMode,
err_sink: Option<ErrSink>,
}
impl<'p> VmTask<'p> {
pub fn new(vm: Vm<'p>, output: Rc<RefCell<Vec<String>>>, err_sink: Option<ErrSink>) -> Self {
VmTask { vm, output: OutputMode::Shared(output), err_sink }
}
pub fn work_stealing(vm: Vm<'p>, err_sink: Option<ErrSink>) -> Self {
VmTask { vm, output: OutputMode::Buffered(Vec::new()), err_sink }
}
fn block_to_step(&self, req: VmBlock) -> TaskStep<'p> {
match req {
VmBlock::NewChan(cap) => TaskStep::NewChan(cap),
VmBlock::Send(ch, p) => TaskStep::Send(ch, p),
VmBlock::Recv(ch) => TaskStep::Recv(ch),
VmBlock::TrySend(ch, p) => TaskStep::TrySend(ch, p),
VmBlock::TryRecv(ch) => TaskStep::TryRecv(ch),
VmBlock::Close(ch) => TaskStep::Close(ch),
VmBlock::SpawnDesc { func, args, want_handle } => match &self.output {
OutputMode::Shared(sink) => {
let child_vm = self.vm.spawn_task_vm(func, &args);
let child = VmTask::new(child_vm, sink.clone(), self.err_sink.clone());
TaskStep::Spawn(Box::new(child))
}
OutputMode::Buffered(_) => TaskStep::SpawnDesc { func, args, want_handle },
},
VmBlock::Await(t) => TaskStep::Await(t),
VmBlock::Abort(t) => TaskStep::Abort(t),
VmBlock::Select(arms) => TaskStep::Select(arms),
VmBlock::Sleep(d) => TaskStep::Sleep(d),
VmBlock::NetConnect(_)
| VmBlock::NetListen(_)
| VmBlock::NetSend(_, _)
| VmBlock::NetStream(_, _)
| VmBlock::NetAwait(_, _)
| VmBlock::NetMakePeer(_)
| VmBlock::NetSync(_, _) => TaskStep::IoPending,
}
}
}
impl<'p> Task<'p> for VmTask<'p> {
fn poll(&mut self, ctx: &mut TaskCtx) -> TaskStep<'p> {
if let Some(arm) = ctx.selected_arm.take() {
let payload = std::mem::replace(&mut ctx.resumed_with, RtPayload::Nothing);
self.vm.deliver_select(arm, Value::from_runtime(marshal::rebuild(payload)));
} else {
let resume = ctx_to_value(ctx);
self.vm.deliver_resume(resume);
}
let step = self.vm.run_until_block();
let lines = self.vm.drain_lines();
if !lines.is_empty() {
match &mut self.output {
OutputMode::Shared(sink) => sink.borrow_mut().extend(lines),
OutputMode::Buffered(buf) => buf.extend(lines),
}
}
match step {
Ok(VmStep::Done(result)) => {
let payload = marshal::materialize(&result).unwrap_or(RtPayload::Nothing);
TaskStep::Exit(payload)
}
Ok(VmStep::Blocked) => {
let req = self
.vm
.take_pending()
.expect("a blocked VM slice must leave a pending request");
self.block_to_step(req)
}
Ok(VmStep::Paused) => {
unreachable!("the driver uses run_until_block; the debug stepper is never driven here")
}
Err(e) => {
if let Some(sink) = &self.err_sink {
let mut slot = sink.borrow_mut();
if slot.is_none() {
*slot = Some(e);
}
}
TaskStep::Exit(RtPayload::Nothing)
}
}
}
fn take_output(&mut self) -> Vec<String> {
match &mut self.output {
OutputMode::Buffered(buf) => std::mem::take(buf),
OutputMode::Shared(_) => Vec::new(),
}
}
}
fn ctx_to_value(ctx: &mut TaskCtx) -> Value {
if let Some(id) = ctx.new_chan {
Value::from_runtime(RuntimeValue::Chan(id))
} else if let Some(id) = ctx.spawned {
Value::from_runtime(RuntimeValue::TaskHandle(id))
} else {
let payload = std::mem::replace(&mut ctx.resumed_with, RtPayload::Nothing);
Value::from_runtime(marshal::rebuild(payload))
}
}