1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
//! 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()
  }
}

impl_via_ambassador!{
  #[dyn_upcast]
  impl OutlineTrait for Piece { inner() } 

  #[dyn_upcast]
  impl PieceBaseTrait for Piece { inner() }

  #[typetag::serde(name="FastSplit")]
  impl PieceTrait for Piece { 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 InstanceGuard<'_> {
  /// The new piece will be below the old one
  #[throws(ApiPieceOpError)]
  pub fn fastsplit_split<I>(
    &mut self, player: PlayerId,
    tpiece: PieceId, _: ShowUnocculted, tpc_new_z: ShouldSetZLevel,
    implementation: I
  ) -> UpdateFromOpComplex
  where I: FnOnce(&IOccults, &GOccults, &GPlayer,
                  &mut GPiece, &IPiece,
                  &mut GPiece)
                  -> Result<UpdateFromOpComplex, ApiPieceOpError>
  {
    // All this complicated machinery exists precisely to let us make
    // pieces without having to save aux.
    //
    // The "save later" part of this ought to be unnecessarily, because
    // we'll be running in piece API context which already does that.
    // But it is clearer to call the appropriate function.
    let modperm = self.modify_pieces_not_necessarily_saving_aux();

    let ig = &mut **self;
    let gpl = ig.gs.players.byid(player)?;
    let tgpc = ig.gs.pieces.byid_mut(tpiece)?;
    let tipc = ig.ipieces.get(tpiece).ok_or(Ia::PieceGone)?;

    let fs_record = (||{
      let fsid = tgpc.fastsplit?;
      ig.ifastsplits.table.get(fsid)
    })().ok_or_else(|| internal_logic_error("fastsplit on non-fastsplit"))?;

    // The new piece (the change, which stays in place) inherits the
    // old piece's Z (but we need to give it a new generation so that
    // we don't risk having two pieces with the precise same Z).  The
    // old piece becomes the amount taken and gets the specified Z.
    let npc_z = ZLevel { z: tgpc.zlevel.z.clone(), zg: ig.gs.gen };
    if ! (tpc_new_z.inspect() > &npc_z) {
      throw!(Ia::BadPieceStateForOperation);
    }

    let mut ngpc = GPiece {
      pos:           tgpc.pos,
      face:          tgpc.face,
      held:          None,
      zlevel:        npc_z,
      pinned:        tgpc.pinned,
      occult:        default(),
      angle:         tgpc.angle,
      gen:           ig.gs.gen,
      lastclient:    default(),
      last_released: default(),
      gen_before_lastclient: Generation(0),
      xdata:         default(),
      moveable:      tgpc.moveable,
      rotateable:    tgpc.rotateable,
      fastsplit:     tgpc.fastsplit,
    };

    let (t_pu, t_unprepared) = implementation(
      &ig.ioccults, &ig.gs.occults, gpl,
      tgpc, tipc,
      &mut ngpc
    )?;

    let ipc_from_record = fs_record.ipc.clone();

    // Committing.

    // This is outside the infallible closure because borrowck
    // can't see that we drop tgpc before doing stuff with ig.
    tpc_new_z.implement(tgpc);
    (||{
      let nipc = IFastSplits::make_ipc(&mut ig.ioccults.ilks, ipc_from_record);
      let npiece = ig.gs.pieces.as_mut(modperm).insert(ngpc);
      ig.ipieces.as_mut(modperm).insert(npiece, nipc);

      // "Recalculate" the occultation of the piece we're leaving behind.
      let ((), r_unprepared) = ToRecalculate::with(|mut to_permute| {
        let () = recalculate_occultation_general(
          &mut ig.gs.gen.unique_gen(),
          &mut ig.gs.players, &mut ig.gs.pieces,
          &mut ig.gs.occults, &ig.ipieces, &ig.ioccults,
          &mut to_permute, npiece,
          // We are treat this as a new piece, but it's not, semantically.
          // We don't log anything, since that new piece is not really
          // supposed to be interesting and the piece implwementation
          // is supposed to have logged whatever is needed.
          || (),
          (),
          |_old,_new,_desc| (),
          |_puopp, ()| (),
        ).unwrap_or_else(|e| {
          // not good
          error!("failed to recalculate after piece split! {:?}", &e);
        });
        ((), to_permute.implement(ig))
      });

      let n_unprepared = vec![(
        npiece,
        PUOs::Simple(PUO::Insert(())),
      )].into_unprepared(None);

      let unprepared = chain!(
        n_unprepared,
        r_unprepared,
        t_unprepared,
      ).collect();

      (t_pu, unprepared)
    })()// <- no ?, infallible (to avoid having not completed implementation
  }

  #[throws(IE)]
  pub fn fastsplit_delete(&mut self,
                          piece: PieceId,
                          #[allow(clippy::ptr_arg)]
                          _proof_that_caller_handles_logging: &Vec<LogEntry>)
                          -> (PieceUpdateOp<(),()>, UnpreparedUpdates)
  {
    let missing_e = || internal_error_bydebug(&piece);

    // See reasoning in fastsplit_split
    let modperm = self.modify_pieces_not_necessarily_saving_aux();

    let gpc = self.gs.pieces.get(piece).ok_or_else(missing_e)?;
    let ipc = self.ipieces.get(piece).ok_or_else(missing_e)?;

    // If we are merging or deleting here, presumably someone has
    // already checked that this is appropriate wrt occultations,
    // since we don't just do that willy-nilly and occultation will
    // have been checked when deciding *what* to merge with.
    let _p: &Piece = ipc.p.direct_trait_access().downcast_piece()?;

    let _fsid: &FastSplitId = gpc.fastsplit.as_ref()
      .ok_or_else(|| internal_error_bydebug(gpc))?;
    // We allow merging things with different FastSplitIds.  The
    // FastSplitId identifies not the "kind of piece", but simply
    // ancestry.  For example, all banknotes split from the same
    // original note will have the same id, but otherwise, different
    // banknotes even of the same currency have different ids.

    match ToRecalculate::with(|mut to_permute| {
      let r = self.delete_piece(modperm,&mut to_permute, piece,|_,_,_,_|());
      (r, to_permute.implement(self))
    }) {
      (Err(e), uu_p) => {
        PrepareUpdatesBuffer::only_unprepared(self, uu_p);
        throw!(e);
      },
      (Ok(((), puo, uu_d)), uu_p) => {
        (puo, chain!(uu_d, uu_p).collect())
      },
    }
  }
}

impl IFastSplits {
  /// During piece addition: make this into a new fastsplit piece family
  ///
  /// Do not just drop the result, because it contains an OwningOccultIlkId
  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
  ///
  /// Just dropping the result on error is ok, despite it containing
  /// an OwningOccultIlkId, because in that case the whole game is toast.
  #[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())
  }

  /// Do not just drop the result, because it contains an OwningOccultIlkId
  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
    })
  }
}

#[ext(pub)]
impl<'r> &'r dyn PieceTrait {
  #[throws(PieceTraitDowncastFailed<'r>)]
  fn downcast_piece_fastsplit<P: PieceTrait>(self) -> &'r P {
    self.downcast_piece::<Piece>()?
      .ipc.as_ref().ok_or_else(
        || PieceTraitDowncastFailed { p: self, why: "unresolved fastsplit"})?
      .p.direct_trait_access() // we're just digging down, this is fine
      .downcast_piece::<P>()?
  }
}