Skip to content

Commit

Permalink
finish std refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dragazo committed Nov 16, 2023
1 parent b5310bb commit f05faaa
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 86 deletions.
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -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

Expand Down
6 changes: 3 additions & 3 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ struct Handler {
enum Defer<C: CustomTypes<S>, S: System<C>> {
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 },
}
Expand All @@ -189,7 +189,7 @@ pub struct ProcContext<'gc, C: CustomTypes<S>, S: System<C>> {
pub locals: SymbolTable<'gc, C, S>,
#[collect(require_static)] pub start_pos: usize,
#[collect(require_static)] pub barrier: Option<Barrier>,
#[collect(require_static)] pub reply_key: Option<S::InternReplyKey>,
#[collect(require_static)] pub reply_key: Option<InternReplyKey>,
#[collect(require_static)] pub local_message: Option<String>,
}

Expand All @@ -204,7 +204,7 @@ pub struct Process<'gc, C: CustomTypes<S>, S: System<C>> {
#[collect(require_static)] pub state: C::ProcessState,
#[collect(require_static)] pos: usize,
#[collect(require_static)] barrier: Option<Barrier>,
#[collect(require_static)] reply_key: Option<S::InternReplyKey>,
#[collect(require_static)] reply_key: Option<InternReplyKey>,
#[collect(require_static)] warp_counter: usize,
call_stack: Vec<CallStackEntry<'gc, C, S>>,
value_stack: Vec<Value<'gc, C, S>>,
Expand Down
2 changes: 1 addition & 1 deletion src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub enum ProjectStep<'gc, C: CustomTypes<S>, S: System<C>> {
pub struct PartialProcContext<'gc, C: CustomTypes<S>, S: System<C>> {
pub locals: SymbolTable<'gc, C, S>,
#[collect(require_static)] pub barrier: Option<Barrier>,
#[collect(require_static)] pub reply_key: Option<S::InternReplyKey>,
#[collect(require_static)] pub reply_key: Option<InternReplyKey>,
#[collect(require_static)] pub local_message: Option<String>,
}

Expand Down
30 changes: 12 additions & 18 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1441,7 +1441,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> GlobalContext<'gc, C, S> {
}
}

