chiark / gitweb /
new framework for piece updates
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 6 Jul 2020 17:10:55 +0000 (18:10 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Mon, 6 Jul 2020 17:10:55 +0000 (18:10 +0100)
src/bin/server.rs
src/gamestate.rs
src/global.rs

index 80c2018e00fa72dc8ef02444effefc9cc3e3ae82..53494a2e717468772aee0ce73eca7007d9cec76d 100644 (file)
@@ -126,8 +126,61 @@ struct ApiPiece<O : ApiPieceOp> {
 struct ApiPieceGrab {
 }
 trait ApiPieceOp : Debug {
+  #[throws(GameError)]
+  fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
+        lens: &dyn Lens)
+        -> (PieceUpdateOp<()>, Vec<LogEntry>);
+}
+
+trait Lens {
+  fn log_pri(&self, piece: PieceId, pc: &PieceRecord)
+             -> PieceRenderInstructions;
+  fn svg_pri(&self, piece: PieceId, pc: &PieceRecord, player: PlayerId)
+             -> PieceRenderInstructions;
+  fn decode_visible_pieceid(&self, vpiece: VisiblePieceId, player: PlayerId)
+                            -> PieceId;
+}
+struct TransparentLens { }
+impl Lens for TransparentLens {
+  fn log_pri(&self, piece: PieceId, pc: &PieceRecord)
+             -> PieceRenderInstructions {
+    let kd : slotmap::KeyData = piece.into();
+    let id = VisiblePieceId(kd);
+    PieceRenderInstructions { id, face : pc.face }
+  }
+  fn svg_pri(&self, piece: PieceId, pc: &PieceRecord, _player: PlayerId)
+             -> PieceRenderInstructions {
+    self.log_pri(piece, pc)
+  }
+  fn decode_visible_pieceid(&self, vpiece: VisiblePieceId, _player: PlayerId)
+                            -> PieceId {
+    let kd : slotmap::KeyData = vpiece.into();
+    PieceId::from(kd)
+  }
+}
+
+impl ApiPieceOp for ApiPieceGrab {
+  #[throws(GameError)]
+  fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
+        lens: &dyn Lens)
+        -> (PieceUpdateOp<()>, Vec<LogEntry>) {
+    let pl = gs.players.byid(player).unwrap();
+    let pc = gs.pieces.byid_mut(piece).unwrap();
+
+    if pc.held.is_some() { Err(GameError::PieceHeld)? }
+    pc.held = Some(player);
+    
+    let update = PieceUpdateOp::Modify(());
+
+    let logent = LogEntry {
+      html : format!("{} grasped {}",
+                     &htmlescape::encode_minimal(&pl.nick),
+                     pc.describe_html(&lens.log_pri(piece, pc))),
+    };
+
+    (update, vec![logent])
+  }
 }
-impl ApiPieceOp for ApiPieceGrab { }
 
 #[throws(OE)]
 fn api_piece_op<O: ApiPieceOp>(form : Json<ApiPiece<O>>)
@@ -139,67 +192,72 @@ fn api_piece_op<O: ApiPieceOp>(form : Json<ApiPiece<O>>)
   let cl = &g.clients.byid(client)?;
   // ^ can only fail if we raced
   let player = cl.player;
