Skip to content

Commit

Permalink
finish time refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dragazo committed Nov 2, 2023
1 parent 093af6e commit b0d90c8
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 196 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "netsblox-vm"
version = "0.2.16"
version = "0.2.17"
edition = "2021"
license = "MIT OR Apache-2.0"
authors = ["Devin Jean <[email protected]>"]
Expand Down
22 changes: 13 additions & 9 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ fn open_project<'a>(content: &str, role: Option<&'a str>) -> Result<(String, ast
Ok((parsed.name, role))
}

fn run_proj_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: String, role: &ast::Role, overrides: Config<C, StdSystem<C>>, utc_offset: UtcOffset) {
fn run_proj_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: String, role: &ast::Role, overrides: Config<C, StdSystem<C>>, clock: Arc<Clock>) {
terminal::enable_raw_mode().unwrap();
execute!(stdout(), cursor::Hide).unwrap();
let _tty_mode_guard = AtExit::new(|| {
Expand Down Expand Up @@ -215,7 +215,6 @@ fn run_proj_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: String
},
});

let clock = Arc::new(FineClock::new(utc_offset));
let system = Rc::new(StdSystem::new_sync(server, Some(project_name), config, clock));
let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_IDLE_SLEEP, Box::new(|| thread::sleep(IDLE_SLEEP_TIME)));
print!("public id: {}\r\n", system.get_public_id());
Expand Down Expand Up @@ -295,7 +294,7 @@ fn run_proj_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: String

execute!(stdout(), terminal::Clear(ClearType::CurrentLine)).unwrap();
}
fn run_proj_non_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: String, role: &ast::Role, overrides: Config<C, StdSystem<C>>, utc_offset: UtcOffset) {
fn run_proj_non_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: String, role: &ast::Role, overrides: Config<C, StdSystem<C>>, clock: Arc<Clock>) {
let config = overrides.fallback(&Config {
request: None,
command: Some(Rc::new(move |_, _, key, command, entity| match command {
Expand All @@ -308,7 +307,6 @@ fn run_proj_non_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: St
})),
});

let clock = Arc::new(FineClock::new(utc_offset));
let system = Rc::new(StdSystem::new_sync(server, Some(project_name), config, clock));
let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_IDLE_SLEEP, Box::new(|| thread::sleep(IDLE_SLEEP_TIME)));
println!(">>> public id: {}\n", system.get_public_id());
Expand All @@ -335,7 +333,7 @@ fn run_proj_non_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: St
});
}
}
fn run_server<C: CustomTypes<StdSystem<C>>>(nb_server: String, addr: String, port: u16, overrides: Config<C, StdSystem<C>>, utc_offset: UtcOffset, syscalls: &[SyscallMenu]) {
fn run_server<C: CustomTypes<StdSystem<C>>>(nb_server: String, addr: String, port: u16, overrides: Config<C, StdSystem<C>>, clock: Arc<Clock>, syscalls: &[SyscallMenu]) {
println!(r#"connect from {nb_server}/?extensions=["http://{addr}:{port}/extension.js"]"#);

let extension = ExtensionArgs {
Expand Down Expand Up @@ -393,7 +391,6 @@ fn run_server<C: CustomTypes<StdSystem<C>>>(nb_server: String, addr: String, por
_ => CommandStatus::UseDefault { key, command },
})),
});
let clock = Arc::new(FineClock::new(utc_offset));
let system = Rc::new(StdSystem::new_sync(nb_server, Some("native-server"), config, clock));
let mut idle_sleeper = IdleAction::new(YIELDS_BEFORE_IDLE_SLEEP, Box::new(|| thread::sleep(IDLE_SLEEP_TIME)));
println!("public id: {}", system.get_public_id());
Expand Down Expand Up @@ -549,15 +546,22 @@ fn run_server<C: CustomTypes<StdSystem<C>>>(nb_server: String, addr: String, por
/// Runs a CLI client using the given [`Mode`] configuration.
pub fn run<C: CustomTypes<StdSystem<C>>>(mode: Mode, config: Config<C, StdSystem<C>>, syscalls: &[SyscallMenu]) {
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
let clock = Arc::new(Clock::new(utc_offset, Some(Precision::Medium)));
let clock_clone = clock.clone();
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(10));
clock_clone.update();
});

