use std::path::Path; use ecs::actions::Actions; use ecs::component::local::Local; use ecs::error::Context as _; use ecs::pair::ChildOf; use ecs::query::term::{Traverse, TraverseUp}; use ecs::uid::Uid; use ecs::Component; use crate::asset::Assets; use crate::camera::{Active as ActiveCamera, Camera}; use crate::color::Rgba; use crate::data_types::dimens::{Dimens, Dimens3}; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; use crate::ecs::query::term::{With, Without}; use crate::ecs::sole::Single; use crate::ecs::Query; use crate::image::Image; use crate::lighting::{ DirectionalLight, Environmental as EnvironmentalLighting, PointLight, }; use crate::material::{Flags as MaterialFlags, Material}; use crate::mesh::Mesh; use crate::model::{ModelMaterial, ModelMesh}; use crate::projection::{ClipVolume as ProjectionClipVolume, Perspective, Projection}; use crate::rendering::object::{Id as ObjectId, Store as ObjectStore}; use crate::rendering::shader::cursor::{ BindingTextureKind as ShaderBindingTextureKind, BindingValue as ShaderBindingValue, Cursor as ShaderCursor, }; use crate::rendering::shader::{ Context as ShaderContext, EntrypointFlags as ShaderEntrypointFlags, ModuleSource as ShaderModuleSource, Program as ShaderProgram, }; use crate::rendering::{ AssetOrValue, BackendProperties, BufferClearMask, Command, DepthFunction, DrawMeshOptions, DrawProperties, DrawPropertiesUpdateFlags, MeshUsage, RenderPass, RenderPasses, RgbaTextureDataType, Surface, TargetWindow, TextureCreation, TexturePixelDataFormat, }; use crate::scene::{Active as ActiveScene, Scene}; use crate::sky_box::SkyBox; use crate::texture::{ Filtering as TextureFiltering, Properties as TextureProperties, Texture, Wrapping as TextureWrapping, }; use crate::transform::Transform; use crate::util::OptionExt; use crate::vector::Vec3; use crate::windowing::window::Window; type RenderableEntity<'a> = ( &'a ModelMesh, Option<&'a ModelMaterial>, Traverse<(With, With), TraverseUp, ChildOf>, Option<&'a MaterialFlags>, Option<&'a Transform>, Option<&'a DrawFlags>, Without, ); struct SkyBoxIds { texture_object: ObjectId, mesh_object: ObjectId, } #[derive(Debug, Default, Component)] pub struct PipelineResources { white_1x1_tex_obj_id: Option, main_3d_shader: Option<(ObjectId, ShaderProgram)>, sky_box_shader: Option<(ObjectId, ShaderProgram)>, } #[tracing::instrument(skip_all)] pub fn add_passes( renderable_query: Query>, camera_query: Query<( &Camera, &Transform, &ActiveCamera, Traverse<(With, With), TraverseUp, ChildOf>, )>, window_query: Query<(&Window, &Surface, With)>, scene_query: Query<( Option<&EnvironmentalLighting>, Option<&SkyBox>, Option<&SkyBoxState>, With, With, )>, point_light_query: Query<( &PointLight, &Transform, Traverse<(With, With), TraverseUp, ChildOf>, )>, directional_light_query: Query<( &DirectionalLight, Traverse<(With, With), TraverseUp, ChildOf>, )>, shader_context: Single, backend_properties: Single, mut assets: Single, mut render_passes: Single, mut object_store: Single, mut actions: Actions, mut pipeline_resources: Local, ) -> Result<(), crate::Error> { let assets = assets.get_mut()?; let shader_context = shader_context.get()?; let render_passes = render_passes.get_mut()?; let object_store = object_store.get_mut()?; let Ok(backend_properties) = backend_properties.get() else { return Ok(()); }; let Some((scene_ent_id, (scene_env_lighting, scene_skybox, scene_skybox_state))) = scene_query.iter_with_euids().next() else { return Ok(()); }; let Some((camera, camera_transform, _)) = camera_query.iter().next() else { tracing::trace!("No active camera"); return Ok(()); }; let render_pass = render_passes.passes.push_front_mut(RenderPass { commands: Vec::with_capacity(30), draw_properties: DrawProperties::default(), }); for (mesh, material, ..) in &renderable_query { add_renderable_creation_commands( assets, object_store, render_pass, &mesh, material.as_deref(), ); } let white_1x1_tex_obj_id = *pipeline_resources .white_1x1_tex_obj_id .get_or_insert_with(|| { create_white_1x1_texture_object(object_store, render_pass) }); let shaders = get_or_create_shaders( shader_context, object_store, render_pass, &mut pipeline_resources, backend_properties, )?; let main_3d_shader_cursor = ShaderCursor::new( shaders .main_3d_shader .reflection(backend_properties.shader_target_id) .context("Unable to get reflection for main 3D shader")? .global_params_var_layout() .ok_or_else(|| { crate::Error::message( "Unable to get reflection for main 3D shader's global parameters", ) })?, ); add_set_3d_shader_point_light_bindings( render_pass, &point_light_query, shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, )?; add_set_3d_shader_dir_lights_bindings( render_pass, &directional_light_query, shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, )?; for (window, window_surface) in &window_query { render_pass .commands .push(Command::MakeCurrent(window_surface.id)); add_set_3d_shader_camera_bindings( render_pass, (&camera, &camera_transform), &window, shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, )?; let mut buf_clear_mask = BufferClearMask::DEPTH; buf_clear_mask.set(BufferClearMask::COLOR, scene_skybox.is_none()); render_pass .commands .push(Command::ClearBuffers(buf_clear_mask)); for (mesh, material, material_flags, transform, draw_flags) in &renderable_query { if assets.get(&mesh.asset).is_none() { continue; } let model_material = match material { Some(material) if let Some(material_desc) = assets.get(&material.asset) => { match assets.get(&material_desc.asset) { Some(material) => material, None => continue, } } Some(_) => { continue; } None => &Material::default(), }; if model_material .textures() .any(|texture_asset| !assets.is_loaded_and_has_type(texture_asset)) { continue; } add_set_3d_shader_renderable_bindings( render_pass, ( model_material, material_flags.as_deref(), transform.as_deref(), ), scene_env_lighting.as_deref(), shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, white_1x1_tex_obj_id, )?; render_pass .commands .push(Command::ActivateShader(shaders.main_3d_shader_obj_id)); if let Some(draw_flags) = draw_flags.as_deref().filter(|&draw_flags| { draw_flags.polygon_mode_config != PolygonModeConfig::default() }) { render_pass.commands.push(Command::UpdateDrawProperties( DrawProperties { polygon_mode_config: draw_flags.polygon_mode_config.clone(), ..Default::default() }, DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, )); } render_pass.commands.push(Command::DrawMesh( ObjectId::Asset(mesh.asset.id()), DrawMeshOptions::default(), )); if draw_flags.as_deref().is_some_and(|draw_flags| { draw_flags.polygon_mode_config != PolygonModeConfig::default() }) { render_pass.commands.push(Command::UpdateDrawProperties( DrawProperties { polygon_mode_config: PolygonModeConfig::default(), ..Default::default() }, DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, )); } } if let Some(scene_skybox) = &scene_skybox { let Some(sky_box_ids) = load_sky_box( scene_ent_id, scene_skybox, scene_skybox_state.as_deref(), assets, object_store, render_pass, &mut actions, )? else { continue; }; add_sky_box_commands( render_pass, &sky_box_ids, &camera, &camera_transform, &window, shaders.sky_box_shader_obj_id, shaders.sky_box_shader, &backend_properties, )?; } } Ok(()) } fn create_white_1x1_texture_object( object_store: &mut ObjectStore, render_pass: &mut RenderPass, ) -> ObjectId { let white_1x1_tex_obj_id = ObjectId::new_sequential(); object_store.insert_pending(white_1x1_tex_obj_id); render_pass.commands.push(Command::CreateTexture { obj_id: white_1x1_tex_obj_id, pixel_data_format: TexturePixelDataFormat::Rgba( RgbaTextureDataType::UnsignedByte, ), creation: TextureCreation::Texture2D { size: Dimens { width: 1, height: 1 }, image: Some( Image::from_color(Rgba::::white(), Dimens { width: 1, height: 1 }) .into_bytes() .into_boxed_slice(), ), }, properties: TextureProperties::default(), }); white_1x1_tex_obj_id } #[derive(Debug)] struct ReadyShaders<'pipeline_resources> { main_3d_shader_obj_id: ObjectId, main_3d_shader: &'pipeline_resources ShaderProgram, sky_box_shader_obj_id: ObjectId, sky_box_shader: &'pipeline_resources ShaderProgram, } fn get_or_create_shaders<'pipeline_resources>( shader_context: &ShaderContext, object_store: &mut ObjectStore, render_pass: &mut RenderPass, pipeline_resources: &'pipeline_resources mut PipelineResources, backend_properties: &BackendProperties, ) -> Result, crate::Error> { let (main_3d_shader_obj_id, main_3d_shader) = pipeline_resources .main_3d_shader .get_or_try_insert_with_fn(|| { let module_source = ShaderModuleSource { name: "main_3d_shader.slang".into(), file_path: Path::new("@engine/main_3d_shader").into(), source: include_str!("../../res/main_3d_shader.slang").into(), link_entry_points: ShaderEntrypointFlags::VERTEX | ShaderEntrypointFlags::FRAGMENT, }; let obj_id = ObjectId::new_sequential(); let shader = shader_context .compose_program( &[shader_context.load_module(&module_source)?], module_source.link_entry_points, backend_properties.shader_target_id, )? .into_linked()?; object_store.insert_pending(obj_id); render_pass .commands .push(Command::CreateShaderProgram(obj_id, shader.clone())); Ok::<_, crate::Error>((obj_id, shader)) }) .context("Creation of main 3D shader failed")?; let (sky_box_shader_obj_id, sky_box_shader) = pipeline_resources .sky_box_shader .get_or_try_insert_with_fn(|| { let module_source = ShaderModuleSource { name: "sky_box_shader.slang".into(), file_path: Path::new("@engine/sky_box_shader").into(), source: include_str!("../../res/sky_box_shader.slang").into(), link_entry_points: ShaderEntrypointFlags::VERTEX | ShaderEntrypointFlags::FRAGMENT, }; let obj_id = ObjectId::new_sequential(); let shader = shader_context .compose_program( &[shader_context.load_module(&module_source)?], module_source.link_entry_points, backend_properties.shader_target_id, )? .into_linked()?; object_store.insert_pending(obj_id); render_pass .commands .push(Command::CreateShaderProgram(obj_id, shader.clone())); Ok::<_, crate::Error>((obj_id, shader)) }) .context("Creation of sky box shader failed")?; Ok(ReadyShaders { main_3d_shader_obj_id: *main_3d_shader_obj_id, main_3d_shader: main_3d_shader, sky_box_shader_obj_id: *sky_box_shader_obj_id, sky_box_shader: sky_box_shader, }) } fn add_renderable_creation_commands( assets: &Assets, object_store: &mut ObjectStore, render_pass: &mut RenderPass, mesh: &ModelMesh, material: Option<&ModelMaterial>, ) { if assets.get(&mesh.asset).is_none() { return; } let model_material = match material { Some(material) if let Some(material_desc) = assets.get(&material.asset) => { match assets.get(&material_desc.asset) { Some(material) => material, None => return, } } Some(_) => { return; } None => &Material::default(), }; // debug_assert!(model_spec.materials.len() <= 1); for texture_asset in model_material.textures() { let Some(texture) = assets.get(texture_asset) else { return; }; let Texture::Texture2D(texture) = texture else { tracing::error!( texture_asset_id = ?texture_asset.id(), texture_asset_label = assets.get_label(texture_asset).map(tracing::field::display), "Material texture map is not 2D" ); return; }; let texture_object_id = ObjectId::Asset(texture_asset.id()); if object_store.contains_maybe_pending_with_id(&texture_object_id) { return; } let Some(tex_pixel_data_format) = TexturePixelDataFormat::for_image(&texture.image) else { tracing::error!("No texture pixel data format is available for image"); return; }; object_store.insert_pending(texture_object_id); render_pass.commands.push(Command::CreateTexture { obj_id: texture_object_id, pixel_data_format: tex_pixel_data_format, creation: TextureCreation::Texture2D { size: texture.image.dimensions(), image: Some(texture.image.as_bytes().to_vec().into_boxed_slice()), }, properties: texture.properties.clone(), }); } let mesh_obj_id = ObjectId::Asset(mesh.asset.id()); if !object_store.contains_maybe_pending_with_id(&mesh_obj_id) { object_store.insert_pending(mesh_obj_id); render_pass.commands.push(Command::CreateMesh { obj_id: mesh_obj_id, mesh: AssetOrValue::Asset(mesh.asset.clone()), usage: MeshUsage::Static, }); } } fn add_set_3d_shader_point_light_bindings( render_pass: &mut RenderPass, point_light_query: &Query<( &PointLight, &Transform, Traverse<(With, With), TraverseUp, ChildOf>, )>, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, ) -> Result<(), crate::Error> { let lighting_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("lighting"); render_pass .commands .reserve(1 + point_light_query.iter().count() * 6); render_pass.commands.push(Command::SetShaderBinding( main_3d_shader_obj_id, lighting_shader_cursor.field("point_light_cnt").binding( u32::try_from(point_light_query.iter().count()) .expect("Point light count does not fit in 32-bit unsigned integer") .into(), )?, )); for (point_light_index, (point_light, point_light_transform)) in point_light_query.iter().enumerate() { let point_light_shader_cursor = lighting_shader_cursor .field("point_lights") .element(point_light_index); let phong_shader_cursor = point_light_shader_cursor.field("phong"); let attenuation_props_shader_cursor = point_light_shader_cursor.field("attenuation_props"); render_pass.commands.extend( [ phong_shader_cursor .field("diffuse") .binding(point_light.diffuse.to_rgb_lossy().into())?, phong_shader_cursor .field("specular") .binding(point_light.specular.to_rgb_lossy().into())?, point_light_shader_cursor.field("position").binding( (point_light_transform.position + point_light.local_position).into(), )?, attenuation_props_shader_cursor .field("constant") .binding(point_light.attenuation_params.constant.into())?, attenuation_props_shader_cursor .field("linear") .binding(point_light.attenuation_params.linear.into())?, attenuation_props_shader_cursor .field("quadratic") .binding(point_light.attenuation_params.quadratic.into())?, ] .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); } Ok(()) } fn add_set_3d_shader_dir_lights_bindings( render_pass: &mut RenderPass, directional_light_query: &Query<( &DirectionalLight, Traverse<(With, With), TraverseUp, ChildOf>, )>, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, ) -> Result<(), crate::Error> { let lighting_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("lighting"); render_pass .commands .reserve(1 + directional_light_query.iter().count() * 3); render_pass.commands.push(Command::SetShaderBinding( main_3d_shader_obj_id, lighting_shader_cursor .field("directional_light_cnt") .binding( u32::try_from(directional_light_query.iter().count()) .expect( "Directional light count does not fit in 32-bit unsigned integer", ) .into(), )?, )); for (directional_light_index, (directional_light,)) in directional_light_query.iter().enumerate() { let directional_light_shader_cursor = lighting_shader_cursor .field("directional_lights") .element(directional_light_index); let phong_shader_cursor = directional_light_shader_cursor.field("phong"); render_pass.commands.extend( [ phong_shader_cursor .field("diffuse") .binding(directional_light.diffuse.to_rgb_lossy().into())?, phong_shader_cursor .field("specular") .binding(directional_light.specular.to_rgb_lossy().into())?, directional_light_shader_cursor .field("direction") .binding(directional_light.direction.into())?, ] .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); } Ok(()) } fn add_set_3d_shader_camera_bindings( render_pass: &mut RenderPass, (camera, camera_transform): (&Camera, &Transform), window: &Window, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, ) -> Result<(), crate::Error> { let model_3d_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("model_3d"); let lighting_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("lighting"); render_pass.commands.extend( [ model_3d_shader_cursor .field("view") .binding(camera.to_view_matrix(camera_transform.position).into())?, model_3d_shader_cursor.field("projection").binding( camera .projection .to_matrix_rh(window.inner_size, ProjectionClipVolume::NegOneToOne) .into(), )?, lighting_shader_cursor .field("view_pos") .binding(camera_transform.position.into())?, ] .into_iter() .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); Ok(()) } fn add_set_3d_shader_renderable_bindings( render_pass: &mut RenderPass, renderable: (&Material, Option<&MaterialFlags>, Option<&Transform>), scene_env_lighting: Option<&EnvironmentalLighting>, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, white_1x1_tex_obj_id: ObjectId, ) -> Result<(), crate::Error> { let (material, material_flags, transform) = renderable; let transform = match transform { Some(transform) => transform, None => &Transform::default(), }; let model_matrix = transform.to_matrix(); let inverted_model_matrix = model_matrix.inverse(); let material_flags = material_flags.unwrap_or(&const { MaterialFlags::builder().build() }); let env_lighting = match &scene_env_lighting { Some(env_lighting) => env_lighting, None => &EnvironmentalLighting::default(), }; let model_3d_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("model_3d"); let material_shader_cursor = main_3d_shader_cursor .field("Uniforms") .field("lighting") .field("material"); let diffuse_map_obj_id = material .diffuse_map .as_ref() .map_or(white_1x1_tex_obj_id, |diffuse_map_asset| { ObjectId::Asset(diffuse_map_asset.id()) }); render_pass.commands.extend( [ model_3d_shader_cursor .field("model") .binding(model_matrix.into())?, model_3d_shader_cursor .field("model_inverted") .binding(inverted_model_matrix.into())?, material_shader_cursor.field("ambient").binding( if material_flags.use_ambient_color { &material.ambient } else { &env_lighting.ambient_color } .to_rgb_lossy() .into(), )?, material_shader_cursor .field("diffuse") .binding(material.diffuse.to_rgb_lossy().into())?, material_shader_cursor .field("specular") .binding(material.specular.to_rgb_lossy().into())?, main_3d_shader_cursor.field("ambient_map").binding( ShaderBindingValue::Texture( material .ambient_map .as_ref() .map_or(diffuse_map_obj_id, |ambient_map_asset| { ObjectId::Asset(ambient_map_asset.id()) }), ShaderBindingTextureKind::Texture2D, ), )?, main_3d_shader_cursor.field("diffuse_map").binding( ShaderBindingValue::Texture( diffuse_map_obj_id, ShaderBindingTextureKind::Texture2D, ), )?, main_3d_shader_cursor.field("specular_map").binding( ShaderBindingValue::Texture( material .specular_map .as_ref() .map_or(white_1x1_tex_obj_id, |specular_map_asset| { ObjectId::Asset(specular_map_asset.id()) }), ShaderBindingTextureKind::Texture2D, ), )?, material_shader_cursor .field("shininess") .binding(material.shininess.into())?, ] .into_iter() .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); Ok(()) } fn add_sky_box_commands( render_pass: &mut RenderPass, sky_box_ids: &SkyBoxIds, camera: &Camera, camera_transform: &Transform, window: &Window, sky_box_shader_obj_id: ObjectId, sky_box_shader_program: &ShaderProgram, backend_properties: &BackendProperties, ) -> Result<(), crate::Error> { render_pass .commands .push(Command::ActivateShader(sky_box_shader_obj_id)); let sky_box_shader_cursor = ShaderCursor::new( sky_box_shader_program .reflection(backend_properties.shader_target_id) .unwrap() .global_params_var_layout() .unwrap(), ); let mut view = camera.to_view_matrix(camera_transform.position); view.translate(&Vec3 { x: 0.0, y: 0.0, z: 0.0 }); let sky_box_shader_bindings = [ sky_box_shader_cursor .field("Uniforms") .field("projection") .binding( (match camera.projection { Projection::Perspective(_) => camera.projection.to_matrix_rh( window.inner_size, ProjectionClipVolume::NegOneToOne, ), Projection::Orthographic(_) => { // A orthographic projection cannot be used for a sky box Perspective::default().to_matrix_rh( window.inner_size.width as f32 / window.inner_size.height as f32, ProjectionClipVolume::NegOneToOne, ) } }) .into(), )?, sky_box_shader_cursor .field("Uniforms") .field("view") .binding(view.into())?, sky_box_shader_cursor.field("cube_texture").binding( ShaderBindingValue::Texture( sky_box_ids.texture_object, ShaderBindingTextureKind::Cube, ), )?, ]; for binding in sky_box_shader_bindings { render_pass .commands .push(Command::SetShaderBinding(sky_box_shader_obj_id, binding)); } render_pass.commands.push(Command::UpdateDrawProperties( DrawProperties { depth_function: DepthFunction::LessOrEqual, ..Default::default() }, DrawPropertiesUpdateFlags::DEPTH_FUNCTION, )); render_pass.commands.push(Command::DrawMesh( sky_box_ids.mesh_object, DrawMeshOptions::default(), )); Ok(()) } fn load_sky_box( scene_ent_id: Uid, sky_box: &SkyBox, sky_box_state: Option<&SkyBoxState>, assets: &mut Assets, object_store: &mut ObjectStore, render_pass: &mut RenderPass, actions: &mut Actions, ) -> Result, crate::Error> { let mesh_object_id = if let Some(sky_box_state) = &sky_box_state { sky_box_state.mesh_object_id } else { let sky_box_mesh = Mesh::cube(Dimens3 { width: 1.0, height: 1.0, depth: 1.0 }); let mesh_object_id = ObjectId::new_sequential(); object_store.insert_pending(mesh_object_id); render_pass.commands.push(Command::CreateMesh { obj_id: mesh_object_id, mesh: AssetOrValue::Value(sky_box_mesh), usage: MeshUsage::Static, }); mesh_object_id }; let texture_object_id = match &sky_box { SkyBox::AssetPerCubeMapFace(cube_map_part_assets) => { if let Some(sky_box_state) = sky_box_state { sky_box_state.texture_object_id } else { let Some(part_images) = cube_map_part_assets .iter() .map_while(|(face, part_texture_asset)| { let part_texture = assets.get(part_texture_asset)?; match part_texture { Texture::Texture2D(part_texture) => { Some((*face, &part_texture.image)) } Texture::CubeMap(_) => { tracing::warn!( "Cube map texture cannot be used as cube map part" ); None } } }) .collect::>() .as_array::<6>() .copied() else { return Ok(None); }; let texture_object_id = ObjectId::new_sequential(); object_store.insert_pending(texture_object_id); render_pass.commands.push(Command::CreateTexture { obj_id: texture_object_id, pixel_data_format: TexturePixelDataFormat::for_image( part_images[0].1, ) .expect("No texture pixel data format is available for image"), creation: TextureCreation::CubeMap { size: part_images[0].1.dimensions(), images: Some(Box::new(part_images.map(|(face, image)| { (face, image.as_bytes().to_vec().into_boxed_slice()) }))), }, properties: TextureProperties::builder() .minifying_filter(TextureFiltering::Linear) .magnifying_filter(TextureFiltering::Linear) .wrap(TextureWrapping::ClampToEdge) .build(), }); texture_object_id } } }; if sky_box_state.is_none() { actions.add_components( scene_ent_id, (SkyBoxState { texture_object_id, mesh_object_id },), ); } Ok(Some(SkyBoxIds { texture_object: texture_object_id, mesh_object: mesh_object_id, })) } #[derive(Debug, Component)] pub struct SkyBoxState { texture_object_id: ObjectId, mesh_object_id: ObjectId, }