From a6ac8faa8a06fc972ada26c884512cce854f9662 Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Thu, 22 Oct 2020 11:53:59 -0700 Subject: [PATCH] port upstream hecs performance improvements (#716) --- crates/bevy_ecs/hecs/src/archetype.rs | 52 ++++++- crates/bevy_ecs/hecs/src/query.rs | 188 ++++++++++++++------------ crates/bevy_ecs/hecs/src/query_one.rs | 12 +- crates/bevy_ecs/src/system/query.rs | 97 +++++-------- 4 files changed, 193 insertions(+), 156 deletions(-) diff --git a/crates/bevy_ecs/hecs/src/archetype.rs b/crates/bevy_ecs/hecs/src/archetype.rs index 6e2847aef1..825e1d8749 100644 --- a/crates/bevy_ecs/hecs/src/archetype.rs +++ b/crates/bevy_ecs/hecs/src/archetype.rs @@ -21,13 +21,15 @@ use crate::{ }, Entity, }; -use bevy_utils::{HashMap, HashMapExt}; +use bevy_utils::AHasher; use core::{ any::{type_name, TypeId}, cell::UnsafeCell, + hash::{BuildHasherDefault, Hasher}, mem, ptr::{self, NonNull}, }; +use std::collections::HashMap; use crate::{borrow::AtomicBorrow, query::Fetch, Access, Component, Query}; @@ -38,7 +40,7 @@ use crate::{borrow::AtomicBorrow, query::Fetch, Access, Component, Query}; #[derive(Debug)] pub struct Archetype { types: Vec, - state: HashMap, + state: TypeIdMap, len: usize, entities: Vec, // UnsafeCell allows unique references into `data` to be constructed while shared references @@ -60,7 +62,7 @@ impl Archetype { types.windows(2).all(|x| x[0] < x[1]), "type info unsorted or contains duplicates" ); - let mut state = HashMap::with_capacity(types.len()); + let mut state = HashMap::with_capacity_and_hasher(types.len(), Default::default()); for ty in &types { state.insert(ty.id, TypeState::new()); } @@ -549,3 +551,47 @@ fn align(x: usize, alignment: usize) -> usize { debug_assert!(alignment.is_power_of_two()); (x + alignment - 1) & (!alignment + 1) } + +/// A hasher optimized for hashing a single TypeId. +/// +/// TypeId is already thoroughly hashed, so there's no reason to hash it again. +/// Just leave the bits unchanged. +#[derive(Default)] +pub(crate) struct TypeIdHasher { + hash: u64, +} + +impl Hasher for TypeIdHasher { + fn write_u64(&mut self, n: u64) { + // Only a single value can be hashed, so the old hash should be zero. + debug_assert_eq!(self.hash, 0); + self.hash = n; + } + + // Tolerate TypeId being either u64 or u128. + fn write_u128(&mut self, n: u128) { + debug_assert_eq!(self.hash, 0); + self.hash = n as u64; + } + + fn write(&mut self, bytes: &[u8]) { + debug_assert_eq!(self.hash, 0); + + // This will only be called if TypeId is neither u64 nor u128, which is not anticipated. + // In that case we'll just fall back to using a different hash implementation. + let mut hasher = AHasher::default(); + hasher.write(bytes); + self.hash = hasher.finish(); + } + + fn finish(&self) -> u64 { + self.hash + } +} + +/// A HashMap with TypeId keys +/// +/// Because TypeId is already a fully-hashed u64 (including data in the high seven bits, +/// which hashbrown needs), there is no need to hash it again. Instead, this uses the much +/// faster no-op hash. +pub(crate) type TypeIdMap = HashMap>; diff --git a/crates/bevy_ecs/hecs/src/query.rs b/crates/bevy_ecs/hecs/src/query.rs index 62c17c5484..cadaec192e 100644 --- a/crates/bevy_ecs/hecs/src/query.rs +++ b/crates/bevy_ecs/hecs/src/query.rs @@ -36,6 +36,10 @@ pub trait Fetch<'a>: Sized { /// Type of value to be fetched type Item; + /// A value on which `get` may never be called + #[allow(clippy::declare_interior_mutable_const)] // no const fn in traits + const DANGLING: Self; + /// How this query will access `archetype`, if at all fn access(archetype: &Archetype) -> Option; @@ -49,22 +53,22 @@ pub trait Fetch<'a>: Sized { /// Release dynamic borrows acquired by `borrow` fn release(archetype: &Archetype); - /// if this returns true, the current item will be skipped during iteration + /// if this returns true, the nth item should be skipped during iteration /// /// # Safety /// shouldn't be called if there is no current item - unsafe fn should_skip(&self) -> bool { + unsafe fn should_skip(&self, _n: usize) -> bool { false } - /// Access the next item in this archetype without bounds checking + /// Access the `n`th item in this archetype without bounds checking /// /// # Safety /// - Must only be called after `borrow` /// - `release` must not be called while `'a` is still live /// - Bounds-checking must be performed externally /// - Any resulting borrows must be legal (e.g. no &mut to something another iterator might access) - unsafe fn next(&mut self) -> Self::Item; + unsafe fn fetch(&self, n: usize) -> Self::Item; } /// Type of access a `Query` may have to an `Archetype` @@ -89,6 +93,8 @@ impl Query for Entity { impl<'a> Fetch<'a> for EntityFetch { type Item = Entity; + const DANGLING: Self = Self(NonNull::dangling()); + #[inline] fn access(_archetype: &Archetype) -> Option { Some(Access::Iterate) @@ -108,10 +114,8 @@ impl<'a> Fetch<'a> for EntityFetch { fn release(_archetype: &Archetype) {} #[inline] - unsafe fn next(&mut self) -> Self::Item { - let id = self.0.as_ptr(); - self.0 = NonNull::new_unchecked(id.add(1)); - *id + unsafe fn fetch(&self, n: usize) -> Self::Item { + *self.0.as_ptr().add(n) } } @@ -127,6 +131,8 @@ unsafe impl ReadOnlyFetch for FetchRead {} impl<'a, T: Component> Fetch<'a> for FetchRead { type Item = &'a T; + const DANGLING: Self = Self(NonNull::dangling()); + fn access(archetype: &Archetype) -> Option { if archetype.has::() { Some(Access::Read) @@ -150,10 +156,8 @@ impl<'a, T: Component> Fetch<'a> for FetchRead { } #[inline] - unsafe fn next(&mut self) -> &'a T { - let x = self.0.as_ptr(); - self.0 = NonNull::new_unchecked(x.add(1)); - &*x + unsafe fn fetch(&self, n: usize) -> &'a T { + &*self.0.as_ptr().add(n) } } @@ -222,6 +226,8 @@ pub struct FetchMut(NonNull, NonNull); impl<'a, T: Component> Fetch<'a> for FetchMut { type Item = Mut<'a, T>; + const DANGLING: Self = Self(NonNull::dangling(), NonNull::dangling()); + fn access(archetype: &Archetype) -> Option { if archetype.has::() { Some(Access::Write) @@ -250,14 +256,10 @@ impl<'a, T: Component> Fetch<'a> for FetchMut { } #[inline] - unsafe fn next(&mut self) -> Mut<'a, T> { - let component = self.0.as_ptr(); - let mutated = self.1.as_ptr(); - self.0 = NonNull::new_unchecked(component.add(1)); - self.1 = NonNull::new_unchecked(mutated.add(1)); + unsafe fn fetch(&self, n: usize) -> Mut<'a, T> { Mut { - value: &mut *component, - mutated: &mut *mutated, + value: &mut *self.0.as_ptr().add(n), + mutated: &mut *self.1.as_ptr().add(n), } } } @@ -271,6 +273,8 @@ macro_rules! impl_or_query { impl<'a, $( $T: Fetch<'a> ),+> Fetch<'a> for FetchOr<($( $T ),+)> { type Item = ($( $T::Item ),+); + const DANGLING: Self = Self(($( $T::DANGLING ),+)); + fn access(archetype: &Archetype) -> Option { let mut max_access = None; $( @@ -296,15 +300,15 @@ macro_rules! impl_or_query { } #[allow(non_snake_case)] - unsafe fn next(&mut self) -> Self::Item { - let ($( $T ),+) = &mut self.0; - ($( $T.next() ),+) + unsafe fn fetch(&self, n: usize) -> Self::Item { + let ($( $T ),+) = &self.0; + ($( $T.fetch(n) ),+) } #[allow(non_snake_case)] - unsafe fn should_skip(&self) -> bool { + unsafe fn should_skip(&self, n: usize) -> bool { let ($( $T ),+) = &self.0; - true $( && $T.should_skip() )+ + true $( && $T.should_skip(n) )+ } } }; @@ -369,6 +373,8 @@ pub struct FetchMutated(NonNull, NonNull); impl<'a, T: Component> Fetch<'a> for FetchMutated { type Item = Mutated<'a, T>; + const DANGLING: Self = Self(NonNull::dangling(), NonNull::dangling()); + fn access(archetype: &Archetype) -> Option { if archetype.has::() { Some(Access::Read) @@ -396,17 +402,16 @@ impl<'a, T: Component> Fetch<'a> for FetchMutated { archetype.release::(); } - unsafe fn should_skip(&self) -> bool { + unsafe fn should_skip(&self, n: usize) -> bool { // skip if the current item wasn't mutated - !*self.1.as_ref() + !*self.1.as_ptr().add(n) } #[inline] - unsafe fn next(&mut self) -> Self::Item { - self.1 = NonNull::new_unchecked(self.1.as_ptr().add(1)); - let value = self.0.as_ptr(); - self.0 = NonNull::new_unchecked(value.add(1)); - Mutated { value: &*value } + unsafe fn fetch(&self, n: usize) -> Self::Item { + Mutated { + value: &*self.0.as_ptr().add(n), + } } } @@ -435,6 +440,8 @@ unsafe impl ReadOnlyFetch for FetchAdded {} impl<'a, T: Component> Fetch<'a> for FetchAdded { type Item = Added<'a, T>; + const DANGLING: Self = Self(NonNull::dangling(), NonNull::dangling()); + fn access(archetype: &Archetype) -> Option { if archetype.has::() { Some(Access::Read) @@ -462,17 +469,16 @@ impl<'a, T: Component> Fetch<'a> for FetchAdded { archetype.release::(); } - unsafe fn should_skip(&self) -> bool { + unsafe fn should_skip(&self, n: usize) -> bool { // skip if the current item wasn't added - !*self.1.as_ref() + !*self.1.as_ptr().add(n) } #[inline] - unsafe fn next(&mut self) -> Self::Item { - self.1 = NonNull::new_unchecked(self.1.as_ptr().add(1)); - let value = self.0.as_ptr(); - self.0 = NonNull::new_unchecked(value.add(1)); - Added { value: &*value } + unsafe fn fetch(&self, n: usize) -> Self::Item { + Added { + value: &*self.0.as_ptr().add(n), + } } } @@ -501,6 +507,12 @@ unsafe impl ReadOnlyFetch for FetchChanged {} impl<'a, T: Component> Fetch<'a> for FetchChanged { type Item = Changed<'a, T>; + const DANGLING: Self = Self( + NonNull::dangling(), + NonNull::dangling(), + NonNull::dangling(), + ); + fn access(archetype: &Archetype) -> Option { if archetype.has::() { Some(Access::Read) @@ -529,18 +541,16 @@ impl<'a, T: Component> Fetch<'a> for FetchChanged { archetype.release::(); } - unsafe fn should_skip(&self) -> bool { + unsafe fn should_skip(&self, n: usize) -> bool { // skip if the current item wasn't added or mutated - !*self.1.as_ref() && !self.2.as_ref() + !*self.1.as_ptr().add(n) && !*self.2.as_ptr().add(n) } #[inline] - unsafe fn next(&mut self) -> Self::Item { - self.1 = NonNull::new_unchecked(self.1.as_ptr().add(1)); - self.2 = NonNull::new_unchecked(self.2.as_ptr().add(1)); - let value = self.0.as_ptr(); - self.0 = NonNull::new_unchecked(value.add(1)); - Changed { value: &*value } + unsafe fn fetch(&self, n: usize) -> Self::Item { + Changed { + value: &*self.0.as_ptr().add(n), + } } } @@ -551,6 +561,8 @@ unsafe impl ReadOnlyFetch for TryFetch where T: ReadOnlyFetch {} impl<'a, T: Fetch<'a>> Fetch<'a> for TryFetch { type Item = Option; + const DANGLING: Self = Self(None); + fn access(archetype: &Archetype) -> Option { Some(T::access(archetype).unwrap_or(Access::Iterate)) } @@ -567,12 +579,12 @@ impl<'a, T: Fetch<'a>> Fetch<'a> for TryFetch { T::release(archetype) } - unsafe fn next(&mut self) -> Option { - Some(self.0.as_mut()?.next()) + unsafe fn fetch(&self, n: usize) -> Option { + Some(self.0.as_ref()?.fetch(n)) } - unsafe fn should_skip(&self) -> bool { - self.0.as_ref().map_or(false, |fetch| fetch.should_skip()) + unsafe fn should_skip(&self, n: usize) -> bool { + self.0.as_ref().map_or(false, |fetch| fetch.should_skip(n)) } } @@ -609,6 +621,8 @@ unsafe impl<'a, T: Component, F: Fetch<'a>> ReadOnlyFetch for FetchWithout impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchWithout { type Item = F::Item; + const DANGLING: Self = Self(F::DANGLING, PhantomData); + fn access(archetype: &Archetype) -> Option { if archetype.has::() { None @@ -632,12 +646,12 @@ impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchWithout { F::release(archetype) } - unsafe fn next(&mut self) -> F::Item { - self.0.next() + unsafe fn fetch(&self, n: usize) -> F::Item { + self.0.fetch(n) } - unsafe fn should_skip(&self) -> bool { - self.0.should_skip() + unsafe fn should_skip(&self, n: usize) -> bool { + self.0.should_skip(n) } } @@ -673,6 +687,8 @@ unsafe impl<'a, T: Component, F: Fetch<'a>> ReadOnlyFetch for FetchWith wh impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchWith { type Item = F::Item; + const DANGLING: Self = Self(F::DANGLING, PhantomData); + fn access(archetype: &Archetype) -> Option { if archetype.has::() { F::access(archetype) @@ -696,12 +712,12 @@ impl<'a, T: Component, F: Fetch<'a>> Fetch<'a> for FetchWith { F::release(archetype) } - unsafe fn next(&mut self) -> F::Item { - self.0.next() + unsafe fn fetch(&self, n: usize) -> F::Item { + self.0.fetch(n) } - unsafe fn should_skip(&self) -> bool { - self.0.should_skip() + unsafe fn should_skip(&self, n: usize) -> bool { + self.0.should_skip(n) } } @@ -731,7 +747,7 @@ impl<'w, Q: Query> QueryBorrow<'w, Q> { QueryIter { borrow: self, archetype_index: 0, - iter: None, + iter: ChunkIter::EMPTY, } } @@ -835,7 +851,7 @@ impl<'q, 'w, Q: Query> IntoIterator for &'q mut QueryBorrow<'w, Q> { pub struct QueryIter<'q, 'w, Q: Query> { borrow: &'q mut QueryBorrow<'w, Q>, archetype_index: usize, - iter: Option>, + iter: ChunkIter, } unsafe impl<'q, 'w, Q: Query> Send for QueryIter<'q, 'w, Q> {} @@ -847,26 +863,21 @@ impl<'q, 'w, Q: Query> Iterator for QueryIter<'q, 'w, Q> { #[inline] fn next(&mut self) -> Option { loop { - match self.iter { + match unsafe { self.iter.next() } { None => { let archetype = self.borrow.archetypes.get(self.archetype_index)?; self.archetype_index += 1; unsafe { - self.iter = Q::Fetch::get(archetype, 0).map(|fetch| ChunkIter { - fetch, - len: archetype.len(), + self.iter = Q::Fetch::get(archetype, 0).map_or(ChunkIter::EMPTY, |fetch| { + ChunkIter { + fetch, + len: archetype.len(), + position: 0, + } }); } } - Some(ref mut iter) => match unsafe { iter.next() } { - None => { - self.iter = None; - continue; - } - Some(components) => { - return Some(components); - } - }, + Some(components) => return Some(components), } } } @@ -890,24 +901,32 @@ impl<'q, 'w, Q: Query> ExactSizeIterator for QueryIter<'q, 'w, Q> { struct ChunkIter { fetch: Q::Fetch, + position: usize, len: usize, } impl ChunkIter { + #[allow(clippy::declare_interior_mutable_const)] // no trait bounds on const fns + const EMPTY: Self = Self { + fetch: Q::Fetch::DANGLING, + position: 0, + len: 0, + }; + unsafe fn next<'a>(&mut self) -> Option<>::Item> { loop { - if self.len == 0 { + if self.position == self.len { return None; } - self.len -= 1; - if self.fetch.should_skip() { - // we still need to progress the iterator - let _ = self.fetch.next(); + if self.fetch.should_skip(self.position as usize) { + self.position += 1; continue; } - break Some(self.fetch.next()); + let item = Some(self.fetch.fetch(self.position as usize)); + self.position += 1; + return item; } } } @@ -941,6 +960,7 @@ impl<'q, 'w, Q: Query> Iterator for BatchedIter<'q, 'w, Q> { _marker: PhantomData, state: ChunkIter { fetch, + position: 0, len: self.batch_size.min(archetype.len() - offset), }, }); @@ -978,6 +998,7 @@ macro_rules! tuple_impl { ($($name: ident),*) => { impl<'a, $($name: Fetch<'a>),*> Fetch<'a> for ($($name,)*) { type Item = ($($name::Item,)*); + const DANGLING: Self = ($($name::DANGLING,)*); #[allow(unused_variables, unused_mut)] fn access(archetype: &Archetype) -> Option { @@ -1002,16 +1023,17 @@ macro_rules! tuple_impl { } #[allow(unused_variables)] - unsafe fn next(&mut self) -> Self::Item { + unsafe fn fetch(&self, n: usize) -> Self::Item { #[allow(non_snake_case)] let ($($name,)*) = self; - ($($name.next(),)*) + ($($name.fetch(n),)*) } - unsafe fn should_skip(&self) -> bool { + #[allow(unused_variables)] + unsafe fn should_skip(&self, n: usize) -> bool { #[allow(non_snake_case)] let ($($name,)*) = self; - $($name.should_skip()||)* false + $($name.should_skip(n)||)* false } } diff --git a/crates/bevy_ecs/hecs/src/query_one.rs b/crates/bevy_ecs/hecs/src/query_one.rs index ae7c330bbd..352d81edc8 100644 --- a/crates/bevy_ecs/hecs/src/query_one.rs +++ b/crates/bevy_ecs/hecs/src/query_one.rs @@ -36,11 +36,11 @@ impl<'a, Q: Query> QueryOne<'a, Q> { /// pre-existing borrow. pub fn get(&mut self) -> Option<>::Item> { unsafe { - let mut fetch = Q::Fetch::get(self.archetype, self.index)?; - if fetch.should_skip() { + let fetch = Q::Fetch::get(self.archetype, self.index)?; + if fetch.should_skip(0) { None } else { - Some(fetch.next()) + Some(fetch.fetch(0)) } } } @@ -107,11 +107,11 @@ where Q::Fetch: ReadOnlyFetch, { unsafe { - let mut fetch = Q::Fetch::get(self.archetype, self.index)?; - if fetch.should_skip() { + let fetch = Q::Fetch::get(self.archetype, self.index)?; + if fetch.should_skip(0) { None } else { - Some(fetch.next()) + Some(fetch.fetch(0)) } } } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 18402ded21..455b70ddd3 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -162,7 +162,7 @@ impl<'w, Q: HecsQuery> QueryBorrowChecked<'w, Q> { QueryIter { borrow: self, archetype_index: 0, - iter: None, + iter: ChunkIter::EMPTY, } } @@ -233,20 +233,7 @@ impl<'q, 'w, Q: HecsQuery> IntoIterator for &'q mut QueryBorrowChecked<'w, Q> { pub struct QueryIter<'q, 'w, Q: HecsQuery> { borrow: &'q mut QueryBorrowChecked<'w, Q>, archetype_index: usize, - iter: Option>, -} - -impl<'q, 'w, Q: HecsQuery> fmt::Debug for QueryIter<'q, 'w, Q> -where - Q::Fetch: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("QueryIter") - .field("borrow", self.borrow) - .field("archetype_index", &self.archetype_index) - .field("iter", &self.iter) - .finish() - } + iter: ChunkIter, } unsafe impl<'q, 'w, Q: HecsQuery> Send for QueryIter<'q, 'w, Q> {} @@ -258,26 +245,21 @@ impl<'q, 'w, Q: HecsQuery> Iterator for QueryIter<'q, 'w, Q> { #[inline] fn next(&mut self) -> Option { loop { - match self.iter { + match unsafe { self.iter.next() } { None => { - let archetype = self.borrow.archetypes.get(self.archetype_index as usize)?; + let archetype = self.borrow.archetypes.get(self.archetype_index)?; self.archetype_index += 1; unsafe { - self.iter = Q::Fetch::get(archetype, 0).map(|fetch| ChunkIter { - fetch, - len: archetype.len(), + self.iter = Q::Fetch::get(archetype, 0).map_or(ChunkIter::EMPTY, |fetch| { + ChunkIter { + fetch, + len: archetype.len(), + position: 0, + } }); } } - Some(ref mut iter) => match unsafe { iter.next() } { - None => { - self.iter = None; - continue; - } - Some(components) => { - return Some(components); - } - }, + Some(components) => return Some(components), } } } @@ -301,41 +283,35 @@ impl<'q, 'w, Q: HecsQuery> ExactSizeIterator for QueryIter<'q, 'w, Q> { struct ChunkIter { fetch: Q::Fetch, + position: usize, len: usize, } -impl fmt::Debug for ChunkIter -where - Q::Fetch: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ChunkIter") - .field("fetch", &self.fetch) - .field("len", &self.len) - .finish() - } -} - impl ChunkIter { - #[inline] + #[allow(clippy::declare_interior_mutable_const)] // no trait bounds on const fns + const EMPTY: Self = Self { + fetch: Q::Fetch::DANGLING, + position: 0, + len: 0, + }; + unsafe fn next<'a>(&mut self) -> Option<>::Item> { loop { - if self.len == 0 { + if self.position == self.len { return None; } - self.len -= 1; - if self.fetch.should_skip() { - // we still need to progress the iterator - let _ = self.fetch.next(); + if self.fetch.should_skip(self.position as usize) { + self.position += 1; continue; } - break Some(self.fetch.next()); + let item = Some(self.fetch.fetch(self.position as usize)); + self.position += 1; + return item; } } } - /// Batched version of `QueryIter` pub struct ParIter<'q, 'w, Q: HecsQuery> { borrow: &'q mut QueryBorrowChecked<'w, Q>, @@ -363,6 +339,7 @@ impl<'q, 'w, Q: HecsQuery> ParallelIterator> for ParIter<'q, 'w, Q> state: ChunkIter { fetch, len: self.batch_size.min(archetype.len() - offset), + position: 0, }, }); } else { @@ -383,18 +360,6 @@ pub struct Batch<'q, Q: HecsQuery> { state: ChunkIter, } -impl<'q, Q: HecsQuery> fmt::Debug for Batch<'q, Q> -where - Q::Fetch: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Batch") - .field("_marker", &self._marker) - .field("state", &self.state) - .finish() - } -} - impl<'q, 'w, Q: HecsQuery> Iterator for Batch<'q, Q> { type Item = >::Item; @@ -438,10 +403,14 @@ impl<'a, Q: HecsQuery> QueryOneChecked<'a, Q> { /// pre-existing borrow. pub fn get(&mut self) -> Option<>::Item> { unsafe { - let mut fetch = Q::Fetch::get(self.archetype, self.index as usize)?; + let fetch = Q::Fetch::get(self.archetype, self.index as usize)?; self.borrowed = true; - Q::Fetch::borrow(self.archetype); - Some(fetch.next()) + if fetch.should_skip(0) { + None + } else { + Q::Fetch::borrow(self.archetype); + Some(fetch.fetch(0)) + } } }