match mode {
Mode::Run { src, role, server } => {
let content = read_file(&src).unwrap_or_else(|_| crash!(1: "failed to read file '{src}'"));
let (project_name, role) = open_project(&content, role.as_deref()).unwrap_or_else(|e| crash!(2: "{e}"));

if stdout().is_tty() {
run_proj_tty(&project_name, server, &role, config, utc_offset);
run_proj_tty(&project_name, server, &role, config, clock);
} else {
run_proj_non_tty(&project_name, server, &role, config, utc_offset);
run_proj_non_tty(&project_name, server, &role, config, clock);
}
}
Mode::Dump { src, role } => {
Expand All @@ -572,7 +576,7 @@ pub fn run<C: CustomTypes<StdSystem<C>>>(mode: Mode, config: Config<C, StdSystem
println!("\ntotal size: {}", bytecode.total_size());
}
Mode::Start { server, addr, port } => {
run_server(server, addr, port, config, utc_offset, syscalls);
run_server(server, addr, port, config, clock, syscalls);
}
}
}
10 changes: 5 additions & 5 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Process<'gc, C, S> {
}
false => return Ok(ProcessStep::Yield),
}
Some(Defer::Sleep { until, aft_pos }) => match global_context.system.time(TimePrecision::Recent).to_arbitrary_ms()? >= *until {
Some(Defer::Sleep { until, aft_pos }) => match global_context.system.time(Precision::Low).to_arbitrary_ms()? >= *until {
true => {
self.pos = *aft_pos;
self.defer = None;
Expand Down Expand Up @@ -1095,11 +1095,11 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Process<'gc, C, S> {
self.pos = aft_pos;
}
Instruction::ResetTimer => {
global_context.timer_start = global_context.system.time(TimePrecision::Now).to_arbitrary_ms()?;
global_context.timer_start = global_context.system.time(Precision::Medium).to_arbitrary_ms()?;
self.pos = aft_pos;
}
Instruction::PushTimer => {
self.value_stack.push(Number::new(global_context.system.time(TimePrecision::Now).to_arbitrary_ms()?.saturating_sub(global_context.timer_start) as f64 / 1000.0)?.into());
self.value_stack.push(Number::new(global_context.system.time(Precision::Low).to_arbitrary_ms()?.saturating_sub(global_context.timer_start) as f64 / 1000.0)?.into());
self.pos = aft_pos;
}
Instruction::Sleep => {
Expand All @@ -1108,10 +1108,10 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> Process<'gc, C, S> {
self.pos = aft_pos;
return Ok(ProcessStep::Yield);
}
self.defer = Some(Defer::Sleep { until: global_context.system.time(TimePrecision::Now).to_arbitrary_ms()? + ms as u64, aft_pos });
self.defer = Some(Defer::Sleep { until: global_context.system.time(Precision::Medium).to_arbitrary_ms()? + ms as u64, aft_pos });
}
Instruction::PushRealTime { query } => {
let t = global_context.system.time(TimePrecision::Now).to_real_local()?;
let t = global_context.system.time(Precision::High).to_real_local()?;
let v = match query {
TimeQuery::UnixTimestampMs => (t.unix_timestamp_nanos() / 1000000) as f64,
TimeQuery::Year => t.year() as f64,
Expand Down
16 changes: 8 additions & 8 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1435,7 +1435,7 @@ impl<'gc, C: CustomTypes<S>, S: System<C>> GlobalContext<'gc, C, S> {
}

let proj_name = init_info.proj_name.clone();
let timer_start = system.time(TimePrecision::Now).to_arbitrary_ms::<C, S>().unwrap_or(0);
let timer_start = system.time(Precision::Medium).to_arbitrary_ms::<C, S>().unwrap_or(0);

Self { proj_name, globals, entities, timer_start, system, settings, bytecode }
}
Expand Down Expand Up @@ -1774,12 +1774,12 @@ impl SysTime {
}
}

/// The required precision of a current time measurement.
pub enum TimePrecision {
/// The real current time.
Now,
/// The real current time or a (recent) cached timestamp.
Recent,
/// The required precision of a measurement.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Precision {
Low,
Medium,
High,
}

/// Represents all the features of an implementing system.
Expand All @@ -1806,7 +1806,7 @@ pub trait System<C: CustomTypes<Self>>: 'static + Sized {
fn rand<T: SampleUniform, R: SampleRange<T>>(&self, range: R) -> T;

/// Gets the current system time.
fn time(&self, precision: TimePrecision) -> SysTime;
fn time(&self, precision: Precision) -> SysTime;

/// Performs a general request which returns a value to the system.
/// Ideally, this function should be non-blocking, and the requestor will await the result asynchronously.
Expand Down
110 changes: 34 additions & 76 deletions src/std_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,80 +97,42 @@ impl Key<Result<(), String>> for CommandKey {
}
}

