chiark / gitweb /
wip new piece records
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 18 Sep 2020 22:37:00 +0000 (23:37 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Fri, 18 Sep 2020 22:37:00 +0000 (23:37 +0100)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
src/api.rs
src/cmdlistener.rs
src/gamestate.rs
src/global.rs
src/session.rs
src/updates.rs

index cff02c05e63efd8b40f8b63d4dc7c7e697647421..ea2ef9f8726cb2cb3138266d7602d55cf4fa5183 100644 (file)
@@ -15,6 +15,7 @@ struct ApiPiece<O : ApiPieceOp> {
 trait ApiPieceOp : Debug {
   #[throws(ApiPieceOpError)]
   fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
+        p: &dyn Piece,
         lens: &dyn Lens /* used for LogEntry and PieceId but not Pos */)
         -> (PieceUpdateOp<()>, Vec<LogEntry>);
 }
@@ -99,12 +100,14 @@ fn api_piece_op<O: ApiPieceOp>(form : Json<ApiPiece<O>>)
   cl.lastseen = Instant::now();
   let player = cl.player;
   let gs = &mut g.gs;
+  let g_pieces = &g.pieces;
   let _ = gs.players.byid(player)?;
   let lens = TransparentLens { };
   let piece = lens.decode_visible_pieceid(form.piece, player);
   use ApiPieceOpError::*;
 
   match (||{
+    let p = g_pieces.get(piece).ok_or(OnlineError::PieceGone)?;
     let pc = gs.pieces.byid_mut(piece)
       .map_err(|()| OnlineError::PieceGone)?;
 
@@ -117,7 +120,7 @@ fn api_piece_op<O: ApiPieceOp>(form : Json<ApiPiece<O>>)
     if pc.held != None && pc.held != Some(player) {
       throw!(OnlineError::PieceHeld)
     };
-    let (update, logents) = form.op.op(gs,player,piece,&lens)?;
+    let (update, logents) = form.op.op(gs,player,piece,p.as_ref(),&lens)?;
     Ok::<_,ApiPieceOpError>((update, logents))
   })() {
     Err(ReportViaUpdate(poe)) => {
@@ -161,7 +164,7 @@ fn api_grab(form : Json<ApiPiece<ApiPieceGrab>>)
 impl ApiPieceOp for ApiPieceGrab {
   #[throws(ApiPieceOpError)]
   fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
-        lens: &dyn Lens)
+        p: &dyn Piece, lens: &dyn Lens)
         -> (PieceUpdateOp<()>, Vec<LogEntry>) {
     let pl = gs.players.byid(player).unwrap();
     let pc = gs.pieces.byid_mut(piece).unwrap();
@@ -174,7 +177,7 @@ impl ApiPieceOp for ApiPieceGrab {
     let logent = LogEntry {
       html : Html(format!("{} grasped {}",
                      &htmlescape::encode_minimal(&pl.nick),
-                     pc.describe_html(&lens.log_pri(piece, pc)).0)),
+                     p.describe_pri(&lens.log_pri(piece, pc)).0)),
     };
 
     (update, vec![logent])
@@ -193,7 +196,7 @@ fn api_ungrab(form : Json<ApiPiece<ApiPieceUngrab>>)
 impl ApiPieceOp for ApiPieceUngrab {
   #[throws(ApiPieceOpError)]
   fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
-        lens: &dyn Lens)
+        p: &dyn Piece, lens: &dyn Lens)
         -> (PieceUpdateOp<()>, Vec<LogEntry>) {
     let pl = gs.players.byid(player).unwrap();
     let pc = gs.pieces.byid_mut(piece).unwrap();
@@ -206,7 +209,7 @@ impl ApiPieceOp for ApiPieceUngrab {
     let logent = LogEntry {
       html : Html(format!("{} released {}",
                      &htmlescape::encode_minimal(&pl.nick),
-                     pc.describe_html(&lens.log_pri(piece, pc)).0)),
+                     p.describe_pri(&lens.log_pri(piece, pc)).0)),
     };
 
     (update, vec![logent])
@@ -226,7 +229,7 @@ fn api_raise(form : Json<ApiPiece<ApiPieceRaise>>)
 impl ApiPieceOp for ApiPieceRaise {
   #[throws(ApiPieceOpError)]
   fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
-        _: &dyn Lens)
+        _p: &dyn Piece, _: &dyn Lens)
         -> (PieceUpdateOp<()>, Vec<LogEntry>) {
     let pc = gs.pieces.byid_mut(piece).unwrap();
     pc.zlevel = ZLevel { z : self.z, zg : gs.gen };
@@ -245,7 +248,7 @@ fn api_move(form : Json<ApiPiece<ApiPieceMove>>) -> impl response::Responder<'st
 impl ApiPieceOp for ApiPieceMove {
   #[throws(ApiPieceOpError)]
   fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
-        _lens: &dyn Lens)
+        _p: &dyn Piece, _lens: &dyn Lens)
         -> (PieceUpdateOp<()>, Vec<LogEntry>) {
     let pc = gs.pieces.byid_mut(piece).unwrap();
     let (pos, clamped) = self.0.clamped(gs.table_size);
index 4dc4daec1a4a5219905395205a034d3a7eadfc54..7f2983999b91004e36498689af63cb771bd1b2bd 100644 (file)
@@ -68,7 +68,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
         scoped_name : name,
       };
 
