From f05faaa747ce21f0b0533d9319c1ba5662e6b55a Mon Sep 17 00:00:00 2001 From: Devin Jean Date: Thu, 16 Nov 2023 13:00:47 -0600 Subject: [PATCH] finish std refactor --- README.md | 19 +++++++++++++------ src/process.rs | 6 +++--- src/project.rs | 2 +- src/runtime.rs | 30 ++++++++++++------------------ src/std_system.rs | 46 +++++++++++++++++++++------------------------- src/std_util.rs | 45 ++++++++++++--------------------------------- 6 files changed, 62 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 02e5a2f..69ce1e4 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,16 @@ | name | default | description | | ---- | ------- | ----------- | -| `std` | on | Enables the `std` crate dependency and access to a number of helper types that depend on the standard library | +| `std` | on | Enables the `std` crate dependency and access to a number of [helper types](crate::std_util) that depend on the standard library | | `std-system` | on | Enables the `std` feature flag and also the [`StdSystem`](crate::std_system::StdSystem) implementation of [`System`](crate::runtime::System) | | `cli` | on | Enables the `std-system` feature flag and additionally gives access to the [`cli`](crate::cli) submodule, which gives API access to the standard CLI rather than having to write a CLI from scratch | | `serde` | on | Enables serialization of some types | -| `native-tls` | on | Enables the `native-tls` feature for TLS-capable dependencies (only used if `std` is also enabled) | -| `native-tls-vendored` | off | Enables the `native-tls-vendored` feature for TLS-capable dependencies (only used if `std` is also enabled) | -| `rustls-tls-native-roots` | off | Enables the `rustls-tls-native-roots` feature for TLS-capable dependencies (only used if `std` is also enabled) | -| `rustls-tls-webpki-roots` | off | Enables the `rustls-tls-webpki-roots` feature for TLS-capable dependencies (only used if `std` is also enabled) | +| `native-tls` | on | Enables the `native-tls` feature for TLS-capable dependencies (only used if `std-system` is also enabled) | +| `native-tls-vendored` | off | Enables the `native-tls-vendored` feature for TLS-capable dependencies (only used if `std-system` is also enabled) | +| `rustls-tls-native-roots` | off | Enables the `rustls-tls-native-roots` feature for TLS-capable dependencies (only used if `std-system` is also enabled) | +| `rustls-tls-webpki-roots` | off | Enables the `rustls-tls-webpki-roots` feature for TLS-capable dependencies (only used if `std-system` is also enabled) | -Note that if `std` is enabled, one of the TLS feature flags must also be enabled in order to connect to the NetsBlox server with [`StdSystem`](crate::std_system::StdSystem). +Note that if `std-system` is enabled, one of the TLS feature flags must also be enabled in order to connect to the NetsBlox server with [`StdSystem`](crate::std_system::StdSystem). The `native-tls` feature is enabled by default to support this on common desktop and server environments; however you may need to disable default features and explicitly opt into a different TLS option for other targets (e.g., Android or iOS). @@ -34,6 +34,13 @@ netsblox_vm = { version = "...", default-features = false } ``` Note that this precludes access to [`StdSystem`](crate::std_system::StdSystem), meaning a new implementation of [`System`](crate::runtime::System) would be required for your target platform. +If your target platform supports the standard library but not standard networking crates (e.g., esp32), you may use the `std` feature flag without the `std-system` feature flag, which will aid in creating a custom implementation of [`System`](crate::runtime::System). + +## Example + +Some boilerplate code is required for using this crate to implement a fully-capable, isolated system. +An example CLI program with basic stdout printing features is available [here](https://github.com/dragazo/netsblox-vm/blob/master/examples/basic.rs). +Note that your dependencies should include both this crate, as well as the [specific version](https://github.com/dragazo/netsblox-vm/blob/master/Cargo.toml) of `gc-arena` used by this crate (or else any derived [`Collect`](crate::gc::Collect) implementations will be incompatible). ## CLI Installation diff --git a/src/process.rs b/src/process.rs index ac1503e..726369b 100644 --- a/src/process.rs +++ b/src/process.rs @@ -170,7 +170,7 @@ struct Handler { enum Defer, S: System> { Request { key: S::RequestKey, aft_pos: usize, action: RequestAction }, Command { key: S::CommandKey, aft_pos: usize }, - MessageReply { key: S::ExternReplyKey, aft_pos: usize }, + MessageReply { key: ExternReplyKey, aft_pos: usize }, Barrier { condition: BarrierCondition, aft_pos: usize }, Sleep { until: u64, aft_pos: usize }, } @@ -189,7 +189,7 @@ pub struct ProcContext<'gc, C: CustomTypes, S: System> { pub locals: SymbolTable<'gc, C, S>, #[collect(require_static)] pub start_pos: usize, #[collect(require_static)] pub barrier: Option, - #[collect(require_static)] pub reply_key: Option, + #[collect(require_static)] pub reply_key: Option, #[collect(require_static)] pub local_message: Option, } @@ -204,7 +204,7 @@ pub struct Process<'gc, C: CustomTypes, S: System> { #[collect(require_static)] pub state: C::ProcessState, #[collect(require_static)] pos: usize, #[collect(require_static)] barrier: Option, - #[collect(require_static)] reply_key: Option, + #[collect(require_static)] reply_key: Option, #[collect(require_static)] warp_counter: usize, call_stack: Vec>, value_stack: Vec>, diff --git a/src/project.rs b/src/project.rs index 5de8b54..6ecfb8a 100644 --- a/src/project.rs +++ b/src/project.rs @@ -101,7 +101,7 @@ pub enum ProjectStep<'gc, C: CustomTypes, S: System> { pub struct PartialProcContext<'gc, C: CustomTypes, S: System> { pub locals: SymbolTable<'gc, C, S>, #[collect(require_static)] pub barrier: Option, - #[collect(require_static)] pub reply_key: Option, + #[collect(require_static)] pub reply_key: Option, #[collect(require_static)] pub local_message: Option, } diff --git a/src/runtime.rs b/src/runtime.rs index 34ad1ff..cf2d927 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1441,7 +1441,7 @@ impl<'gc, C: CustomTypes, S: System> GlobalContext<'gc, C, S> { } } -pub enum OutgoingMessage, S: System> { +pub enum OutgoingMessage { Normal { msg_type: String, values: Vec<(String, Json)>, @@ -1451,17 +1451,17 @@ pub enum OutgoingMessage, S: System> { msg_type: String, values: Vec<(String, Json)>, targets: Vec, - reply_key: S::ExternReplyKey, + reply_key: ExternReplyKey, }, Reply { value: Json, - reply_key: S::InternReplyKey, + reply_key: InternReplyKey, }, } -pub struct IncomingMessage, S: System> { +pub struct IncomingMessage { pub msg_type: String, pub values: Vec<(String, SimpleValue)>, - pub reply_key: Option, + pub reply_key: Option, } /// A blocking handle for a [`BarrierCondition`]. @@ -1784,13 +1784,13 @@ pub enum Precision { /// A key type used to await a reply message from an external source. #[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)] pub struct ExternReplyKey { - request_id: String, + pub request_id: String, } /// A key type required for this client to send a reply message. #[derive(Debug, Clone)] pub struct InternReplyKey { - src_id: String, - request_id: String, + pub src_id: String, + pub request_id: String, } /// Represents all the features of an implementing system. @@ -1805,12 +1805,6 @@ pub trait System>: 'static + Sized { /// Key type used to await the completion of an asynchronous command. type CommandKey: 'static + Key>; - /// Key type used to await the result of a "send message and wait" block (response from target). - type ExternReplyKey: 'static; - /// Key type used to reply to a message that was sent to this client with the expectation of receiving a response. - /// This type is required to be [`Clone`] because there can be multiple message handlers for the same message type. - type InternReplyKey: 'static + Clone; - /// Gets a random value sampled from the given `range`, which is assumed to be non-empty. /// The input for this generic function is such that it is compatible with [`rand::Rng::gen_range`], /// which makes it possible to implement this function with any random provider under the [`rand`] crate standard. @@ -1838,15 +1832,15 @@ pub trait System>: 'static + Sized { /// Sends a message containing a set of named `values` to each of the specified `targets`. /// The `expect_reply` value controls whether or not to use a reply mechanism to asynchronously receive a response from the target(s). /// In the case that there are multiple targets, only the first reply (if any) should be used. - fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec, expect_reply: bool) -> Result, ErrorCause>; + fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec, expect_reply: bool) -> Result, ErrorCause>; /// Polls for a response from a client initiated by [`System::send_message`]. /// If the client responds, a value of [`Some(x)`] is returned. /// The system may elect to impose a timeout for reply results, in which case [`None`] is returned instead. - fn poll_reply(&self, key: &Self::ExternReplyKey) -> AsyncResult>; + fn poll_reply(&self, key: &ExternReplyKey) -> AsyncResult>; /// Sends a reply to the sender of a blocking message this client received. - fn send_reply(&self, key: Self::InternReplyKey, value: Json) -> Result<(), ErrorCause>; + fn send_reply(&self, key: InternReplyKey, value: Json) -> Result<(), ErrorCause>; /// Attempts to receive a message from the message buffer. /// This operation is always non-blocking and returns [`None`] if there are no messages in the buffer. /// If a message is received, a tuple of form `(msg_type, values, reply_key)` is returned. - fn receive_message(&self) -> Option>; + fn receive_message(&self) -> Option; } diff --git a/src/std_system.rs b/src/std_system.rs index 875b7f6..c89a098 100644 --- a/src/std_system.rs +++ b/src/std_system.rs @@ -1,11 +1,6 @@ -//! An customizable implementation of [`System`]. +//! An customizable implementation of [`System`] which depends on the standard library. //! -//! This submodule is only available with the [`std`](crate) feature flag. -//! -//! The primary type of interest is [`StdSystem`], which implements [`System`]. -//! [`StdSystem`] can be configured with [`CustomTypes`] and [`Config`], -//! which together allow for the definition of any external features (e.g., defining syscalls), -//! as well as overriding default behavior (e.g., rpc intercepting). +//! This submodule is only available with the [`std-system`](crate) feature flag. use alloc::string::{String, ToString}; use alloc::collections::BTreeMap; @@ -49,7 +44,7 @@ struct RpcRequest, S: System> { service: String, rpc: String, args: Vec<(String, Json)>, - key: RequestKey, + key: AsyncKey>, } struct ReplyEntry { expiry: OffsetDateTime, @@ -95,6 +90,10 @@ async fn call_rpc_async, S: System>(context: &Context, clie } /// A type implementing the [`System`] trait which supports all features. +/// +/// [`StdSystem`] can be configured with [`CustomTypes`] and [`Config`], +/// which together allow for the definition of any external features (e.g., defining syscalls), +/// as well as overriding default behavior (e.g., rpc intercepting). pub struct StdSystem>> { config: Config, context: Arc, @@ -105,18 +104,18 @@ pub struct StdSystem>> { rpc_request_pipe: Sender>, message_replies: Arc>, - message_sender: Sender>, - message_injector: Sender>, - message_receiver: Receiver>, + message_sender: Sender, + message_injector: Sender, + message_receiver: Receiver, } impl>> StdSystem { /// Equivalent to [`StdSystem::new_async`] except that it can be executed outside of async context. - /// Note that using this from within async context will result in a panic from `tokio` trying to create a runtime within a runtime. + /// Note that using this from within async context can result in a panic from, e.g., `tokio` trying to create a runtime within a runtime. #[tokio::main(flavor = "current_thread")] pub async fn new_sync(base_url: String, project_name: Option<&str>, config: Config, clock: Arc) -> Self { Self::new_async(base_url, project_name, config, clock).await } - /// Initializes a new instance of [`StdSystem`] targeting the given NetsBlox server base url (e.g., `https://cloud.netsblox.org`). + /// Initializes a new instance of [`StdSystem`] targeting the given NetsBlox server base url, e.g., `https://cloud.netsblox.org`. pub async fn new_async(base_url: String, project_name: Option<&str>, config: Config, clock: Arc) -> Self { let configuration = reqwest::get(format!("{base_url}/configuration")).await.unwrap().json::>().await.unwrap(); let services_hosts = configuration["servicesHosts"].as_array().unwrap(); @@ -139,7 +138,7 @@ impl>> StdSystem { let (in_sender, in_receiver) = channel(); #[tokio::main(flavor = "multi_thread", worker_threads = 1)] - async fn handler>>(base_url: String, client_id: String, project_name: String, message_replies: Arc>, out_receiver: Receiver>>, in_sender: Sender>>) { + async fn handler(base_url: String, client_id: String, project_name: String, message_replies: Arc>, out_receiver: Receiver, in_sender: Sender) { let ws_url = format!("{}/network/{client_id}/connect", if let Some(x) = base_url.strip_prefix("http") { format!("ws{x}") } else { format!("wss://{base_url}") }); let (ws, _) = tokio_tungstenite::connect_async(ws_url).await.unwrap(); let (mut ws_sender, ws_receiver) = ws.split(); @@ -354,11 +353,8 @@ impl>> StdSystem { } } impl>> System for StdSystem { - type RequestKey = RequestKey; - type CommandKey = CommandKey; - - type ExternReplyKey = ExternReplyKey; - type InternReplyKey = InternReplyKey; + type RequestKey = AsyncKey>; + type CommandKey = AsyncKey>; fn rand>(&self, range: R) -> T { self.rng.lock().unwrap().gen_range(range) @@ -374,7 +370,7 @@ impl>> System for StdSystem { Ok(match self.config.request.as_ref() { Some(handler) => { - let key = RequestKey::new(); + let key = AsyncKey::new(); match handler(mc, key.clone(), request, proc) { RequestStatus::Handled => key, RequestStatus::UseDefault { key: _, request } => return Err(ErrorCause::NotSupported { feature: request.feature() }), @@ -401,7 +397,7 @@ impl>> System for StdSystem { Ok(match self.config.command.as_ref() { Some(handler) => { - let key = CommandKey::new(); + let key = AsyncKey::new(); match handler(mc, key.clone(), command, proc) { CommandStatus::Handled => key, CommandStatus::UseDefault { key: _, command } => return Err(ErrorCause::NotSupported { feature: command.feature() }), @@ -417,7 +413,7 @@ impl>> System for StdSystem { Ok(key.poll()) } - fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec, expect_reply: bool) -> Result, ErrorCause>> { + fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec, expect_reply: bool) -> Result, ErrorCause>> { let (msg, reply_key) = match expect_reply { false => (OutgoingMessage::Normal { msg_type, values, targets }, None), true => { @@ -430,7 +426,7 @@ impl>> System for StdSystem { self.message_sender.send(msg).unwrap(); Ok(reply_key) } - fn poll_reply(&self, key: &Self::ExternReplyKey) -> AsyncResult> { + fn poll_reply(&self, key: &ExternReplyKey) -> AsyncResult> { let mut message_replies = self.message_replies.lock().unwrap(); let entry = message_replies.get(key).unwrap(); if entry.value.is_some() { @@ -442,11 +438,11 @@ impl>> System for StdSystem { } AsyncResult::Pending } - fn send_reply(&self, key: Self::InternReplyKey, value: Json) -> Result<(), ErrorCause> { + fn send_reply(&self, key: InternReplyKey, value: Json) -> Result<(), ErrorCause> { self.message_sender.send(OutgoingMessage::Reply { value, reply_key: key }).unwrap(); Ok(()) } - fn receive_message(&self) -> Option> { + fn receive_message(&self) -> Option { self.message_receiver.try_recv().ok() } } diff --git a/src/std_util.rs b/src/std_util.rs index e4a1e2c..cc4f2c8 100644 --- a/src/std_util.rs +++ b/src/std_util.rs @@ -1,4 +1,6 @@ -use alloc::string::String; +//! A collection of helper types which depend on the standard library. +//! +//! Note that this module is only available with the [`std`](crate) feature flag. use std::sync::{Arc, Mutex}; @@ -44,46 +46,23 @@ impl Clock { } } -/// A shared [`Key`] type for an asynchronous request. +/// A [`Key`] type which wraps a shared [`AsyncResult`]. #[derive(Educe)] #[educe(Clone)] -pub struct RequestKey, S: System>(Arc>>>); -impl, S: System> RequestKey { +pub struct AsyncKey(Arc>>); +impl AsyncKey { + /// Equivalent to [`AsyncResult::new`] to create a new shared [`AsyncResult`]. pub fn new() -> Self { Self(Arc::new(Mutex::new(AsyncResult::new()))) } - pub fn poll(&self) -> AsyncResult> { + /// Equivalent to [`AsyncResult::poll`] on the shared [`AsyncResult`]. + pub fn poll(&self) -> AsyncResult { self.0.lock().unwrap().poll() } } -impl, S: System> Key> for RequestKey { - /// Completes the request with the given result. - /// A value of [`Ok`] denotes a successful request, whose value will be returned to the system - /// after conversion under [`CustomTypes::from_intermediate`]. - /// A value of [`Err`] denotes a failed request, which will be returned as an error to the runtime, - /// subject to the caller's [`ErrorScheme`] setting. - fn complete(self, value: Result) { +impl Key for AsyncKey { + /// Equivalent to [`AsyncResult::complete`] on the shared [`AsyncResult`] + fn complete(self, value: T) { assert!(self.0.lock().unwrap().complete(value).is_ok()) } } - -/// A shared [`Key`] type for an asynchronous command. -#[derive(Clone)] -pub struct CommandKey(Arc>>>); -impl CommandKey { - pub fn new() -> Self { - Self(Arc::new(Mutex::new(AsyncResult::new()))) - } - pub fn poll(&self) -> AsyncResult> { - self.0.lock().unwrap().poll() - } -} -impl Key> for CommandKey { - /// Completes the command. - /// A value of [`Ok`] denotes a successful command. - /// A value of [`Err`] denotes a failed command, which will be returned as an error to the runtime, - /// subject to the caller's [`ErrorScheme`] setting. - fn complete(self, value: Result<(), String>) { - assert!(self.0.lock().unwrap().complete(value).is_ok()) - } -} \ No newline at end of file