1 // Copyright 2020-2021 Ian Jackson and contributors to Otter
2 // SPDX-License-Identifier: AGPL-3.0-or-later
3 // There is NO WARRANTY.
7 #[path="vpid.rs"] mod vpid;
8 pub use vpid::{PerPlayerIdMap, NotchNumber, Notch, Notches, consistency_check};
10 use slotmap::secondary;
12 type ONI = OldNewIndex;
14 visible_slotmap_key!{ OccId(b'H') }
16 // ========== data structures ==========
18 #[derive(Copy,Clone,Debug)]
21 /// Proof obligation when constructing.
22 pub struct ShowUnocculted(());
24 #[derive(Copy,Clone,Debug)]
25 pub struct OcculterRotationChecked(());
27 #[derive(Debug,Serialize,Deserialize)]
29 pub struct IPieceTraitObj(Box<dyn PieceTrait>);
31 #[derive(Clone,Debug,Default,Serialize,Deserialize)]
33 occults: DenseSlotMap<OccId, Occultation>,
36 #[derive(Clone,Debug,Default,Serialize,Deserialize)]
37 // kept in synch with Occultation::pieces
38 pub struct PieceOccult {
39 active: Option<OccId>, // kept in synch with Occultation::occulter
40 passive: Option<Passive>, // kept in synch with Occultation::notches
43 #[derive(Clone,Copy,Debug,Serialize,Deserialize,Eq,PartialEq)]
46 // If None, this piece does not participate in mixing (IOI::Distinct)
47 permute_notch: Option<Notch>,
50 #[derive(Clone,Debug,Serialize,Deserialize)]
51 pub struct Occultation {
52 region: Region, // automatically affect pieces here
53 occulter: PieceId, // kept in synch with PieceOccult::active
54 notches: Notches, // kept in synch with PieceOccult::passive
56 unnotched: HashSet<PieceId>, // kept in synch with PieceOccult::passive
57 ppiece_use_size: Pos, // taken from first piece
58 #[serde(flatten)] views: OccultationViews,
61 #[derive(Clone,Debug,Serialize,Deserialize)]
62 pub struct OccultationViews {
63 pub views: Vec<OccultView>,
64 #[serde(default)] pub defview: OccultationKind,
67 #[derive(Clone,Debug,Serialize,Deserialize)]
68 pub struct OccultView {
69 #[serde(default)] pub occult: OccultationKind,
70 pub players: Vec<PlayerId>,
73 #[derive(Clone,Copy,Debug,Serialize,Deserialize)]
74 #[derive(Eq,PartialEq,Hash)]
75 pub enum OccultationKindGeneral<D> {
81 pub type OccultationKind = OccultationKindGeneral<(OccDisplacement,ZCoord)>;
83 #[derive(Clone,Debug)]
84 #[derive(Eq,PartialEq,Hash)]
85 pub enum OccultationKindAlwaysOk {
87 // Scrambled is only allowed as the only view; enforced by our
88 // OccultationViewDef trait impls
89 Displaced((OccDisplacement,ZCoord)),
92 impl From<OccultationKindAlwaysOk> for OccultationKind {
93 fn from(i: OccultationKindAlwaysOk) -> OccultationKind {
95 OccKA::Visible => OccKG::Visible,
96 OccKA::Displaced(d) => OccKG::Displaced(d),
97 OccKA::Invisible => OccKG::Invisible,
102 #[derive(Clone,Debug,Serialize,Deserialize)]
103 #[derive(Eq,PartialEq,Hash)]
104 pub enum OccDisplacement {
114 pub fn is_active(&self) -> bool { self.active.is_some() }
117 fn active_occ<'r>(&'r self, goccults: &'r GOccults)
118 -> Option<&'r Occultation> {
119 if let Some(occid) = self.active {
120 let occ = goccults.occults.get(occid).ok_or_else(
121 || internal_error_bydebug(&self))?;
129 pub fn active_views<'r>(&'r self, goccults: &'r GOccults)
130 -> Option<&'r OccultationViews> {
131 self.active_occ(goccults)?.map(
137 pub fn active_region<'r>(&'r self, goccults: &'r GOccults)
138 -> Option<&'r Region> {
139 self.active_occ(goccults)?.map(
145 pub fn active_total_ppieces(&self, goccults: &GOccults)
147 self.active_occ(goccults)?.map(|occ| {
148 let notches_len = usize::try_from(occ.notches.len()).unwrap();
149 notches_len + occ.unnotched.len()
153 pub fn passive_occid(&self) -> Option<OccId> { Some(self.passive?.occid) }
154 pub fn passive_delete_hook(&self, goccults: &mut GOccults,
157 if let Some(Passive { occid, permute_notch }) = self.passive;
158 if let Some(occ) = goccults.occults.get_mut(occid);
160 if let Some(notch) = permute_notch {
161 occ.notches.remove(piece, notch)
162 .unwrap_or_else(|e| error!("removing occulted piece {:?}", e));
164 occ.unnotched.remove(&piece);
171 impl Default for OccultationKind {
172 fn default() -> Self { OccK::Visible }
175 impl Ord for OccultationKind {
176 fn cmp(&self, rhs: &Self) -> Ordering {
177 fn level(k: &OccK) -> u8 { use OccKG::*; match k {
183 level(self).cmp(&level(rhs))
186 impl PartialOrd for OccultationKind {
187 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
192 impl OccultationKind {
193 fn at_all_visible(&self) -> bool {
197 OccK::Displaced { .. }
205 impl<P,Z> OccultationKindGeneral<(P, Z)> {
206 pub fn pri_occulted(self) -> Option<PriOccultedGeneral<P,Z>> {
208 OccKG::Invisible => return None,
209 OccKG::Visible => PriOG::Visible(ShowUnocculted(())),
210 OccKG::Scrambled => PriOG::Occulted,
211 OccKG::Displaced((pos,z)) => PriOG::Displaced(pos, z),
217 impl<T> OccultationKindGeneral<T> {
218 fn map_displaced<U,F>(&self, f: F) -> OccultationKindGeneral<U>
219 where F: FnOnce(&T) -> U,
224 Scrambled => Scrambled,
225 Invisible => Invisible,
226 Displaced(t) => Displaced(f(t)),
231 impl OccultationViews {
232 pub fn get_kind(&self, player: PlayerId) -> &OccultationKind {
233 let kind = self.views.iter().find_map(|view| {
234 if view.players.contains(&player) { return Some(&view.occult); }
244 pub fn get_kind(&self, player: PlayerId) -> &OccultationKind {
245 self.views.get_kind(player)
248 pub fn in_region(&self, pos: Pos) -> bool {
249 self.region.contains(pos)
252 pub fn pieces(&self) -> impl Iterator<Item=PieceId> + '_ {
255 self.unnotched.iter().cloned(),
262 fn by_id(&self, occid: OccId) -> &Occultation {
263 self.occults.get(occid).ok_or_else(
264 || internal_logic_error("piece missing"))?
268 pub fn get_kind(&self, occid: OccId, player: PlayerId) -> &OccultationKind {
269 let occ = self.by_id(occid)?;
270 let kind = occ.get_kind(player);
275 pub fn pos_occulter(&self, goccults: &GOccults, pos: Pos)
277 goccults.occults.iter().find_map(|(_occid, occ)| {
278 if occ.in_region(pos) {
286 pub fn is_empty(&self) -> bool {
287 let GOccults { occults } = self;
292 // ========== public entrypoints ==========
294 /// None => do not render at all
296 _ioccults: &IOccults,
298 player: PlayerId, gpl: &mut GPlayer,
299 piece: PieceId, gpc: &GPiece, _ipc: &IPiece,
300 ) -> Option<PieceRenderInstructions>
302 let mut occk_dbg = None;
303 let occulted = if_chain! {
304 if let Some(Passive { occid, permute_notch }) = gpc.occult.passive;
305 if let Some(occ) = occults.occults.get(occid);
307 if let Some(notch) = permute_notch;
308 if let Some(zg) = occ.notch_zg(notch);
310 let occk = occ.views.get_kind(player)
311 .map_displaced(|(displace, z)| {
312 let notch: NotchNumber = notch.into();
313 let pos = displace.place(occ.ppiece_use_size, notch);
314 let z = z.plus_offset(notch)
315 .unwrap_or_else(|e| { // eek!
316 error!("z coordinate overflow ({:?}), bodging! {:?} {:?}",
320 (pos, ZLevel { z, zg })
323 occk_dbg = Some(occk.clone());
324 match occk.pri_occulted() {
327 trace_dbg!("piece_pri", player, piece, occk_dbg, gpc);
332 else { // No notch, not mixing (so not displacing)
333 match occ.views.get_kind(player) {
334 OccKG::Invisible => {
335 trace_dbg!("piece_pri", player, piece, gpc);
338 OccKG::Visible => PriOG::Visible(ShowUnocculted(())),
339 OccKG::Scrambled => PriOG::Occulted,
340 OccKG::Displaced(_) => PriOG::Occulted,
345 PriOG::Visible(ShowUnocculted(()))
349 let vpid = gpl.idmap.fwd_or_insert(piece);
350 trace_dbg!("piece_pri", player, piece, occk_dbg, vpid, occulted, gpc);
351 Some(PieceRenderInstructions { vpid, occulted })
354 impl OccDisplacement {
355 fn place(&self, ppiece_use_size: Pos, notch: NotchNumber) -> Pos {
356 use OccDisplacement as OD;
358 OD::Stack{pos} => *pos,
359 OD::Rect{rect} => (|| Some({
360 let notch: Coord = notch.try_into().ok()?;
361 let mut spare = ((rect.br() - rect.tl()).ok()?
362 - ppiece_use_size).ok()?;
363 for s in &mut spare.coords { *s = max(*s,1) }
366 let f_stride = max(ppiece_use_size.coords[fi] / 4, 1);
367 let g_stride = max(ppiece_use_size.coords[gi] / 3, 1);
368 let f_count = max(spare.coords[fi] / f_stride, 1);
369 let g_count = max(spare.coords[gi] / g_stride, 1);
370 let mut f_num = notch % f_count;
371 let g_num = notch / f_count;
372 if g_num % 2 != 0 { f_num = f_count - 1 - f_num }
373 let f_coord = rect.br().coords[fi] - ppiece_use_size.coords[fi] / 2 -
375 let g_coord = rect.tl().coords[gi] + ppiece_use_size.coords[gi] / 2 +
378 } else if g_num < spare.coords[gi] {
383 trace_dbg!("placement", spare,
384 f_stride, f_count, f_num, f_coord,
385 g_stride, g_count, g_num, g_coord);
386 let mut pos = PosC::zero();
387 pos.coords[fi] = f_coord;
388 pos.coords[gi] = g_coord;
390 }))().unwrap_or_else(||{
397 impl ShowUnocculted {
398 /// Override. Proof obligation: this context does not require
399 /// honouring occultation.
400 pub const fn new_visible() -> ShowUnocculted {
405 impl PieceRenderInstructions {
406 /// Override. Proof obligation: this context does not require
407 /// honouring occultation.
408 pub fn new_visible(vpid: VisiblePieceId) -> PieceRenderInstructions {
409 PieceRenderInstructions {
411 occulted: PriOcculted::Visible(ShowUnocculted(())),
416 impl IPieceTraitObj {
417 pub fn new(p: Box<dyn PieceTrait>) -> Self { Self(p) }
419 pub fn show(&self, _: ShowUnocculted) -> &dyn PieceTrait {
423 pub fn into_inner(self) -> Box<dyn PieceTrait> { self.0 }
425 pub fn direct_trait_access(&self) -> &dyn PieceTrait {
432 pub fn show_or_instead<'p>(&'p self, ioccults: &'p IOccults,
433 y: Option<ShowUnocculted>)
434 -> Either<ShowUnocculted, /*occulted*/ &'p dyn InertPieceTrait> {
438 if_let!{ Some(occilk) = self.occilk.as_ref();
439 else return Ok(Left(ShowUnocculted::new_visible())); }
440 let occ_data = ioccults.ilks.from_iilk(occilk)
441 .ok_or_else(|| internal_logic_error(format!(
442 "occulted ilk vanished {:?} {:?}", self, occilk)))?;
443 occ_data.p_occ.as_ref()
450 pub fn fully_visible_to_everyone(&self) -> Option<ShowUnocculted> {
451 match self.occult.passive {
453 None => Some(ShowUnocculted(())),
457 pub fn occulter_check_unrotated(&self, _:ShowUnocculted)
458 -> Result<OcculterRotationChecked, Inapplicable> {
459 if self.angle.is_rotated() { Err(Ia::OcculterAlreadyRotated) }
460 else { Ok(OcculterRotationChecked(())) }
464 pub fn fully_visible_to(&self, goccults: &GOccults, player: PlayerId)
465 -> Option<ShowUnocculted>
467 const HIDE: Option<ShowUnocculted> = None;
468 const SHOW: Option<ShowUnocculted> = Some(ShowUnocculted(()));
469 if_let!{ Some(passive) = &self.occult.passive; else return SHOW };
470 want_let!{ Some(occ) = goccults.occults.get(passive.occid);
471 else ?passive.occid; return HIDE };
472 return match occ.views.get_kind(player) {
473 OccK::Visible => SHOW,
476 OccK::Invisible => HIDE,
480 pub fn involved_in_occultation(&self) -> bool {
481 self.occult.passive.is_some() ||
482 self.occult.active.is_some()
486 pub fn forbid_involved_in_occultation(&self) {
487 if self.involved_in_occultation() { throw!(Ia::Occultation) }
491 pub fn vpiece_decode(
496 ) -> Option<PieceId> {
497 let piece: Option<PieceId> = gpl.idmap.rev(vis);
498 let piece: Option<PieceId> = if_chain! {
499 if let Some(p) = piece;
500 if let Some(gpc) = gs.pieces.get(p);
501 if let Some(Passive { occid, permute_notch:_ }) = gpc.occult.passive;
502 if let Some(occ) = gs.occults.occults.get(occid);
503 let kind = occ.views.get_kind(player);
504 if ! kind.at_all_visible();
508 trace!("{} {:?} <= {}", player, piece, vis);
512 #[throws(InternalError)]
513 pub fn recalculate_occultation_general<
514 RD: Debug, // return data
515 LD: Debug, // log data
516 VF: FnOnce() -> RD, // ret_vanilla
517 LF: FnOnce(Option<Html>, Option<Html>, &Html) -> LD, // log_callback
518 RF: FnOnce(PieceUpdateOps_PerPlayer, LD) -> RD, // ret_callback
520 gen: &mut UniqueGenGen,
522 gplayers: &GPlayers, gpieces: &mut GPieces,
523 goccults: &mut GOccults, ipieces: &IPieces, ioccults: &IOccults,
525 to_recalculate: &mut ToRecalculate, piece: PieceId,
526 // if no change, we return ret_vanilla()
528 // otherwise we use log_invisible or log_callback(who_by,old,new,desc)
531 // and then call ret_callback(<calculated>, <logmsgs>)
536 #[derive(Debug,Copy,Clone)]
537 struct OldNewOcculteds<O> {
538 old: Option<(O, Option<Notch>)>,
541 impl<O> OldNewOcculteds<O> {
542 fn main(&self) -> OldNew<Option<O>> where O:Copy {
543 [ self.old.map(|(o,_n)| o), self.new ].into()
545 fn map<P, F:FnMut(O) -> P>(self, mut f: F) -> OldNewOcculteds<P> {
547 old: self.old.map(|(o,n)| (f(o), n)),
548 new: self.new.map(f),
551 fn as_refs(&self) -> OldNewOcculteds<&O> {
553 old: self.old.as_ref().map(|(o,n)| (o, *n)),
554 new: self.new.as_ref()
559 let nopiece = || internal_logic_error("piece vanished");
560 let ipc = ipieces.get(piece).ok_or_else(nopiece)?;
563 let (puos, log, occulteds): (_, _, OldNewOcculteds<OccId>) = {
564 let gpc = gpieces.get(piece).ok_or_else(nopiece)?;
566 #[derive(Debug,Copy,Clone)]
567 struct Occulted<'o> { occid: OccId, occ: &'o Occultation }
569 let occulteds = OldNewOcculteds {
571 gpc.occult.passive.map(|Passive { occid, permute_notch }| Ok::<_,IE>((
574 occ: goccults.occults.get(occid).ok_or_else(
575 || internal_logic_error("uccultation vanished"))?,
581 goccults.occults.iter().find_map(|(occid, occ)| {
583 // Prevent pinned pieces being occulted. What scrambling
584 // them etc. would mean is not entirely clear.
586 } else if gpc.occult.active.is_some() {
587 // prevent occulting pieces being occulted
588 // (also prevents reflexive occultation)
590 } else if occ.in_region(gpc.pos) {
591 Some(Occulted { occid, occ })
597 trace_dbg!("recalculating", piece, occulteds);
599 let occids = occulteds.main().map(|h| h.as_ref().map(|occ| occ.occid));
600 if occids.old() == occids.new() { return ret_vanilla(); }
604 fn get_kind(gs: &GameState, occid: Option<OccultationId>, player: PlayerId)
605 -> Option<(OccultationId, OccultationKind)> {
608 let mut situations: HashMap<
609 OldNew<&OccultationKind>,
612 for (player, _gpl) in gplayers {
615 occulteds.main().map(|occulted| {
616 if let Some(occulted) = occulted {
617 occulted.occ.get_kind(player)
626 trace_dbg!("situations", &situations);
628 let mut puos = SecondarySlotMap::new();
629 let mut most_obscure = None;
631 for (kinds, players) in &situations {
632 // For each player, the message obscuration is the least obscure.
633 // Then the overall obscuration is that from most afflicted player.
634 most_obscure = cmp::max(
636 kinds.iter().map(Deref::deref).min()
640 kinds.old().at_all_visible(),
641 kinds.new().at_all_visible(),
643 (false, false) => None,
644 (false, true ) => Some(PUO::InsertQuiet(())),
645 (true, false) => Some(PUO::Delete()),
646 (true, true ) => Some(PUO::Modify(())),
648 trace_dbg!("situation", &kinds, &players, &most_obscure, &puo);
650 if let Some(puo) = puo {
651 for player in players {
652 puos.insert(*player, puo);
656 // this calculation does not seem to work?
657 // to repro: bob moves a card out of bob's hand
658 // alice sees "a card with a red-striped back"
659 trace_dbg!("most_obscure", most_obscure);
661 let describe_occulter = |oni| Ok::<_,IE>(if_chain! {
662 if let Some(h) = occulteds.as_refs().main()[oni];
663 let opiece = h.occ.occulter;
664 let bad = || internal_error_bydebug(&("missing", opiece, h.occid));
665 let oipc = ipieces.get(opiece).ok_or_else(bad)?;
666 let ogpc = gpieces.get(opiece).ok_or_else(bad)?;
667 let ounocc = ogpc.fully_visible_to_everyone()
668 .ok_or_else(||internal_error_bydebug(&(occulteds, &ogpc)))?;
670 Some(oipc.show(ounocc).describe_html(ogpc, goccults)?)
676 let most_obscure = most_obscure.unwrap_or(&OccK::Visible); // no players!
678 let call_log_callback =
680 log_callback(describe_occulter(ONI::Old)?,
681 describe_occulter(ONI::New)?,
685 let log = match most_obscure.map_displaced(|_|((),())).pri_occulted() {
686 Some(prioc@ PriOG::Visible(_)) |
687 Some(prioc@ PriOG::Occulted) |
688 Some(prioc@ PriOG::Displaced(..)) => {
689 let show = prioc.describe(ioccults, goccults, gpc, ipc);
690 call_log_callback(&show)?
697 (puos, log, occulteds.map(|h| h.occid))
701 trace_dbg!("committing", &puos, &log, &occulteds);
705 &mut dyn for<'g> FnMut(&'g mut GOccults, OccId) -> &mut Occultation
706 = &mut |goccults, occid|
707 // rust-lang/rust/issues/58525
709 to_recalculate.mark_dirty(occid);
710 goccults.occults.get_mut(occid).unwrap()
712 if let Some((occid, old_notch)) = occulteds.old {
713 let occ = occultation(goccults, occid);
714 if let Some(old_notch) = old_notch {
717 .remove(piece, old_notch)
725 let passive = if_chain!{
726 if let Some(occid) = occulteds.new;
728 let occ = occultation(goccults, occid);
729 let ilk = ipc.occilk.as_ref();
731 let permute_notch = match ilk {
732 Some(IOI::Distinct(_)) | None => {
733 occ.unnotched.insert(piece);
736 Some(IOI::Mix(ilk)) => {
738 if occ.notches.is_empty();
739 if let Some(ilk) = wants!( ioccults.ilks.get(ilk) );
740 if let Some(bbox) = want!( Ok = ilk.p_occ.bbox_approx() );
741 if let Some(size) = want!( Ok = bbox.br() - bbox.tl(), ?(bbox) );
742 then { occ.ppiece_use_size = size; }
745 let notch = occ.notches.insert(zg, piece);
749 Some(Passive { occid, permute_notch })
755 gpieces.byid_mut(piece).unwrap().occult.passive = passive;
756 })(); // <- no ?, infallible commitment
758 ret_callback(puos, log)
761 #[throws(InternalError)]
762 pub fn recalculate_occultation_piece(
767 to_recalculate: &mut ToRecalculate,
769 (vanilla_wrc, vanilla_op, vanilla_log): PUFOS,
773 recalculate_occultation_general(
774 &mut gs.gen.unique_gen(),
775 &gs.players, &mut gs.pieces, &mut gs.occults, ipieces, ioccults,
776 to_recalculate, piece,
777 || (vanilla_wrc, vanilla_op, vanilla_log).into(),
779 |old, new, show| vec![ LogEntry { html: hformat!(
783 (None, None) => hformat!("modified {} somehow", show),
784 (Some(old), None) => hformat!("produced {} from {}", show, old),
785 (None, Some(new)) => hformat!("placed {} into {}", show, new),
786 (Some(old), Some(new)) => hformat!("moved {} from {} to {}",
790 |puos, log| PieceUpdate {
791 wrc: WRC::Unpredictable,
792 ops: PieceUpdateOps::PerPlayer(puos),
799 fn recalculate_occultation_ofmany(
800 gen: &mut UniqueGenGen,
802 gpieces: &mut GPieces,
803 goccults: &mut GOccults,
806 to_recalculate: &mut ToRecalculate,
808 updates: &mut Vec<(PieceId, PieceUpdateOps)>,
810 recalculate_occultation_general(
812 gplayers, gpieces, goccults, ipieces, ioccults,
813 to_recalculate, ppiece,
817 updates.push((ppiece, PUOs::PerPlayer(puo_pp)));
827 pub struct ToRecalculate {
828 outdated: HashSet<OccId>,
831 pub struct Implemented(UnpreparedUpdates);
832 impl Debug for Implemented {
833 #[throws(fmt::Error)]
834 fn fmt(&self, f: &mut Formatter) {
835 write!(f, "Implemented({})", self.0.len())?;
840 /// F returning `Implemented` proves that it called `implement`
841 pub fn with<R, F>(f: F) -> (R, UnpreparedUpdates)
842 where F: FnOnce(Self) -> (R, Implemented)
844 let to_recalculate = ToRecalculate { outdated: default() };
845 let (r, Implemented(uu)) = f(to_recalculate);
848 pub fn mark_dirty(&mut self, occid: OccId) { self.outdated.insert(occid); }
850 pub fn implement_auth<A>(self, igu: &mut Unauthorised<InstanceGuard,A>)
852 // Caller must have had some kind of authorisation to do whatever
853 // it was they did. It doesn't amke sense to demand any more.
854 self.implement(igu.by_mut(Authorisation::promise_any()))
856 pub fn implement(self, ig: &mut Instance) -> Implemented {
857 self.implement_inner(&mut ig.gs.players,
862 fn implement_inner(self,
863 gplayers: &mut GPlayers,
864 gpieces: &mut GPieces,
865 goccults: &mut GOccults,
866 ipieces: &IPieces) -> Implemented {
867 let mut unprepared = vec![];
869 for occid in self.outdated {
870 if let Some(occ) = goccults.occults.get_mut(occid) {
871 vpid::permute(occid, occ, gplayers, gpieces, ipieces);
872 if let Some(ipc) = ipieces.get(occ.occulter) {
874 .direct_trait_access()
875 .occultation_notify_hook(occ.occulter);
876 unprepared.extend(uu);
881 consistency_check(gplayers, gpieces, goccults);
883 Implemented(unprepared)
888 pub use recompute::ToRecalculate;
891 pub struct NascentOccultation(Occultation);
893 #[derive(Debug,Clone)]
894 pub struct UniformOccultationView(
897 #[derive(Debug,Clone)]
898 pub struct OwnerOccultationView {
899 pub defview: OccultationKindAlwaysOk,
901 pub owner_view: OccultationKindAlwaysOk,
904 pub trait OccultationViewDef {
905 fn views(self) -> Result<OccultationViews, IE>;
907 impl OccultationViewDef for UniformOccultationView {
909 fn views(self) -> OccultationViews { OccultationViews {
914 impl OccultationViewDef for OwnerOccultationView {
916 fn views(self) -> OccultationViews { OccultationViews {
917 defview: self.defview.into(),
918 views: vec![OccultView {
919 players: vec![self.owner],
920 occult: self.owner_view.into(),
926 pub fn create_occultation(
927 gen: &mut UniqueGenGen,
929 gplayers: &mut GPlayers,
930 gpieces: &mut GPieces,
931 goccults: &mut GOccults,
934 to_recalculate: &mut ToRecalculate,
935 _: OcculterRotationChecked,
938 views: OccultationViews,
939 // Caller must promise not to fail if we succeed, so that this
940 // update actually happens!
941 _puos_will_return: &PUOs_Simple_Modify,
942 ) -> Vec<(PieceId, PieceUpdateOps)> {
943 // We mustn't actually store this in gpieces until we commit.
944 let ogpc_z_new = piece_make_heavy(gpieces, occulter)?;
947 let ogpc = gpieces.get(occulter).ok_or_else(
948 ||internal_logic_error("create occultation with non-piece"))?;
949 if ogpc.occult.active.is_some() {
950 throw!(internal_logic_error("re-occulting!"))
953 if let Some(displ_z) = {
955 .iter().map(|ov| &ov.occult)
956 .chain(iter::once(&views.defview))
957 .filter_map(|ok| { use OccKG::*; match ok {
958 Visible | Scrambled | Invisible => None,
959 Displaced((_region, ref z)) => Some(z)
963 // We expect that ogpc.zlevel.z.increment() is shorter than
964 // the displ_z, but in case it isn't, we must look at both.
965 let max_z = &mut max_z.z;
967 max_z.update_max(&ogpc_z_new.clone_mut().increment()?);
968 max_z.update_max(&displ_z.plus_offset(! 0)?);
974 for occ in goccults.occults.values() {
975 if occ.region.overlaps(®ion) { throw!(Ia::OverlappingOccultation) }
978 let mut recalc = vec![];
979 for (ppiece, pgpc) in gpieces.iter() {
980 if ! region.contains(pgpc.pos) { continue }
981 if pgpc.occult.passive.is_some() { throw!(internal_logic_error(
982 format!("piece {:?} in region, no occulters, but occulted", &pgpc)
987 let occultation = Occultation {
991 ppiece_use_size: PosC::zero(),
993 unnotched: default(),
995 debug!("creating occultation {:?}", &occultation);
996 trace_dbg!("recalc", &recalc);
998 // Everything from here on must be undone if we get an error
999 // but we hope not to get one...
1001 let occid = goccults.occults.insert(occultation);
1002 let mut updates = vec![];
1006 let ogpc = gpieces.get_mut(occulter).ok_or_else(
1007 ||internal_logic_error("occulter vanished"))?;
1008 ogpc.occult.active = Some(occid);
1009 ogpc.zlevel.z = ogpc_z_new;
1011 for &ppiece in &recalc {
1012 recalculate_occultation_ofmany(gen,
1013 gplayers, gpieces, goccults,
1016 ppiece, &mut updates)?;
1021 for &ppiece in &recalc {
1022 let pgpc = gpieces.get_mut(ppiece).expect("had ppiece earlier");
1023 pgpc.occult.passive = None;
1025 let ogpc = gpieces.get_mut(occulter).expect("had occulter earlier");
1026 ogpc.occult.active = None;
1027 goccults.occults.remove(occid).expect("inserted this earlier");
1032 trace_dbg!("created", &updates);
1037 pub fn remove_occultation(
1038 gen: &mut UniqueGenGen,
1039 gplayers: &mut GPlayers,
1040 gpieces: &mut GPieces,
1041 goccults: &mut GOccults,
1043 ioccults: &IOccults,
1044 to_recalculate: &mut ToRecalculate,
1046 ) -> Vec<(PieceId, PieceUpdateOps)> {
1047 let mut aggerr = AggregatedIE::new();
1049 let occid = if_chain! {
1050 if let Some(ogpc) = gpieces.get(occulter);
1051 // This can be None if the occulter is being deleted
1053 if let Some(occid) = ogpc.occult.active.or_else(||{
1054 aggerr.record(internal_logic_error(
1055 "removing occultation by non-active piece"));
1060 else { aggerr.ok()?; panic!(); }
1063 let mut updates = vec![];
1064 let mut unbreak_pieces: Vec<PieceId> = vec![];
1067 let occ = goccults.occults.get_mut(occid).ok_or_else(
1068 || internal_logic_error("removing nonexistent occultation"))?;
1069 debug!("removing occultation {:?}", &occ);
1071 // We have to recalculate with the occultation still active, so
1072 // that the affected pieces can know what the old situation was.
1073 // So we set the region to empty, and do a recalculation of the
1074 // relevant pieces. Only then can we get rid of the occultation.
1075 occ.region = Region::empty();
1077 let pieces: Vec<_> = occ.pieces().collect();
1079 for &ppiece in pieces.iter() {
1080 recalculate_occultation_ofmany(gen,
1081 gplayers, gpieces, goccults,
1084 ppiece, &mut updates)
1085 .unwrap_or_else(|e| {
1087 if let Some(pgpc) = gpieces.get_mut(ppiece) {
1088 pgpc.occult.passive = None;
1093 // now there should be nothing
1094 let occ = goccults.occults.remove(occid).ok_or_else(
1095 || internal_logic_error("occultation vanished in recalc!"))?;
1097 unbreak_pieces.extend(occ.pieces());
1103 unbreak_pieces.extend(gpieces.keys());
1108 if ! unbreak_pieces.is_empty() {
1109 aggerr.record(internal_logic_error(format!(
1110 "occultation remove left pieces: {:?}", &unbreak_pieces)));
1112 for ppiece in unbreak_pieces { if_chain! {
1113 if let Some(pgpc) = gpieces.get_mut(ppiece);
1114 if let Some(passive) = pgpc.occult.passive;
1115 if passive.occid == occid;
1117 pgpc.occult.passive = None;
1118 updates.push((ppiece, PieceUpdateOp::Modify(()).into()));
1123 if let Some(ogpc) = gpieces.get_mut(occulter) {
1124 ogpc.occult.active = None;
1126 aggerr.record(internal_logic_error("removing occultation of non-piece"));