chiark / gitweb /
reset compiles
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 19:42:08 +0000 (20:42 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 22 Aug 2020 19:42:08 +0000 (20:42 +0100)
src/bin/otter.rs
src/cmdlistener.rs
src/commands.rs
src/gamestate.rs
src/global.rs
src/spec.rs
src/updates.rs

index 07c66655443dd3f335bcded18825531ca940ff7c..8fc2db41f946bcfd35b6d81408cb6067ea528bb9 100644 (file)
@@ -69,15 +69,14 @@ inventory::collect!(Subcommand);
 struct ArgumentParseError(String);
 display_as_debug!(ArgumentParseError);
 
-fn parse_args<T,F,C>(
+fn parse_args<T,F>(
   args: Vec<String>,
   apmaker: &F,
-  completer: &C,
+  completer: Option<&dyn Fn(&mut T) -> Result<(), ArgumentParseError>>,
   extra_help: Option<&dyn Fn(&mut dyn Write) -> Result<(), io::Error>>,
 ) -> T
 where T: Default,
       F: Fn(&mut T) -> ArgumentParser,
-      C: Fn(&mut T) -> Result<(), ArgumentParseError>,
 {
   let mut parsed = Default::default();
   let ap = apmaker(&mut parsed);
@@ -103,11 +102,13 @@ where T: Default,
     });
   }
   mem::drop(ap);
-  completer(&mut parsed).unwrap_or_else(|e:ArgumentParseError| {
-    let ap = apmaker(&mut parsed);
-    ap.error(&us, &e.0, &mut stderr);
-    exit(EXIT_USAGE);
-  });
+  if let Some(completer) = completer {
+    completer(&mut parsed).unwrap_or_else(|e:ArgumentParseError| {
+      let ap = apmaker(&mut parsed);
+      ap.error(&us, &e.0, &mut stderr);
+      exit(EXIT_USAGE);
+    });
+  }
   parsed
 }
 
@@ -118,7 +119,7 @@ fn main() {
     subcommand: String,
     subargs: Vec<String>,
   };
