From 0aa680fe39b7efbf4371a856db2596fdb20d2e73 Mon Sep 17 00:00:00 2001 From: John Wells Date: Sun, 28 Jan 2024 16:25:04 -0500 Subject: [PATCH] Add FifoPool and configurable pool bucket sizes --- examples/msaa.rs | 2 +- src/lib.rs | 5 +- src/pool/fifo.rs | 227 +++++++++++++++++++++++++++++++++++++++++++++++ src/pool/hash.rs | 70 ++++++++------- src/pool/lazy.rs | 107 ++++++++++------------ src/pool/mod.rs | 115 +++++++++++++++++++++++- 6 files changed, 429 insertions(+), 97 deletions(-) create mode 100644 src/pool/fifo.rs diff --git a/examples/msaa.rs b/examples/msaa.rs index 14bf40d..1564344 100644 --- a/examples/msaa.rs +++ b/examples/msaa.rs @@ -24,7 +24,7 @@ fn main() -> anyhow::Result<()> { let mesh_msaa_pipeline = create_mesh_pipeline(&event_loop.device, sample_count)?; let mesh_noaa_pipeline = create_mesh_pipeline(&event_loop.device, SampleCount::X1)?; let cube_mesh = load_cube_mesh(&event_loop.device)?; - let mut pool = HashPool::new(&event_loop.device); + let mut pool = FifoPool::new(&event_loop.device); let mut angle = 0f32; diff --git a/src/lib.rs b/src/lib.rs index b62b287..476480f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -385,7 +385,10 @@ pub mod prelude { input::{ update_input, update_keyboard, update_mouse, KeyBuf, KeyMap, MouseBuf, MouseButton, }, - pool::{hash::HashPool, lazy::LazyPool, Lease, Pool}, + pool::{ + fifo::FifoPool, hash::HashPool, lazy::LazyPool, Lease, Pool, PoolInfo, + PoolInfoBuilder, + }, }, ash::vk, log::{debug, error, info, logger, trace, warn}, // Everyone wants a log diff --git a/src/pool/fifo.rs b/src/pool/fifo.rs new file mode 100644 index 0000000..3ca8b5a --- /dev/null +++ b/src/pool/fifo.rs @@ -0,0 +1,227 @@ +//! TODO + +use { + super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, + crate::driver::{ + accel_struct::{AccelerationStructure, AccelerationStructureInfo}, + buffer::{Buffer, BufferInfo}, + device::Device, + image::{Image, ImageInfo}, + CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, + RenderPass, RenderPassInfo, + }, + std::{collections::HashMap, sync::Arc}, +}; + +/// A space-efficient resource allocator. +#[derive(Debug)] +pub struct FifoPool { + accel_struct_cache: Cache, + buffer_cache: Cache, + command_buffer_cache: HashMap>, + descriptor_pool_cache: Cache, + device: Arc, + image_cache: Cache, + render_pass_cache: HashMap>, +} + +impl FifoPool { + /// Constructs a new `FifoPool`. + pub fn new(device: &Arc) -> Self { + Self::with_capacity(device, PoolInfo::default()) + } + + /// Constructs a new `FifoPool` with the given capacity information. + pub fn with_capacity(device: &Arc, info: impl Into) -> Self { + let info: PoolInfo = info.into(); + let device = Arc::clone(device); + + Self { + accel_struct_cache: PoolInfo::explicit_cache(info.accel_struct_capacity), + buffer_cache: PoolInfo::explicit_cache(info.buffer_capacity), + command_buffer_cache: Default::default(), + descriptor_pool_cache: PoolInfo::default_cache(), + device, + image_cache: PoolInfo::explicit_cache(info.image_capacity), + render_pass_cache: Default::default(), + } + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease( + &mut self, + info: AccelerationStructureInfo, + ) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.accel_struct_cache); + let mut cache = self.accel_struct_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible acceleration structure (big enough and same type) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.size >= info.size && item.info.ty == info.ty { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = AccelerationStructure::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: BufferInfo) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.buffer_cache); + let mut cache = self.buffer_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible buffer (compatible alignment, same mapping mode, big enough and + // superset of usage flags) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.alignment >= info.alignment + && item.info.can_map == info.can_map + && item.info.size >= info.size + && item.info.usage.contains(info.usage) + { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = Buffer::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { + let cache = self + .command_buffer_cache + .entry(info.queue_family_index) + .or_insert_with(PoolInfo::default_cache); + let mut item = cache + .lock() + .pop_front() + .filter(can_lease_command_buffer) + .map(Ok) + .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; + + // Drop anything we were holding from the last submission + CommandBuffer::drop_fenced(&mut item); + + Ok(Lease::new(Arc::downgrade(cache), item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: DescriptorPoolInfo) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.descriptor_pool_cache); + let mut cache = self.descriptor_pool_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible descriptor pool (has enough sets and descriptors) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.max_sets >= info.max_sets + && item.info.acceleration_structure_count >= info.acceleration_structure_count + && item.info.combined_image_sampler_count >= info.combined_image_sampler_count + && item.info.input_attachment_count >= info.input_attachment_count + && item.info.sampled_image_count >= info.sampled_image_count + && item.info.storage_buffer_count >= info.storage_buffer_count + && item.info.storage_buffer_dynamic_count >= info.storage_buffer_dynamic_count + && item.info.storage_image_count >= info.storage_image_count + && item.info.storage_texel_buffer_count >= info.storage_texel_buffer_count + && item.info.uniform_buffer_count >= info.uniform_buffer_count + && item.info.uniform_buffer_dynamic_count >= info.uniform_buffer_dynamic_count + && item.info.uniform_texel_buffer_count >= info.uniform_texel_buffer_count + { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = DescriptorPool::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: ImageInfo) -> Result, DriverError> { + let cache_ref = Arc::downgrade(&self.image_cache); + let mut cache = self.image_cache.lock(); + + { + profiling::scope!("Check cache"); + + // Look for a compatible image (same properties, superset of creation flags and usage + // flags) + for idx in 0..cache.len() { + let item = &cache[idx]; + if item.info.array_elements == info.array_elements + && item.info.depth == info.depth + && item.info.fmt == info.fmt + && item.info.height == info.height + && item.info.linear_tiling == info.linear_tiling + && item.info.mip_level_count == info.mip_level_count + && item.info.sample_count == info.sample_count + && item.info.ty == info.ty + && item.info.width == info.width + && item.info.flags.contains(info.flags) + && item.info.usage.contains(info.usage) + { + let item = cache.remove(idx).unwrap(); + + return Ok(Lease::new(cache_ref, item)); + } + } + } + + let item = Image::create(&self.device, info)?; + + Ok(Lease::new(cache_ref, item)) + } +} + +impl Pool for FifoPool { + #[profiling::function] + fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { + let cache = if let Some(cache) = self.render_pass_cache.get(&info) { + cache + } else { + // We tried to get the cache first in order to avoid this clone + self.render_pass_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache) + }; + let item = cache + .lock() + .pop_front() + .map(Ok) + .unwrap_or_else(|| RenderPass::create(&self.device, info))?; + + Ok(Lease::new(Arc::downgrade(cache), item)) + } +} diff --git a/src/pool/hash.rs b/src/pool/hash.rs index 401257a..7bfa325 100644 --- a/src/pool/hash.rs +++ b/src/pool/hash.rs @@ -4,14 +4,12 @@ //! the exact information provided then a new resource is created and returned. use { - super::{can_lease_command_buffer, Cache, Lease, Pool}, + super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, crate::driver::{ - accel_struct::{ - AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, - }, - buffer::{Buffer, BufferInfo, BufferInfoBuilder}, + accel_struct::{AccelerationStructure, AccelerationStructureInfo}, + buffer::{Buffer, BufferInfo}, device::Device, - image::{Image, ImageInfo, ImageInfoBuilder}, + image::{Image, ImageInfo}, CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, RenderPass, RenderPassInfo, }, @@ -32,12 +30,19 @@ pub struct HashPool { descriptor_pool_cache: HashMap>, device: Arc, image_cache: HashMap>, + info: PoolInfo, render_pass_cache: HashMap>, } impl HashPool { /// Constructs a new `HashPool`. pub fn new(device: &Arc) -> Self { + Self::with_capacity(device, PoolInfo::default()) + } + + /// Constructs a new `HashPool` with the given capacity information. + pub fn with_capacity(device: &Arc, info: impl Into) -> Self { + let info: PoolInfo = info.into(); let device = Arc::clone(device); Self { @@ -47,6 +52,7 @@ impl HashPool { descriptor_pool_cache: Default::default(), device, image_cache: Default::default(), + info, render_pass_cache: Default::default(), } } @@ -111,7 +117,7 @@ impl Pool for HashPool { let cache = self .command_buffer_cache .entry(info.queue_family_index) - .or_default(); + .or_insert_with(PoolInfo::default_cache); let mut item = cache .lock() .pop_front() @@ -126,6 +132,23 @@ impl Pool for HashPool { } } +impl Pool for HashPool { + #[profiling::function] + fn lease(&mut self, info: DescriptorPoolInfo) -> Result, DriverError> { + let cache = self + .descriptor_pool_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache); + let item = cache + .lock() + .pop_front() + .map(Ok) + .unwrap_or_else(|| DescriptorPool::create(&self.device, info))?; + + Ok(Lease::new(Arc::downgrade(cache), item)) + } +} + impl Pool for HashPool { #[profiling::function] fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { @@ -133,7 +156,9 @@ impl Pool for HashPool { cache } else { // We tried to get the cache first in order to avoid this clone - self.render_pass_cache.entry(info.clone()).or_default() + self.render_pass_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache) }; let item = cache .lock() @@ -147,14 +172,14 @@ impl Pool for HashPool { // Enable leasing items using their basic info macro_rules! lease { - ($info:ident => $item:ident) => { + ($info:ident => $item:ident, $capacity:ident) => { paste::paste! { impl Pool<$info, $item> for HashPool { #[profiling::function] fn lease(&mut self, info: $info) -> Result, DriverError> { let cache = self.[<$item:snake _cache>].entry(info.clone()) .or_insert_with(|| { - Arc::new(Mutex::new(VecDeque::new())) + Cache::new(Mutex::new(VecDeque::with_capacity(self.info.$capacity))) }); let item = cache.lock().pop_front().map(Ok).unwrap_or_else(|| $item::create(&self.device, info))?; @@ -165,25 +190,6 @@ macro_rules! lease { }; } -lease!(DescriptorPoolInfo => DescriptorPool); - -// Enable leasing items as above, but also using their info builder type for convenience -macro_rules! lease_builder { - ($info:ident => $item:ident) => { - lease!($info => $item); - - paste::paste! { - impl Pool<[<$info Builder>], $item> for HashPool { - fn lease(&mut self, builder: [<$info Builder>]) -> Result, DriverError> { - let info = builder.build(); - - self.lease(info) - } - } - } - }; -} - -lease_builder!(AccelerationStructureInfo => AccelerationStructure); -lease_builder!(BufferInfo => Buffer); -lease_builder!(ImageInfo => Image); +lease!(AccelerationStructureInfo => AccelerationStructure, accel_struct_capacity); +lease!(BufferInfo => Buffer, buffer_capacity); +lease!(ImageInfo => Image, image_capacity); diff --git a/src/pool/lazy.rs b/src/pool/lazy.rs index 8c6752e..e32aa97 100644 --- a/src/pool/lazy.rs +++ b/src/pool/lazy.rs @@ -9,14 +9,12 @@ //! * Images may have additional usage flags use { - super::{can_lease_command_buffer, Cache, Lease, Pool}, + super::{can_lease_command_buffer, Cache, Lease, Pool, PoolInfo}, crate::driver::{ - accel_struct::{ - AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, - }, - buffer::{Buffer, BufferInfo, BufferInfoBuilder}, + accel_struct::{AccelerationStructure, AccelerationStructureInfo}, + buffer::{Buffer, BufferInfo}, device::Device, - image::{Image, ImageInfo, ImageInfoBuilder, ImageType, SampleCount}, + image::{Image, ImageInfo, ImageType, SampleCount}, CommandBuffer, CommandBufferInfo, DescriptorPool, DescriptorPoolInfo, DriverError, RenderPass, RenderPassInfo, }, @@ -40,28 +38,35 @@ struct ImageKey { /// A high-efficiency resource allocator. #[derive(Debug)] pub struct LazyPool { - acceleration_structure_cache: - HashMap>, - buffer_cache: HashMap>, + accel_struct_cache: HashMap>, + buffer_cache: HashMap<(bool, vk::DeviceSize), Cache>, command_buffer_cache: HashMap>, descriptor_pool_cache: Cache, device: Arc, image_cache: HashMap>, + info: PoolInfo, render_pass_cache: HashMap>, } impl LazyPool { /// Constructs a new `LazyPool`. pub fn new(device: &Arc) -> Self { + Self::with_capacity(device, PoolInfo::default()) + } + + /// Constructs a new `LazyPool` with the given capacity information. + pub fn with_capacity(device: &Arc, info: impl Into) -> Self { + let info: PoolInfo = info.into(); let device = Arc::clone(device); Self { - acceleration_structure_cache: Default::default(), + accel_struct_cache: Default::default(), buffer_cache: Default::default(), command_buffer_cache: Default::default(), - descriptor_pool_cache: Default::default(), + descriptor_pool_cache: PoolInfo::default_cache(), device, image_cache: Default::default(), + info, render_pass_cache: Default::default(), } } @@ -75,13 +80,13 @@ impl LazyPool { /// Clears the pool of acceleration structures, removing all resources. pub fn clear_accel_structs(&mut self) { - self.acceleration_structure_cache.clear(); + self.accel_struct_cache.clear(); } /// Clears the pool of acceleration structures, removing resources matching the given /// type. pub fn clear_accel_structs_by_ty(&mut self, ty: vk::AccelerationStructureTypeKHR) { - self.acceleration_structure_cache.remove(&ty); + self.accel_struct_cache.remove(&ty); } /// Clears the pool of buffers, removing all resources. @@ -109,7 +114,7 @@ impl LazyPool { where F: FnMut(vk::AccelerationStructureTypeKHR) -> bool, { - self.acceleration_structure_cache.retain(|&ty, _| f(ty)) + self.accel_struct_cache.retain(|&ty, _| f(ty)) } } @@ -120,9 +125,9 @@ impl Pool for LazyPool { info: AccelerationStructureInfo, ) -> Result, DriverError> { let cache = self - .acceleration_structure_cache + .accel_struct_cache .entry(info.ty) - .or_default(); + .or_insert_with(|| PoolInfo::explicit_cache(self.info.accel_struct_capacity)); let cache_ref = Arc::downgrade(cache); let mut cache = cache.lock(); @@ -146,26 +151,20 @@ impl Pool for LazyPool { } } -impl Pool for LazyPool { - fn lease( - &mut self, - info: AccelerationStructureInfoBuilder, - ) -> Result, DriverError> { - self.lease(info.build()) - } -} - impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: BufferInfo) -> Result, DriverError> { - let cache = self.buffer_cache.entry(info.can_map).or_default(); + let cache = self + .buffer_cache + .entry((info.can_map, info.alignment)) + .or_insert_with(|| PoolInfo::explicit_cache(self.info.buffer_capacity)); let cache_ref = Arc::downgrade(cache); let mut cache = cache.lock(); { profiling::scope!("Check cache"); - // Look for a compatible buffer (same mapping mode, big enough, superset of usage flags) + // Look for a compatible buffer (big enough and superset of usage flags) for idx in 0..cache.len() { let item = &cache[idx]; if item.info.size >= info.size && item.info.usage.contains(info.usage) { @@ -182,9 +181,24 @@ impl Pool for LazyPool { } } -impl Pool for LazyPool { - fn lease(&mut self, info: BufferInfoBuilder) -> Result, DriverError> { - self.lease(info.build()) +impl Pool for LazyPool { + #[profiling::function] + fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { + let cache = self + .command_buffer_cache + .entry(info.queue_family_index) + .or_insert_with(PoolInfo::default_cache); + let mut item = cache + .lock() + .pop_front() + .filter(can_lease_command_buffer) + .map(Ok) + .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; + + // Drop anything we were holding from the last submission + CommandBuffer::drop_fenced(&mut item); + + Ok(Lease::new(Arc::downgrade(cache), item)) } } @@ -242,7 +256,7 @@ impl Pool for LazyPool { ty: info.ty, width: info.width, }) - .or_default(); + .or_insert_with(|| PoolInfo::explicit_cache(self.info.image_capacity)); let cache_ref = Arc::downgrade(cache); let mut cache = cache.lock(); @@ -266,12 +280,6 @@ impl Pool for LazyPool { } } -impl Pool for LazyPool { - fn lease(&mut self, info: ImageInfoBuilder) -> Result, DriverError> { - self.lease(info.build()) - } -} - impl Pool for LazyPool { #[profiling::function] fn lease(&mut self, info: RenderPassInfo) -> Result, DriverError> { @@ -279,7 +287,9 @@ impl Pool for LazyPool { cache } else { // We tried to get the cache first in order to avoid this clone - self.render_pass_cache.entry(info.clone()).or_default() + self.render_pass_cache + .entry(info.clone()) + .or_insert_with(PoolInfo::default_cache) }; let item = cache .lock() @@ -290,24 +300,3 @@ impl Pool for LazyPool { Ok(Lease::new(Arc::downgrade(cache), item)) } } - -impl Pool for LazyPool { - #[profiling::function] - fn lease(&mut self, info: CommandBufferInfo) -> Result, DriverError> { - let cache = self - .command_buffer_cache - .entry(info.queue_family_index) - .or_default(); - let mut item = cache - .lock() - .pop_front() - .filter(can_lease_command_buffer) - .map(Ok) - .unwrap_or_else(|| CommandBuffer::create(&self.device, info))?; - - // Drop anything we were holding from the last submission - CommandBuffer::drop_fenced(&mut item); - - Ok(Lease::new(Arc::downgrade(cache), item)) - } -} diff --git a/src/pool/mod.rs b/src/pool/mod.rs index 1ddc0b2..61a4a75 100644 --- a/src/pool/mod.rs +++ b/src/pool/mod.rs @@ -44,11 +44,20 @@ //! * Processor usage is most important //! * Resources have consistent attributes each frame +pub mod fifo; pub mod hash; pub mod lazy; use { - crate::driver::{CommandBuffer, DriverError}, + crate::driver::{ + accel_struct::{ + AccelerationStructure, AccelerationStructureInfo, AccelerationStructureInfoBuilder, + }, + buffer::{Buffer, BufferInfo, BufferInfoBuilder}, + image::{Image, ImageInfo, ImageInfoBuilder}, + CommandBuffer, DriverError, + }, + derive_builder::{Builder, UninitializedFieldError}, parking_lot::Mutex, std::{ collections::VecDeque, @@ -129,9 +138,13 @@ impl Drop for Lease { // If the pool cache has been dropped we must manually drop the item, otherwise it goes back // into the pool. if let Some(cache) = self.cache_ref.upgrade() { - cache - .lock() - .push_back(unsafe { ManuallyDrop::take(&mut self.item) }); + let mut cache = cache.lock(); + + if cache.len() >= cache.capacity() { + cache.pop_front(); + } + + cache.push_back(unsafe { ManuallyDrop::take(&mut self.item) }); } else { unsafe { ManuallyDrop::drop(&mut self.item); @@ -145,3 +158,97 @@ pub trait Pool { /// Lease a resource. fn lease(&mut self, info: I) -> Result, DriverError>; } + +// Enable leasing items using their info builder type for convenience +macro_rules! lease_builder { + ($info:ident => $item:ident) => { + paste::paste! { + impl Pool<[<$info Builder>], $item> for T where T: Pool<$info, $item> { + fn lease(&mut self, builder: [<$info Builder>]) -> Result, DriverError> { + let info = builder.build(); + + self.lease(info) + } + } + } + }; +} + +lease_builder!(AccelerationStructureInfo => AccelerationStructure); +lease_builder!(BufferInfo => Buffer); +lease_builder!(ImageInfo => Image); + +/// TBD +#[derive(Builder, Clone, Copy, Debug)] +#[builder( + build_fn(private, name = "fallible_build", error = "PoolInfoBuilderError"), + derive(Debug), + pattern = "owned" +)] +pub struct PoolInfo { + /// TBD + #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] + pub accel_struct_capacity: usize, + + /// TBD + #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] + pub buffer_capacity: usize, + + /// TBD + #[builder(default = "PoolInfo::DEFAULT_RESOURCE_CAPACITY", setter(strip_option))] + pub image_capacity: usize, +} + +impl PoolInfo { + /// TBD + pub const DEFAULT_RESOURCE_CAPACITY: usize = 4; + + /// TBD + pub const fn with_capacity(resource_capacity: usize) -> Self { + Self { + accel_struct_capacity: resource_capacity, + buffer_capacity: resource_capacity, + image_capacity: resource_capacity, + } + } + + fn default_cache() -> Cache { + Cache::new(Mutex::new(VecDeque::with_capacity( + Self::DEFAULT_RESOURCE_CAPACITY, + ))) + } + + fn explicit_cache(capacity: usize) -> Cache { + Cache::new(Mutex::new(VecDeque::with_capacity(capacity))) + } +} + +impl Default for PoolInfo { + fn default() -> Self { + PoolInfoBuilder::default().into() + } +} + +impl From for PoolInfo { + fn from(info: PoolInfoBuilder) -> Self { + info.build() + } +} + +// HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56 +impl PoolInfoBuilder { + /// Builds a new `PoolInfo`. + pub fn build(self) -> PoolInfo { + self.fallible_build() + .expect("All required fields set at initialization") + } +} + +#[derive(Debug)] +struct PoolInfoBuilderError; + +impl From for PoolInfoBuilderError { + fn from(_: UninitializedFieldError) -> Self { + Self + } +}