1use alloc::boxed::Box;
12use alloc::vec::Vec;
13use core::error::Error;
14use core::fmt;
15use core::hash::Hash;
16use core::ops::Index;
17
18use exhaust::Exhaust;
19use hashbrown::HashMap as HbHashMap;
20
21use crate::block::{self, Block, BlockDef};
22use crate::space::{self, SetCubeError, SpaceTransaction};
23use crate::transaction::ExecuteError;
24use crate::universe::{
25 self, Handle, InsertError, Name, ReadTicket, Universe, UniverseTransaction, VisitHandles,
26};
27use crate::util::YieldProgress;
28
29#[cfg(doc)]
30use crate::block::Primitive;
31
32fn name_in_module<E: BlockModule>(key: &E) -> Name {
33 Name::from(format!("{ns}/{key}", ns = E::namespace()))
34}
35
36pub trait DefaultProvision<T> {
42 fn module_default(self) -> T;
44}
45
46pub trait BlockModule: Exhaust + fmt::Debug + fmt::Display + Eq + Hash + Clone {
58 fn namespace() -> &'static str;
62}
63
64pub type BlockProvider<E> = Provider<E, Block>;
68
69#[derive(Clone, Debug)]
71pub struct Provider<E, V> {
72 map: HbHashMap<E, V>,
75}
76
77impl<E, V> Default for Provider<E, V>
78where
79 E: DefaultProvision<V> + Exhaust + Eq + Hash + Clone,
80{
81 fn default() -> Self {
82 Self {
83 map: E::exhaust()
84 .map(|key| {
85 let value = DefaultProvision::module_default(key.clone());
86 (key, value)
87 })
88 .collect(),
89 }
90 }
91}
92
93impl<E, V> Provider<E, V>
94where
95 E: BlockModule,
96{
97 pub async fn new<F>(progress: YieldProgress, mut definer: F) -> Result<Self, GenError>
103 where
104 F: FnMut(E) -> Result<V, InGenError>,
105 {
106 let count = E::exhaust().count();
107 let mut map = HbHashMap::with_capacity(count);
108 #[expect(
109 clippy::shadow_unrelated,
110 reason = "https://github.com/rust-lang/rust-clippy/issues/11827"
111 )]
112 for (key, progress) in E::exhaust().zip(progress.split_evenly(count)) {
113 match definer(key.clone()) {
114 Ok(value) => {
115 map.insert(key, value);
116 progress.finish().await;
117 }
118 Err(e) => return Err(GenError::failure(e, name_in_module(&key))),
119 }
120 }
121 Ok(Self { map })
122 }
123}
124
125impl<E: BlockModule> Provider<E, Block> {
127 pub async fn new_installed_cyclic<F>(
137 progress: YieldProgress,
138 txn: &mut UniverseTransaction,
139 mut definer: F,
140 ) -> Result<Self, GenError>
141 where
142 F: FnMut(&Provider<E, Block>, &mut UniverseTransaction, E) -> Result<Block, InGenError>,
143 {
144 let module: Self = Self::new_sync(|key| {
145 let block_def_handle: Handle<BlockDef> = txn
146 .insert_without_value(name_in_module(&key))
147 .expect("module failed to produce distinct keys");
148 Block::from(block_def_handle)
149 });
150
151 let count = module.map.len();
152 #[expect(
153 clippy::shadow_unrelated,
154 reason = "https://github.com/rust-lang/rust-clippy/issues/11827"
155 )]
156 for (key, progress) in E::exhaust().zip(progress.split_evenly(count)) {
157 match definer(&module, txn, key.clone()) {
158 Ok(block_value) => {
159 let block::Primitive::Indirect(block_def_handle) = module[key].primitive()
160 else {
161 unreachable!()
162 };
163 txn.set_pending_value(
164 block_def_handle,
165 BlockDef::new(
166 txn.read_ticket().expect_may_fail(), block_value,
168 ),
169 );
170 progress.finish().await;
171 }
172 Err(e) => return Err(GenError::failure(e, name_in_module(&key))),
173 }
174 }
175 Ok(module)
176 }
177
178 pub fn install(
185 &self,
186 read_ticket: ReadTicket<'_>,
187 txn: &mut UniverseTransaction,
188 ) -> Result<Self, InsertError> {
189 #[inline(never)]
191 fn create_block_def_and_indirect(
192 read_ticket: ReadTicket<'_>,
193 txn: &mut UniverseTransaction,
194 name: Name,
195 block: &Block,
196 ) -> Result<Block, InsertError> {
197 let value = BlockDef::new(read_ticket.with_transaction(txn), block.clone());
198 let block_def_handle = txn.insert_mut(name, value)?;
199 let indirect_block = Block::from(block_def_handle);
200 Ok(indirect_block)
201 }
202
203 let mut map = HbHashMap::with_capacity(self.map.len());
204 for key in E::exhaust() {
205 let indirect_block =
206 create_block_def_and_indirect(read_ticket, txn, name_in_module(&key), &self[&key])?;
207 map.insert(key, indirect_block);
208 }
209 Ok(Self { map })
210 }
211
212 pub fn using(universe: &Universe) -> Result<Self, ProviderError>
218 where
219 E: Eq + Hash + fmt::Display,
220 {
221 let mut found: HbHashMap<E, Handle<BlockDef>> = HbHashMap::new();
222 let mut missing = Vec::new();
223 for key in E::exhaust() {
224 let name = name_in_module(&key);
225 if let Some(handle) = universe.get(&name) {
226 found.insert(key, handle);
227 } else {
228 missing.push(name);
229 }
230 }
231 if !missing.is_empty() {
232 return Err(ProviderError {
233 missing: missing.into(),
234 });
235 }
236 Ok(Provider {
237 map: E::exhaust()
238 .map(|key| {
239 let block = Block::from(found.remove(&key).unwrap());
240 (key, block)
241 })
242 .collect(),
243 })
244 }
245}
246
247impl<E: Exhaust + fmt::Debug + Clone + Eq + Hash, V> Provider<E, V> {
249 pub fn new_sync<F>(mut definer: F) -> Self
251 where
252 F: FnMut(E) -> V,
253 {
254 Provider {
255 map: E::exhaust().map(|key| (key.clone(), definer(key))).collect(),
256 }
257 }
258
259 #[must_use]
264 pub fn subset<K>(&self, function: impl Fn(K) -> E) -> Provider<K, V>
265 where
266 K: Exhaust + fmt::Debug + Clone + Eq + Hash,
267 V: Clone,
268 {
269 Provider::new_sync(|key: K| self[function(key)].clone())
270 }
271
272 #[must_use]
274 pub fn map<V2>(&self, mut function: impl FnMut(&E, &V) -> V2) -> Provider<E, V2> {
275 Provider {
276 map: self
277 .map
278 .iter()
279 .map(|(key, value)| (key.clone(), function(key, value)))
280 .collect(),
281 }
282 }
283
284 pub fn iter(&self) -> ModuleIter<'_, E, V> {
286 ModuleIter {
287 key_iter: E::exhaust(),
288 map: &self.map,
289 }
290 }
291
292 #[cfg(test)]
293 fn consistency_check(&self) {
294 use hashbrown::HashSet;
295 let expected_keys: HashSet<E> = E::exhaust().collect();
296 let actual_keys: HashSet<E> = self.map.keys().cloned().collect();
297 assert_eq!(
298 expected_keys, actual_keys,
299 "Provider keys are not as expected"
300 );
301 }
302}
303
304impl<E: Eq + Hash, V: PartialEq> PartialEq for Provider<E, V> {
305 fn eq(&self, other: &Self) -> bool {
306 let Self { map } = self;
307 *map == other.map
308 }
309}
310impl<E: Eq + Hash, V: PartialEq> Eq for Provider<E, V> {}
311
312impl<E: Eq + Hash, V> Index<E> for Provider<E, V> {
313 type Output = V;
314
315 fn index(&self, index: E) -> &Self::Output {
316 &self.map[&index]
317 }
318}
319impl<E: Eq + Hash, V> Index<&E> for Provider<E, V> {
320 type Output = V;
321
322 fn index(&self, index: &E) -> &Self::Output {
323 &self.map[index]
324 }
325}
326
327impl<'provider, E: Exhaust + fmt::Debug + Clone + Eq + Hash, V> IntoIterator
328 for &'provider Provider<E, V>
329{
330 type Item = (E, &'provider V);
331 type IntoIter = ModuleIter<'provider, E, V>;
332 fn into_iter(self) -> Self::IntoIter {
333 self.iter()
334 }
335}
336
337impl<E: Eq + Hash + VisitHandles, V: VisitHandles> VisitHandles for Provider<E, V> {
338 fn visit_handles(&self, visitor: &mut dyn universe::HandleVisitor) {
339 let Self { map } = self;
340 for (key, value) in map {
341 key.visit_handles(visitor);
342 value.visit_handles(visitor);
343 }
344 }
345}
346
347#[expect(missing_debug_implementations)]
351pub struct ModuleIter<'provider, E: Exhaust, V> {
352 key_iter: exhaust::Iter<E>,
355 map: &'provider HbHashMap<E, V>,
356}
357
358impl<'provider, E: Exhaust + Eq + Hash, V> Iterator for ModuleIter<'provider, E, V> {
359 type Item = (E, &'provider V);
360
361 fn next(&mut self) -> Option<Self::Item> {
362 self.key_iter.next().map(|key| {
363 let value: &V = &self.map[&key];
364 (key, value)
365 })
366 }
367
368 fn size_hint(&self) -> (usize, Option<usize>) {
369 self.key_iter.size_hint()
370 }
371}
372
373impl<E, V> ExactSizeIterator for ModuleIter<'_, E, V>
374where
375 E: Exhaust + Eq + Hash,
376 exhaust::Iter<E>: ExactSizeIterator,
377{
378}
379
380#[derive(Clone, Debug, Eq, displaydoc::Display, PartialEq)]
383#[displaydoc("module definitions missing from universe: {missing:?}")] pub struct ProviderError {
385 missing: Box<[Name]>,
386}
387
388impl Error for ProviderError {}
389
390#[derive(Debug)]
400pub struct GenError {
401 detail: InGenError,
402 for_object: Option<Name>,
403}
404
405impl Error for GenError {
406 fn source(&self) -> Option<&(dyn Error + 'static)> {
407 Some(&self.detail)
408 }
409}
410
411impl GenError {
412 pub fn failure(error: impl Into<InGenError>, object: Name) -> Self {
415 Self {
416 detail: error.into(),
417 for_object: Some(object),
418 }
419 }
420}
421
422impl fmt::Display for GenError {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 if let Some(name) = &self.for_object {
427 write!(f, "An error occurred while generating object {name}")?;
428 } else {
429 write!(f, "An error occurred while generating an object")?;
430 }
431 Ok(())
432 }
433}
434
435impl From<InsertError> for GenError {
436 fn from(error: InsertError) -> Self {
438 GenError {
439 for_object: Some(error.name.clone()),
440 detail: error.into(),
441 }
442 }
443}
444
445impl From<ExecuteError<UniverseTransaction>> for GenError {
446 fn from(error: ExecuteError<UniverseTransaction>) -> Self {
450 GenError {
451 for_object: None,
452 detail: error.into(),
453 }
454 }
455}
456
457#[derive(Debug)]
470#[non_exhaustive]
471pub enum InGenError {
472 Other(Box<dyn Error + Send + Sync>),
474
475 Gen(Box<GenError>), Insert(InsertError),
480
481 Provider(ProviderError),
484
485 Space(space::builder::Error),
487
488 SetCube(SetCubeError),
490
491 UniverseTransaction(ExecuteError<UniverseTransaction>),
493
494 SpaceTransaction(ExecuteError<SpaceTransaction>),
496}
497
498impl InGenError {
499 #[cfg_attr(not(feature = "std"), doc(hidden))]
501 pub fn other<E: Error + Send + Sync + 'static>(error: E) -> Self {
502 Self::Other(Box::new(error))
503 }
504}
505
506impl Error for InGenError {
507 fn source(&self) -> Option<&(dyn Error + 'static)> {
508 match self {
509 InGenError::Other(e) => e.source(),
510 InGenError::Gen(e) => e.source(),
511 InGenError::Insert(e) => e.source(),
512 InGenError::Provider(e) => e.source(),
513 InGenError::Space(e) => e.source(),
514 InGenError::SetCube(e) => e.source(),
515 InGenError::UniverseTransaction(e) => e.source(),
516 InGenError::SpaceTransaction(e) => e.source(),
517 }
518 }
519}
520
521impl fmt::Display for InGenError {
522 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523 match self {
524 InGenError::Other(e) => e.fmt(f),
525 InGenError::Gen(e) => e.fmt(f),
526 InGenError::Insert(e) => e.fmt(f),
527 InGenError::Provider(e) => e.fmt(f),
528 InGenError::Space(e) => e.fmt(f),
529 InGenError::SetCube(e) => e.fmt(f),
530 InGenError::UniverseTransaction(e) => e.fmt(f),
531 InGenError::SpaceTransaction(e) => e.fmt(f),
532 }
533 }
534}
535
536impl From<GenError> for InGenError {
537 fn from(error: GenError) -> Self {
538 InGenError::Gen(Box::new(error))
540 }
541}
542impl From<InsertError> for InGenError {
543 fn from(error: InsertError) -> Self {
544 InGenError::Insert(error)
545 }
546}
547impl From<ProviderError> for InGenError {
548 fn from(error: ProviderError) -> Self {
549 InGenError::Provider(error)
550 }
551}
552impl From<space::builder::Error> for InGenError {
553 fn from(error: space::builder::Error) -> Self {
554 InGenError::Space(error)
555 }
556}
557impl From<SetCubeError> for InGenError {
558 fn from(error: SetCubeError) -> Self {
559 InGenError::SetCube(error)
560 }
561}
562impl From<crate::content::load_image::BlockFromImageError> for InGenError {
563 fn from(error: crate::content::load_image::BlockFromImageError) -> Self {
564 InGenError::Other(Box::new(error))
566 }
567}
568impl From<ExecuteError<UniverseTransaction>> for InGenError {
569 fn from(error: ExecuteError<UniverseTransaction>) -> Self {
570 InGenError::UniverseTransaction(error)
571 }
572}
573impl From<ExecuteError<SpaceTransaction>> for InGenError {
574 fn from(error: ExecuteError<SpaceTransaction>) -> Self {
575 InGenError::SpaceTransaction(error)
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582 use crate::block::{AIR, Quote, Resolution::*};
583 use crate::content::make_some_blocks;
584 use crate::math::GridAab;
585 use crate::transaction::Transactional as _;
586 use crate::util::assert_conditional_send_sync;
587
588 #[derive(Exhaust, Clone, Debug, Eq, Hash, PartialEq)]
589 enum Key {
590 A,
591 B,
592 C,
593 }
594 impl fmt::Display for Key {
595 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596 write!(f, "{self:?}")
597 }
598 }
599 impl BlockModule for Key {
600 fn namespace() -> &'static str {
601 "test-key"
602 }
603 }
604
605 fn test_provider() -> ([Block; 3], BlockProvider<Key>) {
606 let blocks = make_some_blocks();
607 let provider = BlockProvider::new_sync(|k: Key| match k {
608 Key::A => blocks[0].clone(),
609 Key::B => blocks[1].clone(),
610 Key::C => blocks[2].clone(),
611 });
612 provider.consistency_check();
613
614 (blocks, provider)
615 }
616
617 #[test]
618 fn provider_install() {
619 let mut universe = Universe::new();
620 let (_, provider) = test_provider();
621
622 let installed = universe
624 .transact(|txn, u| Ok(provider.install(u.read_ticket(), txn)))
625 .unwrap()
626 .unwrap();
627
628 assert_eq!(installed, BlockProvider::using(&universe).unwrap());
629 }
630
631 #[test]
632 fn provider_subset() {
633 let (_, p1) = test_provider();
634 let p2 = p1.subset(|x: bool| if x { Key::A } else { Key::B });
635 p2.consistency_check();
636 assert_eq!(p1[Key::A], p2[true]);
637 assert_eq!(p1[Key::B], p2[false]);
638 }
639
640 #[test]
641 fn provider_map() {
642 let (_, p1) = test_provider();
643 let p2 = p1.map(|_, block| block.clone().with_modifier(Quote::default()));
644 p2.consistency_check();
645 assert_eq!(
646 p1[Key::A].clone().with_modifier(Quote::default()),
647 p2[Key::A],
648 );
649 }
650
651 #[test]
652 fn provider_eq() {
653 let (_, p1) = test_provider();
654 let (_, p2) = test_provider();
655 assert_eq!(p1, p2);
656 assert_ne!(
657 p1,
658 p2.map(|key, block| if *key == Key::B { AIR } else { block.clone() })
659 );
660 }
661
662 #[test]
663 fn errors_are_send_sync() {
664 assert_conditional_send_sync::<GenError>();
665 assert_conditional_send_sync::<InGenError>();
666 }
667
668 #[test]
669 fn gen_error_message() {
670 use alloc::string::ToString;
671
672 let set_cube_error = SetCubeError::OutOfBounds {
673 modification: GridAab::for_block(R1),
674 space_bounds: GridAab::for_block(R4),
675 };
676 let e = GenError::failure(set_cube_error.clone(), "x".into());
677 assert_eq!(
678 e.to_string(),
679 "An error occurred while generating object 'x'",
680 );
681 let source = Error::source(&e)
682 .expect("has source")
683 .downcast_ref::<InGenError>()
684 .expect("is InGenError");
685 assert_eq!(source.to_string(), set_cube_error.to_string());
686 }
687
688 #[test]
689 #[expect(clippy::try_err)]
690 fn gen_error_composition() {
691 fn a() -> Result<(), GenError> {
693 b().map_err(|e| GenError::failure(e, "x".into()))?;
694 Ok(())
695 }
696 fn b() -> Result<(), InGenError> {
697 Err(SetCubeError::OutOfBounds {
698 modification: GridAab::for_block(R1),
699 space_bounds: GridAab::for_block(R1),
700 })?;
701 Ok(())
702 }
703 let r = a();
704 assert!(
705 matches!(
706 r,
707 Err(GenError {
708 detail: InGenError::SetCube(_),
709 for_object: Some(Name::Specific(_)),
710 })
711 ),
712 "got error: {r:?}"
713 );
714 }
715}