From: Ian Jackson Date: Sun, 24 Apr 2022 20:18:58 +0000 (+0100) Subject: fake time: Introduce new feature X-Git-Tag: otter-1.1.0~453 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=4d518f8c29a7d584ae4a394fd8c5bb1ac1ba5eba;p=otter.git fake time: Introduce new feature Needs a little work still, and not used. Signed-off-by: Ian Jackson --- diff --git a/daemon/cmdlistener.rs b/daemon/cmdlistener.rs index f826c6ae..6c1a93aa 100644 --- a/daemon/cmdlistener.rs +++ b/daemon/cmdlistener.rs @@ -486,6 +486,13 @@ fn execute_and_respond(cs: &mut CommandStreamData, cmd: MgmtCommand, config().game_rng.set_fake(ents, superuser)?; Fine } + + MC::SetFakeTime(fspec) => { + let superuser = cs.superuser() + .ok_or(ME::SuperuserAuthorisationRequired)?; + config().global_clock.set_fake(fspec, superuser)?; + Fine + } }))(); let resp = match resp { diff --git a/daemon/session.rs b/daemon/session.rs index 0d41792c..9947e621 100644 --- a/daemon/session.rs +++ b/daemon/session.rs @@ -25,6 +25,7 @@ struct SessionRenderContext { player_info_pane: Html, bundles_info_pane: Html, fake_rng: bool, + fake_time: bool, } #[derive(Debug,Serialize)] @@ -245,6 +246,7 @@ fn session_inner(form: Json, ptoken: form.ptoken.clone(), links: (&*ig.links).into(), fake_rng: config().game_rng.is_fake(), + fake_time: config().global_clock.is_fake(), load: serde_json::to_string(&DataLoad { movehist, players: load_players, diff --git a/src/commands.rs b/src/commands.rs index 18a573b4..5a9db84a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -66,6 +66,7 @@ pub enum MgmtCommand { SshReinstallKeys, // managment only LoadFakeRng(Vec), + SetFakeTime(FakeTimeSpec), } //---------- Accounts file ---------- @@ -264,6 +265,7 @@ pub enum MgmtError { #[error("TOML syntax error: {0}")] TomlSyntaxError(String), #[error("TOML structure error: {0}")] TomlStructureError(String), #[error("RNG is real, command not supported")] RngIsReal, + #[error("Time is real, command not supported")] TimeIsReal, #[error("upload truncated")] UploadTruncated, #[error("upload corrupted")] UploadCorrupted, #[error("too many bundles")] TooManyBundles, diff --git a/src/config.rs b/src/config.rs index 48dda41d..9d24e754 100644 --- a/src/config.rs +++ b/src/config.rs @@ -49,6 +49,7 @@ pub struct ServerConfigSpec { pub authorized_keys_include: Option, pub debug_js_inject_file: Option, #[serde(default)] pub fake_rng: FakeRngSpec, + #[serde(default)] pub fake_time: FakeTimeConfig, /// Disable this for local testing only. See LICENCE. pub check_bundled_sources: Option, } @@ -84,6 +85,7 @@ pub struct ServerConfig { pub debug_js_inject: Arc, pub check_bundled_sources: bool, pub game_rng: RngWrap, + pub global_clock: GlobalClock, pub prctx: PathResolveContext, } @@ -134,12 +136,13 @@ impl ServerConfigSpec { http_port, listen, public_url, sse_wildcard_url, template_dir, specs_dir, nwtemplate_dir, wasm_dir, libexec_dir, usvg_bin, log, bundled_sources, shapelibs, sendmail, - debug_js_inject_file, check_bundled_sources, fake_rng, + debug_js_inject_file, check_bundled_sources, fake_rng, fake_time, ssh_proxy_command, ssh_proxy_user, ssh_restrictions, authorized_keys, authorized_keys_include, } = self; let game_rng = fake_rng.make_game_rng(); + let global_clock = fake_time.make_global_clock(); let home = || env::var("HOME").context("HOME"); let prctx = if let Some(ref cd) = change_directory { @@ -303,7 +306,7 @@ impl ServerConfigSpec { listen, public_url, sse_wildcard_url, template_dir, specs_dir, nwtemplate_dir, wasm_dir, libexec_dir, bundled_sources, shapelibs, sendmail, usvg_bin, - debug_js_inject, check_bundled_sources, game_rng, prctx, + debug_js_inject, check_bundled_sources, game_rng, global_clock, prctx, ssh_proxy_bin, ssh_proxy_uid, ssh_restrictions, authorized_keys, authorized_keys_include, }; diff --git a/src/fake-time.rs b/src/fake-time.rs new file mode 100644 index 00000000..c28bcaab --- /dev/null +++ b/src/fake-time.rs @@ -0,0 +1,71 @@ +// Copyright 2020-2021 Ian Jackson and contributors to Otter +// SPDX-License-Identifier: AGPL-3.0-or-later +// There is NO WARRANTY. + +use crate::prelude::*; + +use parking_lot::Mutex; + +type Millis = u32; +type Micros = u64; + +#[derive(Deserialize,Debug,Clone,Default)] +#[serde(transparent)] +pub struct FakeTimeConfig(Option); + +#[derive(Deserialize,Serialize,Debug,Clone,Default)] +#[serde(transparent)] +pub struct FakeTimeSpec(Option); + +#[derive(Debug)] +pub struct GlobalClock { + fakeable: Option>>, +} + +#[derive(Debug)] +struct FakeClock { + start: Instant, + current: Micros, +} + +impl FakeClock { + fn from_millis(ms: Millis) -> FakeClock { FakeClock { + start: Instant::now(), + current: (ms as Micros) * 1000, + } } + + fn from_spec(FakeTimeSpec(fspec): FakeTimeSpec) -> Option { + fspec.map(FakeClock::from_millis) + } +} + +impl FakeTimeConfig { + pub fn make_global_clock(self) -> GlobalClock { + let fakeable = self.0.map(|fspec| FakeClock::from_spec(fspec).into()); + GlobalClock { fakeable } + } +} + +impl GlobalClock { + pub fn now(&self) -> Instant { + self.now_fake().unwrap_or_else(|| Instant::now()) + } + + #[throws(as Option)] + fn now_fake(&self) -> Instant { + let mut guard = self.fakeable.as_ref()?.lock(); + let fake = guard.as_mut()?; + fake.current += 1; + fake.start + Duration::from_micros(fake.current) + } + + #[throws(MgmtError)] + pub fn set_fake(&self, fspec: FakeTimeSpec, _: AuthorisationSuperuser) { + let mut guard = self.fakeable.as_ref().ok_or(ME::TimeIsReal)?.lock(); + *guard = FakeClock::from_spec(fspec) + } + + pub fn is_fake(&self) -> bool { + self.fakeable.is_some() + } +} diff --git a/src/lib.rs b/src/lib.rs index 7394bd94..9f0e39f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,3 +62,4 @@ pub mod utils; #[path = "slotmap-slot-idx.rs"] pub mod slotmap_slot_idx; #[path = "toml-de.rs"] pub mod toml_de; #[path = "fake-rng.rs"] pub mod fake_rng; +#[path = "fake-time.rs"] pub mod fake_time; diff --git a/src/prelude.rs b/src/prelude.rs index f62e4ba7..5e700633 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -156,6 +156,7 @@ pub use crate::debugmutex::DebugIdentify; pub use crate::debugreader::DebugReader; pub use crate::error::*; pub use crate::fake_rng::*; +pub use crate::fake_time::*; pub use crate::gamestate::*; pub use crate::global::*; pub use crate::hidden::*; diff --git a/templates/macros.tera b/templates/macros.tera index 4ca17ce7..71e267b3 100644 --- a/templates/macros.tera +++ b/templates/macros.tera @@ -51,6 +51,7 @@ Hi {{nick | escape}} {% macro status() %} {%- if fake_rng %}FAKING RANDOMNESS!{% endif %} +{%- if fake_time %}FAKING TIME!{% endif %} nothing {% endmacro status %}