moveable: default(),
last_released: default(),
rotateable: true,
+ fastsplit: None,
};
let SpecLoaded { p, occultable, special } =
info.load(PLA {
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;
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)?;
--- /dev/null
+//! 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<FastSplitId> { Some(default()) }
+}
+
+#[derive(Default,Debug,Serialize,Deserialize)]
+pub struct IFastSplits {
+ table: DenseSlotMap<FastSplitId, Record>
+}
+
+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<Arc<IPiece>>,
+}
+
+#[derive(Debug,Serialize,Deserialize)]
+pub struct Record {
+ ipc: Arc<IPiece>,
+}
+
+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>) -> 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
+ })
+ }
+}
pub xdata: PieceXDataState,
pub moveable: PieceMoveable,
#[serde(default)] pub rotateable: bool,
+ #[serde(default, skip_serializing_if="Option::is_none")]
+ pub fastsplit: Option<FastSplitId>,
}
pub type PieceXDataState = Option<Box<dyn PieceXData>>;
xdata: None,
moveable: default(),
rotateable: true,
+ fastsplit: default(),
}
}
}
pub bundle_hashes: bundles::HashCache,
pub asset_url_key: AssetUrlKey,
pub local_libs: shapelib::Registry,
+ pub ifastsplits: IFastSplits,
}
pub struct PlayerRecord {
#[derive(Debug,Default,Serialize,Deserialize)]
struct InstanceSaveAuxiliary<RawTokenStr, PiecesLoadedRef, OccultIlksRef,
- PieceAliasesRef> {
+ PieceAliasesRef, IFastSplitsRef> {
ipieces: PiecesLoadedRef,
ioccults: OccultIlksRef,
pcaliases: PieceAliasesRef,
pub links: Arc<LinksTable>,
asset_url_key: AssetUrlKey,
#[serde(default)] pub bundle_hashes: bundles::HashCache,
+ ifastsplits: IFastSplitsRef,
}
pub struct PrivateCaller(());
bundle_hashes: default(),
asset_url_key: AssetUrlKey::new_random()?,
local_libs: default(),
+ ifastsplits: default(),
};
let c = InstanceContainer {
asset_url_key: AssetUrlKey::Dummy,
local_libs: default(),
iplayers: default(),
+ ifastsplits: default(),
}
}
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
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)
})?;
fn load_game(accounts: &AccountsGuard,
games: &mut GamesGuard,
name: InstanceName) -> Option<InstanceRef> {
- let InstanceSaveAuxiliary::<String,ActualIPieces,IOccults,PieceAliases> {
- tokens_players, mut ipieces, ioccults, mut aplayers, acl, links,
- pcaliases, asset_url_key, bundle_hashes,
+ let InstanceSaveAuxiliary::
+ <String,ActualIPieces,IOccults,PieceAliases,IFastSplits> {
+ 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 (||{
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);
bundle_specs: default(), // set by load_game_bundles
asset_url_key,
bundle_hashes,
+ ifastsplits,
};
let b = InstanceBundles::reload_game_bundles(&mut g)?;
pub mod debugmutex;
pub mod debugreader;
pub mod error;
+pub mod fastsplit;
pub mod gamestate;
pub mod global;
pub mod hand;
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::*;