-  let pl = g.gs.players.byid(player)?;
-  let g_updates = &mut g.updates;
-  let gs_pieces = &mut g.gs.pieces;
-  let gs_gen = &mut g.gs.gen;
-  let gs_log = &mut g.gs.log;
-  let r : Result<(),GameError> = (||{
-    let piece = decode_visible_pieceid(form.piece);
-    let p = gs_pieces.byid_mut(piece)?;
+  let gs = &mut g.gs;
+  let _ = gs.players.byid(player)?;
+  let lens = TransparentLens { };
+  let piece = lens.decode_visible_pieceid(form.piece, player);
+
+  match (||{
+    let pc = gs.pieces.byid_mut(piece)?;
+
     let q_gen = form.gen;
     let u_gen =
-      if client == p.lastclient { p.gen_lastclient }
-      else { p.gen_before_lastclient };
+      if client == pc.lastclient { pc.gen_lastclient }
+      else { pc.gen_before_lastclient };
     if u_gen > q_gen { Err(GameError::Conflict)? }
-    if p.held != None { Err(GameError::PieceHeld)? };
-    p.held = Some(player);
-    gs_gen.increment();
-    let gen = *gs_gen;
-    if client != p.lastclient {
-      p.gen_before_lastclient = p.gen_lastclient;
-      p.lastclient = client;
-    }
-    let vpiece = form.piece; // split view needs modified value!
-    let pri = PieceRenderInstructions {
-      id : vpiece, 
-      face : p.face,
-    };
-    let logentry = Arc::new(LogEntry {
-      html : format!("{} grasped {}",
-                     &htmlescape::encode_minimal(&pl.nick),
-                     p.describe_html(&pri
-                                     // split view: pri should be global
-                                     // (currently log is one global view)
-                     )),
-    });
-    gs_log.push((gen,logentry.clone()));
-    let op = PieceUpdateOp::Modify(p.prep_piecestate(&pri));
-    let update = PreparedUpdate {
-      gen,
-      us : vec![
-        PreparedUpdateEntry::Piece {
-          client,
-          sameclient_cseq : form.cseq,
-          piece : vpiece,
-          op,
-        },
-        PreparedUpdateEntry::Log (
-          logentry,
-        ),
-      ],
+    if pc.held != None && pc.held != Some(player) {
+      Err(GameError::PieceHeld)?
     };
-    let update = Arc::new(update);
-    eprintln!("UPDATE {:?}", &update);
-    // split vie wthing would go here, see also update.piece
-    p.gen_lastclient = gen;
-    for (_tplayer, tplupdates) in g_updates {
-      tplupdates.log.push_back(update.clone());
-      tplupdates.cv.notify_all();
+    let (update, logents) = form.op.op(gs,player,piece,&lens)?;
+    Ok((update, logents))
+  })() {
+    Err(err) => {
+      let err : GameError = err;
+      eprintln!("API {:?} => {:?}", &form, &err);
+    },
+    Ok((update, logents)) => {
+      let pc = gs.pieces.byid_mut(piece).expect("piece deleted by op!");
+
+      gs.gen.increment();
+      let gen = gs.gen;
+      if client != pc.lastclient {
+        pc.gen_before_lastclient = pc.gen_lastclient;
+        pc.lastclient = client;
+      }
+
+      let pri_for_all = lens.svg_pri(piece,pc,Default::default());
+
+      let update = update.map_new_state(|_|{
+        pc.prep_piecestate(&pri_for_all)
+      });
+
+      let mut us = Vec::with_capacity(1 + logents.len());
+
+      us.push(PreparedUpdateEntry::Piece {
+        client,
+        sameclient_cseq : form.cseq,
+        piece : pri_for_all.id,
+        op : update,
+      });
+
+      for logentry in logents {
+        let logentry = Arc::new(logentry);
+        gs.log.push((gen, logentry.clone()));
+        us.push(PreparedUpdateEntry::Log(logentry));
+      }
+
+      let update = PreparedUpdate { gen, us, };
+      let update = Arc::new(update);
+      eprintln!("UPDATE {:?}", &update);
+
+      pc.gen_lastclient = gen;
+      for (_tplayer, tplupdates) in &mut g.updates {
+        tplupdates.log.push_back(update.clone());
+        tplupdates.cv.notify_all();
+      }
+      eprintln!("API {:?} OK", &form);
     }
-    Ok(())
-  })();
-  eprintln!("API {:?} => {:?}", &form, &r);
+  }
   ""
 }
 
index 2a8fc82504c3476e44e77692afc02d2c6fd6ff32..f5a413b240c626aa360de32919fe15b408cc1d23 100644 (file)
@@ -22,13 +22,7 @@ pub fn make_pieceid_visible(p : PieceId) -> VisiblePieceId {
   VisiblePieceId(kd)
 }
 
-pub fn decode_visible_pieceid(p : VisiblePieceId) -> PieceId {
-  // xxx need to do censorship mapping here
-  let kd : slotmap::KeyData = p.into();
-  PieceId(kd)
-}
-
-#[derive(Debug)]
+#[derive(Debug,Copy,Clone)]
 pub struct PieceRenderInstructions {
   pub id : VisiblePieceId,
   pub face : FaceId,
index b125a9c2ea0a35f6693d8a1b3ecb739e0126efce..165f255a95f9da014fb3b215f1cc5a34ff2f42be 100644 (file)
@@ -70,6 +70,16 @@ impl<NS> PieceUpdateOp<NS> {
       Move(_) => None,
     }
   }
+  pub fn map_new_state<NS2,F: FnOnce(NS) -> NS2>(self, f:F)
+                            -> PieceUpdateOp<NS2> {
+    use PieceUpdateOp::*;
+    match self {
+      Delete => Delete,
+      Insert(ns) => Insert(f(ns)),
+      Modify(ns) => Modify(f(ns)),
+      Move(pos) => Move(pos),
+    }
+  }
 }      
 
 #[derive(Debug,Default)]