sim-lib-server 0.1.0

SIM workspace package for sim lib server.
Documentation
use std::sync::Arc;

use crate::{
    LoopEvalSite, ServerStatus, connection_arg, coroutine_target_from_value, server_arg,
    server_cancel_coroutine, server_connect, server_coroutine_status, server_from_value,
    server_lisp, server_loop, server_notify, server_pipeline, server_realize, server_receive,
    server_repl, server_request, server_resume_exprs, server_send, server_start, server_start_loop,
    server_stream, server_stream_next, server_trigger, server_trigger_poll, server_wasm_region,
    server_yield, shutdown_server_transport, stream_handle_arg,
};
use sim_kernel::{
    Args, CORE_FUNCTION_CLASS_ID, Callable, ClassRef, Cx, Error, Linker, Object, RawArgs, Result,
    Symbol, Value,
};

#[derive(Clone, Copy)]
enum ServerFnKind {
    Start,
    Stop,
    StopForce,
    Close,
    Suspend,
    Resume,
    Connect,
    Pipeline,
    Loop,
    StartLoop,
    Yield,
    Send,
    Request,
    Notify,
    Receive,
    CoroutineStatus,
    CancelCoroutine,
    Stream,
    StreamNext,
    StreamDone,
    StreamCancel,
    AsFabric,
    Realize,
    Health,
    Sessions,
    Lisp,
    Reflect,
    Repl,
    Trigger,
    TriggerPoll,
    WasmRegion,
}

#[derive(Clone)]
struct ServerFunction {
    symbol: Symbol,
    kind: ServerFnKind,
}

pub(crate) fn server_exports() -> Vec<sim_kernel::Export> {
    [
        Symbol::qualified("server", "start"),
        Symbol::qualified("server", "stop"),
        Symbol::qualified("server", "stop!"),
        Symbol::qualified("server", "close"),
        Symbol::qualified("server", "suspend"),
        Symbol::qualified("server", "resume"),
        Symbol::qualified("server", "connect"),
        Symbol::qualified("server", "pipeline"),
        Symbol::qualified("server", "loop"),
        Symbol::qualified("server", "start-loop"),
        Symbol::qualified("server", "yield"),
        Symbol::qualified("server", "send"),
        Symbol::qualified("server", "request"),
        Symbol::qualified("server", "notify"),
        Symbol::qualified("server", "receive"),
        Symbol::qualified("server", "coroutine-status"),
        Symbol::qualified("server", "cancel-coroutine"),
        Symbol::qualified("server", "stream"),
        Symbol::qualified("server", "stream-next"),
        Symbol::qualified("server", "stream-done?"),
        Symbol::qualified("server", "stream-cancel"),
        Symbol::qualified("server", "as-fabric"),
        Symbol::qualified("server", "realize"),
        Symbol::qualified("server", "health"),
        Symbol::qualified("server", "sessions"),
        Symbol::qualified("server", "lisp"),
        Symbol::qualified("server", "reflect"),
        Symbol::qualified("server", "repl"),
        Symbol::qualified("server", "trigger"),
        Symbol::qualified("server", "trigger-poll"),
        Symbol::qualified("server", "wasm-region"),
    ]
    .into_iter()
    .map(|symbol| sim_kernel::Export::Function {
        symbol,
        function_id: None,
    })
    .collect()
}