-  let ma = parse_args::<MainArgs,_,_>(
+  let ma = parse_args::<MainArgs,_>(
     env::args().collect(),
   &|ma|{
     use argparse::*;
@@ -148,7 +149,7 @@ fn main() {
     verbose.add_option(&["-v","--verbose"], IncrBy(1),
        "increase verbosity (default is short progress messages)");
     ap
-  }, &|ma| {
+  }, Some(&|ma| {
     if let ref mut scope @None = ma.opts.scope {
       let user = env::var("USER").map_err(|e| ArgumentParseError(
         format!("--scope-unix needs USER env var: {}", &e)
@@ -156,7 +157,7 @@ fn main() {
       *scope = Some(ManagementScope::Unix { user });
     }
     Ok(())
-  }, Some(&|w|{
+  }), Some(&|w|{
     writeln!(w, "\nSubcommands:")?;
     let maxlen = inventory::iter::<Subcommand>.into_iter()
       .map(|Subcommand(verb,_,_)| verb.len())
@@ -226,7 +227,7 @@ impl DerefMut for ConnForGame {
 impl ConnForGame {
   #[throws(AE)]
   fn alter_game(&mut self, insns: Vec<MgmtGameInstruction>,
-                f: &mut dyn FnMut(&MgmtGameResponse) -> Result<(),AE>)
+                f: Option<&mut dyn FnMut(&MgmtGameResponse) -> Result<(),AE>>)
                 -> Vec<MgmtGameResponse> {
     let insns_len = insns.len();
     let cmd = MgmtCommand::AlterGame {
@@ -241,25 +242,28 @@ impl ConnForGame {
       wat => Err(anyhow!("unexpected AlterGame response: {:?} => {:?}",
                          &cmd, &wat))?,
     };
-    for response in &responses {
-      f(response)?;
+    if let Some(f) = f {
+      for response in &responses {
+        f(response)?;
+      }
     }
     responses
   }
 
-  fn get_players(&mut self) ->
-    Result<(PlayerMap, HashMap<String,PlayerId>),AE>
+  fn get_info(&mut self) -> Result<
+      (MgmtGameResponseGameInfo, HashMap<String,PlayerId>
+      ),AE>
   {
     let mut players = self.alter_game(
-      vec![ MgmtGameInstruction::GetPlayers { } ],
-      &mut |_|Ok(())
+      vec![ MgmtGameInstruction::Info ],
+      None,
     )?;
-    let players = match players.pop() {
-      Some(MgmtGameResponse::Players(players)) => players,
+    let info = match players.pop() {
+      Some(MgmtGameResponse::Info(info)) => info,
       wat => Err(anyhow!("GetGstate got {:?}", &wat))?,
     };
     let mut nick2id = HashMap::new();
-    for (player, pstate) in players.iter() {
+    for (player, pstate) in info.players.iter() {
       use hash_map::Entry::*;
       match nick2id.entry(pstate.nick.clone()) {
         Occupied(oe) => Err(anyhow!("game has duplicate nick {:?}, {} {}",
@@ -267,7 +271,7 @@ impl ConnForGame {
         Vacant(ve) => ve.insert(player),
       };
     }
-    Ok((players, nick2id))
+    Ok((info, nick2id))
   }
 }
 
@@ -285,7 +289,7 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> {
 
   // create missing players
   let (added_players,) = {
-    let (_, nick2id) = chan.get_players()?;
+    let (_, nick2id) = chan.get_info()?;
 
     let mut insns = vec![];
     for pspec in &spec.players {
@@ -296,7 +300,7 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> {
       }
     }
     let mut added_players = HashSet::new();
-    chan.alter_game(insns, &mut |response| {
+    chan.alter_game(insns, Some(&mut |response| {
       let player = match response {
         &MgmtGameResponse::AddPlayer(player) => player,
         _ => Err(anyhow!("AddPlayer strange answer {:?}",
@@ -304,14 +308,14 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> {
       };
       added_players.insert(player);
       Ok(())
-    })?;
+    }))?;
 
     (added_players,)
   };
 
   // ensure players have access tokens
   {
-    let (_, nick2id) = chan.get_players()?;
+    let (_, nick2id) = chan.get_info()?;
     let mut insns = vec![];
     let mut resetreport = vec![];
     let mut resetspecs = vec![];
@@ -338,12 +342,12 @@ fn setup_table(chan: &mut ConnForGame, spec: &TableSpec) -> Result<(),AE> {
       players: resetreport.clone(),
     });
     let mut got_tokens = None;
-    chan.alter_game(insns, &mut |response| {
+    chan.alter_game(insns, Some(&mut |response| {
       if let MgmtGameResponse::PlayerAccessTokens(tokens) = response {
         got_tokens = Some(tokens.clone());
       }
       Ok(())
-    })?;
+    }))?;
     let got_tokens = match got_tokens {
       Some(t) if t.len() == resetreport.len() => t,
       wat => Err(anyhow!("Did not get expected ReportPlayerAccesses! {:?}",
@@ -371,6 +375,8 @@ fn read_spec<T: DeserializeOwned>(filename: &str, what: &str) -> T {
   })().with_context(|| format!("read {} {:?}", what, filename))?
 }
 
+//---------- create-game ----------
+
 mod create_table {
   use super::*;
 
@@ -381,28 +387,19 @@ mod create_table {
   }
 
   fn subargs(sa: &mut Args) -> ArgumentParser {
-      use argparse::*;
-      let mut ap = ArgumentParser::new();
-      ap.refer(&mut sa.name).required()
-        .add_argument("TABLE-NAME",Store,"table name");
-      ap.refer(&mut sa.file).required()
-        .add_argument("TABLE-SPEC-TOML",Store,"table spec");
-      ap
+    use argparse::*;
+    let mut ap = ArgumentParser::new();
+    ap.refer(&mut sa.name).required()
+      .add_argument("TABLE-NAME",Store,"table name");
+    ap.refer(&mut sa.file).required()
+      .add_argument("TABLE-SPEC-TOML",Store,"table spec");
+    ap
   }
 
-  #[throws(ArgumentParseError)]
-  fn complete(_sa: &mut Args) { }
-
   #[throws(E)]
   fn call(_sc: &Subcommand, ma: MainOpts, args: Vec<String>) {
-    let args = parse_args::<Args,_,_>(args, &subargs, &complete, None);
-
-    if ma.verbose >= 2 {
-      eprintln!("CREATE-TABLE {:?} {:?}", &ma, &args);
-    }
-
-    let spec : TableSpec = read_spec(&args.file, "game spec")?;
-
+    let args = parse_args::<Args,_>(args, &subargs, None, None);
+    let spec : TableSpec = read_spec(&args.file, "table spec")?;
     let mut chan = connect(&ma)?;
 
     chan.cmd(&MgmtCommand::CreateGame {
@@ -429,8 +426,81 @@ mod create_table {
   )}
 }
 
+//---------- reset-game ----------
+
+type Insn = MgmtGameInstruction;
+
+mod reset_game {
+  use super::*;
 
-/*
-impl Default for Args {
-  fn default() -> Args { Args { name: String::new(), file: String::new() }}
-}*/
+  #[derive(Default,Debug)]
+  struct Args {
+    name: String,
+    game_file: String,
+    table_file: Option<String>,
+  }
+
+  fn subargs(sa: &mut Args) -> ArgumentParser {
+    use argparse::*;
+    let mut ap = ArgumentParser::new();
+    ap.refer(&mut sa.table_file)
+      .add_option(&["--reset-table"],StoreOption,
+                  "reset the players and access too");
+    ap.refer(&mut sa.name).required()
+      .add_argument("TABLE-NAME",Store,"table name");
+    ap.refer(&mut sa.game_file).required()
+      .add_argument("GAME-SPEC-TOML",Store,"game spec");
+    ap
+  }
+
+  fn call(_sc: &Subcommand, ma: MainOpts, args: Vec<String>) ->Result<(),AE> {
+    let args = parse_args::<Args,_>(args, &subargs, None, None);
+    let mut chan = ConnForGame {
+      conn: connect(&ma)?,
+      name: args.name.clone(),
+      how: MgmtGameUpdateMode::Bulk,
+    };
+    let game : GameSpec = read_spec(&args.game_file, "game spec")?;
+    if let Some(table_file) = args.table_file {
+      let table_spec = read_spec(&table_file, "table spec")?;
+      setup_table(&mut chan, &table_spec)?;
+    }
+
+    let mut insns = vec![];
+
+    chan.alter_game(
+      vec![ MgmtGameInstruction::ListPieces ],
+      Some(&mut |response|{ match response {
+        MgmtGameResponse::Pieces(pieces) => {
+          for p in pieces {
+            insns.push(MgmtGameInstruction::DeletePiece(p.piece));
+          }
+          Ok(())
+        },
+        wat => Err(anyhow!("ListPieces => {:?}", &wat))?,
+      }})
+    )?;
+
+    if let Some(size) = game.table_size {
+      insns.push(Insn::SetTableSize(size));
+    }
+
+    let mut game = game;
+    for pspec in game.pieces.drain(..) {
+      insns.push(Insn::AddPieces(pspec));
+    }
+
+    chan.alter_game(insns, None)?;
+
+    if ma.verbose >= 0 {
+      eprintln!("reset successful.");
+    }
+    Ok(())
+  }
+
+  inventory::submit!{Subcommand(
+    "reset",
+    "Reset the state of the game table",
+    call,
+  )}
+}
index dbf9dc50b626a15626ec4cd36b3c3139ace3eeeb..444a668bc9bdaee7deefeb5eaf7354a587c36488 100644 (file)
@@ -204,6 +204,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
 
     CreateGame { name, insns } => {
       let gs = crate::gamestate::GameState {
+        table_size : DEFAULT_TABLE_SIZE,
         pieces : Default::default(),
         players : Default::default(),
         log : Default::default(),
@@ -262,6 +263,7 @@ fn execute(cs: &mut CommandStream, cmd: MgmtCommand) -> MgmtResponse {
 struct UpdateHandlerBulk {
   pieces : slotmap::SparseSecondaryMap<PieceId, PieceUpdateOp<()>>,
   logs : bool,
+  raw : Vec<PreparedUpdateEntry>,
 }
 
 #[derive(Debug)]
@@ -282,7 +284,8 @@ impl UpdateHandler {
   #[throws(SVGProcessingError)]
   fn accumulate(&mut self, g: &mut Instance,
                 upieces: Vec<(PieceId,PieceUpdateOp<()>)>,
-                ulogs: Vec<LogEntry>) {
+                ulogs: Vec<LogEntry>,
+                mut raw: Vec<PreparedUpdateEntry>) {
     use UpdateHandler::*;
     match self {
       Bulk(bulk) => {
@@ -301,6 +304,7 @@ impl UpdateHandler {
           };
         }
         bulk.logs |= ulogs.len() != 0;
+        bulk.raw.append(&mut raw);
       },
       Online => {
         let estimate = upieces.len() + ulogs.len();
@@ -310,6 +314,7 @@ impl UpdateHandler {
           buf.piece_update(upiece, uuop, &lens);
         }
         buf.log_updates(ulogs);
+        buf.raw_updates(raw);
       },
     }
   }
@@ -331,6 +336,8 @@ impl UpdateHandler {
             // xxx use cs.desc
           }]);
         }
+
+        buf.raw_updates(bulk.raw);
       },
       Online => { },
     }
@@ -345,8 +352,8 @@ fn execute_for_game(cs: &CommandStream, ig: &mut InstanceGuard,
   let mut responses = Vec::with_capacity(insns.len());
   let ok = (||{
     for insn in insns.drain(0..) {
-      let (upieces, ulogs, resp) = execute_game_insn(cs, ig, insn)?;
-      uh.accumulate(ig, upieces, ulogs)?;
+      let (upieces, ulogs, raw, resp) = execute_game_insn(cs, ig, insn)?;
+      uh.accumulate(ig, upieces, ulogs, raw.unwrap_or_default())?;
       responses.push(resp);
     }
     uh.complete(cs,ig)?;
@@ -363,47 +370,85 @@ const XXX_DEFAULT_POSD : Pos = [5,5];
 
 const CREATE_PIECES_MAX : u32 = 300;
 
+type ExecuteGameInsnResults = (
+  Vec<(PieceId,PieceUpdateOp<()>)>,
+  Vec<LogEntry>,
+  Option<Vec<PreparedUpdateEntry>>,
+  MgmtGameResponse,
+);
+
 #[throws(ME)]
 fn execute_game_insn(cs: &CommandStream,
                      ig: &mut InstanceGuard, update: MgmtGameInstruction)
-                     -> (Vec<(PieceId,PieceUpdateOp<()>)>,
-                         Vec<LogEntry>,
-                         MgmtGameResponse) {
+                     -> ExecuteGameInsnResults {
   use MgmtGameInstruction::*;
   use MgmtGameResponse::*;
+  type Insn = MgmtGameInstruction;
+  type Resp = MgmtGameResponse;
+  fn readonly(_ig: &InstanceGuard, resp: Resp) -> ExecuteGameInsnResults {
+    (vec![], vec![], None, resp)
+  }
+
   match update {
-    Noop { } => (vec![], vec![], Fine),
+    Noop { } => readonly(ig, Fine),
+
+    Insn::DeletePiece(_) => panic!(),//xxx
+
+    MgmtGameInstruction::SetTableSize(size) => {
+      ig.gs.table_size = size;
+      (vec![],
+       vec![ LogEntry {
+         html: format!("The table was resized to {}x{}", size[0], size[1]),
+       }],
+       Some(vec![ PreparedUpdateEntry::SetTableSize(size) ]),
+       Fine)
+    }
 
     MgmtGameInstruction::AddPlayer(pl) => {
+      if ig.gs.players.values().any(|p| p.nick == pl.nick) {
+        Err(ME::AlreadyExists)?;
+      }
       let player = ig.player_new(pl)?;
       #[allow(clippy::useless_format)] // xxx below
-      // xxx check for duplicate players
       (vec![],
        vec![ LogEntry {
          html: format!("The facilitator added a player xxx"),
        } ],
+       None,
        MgmtGameResponse::AddPlayer(player))
     },
 
+    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)
+          .unwrap_or_else(|_|"[html formatting failed!]".to_string());
+        MgmtGamePieceInfo { piece, pos, face, desc_html }
+      }).collect();
+      Resp::Pieces(pieces)
+    }),
+
     RemovePlayer(player) => {
       ig.player_remove(player)?;
-      (vec![], vec![], Fine)
+      (vec![], vec![], None, Fine)
     },
 
-    GetPlayers { } => {
+    MgmtGameInstruction::Info { } => {
       let players = ig.gs.players.clone();
-      (vec![], vec![], Players(players))
+      let table_size = ig.gs.table_size;
+      let info = MgmtGameResponseGameInfo { table_size, players };
+      (vec![], vec![], None, Resp::Info(info))
     },
 
     ResetPlayerAccesses { players } => {
       let tokens = ig.players_access_reset(&players)?
         .drain(0..).map(|token| vec![token]).collect();
-      (vec![], vec![], PlayerAccessTokens(tokens))
+      (vec![], vec![], None, PlayerAccessTokens(tokens))
     }
 
     ReportPlayerAccesses { players } => {
       let tokens = ig.players_access_report(&players)?;
-      (vec![], vec![], PlayerAccessTokens(tokens))
+      (vec![], vec![], None, PlayerAccessTokens(tokens))
     }
 
     SetFixedPlayerAccess { player, token } => {
@@ -416,10 +461,10 @@ fn execute_game_insn(cs: &CommandStream,
       ig.player_access_register_fixed(
         player, token, authorised
       )?;
-      (vec![], vec![], Fine)
+      (vec![], vec![], None, Fine)
     }
 
-    AddPiece(PiecesSpec{ pos,posd,count,face,info }) => {
+    AddPieces(PiecesSpec{ pos,posd,count,face,info }) => {
       let gs = &mut ig.gs;
       let count = count.unwrap_or(1);
       if count > CREATE_PIECES_MAX { throw!(LimitExceeded) }
@@ -445,9 +490,11 @@ fn execute_game_insn(cs: &CommandStream,
         pos[1] += posd[1];
       }
 
-      (updates, vec![ LogEntry {
-        html: format!("The facilitaror added {} pieces", count),
-      }],
+      (updates,
+       vec![ LogEntry {
+         html: format!("The facilitaror added {} pieces", count),
+       }],
+       None,
        Fine
       )
     },
index cdd3b5f3a82004d8b96ee6e15a262b2fd18f31fa..a61b6ae3c1faafbfccb45f375811155ea7205ca6 100644 (file)
@@ -24,11 +24,15 @@ pub enum MgmtResponse {
 #[derive(Debug,Serialize,Deserialize)]
 pub enum MgmtGameInstruction {
   Noop,
-  AddPiece(PiecesSpec),
-  // todo: RemovePiece
+  Info,
+  SetTableSize(Pos),
+
+  ListPieces,
+  AddPieces(PiecesSpec),
+  DeletePiece(PieceId),
+
   AddPlayer(PlayerState),
   RemovePlayer(PlayerId),
-  GetPlayers,
   ResetPlayerAccesses { players: Vec<PlayerId> },
   ReportPlayerAccesses { players: Vec<PlayerId> },
   SetFixedPlayerAccess { player: PlayerId, token: RawToken },
@@ -37,9 +41,26 @@ pub enum MgmtGameInstruction {
 #[derive(Debug,Serialize,Deserialize)]
 pub enum MgmtGameResponse {
   Fine,
+  Info(MgmtGameResponseGameInfo),
+
+  Pieces(Vec<MgmtGamePieceInfo>),
+
   AddPlayer(PlayerId),
   PlayerAccessTokens(Vec<Vec<RawToken>>),
-  Players(PlayerMap),
+}
+
+#[derive(Debug,Clone,Serialize,Deserialize)]
+pub struct MgmtGameResponseGameInfo {
+  pub table_size: Pos,
+  pub players: PlayerMap,
+}
+
+#[derive(Debug,Clone,Serialize,Deserialize)]
+pub struct MgmtGamePieceInfo {
+  pub piece: PieceId,
+  pub pos: Pos,
+  pub face: FaceId,
+  pub desc_html: String,
 }
 
 #[derive(Debug,Copy,Clone,Serialize,Deserialize)]
index 35aa60517cb2d4afaf5ab75a2dfb854771a12f99..905f5cb6825029be977f7aee670397bbca6200c9 100644 (file)
@@ -27,6 +27,8 @@ visible_slotmap_key!{ VisiblePieceId('.') }
 #[serde(try_from="f64")]
 pub struct ZCoord(pub f64);
 
+pub const DEFAULT_TABLE_SIZE : Pos = [ 200, 100 ];
+
 // ---------- general data types ----------
 
 #[derive(Debug,Copy,Clone,Serialize,Deserialize,Eq,PartialEq,Ord,PartialOrd)]
@@ -39,6 +41,7 @@ pub struct ZLevel {
 
 #[derive(Debug,Serialize,Deserialize)]
 pub struct GameState {
+  pub table_size : Pos, // xxx send to client etc.
   pub pieces : DenseSlotMap<PieceId,PieceState>,
   pub players : PlayerMap,
   pub gen : Generation,
@@ -207,7 +210,8 @@ pub fn xxx_gamestate_init() -> GameState {
     gen.increment();
     pieces.insert(pr);
   }
-  GameState { pieces, gen, players : Default::default(),
+  GameState { table_size : DEFAULT_TABLE_SIZE,
+              pieces, gen, players : Default::default(),
               max_z: ZCoord(0.),
               log : Default::default(), }
 }
index 6edd2df48462079de35de878a0a3a86ddb153e72..7bc960b6ca28a48885e1708bddc08cb9da637345 100644 (file)
@@ -334,6 +334,7 @@ impl InstanceGuard<'_> {
     // New state
     let mut gs = GameState {
       // These parts are straightforward and correct
+      table_size : self.c.g.gs.table_size,
       gen : self.c.g.gs.gen,
       max_z : self.gs.max_z,
       players,
index 5082649994848c3818179cc7357b0bbe5f1d8e25..71717f0b5da1df3949d9133a2a8a1f87323ad6b3 100644 (file)
@@ -17,7 +17,7 @@ pub struct PlayerSpec {
 
 #[derive(Debug,Serialize,Deserialize)]
 pub struct GameSpec {
-  pub table : Pos,
+  pub table_size : Option<Pos>,
   pub pieces : Vec<PiecesSpec>,
 }
 
index dd43cf34a7575c298ed8ad775d522c3a9d883843..910d3274c10dcfa3f2c60992a0053fbe142a5ecb 100644 (file)
@@ -36,6 +36,7 @@ pub enum PreparedUpdateEntry {
     piece : VisiblePieceId,
     op : PieceUpdateOp<PreparedPieceState>,
   },
+  SetTableSize(Pos),
   Log (Arc<LogEntry>),
   Error (ErrorSignaledViaUpdate),
 }
@@ -79,6 +80,7 @@ enum TransmitUpdateEntry<'u> {
     piece : VisiblePieceId,
     op : &'u PieceUpdateOp<PreparedPieceState>,
   },
+  SetTableSize(Pos),
   Log (&'u LogEntry),
   Error(ErrorSignaledViaUpdate),
 }
@@ -122,6 +124,7 @@ impl PreparedUpdateEntry {
       Log(logent) => {
         logent.html.as_bytes().len() * 3
       },
+      SetTableSize(_) |
       Error(_) => {
         100
       },
@@ -260,6 +263,10 @@ impl<'r> PrepareUpdatesBuffer<'r> {
     self.us.push(update);
   }
 
+  pub fn raw_updates(&mut self, mut raw: Vec<PreparedUpdateEntry>) {
+    self.us.append(&mut raw)
+  }
+
   pub fn log_updates(&mut self, logents: Vec<LogEntry>) {
     for logentry in logents {
       let logentry = Arc::new(logentry);
@@ -292,8 +299,9 @@ impl PreparedUpdate {
   pub fn for_transmit(&self, dest : ClientId) -> TransmitUpdate {
     let mut ents = vec![];
     for u in &self.us {
+      type Prep = PreparedUpdateEntry;
       let ue = match u {
-        &PreparedUpdateEntry::Piece
+        &Prep::Piece
         { piece, client, sameclient_cseq : cseq, ref op }
         if client == dest => {
           let zg = op.new_z_generation();
@@ -305,6 +313,9 @@ impl PreparedUpdate {
         PreparedUpdateEntry::Log(logent) => {
           TransmitUpdateEntry::Log(&logent)
         },
+        &PreparedUpdateEntry::SetTableSize(size) => {
+          TransmitUpdateEntry::SetTableSize(size)
+        },
         &PreparedUpdateEntry::Error(e) => {
           TransmitUpdateEntry::Error(e)
         }