From b4d09fdea8ec87a7101b7f0eea06030b06b1cebb Mon Sep 17 00:00:00 2001 From: Ian Jackson Date: Sun, 1 May 2022 14:43:04 +0100 Subject: [PATCH] fastsplit_delete: Implement, and use in currency Amazingly, this seems to work at least in my basic ad-hoc test. The log message needs a little work. Signed-off-by: Ian Jackson --- src/currency.rs | 117 +++++++++++++++++++++++++++++++++++++++++++++++ src/fastsplit.rs | 38 +++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/src/currency.rs b/src/currency.rs index 408f1334..ed8d3ac5 100644 --- a/src/currency.rs +++ b/src/currency.rs @@ -164,4 +164,121 @@ impl PieceTrait for Banknote { default() )) })}))} + + #[throws(IE)] + fn held_change_hook(&self, + _ig: &InstanceRef, + gplayers: &GPlayers, + ipieces: &IPieces, + goccults: &GOccults, + gpieces: &mut GPieces, + tpiece: PieceId, + was_held: Option) + -> OpHookThunk { + let missing_e = || internal_error_bydebug(&(was_held, tpiece)); + + let tself = self; + let tgpc = gpieces.get(tpiece).ok_or_else(missing_e)?; + let tipc = ipieces.get(tpiece).ok_or_else(missing_e)?; + + if_let!{ Some(player) = was_held; else return Ok(default()) } + if tgpc.held.is_some() { /*wat*/ return default(); } + let gpl = gplayers.get(player); + + // Occultation is not yet supported here. When implementing + // occultation, delete this and fix all the things. + let show = ShowUnocculted::new_visible(); + + let merge_with = gpieces.iter().filter_map(|(mpiece, mgpc)|{ + if mpiece == tpiece { throw!() } + let mipc = ipieces.get(mpiece)?; + + // Our position is within its bbox + if ! mipc.show(show).abs_bbox(mgpc).ok()?.contains(tgpc.pos) { throw!() } + + // It's a banknote + let mself: &Banknote = mipc.p.show(show) + .downcast_piece_fastsplit().ok()?; + + // Of our currency + if mself.currency != tself.currency { throw!() } + let currency = &mself.currency; + + if mgpc.occult.passive_occid().is_some() { + // We don't do occultation yet. But, anyway, we don't want to + // deal with this since it might mean we're totally invisible + // to our player! When we do support this, call + // Occultation::get_kind ? + throw!(); + } + + // We are in the ellipse inscribed in its bbox + let delta = (tgpc.pos - mgpc.pos).ok()?.promote(); + let bbox_sz = mipc.show(show).bbox_approx().ok()?; + let dist2: f64 = (0..2).map(|i| { + // The bbox may not be centred. We imagine a quarter ellipse + // inscribed in each corner, with the centre at the nominal position. + let delta = delta.coords[i]; + let cnr = if delta < 0. { bbox_sz.tl() } else { bbox_sz.br() }; + let rel = delta / (cnr.coords[i] as f64); + rel*rel + }).sum(); + if ! (dist2 <= 1.) { throw!() } + + Some((mpiece,mgpc,currency)) + }); + + if_let!{ + Some((mpiece,mgpc,currency)) = + merge_with.at_most_one().ok().flatten(); + else return Ok(default()); + } + + let tqty = tgpc.xdata_exp::()?.qty; + let mqty = mgpc.xdata_exp::()?.qty; + if_let!{ + Some(new_qty) = mqty.checked_add(tqty); + else return Ok(default()); // arithmetic overflow! + } + + let logents = vec![ LogEntry { html: hformat!( + "{} deposited {} {}{}, giving {}{}", + match gpl { + Some(gpl) => gpl.nick.to_html(), + None => Html::lit("Departing player").into(), + }, + tipc.p.show(show).describe_html(tgpc, goccults)?, + tqty, currency, + new_qty, currency, + )}]; + + OpHookThunk::Reborrow(Box::new(move |igg: &mut InstanceGuard, (_player,)| { + + let (puo, uu_d) = igg.fastsplit_delete(show, tpiece, &logents)?; + // commitment point + Ok(((move ||{ + let ig = &mut **igg; + + let () = (||{ + // None of these situations ought to happen, really, but the + // callback structure means it isn't 100% possible to rule them out. + let mgpc = ig.gs.pieces.get_mut(mpiece).ok_or("tpiece vanished")?; + let mvalue = mgpc.xdata_mut_exp::().map_err(|_|"xdata vanished")?; + mvalue.qty = mvalue.qty.checked_add(tqty).ok_or("overflow")?; + if mvalue.qty != new_qty { throw!("modified value") } + Ok::<_,&'static str>(()) + })().unwrap_or_else(|m|{ + warn!("during dorp-and-merge of currency {tpiece:?} into {mpiece:?}: {m}"); + }); + + vec![Box::new(move |prepub: &mut PrepareUpdatesBuffer| { + prepub.piece_update_image(mpiece, &None).unwrap_or_else( + |e| error!("currency image update failed: {} {:?}", &e, &e)); + prepub.piece_update(tpiece, &None, puo.into()); + prepub.log_updates(logents); + prepub.add_unprepared(uu_d); + }) as _] + + }))()) // <- no ? + }))} } diff --git a/src/fastsplit.rs b/src/fastsplit.rs index 005cf69a..8d1acb42 100644 --- a/src/fastsplit.rs +++ b/src/fastsplit.rs @@ -166,6 +166,44 @@ impl InstanceGuard<'_> { (t_pu, unprepared) })()// <- no ?, infallible (to avoid having not completed implementation } + + #[throws(IE)] + pub fn fastsplit_delete(&mut self, + show: ShowUnocculted, + piece: PieceId, + _proof_that_caller_handles_logging: &Vec) + -> (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)?; + let _p: &Piece = ipc.p.show(show).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 { -- 2.30.2