Skip to content

Commit

Permalink
partial std refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dragazo committed Nov 16, 2023
1 parent ca77354 commit b5310bb
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 104 deletions.
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ strip = true

[features]
default = [
"std",
"cli",
"native-tls",
]
std = [
std = []
std-system = [
"std",
"tokio",
"futures",
"async-channel",
Expand All @@ -40,7 +41,7 @@ std = [
"time/std",
]
cli = [
"std",
"std-system",
"actix-web",
"actix-cors",
"clap",
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@

| name | default | description |
| ---- | ------- | ----------- |
| `std` | on | Enables the `std` crate dependency and access to the default [`StdSystem`](crate::std_system::StdSystem) implementation of [`System`](crate::runtime::System) |
| `cli` | on | Enables the `std` feature flag and additionally gives access to the [`cli`](crate::cli) submodule, which gives API access to the standard CLI (needed for syscall extensions) rather than having to write a CLI from scratch |
| `std` | on | Enables the `std` crate dependency and access to a number of helper types 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) |
Expand Down
1 change: 1 addition & 0 deletions examples/basic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use netsblox_vm::real_time::*;
use netsblox_vm::std_system::*;
use netsblox_vm::std_util::*;
use netsblox_vm::bytecode::*;
use netsblox_vm::process::*;
use netsblox_vm::runtime::*;
Expand Down
7 changes: 2 additions & 5 deletions src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ use alloc::rc::Rc;

use core::mem;

#[cfg(feature = "std")]
use std::io::{self, Write};

#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};

Expand Down Expand Up @@ -2574,7 +2571,7 @@ impl ByteCode {
}
/// Generates a hex dump of the stored code, including instructions and addresses.
#[cfg(feature = "std")]
pub fn dump_code(&self, f: &mut dyn Write) -> io::Result<()> {
pub fn dump_code(&self, f: &mut dyn std::io::Write) -> std::io::Result<()> {
let mut pos = 0;
while pos < self.code.len() {
let (ins, aft) = Instruction::read(&self.code, &self.data, pos);
Expand Down Expand Up @@ -2603,7 +2600,7 @@ impl ByteCode {
}
/// Generate a hex dump of the stored program data, including string literals and meta values.
#[cfg(feature = "std")]
pub fn dump_data(&self, f: &mut dyn Write) -> io::Result<()> {
pub fn dump_data(&self, f: &mut dyn std::io::Write) -> std::io::Result<()> {
for (i, bytes) in self.data.chunks(BYTES_PER_LINE).enumerate() {
write!(f, "{:08} ", i * BYTES_PER_LINE)?;
for &b in bytes {
Expand Down
1 change: 1 addition & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::gc::*;
use crate::json::*;
use crate::real_time::*;
use crate::std_system::*;
use crate::std_util::*;
use crate::bytecode::*;
use crate::runtime::*;
use crate::process::*;
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ mod meta {
include!(concat!(env!("OUT_DIR"), "/meta.rs"));
}

#[cfg(feature = "std")] pub mod std_system;
#[cfg(feature = "std")] pub mod std_util;
#[cfg(feature = "std-system")] pub mod std_system;
#[cfg(feature = "cli")] pub mod cli;

#[cfg(test)] mod test;
12 changes: 12 additions & 0 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,18 @@ pub enum Precision {
High,
}

/// 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,
}
/// A key type required for this client to send a reply message.
#[derive(Debug, Clone)]
pub struct InternReplyKey {
src_id: String,

Check warning on line 1792 in src/runtime.rs

View workflow job for this annotation

GitHub Actions / no-std

fields `src_id` and `request_id` are never read

Check warning on line 1792 in src/runtime.rs

View workflow job for this annotation

GitHub Actions / no-std (--release)

fields `src_id` and `request_id` are never read
request_id: String,
}

/// Represents all the features of an implementing system.
///
/// This type encodes any features that cannot be performed without platform-specific resources.
Expand Down
106 changes: 13 additions & 93 deletions src/std_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,11 @@ use crate::runtime::*;
use crate::process::*;
use crate::json::*;
use crate::gc::*;
use crate::std_util::*;
use crate::*;

const MESSAGE_REPLY_TIMEOUT: Duration = Duration::from_millis(1500);

/// A [`StdSystem`] 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,
}
/// A [`StdSystem`] key type required for this client to send a reply message.
#[derive(Debug, Clone)]
pub struct InternReplyKey {
src_id: String,
request_id: String,
}

struct Context {
base_url: String,
client_id: String,
Expand All @@ -56,89 +45,20 @@ struct Context {
role_name: String,
role_id: String,
}
struct RpcRequest<C: CustomTypes<StdSystem<C>>> {
struct RpcRequest<C: CustomTypes<S>, S: System<C>> {
service: String,
rpc: String,
args: Vec<(String, Json)>,
key: RequestKey<C>,
key: RequestKey<C, S>,
}
struct ReplyEntry {
expiry: OffsetDateTime,
value: Option<Json>,
}

/// A [`StdSystem`] key type for an asynchronous request.
pub struct RequestKey<C: CustomTypes<StdSystem<C>>>(Arc<Mutex<AsyncResult<Result<C::Intermediate, String>>>>);
impl<C: CustomTypes<StdSystem<C>>> RequestKey<C> {
fn poll(&self) -> AsyncResult<Result<C::Intermediate, String>> { self.0.lock().unwrap().poll() }
}
impl<C: CustomTypes<StdSystem<C>>> Key<Result<C::Intermediate, String>> for RequestKey<C> {
/// 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<C::Intermediate, String>) {
assert!(self.0.lock().unwrap().complete(value).is_ok())
}
}

/// A [`StdSystem`] key type for an asynchronous command.
pub struct CommandKey(Arc<Mutex<AsyncResult<Result<(), String>>>>);
impl CommandKey {
fn poll(&self) -> AsyncResult<Result<(), String>> { self.0.lock().unwrap().poll() }
}
impl Key<Result<(), String>> 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())
}
}

struct ClockCache {
value: Mutex<OffsetDateTime>,
precision: Precision,
}

/// A clock with optional coarse granularity.
pub struct Clock {
utc_offset: UtcOffset,
cache: Option<ClockCache>,
}
impl Clock {
/// Creates a new [`Clock`] with the specified [`UtcOffset`] and (optional) cache [`Precision`] (see [`Clock::read`]).
pub fn new(utc_offset: UtcOffset, cache_precision: Option<Precision>) -> Self {
let mut res = Self { utc_offset, cache: None };
if let Some(precision) = cache_precision {
res.cache = Some(ClockCache { value: Mutex::new(res.update()), precision });
}
res
}
/// Reads the current time with the specified level of precision.
/// If caching was enabled by [`Clock::new`], requests at or below the cache precision level will use the cached timestamp.
/// In any other case, the real current time is fetched by [`Clock::update`] and the result is stored in the cache if caching is enabled.
pub fn read(&self, precision: Precision) -> OffsetDateTime {
match &self.cache {
Some(cache) if precision <= cache.precision => *cache.value.lock().unwrap(),
_ => self.update(),
}
}
/// Reads the real world time and stores the result in the cache if caching was enabled by [`Clock::new`].
pub fn update(&self) -> OffsetDateTime {
let t = OffsetDateTime::now_utc().to_offset(self.utc_offset);
if let Some(cache) = &self.cache {
*cache.value.lock().unwrap() = t;
}
t
}
}

type MessageReplies = BTreeMap<ExternReplyKey, ReplyEntry>;

async fn call_rpc_async<C: CustomTypes<StdSystem<C>>>(context: &Context, client: &reqwest::Client, service: &str, rpc: &str, args: &[(&str, &Json)]) -> Result<SimpleValue, String> {
async fn call_rpc_async<C: CustomTypes<S>, S: System<C>>(context: &Context, client: &reqwest::Client, service: &str, rpc: &str, args: &[(&str, &Json)]) -> Result<SimpleValue, String> {
let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
let url = format!("{services_url}/{service}/{rpc}?clientId={client_id}&t={time}",
services_url = context.services_url, client_id = context.client_id);
Expand Down Expand Up @@ -182,7 +102,7 @@ pub struct StdSystem<C: CustomTypes<StdSystem<C>>> {
rng: Mutex<ChaChaRng>,
clock: Arc<Clock>,

rpc_request_pipe: Sender<RpcRequest<C>>,
rpc_request_pipe: Sender<RpcRequest<C, Self>>,

message_replies: Arc<Mutex<MessageReplies>>,
message_sender: Sender<OutgoingMessage<C, Self>>,
Expand Down Expand Up @@ -339,11 +259,11 @@ impl<C: CustomTypes<StdSystem<C>>> StdSystem<C> {
let (sender, receiver) = channel();

#[tokio::main(flavor = "multi_thread", worker_threads = 1)]
async fn handler<C: CustomTypes<StdSystem<C>>>(client: Arc<reqwest::Client>, context: Arc<Context>, receiver: Receiver<RpcRequest<C>>) {
async fn handler<C: CustomTypes<StdSystem<C>>>(client: Arc<reqwest::Client>, context: Arc<Context>, receiver: Receiver<RpcRequest<C, StdSystem<C>>>) {
while let Ok(request) = receiver.recv() {
let (client, context) = (client.clone(), context.clone());
tokio::spawn(async move {
let res = call_rpc_async::<C>(&context, &client, &request.service, &request.rpc, &request.args.iter().map(|x| (x.0.as_str(), &x.1)).collect::<Vec<_>>()).await;
let res = call_rpc_async::<C, StdSystem<C>>(&context, &client, &request.service, &request.rpc, &request.args.iter().map(|x| (x.0.as_str(), &x.1)).collect::<Vec<_>>()).await;
request.key.complete(res.map(Into::into));
});
}
Expand Down Expand Up @@ -390,7 +310,7 @@ impl<C: CustomTypes<StdSystem<C>>> StdSystem<C> {
/// Asynchronously calls an RPC and returns the result.
/// This function directly makes requests to NetsBlox, bypassing any RPC hook defined by [`Config`].
pub async fn call_rpc_async(&self, service: &str, rpc: &str, args: &[(&str, &Json)]) -> Result<SimpleValue, String> {
call_rpc_async::<C>(&self.context, &self.client, service, rpc, args).await
call_rpc_async::<C, Self>(&self.context, &self.client, service, rpc, args).await
}

/// Gets the public id of the running system that can be used to send messages to this client.
Expand Down Expand Up @@ -434,7 +354,7 @@ impl<C: CustomTypes<StdSystem<C>>> StdSystem<C> {
}
}
impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {
type RequestKey = RequestKey<C>;
type RequestKey = RequestKey<C, Self>;
type CommandKey = CommandKey;

type ExternReplyKey = ExternReplyKey;
Expand All @@ -454,8 +374,8 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {

Ok(match self.config.request.as_ref() {
Some(handler) => {
let key = RequestKey(Arc::new(Mutex::new(AsyncResult::new())));
match handler(mc, RequestKey(key.0.clone()), request, proc) {
let key = RequestKey::new();
match handler(mc, key.clone(), request, proc) {
RequestStatus::Handled => key,
RequestStatus::UseDefault { key: _, request } => return Err(ErrorCause::NotSupported { feature: request.feature() }),
}
Expand All @@ -481,8 +401,8 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {

Ok(match self.config.command.as_ref() {
Some(handler) => {
let key = CommandKey(Arc::new(Mutex::new(AsyncResult::new())));
match handler(mc, CommandKey(key.0.clone()), command, proc) {
let key = CommandKey::new();
match handler(mc, key.clone(), command, proc) {
CommandStatus::Handled => key,
CommandStatus::UseDefault { key: _, command } => return Err(ErrorCause::NotSupported { feature: command.feature() }),
}
Expand Down
89 changes: 89 additions & 0 deletions src/std_util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use alloc::string::String;

use std::sync::{Arc, Mutex};

use crate::real_time::*;
use crate::runtime::*;
use crate::*;

struct ClockCache {
value: Mutex<OffsetDateTime>,
precision: Precision,
}

/// A clock with optional coarse granularity.
pub struct Clock {
utc_offset: UtcOffset,
cache: Option<ClockCache>,
}
impl Clock {
/// Creates a new [`Clock`] with the specified [`UtcOffset`] and (optional) cache [`Precision`] (see [`Clock::read`]).
pub fn new(utc_offset: UtcOffset, cache_precision: Option<Precision>) -> Self {
let mut res = Self { utc_offset, cache: None };
if let Some(precision) = cache_precision {
res.cache = Some(ClockCache { value: Mutex::new(res.update()), precision });
}
res
}
/// Reads the current time with the specified level of precision.
/// If caching was enabled by [`Clock::new`], requests at or below the cache precision level will use the cached timestamp.
/// In any other case, the real current time is fetched by [`Clock::update`] and the result is stored in the cache if caching is enabled.
pub fn read(&self, precision: Precision) -> OffsetDateTime {
match &self.cache {
Some(cache) if precision <= cache.precision => *cache.value.lock().unwrap(),
_ => self.update(),
}
}
/// Reads the real world time and stores the result in the cache if caching was enabled by [`Clock::new`].
pub fn update(&self) -> OffsetDateTime {
let t = OffsetDateTime::now_utc().to_offset(self.utc_offset);
if let Some(cache) = &self.cache {
*cache.value.lock().unwrap() = t;
}
t
}
}

/// A shared [`Key`] type for an asynchronous request.
#[derive(Educe)]
#[educe(Clone)]
pub struct RequestKey<C: CustomTypes<S>, S: System<C>>(Arc<Mutex<AsyncResult<Result<C::Intermediate, String>>>>);
impl<C: CustomTypes<S>, S: System<C>> RequestKey<C, S> {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(AsyncResult::new())))
}
pub fn poll(&self) -> AsyncResult<Result<C::Intermediate, String>> {
self.0.lock().unwrap().poll()
}
}
impl<C: CustomTypes<S>, S: System<C>> Key<Result<C::Intermediate, String>> for RequestKey<C, S> {
/// 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<C::Intermediate, String>) {
assert!(self.0.lock().unwrap().complete(value).is_ok())
}
}

/// A shared [`Key`] type for an asynchronous command.
#[derive(Clone)]
pub struct CommandKey(Arc<Mutex<AsyncResult<Result<(), String>>>>);
impl CommandKey {
pub fn new() -> Self {
Self(Arc::new(Mutex::new(AsyncResult::new())))
}
pub fn poll(&self) -> AsyncResult<Result<(), String>> {
self.0.lock().unwrap().poll()
}
}
impl Key<Result<(), String>> 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())
}
}
1 change: 1 addition & 0 deletions src/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use core::iter;
use crate::runtime::*;
use crate::process::*;
use crate::std_system::*;
use crate::std_util::*;
use crate::gc::*;

mod process;
Expand Down

0 comments on commit b5310bb

Please sign in to comment.