1
0
forked from ROMEO/nexosim

Expose the Protobuf simulation service to WASM/JS

This commit is contained in:
Serge Barral 2024-05-27 23:21:26 +02:00
parent 77e6e569ff
commit 59d2af51ba
12 changed files with 252 additions and 242 deletions

View File

@ -28,7 +28,22 @@ jobs:
toolchain: ${{ matrix.rust }}
- name: Run cargo check
run: cargo check --features="rpc grpc-server"
run: cargo check --features="rpc grpc-service"
build-wasm:
name: Build wasm32
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Run cargo build (wasm)
run: cargo build --target wasm32-unknown-unknown --features="rpc"
test:
name: Test suite
@ -41,7 +56,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Run cargo test
run: cargo test --features="rpc grpc-server"
run: cargo test --features="rpc grpc-service"
loom-dry-run:
name: Loom dry run
@ -54,7 +69,7 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Dry-run cargo test (Loom)
run: cargo test --no-run --tests --features="rpc grpc-server"
run: cargo test --no-run --tests --features="rpc grpc-service"
env:
RUSTFLAGS: --cfg asynchronix_loom
@ -71,12 +86,12 @@ jobs:
components: miri
- name: Run cargo miri tests (single-threaded executor)
run: cargo miri test --tests --lib --features="rpc grpc-server"
run: cargo miri test --tests --lib --features="rpc grpc-service"
env:
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=1
- name: Run cargo miri tests (multi-threaded executor)
run: cargo miri test --tests --lib --features="rpc grpc-server"
run: cargo miri test --tests --lib --features="rpc grpc-service"
env:
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=4
@ -134,7 +149,7 @@ jobs:
run: cargo fmt --all -- --check
- name: Run cargo clippy
run: cargo clippy --features="rpc grpc-server"
run: cargo clippy --features="rpc grpc-service"
docs:
name: Docs
@ -147,4 +162,4 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Run cargo doc
run: cargo doc --no-deps --features="rpc grpc-server" --document-private-items
run: cargo doc --no-deps --features="rpc grpc-service" --document-private-items

View File

@ -26,8 +26,10 @@ autotests = false
rpc = ["dep:rmp-serde", "dep:serde", "dep:tonic", "dep:prost", "dep:prost-types", "dep:bytes"]
# This feature forces protobuf/gRPC code (re-)generation.
rpc-codegen = ["dep:tonic-build"]
# gRPC server.
grpc-server = ["rpc", "dep:tokio"]
# gRPC service.
grpc-service = ["rpc", "dep:tokio" , "tonic/transport"]
# wasm service.
wasm-service = ["rpc", "dep:wasm-bindgen"]
# API-unstable public exports meant for external test/benchmarking; development only.
dev-hooks = []
# Logging of performance-related statistics; development only.
@ -58,10 +60,12 @@ prost-types = { version = "0.12", optional = true }
rmp-serde = { version = "1.1", optional = true }
serde = { version = "1", optional = true }
# gRPC dependencies.
# gRPC service dependencies.
tokio = { version = "1.0", features=["net"], optional = true }
tonic = { version = "0.11", optional = true }
tonic = { version = "0.11", default-features = false, features=["codegen", "prost"], optional = true }
# WASM service dependencies.
wasm-bindgen = { version = "0.2", optional = true }
[target.'cfg(asynchronix_loom)'.dependencies]
loom = "0.5"

View File

@ -7,14 +7,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.build_client(false)
.out_dir("src/rpc/codegen/");
#[cfg(all(feature = "rpc-codegen", not(feature = "grpc-server")))]
#[cfg(all(feature = "rpc-codegen", not(feature = "grpc-service")))]
let builder = builder.build_server(false);
#[cfg(feature = "rpc-codegen")]
builder.compile(
&["simulation.proto", "custom_transport.proto"],
&["src/rpc/api/"],
)?;
builder.compile(&["simulation.proto"], &["src/rpc/api/"])?;
Ok(())
}

View File

@ -2,9 +2,12 @@
mod codegen;
mod endpoint_registry;
mod generic_server;
#[cfg(feature = "grpc-server")]
#[cfg(feature = "grpc-service")]
pub mod grpc;
mod key_registry;
mod simulation_service;
#[cfg(feature = "wasm-service")]
pub mod wasm;
pub use endpoint_registry::EndpointRegistry;
pub use simulation_service::SimulationService;

View File

