From: Ian Jackson Date: Fri, 18 Sep 2020 22:37:00 +0000 (+0100) Subject: wip new piece records X-Git-Tag: otter-0.2.0~928 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=01e2346cb39865c51906847d09c378eece68c06e;p=otter.git wip new piece records Signed-off-by: Ian Jackson --- diff --git a/src/api.rs b/src/api.rs index cff02c05..ea2ef9f8 100644 --- a/src/api.rs +++ b/src/api.rs @@ -15,6 +15,7 @@ struct ApiPiece { 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); } @@ -99,12 +100,14 @@ fn api_piece_op(form : Json>) 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(form : Json>) 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>) 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) { 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>) 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) { 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>) impl ApiPieceOp for ApiPieceRaise { #[throws(ApiPieceOpError)] fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId, - _: &dyn Lens) + _p: &dyn Piece, _: &dyn Lens) -> (PieceUpdateOp<()>, Vec) { 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>) -> 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) { let pc = gs.pieces.byid_mut(piece).unwrap(); let (pos, clamped) = self.0.clamped(gs.table_size); diff --git a/src/cmdlistener.rs b/src/cmdlistener.rs index 4dc4daec..7f298399 100644 --- a/src/cmdlistener.rs +++ b/src/cmdlistener.rs @@ -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]; diff --git a/src/gamestate.rs b/src/gamestate.rs index 070ce2c1..aedb3814 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -54,7 +54,6 @@ pub struct GameState { #[derive(Debug,Serialize,Deserialize)] pub struct PieceState { pub pos : Pos, - pub p : Box, pub face : FaceId, pub held : Option, 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; + fn describe_pri(&self, pri : &PieceRenderInstructions) -> Html; +} + +impl 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##""##, pri.id, dragraise)?; - pr.p.svg_piece(&mut defs, &pri)?; + self.svg_piece(&mut defs, &pri)?; write!(&mut defs.0, r##""##)?; write!(&mut defs.0, r##""##, - 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)) } } diff --git a/src/global.rs b/src/global.rs index b5282277..5cec5a4c 100644 --- a/src/global.rs +++ b/src/global.rs @@ -20,6 +20,8 @@ const GAME_SAVE_LAG : Duration = Duration::from_millis(500); #[repr(transparent)] pub struct RawTokenVal(str); +pub type PiecesLoaded = SecondarySlotMap>; + // ---------- public data structure ---------- #[derive(Debug,Serialize,Deserialize)] @@ -35,6 +37,7 @@ pub struct InstanceRef (Arc>); pub struct Instance { pub name : Arc, pub gs : GameState, + pub pieces : PiecesLoaded, pub clients : DenseSlotMap, pub updates : SecondarySlotMap, pub tokens_players : TokenRegistry, @@ -56,11 +59,11 @@ pub struct Client { /// KINDS OF PERSISTENT STATE /// -/// TokenTable TokenTable GameState GameState -/// .players .pieces +/// TokenTable TokenTable GameState Instance GameState +/// .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 { - tokens_players : Vec<(RawTokenStr, PlayerId)>, +struct InstanceSaveAccesses { + 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 - = Self::load_something(&name, "a-") + let InstanceSaveAccesses:: + { 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::() { @@ -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, diff --git a/src/session.rs b/src/session.rs index 5fd918cc..5200bcf1 100644 --- a/src/session.rs +++ b/src/session.rs @@ -95,7 +95,9 @@ fn session(form : Json) -> Result { 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 { diff --git a/src/updates.rs b/src/updates.rs index 11e09b33..0734570b 100644 --- a/src/updates.rs +++ b/src/updates.rs @@ -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); >::Ok(ns) }, @@ -312,7 +315,7 @@ impl<'r> PrepareUpdatesBuffer<'r> { (update, pri_for_all.id) }, - Err(()) => { + _ => { (PieceUpdateOp::Delete(), lens.pieceid2visible(piece)) } };