pub(crate) fn register_server_functions(
    cx: &mut sim_kernel::LoadCx,
    linker: &mut Linker<'_>,
) -> Result<()> {
    for (symbol, kind) in [
        (Symbol::qualified("server", "start"), ServerFnKind::Start),
        (Symbol::qualified("server", "stop"), ServerFnKind::Stop),
        (
            Symbol::qualified("server", "stop!"),
            ServerFnKind::StopForce,
        ),
        (Symbol::qualified("server", "close"), ServerFnKind::Close),
        (
            Symbol::qualified("server", "suspend"),
            ServerFnKind::Suspend,
        ),
        (Symbol::qualified("server", "resume"), ServerFnKind::Resume),
        (
            Symbol::qualified("server", "connect"),
            ServerFnKind::Connect,
        ),
        (
            Symbol::qualified("server", "pipeline"),
            ServerFnKind::Pipeline,
        ),
        (Symbol::qualified("server", "loop"), ServerFnKind::Loop),
        (
            Symbol::qualified("server", "start-loop"),
            ServerFnKind::StartLoop,
        ),
        (Symbol::qualified("server", "yield"), ServerFnKind::Yield),
        (Symbol::qualified("server", "send"), ServerFnKind::Send),
        (
            Symbol::qualified("server", "request"),
            ServerFnKind::Request,
        ),
        (Symbol::qualified("server", "notify"), ServerFnKind::Notify),
        (
            Symbol::qualified("server", "receive"),
            ServerFnKind::Receive,
        ),
        (
            Symbol::qualified("server", "coroutine-status"),
            ServerFnKind::CoroutineStatus,
        ),
        (
            Symbol::qualified("server", "cancel-coroutine"),
            ServerFnKind::CancelCoroutine,
        ),
        (Symbol::qualified("server", "stream"), ServerFnKind::Stream),
        (
            Symbol::qualified("server", "stream-next"),
            ServerFnKind::StreamNext,
        ),
        (
            Symbol::qualified("server", "stream-done?"),
            ServerFnKind::StreamDone,
        ),
        (
            Symbol::qualified("server", "stream-cancel"),
            ServerFnKind::StreamCancel,
        ),
        (
            Symbol::qualified("server", "as-fabric"),
            ServerFnKind::AsFabric,
        ),
        (
            Symbol::qualified("server", "realize"),
            ServerFnKind::Realize,
        ),
        (Symbol::qualified("server", "health"), ServerFnKind::Health),
        (
            Symbol::qualified("server", "sessions"),
            ServerFnKind::Sessions,
        ),
        (Symbol::qualified("server", "lisp"), ServerFnKind::Lisp),
        (
            Symbol::qualified("server", "reflect"),
            ServerFnKind::Reflect,
        ),
        (Symbol::qualified("server", "repl"), ServerFnKind::Repl),
        (
            Symbol::qualified("server", "trigger"),
            ServerFnKind::Trigger,
        ),
        (
            Symbol::qualified("server", "trigger-poll"),
            ServerFnKind::TriggerPoll,
        ),
        (
            Symbol::qualified("server", "wasm-region"),
            ServerFnKind::WasmRegion,
        ),
    ] {
        let function_id = linker.function(symbol.clone())?;
        let value = cx
            .factory()
            .opaque(Arc::new(ServerFunction { symbol, kind }))?;
        linker.bind_function_value(function_id, value)?;
    }
    Ok(())
}