-      let gref = Instance::new(name, gs)?;
+      let gref = Instance::new(name, gs, Default::default())?;
       let mut ig = gref.lock()?;
 
       execute_for_game(cs, &mut ig, insns, MgmtGameUpdateMode::Bulk)
@@ -163,9 +163,9 @@ fn execute_game_insn(cs: &CommandStream,
     Insn::ListPieces => readonly(ig, {
       let pieces = ig.gs.pieces.iter().map(|(piece,p)|{
         let &PieceState { pos, face, .. } = p;
-        let desc_html = p.p.describe_html(None);
-        MgmtGamePieceInfo { piece, pos, face, desc_html }
-      }).collect();
+        let desc_html = ig.pieces.get(piece)?.describe_html(None);
+        Some(MgmtGamePieceInfo { piece, pos, face, desc_html })
+      }).flatten().collect();
       Resp::Pieces(pieces)
     }),
 
@@ -221,10 +221,11 @@ fn execute_game_insn(cs: &CommandStream,
     }
 
     DeletePiece(piece) => {
+      let p = ig.pieces.remove(piece).ok_or(ME::PieceNotFound)?;
       let gs = &mut ig.gs;
-      let p = gs.pieces.remove(piece).ok_or(ME::PieceNotFound)?;
-      let desc_html = p.p.describe_html(Some(Default::default()));
-      p.p.delete_hook(&p, gs);
+      let pc = gs.pieces.remove(piece);
+      let desc_html = p.describe_html(Some(Default::default()));
+      if let Some(pc) = pc { p.delete_hook(&pc, gs); }
       (U{ pcs: vec![(piece, PieceUpdateOp::Delete())],
           log: vec![ LogEntry {
             html: Html(format!("A piece {} was removed from the game",
@@ -235,6 +236,7 @@ fn execute_game_insn(cs: &CommandStream,
     },
 
     AddPieces(PiecesSpec{ pos,posd,count,face,info }) => {
+      let ig = &mut **ig;
       let gs = &mut ig.gs;
       let count = count.unwrap_or(1);
       if count > CREATE_PIECES_MAX { throw!(LimitExceeded) }
@@ -252,12 +254,13 @@ fn execute_game_insn(cs: &CommandStream,
           lastclient: Default::default(),
           gen_before_lastclient: Generation(0),
           gen: gs.gen,
-          pos, p, face,
+          pos, face,
         };
         if let (_, true) = pc.pos.clamped(gs.table_size) {
           throw!(SpecError::PosOffTable);
         }
         let piece = gs.pieces.insert(pc);
+        ig.pieces.insert(piece, p);
         updates.push((piece, PieceUpdateOp::Insert(())));
         pos[0] += posd[0];
         pos[1] += posd[1];
index 070ce2c177edfceeb86d6e53dd3dcccce67bf8b6..aedb3814210129b086e7f4de9dec13271565b7c5 100644 (file)
@@ -54,7 +54,6 @@ pub struct GameState {
 #[derive(Debug,Serialize,Deserialize)]
 pub struct PieceState {
   pub pos : Pos,
-  pub p : Box<dyn Piece>,
   pub face : FaceId,
   pub held : Option<PlayerId>,
   pub zlevel : ZLevel,
@@ -197,10 +196,28 @@ impl Debug for Html {
 
 impl PieceState {
   #[throws(IE)]
-  pub fn make_defs(&self, pri : &PieceRenderInstructions) -> Html {
-    let pr = self;
+  pub fn prep_piecestate(&self, p: &dyn Piece, pri : &PieceRenderInstructions)
+                     -> PreparedPieceState {
+    PreparedPieceState {
+      pos        : self.pos,
+      held       : self.held,
+      svg        : p.make_defs(pri)?,
+      z          : self.zlevel.z,
+      zg         : self.zlevel.zg,
+    }
+  }
+}
+
+pub trait PieceExt {
+  fn make_defs(&self, pri : &PieceRenderInstructions) -> Result<Html,IE>;
+  fn describe_pri(&self, pri : &PieceRenderInstructions) -> Html;
+}
+
+impl<T> PieceExt for T where T: Piece + ?Sized {
+  #[throws(IE)]
+  fn make_defs(&self, pri : &PieceRenderInstructions) -> Html {
     let mut defs = Html(String::new());
-    let dragraise = match pr.p.thresh_dragraise(pri)? {
+    let dragraise = match self.thresh_dragraise(pri)? {
       Some(n) if n < 0 => throw!(SE::NegativeDragraise),
       Some(n) => n,
       None => -1,
@@ -208,29 +225,17 @@ impl PieceState {
     write!(&mut defs.0,
            r##"<g id="piece{}" data-dragraise="{}">"##,
            pri.id, dragraise)?;
-    pr.p.svg_piece(&mut defs, &pri)?;
+    self.svg_piece(&mut defs, &pri)?;
     write!(&mut defs.0, r##"</g>"##)?;
     write!(&mut defs.0,
            r##"<path id="surround{}" d="{}"/>"##,
-           pri.id, pr.p.surround_path(&pri)?.0)?;
-    pr.p.svg_x_defs(&mut defs, &pri)?;
+           pri.id, self.surround_path(&pri)?.0)?;
+    self.svg_x_defs(&mut defs, &pri)?;
     defs
   }
 
-  #[throws(IE)]
-  pub fn prep_piecestate(&self, pri : &PieceRenderInstructions)
-                         -> PreparedPieceState {
-    PreparedPieceState {
-      pos        : self.pos,
-      held       : self.held,
-      svg        : self.make_defs(pri)?,
-      z          : self.zlevel.z,
-      zg         : self.zlevel.zg,
-    }
-  }
-
-  pub fn describe_html(&self, pri : &PieceRenderInstructions) -> Html {
-    self.p.describe_html(Some(pri.face))
+  fn describe_pri(&self, pri : &PieceRenderInstructions) -> Html {
+    self.describe_html(Some(pri.face))
   }
 }
 
index b5282277cdf8a1308653783ad94aed0e588d68a8..5cec5a4ca9dce3b686b4246a85ab0ef487cd6357 100644 (file)
@@ -20,6 +20,8 @@ const GAME_SAVE_LAG : Duration = Duration::from_millis(500);
 #[repr(transparent)]
 pub struct RawTokenVal(str);
 
+pub type PiecesLoaded = SecondarySlotMap<PieceId,Box<dyn Piece>>;
+
 // ---------- public data structure ----------
 
 #[derive(Debug,Serialize,Deserialize)]
@@ -35,6 +37,7 @@ pub struct InstanceRef (Arc<Mutex<InstanceContainer>>);
 pub struct Instance {
   pub name : Arc<InstanceName>,
   pub gs : GameState,
+  pub pieces : PiecesLoaded,
   pub clients : DenseSlotMap<ClientId,Client>,
   pub updates : SecondarySlotMap<PlayerId, PlayerUpdates>,
   pub tokens_players : TokenRegistry<PlayerId>,
@@ -56,11 +59,11 @@ pub struct Client {
 
 /// KINDS OF PERSISTENT STATE
 ///
-///               TokenTable   TokenTable    GameState    GameState
-///                <ClientId>   <PlayerId>    .players     .pieces
+///               TokenTable   TokenTable    GameState    Instance GameState
+///                <ClientId>   <PlayerId>    .players    .pieces  .pieces
 ///
-///   Saved        No           a-*           g-*          g-*
-///   Spec TOML    Absent       table, ish    table        game
+///   Saved        No           a-*           g-*         a-*      g-*
+///   Spec TOML    Absent       table, ish    table       game     game
 ///
 ///
 /// UPDATE RELIABILITY/PERSISTENCE RULES
@@ -175,8 +178,9 @@ pub struct InstanceContainer {
 }
 
 #[derive(Debug,Default,Serialize,Deserialize)]
-struct InstanceSaveAccesses<RawTokenStr> {
-  tokens_players : Vec<(RawTokenStr, PlayerId)>,
+struct InstanceSaveAccesses<RawTokenStr, PiecesLoadedRef> {
+  pieces: PiecesLoadedRef,
+  tokens_players: Vec<(RawTokenStr, PlayerId)>,
 }
 
 display_as_debug!{InstanceLockError}
@@ -234,12 +238,13 @@ impl Instance {
   /// Returns `None` if a game with this name already exists
   #[allow(clippy::new_ret_no_self)]
   #[throws(MgmtError)]
-  pub fn new(name: InstanceName, gs: GameState) -> InstanceRef {
+  pub fn new(name: InstanceName, gs: GameState, pieces: PiecesLoaded)
+             -> InstanceRef {
     let name = Arc::new(name);
 
     let g = Instance {
       name : name.clone(),
-      gs,
+      gs, pieces,
       clients : Default::default(),
       updates : Default::default(),
       tokens_players : Default::default(),
@@ -641,6 +646,7 @@ impl InstanceGuard<'_> {
   #[throws(InternalError)]
   fn save_access_now(&mut self) {
     self.save_something("a-", |s,w| {
+      let pieces = &s.c.g.pieces;
       let tokens_players : Vec<(&str, PlayerId)> = {
         let global_players = GLOBAL.players.read().unwrap();
         s.c.g.tokens_players.tr
@@ -651,7 +657,7 @@ impl InstanceGuard<'_> {
           .flatten()
           .collect()
       };
-      let isa = InstanceSaveAccesses { tokens_players };
+      let isa = InstanceSaveAccesses { pieces, tokens_players };
       rmp_serde::encode::write_named(w, &isa)
     })?;
     info!("saved accesses for {:?}", &self.name);
@@ -682,16 +688,8 @@ impl InstanceGuard<'_> {
         })().context(lockfile).context("lock global save area")?);
       }
     }
-    let gs = {
-      let mut gs : GameState = Self::load_something(&name, "g-")?;
-      for mut p in gs.pieces.values_mut() {
-        p.lastclient = Default::default();
-      }
-      gs
-    };
-
-    let mut access_load : InstanceSaveAccesses<String>
-      = Self::load_something(&name, "a-")
+    let InstanceSaveAccesses::<String,PiecesLoaded>
+    { mut tokens_players, mut pieces } = Self::load_something(&name, "a-")
       .or_else(|e| {
         if let InternalError::Anyhow(ae) = &e {
           if let Some(ioe) = ae.downcast_ref::<io::Error>() {
@@ -702,18 +700,30 @@ impl InstanceGuard<'_> {
         }
         Err(e)
       })?;
+
+    let gs = {
+      let mut gs : GameState = Self::load_something(&name, "g-")?;
+      for mut p in gs.pieces.values_mut() {
+        p.lastclient = Default::default();
+      }
+      gs.pieces.retain(|k,_v| pieces.contains_key(k));
+      pieces.retain(|k,_v| gs.pieces.contains_key(k));
+
+      gs
+    };
+
     let mut updates : SecondarySlotMap<_,_> = Default::default();
     let pu_bc = PlayerUpdates::new_begin(&gs);
     for player in gs.players.keys() {
       updates.insert(player, pu_bc.new());
     }
     let name = Arc::new(name);
-    access_load.tokens_players.retain(
+    tokens_players.retain(
       |&(_,player)| gs.players.contains_key(player)
     );
 
     let g = Instance {
-      gs, updates,
+      gs, pieces, updates,
       name: name.clone(),
       clients : Default::default(),
       tokens_clients : Default::default(),
@@ -726,11 +736,11 @@ impl InstanceGuard<'_> {
     };
     let gref = InstanceRef(Arc::new(Mutex::new(cont)));
     let mut g = gref.lock().unwrap();
-    for (token, _) in &access_load.tokens_players {
+    for (token, _) in &tokens_players {
       g.tokens_players.tr.insert(RawToken(token.clone()));
     }
     let mut global = GLOBAL.players.write().unwrap();
-    for (token, player) in access_load.tokens_players.drain(0..) {
+    for (token, player) in tokens_players.drain(0..) {
       let iad = InstanceAccessDetails {
         gref : gref.clone(),
         ident : player,
index 5fd918cc5dfa3601ba6ff54f39d8ea7bf13cb963..5200bcf1a91766c51bdc8db9178ce99c2f94b61b 100644 (file)
@@ -95,7 +95,9 @@ fn session(form : Json<SessionForm>) -> Result<Template,OE> {
         id : make_pieceid_visible(gpid),
         face : pr.face,
       };
-      let defs = pr.make_defs(&pri)?;
+      let p = if let Some(p) = ig.pieces.get(gpid) { p }
+      else { continue /* was deleted */ };
+      let defs = p.make_defs(&pri)?;
       alldefs.push((pri.id, defs));
 
       let for_info = SessionPieceLoadJson {
index 11e09b33c852f2553a0a91e7e1e4175c461db154..0734570b51fa0f44176f223e0d3076a107e2f6d5 100644 (file)
@@ -291,8 +291,11 @@ impl<'r> PrepareUpdatesBuffer<'r> {
                            lens: &dyn Lens) -> PreparedUpdateEntry {
     let gs = &mut self.g.gs;
 
-    let (update, piece) = match gs.pieces.byid_mut(piece) {
-      Ok(pc) => {
+    let (update, piece) = match (
+      gs.pieces.byid_mut(piece),
+      self.g.pieces.byid(piece),
+    ) {
+      (Ok(pc), Ok(p)) => {
         gs.max_z.update_max(pc.zlevel.z);
 
         if self.by_client != pc.lastclient {
@@ -304,7 +307,7 @@ impl<'r> PrepareUpdatesBuffer<'r> {
 
         let update = update.try_map_new_state(
           |_|{
-            let mut ns = pc.prep_piecestate(&pri_for_all)?;
+            let mut ns = pc.prep_piecestate(p.as_ref(), &pri_for_all)?;
             lens.massage_prep_piecestate(&mut ns);
             <Result<_,InternalError>>::Ok(ns)
           },
@@ -312,7 +315,7 @@ impl<'r> PrepareUpdatesBuffer<'r> {
 
         (update, pri_for_all.id)
       },
-      Err(()) => {
+      _ => {
         (PieceUpdateOp::Delete(), lens.pieceid2visible(piece))
       }
     };