chiark / gitweb /
wip remove
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 30 Jul 2020 20:56:02 +0000 (21:56 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 30 Jul 2020 20:56:02 +0000 (21:56 +0100)
src/gamestate.rs
src/global.rs

index fb56afabfab3eca53bcc3d0aacce1c7e6c08c109..1cb5ac788c02b5dd1e5f9baef7efd5377ef0e8df 100644 (file)
@@ -46,7 +46,7 @@ pub struct GameState {
   pub max_z : ZCoord,
 }
 
-#[derive(Debug,Serialize,Deserialize)]
+#[derive(Debug,Clone,Serialize,Deserialize)]
 pub struct PieceState {
   pub pos : Pos,
   pub p : Box<dyn Piece>,
@@ -58,7 +58,7 @@ pub struct PieceState {
   pub gen_before_lastclient : Generation,
 }
 
-#[derive(Debug,Serialize,Deserialize)]
+#[derive(Debug,Clone,Serialize,Deserialize)]
 pub struct PlayerState {
   pub nick : String,
 }
index 4f75a6f8a170bdc1c1d1f29ca9a9e442486a7ae8..46d19f79bf273350f0298de626fe7b8bad2c5492 100644 (file)
@@ -191,7 +191,7 @@ impl Borrow<str> for RawToken {
   fn borrow(&self) -> &str { &self.0 }
 }
 
-// ---------- Main instance API ----------
+// ---------- Main API for instance lifecycle ----------
 
 impl InstanceRef {
   #[throws(InstanceLockError)]
@@ -304,16 +304,76 @@ impl DerefMut for InstanceGuard<'_> {
 // ---------- Player and token functionality ----------
 
 impl InstanceGuard<'_> {
-  #[throws(OE)]
+  #[throws(ServerFailure)]
   pub fn player_new(&mut self, newplayer: PlayerState) -> PlayerId {
+    // saving is fallible, but we can't attempt to save unless
+    // we have a thing to serialise with the player in it
     let player = self.c.g.gs.players.insert(newplayer);
-    self.c.g.updates.insert(player, Default::default());
-    self.save_game_now()?;
+    self.save_game_now().map_err(|e|{
+      self.c.g.gs.players.remove(player);
+      e
+    })?;
+    (||{
+      self.c.g.updates.insert(player, Default::default());
+      // xxx send log message, should be provided by caller
+    })(); // <- No ?, ensures that IEFE is infallible (barring panics)
     player
   }
 
+  #[throws(ServerFailure)]
+  pub fn player_remove(&mut self, oldplayer: PlayerId) {
+    // We have to filter this player out of everything
+    // Then save
+    // Then send updates
+    // We make a copy so if the save fails, we can put everything back
+    let pieces = self.c.g.gs.pieces.clone();
+    for (_piece,p) in &mut pieces {
+      if p.held == Some(oldplayer) { p.held = None }
+    }
+    let players = self.c.g.gs.players.clone();
+    players.remove(oldplayer);
+    let gs = GameState {
+      gen : self.c.g.gs.gen,
+      log : mem::take(&mut self.c.g.gs.log), // must put this back
+      max_z : self.gs.max_z,
+      pieces, players,
+    };
+
+    mem::swap(&mut gs, &mut self.gs);
+    self.save_game_now().map_err(|e|{
+      // oof
+      mem::swap(&mut gs,     &mut self.gs);
+      mem::swap(&mut gs.log, &mut self.gs.log);
+      e
+    })?;
+
+    // point of no return
+    (||{
+      let mut clients_to_remove = HashSet::new();
+      self.clients.retain(|k,v| {
+        let remove = v.player == oldplayer;
+        if remove { clients_to_remove.insert(k); }
+        !remove
+      });
+      // xxx signal this client to abandon ?
+      self.updates.remove(oldplayer);
+      self.tokens_deregister_for_id(|id:PlayerId| id==oldplayer);
+      self.tokens_deregister_for_id(|id| clients_to_remove.contains(&id));
+      self.save_access_now().map_err(
+        |e| eprintln!(
+          "trouble garbage collecting accesses for deleted player: {:?}",
+          &e)
+      );
+    })(); // <- No ?, ensures that IEFE is infallible (barring panics)
+  }
+
   #[throws(OE)]
   pub fn player_access_register(&mut self, token: RawToken, player: PlayerId) {
+    // xxx server has to not allow unpriv users to define tokens
+    // xxx which is a shame because it means tokens can't persist,
+    // xxx unless game is never destroyed ?
+    // xxx these things are then more like tables, and persistent
+    // xxx boxes feature maybe
     let iad = InstanceAccessDetails { gref : self.gref.clone(), ident : player };
     self.token_register(token, iad);
     self.save_access_now()?;
@@ -333,6 +393,16 @@ impl InstanceGuard<'_> {
     let mut global = global.write().unwrap();
     for t in tokens.tr.drain() { global.remove(&t); }
   }
+
+  fn tokens_deregister_for_id<Id:AccessId, F: Fn(Id) -> bool
+                              > (&mut self, oldid: F) {
+    let mut tokens = AccessId::global_tokens(PRIVATE_Y).write().unwrap();
+    tokens.retain(|k,v| {
+      let remove = oldid(v.ident);
+      if remove { Id::tokens_registry(self, PRIVATE_Y).tr.remove(k); }
+      !remove
+    });
+  }
 }
 
 // ---------- save/load ----------
@@ -402,6 +472,7 @@ impl InstanceGuard<'_> {
   pub fn load(name: InstanceName) -> InstanceRef {
     // xxx scan on startup, rather than asking caller to specify names
     // xxx should take a file lock on save area
+    // xxx check for deleted players, throw their tokens away
     let gs : GameState = Self::load_something(&name, "g-")?;
     let mut al : InstanceSaveAccesses<String>
                        = Self::load_something(&name, "a-")?;
@@ -445,13 +516,16 @@ impl InstanceGuard<'_> {
 
 pub type TokenTable<Id> = HashMap<RawToken, InstanceAccessDetails<Id>>;
 
-pub trait AccessId : Copy + Clone + 'static {
+pub trait AccessId : Copy + Clone + Sealed + 'static {
   fn global_tokens(_:PrivateCaller) -> &'static RwLock<TokenTable<Self>>;
   fn tokens_registry(ig: &mut Instance, _:PrivateCaller)
                      -> &mut TokenRegistry<Self>;
   const ERROR : OnlineError;
 }
 
+trait Sealed { }
+impl<T> Sealed for T { }
+
 impl AccessId for PlayerId {
   const ERROR : OnlineError = NoPlayer;
   fn global_tokens(_: PrivateCaller) -> &'static RwLock<TokenTable<Self>> {