forked from ROMEO/nexosim
Expose the Protobuf simulation service to WASM/JS
This commit is contained in:
parent
77e6e569ff
commit
59d2af51ba
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
@ -28,7 +28,22 @@ jobs:
|
|||||||
toolchain: ${{ matrix.rust }}
|
toolchain: ${{ matrix.rust }}
|
||||||
|
|
||||||
- name: Run cargo check
|
- 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:
|
test:
|
||||||
name: Test suite
|
name: Test suite
|
||||||
@ -41,7 +56,7 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: Run cargo test
|
- name: Run cargo test
|
||||||
run: cargo test --features="rpc grpc-server"
|
run: cargo test --features="rpc grpc-service"
|
||||||
|
|
||||||
loom-dry-run:
|
loom-dry-run:
|
||||||
name: Loom dry run
|
name: Loom dry run
|
||||||
@ -54,7 +69,7 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: Dry-run cargo test (Loom)
|
- 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:
|
env:
|
||||||
RUSTFLAGS: --cfg asynchronix_loom
|
RUSTFLAGS: --cfg asynchronix_loom
|
||||||
|
|
||||||
@ -71,12 +86,12 @@ jobs:
|
|||||||
components: miri
|
components: miri
|
||||||
|
|
||||||
- name: Run cargo miri tests (single-threaded executor)
|
- 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:
|
env:
|
||||||
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=1
|
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=1
|
||||||
|
|
||||||
- name: Run cargo miri tests (multi-threaded executor)
|
- 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:
|
env:
|
||||||
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=4
|
MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-disable-isolation -Zmiri-num-cpus=4
|
||||||
|
|
||||||
@ -134,7 +149,7 @@ jobs:
|
|||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
- name: Run cargo clippy
|
- name: Run cargo clippy
|
||||||
run: cargo clippy --features="rpc grpc-server"
|
run: cargo clippy --features="rpc grpc-service"
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
name: Docs
|
name: Docs
|
||||||
@ -147,4 +162,4 @@ jobs:
|
|||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: Run cargo doc
|
- 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
|
||||||
|
@ -26,8 +26,10 @@ autotests = false
|
|||||||
rpc = ["dep:rmp-serde", "dep:serde", "dep:tonic", "dep:prost", "dep:prost-types", "dep:bytes"]
|
rpc = ["dep:rmp-serde", "dep:serde", "dep:tonic", "dep:prost", "dep:prost-types", "dep:bytes"]
|
||||||
# This feature forces protobuf/gRPC code (re-)generation.
|
# This feature forces protobuf/gRPC code (re-)generation.
|
||||||
rpc-codegen = ["dep:tonic-build"]
|
rpc-codegen = ["dep:tonic-build"]
|
||||||
# gRPC server.
|
# gRPC service.
|
||||||
grpc-server = ["rpc", "dep:tokio"]
|
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.
|
# API-unstable public exports meant for external test/benchmarking; development only.
|
||||||
dev-hooks = []
|
dev-hooks = []
|
||||||
# Logging of performance-related statistics; development only.
|
# 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 }
|
rmp-serde = { version = "1.1", optional = true }
|
||||||
serde = { version = "1", optional = true }
|
serde = { version = "1", optional = true }
|
||||||
|
|
||||||
# gRPC dependencies.
|
# gRPC service dependencies.
|
||||||
tokio = { version = "1.0", features=["net"], optional = true }
|
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]
|
[target.'cfg(asynchronix_loom)'.dependencies]
|
||||||
loom = "0.5"
|
loom = "0.5"
|
||||||
|
@ -7,14 +7,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.build_client(false)
|
.build_client(false)
|
||||||
.out_dir("src/rpc/codegen/");
|
.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);
|
let builder = builder.build_server(false);
|
||||||
|
|
||||||
#[cfg(feature = "rpc-codegen")]
|
#[cfg(feature = "rpc-codegen")]
|
||||||
builder.compile(
|
builder.compile(&["simulation.proto"], &["src/rpc/api/"])?;
|
||||||
&["simulation.proto", "custom_transport.proto"],
|
|
||||||
&["src/rpc/api/"],
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
mod codegen;
|
mod codegen;
|
||||||
mod endpoint_registry;
|
mod endpoint_registry;
|
||||||
mod generic_server;
|
#[cfg(feature = "grpc-service")]
|
||||||
#[cfg(feature = "grpc-server")]
|
|
||||||
pub mod grpc;
|
pub mod grpc;
|
||||||
mod key_registry;
|
mod key_registry;
|
||||||
|
mod simulation_service;
|
||||||
|
#[cfg(feature = "wasm-service")]
|
||||||
|
pub mod wasm;
|
||||||
|
|
||||||
pub use endpoint_registry::EndpointRegistry;
|
pub use endpoint_registry::EndpointRegistry;
|
||||||
|
pub use simulation_service::SimulationService;
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 {
|
service Simulation {
|
||||||
rpc Init(InitRequest) returns (InitReply);
|
rpc Init(InitRequest) returns (InitReply);
|
||||||
rpc Time(TimeRequest) returns (TimeReply);
|
rpc Time(TimeRequest) returns (TimeReply);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#![allow(unreachable_pub)]
|
#![allow(unreachable_pub)]
|
||||||
#![allow(clippy::enum_variant_names)]
|
#![allow(clippy::enum_variant_names)]
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub(crate) mod custom_transport;
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub(crate) mod simulation;
|
pub(crate) mod simulation;
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -332,6 +332,44 @@ pub mod close_sink_reply {
|
|||||||
Error(super::Error),
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||||
#[repr(i32)]
|
#[repr(i32)]
|
||||||
pub enum ErrorCode {
|
pub enum ErrorCode {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! GRPC simulation server.
|
//! gRPC simulation service.
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
@ -10,12 +10,12 @@ use crate::rpc::EndpointRegistry;
|
|||||||
use crate::simulation::SimInit;
|
use crate::simulation::SimInit;
|
||||||
|
|
||||||
use super::codegen::simulation::*;
|
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
|
/// 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
|
/// complemented by a registry that exposes the public event and query
|
||||||
/// interface.
|
/// interface.
|
||||||
pub fn run<F>(sim_gen: F, addr: SocketAddr) -> Result<(), Box<dyn std::error::Error>>
|
pub fn run<F>(sim_gen: F, addr: SocketAddr) -> Result<(), Box<dyn std::error::Error>>
|
||||||
@ -27,7 +27,7 @@ where
|
|||||||
.enable_io()
|
.enable_io()
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let sim_manager = GrpcServer::new(sim_gen);
|
let sim_manager = GrpcSimulationService::new(sim_gen);
|
||||||
|
|
||||||
rt.block_on(async move {
|
rt.block_on(async move {
|
||||||
Server::builder()
|
Server::builder()
|
||||||
@ -39,33 +39,27 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GrpcServer<F>
|
struct GrpcSimulationService {
|
||||||
where
|
inner: Mutex<SimulationService>,
|
||||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
|
||||||
{
|
|
||||||
inner: Mutex<GenericServer<F>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> GrpcServer<F>
|
impl GrpcSimulationService {
|
||||||
where
|
fn new<F>(sim_gen: F) -> Self
|
||||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
where
|
||||||
{
|
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
||||||
fn new(sim_gen: F) -> Self {
|
{
|
||||||
Self {
|
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()
|
self.inner.lock().unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tonic::async_trait]
|
#[tonic::async_trait]
|
||||||
impl<F> simulation_server::Simulation for GrpcServer<F>
|
impl simulation_server::Simulation for GrpcSimulationService {
|
||||||
where
|
|
||||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
|
||||||
{
|
|
||||||
async fn init(&self, request: Request<InitRequest>) -> Result<Response<InitReply>, Status> {
|
async fn init(&self, request: Request<InitRequest>) -> Result<Response<InitReply>, Status> {
|
||||||
let request = request.into_inner();
|
let request = request.into_inner();
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
@ -9,86 +11,87 @@ use crate::rpc::key_registry::{KeyRegistry, KeyRegistryId};
|
|||||||
use crate::rpc::EndpointRegistry;
|
use crate::rpc::EndpointRegistry;
|
||||||
use crate::simulation::{SimInit, Simulation};
|
use crate::simulation::{SimInit, Simulation};
|
||||||
|
|
||||||
use super::codegen::custom_transport::*;
|
|
||||||
use super::codegen::simulation::*;
|
use super::codegen::simulation::*;
|
||||||
|
|
||||||
/// Transport-independent server implementation.
|
/// Protobuf-based simulation manager.
|
||||||
///
|
///
|
||||||
/// This implements the protobuf services without any transport-specific
|
/// A `SimulationService` enables the management of the lifecycle of a
|
||||||
/// management.
|
/// simulation, including creating a
|
||||||
pub(crate) struct GenericServer<F> {
|
/// [`Simulation`](crate::simulation::Simulation), invoking its methods and
|
||||||
sim_gen: F,
|
/// 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)>,
|
sim_context: Option<(Simulation, EndpointRegistry, KeyRegistry)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F> GenericServer<F>
|
impl SimulationService {
|
||||||
where
|
/// Creates a new `SimulationService` without any active simulation.
|
||||||
F: FnMut() -> (SimInit, EndpointRegistry) + Send + 'static,
|
///
|
||||||
{
|
/// The argument is a closure that is called every time the simulation is
|
||||||
/// Creates a new `GenericServer` without any active simulation.
|
/// (re)started by the remote client. It must create a new `SimInit` object
|
||||||
pub(crate) fn new(sim_gen: F) -> Self {
|
/// 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 {
|
Self {
|
||||||
sim_gen,
|
sim_gen: Box::new(sim_gen),
|
||||||
sim_context: None,
|
sim_context: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes an encoded `AnyRequest` message and returns an encoded
|
/// Processes an encoded `AnyRequest` message and returns an encoded reply.
|
||||||
/// `AnyReply`.
|
pub fn process_request<B>(&mut self, request_buf: B) -> Result<Vec<u8>, InvalidRequest>
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn service_request<B>(&mut self, request_buf: B) -> Vec<u8>
|
|
||||||
where
|
where
|
||||||
B: Buf,
|
B: Buf,
|
||||||
{
|
{
|
||||||
let reply = match AnyRequest::decode(request_buf) {
|
match AnyRequest::decode(request_buf) {
|
||||||
Ok(AnyRequest { request: Some(req) }) => match req {
|
Ok(AnyRequest { request: Some(req) }) => match req {
|
||||||
any_request::Request::InitRequest(request) => {
|
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_request::Request::TimeRequest(request) => {
|
||||||
any_reply::Reply::TimeReply(self.time(request))
|
Ok(self.time(request).encode_to_vec())
|
||||||
}
|
}
|
||||||
any_request::Request::StepRequest(request) => {
|
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_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_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_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_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_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_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_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_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 {
|
Ok(AnyRequest { request: None }) => Err(InvalidRequest {
|
||||||
code: ServerErrorCode::EmptyRequest as i32,
|
description: "the message did not contain any request".to_string(),
|
||||||
message: "the message did not contain any request".to_string(),
|
|
||||||
}),
|
}),
|
||||||
Err(err) => any_reply::Reply::Error(ServerError {
|
Err(err) => Err(InvalidRequest {
|
||||||
code: ServerErrorCode::UnknownRequest as i32,
|
description: format!("bad request: {}", err),
|
||||||
message: format!("bad request: {}", err),
|
|
||||||
}),
|
}),
|
||||||
};
|
}
|
||||||
|
|
||||||
let reply = AnyReply { reply: Some(reply) };
|
|
||||||
|
|
||||||
reply.encode_to_vec()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize a simulation with the provided time.
|
/// 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`.
|
/// Attempts a cast from a `MonotonicTime` to a protobuf `Timestamp`.
|
||||||
///
|
///
|
||||||
/// This will fail if the time is outside the protobuf-specified range for
|
/// This will fail if the time is outside the protobuf-specified range for
|
82
asynchronix/src/rpc/wasm.rs
Normal file
82
asynchronix/src/rpc/wasm.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user