impl Object for ServerFunction {
    fn display(&self, _cx: &mut Cx) -> Result<String> {
        Ok(format!("#<function {}>", self.symbol))
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

impl sim_kernel::ObjectCompat for ServerFunction {
    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
        if let Some(value) = cx
            .registry()
            .class_by_symbol(&Symbol::qualified("core", "Function"))
        {
            return Ok(value.clone());
        }
        cx.factory().class_stub(
            CORE_FUNCTION_CLASS_ID,
            Symbol::qualified("core", "Function"),
        )
    }
    fn as_callable(&self) -> Option<&dyn Callable> {
        Some(self)
    }
}

impl Callable for ServerFunction {
    fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
        match self.kind {
            ServerFnKind::Start => Err(Error::Eval(
                "server/start must be called with keyword expressions".to_owned(),
            )),
            ServerFnKind::Stop | ServerFnKind::StopForce => {
                let server = server_arg(&args, 0, "server/stop expects one server value")?;
                server.set_status(ServerStatus::Stopped);
                if let Some(runtime) = server.runtime() {
                    runtime.begin_stop();
                }
                server.stop_triggers()?;
                shutdown_server_transport(server)?;
                Ok(args.values()[0].clone())
            }
            ServerFnKind::Close => {
                let connection =
                    connection_arg(&args, 0, "server/close expects one connection value")?;
                connection.close(cx)?;
                Ok(args.values()[0].clone())
            }
            ServerFnKind::Suspend => {
                let server = server_arg(&args, 0, "server/suspend expects one server value")?;
                server.set_status(ServerStatus::Suspended);
                Ok(args.values()[0].clone())
            }
            ServerFnKind::Resume => {
                let Some(target) = args.values().first() else {
                    return Err(Error::Eval(
                        "server/resume expects a server or coroutine value".to_owned(),
                    ));
                };
                if let Some(server) = server_from_value(target) {
                    server.set_status(ServerStatus::Running);
                    return Ok(target.clone());
                }
                let coroutine = coroutine_target_from_value(target)?;
                if let Some(input) = args.values().get(1) {
                    return coroutine.resume(cx, input.clone());
                }
                Err(Error::Eval(
                    "server/resume expects an expression when targeting a coroutine".to_owned(),
                ))
            }
            ServerFnKind::Connect
            | ServerFnKind::Yield
            | ServerFnKind::Send
            | ServerFnKind::Request
            | ServerFnKind::Notify
            | ServerFnKind::Receive
            | ServerFnKind::CoroutineStatus
            | ServerFnKind::CancelCoroutine
            | ServerFnKind::Stream
            | ServerFnKind::StreamNext
            | ServerFnKind::Realize
            | ServerFnKind::Repl
            | ServerFnKind::Trigger
            | ServerFnKind::TriggerPoll
            | ServerFnKind::WasmRegion => Err(Error::Eval(format!(
                "{} must be called with expression arguments",
                self.symbol
            ))),
            ServerFnKind::StreamDone => {
                let handle =
                    stream_handle_arg(&args, 0, "server/stream-done? expects one stream value")?;
                cx.factory().bool(handle.is_done())
            }
            ServerFnKind::StreamCancel => {
                let handle =
                    stream_handle_arg(&args, 0, "server/stream-cancel expects one stream value")?;
                handle.cancel();
                cx.factory().nil()
            }
            ServerFnKind::AsFabric => {
                let connection =
                    connection_arg(&args, 0, "server/as-fabric expects one connection value")?;
                Ok(cx.factory().opaque(Arc::new(connection.clone()))?)
            }
            ServerFnKind::Health => {
                let server = server_arg(&args, 0, "server/health expects one server value")?;
                server.health_value(cx)
            }
            ServerFnKind::Sessions => {
                let server = server_arg(&args, 0, "server/sessions expects one server value")?;
                server.sessions_value(cx)
            }
            ServerFnKind::Lisp => {
                let server = server_arg(&args, 0, "server/lisp expects one server value")?;
                cx.factory().expr(server_lisp(server))
            }
            ServerFnKind::Reflect => {
                let server = server_arg(&args, 0, "server/reflect expects one server value")?;
                server.reflect_value(cx)
            }
            ServerFnKind::Pipeline | ServerFnKind::Loop => Err(Error::Eval(format!(
                "{} expects unevaluated option expressions",
                self.symbol
            ))),
            ServerFnKind::StartLoop => {
                let connection =
                    connection_arg(&args, 0, "server/start-loop expects one loop connection")?;
                if connection
                    .site()
                    .as_any()
                    .downcast_ref::<LoopEvalSite>()
                    .is_none()
                {
                    return Err(Error::TypeMismatch {
                        expected: "loop connection",
                        found: "non-loop connection",
                    });
                }
                Ok(cx.factory().opaque(Arc::new(connection.clone()))?)
            }
        }
    }

    fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
        match self.kind {
            ServerFnKind::Start => server_start(cx, args),
            ServerFnKind::Resume => server_resume_exprs(cx, args),
            ServerFnKind::Connect => server_connect(cx, args),
            ServerFnKind::Pipeline => server_pipeline(cx, args),
            ServerFnKind::Loop => server_loop(cx, args),
            ServerFnKind::StartLoop => server_start_loop(cx, args),
            ServerFnKind::Yield => server_yield(cx, args),
            ServerFnKind::Send => server_send(cx, args),
            ServerFnKind::Request => server_request(cx, args),
            ServerFnKind::Notify => server_notify(cx, args),
            ServerFnKind::Receive => server_receive(cx, args),
            ServerFnKind::CoroutineStatus => server_coroutine_status(cx, args),
            ServerFnKind::CancelCoroutine => server_cancel_coroutine(cx, args),
            ServerFnKind::Stream => server_stream(cx, args),
            ServerFnKind::StreamNext => server_stream_next(cx, args),
            ServerFnKind::Realize => server_realize(cx, args),
            ServerFnKind::Repl => server_repl(cx, args),
            ServerFnKind::Trigger => server_trigger(cx, args),
            ServerFnKind::TriggerPoll => server_trigger_poll(cx, args),
            ServerFnKind::WasmRegion => server_wasm_region(cx, args),
            _ => {
                let values = args
                    .into_exprs()
                    .into_iter()
                    .map(|expr| cx.eval_expr(expr))
                    .collect::<Result<Vec<_>>>()?;
                self.call(cx, Args::new(values))
            }
        }
    }
}