@ -1,50 +0,0 @@
// Additional types for transport implementations which, unlike gRPC, do not
// support auto-generation from the `Simulation` service description.
syntax = "proto3";
package custom_transport;
import "simulation.proto";
enum ServerErrorCode {
UNKNOWN_REQUEST = 0;
EMPTY_REQUEST = 1;
}
message ServerError {
ServerErrorCode code = 1;
string message = 2;
}
message AnyRequest {
oneof request { // Expects exactly 1 variant.
simulation.InitRequest init_request = 1;
simulation.TimeRequest time_request = 2;
simulation.StepRequest step_request = 3;
simulation.StepUntilRequest step_until_request = 4;
simulation.ScheduleEventRequest schedule_event_request = 5;
simulation.CancelEventRequest cancel_event_request = 6;
simulation.ProcessEventRequest process_event_request = 7;
simulation.ProcessQueryRequest process_query_request = 8;
simulation.ReadEventsRequest read_events_request = 9;
simulation.OpenSinkRequest open_sink_request = 10;
simulation.CloseSinkRequest close_sink_request = 11;
}
}
message AnyReply {
oneof reply { // Contains exactly 1 variant.
simulation.InitReply init_reply = 1;
simulation.TimeReply time_reply = 2;
simulation.StepReply step_reply = 3;
simulation.StepUntilReply step_until_reply = 4;
simulation.ScheduleEventReply schedule_event_reply = 5;
simulation.CancelEventReply cancel_event_reply = 6;
simulation.ProcessEventReply process_event_reply = 7;
simulation.ProcessQueryReply process_query_reply = 8;
simulation.ReadEventsReply read_events_reply = 9;
simulation.OpenSinkReply open_sink_reply = 10;
simulation.CloseSinkReply close_sink_reply = 11;
ServerError error = 100;
}
}

View File

@ -146,6 +146,23 @@ message CloseSinkReply {
}
}
// A convenience message type for custom transport implementation.
message AnyRequest {
oneof request { // Expects exactly 1 variant.
InitRequest init_request = 1;
TimeRequest time_request = 2;
StepRequest step_request = 3;
StepUntilRequest step_until_request = 4;
ScheduleEventRequest schedule_event_request = 5;
CancelEventRequest cancel_event_request = 6;
ProcessEventRequest process_event_request = 7;
ProcessQueryRequest process_query_request = 8;
ReadEventsRequest read_events_request = 9;
OpenSinkRequest open_sink_request = 10;
CloseSinkRequest close_sink_request = 11;
}
}
service Simulation {
rpc Init(InitRequest) returns (InitReply);
rpc Time(TimeRequest) returns (TimeReply);

View File

@ -1,7 +1,6 @@
#![allow(unreachable_pub)]
#![allow(clippy::enum_variant_names)]
#![allow(missing_docs)]
#[rustfmt::skip]
pub(crate) mod custom_transport;
#[rustfmt::skip]
pub(crate) mod simulation;

View File

@ -1,111 +0,0 @@
// This file is @generated by prost-build.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ServerError {
#[prost(enumeration = "ServerErrorCode", tag = "1")]
pub code: i32,
#[prost(string, tag = "2")]
pub message: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnyRequest {
/// Expects exactly 1 variant.
#[prost(oneof = "any_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")]
pub request: ::core::option::Option<any_request::Request>,
}
/// Nested message and enum types in `AnyRequest`.
pub mod any_request {
/// Expects exactly 1 variant.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Request {
#[prost(message, tag = "1")]
InitRequest(super::super::simulation::InitRequest),
#[prost(message, tag = "2")]
TimeRequest(super::super::simulation::TimeRequest),
#[prost(message, tag = "3")]
StepRequest(super::super::simulation::StepRequest),
#[prost(message, tag = "4")]
StepUntilRequest(super::super::simulation::StepUntilRequest),
#[prost(message, tag = "5")]
ScheduleEventRequest(super::super::simulation::ScheduleEventRequest),
#[prost(message, tag = "6")]
CancelEventRequest(super::super::simulation::CancelEventRequest),
#[prost(message, tag = "7")]
ProcessEventRequest(super::super::simulation::ProcessEventRequest),
#[prost(message, tag = "8")]
ProcessQueryRequest(super::super::simulation::ProcessQueryRequest),
#[prost(message, tag = "9")]
ReadEventsRequest(super::super::simulation::ReadEventsRequest),
#[prost(message, tag = "10")]
OpenSinkRequest(super::super::simulation::OpenSinkRequest),
#[prost(message, tag = "11")]
CloseSinkRequest(super::super::simulation::CloseSinkRequest),
}
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnyReply {
/// Contains exactly 1 variant.
#[prost(oneof = "any_reply::Reply", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 100")]
pub reply: ::core::option::Option<any_reply::Reply>,
}
/// Nested message and enum types in `AnyReply`.
pub mod any_reply {
/// Contains exactly 1 variant.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Reply {
#[prost(message, tag = "1")]
InitReply(super::super::simulation::InitReply),
#[prost(message, tag = "2")]
TimeReply(super::super::simulation::TimeReply),
#[prost(message, tag = "3")]
StepReply(super::super::simulation::StepReply),
#[prost(message, tag = "4")]
StepUntilReply(super::super::simulation::StepUntilReply),
#[prost(message, tag = "5")]
ScheduleEventReply(super::super::simulation::ScheduleEventReply),
#[prost(message, tag = "6")]
CancelEventReply(super::super::simulation::CancelEventReply),
#[prost(message, tag = "7")]
ProcessEventReply(super::super::simulation::ProcessEventReply),
#[prost(message, tag = "8")]
ProcessQueryReply(super::super::simulation::ProcessQueryReply),
#[prost(message, tag = "9")]
ReadEventsReply(super::super::simulation::ReadEventsReply),
#[prost(message, tag = "10")]
OpenSinkReply(super::super::simulation::OpenSinkReply),
#[prost(message, tag = "11")]
CloseSinkReply(super::super::simulation::CloseSinkReply),
#[prost(message, tag = "100")]
Error(super::ServerError),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ServerErrorCode {
UnknownRequest = 0,
EmptyRequest = 1,
}
impl ServerErrorCode {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
ServerErrorCode::UnknownRequest => "UNKNOWN_REQUEST",
ServerErrorCode::EmptyRequest => "EMPTY_REQUEST",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"UNKNOWN_REQUEST" => Some(Self::UnknownRequest),
"EMPTY_REQUEST" => Some(Self::EmptyRequest),
_ => None,
}
}
}

