1#![expect(clippy::elidable_lifetime_names, reason = "names for clarity")]
4
5use alloc::boxed::Box;
6
7use crate::behavior::BehaviorSet;
8use crate::block::{AIR, Block};
9use crate::character::Spawn;
10use crate::math::{FreePoint, Rgb, Vol};
11use crate::space::{
12 BlockIndex, GridAab, LightPhysics, PackedLight, Palette, PaletteError, SetCubeError, Sky,
13 Space, SpacePhysics,
14};
15use crate::universe::ReadTicket;
16
17#[derive(Clone, Debug)]
25#[must_use]
26pub struct Builder<'universe, B> {
27 pub(super) read_ticket: ReadTicket<'universe>,
28 pub(super) bounds: B,
29 pub(super) spawn: Option<Spawn>,
30 pub(super) physics: SpacePhysics,
31 pub(super) behaviors: BehaviorSet<Space>,
32 pub(super) contents: Fill,
33}
34
35#[derive(Clone, Debug)]
36pub(super) enum Fill {
37 Block(Block),
38 Data {
39 palette: Palette,
41 contents: Vol<Box<[BlockIndex]>>,
42 light: Option<Vol<Box<[PackedLight]>>>,
43 },
44}
45
46impl<'universe, B> Builder<'universe, B> {
47 pub fn read_ticket<'u2>(self, read_ticket: ReadTicket<'u2>) -> Builder<'u2, B> {
51 Builder {
52 read_ticket,
53 bounds: self.bounds,
54 spawn: self.spawn,
55 physics: self.physics,
56 behaviors: self.behaviors,
57 contents: self.contents,
58 }
59 }
60
61 pub fn filled_with(mut self, block: Block) -> Self {
66 self.contents = Fill::Block(block);
67 self
68 }
69
70 pub fn physics(mut self, physics: SpacePhysics) -> Self {
73 self.physics = physics;
74 self
75 }
76
77 pub fn sky(mut self, sky: Sky) -> Self {
79 self.physics.sky = sky;
80 self
81 }
82
83 pub fn sky_color(self, color: Rgb) -> Self {
85 self.sky(Sky::Uniform(color))
86 }
87
88 pub fn light_physics(mut self, light_physics: LightPhysics) -> Self {
91 self.physics.light = light_physics;
92 self
93 }
94
95 pub fn spawn(mut self, spawn: Spawn) -> Self {
101 self.spawn = Some(spawn);
102 self
103 }
104
105 #[allow(unused, reason = "currently only used on feature=save")]
107 pub(crate) fn behaviors(mut self, behaviors: BehaviorSet<Space>) -> Self {
108 self.behaviors = behaviors;
109 self
110 }
111}
112
113impl<'universe, B: Bounds> Builder<'universe, B> {
114 pub fn bounds_if_not_set(
116 self,
117 bounds_fn: impl FnOnce() -> GridAab,
118 ) -> Builder<'universe, Vol<()>> {
119 Bounds::bounds_if_not_set(self, bounds_fn)
121 }
122}
123
124impl<'universe> Builder<'universe, ()> {
125 #[track_caller] pub(super) fn new() -> Self {
128 Self {
129 read_ticket: ReadTicket::stub(),
130 bounds: (),
131 spawn: None,
132 physics: SpacePhysics::DEFAULT,
133 behaviors: BehaviorSet::new(),
134 contents: Fill::Block(AIR),
135 }
136 }
137
138 pub fn bounds(self, bounds: GridAab) -> Builder<'universe, Vol<()>> {
143 Builder {
144 read_ticket: self.read_ticket,
145 bounds: bounds.to_vol().unwrap(),
146 spawn: self.spawn,
147 physics: self.physics,
148 behaviors: self.behaviors,
149 contents: self.contents,
150 }
151 }
152}
153
154impl Builder<'_, Vol<()>> {
155 #[track_caller]
159 pub fn spawn_position(mut self, position: FreePoint) -> Self {
160 assert!(
161 position.to_vector().square_length().is_finite(),
162 "spawn_position must be finite"
163 );
164
165 let mut spawn =
166 self.spawn.unwrap_or_else(|| Spawn::default_for_new_space(self.bounds.bounds()));
167 spawn.set_eye_position(position);
168 self.spawn = Some(spawn);
169 self
170 }
171
172 pub fn palette_and_contents<P>(
190 self,
191 palette: P,
192 contents: Vol<Box<[BlockIndex]>>,
193 light: Option<Vol<Box<[PackedLight]>>>,
194 ) -> Result<Self, PaletteError>
195 where
196 P: IntoIterator<IntoIter: ExactSizeIterator<Item = Block>>,
197 {
198 let ticket = self.read_ticket;
199 self.palette_and_contents_impl(ticket, &mut palette.into_iter(), contents, light)
200 }
201
202 fn palette_and_contents_impl(
203 mut self,
204 read_ticket: ReadTicket<'_>,
205 palette: &mut dyn ExactSizeIterator<Item = Block>,
206 mut contents: Vol<Box<[BlockIndex]>>,
207 light: Option<Vol<Box<[PackedLight]>>>,
208 ) -> Result<Self, PaletteError> {
209 let (mut palette, remapping) = Palette::from_blocks(read_ticket, palette)?;
211
212 if contents.bounds() != self.bounds {
214 return Err(PaletteError::WrongDataBounds {
215 expected: self.bounds.bounds(),
216 actual: contents.bounds(),
217 });
218 }
219 if let Some(light) = light.as_ref()
220 && light.bounds() != self.bounds
221 {
222 return Err(PaletteError::WrongDataBounds {
223 expected: self.bounds.bounds(),
224 actual: light.bounds(),
225 });
226 }
227
228 let palette_len = palette.entries().len();
230 for (cube, contents_block_index) in contents.iter_mut() {
231 if let Some(&new_block_index) = remapping.get(contents_block_index) {
232 *contents_block_index = new_block_index;
234 } else if usize::from(*contents_block_index) >= palette_len {
235 return Err(PaletteError::Index {
237 index: *contents_block_index,
238 cube,
239 palette_len,
240 });
241 }
242
243 palette.increment(*contents_block_index);
244 }
245
246 palette.free_all_zero_counts();
247
248 self.contents = Fill::Data {
250 palette,
251 contents,
252 light,
253 };
254
255 Ok(self)
256 }
257
258 #[track_caller]
266 pub fn build(self) -> Space {
267 Space::new_from_builder(self).unwrap()
268 }
269
270 pub fn try_build(self) -> Result<Space, Error> {
277 Space::new_from_builder(self)
278 }
279
280 pub fn build_and_mutate(
293 self,
294 f: impl FnOnce(&mut super::Mutation<'_, '_>) -> Result<(), SetCubeError>,
295 ) -> Result<Space, Error> {
296 let read_ticket = self.read_ticket;
297 let mut space = self.try_build()?;
298 space.mutate(read_ticket, f).map_err(Error::Mutate)?;
299 Ok(space)
300 }
301}
302
303impl Default for Builder<'_, ()> {
304 #[track_caller] fn default() -> Self {
306 Self::new()
307 }
308}
309
310pub trait Bounds: sealed::Sealed + Sized {
312 #[doc(hidden)]
317 fn bounds_if_not_set<'u>(
318 builder: Builder<'u, Self>,
319 bounds_fn: impl FnOnce() -> GridAab,
320 ) -> Builder<'u, Vol<()>>;
321}
322
323impl Bounds for () {
324 fn bounds_if_not_set<'u>(
325 builder: Builder<'u, Self>,
326 bounds_fn: impl FnOnce() -> GridAab,
327 ) -> Builder<'u, Vol<()>> {
328 builder.bounds(bounds_fn())
329 }
330}
331
332impl Bounds for Vol<()> {
333 fn bounds_if_not_set<'u>(
334 builder: Builder<'u, Self>,
335 _bounds_fn: impl FnOnce() -> GridAab,
336 ) -> Builder<'u, Vol<()>> {
337 builder
338 }
339}
340
341mod sealed {
343 use super::*;
344 #[doc(hidden)]
345 #[expect(unnameable_types)]
346 pub trait Sealed {}
347 impl Sealed for () {}
348 impl Sealed for Vol<()> {}
349}
350
351#[cfg(feature = "arbitrary")]
352#[mutants::skip]
353impl<'a> arbitrary::Arbitrary<'a> for Space {
354 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
355 use crate::content::make_some_blocks;
356
357 let mut blocks = alloc::vec::Vec::from(make_some_blocks::<2>()); #[expect(clippy::same_item_push)]
362 for _ in 0..6 {
363 blocks.push(AIR);
365 }
366
367 let mut failure = None;
368
369 let bounds = Vol::<()>::arbitrary_with_max_volume(u, 2048)?;
370 let space = Space::builder(bounds.bounds()) .physics(u.arbitrary()?)
372 .spawn(u.arbitrary()?)
373 .build_and_mutate(|m| {
374 m.fill_all(|_| {
377 match u.choose(&blocks) {
378 Ok(block) => Some(block),
379 Err(e) => {
380 failure = Some(e);
382 None
383 }
384 }
385 })
386 })
387 .unwrap();
388
389 if let Some(e) = failure {
390 return Err(e);
391 }
392
393 Ok(space)
394 }
395}
396
397#[derive(Clone, Debug, displaydoc::Display)]
401#[non_exhaustive]
402pub enum Error {
403 #[non_exhaustive]
405 OutOfMemory {},
406
407 Mutate(SetCubeError),
409}
410
411impl core::error::Error for Error {
412 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
413 match self {
414 Error::OutOfMemory {} => None,
415 Error::Mutate(e) => Some(e),
416 }
417 }
418}
419
420#[cfg(test)]
423mod tests {
424 use crate::block;
425 use crate::content::make_some_blocks;
426 use crate::math::{Cube, Rgba};
427
428 use super::*;
429
430 #[test]
431 fn defaults() {
432 let bounds = GridAab::from_lower_size([1, 2, 3], [1, 1, 1]);
433 let space = Space::builder(bounds).build();
434 space.consistency_check();
435 assert_eq!(space.bounds(), bounds);
436 assert_eq!(space[bounds.lower_bounds()], AIR);
437 assert_eq!(space.physics(), &SpacePhysics::default());
438 assert_eq!(space.spawn(), &Spawn::default_for_new_space(bounds));
439 }
440
441 #[test]
442 fn filled_with() {
443 let bounds = GridAab::from_lower_size([1, 2, 3], [1, 1, 1]);
444 let block = block::from_color!(Rgba::WHITE);
445 let space = Space::builder(bounds).filled_with(block.clone()).build();
446 space.consistency_check();
447 assert_eq!(space[bounds.lower_bounds()], block);
448 }
449
450 #[test]
451 fn bounds_if_not_set_when_not_set() {
452 let bounds = GridAab::from_lower_size([1, 2, 3], [1, 1, 1]);
453 assert_eq!(
454 Builder::new().bounds_if_not_set(|| bounds).build().bounds(),
455 bounds
456 );
457 }
458
459 #[test]
460 fn bounds_if_not_set_when_already_set() {
461 let first_bounds = GridAab::from_lower_size([1, 2, 3], [1, 1, 1]);
462 let ignored_bounds = GridAab::from_lower_size([100, 2, 3], [1, 1, 1]);
463 assert_eq!(
464 Space::builder(first_bounds)
465 .bounds_if_not_set(|| ignored_bounds)
466 .build()
467 .bounds(),
468 first_bounds
469 );
470 }
471
472 #[test]
473 fn palette_err_too_long() {
474 let bounds = GridAab::ORIGIN_CUBE;
475 assert_eq!(
476 Space::builder(bounds)
477 .palette_and_contents(vec![AIR; 65537], Vol::from_element(2), None,)
478 .unwrap_err(),
479 PaletteError::PaletteTooLarge { len: 65537 }
480 );
481 }
482
483 #[test]
484 fn palette_err_too_short_for_contents() {
485 let bounds = GridAab::ORIGIN_CUBE;
486 assert_eq!(
487 Space::builder(bounds)
488 .palette_and_contents([AIR], Vol::from_element(2), None,)
489 .unwrap_err(),
490 PaletteError::Index {
491 index: 2,
492 cube: Cube::new(0, 0, 0),
493 palette_len: 1
494 }
495 );
496 }
497
498 #[test]
499 fn palette_err_contents_wrong_bounds() {
500 assert_eq!(
501 Space::builder(GridAab::single_cube(Cube::new(1, 0, 0)))
502 .palette_and_contents([AIR], Vol::from_element(0), None)
503 .unwrap_err(),
504 PaletteError::WrongDataBounds {
505 expected: GridAab::single_cube(Cube::new(1, 0, 0)),
506 actual: GridAab::ORIGIN_CUBE,
507 }
508 );
509 }
510
511 #[test]
516 fn palette_with_duplicate_entries() {
517 let bounds = GridAab::from_lower_size([0, 0, 0], [3, 1, 1]);
518 let [block0, block1] = make_some_blocks();
519 let space = Space::builder(bounds)
520 .palette_and_contents(
521 [block0.clone(), block1.clone(), block0.clone()],
522 Vol::from_elements(bounds, [0, 1, 2]).unwrap(),
523 None,
524 )
525 .unwrap()
526 .build();
527
528 space.consistency_check();
529
530 assert_eq!(space[[0, 0, 0]], block0);
533 assert_eq!(space[[1, 0, 0]], block1);
534 assert_eq!(space[[2, 0, 0]], block0);
535 }
536
537 #[test]
539 fn palette_with_unused_entries() {
540 let bounds = GridAab::from_lower_size([0, 0, 0], [2, 1, 1]);
541 let blocks = make_some_blocks::<3>();
542 let space = Space::builder(bounds)
543 .palette_and_contents(
544 blocks.clone(),
545 Vol::from_elements(bounds, [0, 2]).unwrap(),
546 None,
547 )
548 .unwrap()
549 .build();
550
551 space.consistency_check();
552
553 let found = space.block_data().iter().find(|entry| entry.block == blocks[1]);
555 assert!(found.is_none(), "{found:?}");
556 }
557
558 }