1use std::mem;
10use std::mem::MaybeUninit;
11use std::ptr;
12use std::ptr::NonNull;
13
14use futures::Future;
15use ndslice::view;
16use ndslice::view::Ranked;
17use ndslice::view::Region;
18
19#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ValueMesh<T> {
27 region: Region,
28 ranks: Vec<T>,
29}
30
31impl<T> ValueMesh<T> {
32 pub(crate) fn new(region: Region, ranks: Vec<T>) -> crate::v1::Result<Self> {
46 let (actual, expected) = (ranks.len(), region.num_ranks());
47 if actual != expected {
48 return Err(crate::v1::Error::InvalidRankCardinality { expected, actual });
49 }
50 Ok(Self { region, ranks })
51 }
52
53 #[inline]
56 pub(crate) fn new_unchecked(region: Region, ranks: Vec<T>) -> Self {
57 debug_assert_eq!(region.num_ranks(), ranks.len());
58 Self { region, ranks }
59 }
60}
61
62impl<F: Future> ValueMesh<F> {
63 pub async fn join(self) -> ValueMesh<F::Output> {
66 let ValueMesh { region, ranks } = self;
67 ValueMesh::new_unchecked(region, futures::future::join_all(ranks).await)
68 }
69}
70
71impl<T, E> ValueMesh<Result<T, E>> {
72 pub fn transpose(self) -> Result<ValueMesh<T>, E> {
75 let ValueMesh { region, ranks } = self;
76 let ranks = ranks.into_iter().collect::<Result<Vec<T>, E>>()?;
77 Ok(ValueMesh::new_unchecked(region, ranks))
78 }
79}
80
81impl<T: 'static> view::Ranked for ValueMesh<T> {
82 type Item = T;
83
84 fn region(&self) -> &Region {
85 &self.region
86 }
87
88 fn get(&self, rank: usize) -> Option<&Self::Item> {
89 self.ranks.get(rank)
90 }
91}
92
93impl<T: Clone + 'static> view::RankedSliceable for ValueMesh<T> {
94 fn sliced(&self, region: Region) -> Self {
95 debug_assert!(region.is_subset(self.region()), "sliced: not a subset");
96 let ranks: Vec<T> = self
97 .region()
98 .remap(®ion)
99 .unwrap()
100 .map(|index| self.get(index).unwrap().clone())
101 .collect();
102 debug_assert_eq!(
103 region.num_ranks(),
104 ranks.len(),
105 "sliced: cardinality mismatch"
106 );
107 Self::new_unchecked(region, ranks)
108 }
109}
110
111impl<T> view::BuildFromRegion<T> for ValueMesh<T> {
112 type Error = crate::v1::Error;
113
114 fn build_dense(region: Region, values: Vec<T>) -> Result<Self, Self::Error> {
115 Self::new(region, values)
116 }
117
118 fn build_dense_unchecked(region: Region, values: Vec<T>) -> Self {
119 Self::new_unchecked(region, values)
120 }
121}
122
123impl<T> view::BuildFromRegionIndexed<T> for ValueMesh<T> {
124 type Error = crate::v1::Error;
125
126 fn build_indexed(
127 region: Region,
128 pairs: impl IntoIterator<Item = (usize, T)>,
129 ) -> Result<Self, Self::Error> {
130 let n = region.num_ranks();
131
132 let mut buf: Vec<MaybeUninit<T>> = Vec::with_capacity(n);
138 unsafe {
144 buf.set_len(n);
145 }
146
147 let words = n.div_ceil(64);
149 let mut bits = vec![0u64; words];
150 let mut filled = 0usize;
151
152 struct DropGuard<T> {
158 buf: NonNull<MaybeUninit<T>>,
159 bits: NonNull<u64>,
160 n_elems: usize,
161 n_words: usize,
162 disarm: bool,
163 }
164
165 impl<T> DropGuard<T> {
166 unsafe fn new(buf: &mut [MaybeUninit<T>], bits: &mut [u64]) -> Self {
185 let n_elems = buf.len();
186 let n_words = bits.len();
187 Self {
190 buf: NonNull::new(buf.as_mut_ptr()).unwrap_or_else(NonNull::dangling),
191 bits: NonNull::new(bits.as_mut_ptr()).unwrap_or_else(NonNull::dangling),
192 n_elems,
193 n_words,
194 disarm: false,
195 }
196 }
197
198 #[inline]
199 fn disarm(&mut self) {
200 self.disarm = true;
201 }
202 }
203
204 impl<T> Drop for DropGuard<T> {
205 fn drop(&mut self) {
206 if self.disarm {
207 return;
208 }
209
210 unsafe {
228 let buf_base = self.buf.as_ptr();
229 let bits_base = self.bits.as_ptr();
230
231 for w in 0..self.n_words {
232 let mut word = *bits_base.add(w);
234
235 if w == self.n_words.saturating_sub(1) {
238 let used_bits = self.n_elems.saturating_sub(w * 64);
239 if used_bits < 64 {
240 let mask = if used_bits == 0 {
241 0
242 } else {
243 (1u64 << used_bits) - 1
244 };
245 word &= mask;
246 }
247 }
248
249 while word != 0 {
251 let tz = word.trailing_zeros() as usize;
252 let i = w * 64 + tz;
253 debug_assert!(i < self.n_elems);
254
255 let slot = buf_base.add(i);
256 ptr::drop_in_place((*slot).as_mut_ptr());
258
259 word &= word - 1;
261 }
262 }
263 }
264 }
265 }
266
267 let mut guard = unsafe { DropGuard::new(&mut buf, &mut bits) };
276
277 for (rank, value) in pairs {
278 if rank >= guard.n_elems {
280 return Err(crate::v1::Error::InvalidRankCardinality {
281 expected: guard.n_elems,
282 actual: rank + 1,
283 });
284 }
285
286 let w = rank / 64;
288 let b = rank % 64;
289 let mask = 1u64 << b;
290
291 unsafe {
310 let bits_ptr = guard.bits.as_ptr().add(w);
312 let buf_slot = guard.buf.as_ptr().add(rank);
313
314 let word = *bits_ptr;
316
317 if (word & mask) != 0 {
318 core::ptr::drop_in_place((*buf_slot).as_mut_ptr());
320 } else {
323 *bits_ptr = word | mask;
325 filled += 1;
326 }
327
328 (*buf_slot).write(value);
330 }
331 }
332
333 if filled != n {
334 return Err(crate::v1::Error::InvalidRankCardinality {
336 expected: n,
337 actual: filled,
338 });
339 }
340
341 guard.disarm();
343
344 let ranks = unsafe {
346 let ptr = buf.as_mut_ptr() as *mut T;
347 let len = buf.len();
348 let cap = buf.capacity();
349 mem::forget(buf);
354 Vec::from_raw_parts(ptr, len, cap)
355 };
356
357 Ok(Self::new_unchecked(region, ranks))
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use std::convert::Infallible;
364 use std::future::Future;
365 use std::pin::Pin;
366 use std::task::Context;
367 use std::task::Poll;
368 use std::task::RawWaker;
369 use std::task::RawWakerVTable;
370 use std::task::Waker;
371
372 use futures::executor::block_on;
373 use futures::future;
374 use ndslice::extent;
375 use ndslice::strategy::gen_region;
376 use ndslice::view::CollectExactMeshExt;
377 use ndslice::view::CollectIndexedMeshExt;
378 use ndslice::view::CollectMeshExt;
379 use ndslice::view::MapIntoExt;
380 use ndslice::view::Ranked;
381 use ndslice::view::ViewExt;
382 use proptest::prelude::*;
383 use proptest::strategy::ValueTree;
384
385 use super::*;
386
387 #[test]
388 fn value_mesh_new_ok() {
389 let region: Region = extent!(replica = 2, gpu = 3).into();
390 let mesh = ValueMesh::new(region.clone(), (0..6).collect()).expect("new should succeed");
391 assert_eq!(mesh.region().num_ranks(), 6);
392 assert_eq!(mesh.values().count(), 6);
393 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 1, 2, 3, 4, 5]);
394 }
395
396 #[test]
397 fn value_mesh_new_len_mismatch_is_error() {
398 let region: Region = extent!(replica = 2, gpu = 3).into();
399 let err = ValueMesh::new(region, vec![0_i32; 5]).unwrap_err();
400 match err {
401 crate::v1::Error::InvalidRankCardinality { expected, actual } => {
402 assert_eq!(expected, 6);
403 assert_eq!(actual, 5);
404 }
405 other => panic!("unexpected error: {other:?}"),
406 }
407 }
408
409 #[test]
410 fn value_mesh_transpose_ok_and_err() {
411 let region: Region = extent!(x = 2).into();
412
413 let ok_mesh = ValueMesh::new(region.clone(), vec![Ok::<_, Infallible>(1), Ok(2)]).unwrap();
415 let ok = ok_mesh.transpose().unwrap();
416 assert_eq!(ok.values().collect::<Vec<_>>(), vec![1, 2]);
417
418 #[derive(Debug, PartialEq)]
420 enum E {
421 Boom,
422 }
423 let err_mesh = ValueMesh::new(region, vec![Ok(1), Err(E::Boom)]).unwrap();
424 let err = err_mesh.transpose().unwrap_err();
425 assert_eq!(err, E::Boom);
426 }
427
428 #[test]
429 fn value_mesh_join_preserves_region_and_values() {
430 let region: Region = extent!(x = 2, y = 2).into();
431 let futs = vec![
432 future::ready(10),
433 future::ready(11),
434 future::ready(12),
435 future::ready(13),
436 ];
437 let mesh = ValueMesh::new(region.clone(), futs).unwrap();
438
439 let joined = block_on(mesh.join());
440 assert_eq!(joined.region().num_ranks(), 4);
441 assert_eq!(joined.values().collect::<Vec<_>>(), vec![10, 11, 12, 13]);
442 }
443
444 #[test]
445 fn collect_mesh_ok() {
446 let region: Region = extent!(x = 2, y = 3).into();
447 let mesh = (0..6)
448 .collect_mesh::<ValueMesh<_>>(region.clone())
449 .expect("collect_mesh should succeed");
450
451 assert_eq!(mesh.region().num_ranks(), 6);
452 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 1, 2, 3, 4, 5]);
453 }
454
455 #[test]
456 fn collect_mesh_len_too_short_is_error() {
457 let region: Region = extent!(x = 2, y = 3).into();
458 let err = (0..5).collect_mesh::<ValueMesh<_>>(region).unwrap_err();
459
460 match err {
461 crate::v1::Error::InvalidRankCardinality { expected, actual } => {
462 assert_eq!(expected, 6);
463 assert_eq!(actual, 5);
464 }
465 other => panic!("unexpected error: {other:?}"),
466 }
467 }
468
469 #[test]
470 fn collect_mesh_len_too_long_is_error() {
471 let region: Region = extent!(x = 2, y = 3).into();
472 let err = (0..7).collect_mesh::<ValueMesh<_>>(region).unwrap_err();
473 match err {
474 crate::v1::Error::InvalidRankCardinality { expected, actual } => {
475 assert_eq!(expected, 6);
476 assert_eq!(actual, 7);
477 }
478 other => panic!("unexpected error: {other:?}"),
479 }
480 }
481
482 #[test]
483 fn collect_mesh_from_map_pipeline() {
484 let region: Region = extent!(x = 2, y = 2).into();
485 let mesh = (0..4)
486 .map(|i| i * 10)
487 .collect_mesh::<ValueMesh<_>>(region.clone())
488 .unwrap();
489
490 assert_eq!(mesh.region().num_ranks(), 4);
491 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 10, 20, 30]);
492 }
493
494 #[test]
495 fn collect_exact_mesh_ok() {
496 let region: Region = extent!(x = 2, y = 3).into();
497 let mesh = (0..6)
498 .collect_exact_mesh::<ValueMesh<_>>(region.clone())
499 .expect("collect_exact_mesh should succeed");
500
501 assert_eq!(mesh.region().num_ranks(), 6);
502 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 1, 2, 3, 4, 5]);
503 }
504
505 #[test]
506 fn collect_exact_mesh_len_too_short_is_error() {
507 let region: Region = extent!(x = 2, y = 3).into();
508 let err = (0..5)
509 .collect_exact_mesh::<ValueMesh<_>>(region)
510 .unwrap_err();
511
512 match err {
513 crate::v1::Error::InvalidRankCardinality { expected, actual } => {
514 assert_eq!(expected, 6);
515 assert_eq!(actual, 5);
516 }
517 other => panic!("unexpected error: {other:?}"),
518 }
519 }
520
521 #[test]
522 fn collect_exact_mesh_len_too_long_is_error() {
523 let region: Region = extent!(x = 2, y = 3).into();
524 let err = (0..7)
525 .collect_exact_mesh::<ValueMesh<_>>(region)
526 .unwrap_err();
527
528 match err {
529 crate::v1::Error::InvalidRankCardinality { expected, actual } => {
530 assert_eq!(expected, 6);
531 assert_eq!(actual, 7);
532 }
533 other => panic!("unexpected error: {other:?}"),
534 }
535 }
536
537 #[test]
538 fn collect_exact_mesh_from_map_pipeline() {
539 let region: Region = extent!(x = 2, y = 2).into();
540 let mesh = (0..4)
541 .map(|i| i * 10)
542 .collect_exact_mesh::<ValueMesh<_>>(region.clone())
543 .unwrap();
544
545 assert_eq!(mesh.region().num_ranks(), 4);
546 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![0, 10, 20, 30]);
547 }
548
549 #[test]
550 fn collect_indexed_ok_shuffled() {
551 let region: Region = extent!(x = 2, y = 3).into();
552 let pairs = vec![(3, 30), (0, 0), (5, 50), (2, 20), (1, 10), (4, 40)];
554 let mesh = pairs
555 .into_iter()
556 .collect_indexed::<ValueMesh<_>>(region.clone())
557 .unwrap();
558
559 assert_eq!(mesh.region().num_ranks(), 6);
560 assert_eq!(
561 mesh.values().collect::<Vec<_>>(),
562 vec![0, 10, 20, 30, 40, 50]
563 );
564 }
565
566 #[test]
567 fn collect_indexed_missing_rank_is_error() {
568 let region: Region = extent!(x = 2, y = 2).into(); let pairs = vec![(0, 100), (1, 101), (2, 102)];
571 let err = pairs
572 .into_iter()
573 .collect_indexed::<ValueMesh<_>>(region)
574 .unwrap_err();
575
576 match err {
577 crate::v1::Error::InvalidRankCardinality { expected, actual } => {
578 assert_eq!(expected, 4);
579 assert_eq!(actual, 3); }
581 other => panic!("unexpected error: {other:?}"),
582 }
583 }
584
585 #[test]
586 fn collect_indexed_out_of_bounds_is_error() {
587 let region: Region = extent!(x = 2, y = 2).into(); let pairs = vec![(0, 1), (4, 9)]; let err = pairs
590 .into_iter()
591 .collect_indexed::<ValueMesh<_>>(region)
592 .unwrap_err();
593
594 match err {
595 crate::v1::Error::InvalidRankCardinality { expected, actual } => {
596 assert_eq!(expected, 4);
597 assert_eq!(actual, 5); }
599 other => panic!("unexpected error: {other:?}"),
600 }
601 }
602
603 #[test]
604 fn collect_indexed_duplicate_last_write_wins() {
605 let region: Region = extent!(x = 1, y = 3).into(); let pairs = vec![(0, 7), (1, 8), (1, 88), (2, 9)];
608 let mesh = pairs
609 .into_iter()
610 .collect_indexed::<ValueMesh<_>>(region.clone())
611 .unwrap();
612
613 assert_eq!(mesh.values().collect::<Vec<_>>(), vec![7, 88, 9]);
614 }
615
616 fn build_value_mesh_indexed<T>(
618 region: Region,
619 pairs: impl IntoIterator<Item = (usize, T)>,
620 ) -> crate::v1::Result<ValueMesh<T>> {
621 let n = region.num_ranks();
622
623 let mut buf: Vec<Option<T>> = std::iter::repeat_with(|| None).take(n).collect();
625 let mut filled = 0usize;
626
627 for (rank, value) in pairs {
628 if rank >= n {
629 return Err(crate::v1::Error::InvalidRankCardinality {
633 expected: n,
634 actual: rank + 1,
635 });
636 }
637 if buf[rank].is_none() {
638 filled += 1;
639 }
640 buf[rank] = Some(value); }
642
643 if filled != n {
644 return Err(crate::v1::Error::InvalidRankCardinality {
646 expected: n,
647 actual: filled,
648 });
649 }
650
651 let ranks: Vec<T> = buf.into_iter().map(Option::unwrap).collect();
653 Ok(ValueMesh::new_unchecked(region, ranks))
654 }
655
656 fn hash_key(x: usize) -> u64 {
664 let mut z = x as u64 ^ 0x9E3779B97F4A7C15;
665 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
666 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
667 z ^ (z >> 31)
668 }
669
670 fn pseudo_shuffle<'a, T: 'a>(v: &'a mut [T], key: impl Fn(usize) -> u64 + Copy) {
687 let mut with_keys: Vec<(u64, usize)> = (0..v.len()).map(|i| (key(i), i)).collect();
689 with_keys.sort_by_key(|&(k, _)| k);
690 let perm: Vec<usize> = with_keys.into_iter().map(|(_, i)| i).collect();
691
692 let mut seen = vec![false; v.len()];
695 for i in 0..v.len() {
696 if seen[i] {
697 continue;
698 }
699 let mut a = i;
700 while !seen[a] {
701 seen[a] = true;
702 let b = perm[a];
703 if b == i {
705 break;
706 }
707 v.swap(a, b);
708 a = b;
709 }
710 }
711 }
712
713 proptest! {
729 #[test]
730 fn try_collect_opt_equivalence(region in gen_region(1..=4, 6), extra_len in 0usize..=12) {
731 let n = region.num_ranks();
732
733 let mut pairs: Vec<(usize, i64)> = (0..n).map(|r| (r, r as i64)).collect();
735
736 let extras = proptest::collection::vec(0..n, extra_len)
739 .new_tree(&mut proptest::test_runner::TestRunner::default())
740 .unwrap()
741 .current();
742 for (k, r) in extras.into_iter().enumerate() {
743 pairs.push((r, (n as i64) + (k as i64)));
744 }
745
746 pseudo_shuffle(&mut pairs, hash_key);
749
750 let mesh_ref = build_value_mesh_indexed(region.clone(), pairs.clone()).unwrap();
752 let mesh_opt = pairs.into_iter().collect_indexed::<ValueMesh<_>>(region.clone()).unwrap();
753
754 prop_assert_eq!(mesh_ref.region(), mesh_opt.region());
755 prop_assert_eq!(mesh_ref.values().collect::<Vec<_>>(), mesh_opt.values().collect::<Vec<_>>());
756 }
757 }
758
759 proptest! {
770 #[test]
771 fn try_collect_opt_missing_rank_errors_match(region in gen_region(1..=4, 6)) {
772 let n = region.num_ranks();
773 let mut pairs: Vec<(usize, i64)> = (0..n).map(|r| (r, r as i64)).collect();
775 if n > 0 {
777 let drop_idx = 0usize; pairs.remove(drop_idx);
779 }
780 pseudo_shuffle(&mut pairs, hash_key);
782
783 let ref_err = build_value_mesh_indexed(region.clone(), pairs.clone()).unwrap_err();
784 let opt_err = pairs.into_iter().collect_indexed::<ValueMesh<_>>(region).unwrap_err();
785 assert_eq!(format!("{ref_err:?}"), format!("{opt_err:?}"));
786 }
787 }
788
789 proptest! {
801 #[test]
802 fn try_collect_opt_out_of_bound_errors_match(region in gen_region(1..=4, 6)) {
803 let n = region.num_ranks();
804 let mut pairs = vec![(0usize, 0i64), (n, 123i64)];
806 pseudo_shuffle(&mut pairs, hash_key);
807
808 let ref_err = build_value_mesh_indexed(region.clone(), pairs.clone()).unwrap_err();
809 let opt_err = pairs.into_iter().collect_indexed::<ValueMesh<_>>(region).unwrap_err();
810 assert_eq!(format!("{ref_err:?}"), format!("{opt_err:?}"));
811 }
812 }
813
814 #[test]
815 fn map_into_preserves_region_and_order() {
816 let region: Region = extent!(rows = 2, cols = 3).into();
817 let vm = ValueMesh::new_unchecked(region.clone(), vec![0, 1, 2, 3, 4, 5]);
818
819 let doubled: ValueMesh<_> = vm.map_into(|x| x * 2);
820 assert_eq!(doubled.region, region);
821 assert_eq!(doubled.ranks, vec![0, 2, 4, 6, 8, 10]);
822 }
823
824 #[test]
825 fn map_into_ref_borrows_and_preserves() {
826 let region: Region = extent!(n = 4).into();
827 let vm = ValueMesh::new_unchecked(
828 region.clone(),
829 vec!["a".to_string(), "b".into(), "c".into(), "d".into()],
830 );
831
832 let lens: ValueMesh<_> = vm.map_into(|s| s.len());
833 assert_eq!(lens.region, region);
834 assert_eq!(lens.ranks, vec![1, 1, 1, 1]);
835 }
836
837 #[test]
838 fn try_map_into_short_circuits_on_error() {
839 let region = extent!(n = 4).into();
840 let vm = ValueMesh::new_unchecked(region, vec![1, 2, 3, 4]);
841
842 let res: Result<ValueMesh<i32>, &'static str> =
843 vm.try_map_into(|x| if x == &3 { Err("boom") } else { Ok(x + 10) });
844
845 assert!(res.is_err());
846 assert_eq!(res.unwrap_err(), "boom");
847 }
848
849 #[test]
850 fn try_map_into_ref_short_circuits_on_error() {
851 let region = extent!(n = 4).into();
852 let vm = ValueMesh::new_unchecked(region, vec![1, 2, 3, 4]);
853
854 let res: Result<ValueMesh<i32>, &'static str> =
855 vm.try_map_into(|x| if x == &3 { Err("boom") } else { Ok(x + 10) });
856
857 assert!(res.is_err());
858 assert_eq!(res.unwrap_err(), "boom");
859 }
860
861 fn noop_waker() -> Waker {
863 fn clone(_: *const ()) -> RawWaker {
864 RawWaker::new(std::ptr::null(), &VTABLE)
865 }
866 fn wake(_: *const ()) {}
867 fn wake_by_ref(_: *const ()) {}
868 fn drop(_: *const ()) {}
869 static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
870 unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }
875 }
876
877 fn poll_now<F: Future>(mut fut: F) -> F::Output {
878 let waker = noop_waker();
879 let mut cx = Context::from_waker(&waker);
880 let mut fut = unsafe { Pin::new_unchecked(&mut fut) };
885 match fut.as_mut().poll(&mut cx) {
886 Poll::Ready(v) => v,
887 Poll::Pending => unreachable!("Ready futures must complete immediately"),
888 }
889 }
890 #[test]
893 fn map_into_ready_futures() {
894 let region: Region = extent!(r = 2, c = 2).into();
895 let vm = ValueMesh::new_unchecked(region.clone(), vec![10, 20, 30, 40]);
896
897 let pending: ValueMesh<core::future::Ready<_>> =
899 vm.map_into(|x| core::future::ready(x + 1));
900 assert_eq!(pending.region, region);
901
902 let results: Vec<_> = pending.ranks.into_iter().map(poll_now).collect();
904 assert_eq!(results, vec![11, 21, 31, 41]);
905 }
906
907 #[test]
908 fn map_into_single_element_mesh() {
909 let region: Region = extent!(n = 1).into();
910 let vm = ValueMesh::new_unchecked(region.clone(), vec![7]);
911
912 let out: ValueMesh<_> = vm.map_into(|x| x * x);
913 assert_eq!(out.region, region);
914 assert_eq!(out.ranks, vec![49]);
915 }
916
917 #[test]
918 fn map_into_ref_with_non_clone_field() {
919 #[derive(Debug, PartialEq, Eq)]
921 struct NotClone(i32);
922
923 let region: Region = extent!(x = 3).into();
924 let values = vec![(10, NotClone(1)), (20, NotClone(2)), (30, NotClone(3))];
925 let mesh: ValueMesh<(i32, NotClone)> =
926 values.into_iter().collect_mesh(region.clone()).unwrap();
927
928 let projected: ValueMesh<i32> = mesh.map_into(|t| t.0);
929 assert_eq!(projected.values().collect::<Vec<_>>(), vec![10, 20, 30]);
930 assert_eq!(projected.region(), ®ion);
931 }
932}