pub enum OutgoingMessage<C: CustomTypes<S>, S: System<C>> {
pub enum OutgoingMessage {
Normal {
msg_type: String,
values: Vec<(String, Json)>,
Expand All @@ -1451,17 +1451,17 @@ pub enum OutgoingMessage<C: CustomTypes<S>, S: System<C>> {
msg_type: String,
values: Vec<(String, Json)>,
targets: Vec<String>,
reply_key: S::ExternReplyKey,
reply_key: ExternReplyKey,
},
Reply {
value: Json,
reply_key: S::InternReplyKey,
reply_key: InternReplyKey,
},
}
pub struct IncomingMessage<C: CustomTypes<S>, S: System<C>> {
pub struct IncomingMessage {
pub msg_type: String,
pub values: Vec<(String, SimpleValue)>,
pub reply_key: Option<S::InternReplyKey>,
pub reply_key: Option<InternReplyKey>,
}

/// A blocking handle for a [`BarrierCondition`].
Expand Down Expand Up @@ -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.
Expand All @@ -1805,12 +1805,6 @@ pub trait System<C: CustomTypes<Self>>: 'static + Sized {
/// Key type used to await the completion of an asynchronous command.
type CommandKey: 'static + Key<Result<(), String>>;

/// 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.
Expand Down Expand Up @@ -1838,15 +1832,15 @@ pub trait System<C: CustomTypes<Self>>: '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<String>, expect_reply: bool) -> Result<Option<Self::ExternReplyKey>, ErrorCause<C, Self>>;
fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec<String>, expect_reply: bool) -> Result<Option<ExternReplyKey>, ErrorCause<C, Self>>;
/// 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<Option<Json>>;
fn poll_reply(&self, key: &ExternReplyKey) -> AsyncResult<Option<Json>>;
/// Sends a reply to the sender of a blocking message this client received.
fn send_reply(&self, key: Self::InternReplyKey, value: Json) -> Result<(), ErrorCause<C, Self>>;
fn send_reply(&self, key: InternReplyKey, value: Json) -> Result<(), ErrorCause<C, Self>>;
/// 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<IncomingMessage<C, Self>>;
fn receive_message(&self) -> Option<IncomingMessage>;
}
46 changes: 21 additions & 25 deletions src/std_system.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -49,7 +44,7 @@ struct RpcRequest<C: CustomTypes<S>, S: System<C>> {
service: String,
rpc: String,
args: Vec<(String, Json)>,
key: RequestKey<C, S>,
key: AsyncKey<Result<C::Intermediate, String>>,
}
struct ReplyEntry {
expiry: OffsetDateTime,
Expand Down Expand Up @@ -95,6 +90,10 @@ async fn call_rpc_async<C: CustomTypes<S>, S: System<C>>(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<C: CustomTypes<StdSystem<C>>> {
config: Config<C, Self>,
context: Arc<Context>,
Expand All @@ -105,18 +104,18 @@ pub struct StdSystem<C: CustomTypes<StdSystem<C>>> {
rpc_request_pipe: Sender<RpcRequest<C, Self>>,

message_replies: Arc<Mutex<MessageReplies>>,
message_sender: Sender<OutgoingMessage<C, Self>>,
message_injector: Sender<IncomingMessage<C, Self>>,
message_receiver: Receiver<IncomingMessage<C, Self>>,
message_sender: Sender<OutgoingMessage>,
message_injector: Sender<IncomingMessage>,
message_receiver: Receiver<IncomingMessage>,
}
impl<C: CustomTypes<StdSystem<C>>> StdSystem<C> {
/// 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<C, Self>, clock: Arc<Clock>) -> 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<C, Self>, clock: Arc<Clock>) -> Self {
let configuration = reqwest::get(format!("{base_url}/configuration")).await.unwrap().json::<BTreeMap<String, Json>>().await.unwrap();
let services_hosts = configuration["servicesHosts"].as_array().unwrap();
Expand All @@ -139,7 +138,7 @@ impl<C: CustomTypes<StdSystem<C>>> StdSystem<C> {
let (in_sender, in_receiver) = channel();

#[tokio::main(flavor = "multi_thread", worker_threads = 1)]
async fn handler<C: CustomTypes<StdSystem<C>>>(base_url: String, client_id: String, project_name: String, message_replies: Arc<Mutex<MessageReplies>>, out_receiver: Receiver<OutgoingMessage<C, StdSystem<C>>>, in_sender: Sender<IncomingMessage<C, StdSystem<C>>>) {
async fn handler(base_url: String, client_id: String, project_name: String, message_replies: Arc<Mutex<MessageReplies>>, out_receiver: Receiver<OutgoingMessage>, in_sender: Sender<IncomingMessage>) {
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();
Expand Down Expand Up @@ -354,11 +353,8 @@ impl<C: CustomTypes<StdSystem<C>>> StdSystem<C> {
}
}
impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {
type RequestKey = RequestKey<C, Self>;
type CommandKey = CommandKey;

type ExternReplyKey = ExternReplyKey;
type InternReplyKey = InternReplyKey;
type RequestKey = AsyncKey<Result<C::Intermediate, String>>;
type CommandKey = AsyncKey<Result<(), String>>;

fn rand<T: SampleUniform, R: SampleRange<T>>(&self, range: R) -> T {
self.rng.lock().unwrap().gen_range(range)
Expand All @@ -374,7 +370,7 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {

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() }),
Expand All @@ -401,7 +397,7 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {

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() }),
Expand All @@ -417,7 +413,7 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {
Ok(key.poll())
}

fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec<String>, expect_reply: bool) -> Result<Option<Self::ExternReplyKey>, ErrorCause<C, StdSystem<C>>> {
fn send_message(&self, msg_type: String, values: Vec<(String, Json)>, targets: Vec<String>, expect_reply: bool) -> Result<Option<ExternReplyKey>, ErrorCause<C, StdSystem<C>>> {
let (msg, reply_key) = match expect_reply {
false => (OutgoingMessage::Normal { msg_type, values, targets }, None),
true => {
Expand All @@ -430,7 +426,7 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {
self.message_sender.send(msg).unwrap();
Ok(reply_key)
}
fn poll_reply(&self, key: &Self::ExternReplyKey) -> AsyncResult<Option<Json>> {
fn poll_reply(&self, key: &ExternReplyKey) -> AsyncResult<Option<Json>> {
let mut message_replies = self.message_replies.lock().unwrap();
let entry = message_replies.get(key).unwrap();
if entry.value.is_some() {
Expand All @@ -442,11 +438,11 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {
}
AsyncResult::Pending
}
fn send_reply(&self, key: Self::InternReplyKey, value: Json) -> Result<(), ErrorCause<C, Self>> {
fn send_reply(&self, key: InternReplyKey, value: Json) -> Result<(), ErrorCause<C, Self>> {
self.message_sender.send(OutgoingMessage::Reply { value, reply_key: key }).unwrap();
Ok(())
}
fn receive_message(&self) -> Option<IncomingMessage<C, Self>> {
fn receive_message(&self) -> Option<IncomingMessage> {
self.message_receiver.try_recv().ok()
}
}
Loading

0 comments on commit f05faaa

Please sign in to comment.