Skip to content

Commit

Permalink
time refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
dragazo committed Nov 2, 2023
1 parent d445c9c commit b28fd23
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 114 deletions.
9 changes: 6 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ fn run_proj_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: String
},
});

let system = Rc::new(StdSystem::new_sync(server, Some(project_name), config, utc_offset));
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 @@ -307,7 +308,8 @@ fn run_proj_non_tty<C: CustomTypes<StdSystem<C>>>(project_name: &str, server: St
})),
});

let system = Rc::new(StdSystem::new_sync(server, Some(project_name), config, utc_offset));
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 Down Expand Up @@ -391,7 +393,8 @@ fn run_server<C: CustomTypes<StdSystem<C>>>(nb_server: String, addr: String, por
_ => CommandStatus::UseDefault { key, command },
})),
});
let system = Rc::new(StdSystem::new_sync(nb_server, Some("native-server"), config, utc_offset));
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
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().to_arbitrary_ms()? >= *until {
Some(Defer::Sleep { until, aft_pos }) => match global_context.system.time(TimePrecision::Recent).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().to_arbitrary_ms()?;
global_context.timer_start = global_context.system.time(TimePrecision::Now).to_arbitrary_ms()?;
self.pos = aft_pos;
}
Instruction::PushTimer => {
self.value_stack.push(Number::new(global_context.system.time().to_arbitrary_ms()?.saturating_sub(global_context.timer_start) as f64 / 1000.0)?.into());
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.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().to_arbitrary_ms()? + ms as u64, aft_pos });
self.defer = Some(Defer::Sleep { until: global_context.system.time(TimePrecision::Now).to_arbitrary_ms()? + ms as u64, aft_pos });
}
Instruction::PushRealTime { query } => {
let t = global_context.system.time().to_real_local()?;
let t = global_context.system.time(TimePrecision::Now).to_real_local()?;
let v = match query {
TimeQuery::UnixTimestampMs => (t.unix_timestamp_nanos() / 1000000) as f64,
TimeQuery::Year => t.year() as f64,
Expand Down
12 changes: 10 additions & 2 deletions src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1434,7 +1434,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().to_arbitrary_ms::<C, S>().unwrap_or(0);
let timer_start = system.time(TimePrecision::Now).to_arbitrary_ms::<C, S>().unwrap_or(0);

Self { proj_name, globals, entities, timer_start, system, settings, bytecode }
}
Expand Down Expand Up @@ -1773,6 +1773,14 @@ 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,
}

/// Represents all the features of an implementing system.
///
/// This type encodes any features that cannot be performed without platform-specific resources.
Expand All @@ -1797,7 +1805,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) -> SysTime;
fn time(&self, precision: TimePrecision) -> 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
94 changes: 87 additions & 7 deletions src/std_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,82 @@ 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;
}

/// 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)
}
}
impl Clock for CoarseClock {
fn now(&self) -> OffsetDateTime {
let t = self.1.now();
*self.0.lock().unwrap() = t;
t
}
fn recent(&self) -> OffsetDateTime {
*self.0.lock().unwrap()
}
}

#[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());
}
check_eq(a.recent(), b.recent());
b.now();
check_eq(a.recent(), b.recent());
}
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>;

async fn call_rpc_async<C: CustomTypes<StdSystem<C>>>(context: &Context, client: &reqwest::Client, service: &str, rpc: &str, args: &[(&str, &Json)]) -> Result<SimpleValue, String> {
Expand Down Expand Up @@ -141,7 +217,7 @@ pub struct StdSystem<C: CustomTypes<StdSystem<C>>> {
context: Arc<Context>,
client: Arc<reqwest::Client>,
rng: Mutex<ChaChaRng>,
utc_offset: UtcOffset,
clock: Arc<dyn Clock>,

rpc_request_pipe: Sender<RpcRequest<C>>,

Expand All @@ -154,11 +230,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>, utc_offset: UtcOffset) -> Self {
Self::new_async(base_url, project_name, config, utc_offset).await
pub async fn new_sync(base_url: String, project_name: Option<&str>, config: Config<C, Self>, clock: Arc<dyn 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>, utc_offset: UtcOffset) -> Self {
pub async fn new_async(base_url: String, project_name: Option<&str>, config: Config<C, Self>, clock: Arc<dyn 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 @@ -341,7 +417,7 @@ impl<C: CustomTypes<StdSystem<C>>> StdSystem<C> {
});

Self {
config, context, client, utc_offset,
config, context, client, clock,
rng: Mutex::new(ChaChaRng::from_seed(seed)),
rpc_request_pipe,
message_replies, message_sender, message_receiver, message_injector,
Expand Down Expand Up @@ -375,8 +451,12 @@ impl<C: CustomTypes<StdSystem<C>>> System<C> for StdSystem<C> {
self.rng.lock().unwrap().gen_range(range)
}

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

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 b28fd23

Please sign in to comment.