chiark / gitweb /
fake time: Introduce new feature
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sun, 24 Apr 2022 20:18:58 +0000 (21:18 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 25 Apr 2022 00:13:18 +0000 (01:13 +0100)
Needs a little work still, and not used.

Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
daemon/cmdlistener.rs
daemon/session.rs
src/commands.rs
src/config.rs
src/fake-time.rs [new file with mode: 0644]
src/lib.rs
src/prelude.rs
templates/macros.tera

index f826c6ae4212a53bbbb5e284c1d8d5a74c320483..6c1a93aa13a1eb96925239911dac9731da3d97a6 100644 (file)
@@ -486,6 +486,13 @@ fn execute_and_respond<W>(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 {
index 0d41792c57f82fa25c0f40b73f6759c2d31676d9..9947e62182bec1e20a75e80c383c4829ba859a53 100644 (file)
@@ -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<SessionForm>,
       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,
index 18a573b44f16106bf9915fff31762efa0daae0f4..5a9db84a68a853bcc44045ceb6977c99fa845f63 100644 (file)
@@ -66,6 +66,7 @@ pub enum MgmtCommand {
   SshReinstallKeys, // managment only
 
   LoadFakeRng(Vec<String>),
+  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,
index 48dda41dc01639fc47b42ee1092155d5de327d11..9d24e7547834f2cb2b73341e178094a8506d224e 100644 (file)
@@ -49,6 +49,7 @@ pub struct ServerConfigSpec {
   pub authorized_keys_include: Option<String>,
   pub debug_js_inject_file: Option<String>,
   #[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<bool>,
 }
@@ -84,6 +85,7 @@ pub struct ServerConfig {
   pub debug_js_inject: Arc<String>,
   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 (file)
index 0000000..c28bcaa
--- /dev/null
@@ -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<FakeTimeSpec>);
+
+#[derive(Deserialize,Serialize,Debug,Clone,Default)]
+#[serde(transparent)]
+pub struct FakeTimeSpec(Option<Millis>);
+
+#[derive(Debug)]
+pub struct GlobalClock {
+  fakeable: Option<Mutex<Option<FakeClock>>>,
+}
+
+#[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<FakeClock> {
+    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()
+  }
+} 
index 7394bd94ac047737d4556c5512dd8b09b543c833..9f0e39f59146ed991ace791b857926795c325741 100644 (file)
@@ -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;
index f62e4ba75faee627f49e9aa1b64f8d1438f16332..5e700633b7bd0babb03f6d161be90ed92852f397 100644 (file)
@@ -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::*;
index 4ca17ce7fe6dafea84b82649f6db8e1606ae28ee..71e267b3b89bae03271460675c99f179d752ddbf 100644 (file)
@@ -51,6 +51,7 @@ Hi {{nick | escape}}
 
 {% macro status() %}
 {%- if fake_rng %}<strong>FAKING RANDOMNESS!</strong>{% endif %}
+{%- if fake_time %}<strong>FAKING TIME!</strong>{% endif %}
 <span id="status">nothing</span>
 {% endmacro status %}