View File

@ -332,6 +332,44 @@ pub mod close_sink_reply {
Error(super::Error),
}
}
/// A convenience message type for custom transport implementation.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AnyRequest {
/// Expects exactly 1 variant.
#[prost(oneof = "any_request::Request", tags = "1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")]
pub request: ::core::option::Option<any_request::Request>,
}
/// Nested message and enum types in `AnyRequest`.
pub mod any_request {
/// Expects exactly 1 variant.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Request {
#[prost(message, tag = "1")]
InitRequest(super::InitRequest),
#[prost(message, tag = "2")]
TimeRequest(super::TimeRequest),
#[prost(message, tag = "3")]
StepRequest(super::StepRequest),
#[prost(message, tag = "4")]
StepUntilRequest(super::StepUntilRequest),
#[prost(message, tag = "5")]
ScheduleEventRequest(super::ScheduleEventRequest),
#[prost(message, tag = "6")]
CancelEventRequest(super::CancelEventRequest),
#[prost(message, tag = "7")]
ProcessEventRequest(super::ProcessEventRequest),
#[prost(message, tag = "8")]
ProcessQueryRequest(super::ProcessQueryRequest),
#[prost(message, tag = "9")]
ReadEventsRequest(super::ReadEventsRequest),
#[prost(message, tag = "10")]
OpenSinkRequest(super::OpenSinkRequest),
#[prost(message, tag = "11")]
CloseSinkRequest(super::CloseSinkRequest),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ErrorCode {

View File

@ -1,4 +1,4 @@
//! GRPC simulation server.
//! gRPC simulation service.
use std::net::SocketAddr;
use std::sync::Mutex;
@ -10,12 +10,12 @@ use crate::rpc::EndpointRegistry;
use crate::simulation::SimInit;
use super::codegen::simulation::*;
use super::generic_server::GenericServer;
use super::simulation_service::SimulationService;
/// Runs a GRPC simulation server.
/// Runs a gRPC simulation server.
///
/// The first argument is a closure that is called every time the simulation is
/// started by the remote client. It must create a new `SimInit` object
/// (re)started by the remote client. It must create a new `SimInit` object
/// complemented by a registry that exposes the public event and query
/// interface.
pub fn run<F>(sim_gen: F, addr: SocketAddr) -> Result<(), Box<dyn std::error::Error>>
@ -27,7 +27,7 @@ where
.enable_io()
.build()?;
let sim_manager = GrpcServer::new(sim_gen);
let sim_manager = GrpcSimulationService::new(sim_gen);
rt.block_on(async move {
Server::builder()
@ -39,33 +39,27 @@ where
})
}
struct GrpcServer<F>
where
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
{
inner: Mutex<GenericServer<F>>,
struct GrpcSimulationService {
inner: Mutex<SimulationService>,
}
impl<F> GrpcServer<F>
where
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
{
fn new(sim_gen: F) -> Self {
impl GrpcSimulationService {
fn new<F>(sim_gen: F) -> Self
where
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
{
Self {
inner: Mutex::new(GenericServer::new(sim_gen)),
inner: Mutex::new(SimulationService::new(sim_gen)),
}
}
fn inner(&self) -> MutexGuard<'_, GenericServer<F>> {
fn inner(&self) -> MutexGuard<'_, SimulationService> {
self.inner.lock().unwrap()
}
}
#[tonic::async_trait]
impl<F> simulation_server::Simulation for GrpcServer<F>
where
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
{
impl simulation_server::Simulation for GrpcSimulationService {
async fn init(&self, request: Request<InitRequest>) -> Result<Response<InitReply>, Status> {
let request = request.into_inner();

View File

@ -1,3 +1,5 @@
use std::error;
use std::fmt;
use std::time::Duration;
use bytes::Buf;
@ -9,86 +11,87 @@ use crate::rpc::key_registry::{KeyRegistry, KeyRegistryId};
use crate::rpc::EndpointRegistry;
use crate::simulation::{SimInit, Simulation};
use super::codegen::custom_transport::*;
use super::codegen::simulation::*;
/// Transport-independent server implementation.
/// Protobuf-based simulation manager.
///
/// This implements the protobuf services without any transport-specific
/// management.
pub(crate) struct GenericServer<F> {
sim_gen: F,
/// A `SimulationService` enables the management of the lifecycle of a
/// simulation, including creating a
/// [`Simulation`](crate::simulation::Simulation), invoking its methods and
/// instantiating a new simulation.
///
/// Its methods map the various RPC service methods defined in
/// `simulation.proto`.
pub struct SimulationService {
sim_gen: Box<dyn FnMut() -> (SimInit, EndpointRegistry) + Send + 'static>,
sim_context: Option<(Simulation, EndpointRegistry, KeyRegistry)>,
}
impl<F> GenericServer<F>
where
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
{
/// Creates a new `GenericServer` without any active simulation.
pub(crate) fn new(sim_gen: F) -> Self {
impl SimulationService {
/// Creates a new `SimulationService` without any active simulation.
///
/// The argument is a closure that is called every time the simulation is
/// (re)started by the remote client. It must create a new `SimInit` object
/// complemented by a registry that exposes the public event and query
/// interface.
pub fn new<F>(sim_gen: F) -> Self
where
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
{
Self {
sim_gen,
sim_gen: Box::new(sim_gen),
sim_context: None,
}
}
/// Processes an encoded `AnyRequest` message and returns an encoded
/// `AnyReply`.
#[allow(dead_code)]
pub(crate) fn service_request<B>(&mut self, request_buf: B) -> Vec<u8>
/// Processes an encoded `AnyRequest` message and returns an encoded reply.
pub fn process_request<B>(&mut self, request_buf: B) -> Result<Vec<u8>, InvalidRequest>
where
B: Buf,
{
let reply = match AnyRequest::decode(request_buf) {
match AnyRequest::decode(request_buf) {
Ok(AnyRequest { request: Some(req) }) => match req {
any_request::Request::InitRequest(request) => {
any_reply::Reply::InitReply(self.init(request))
Ok(self.init(request).encode_to_vec())
}
any_request::Request::TimeRequest(request) => {
any_reply::Reply::TimeReply(self.time(request))
Ok(self.time(request).encode_to_vec())
}
any_request::Request::StepRequest(request) => {
any_reply::Reply::StepReply(self.step(request))
Ok(self.step(request).encode_to_vec())
}
any_request::Request::StepUntilRequest(request) => {
any_reply::Reply::StepUntilReply(self.step_until(request))
Ok(self.step_until(request).encode_to_vec())
}
any_request::Request::ScheduleEventRequest(request) => {
any_reply::Reply::ScheduleEventReply(self.schedule_event(request))
Ok(self.schedule_event(request).encode_to_vec())
}
any_request::Request::CancelEventRequest(request) => {
any_reply::Reply::CancelEventReply(self.cancel_event(request))
Ok(self.cancel_event(request).encode_to_vec())
}
any_request::Request::ProcessEventRequest(request) => {
any_reply::Reply::ProcessEventReply(self.process_event(request))
Ok(self.process_event(request).encode_to_vec())
}
any_request::Request::ProcessQueryRequest(request) => {
any_reply::Reply::ProcessQueryReply(self.process_query(request))
Ok(self.process_query(request).encode_to_vec())
}
any_request::Request::ReadEventsRequest(request) => {
any_reply::Reply::ReadEventsReply(self.read_events(request))
Ok(self.read_events(request).encode_to_vec())
}
any_request::Request::OpenSinkRequest(request) => {
any_reply::Reply::OpenSinkReply(self.open_sink(request))
Ok(self.open_sink(request).encode_to_vec())
}
any_request::Request::CloseSinkRequest(request) => {
any_reply::Reply::CloseSinkReply(self.close_sink(request))
Ok(self.close_sink(request).encode_to_vec())
}
},
Ok(AnyRequest { request: None }) => any_reply::Reply::Error(ServerError {
code: ServerErrorCode::EmptyRequest as i32,
message: "the message did not contain any request".to_string(),
Ok(AnyRequest { request: None }) => Err(InvalidRequest {
description: "the message did not contain any request".to_string(),
}),
Err(err) => any_reply::Reply::Error(ServerError {
code: ServerErrorCode::UnknownRequest as i32,
message: format!("bad request: {}", err),
Err(err) => Err(InvalidRequest {
description: format!("bad request: {}", err),
}),
};
let reply = AnyReply { reply: Some(reply) };
reply.encode_to_vec()
}
}
/// Initialize a simulation with the provided time.
@ -606,6 +609,25 @@ where
}
}
impl fmt::Debug for SimulationService {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SimulationService").finish_non_exhaustive()
}
}
#[derive(Clone, Debug)]
pub struct InvalidRequest {
description: String,
}
impl fmt::Display for InvalidRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.description)
}
}
impl error::Error for InvalidRequest {}
/// Attempts a cast from a `MonotonicTime` to a protobuf `Timestamp`.
///
/// This will fail if the time is outside the protobuf-specified range for

View File

@ -0,0 +1,82 @@
//! WASM simulation service.
//!
//! This module provides [`WasmSimulationService`], a thin wrapper over a
//! [`SimulationService`] that can be use from JavaScript.
//!
//! Although it is readily possible to use a
//! [`Simulation`](crate::simulation::Simulation) object from WASM,
//! [`WasmSimulationService`] goes further by exposing the complete simulation
//! API to JavaScript through protobuf.
//!
//! Keep in mind that WASM only supports single-threaded execution and therefore
//! any simulation bench compiled to WASM should instantiate simulations with
//! either [`SimInit::new()`](crate::simulation::SimInit::new) or
//! [`SimInit::with_num_threads(1)`](crate::simulation::SimInit::with_num_threads),
//! failing which the simulation will panic upon initialization.
//!
//! [`WasmSimulationService`] is exported to the JavaScript namespace as
//! `SimulationService`, and [`WasmSimulationService::process_request`] as
//! `SimulationService.processRequest`.
use wasm_bindgen::prelude::*;
use super::{EndpointRegistry, SimulationService};
use crate::simulation::SimInit;
/// A simulation service that can be used from JavaScript.
///
/// This would typically be used by implementing a `run` function in Rust and
/// export it to WASM:
///
/// ```no_run
/// #[wasm_bindgen]
/// pub fn run() -> WasmSimulationService {
/// WasmSimulationService::new(my_custom_bench_generator)
/// }
/// ```
///
/// which can then be used on the JS side to create a `SimulationService` as a
/// JS object, e.g. with:
///
/// ```js
/// const simu = run();
///
/// // ...build a protobuf request and encode it as a `Uint8Array`...
///
/// const reply = simu.processRequest(myRequest);
///
/// // ...decode the protobuf reply...
/// ```
#[wasm_bindgen(js_name = SimulationService)]
#[derive(Debug)]
pub struct WasmSimulationService(SimulationService);
#[wasm_bindgen(js_class = SimulationService)]
impl WasmSimulationService {
/// Processes a protobuf-encoded `AnyRequest` message and returns a
/// protobuf-encoded reply.
///
/// For the Protocol Buffer definitions, see the `simulation.proto` file.
#[wasm_bindgen(js_name = processRequest)]
pub fn process_request(&mut self, request: &[u8]) -> Result<Box<[u8]>, JsError> {
self.0
.process_request(request)
.map(|reply| reply.into_boxed_slice())
.map_err(|e| JsError::new(&e.to_string()))
}
}
impl WasmSimulationService {
/// Creates a new `SimulationService` without any active simulation.
///
/// The argument is a closure that is called every time the simulation is
/// (re)started by the remote client. It must create a new `SimInit` object
/// complemented by a registry that exposes the public event and query
/// interface.
pub fn new<F>(sim_gen: F) -> Self
where
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
{
Self(SimulationService::new(sim_gen))
}
}