/// An abstract wall-time clock with optional coarse granularity.
pub trait Clock {
/// Read the real current time.
fn now(&self) -> OffsetDateTime;
/// Read a recent cached time.
fn recent(&self) -> OffsetDateTime;
struct ClockCache {
value: Mutex<OffsetDateTime>,
precision: Precision,
}

/// A [`Clock`] that has fine granularity.
///
/// This type implements [`Clock::recent`] by calling [`Clock::now`] (no caching).
/// This can be inefficient if called many times in rapid succession, in which case [`CoarseClock`] could be used.
pub struct FineClock(UtcOffset);
impl FineClock {
pub fn new(utc_offset: UtcOffset) -> Self {
Self(utc_offset)
}
}
impl Clock for FineClock {
fn now(&self) -> OffsetDateTime {
OffsetDateTime::now_utc().to_offset(self.0)
}
fn recent(&self) -> OffsetDateTime {
self.now()
}
}

/// A [`Clock`] that has coarse granularity.
///
/// This type implements [`Clock::recent`] by quickly fetching the most recent cached result of [`Clock::now`].
/// [`StdSystem`] will automatically call [`Clock::now`] for some operations that require precise timing;
/// however, it is your responsibility to (externally) periodically call [`Clock::now`] for use with fuzzy timing logic like sleep operations.
pub struct CoarseClock(Mutex<OffsetDateTime>, FineClock);
impl CoarseClock {
pub fn new(utc_offset: UtcOffset) -> Self {
let c = FineClock::new(utc_offset);
Self(Mutex::new(c.now()), c)
}
/// A clock with optional coarse granularity.
pub struct Clock {
utc_offset: UtcOffset,
cache: Option<ClockCache>,
}
impl Clock for CoarseClock {
fn now(&self) -> OffsetDateTime {
let t = self.1.now();
*self.0.lock().unwrap() = t;
t
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
}
fn recent(&self) -> OffsetDateTime {
*self.0.lock().unwrap()
/// 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(),
}
}
}

#[test]
fn test_clocks() {
fn test_at(utc_offset: UtcOffset) {
let a = FineClock::new(utc_offset);
let b = CoarseClock::new(utc_offset);

fn check_eq(a: OffsetDateTime, b: OffsetDateTime) {
assert_eq!(a.offset(), b.offset());
assert!((a - b).abs().whole_microseconds() <= 1000);
assert_eq!(a.hour(), b.hour());
assert_eq!(a.minute(), b.minute());
/// 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;
}
check_eq(a.recent(), b.recent());
b.now();
check_eq(a.recent(), b.recent());
t
}
test_at(UtcOffset::UTC);
test_at(UtcOffset::from_hms(0, 0, 0).unwrap());
test_at(UtcOffset::from_hms(0, 0, 50).unwrap());
test_at(UtcOffset::from_hms(-19, 0, 50).unwrap());
test_at(UtcOffset::from_hms(-19, 30, 50).unwrap());
test_at(UtcOffset::from_hms(2, 30, 50).unwrap());
test_at(UtcOffset::from_hms(2, -10, 50).unwrap());
test_at(UtcOffset::from_hms(2, -10, -12).unwrap());
}

type MessageReplies = BTreeMap<ExternReplyKey, ReplyEntry>;
Expand Down Expand Up @@ -217,7 +179,7 @@ pub struct StdSystem<C: CustomTypes<StdSystem<C>>> {
context: Arc<Context>,
client: Arc<reqwest::Client>,
rng: Mutex<ChaChaRng>,
clock: Arc<dyn Clock>,
clock: Arc<Clock>,

rpc_request_pipe: Sender<RpcRequest<C>>,

Expand All @@ -230,11 +192,11 @@ 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.
#[tokio::main(flavor = "current_thread")]
pub async fn new_sync(base_url: String, project_name: Option<&str>, config: Config<C, Self>, clock: Arc<dyn Clock>) -> Self {
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`).
pub async fn new_async(base_url: String, project_name: Option<&str>, config: Config<C, Self>, clock: Arc<dyn Clock>) -> Self {
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 Down Expand Up @@ -451,12 +413,8 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {
self.rng.lock().unwrap().gen_range(range)
}

fn time(&self, precision: TimePrecision) -> SysTime {
let local = match precision {
TimePrecision::Now => self.clock.now(),
TimePrecision::Recent => self.clock.recent(),
};
SysTime::Real { local }
fn time(&self, precision: Precision) -> SysTime {
SysTime::Real { local: self.clock.read(precision) }
}

fn perform_request<'gc>(&self, mc: &Mutation<'gc>, request: Request<'gc, C, Self>, entity: &mut Entity<'gc, C, Self>) -> Result<Self::RequestKey, ErrorCause<C, Self>> {
Expand Down
Loading

0 comments on commit b0d90c8

Please sign in to comment.