1use alloc::boxed::Box;
2use alloc::string::ToString;
3use alloc::vec::Vec;
4use core::any::Any;
5use core::fmt;
6
7use hashbrown::HashMap as HbHashMap;
8
9use crate::behavior;
10use crate::transaction::{self, CommitError, Equal, Merge, Transaction, Transactional};
11#[cfg(doc)]
12use crate::universe::HandleError;
13use crate::universe::{
14 AnyHandle, ErasedHandle, Handle, InsertError, InsertErrorKind, Name, UBorrowMut, Universe,
15 UniverseId, UniverseMember, UniverseTable,
16};
17
18#[doc(inline)]
20pub(in crate::universe) use crate::universe::members::{
21 AnyTransaction, AnyTransactionConflict, AnyTransactionMismatch,
22};
23
24pub trait UTransactional: Transactional + 'static
30where
31 Self: Sized,
32{
33 fn bind(target: Handle<Self>, transaction: Self::Transaction) -> UniverseTransaction;
38}
39
40#[derive(Debug, Eq)]
46pub(in crate::universe) struct TransactionInUniverse<O: Transactional + 'static> {
47 pub(crate) target: Handle<O>,
48 pub(crate) transaction: O::Transaction,
49}
50
51impl<O> Transaction for TransactionInUniverse<O>
52where
53 O: Transactional + 'static,
54{
55 type Target = ();
56 type CommitCheck = TransactionInUniverseCheck<O>;
57 type Output = <O::Transaction as Transaction>::Output;
58 type Mismatch = <O::Transaction as Transaction>::Mismatch;
59
60 fn check(&self, (): &()) -> Result<Self::CommitCheck, Self::Mismatch> {
61 let guard = self
62 .target
63 .try_borrow_mut()
64 .expect("Attempted to execute transaction with target already borrowed");
66 let check = self.transaction.check(&guard)?;
67 Ok(TransactionInUniverseCheck { guard, check })
68 }
69
70 fn commit(
71 &self,
72 _dummy_target: &mut (),
73 mut tu_check: Self::CommitCheck,
74 outputs: &mut dyn FnMut(Self::Output),
75 ) -> Result<(), CommitError> {
76 self.transaction
77 .commit(&mut tu_check.guard, tu_check.check, outputs)
78 }
79}
80
81pub(in crate::universe) struct TransactionInUniverseCheck<O>
83where
84 O: Transactional + 'static,
85{
86 guard: UBorrowMut<O>,
87 check: <O::Transaction as Transaction>::CommitCheck,
88}
89
90impl<O> Merge for TransactionInUniverse<O>
91where
92 O: Transactional + 'static,
93{
94 type MergeCheck = <O::Transaction as Merge>::MergeCheck;
95 type Conflict = <O::Transaction as Merge>::Conflict;
96
97 fn check_merge(&self, other: &Self) -> Result<Self::MergeCheck, Self::Conflict> {
98 if self.target != other.target {
99 panic!("TransactionInUniverse cannot have multiple targets; use UniverseTransaction instead");
101 }
102 self.transaction.check_merge(&other.transaction)
103 }
104
105 fn commit_merge(&mut self, other: Self, check: Self::MergeCheck) {
106 self.transaction.commit_merge(other.transaction, check);
107 }
108}
109
110impl<O> Clone for TransactionInUniverse<O>
112where
113 O: Transactional<Transaction: Clone>,
114{
115 fn clone(&self) -> Self {
116 Self {
117 target: self.target.clone(),
118 transaction: self.transaction.clone(),
119 }
120 }
121}
122impl<O> PartialEq for TransactionInUniverse<O>
124where
125 O: Transactional<Transaction: PartialEq>,
126{
127 fn eq(&self, other: &Self) -> bool {
128 self.target == other.target && self.transaction == other.transaction
129 }
130}
131
132pub(in crate::universe) type AnyTransactionCheck = Box<dyn Any>;
135
136impl AnyTransaction {
137 fn target_name(&self) -> Option<Name> {
138 self.target_erased().map(ErasedHandle::name)
139 }
140
141 fn universe_id(&self) -> Option<UniverseId> {
142 self.target_erased().and_then(ErasedHandle::universe_id)
143 }
144}
145
146impl fmt::Debug for AnyTransaction {
148 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
149 fmt::Debug::fmt(self.transaction_as_debug(), fmt)
150 }
151}
152
153pub(in crate::universe) fn anytxn_merge_helper<O>(
155 t1: &mut TransactionInUniverse<O>,
156 t2: TransactionInUniverse<O>,
157 check: AnyTransactionCheck, ) where
159 O: Transactional,
160 TransactionInUniverse<O>: Transaction,
161{
162 t1.commit_merge(t2, *check.downcast().unwrap())
163}
164
165pub(in crate::universe) fn anytxn_commit_helper<O>(
167 transaction: &TransactionInUniverse<O>,
168 check: AnyTransactionCheck,
169 outputs: &mut dyn FnMut(<TransactionInUniverse<O> as Transaction>::Output),
170) -> Result<(), CommitError>
171where
172 O: Transactional,
173 TransactionInUniverse<O>: Transaction<Target = ()>,
174{
175 let check: <TransactionInUniverse<O> as Transaction>::CommitCheck =
176 *(check.downcast().map_err(|_| {
177 CommitError::message::<AnyTransaction>("type mismatch in check data".into())
178 })?);
179 transaction.commit(&mut (), check, outputs)
180}
181
182#[derive(Clone, Default, PartialEq)]
188#[must_use]
189pub struct UniverseTransaction {
190 members: HbHashMap<Name, MemberTxn>,
193
194 anonymous_insertions: Vec<MemberTxn>,
203
204 behaviors: behavior::BehaviorSetTransaction<Universe>,
205
206 universe_id: Equal<UniverseId>,
208}
209
210#[doc(hidden)] #[derive(Debug)]
213pub struct UniverseMergeCheck {
214 members: HbHashMap<Name, MemberMergeCheck>,
215 behaviors: behavior::MergeCheck,
216}
217#[doc(hidden)] #[derive(Debug)]
219pub struct UniverseCommitCheck {
220 members: HbHashMap<Name, MemberCommitCheck>,
221 anonymous_insertions: Vec<MemberCommitCheck>,
222 behaviors: behavior::CommitCheck,
223}
224
225#[derive(Clone, Debug, PartialEq)]
227#[non_exhaustive]
228pub enum UniverseMismatch {
229 DifferentUniverse {
231 transaction: UniverseId,
233 target: UniverseId,
235 },
236
237 Member(transaction::MapMismatch<Name, MemberMismatch>),
239
240 InvalidPending,
242
243 Behaviors(<behavior::BehaviorSetTransaction<Universe> as Transaction>::Mismatch),
245}
246
247#[derive(Clone, Debug, PartialEq)]
249#[non_exhaustive]
250pub enum UniverseConflict {
251 DifferentUniverse(UniverseId, UniverseId),
253
254 Member(transaction::MapConflict<Name, MemberConflict>),
256
257 Behaviors(behavior::BehaviorTransactionConflict),
259}
260
261impl core::error::Error for UniverseMismatch {
262 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
263 match self {
264 UniverseMismatch::DifferentUniverse { .. } => None,
265 UniverseMismatch::Member(mc) => Some(&mc.mismatch),
266 UniverseMismatch::Behaviors(c) => Some(c),
267 UniverseMismatch::InvalidPending => None,
268 }
269 }
270}
271impl core::error::Error for UniverseConflict {
272 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
273 match self {
274 UniverseConflict::DifferentUniverse(_, _) => None,
275 UniverseConflict::Member(mc) => Some(&mc.conflict),
276 UniverseConflict::Behaviors(c) => Some(c),
277 }
278 }
279}
280
281impl fmt::Display for UniverseMismatch {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self {
284 UniverseMismatch::DifferentUniverse { .. } => {
285 write!(
286 f,
287 "cannot commit a transaction to a different universe \
288 than it was constructed for"
289 )
290 }
291 UniverseMismatch::Member(c) => {
292 write!(
294 f,
295 "transaction precondition not met in member {key}",
296 key = c.key
297 )
298 }
299 UniverseMismatch::InvalidPending => {
300 write!(
301 f,
302 "universe transactions may not modify handles that \
303 are in the [`Name::Pending`] state"
304 )
305 }
306 UniverseMismatch::Behaviors(_) => {
307 write!(f, "transaction precondition not met in behaviors")
309 }
310 }
311 }
312}
313
314impl fmt::Display for UniverseConflict {
315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
316 match self {
317 UniverseConflict::DifferentUniverse(_, _) => {
318 write!(f, "cannot merge transactions From different universes")
319 }
320 UniverseConflict::Member(c) => {
321 write!(f, "transaction conflict at member {key}", key = c.key)
322 }
323 UniverseConflict::Behaviors(_) => write!(f, "conflict in behaviors"),
324 }
325 }
326}
327
328impl From<transaction::MapConflict<Name, MemberConflict>> for UniverseConflict {
329 fn from(value: transaction::MapConflict<Name, MemberConflict>) -> Self {
330 Self::Member(value)
331 }
332}
333
334impl Transactional for Universe {
335 type Transaction = UniverseTransaction;
336}
337
338impl UniverseTransaction {
339 fn from_member_txn(name: Name, transaction: MemberTxn) -> Self {
343 UniverseTransaction {
344 universe_id: Equal(transaction.universe_id()),
345 members: HbHashMap::from([(name, transaction)]),
346 anonymous_insertions: Vec::new(),
347 behaviors: Default::default(),
348 }
349 }
350
351 pub fn insert<T: UniverseMember>(handle: Handle<T>) -> Self {
356 match handle.name() {
358 Name::Specific(_) | Name::Anonym(_) => Self::from_member_txn(
359 handle.name(),
360 MemberTxn::Insert(UniverseMember::into_any_handle(handle)),
361 ),
362 Name::Pending => Self {
363 members: HbHashMap::new(),
364 anonymous_insertions: vec![MemberTxn::Insert(UniverseMember::into_any_handle(
365 handle,
366 ))],
367 universe_id: Equal(None),
368 behaviors: Default::default(),
369 },
370 }
371 }
372
373 pub fn insert_mut<T: UniverseMember>(&mut self, handle: Handle<T>) -> Result<(), InsertError> {
384 let name = handle.name();
385 let insertion = MemberTxn::Insert(UniverseMember::into_any_handle(handle));
386
387 match name {
389 name @ (Name::Specific(_) | Name::Anonym(_)) => {
390 match self.members.entry(name.clone()) {
391 hashbrown::hash_map::Entry::Occupied(_) => {
392 Err(InsertError {
394 name: name.clone(),
395 kind: InsertErrorKind::AlreadyExists,
396 })
397 }
398 hashbrown::hash_map::Entry::Vacant(ve) => {
399 ve.insert(insertion);
400 Ok(())
401 }
402 }
403 }
404 Name::Pending => {
405 self.anonymous_insertions.push(insertion);
406 Ok(())
407 }
408 }
409 }
410
411 pub fn insert_anonymous<T: UniverseMember>(&mut self, value: T) -> Handle<T> {
418 let handle = Handle::new_pending(Name::Pending, value);
419 self.anonymous_insertions
420 .push(MemberTxn::Insert(UniverseMember::into_any_handle(
421 handle.clone(),
422 )));
423 handle
424 }
425
426 #[expect(clippy::needless_pass_by_value)] pub fn delete<R: ErasedHandle>(member_handle: R) -> Self {
436 Self::from_member_txn(member_handle.name(), MemberTxn::Delete)
437 }
438
439 pub fn behaviors(t: behavior::BehaviorSetTransaction<Universe>) -> Self {
441 Self {
442 behaviors: t,
443 members: HbHashMap::new(),
444 anonymous_insertions: Vec::new(),
445 universe_id: Equal(None),
446 }
447 }
448
449 pub fn universe_id(&self) -> Option<UniverseId> {
453 self.universe_id.0
454 }
455}
456
457impl From<AnyTransaction> for UniverseTransaction {
458 fn from(transaction: AnyTransaction) -> Self {
459 if let Some(name) = transaction.target_name() {
460 Self::from_member_txn(name, MemberTxn::Modify(transaction))
461 } else {
462 UniverseTransaction::default()
463 }
464 }
465}
466
467impl Transaction for UniverseTransaction {
468 type Target = Universe;
469 type CommitCheck = UniverseCommitCheck;
470 type Output = transaction::NoOutput;
471 type Mismatch = UniverseMismatch;
472
473 fn check(&self, target: &Universe) -> Result<Self::CommitCheck, UniverseMismatch> {
474 if let Some(universe_id) = self.universe_id() {
475 if universe_id != target.id {
476 return Err(UniverseMismatch::DifferentUniverse {
477 transaction: universe_id,
478 target: target.id,
479 });
480 }
481 }
482 let mut member_checks = HbHashMap::with_capacity(self.members.len());
483 for (name, member) in self.members.iter() {
484 match name {
485 Name::Specific(_) | Name::Anonym(_) => {}
486 Name::Pending => {
487 return Err(UniverseMismatch::InvalidPending);
491 }
492 }
493
494 member_checks.insert(
495 name.clone(),
496 member.check(target, name).map_err(|e| {
497 UniverseMismatch::Member(transaction::MapMismatch {
498 key: name.clone(),
499 mismatch: e,
500 })
501 })?,
502 );
503 }
504
505 let mut insert_checks = Vec::with_capacity(self.anonymous_insertions.len());
506 for insert_txn in self.anonymous_insertions.iter() {
507 insert_checks.push(
508 insert_txn
509 .check(target, &Name::Pending)
510 .map_err(|mismatch| {
511 UniverseMismatch::Member(transaction::MapMismatch {
512 key: Name::Pending,
513 mismatch,
514 })
515 })?,
516 );
517 }
518
519 Ok(UniverseCommitCheck {
520 members: member_checks,
521 anonymous_insertions: insert_checks,
522 behaviors: self
523 .behaviors
524 .check(&target.behaviors)
525 .map_err(UniverseMismatch::Behaviors)?,
526 })
527 }
528
529 fn commit(
530 &self,
531 target: &mut Universe,
532 checks: Self::CommitCheck,
533 outputs: &mut dyn FnMut(Self::Output),
534 ) -> Result<(), CommitError> {
535 let Self {
536 members,
537 anonymous_insertions,
538 behaviors,
539 universe_id,
540 } = self;
541 let UniverseCommitCheck {
542 members: check_members,
543 anonymous_insertions: check_anon,
544 behaviors: check_behaviors,
545 } = checks;
546
547 if universe_id.check(&target.id).is_err() {
549 return Err(CommitError::message::<Self>(
550 "cannot commit a transaction to a different universe \
551 than it was constructed for"
552 .into(),
553 ));
554 }
555
556 for (name, check) in check_members {
557 members[&name]
558 .commit(target, &name, check, outputs)
559 .map_err(|e| e.context(format!("universe member {name}")))?;
560 }
561
562 for (new_member, check) in anonymous_insertions.iter().cloned().zip(check_anon) {
563 new_member.commit(target, &Name::Pending, check, outputs)?;
564 }
565
566 behaviors.commit(
567 &mut target.behaviors,
568 check_behaviors,
569 &mut transaction::no_outputs,
570 )?;
571
572 Ok(())
573 }
574}
575
576impl Merge for UniverseTransaction {
577 type MergeCheck = UniverseMergeCheck;
578 type Conflict = UniverseConflict;
579
580 fn check_merge(&self, other: &Self) -> Result<Self::MergeCheck, Self::Conflict> {
581 if let (Some(id1), Some(id2)) = (self.universe_id(), other.universe_id()) {
583 if id1 != id2 {
584 return Err(UniverseConflict::DifferentUniverse(id1, id2));
585 }
586 }
587 Ok(UniverseMergeCheck {
588 members: self
589 .members
590 .check_merge(&other.members)
591 .map_err(UniverseConflict::Member)?,
592 behaviors: self
593 .behaviors
594 .check_merge(&other.behaviors)
595 .map_err(UniverseConflict::Behaviors)?,
596 })
597 }
598
599 fn commit_merge(&mut self, other: Self, check: Self::MergeCheck) {
600 let Self {
601 members,
602 anonymous_insertions,
603 behaviors,
604 universe_id,
605 } = self;
606
607 members.commit_merge(other.members, check.members);
608 anonymous_insertions.extend(other.anonymous_insertions);
609 behaviors.commit_merge(other.behaviors, check.behaviors);
610 universe_id.commit_merge(other.universe_id, ());
611 }
612}
613
614impl fmt::Debug for UniverseTransaction {
616 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
617 let Self {
618 members,
619 anonymous_insertions,
620 behaviors,
621 universe_id: _, } = self;
623
624 let mut ds = fmt.debug_struct("UniverseTransaction");
625 for (name, txn) in members {
626 ds.field(&name.to_string(), txn.transaction_as_debug());
629 }
630 for txn in anonymous_insertions {
631 ds.field("[anonymous pending]", txn.transaction_as_debug());
632 }
633 if !behaviors.is_empty() {
634 ds.field("behaviors", behaviors);
635 }
636 ds.finish()
637 }
638}
639
640#[derive(Clone, Debug, Default, PartialEq)]
646enum MemberTxn {
647 #[default]
650 Noop,
651 Modify(AnyTransaction),
653 Insert(AnyHandle),
658 Delete,
662}
663
664#[derive(Debug)]
665struct MemberMergeCheck(Option<AnyTransactionCheck>);
666#[derive(Debug)]
667struct MemberCommitCheck(Option<AnyTransactionCheck>);
668
669impl MemberTxn {
670 fn check(&self, universe: &Universe, name: &Name) -> Result<MemberCommitCheck, MemberMismatch> {
671 match self {
672 MemberTxn::Noop => Ok(MemberCommitCheck(None)),
673 MemberTxn::Modify(txn) => {
676 Ok(MemberCommitCheck(Some(txn.check(&()).map_err(|e| {
677 MemberMismatch::Modify(ModifyMemberMismatch(e))
678 })?)))
679 }
680 MemberTxn::Insert(pending_handle) => {
681 {
682 if pending_handle.name() != *name {
683 return Err(MemberMismatch::Insert(InsertError {
684 name: name.clone(),
685 kind: InsertErrorKind::AlreadyInserted,
686 }));
687 }
688 }
689
690 match name {
692 Name::Specific(_) | Name::Pending => {}
693 Name::Anonym(_) => {
694 return Err(MemberMismatch::Insert(InsertError {
695 name: name.clone(),
696 kind: InsertErrorKind::InvalidName,
697 }))
698 }
699 }
700
701 if universe.get_any(name).is_some() {
702 return Err(MemberMismatch::Insert(InsertError {
703 name: name.clone(),
704 kind: InsertErrorKind::AlreadyExists,
705 }));
706 }
707 pending_handle
710 .check_upgrade_pending(universe.id)
711 .map_err(MemberMismatch::Insert)?;
712 Ok(MemberCommitCheck(None))
713 }
714 MemberTxn::Delete => {
715 if let Name::Specific(_) = name {
716 if universe.get_any(name).is_some() {
717 Ok(MemberCommitCheck(None))
718 } else {
719 Err(MemberMismatch::DeleteNonexistent(name.clone()))
720 }
721 } else {
722 Err(MemberMismatch::DeleteInvalid(name.clone()))
723 }
724 }
725 }
726 }
727
728 fn commit(
729 &self,
730 universe: &mut Universe,
731 name: &Name,
732 MemberCommitCheck(check): MemberCommitCheck,
733 outputs: &mut dyn FnMut(core::convert::Infallible), ) -> Result<(), CommitError> {
735 match self {
736 MemberTxn::Noop => {
737 assert!(check.is_none());
738 Ok(())
739 }
740 MemberTxn::Modify(txn) => {
741 txn.commit(&mut (), check.expect("missing check value"), outputs)
742 }
743 MemberTxn::Insert(pending_handle) => {
744 pending_handle
745 .insert_and_upgrade_pending(universe)
746 .map_err(CommitError::catch::<UniverseTransaction, InsertError>)?;
747 Ok(())
748 }
749 MemberTxn::Delete => {
750 assert!(check.is_none());
751 let did_delete = universe.delete(name);
752 assert!(did_delete);
753 Ok(())
754 }
755 }
756 }
757
758 fn transaction_as_debug(&self) -> &dyn fmt::Debug {
760 use MemberTxn::*;
761 match self {
762 Modify(t) => t.transaction_as_debug(),
763 Noop | Insert(_) | Delete => self,
764 }
765 }
766
767 fn universe_id(&self) -> Option<UniverseId> {
768 use MemberTxn::*;
769 match self {
770 Modify(t) => t.universe_id(),
771 Noop | Insert(_) | Delete => None,
772 }
773 }
774}
775
776pub(in crate::universe) fn any_handle_insert_and_upgrade_pending<T>(
779 universe: &mut Universe,
780 pending_handle: &Handle<T>,
781) -> Result<(), InsertError>
782where
783 T: 'static,
784 Universe: UniverseTable<T, Table = super::Storage<T>>,
785{
786 let new_root_handle = pending_handle.upgrade_pending(universe)?;
787
788 UniverseTable::<T>::table_mut(universe).insert(pending_handle.name(), new_root_handle);
789 universe.wants_gc = true;
790
791 Ok(())
792}
793
794impl Merge for MemberTxn {
795 type MergeCheck = MemberMergeCheck;
796 type Conflict = MemberConflict;
797
798 fn check_merge(&self, other: &Self) -> Result<Self::MergeCheck, Self::Conflict> {
799 use MemberTxn::*;
800 match (self, other) {
801 (Noop, _) | (_, Noop) => Ok(MemberMergeCheck(None)),
803 (Modify(t1), Modify(t2)) => {
805 let check = t1
806 .check_merge(t2)
807 .map_err(|e| MemberConflict::Modify(ModifyMemberConflict(e)))?;
808 Ok(MemberMergeCheck(Some(check)))
809 }
810 (Insert(_), _) | (_, Insert(_)) => Err(MemberConflict::InsertVsOther),
812 (Delete, Delete) => Ok(MemberMergeCheck(None)),
814 (Delete, _) | (_, Delete) => Err(MemberConflict::DeleteVsOther),
815 }
816 }
817
818 fn commit_merge(&mut self, other: Self, MemberMergeCheck(check): Self::MergeCheck)
819 where
820 Self: Sized,
821 {
822 use MemberTxn::*;
823 match (self, other) {
824 (t1 @ Noop, t2) => {
825 assert!(check.is_none());
826 *t1 = t2;
827 }
828 (_, Noop) => {
829 assert!(check.is_none());
830 }
831 (Modify(t1), Modify(t2)) => {
832 t1.commit_merge(t2, check.expect("missing check value"));
833 }
834 (Delete, Delete) => {}
835 (a @ (Insert(_) | Delete), b) | (a, b @ (Insert(_) | Delete)) => {
836 panic!(
837 "Invalid merge check: tried to merge {a:?} with {b:?}, \
838 which are not mergeable"
839 );
840 }
841 }
842 }
843}
844
845#[derive(Clone, Debug, Eq, PartialEq, displaydoc::Display)]
847#[non_exhaustive]
848pub enum MemberConflict {
849 InsertVsOther,
851
852 DeleteVsOther,
854
855 #[displaydoc("{0}")]
857 Modify(ModifyMemberConflict),
858}
859
860impl core::error::Error for MemberConflict {
861 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
862 match self {
863 MemberConflict::InsertVsOther => None,
864 MemberConflict::DeleteVsOther => None,
865 MemberConflict::Modify(e) => e.source(),
866 }
867 }
868}
869
870#[derive(Clone, Debug, Eq, PartialEq, displaydoc::Display)]
872#[non_exhaustive]
873pub enum MemberMismatch {
874 Insert(InsertError),
876
877 DeleteNonexistent(Name),
879
880 DeleteInvalid(Name),
882
883 #[displaydoc("{0}")]
885 Modify(ModifyMemberMismatch),
886}
887
888impl core::error::Error for MemberMismatch {
889 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
890 match self {
891 MemberMismatch::Insert(e) => e.source(),
892 MemberMismatch::DeleteNonexistent(_) => None,
893 MemberMismatch::DeleteInvalid(_) => None,
894 MemberMismatch::Modify(e) => e.source(),
895 }
896 }
897}
898
899#[derive(Clone, Debug, Eq, PartialEq)]
906pub struct ModifyMemberMismatch(AnyTransactionMismatch);
907
908#[derive(Clone, Debug, Eq, PartialEq)]
915pub struct ModifyMemberConflict(AnyTransactionConflict);
916
917impl core::error::Error for ModifyMemberMismatch {
918 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
919 self.0.source()
920 }
921}
922impl core::error::Error for ModifyMemberConflict {
923 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
924 self.0.source()
925 }
926}
927
928impl fmt::Display for ModifyMemberMismatch {
929 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
930 self.0.fmt(f)
931 }
932}
933impl fmt::Display for ModifyMemberConflict {
934 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
935 self.0.fmt(f)
936 }
937}
938
939impl From<AnyTransactionMismatch> for ModifyMemberMismatch {
940 fn from(value: AnyTransactionMismatch) -> Self {
941 Self(value)
942 }
943}
944impl From<AnyTransactionConflict> for ModifyMemberConflict {
945 fn from(value: AnyTransactionConflict) -> Self {
946 Self(value)
947 }
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
956 use crate::block::BlockDef;
957 use crate::block::AIR;
958 use crate::content::make_some_blocks;
959 use crate::math::Cube;
960 use crate::space::CubeConflict;
961 use crate::space::Space;
962 use crate::space::SpaceTransaction;
963 use crate::space::SpaceTransactionConflict;
964 use crate::transaction::{ExecuteError, MapConflict, TransactionTester};
965 use crate::universe::{self, HandleError};
966 use alloc::sync::Arc;
967 use indoc::indoc;
968
969 #[test]
970 fn has_default() {
971 assert_eq!(
972 UniverseTransaction::default(),
973 UniverseTransaction {
974 members: HbHashMap::new(),
975 anonymous_insertions: Vec::new(),
976 universe_id: Equal(None),
977 behaviors: behavior::BehaviorSetTransaction::default()
978 }
979 )
980 }
981
982 #[test]
983 fn debug_empty() {
984 let transaction = UniverseTransaction::default();
985
986 pretty_assertions::assert_str_eq!(format!("{transaction:#?}"), "UniverseTransaction");
987 }
988
989 #[test]
990 fn debug_full() {
991 let [block] = make_some_blocks();
992 let mut u = Universe::new();
993 let space = u.insert_anonymous(Space::empty_positive(1, 1, 1));
994
995 #[derive(Clone, Debug, PartialEq)]
997 struct UTestBehavior {}
998 impl behavior::Behavior<Universe> for UTestBehavior {
999 fn step(
1000 &self,
1001 _: &behavior::Context<'_, Universe>,
1002 ) -> (UniverseTransaction, behavior::Then) {
1003 unimplemented!()
1004 }
1005 fn persistence(&self) -> Option<behavior::Persistence> {
1006 None
1007 }
1008 }
1009 impl universe::VisitHandles for UTestBehavior {
1010 fn visit_handles(&self, _visitor: &mut dyn universe::HandleVisitor) {}
1012 }
1013
1014 let mut transaction = SpaceTransaction::set_cube([0, 0, 0], None, Some(block)).bind(space);
1017 transaction.insert_anonymous(BlockDef::new(AIR));
1019 transaction
1021 .merge_from(UniverseTransaction::behaviors(
1022 behavior::BehaviorSetTransaction::insert((), Arc::new(UTestBehavior {})),
1023 ))
1024 .unwrap();
1025
1026 println!("{transaction:#?}");
1027 pretty_assertions::assert_str_eq!(
1028 format!("{transaction:#?}\n"),
1029 indoc! {r#"
1030 UniverseTransaction {
1031 [anonymous #0]: SpaceTransaction {
1032 (+0, +0, +0): CubeTransaction {
1033 old: None,
1034 new: Some(
1035 Block {
1036 primitive: Atom {
1037 color: Rgba(0.5, 0.5, 0.5, 1.0),
1038 collision: Hard,
1039 },
1040 modifiers: [
1041 BlockAttributes {
1042 display_name: "0",
1043 },
1044 ],
1045 },
1046 ),
1047 conserved: true,
1048 },
1049 },
1050 [anonymous pending]: Insert(
1051 BlockDef(
1052 Handle([pending anonymous] in no universe = BlockDef {
1053 block: Block {
1054 primitive: Air,
1055 },
1056 cache_dirty: Flag(false),
1057 listeners_ok: true,
1058 notifier: Notifier(0),
1059 ..
1060 }),
1061 ),
1062 ),
1063 behaviors: BehaviorSetTransaction {
1064 replace: {},
1065 insert: [
1066 UTestBehavior @ (),
1067 ],
1068 },
1069 }
1070 "#}
1071 .to_string()
1072 );
1073 }
1074
1075 #[test]
1076 fn merge_unrelated() {
1077 let [block_1, block_2] = make_some_blocks();
1078 let mut u = Universe::new();
1079 let s1 = u.insert_anonymous(Space::empty_positive(1, 1, 1));
1080 let s2 = u.insert_anonymous(Space::empty_positive(1, 1, 1));
1081 let t1 = SpaceTransaction::set_cube([0, 0, 0], None, Some(block_1)).bind(s1);
1082 let t2 = SpaceTransaction::set_cube([0, 0, 0], None, Some(block_2)).bind(s2);
1083 let _ = t1.merge(t2).unwrap();
1084 }
1086
1087 #[test]
1088 fn merge_conflict() {
1089 let [block_1, block_2] = make_some_blocks();
1090 let mut u = Universe::new();
1091 let s = u.insert_anonymous(Space::empty_positive(1, 1, 1));
1092 let t1 = SpaceTransaction::set_cube([0, 0, 0], None, Some(block_1)).bind(s.clone());
1093 let t2 = SpaceTransaction::set_cube([0, 0, 0], None, Some(block_2)).bind(s.clone());
1094
1095 let error = t1.merge(t2).unwrap_err();
1096
1097 let UniverseConflict::Member(MapConflict {
1098 key,
1099 conflict:
1100 MemberConflict::Modify(ModifyMemberConflict(AnyTransactionConflict::Space(
1101 SpaceTransactionConflict::Cube {
1102 cube: Cube { x: 0, y: 0, z: 0 },
1103 conflict:
1104 CubeConflict {
1105 old: false,
1106 new: true,
1107 },
1108 },
1109 ))),
1110 }) = error
1111 else {
1112 panic!("not as expected: {error:?}");
1113 };
1114 assert_eq!(key, s.name());
1115 }
1116
1117 #[test]
1118 fn merges_members() {
1119 let [old_block, new_block] = make_some_blocks();
1120 let mut u = Universe::new();
1121 let s = u.insert_anonymous(Space::empty_positive(1, 1, 1));
1122 let t1 =
1123 SpaceTransaction::set_cube([0, 0, 0], None, Some(new_block.clone())).bind(s.clone());
1124 let t2 =
1125 SpaceTransaction::set_cube([0, 0, 0], Some(old_block.clone()), None).bind(s.clone());
1126 let t3 = t1.merge(t2).unwrap();
1127 assert_eq!(
1128 t3,
1129 SpaceTransaction::set_cube([0, 0, 0], Some(old_block), Some(new_block)).bind(s)
1130 );
1131 }
1132
1133 #[test]
1134 fn insert_affects_clones() {
1135 let mut u = Universe::new();
1136 let pending_1 = Handle::new_pending("foo".into(), Space::empty_positive(1, 1, 1));
1137 let pending_2 = pending_1.clone();
1138
1139 UniverseTransaction::insert(pending_2)
1140 .execute(&mut u, &mut drop)
1141 .unwrap();
1142
1143 assert_eq!(pending_1.universe_id(), Some(u.universe_id()));
1144 }
1146
1147 #[test]
1150 fn insert_anonymous() {
1151 let mut u = Universe::new();
1152
1153 let foo = Handle::new_pending(Name::Pending, Space::empty_positive(1, 1, 1));
1155 let bar = Handle::new_pending(Name::Pending, Space::empty_positive(2, 2, 2));
1156
1157 UniverseTransaction::insert(foo.clone())
1158 .merge(UniverseTransaction::insert(bar.clone()))
1159 .expect("merge should allow 2 pending")
1160 .execute(&mut u, &mut drop)
1161 .expect("execute");
1162
1163 assert_eq!(u.get(&foo.name()).unwrap(), foo);
1165 assert_eq!(u.get(&bar.name()).unwrap(), bar);
1166 assert_ne!(foo, bar);
1167 }
1168
1169 #[test]
1170 fn insert_anonymous_equivalence() {
1171 let mut txn = UniverseTransaction::default();
1172 let handle = txn.insert_anonymous(Space::empty_positive(1, 1, 1));
1173
1174 assert_eq!(handle.name(), Name::Pending);
1175 assert_eq!(txn, UniverseTransaction::insert(handle));
1176 }
1177
1178 #[test]
1179 #[ignore] fn systematic() {
1181 TransactionTester::new()
1182 .transaction(UniverseTransaction::default(), |_, _| Ok(()))
1184 .transaction(
1186 UniverseTransaction::insert(Handle::new_pending(
1187 "foo".into(),
1188 Space::empty_positive(1, 1, 1),
1189 )),
1190 |_, _| Ok(()),
1191 )
1192 .target(Universe::new)
1193 .test();
1195 }
1196
1197 #[test]
1198 fn wrong_universe_execute() {
1199 let [block] = make_some_blocks();
1200 let mut u1 = Universe::new();
1201 let mut u2 = Universe::new();
1202 let s1 = u1.insert_anonymous(Space::empty_positive(1, 1, 1));
1203 let t1 = SpaceTransaction::set_cube([0, 0, 0], None, Some(block)).bind(s1);
1204
1205 let e = t1.execute(&mut u2, &mut drop).unwrap_err();
1206 assert!(matches!(e, ExecuteError::Check(_)));
1207 }
1208
1209 #[test]
1210 fn wrong_universe_merge() {
1211 let [block] = make_some_blocks();
1212 let mut u1 = Universe::new();
1213 let mut u2 = Universe::new();
1214 let s1 = u1.insert_anonymous(Space::empty_positive(1, 1, 1));
1215 let s2 = u2.insert_anonymous(Space::empty_positive(1, 1, 1));
1216 let bare_txn = SpaceTransaction::set_cube([0, 0, 0], None, Some(block));
1217 let t1 = bare_txn.clone().bind(s1);
1218 let t2 = bare_txn.bind(s2);
1219
1220 t1.merge(t2).unwrap_err();
1221 }
1222
1223 #[test]
1224 fn insert_named_already_in_different_universe() {
1225 let mut u1 = Universe::new();
1226 let mut u2 = Universe::new();
1227 let handle = u1
1228 .insert("foo".into(), Space::empty_positive(1, 1, 1))
1229 .unwrap();
1230 let txn = UniverseTransaction::insert(handle);
1231
1232 let e = txn.execute(&mut u2, &mut drop).unwrap_err();
1233 assert_eq!(
1234 dbg!(e),
1235 ExecuteError::Check(UniverseMismatch::Member(transaction::MapMismatch {
1236 key: "foo".into(),
1237 mismatch: MemberMismatch::Insert(InsertError {
1238 name: "foo".into(),
1239 kind: InsertErrorKind::AlreadyInserted
1240 })
1241 }))
1242 );
1243 }
1244
1245 #[test]
1246 fn insert_anonymous_already_in_different_universe() {
1247 let handle = Handle::new_pending(Name::Pending, Space::empty_positive(1, 1, 1));
1248 let mut u1 = Universe::new();
1249 let mut u2 = Universe::new();
1250 let txn = UniverseTransaction::insert(handle);
1251
1252 txn.execute(&mut u1, &mut drop).unwrap();
1253 let e = txn.execute(&mut u2, &mut drop).unwrap_err();
1254
1255 assert_eq!(
1256 dbg!(e),
1257 ExecuteError::Check(UniverseMismatch::Member(transaction::MapMismatch {
1258 key: Name::Pending,
1259 mismatch: MemberMismatch::Insert(InsertError {
1260 name: Name::Pending,
1261 kind: InsertErrorKind::AlreadyInserted
1262 })
1263 }))
1264 );
1265 }
1266
1267 #[test]
1268 fn handle_error_from_handle_execute() {
1269 let e = Handle::<Space>::new_gone("foo".into())
1270 .execute(&SpaceTransaction::default())
1271 .unwrap_err();
1272
1273 assert_eq!(e, ExecuteError::Handle(HandleError::Gone("foo".into())));
1274 }
1275
1276 #[test]
1278 #[should_panic = "Attempted to execute transaction with target already borrowed: Gone(Specific(\"foo\"))"]
1279 fn handle_error_from_universe_txn() {
1280 let mut u = Universe::new();
1281 let txn = SpaceTransaction::default().bind(Handle::<Space>::new_gone("foo".into()));
1282
1283 _ = txn.execute(&mut u, &mut transaction::no_outputs);
1284 }
1285}