From: Ian Jackson Date: Wed, 20 Apr 2022 08:40:03 +0000 (+0100) Subject: fastsplit: Introduce fastsplit concept X-Git-Tag: otter-1.1.0~410 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=8e87c665372c5d881c3c5a3f61dc52c98b538b94;p=otter.git fastsplit: Introduce fastsplit concept This has all the relevant data structures and support code, but does not yet implemnet the actual splitting and deleting/merging operations which will be needed for the piece api op impls. Signed-off-by: Ian Jackson --- diff --git a/daemon/cmdlistener.rs b/daemon/cmdlistener.rs index e40ef154..e1fb0b12 100644 --- a/daemon/cmdlistener.rs +++ b/daemon/cmdlistener.rs @@ -1116,6 +1116,7 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>( moveable: default(), last_released: default(), rotateable: true, + fastsplit: None, }; let SpecLoaded { p, occultable, special } = info.load(PLA { @@ -1131,6 +1132,8 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>( gpc.pos.clamped(gs.table_size)?; if gpc.zlevel > gs.max_z { gs.max_z = gpc.zlevel.clone() } let piece = gs.pieces.as_mut(modperm).insert(gpc); + let gpc = gs.pieces.get_mut(piece).ok_or_else( + || internal_logic_error("just inserted but now missing!"))?; let p = IPieceTraitObj::new(p); (||{ let ilks = &mut ig.ioccults.ilks; @@ -1138,9 +1141,11 @@ fn execute_game_insn<'cs, 'igr, 'ig: 'igr>( let data = OccultIlkData { p_occ }; ilks.load_lilk(lilk, data) }); - ig.ipieces.as_mut(modperm).insert(piece, IPiece { - p, occilk, special, - }); + let mut ipc = IPiece { p, occilk, special }; + if let Some(fsid) = &mut gpc.fastsplit { + (ipc, *fsid) = ig.ifastsplits.new_original(ilks, ipc); + } + ig.ipieces.as_mut(modperm).insert(piece, ipc); updates.push((piece, PieceUpdateOp::InsertQuiet(()))); })(); // <- no ?, infallible (to avoid leaking ilk) pos = (pos + posd)?; diff --git a/src/fastsplit.rs b/src/fastsplit.rs new file mode 100644 index 00000000..39424e53 --- /dev/null +++ b/src/fastsplit.rs @@ -0,0 +1,127 @@ +//! Pieces that can split and merge in play +//! +//! *Not* done to enable sharing of the piece image between instances +//! of "the same" piece. Rather, to avoid having to rewrite the aux +//! file when a piece is split or merged. +//! +//! The approach is that splittable/mergeable pieces trait objects +//! are all wrapped up in fastsplit::Piece, and GPiece has an ID +//! that can make more of them so they can be recovered on game load. + +use crate::prelude::*; + +visible_slotmap_key!{ FastSplitId(b'F') } + +impl FastSplitId { + pub fn new_placeholder() -> Option { Some(default()) } +} + +#[derive(Default,Debug,Serialize,Deserialize)] +pub struct IFastSplits { + table: DenseSlotMap +} + +use crate::*; // wat, https://github.com/rust-lang/rust/pull/52234 + +#[derive(Serialize,Deserialize,Debug)] +struct Piece { + // When we deserialize, we can effectiely clone the contents of the Arc. + // But it contains an OccultIlkOwningId which is not Clone (since + // it needs to manage the refcount in the occult ilks table). + // + // So instead, we serialize this to DummyPiece. That means that + // when we serialize, we only visit each Arc once, via Record. So + // when we deserialize, we preserve the original number of + // OccultIlkOwningIds. + // + // When loaded, an occultable Piece has in fact *two* OccultIlkOwningIds, + // one in the IPiece here inside this Arc (accessible from Piece + // or Record) and one in the outer IPiece which is in the IPieces table. + // Trying to optimise one of these away would be quite confusing. + #[serde(skip)] + ipc: Option>, +} + +#[derive(Debug,Serialize,Deserialize)] +pub struct Record { + ipc: Arc, +} + +impl Piece { + fn inner(&self) -> &dyn PieceTrait { + self.ipc + .as_ref() + .expect("attempted to invoke unresolved fastsplit::Piece") + .p.direct_trait_access() + } +} + +#[dyn_upcast] +impl OutlineTrait for Piece { + ambassador_impl_OutlineTrait_body_single_struct!{ inner() } +} +#[dyn_upcast] +impl PieceBaseTrait for Piece { + ambassador_impl_PieceBaseTrait_body_single_struct!{ inner() } +} +#[typetag::serde(name="FastSplit")] +impl PieceTrait for Piece { + ambassador_impl_PieceTrait_body_single_struct!{ inner() } +} + +impl Record { + fn dummy() -> Record { + let p = Piece { ipc: None }; + let p = IPieceTraitObj::new(Box::new(p) as _); + let ipc = IPiece { p, occilk: None, special: default() }; + Record { ipc: Arc::new(ipc) } + } +} + +impl IFastSplits { + /// During piece addition: make this into a new fastsplit piece family + pub fn new_original(&mut self, ilks: &mut OccultIlks, ipc: IPiece) + -> (IPiece, FastSplitId) { + let ipc = Arc::new(ipc); + let record = Record { ipc: ipc.clone() }; + let fsid = self.table.insert(record); + (Self::make_ipc(ilks, ipc), fsid) + } + + /// During game load: recover a proper IPiece for a fastsplit piece + #[throws(as Option)] + pub fn recover_ipc(&self, ilks: &mut OccultIlks, fsid: FastSplitId) + -> IPiece { + let record = self.table.get(fsid)?; + Self::make_ipc(ilks, record.ipc.clone()) + } + + fn make_ipc(ilks: &mut OccultIlks, ipc: Arc) -> IPiece { + let occilk = ipc.occilk.as_ref().map(|i| ilks.clone_iilk(i)); + let special = ipc.special.clone(); + let piece = Box::new(Piece { ipc: Some(ipc) }); + IPiece { + p: IPieceTraitObj::new(piece as _), + occilk, special, + } + } + + /// During save: free up any now-unused family records + pub fn cleanup(&mut self, ilks: &mut OccultIlks) { + self.table.retain(|_fsid, record| { + if Arc::strong_count(&record.ipc) != 1 { return true; } + let removed = mem::replace(record, Record::dummy()); + if_let!{ + Ok(ipc) = Arc::try_unwrap(removed.ipc); + Err(busy) => { + // someone upgraded a weak reference, since we have the only + // strong one? but we don't use weak references. odd. + *record = Record { ipc: busy }; + return true; + } + }; + if let Some(i) = ipc.occilk { ilks.dispose_iilk(i); } + false + }) + } +} diff --git a/src/gamestate.rs b/src/gamestate.rs index 8460504b..8596e500 100644 --- a/src/gamestate.rs +++ b/src/gamestate.rs @@ -84,6 +84,8 @@ pub struct GPiece { // usual variable: gpc pub xdata: PieceXDataState, pub moveable: PieceMoveable, #[serde(default)] pub rotateable: bool, + #[serde(default, skip_serializing_if="Option::is_none")] + pub fastsplit: Option, } pub type PieceXDataState = Option>; @@ -526,6 +528,7 @@ impl GPiece { xdata: None, moveable: default(), rotateable: true, + fastsplit: default(), } } } diff --git a/src/global.rs b/src/global.rs index 2feb7cb2..9ab38bb6 100644 --- a/src/global.rs +++ b/src/global.rs @@ -61,6 +61,7 @@ pub struct Instance { pub bundle_hashes: bundles::HashCache, pub asset_url_key: AssetUrlKey, pub local_libs: shapelib::Registry, + pub ifastsplits: IFastSplits, } pub struct PlayerRecord { @@ -244,7 +245,7 @@ pub struct InstanceContainer { #[derive(Debug,Default,Serialize,Deserialize)] struct InstanceSaveAuxiliary { + PieceAliasesRef, IFastSplitsRef> { ipieces: PiecesLoadedRef, ioccults: OccultIlksRef, pcaliases: PieceAliasesRef, @@ -254,6 +255,7 @@ struct InstanceSaveAuxiliary, asset_url_key: AssetUrlKey, #[serde(default)] pub bundle_hashes: bundles::HashCache, + ifastsplits: IFastSplitsRef, } pub struct PrivateCaller(()); @@ -368,6 +370,7 @@ impl Instance { bundle_hashes: default(), asset_url_key: AssetUrlKey::new_random()?, local_libs: default(), + ifastsplits: default(), }; let c = InstanceContainer { @@ -529,6 +532,7 @@ impl Instance { asset_url_key: AssetUrlKey::Dummy, local_libs: default(), iplayers: default(), + ifastsplits: default(), } } @@ -1114,6 +1118,7 @@ impl InstanceGuard<'_> { let ipieces = &s.c.g.ipieces; let ioccults = &s.c.g.ioccults; let pcaliases = &s.c.g.pcaliases; + let ifastsplits = &s.c.g.ifastsplits; let tokens_players: Vec<(&str, PlayerId)> = { let global_players = GLOBAL.players.read(); s.c.g.tokens_players.tr @@ -1133,7 +1138,7 @@ impl InstanceGuard<'_> { let bundle_hashes = s.c.g.bundle_hashes.clone(); let isa = InstanceSaveAuxiliary { ipieces, ioccults, tokens_players, aplayers, acl, links, - pcaliases, asset_url_key, bundle_hashes, + pcaliases, asset_url_key, bundle_hashes, ifastsplits, }; rmp_serde::encode::write_named(w, &isa) })?; @@ -1156,9 +1161,10 @@ impl InstanceGuard<'_> { fn load_game(accounts: &AccountsGuard, games: &mut GamesGuard, name: InstanceName) -> Option { - let InstanceSaveAuxiliary:: { - tokens_players, mut ipieces, ioccults, mut aplayers, acl, links, - pcaliases, asset_url_key, bundle_hashes, + let InstanceSaveAuxiliary:: + { + tokens_players, mut ipieces, mut ioccults, mut aplayers, acl, links, + pcaliases, asset_url_key, bundle_hashes, mut ifastsplits, } = match Self::load_something(&name, "a-") { Ok(data) => data, Err(e) => if (||{ @@ -1183,6 +1189,26 @@ impl InstanceGuard<'_> { secondary.retain(|k,_v| primary.contains_key(k)); } + for (piece, gpc) in &mut gs.pieces.0 { + if_chain!{ + if let Some(fsid) = gpc.fastsplit; + if let Some(ipc) = ipieces.get_mut(piece); /* ought to be good! */ + let ilks = &mut ioccults.ilks; + then { + if let Some(old_iilk) = ipc.occilk.take() { + ilks.dispose_iilk(old_iilk); + } + if let Some(got) = ifastsplits.recover_ipc(ilks, fsid) { + *ipc = got; + } else { + ipieces.remove(piece); + // This will get rid of it from gpieces, too, below + } + } + } + } + ifastsplits.cleanup(&mut ioccults.ilks); + discard_mismatches(&mut gs.players, &mut aplayers); discard_mismatches(&mut gs.pieces.0, &mut ipieces); @@ -1233,6 +1259,7 @@ impl InstanceGuard<'_> { bundle_specs: default(), // set by load_game_bundles asset_url_key, bundle_hashes, + ifastsplits, }; let b = InstanceBundles::reload_game_bundles(&mut g)?; diff --git a/src/lib.rs b/src/lib.rs index 9f0e39f5..38ec79cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ pub mod dice; pub mod debugmutex; pub mod debugreader; pub mod error; +pub mod fastsplit; pub mod gamestate; pub mod global; pub mod hand; diff --git a/src/prelude.rs b/src/prelude.rs index c03b62f4..6b6402a8 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -157,6 +157,7 @@ pub use crate::debugreader::DebugReader; pub use crate::error::*; pub use crate::fake_rng::*; pub use crate::fake_time::*; +pub use crate::fastsplit::*; pub use crate::gamestate::*; pub use crate::global::*; pub use crate::hidden::*;