chiark / gitweb /
fastsplit: Introduce fastsplit concept
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Wed, 20 Apr 2022 08:40:03 +0000 (09:40 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 30 Apr 2022 14:15:30 +0000 (15:15 +0100)
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 <ijackson@chiark.greenend.org.uk>
daemon/cmdlistener.rs
src/fastsplit.rs [new file with mode: 0644]
src/gamestate.rs
src/global.rs
src/lib.rs
src/prelude.rs

index e40ef154047fadf31f4aaa4fff265e80ef2bbd31..e1fb0b121ab641641824ca98e2eccb64868e988f 100644 (file)
@@ -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 (file)
index 0000000..39424e5
--- /dev/null
@@ -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<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
+    })
+  }
+}
index 8460504b7cea80db26f0d206172b4cf22dc5a780..8596e50096dfe365b2e2340ecc8b53271b1520c0 100644 (file)
@@ -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<FastSplitId>,
 }
 
 pub type PieceXDataState = Option<Box<dyn PieceXData>>;
@@ -526,6 +528,7 @@ impl GPiece {
       xdata: None,
       moveable: default(),
       rotateable: true,
+      fastsplit: default(),
     }
   }
 }
index 2feb7cb215cf092b05648dfd75f939ee859b421f..9ab38bb6ff1f8634e0fdee50bb8899c1d7d47f60 100644 (file)
@@ -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<RawTokenStr, PiecesLoadedRef, OccultIlksRef,
-                             PieceAliasesRef> {
+                             PieceAliasesRef, IFastSplitsRef> {
   ipieces: PiecesLoadedRef,
   ioccults: OccultIlksRef,
   pcaliases: PieceAliasesRef,
@@ -254,6 +255,7 @@ struct InstanceSaveAuxiliary<RawTokenStr, PiecesLoadedRef, OccultIlksRef,
   pub links: Arc<LinksTable>,
   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<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 (||{
@@ -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)?;
index 9f0e39f59146ed991ace791b857926795c325741..38ec79cf5d97bfd86c435393c3c27a30ef81cdcc 100644 (file)
@@ -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;
index c03b62f474a88619fca89c9687b72ba4b70fe363..6b6402a85c12deb58e40c23e679bde378fb9b963 100644 (file)
@@ -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::*;