Add debug lines and improve tile queuing (#226)

* Add debug lines

* Upgrade package lock

* Improve tile view pattern

* Improve naming and simplify

* Render children in parent is not yet available

* Increase buffer pool size

* Use 3d compatible stencil value

* Only use coords intead of whole TileShape in TileView

* Add some comments

* Fix stencil value
This commit is contained in:
Max Ammann 2022-12-15 15:40:08 +01:00 committed by GitHub
parent f453ede220
commit 2b917e9e08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 676 additions and 379 deletions

View File

@ -5,27 +5,6 @@
use maplibre_build_tools::wgsl::validate_project_wgsl;
/*
fn generate_type_def() {
use std::fs::File;
use std::io::BufReader;
use serde_json::Value;
let f = File::open("style-spec-v8.json").unwrap();
let mut reader = BufReader::new(f);
let result = serde_json::from_reader::<_, Value>(&mut reader).unwrap();
let spec_root = result.as_object()?;
let version = &spec_root["$version"].as_i64()?;
let root = &spec_root["$root"].as_object()?;
for x in spec_root {
}
println!("cargo:warning={:?}", version);
}
*/
#[cfg(feature = "embed-static-tiles")]
fn embed_tiles_statically() {
use std::{env, path::Path};

View File

@ -1,8 +1,9 @@
//! Tile cache.
use std::collections::{btree_map, BTreeMap};
use std::collections::{btree_map, btree_map::Entry, BTreeMap};
use bytemuck::Pod;
use thiserror::Error;
use crate::{
coords::{Quadkey, WorldTileCoords},
@ -85,6 +86,13 @@ impl StoredTile {
}
}
}
#[derive(Error, Debug)]
pub enum MarkError {
#[error("no pending tile at coords")]
NoPendingTile,
#[error("unable to construct quadkey")]
QuadKey,
}
/// Stores and provides access to a quad tree of cached tiles with world tile coords.
#[derive(Default)]
@ -99,10 +107,6 @@ impl TileRepository {
}
}
pub fn clear(&mut self) {
self.tree.clear();
}
/// Inserts a tessellated layer into the quad tree at its world tile coords.
/// If the space is vacant, the tessellated layer is inserted into a new
/// [crate::io::tile_repository::StoredLayer].
@ -126,12 +130,6 @@ impl TileRepository {
}
}
pub fn put_tile(&mut self, tile: StoredTile) {
if let Some(key) = tile.coords.build_quad_key() {
self.tree.insert(key, tile);
}
}
/// Returns the list of tessellated layers at the given world tile coords. None if tile is
/// missing from the cache.
pub fn iter_layers_at(
@ -141,7 +139,14 @@ impl TileRepository {
coords
.build_quad_key()
.and_then(|key| self.tree.get(&key))
.map(|results| results.layers.iter())
.and_then(|tile| {
if tile.status == TileStatus::Success {
Some(tile)
} else {
None
}
})
.map(|tile| tile.layers.iter())
}
/// Returns the list of tessellated layers at the given world tile coords, which are loaded in
@ -160,18 +165,8 @@ impl TileRepository {
})
}
/// Create a new tile.
pub fn create_tile(&mut self, coords: WorldTileCoords) -> bool {
if let Some(btree_map::Entry::Vacant(entry)) =
coords.build_quad_key().map(|key| self.tree.entry(key))
{
entry.insert(StoredTile::pending(coords));
}
true
}
/// Checks if a layer has been fetched.
pub fn has_tile(&self, coords: &WorldTileCoords) -> bool {
/// Checks fetching of a tile has been started
pub fn is_tile_pending_or_done(&self, coords: &WorldTileCoords) -> bool {
if coords
.build_quad_key()
.and_then(|key| self.tree.get(&key))
@ -182,22 +177,52 @@ impl TileRepository {
true
}
pub fn mark_tile_succeeded(&mut self, coords: &WorldTileCoords) {
if let Some(cached_tile) = coords
.build_quad_key()
.and_then(|key| self.tree.get_mut(&key))
{
cached_tile.status = TileStatus::Success;
/// Mark the tile at `coords` pending in this tile repository.
pub fn mark_tile_pending(&mut self, coords: WorldTileCoords) -> Result<(), MarkError> {
let Some(key) = coords.build_quad_key() else { return Err(MarkError::QuadKey); };
match self.tree.entry(key) {
Entry::Vacant(entry) => {
entry.insert(StoredTile::pending(coords));
}
Entry::Occupied(mut entry) => {
entry.get_mut().status = TileStatus::Pending;
}
}
Ok(())
}
/// Mark the tile at `coords` succeeded in this tile repository. Only succeeds if there is a
/// pending tile at `coords`.
pub fn mark_tile_succeeded(&mut self, coords: &WorldTileCoords) -> Result<(), MarkError> {
self.mark_tile(coords, TileStatus::Success)
}
/// Mark the tile at `coords` failed in this tile repository. Only succeeds if there is a
/// pending tile at `coords`.
pub fn mark_tile_failed(&mut self, coords: &WorldTileCoords) -> Result<(), MarkError> {
self.mark_tile(coords, TileStatus::Failed)
}
fn mark_tile(&mut self, coords: &WorldTileCoords, status: TileStatus) -> Result<(), MarkError> {
let Some(key) = coords.build_quad_key() else { return Err(MarkError::QuadKey); };
if let Entry::Occupied(mut entry) = self.tree.entry(key) {
entry.get_mut().status = status;
Ok(())
} else {
Err(MarkError::NoPendingTile)
}
}
/// Checks if a layer has been fetched.
pub fn mark_tile_failed(&mut self, coords: &WorldTileCoords) {
if let Some(cached_tile) = coords
.build_quad_key()
.and_then(|key| self.tree.get_mut(&key))
{
cached_tile.status = TileStatus::Failed;
pub fn put_tile(&mut self, tile: StoredTile) {
if let Some(key) = tile.coords.build_quad_key() {
self.tree.insert(key, tile);
}
}
pub fn clear(&mut self) {
self.tree.clear();
}
}

View File

@ -93,7 +93,7 @@ where
.build()
.initialize_renderer::<E::MapWindowConfig>(&self.window)
.await
.map_err(|e| MapError::DeviceInit(e))?;
.map_err(MapError::DeviceInit)?;
let window_size = self.window.size();

View File

@ -0,0 +1,65 @@
use std::ops::Deref;
use crate::render::{
graph::{Node, NodeRunError, RenderContext, RenderGraphContext, SlotInfo},
render_commands::DrawDebugOutlines,
render_phase::RenderCommand,
resource::TrackedRenderPass,
Eventually::Initialized,
RenderState,
};
/// Pass which renders debug information on top of the map.
pub struct DebugPassNode {}
impl DebugPassNode {
pub fn new() -> Self {
Self {}
}
}
impl Node for DebugPassNode {
fn input(&self) -> Vec<SlotInfo> {
vec![]
}
fn update(&mut self, _state: &mut RenderState) {}
fn run(
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
state: &RenderState,
) -> Result<(), NodeRunError> {
let Initialized(render_target) = &state.render_target else {
return Ok(());
};
let color_attachment = wgpu::RenderPassColorAttachment {
view: render_target.deref(),
ops: wgpu::Operations {
// Draws on-top of previously rendered data
load: wgpu::LoadOp::Load,
store: true,
},
resolve_target: None,
};
let render_pass =
render_context
.command_encoder
.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(color_attachment)],
depth_stencil_attachment: None,
});
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in &state.mask_phase.items {
DrawDebugOutlines::render(state, item, &mut tracked_pass);
}
Ok(())
}
}

View File

@ -14,12 +14,6 @@ pub enum RenderError {
impl RenderError {
pub fn should_exit(&self) -> bool {
match self {
RenderError::Surface(e) => match e {
wgpu::SurfaceError::OutOfMemory => true,
_ => false,
},
_ => true,
}
matches!(self, RenderError::Surface(wgpu::SurfaceError::OutOfMemory))
}
}

View File

@ -27,7 +27,7 @@ where
}
}
impl<'a, T> Eventually<T>
impl<T> Eventually<T>
where
T: HasChanged,
{

View File

@ -36,17 +36,13 @@ impl Node for MainPassNode {
render_context: &mut RenderContext,
state: &RenderState,
) -> Result<(), NodeRunError> {
let (render_target, multisampling_texture, depth_texture) = if let (
Initialized(render_target),
Initialized(multisampling_texture),
Initialized(depth_texture),
) = (
&state.render_target,
&state.multisampling_texture,
&state.depth_texture,
) {
(render_target, multisampling_texture, depth_texture)
} else {
let Initialized(render_target) = &state.render_target else {
return Ok(());
};
let Initialized(multisampling_texture) = &state.multisampling_texture else {
return Ok(());
};
let Initialized(depth_texture) = &state.depth_texture else {
return Ok(());
};
@ -98,6 +94,7 @@ impl Node for MainPassNode {
for item in &state.tile_phase.items {
DrawTiles::render(state, item, &mut tracked_pass);
}
Ok(())
}
}

View File

@ -27,7 +27,7 @@ use crate::{
resource::{BufferPool, Globals, Head, IndexEntry, Surface, Texture, TextureView},
settings::{RendererSettings, WgpuSettings},
shaders::{ShaderFeatureStyle, ShaderLayerMetadata},
tile_view_pattern::{TileInView, TileShape, TileViewPattern},
tile_view_pattern::{TileShape, TileViewPattern},
},
tessellation::IndexDataType,
};
@ -37,6 +37,7 @@ pub mod resource;
pub mod stages;
// Rendering internals
mod debug_pass;
mod graph_runner;
mod main_pass;
mod render_commands;
@ -57,6 +58,7 @@ pub use stages::register_default_render_stages;
use crate::{
render::{
debug_pass::DebugPassNode,
error::RenderError,
graph::{EmptyNode, RenderGraph, RenderGraphError},
main_pass::{MainPassDriverNode, MainPassNode},
@ -83,6 +85,7 @@ pub struct RenderState {
tile_pipeline: Eventually<wgpu::RenderPipeline>,
mask_pipeline: Eventually<wgpu::RenderPipeline>,
debug_pipeline: Eventually<wgpu::RenderPipeline>,
globals_bind_group: Eventually<Globals>,
@ -91,7 +94,7 @@ pub struct RenderState {
surface: Surface,
mask_phase: RenderPhase<TileInView>,
mask_phase: RenderPhase<TileShape>,
tile_phase: RenderPhase<(IndexEntry, TileShape)>,
}
@ -103,6 +106,7 @@ impl RenderState {
tile_view_pattern: Default::default(),
tile_pipeline: Default::default(),
mask_pipeline: Default::default(),
debug_pipeline: Default::default(),
globals_bind_group: Default::default(),
depth_texture: Default::default(),
multisampling_texture: Default::default(),
@ -242,7 +246,7 @@ impl Renderer {
let adapter = instance
.request_adapter(request_adapter_options)
.await
.ok_or_else(|| wgpu::RequestDeviceError)?;
.ok_or(wgpu::RequestDeviceError)?;
let adapter_info = adapter.get_info();
@ -497,6 +501,7 @@ pub mod draw_graph {
// Labels for non-input nodes
pub mod node {
pub const MAIN_PASS: &str = "main_pass";
pub const DEBUG_PASS: &str = "debug_pass";
#[cfg(feature = "headless")]
pub const COPY: &str = "copy";
}
@ -506,9 +511,15 @@ pub fn create_default_render_graph() -> Result<RenderGraph, RenderGraphError> {
let mut graph = RenderGraph::default();
let mut draw_graph = RenderGraph::default();
// Draw nodes
draw_graph.add_node(draw_graph::node::MAIN_PASS, MainPassNode::new());
draw_graph.add_node(draw_graph::node::DEBUG_PASS, DebugPassNode::new());
// Input node
let input_node_id = draw_graph.set_input(vec![]);
// Edges
draw_graph.add_node_edge(input_node_id, draw_graph::node::MAIN_PASS)?;
// TODO: Enable debug pass via runtime flag
draw_graph.add_node_edge(draw_graph::node::MAIN_PASS, draw_graph::node::DEBUG_PASS)?;
graph.add_sub_graph(draw_graph::NAME, draw_graph);
graph.add_node(main_graph::node::MAIN_PASS_DEPENDENCIES, EmptyNode);

View File

@ -5,11 +5,11 @@ use crate::render::{
eventually::Eventually::Initialized,
render_phase::{PhaseItem, RenderCommand, RenderCommandResult},
resource::{Globals, IndexEntry, TrackedRenderPass},
tile_view_pattern::{TileInView, TileShape},
tile_view_pattern::TileShape,
RenderState, INDEX_FORMAT,
};
impl PhaseItem for TileInView {
impl PhaseItem for TileShape {
type SortKey = ();
fn sort_key(&self) -> Self::SortKey {}
@ -49,6 +49,19 @@ impl<P: PhaseItem> RenderCommand<P> for SetMaskPipeline {
}
}
pub struct SetDebugPipeline;
impl<P: PhaseItem> RenderCommand<P> for SetDebugPipeline {
fn render<'w>(
state: &'w RenderState,
_item: &P,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Initialized(pipeline) = &state.debug_pipeline else { return RenderCommandResult::Failure; };
pass.set_render_pipeline(pipeline);
RenderCommandResult::Success
}
}
pub struct SetTilePipeline;
impl<P: PhaseItem> RenderCommand<P> for SetTilePipeline {
fn render<'w>(
@ -63,25 +76,50 @@ impl<P: PhaseItem> RenderCommand<P> for SetTilePipeline {
}
pub struct DrawMask;
impl RenderCommand<TileInView> for DrawMask {
impl RenderCommand<TileShape> for DrawMask {
fn render<'w>(
state: &'w RenderState,
TileInView { shape, fallback }: &TileInView,
source_shape: &TileShape,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Initialized(tile_view_pattern) = &state.tile_view_pattern else { return RenderCommandResult::Failure; };
tracing::trace!("Drawing mask {}", &shape.coords);
tracing::trace!("Drawing mask {}", &source_shape.coords());
let shape_to_render = fallback.as_ref().unwrap_or(shape);
let reference = tile_view_pattern.stencil_reference_value(&shape_to_render.coords) as u32;
// Draw mask with stencil value of e.g. parent
let reference = tile_view_pattern.stencil_reference_value_3d(&source_shape.coords()) as u32;
pass.set_stencil_reference(reference);
pass.set_vertex_buffer(
0,
tile_view_pattern.buffer().slice(shape.buffer_range.clone()),
// Mask is of the requested shape
tile_view_pattern
.buffer()
.slice(source_shape.buffer_range()),
);
pass.draw(0..6, 0..1);
const TILE_MASK_SHADER_VERTICES: u32 = 6;
pass.draw(0..TILE_MASK_SHADER_VERTICES, 0..1);
RenderCommandResult::Success
}
}
pub struct DrawDebugOutline;
impl RenderCommand<TileShape> for DrawDebugOutline {
fn render<'w>(
state: &'w RenderState,
source_shape: &TileShape,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Initialized(tile_view_pattern) = &state.tile_view_pattern else { return RenderCommandResult::Failure; };
pass.set_vertex_buffer(
0,
tile_view_pattern
.buffer()
.slice(source_shape.buffer_range()),
);
const TILE_MASK_SHADER_VERTICES: u32 = 24;
pass.draw(0..TILE_MASK_SHADER_VERTICES, 0..1);
RenderCommandResult::Success
}
}
@ -96,7 +134,8 @@ impl RenderCommand<(IndexEntry, TileShape)> for DrawTile {
let (Initialized(buffer_pool), Initialized(tile_view_pattern)) =
(&state.buffer_pool, &state.tile_view_pattern) else { return RenderCommandResult::Failure; };
let reference = tile_view_pattern.stencil_reference_value(&shape.coords) as u32;
// Uses stencil value of requested tile and the shape of the requested tile
let reference = tile_view_pattern.stencil_reference_value_3d(&shape.coords()) as u32;
tracing::trace!(
"Drawing layer {:?} at {}",
@ -113,10 +152,7 @@ impl RenderCommand<(IndexEntry, TileShape)> for DrawTile {
0,
buffer_pool.vertices().slice(entry.vertices_buffer_range()),
);
pass.set_vertex_buffer(
1,
tile_view_pattern.buffer().slice(shape.buffer_range.clone()),
);
pass.set_vertex_buffer(1, tile_view_pattern.buffer().slice(shape.buffer_range()));
pass.set_vertex_buffer(
2,
buffer_pool
@ -137,3 +173,5 @@ impl RenderCommand<(IndexEntry, TileShape)> for DrawTile {
pub type DrawTiles = (SetTilePipeline, SetViewBindGroup<0>, DrawTile);
pub type DrawMasks = (SetMaskPipeline, DrawMask);
pub type DrawDebugOutlines = (SetDebugPipeline, DrawDebugOutline);

View File

@ -17,11 +17,12 @@ use crate::{
tessellation::OverAlignedVertexBuffer,
};
pub const VERTEX_SIZE: wgpu::BufferAddress = 1_000_000;
pub const INDICES_SIZE: wgpu::BufferAddress = 1_000_000;
// FIXME: Too low values can cause a back-and-forth between unloading and loading layers
pub const VERTEX_SIZE: wgpu::BufferAddress = 10 * 1_000_000;
pub const INDICES_SIZE: wgpu::BufferAddress = 10 * 1_000_000;
pub const FEATURE_METADATA_SIZE: wgpu::BufferAddress = 1024 * 1000;
pub const LAYER_METADATA_SIZE: wgpu::BufferAddress = 1024;
pub const FEATURE_METADATA_SIZE: wgpu::BufferAddress = 10 * 1024 * 1000;
pub const LAYER_METADATA_SIZE: wgpu::BufferAddress = 10 * 1024;
/// This is inspired by the memory pool in Vulkan documented
/// [here](https://gpuopen-librariesandsdks.github.io/VulkanMemoryAllocator/html/custom_memory_pools.html).
@ -476,9 +477,14 @@ impl IndexEntry {
}
}
#[derive(Debug)]
pub struct RingIndexEntry {
layers: VecDeque<IndexEntry>,
}
#[derive(Debug)]
pub struct RingIndex {
tree_index: BTreeMap<Quadkey, VecDeque<IndexEntry>>,
tree_index: BTreeMap<Quadkey, RingIndexEntry>,
linear_index: VecDeque<Quadkey>,
}
@ -496,44 +502,33 @@ impl RingIndex {
}
pub fn front(&self) -> Option<&IndexEntry> {
self.linear_index
.front()
.and_then(|key| self.tree_index.get(key).and_then(|entries| entries.front()))
self.linear_index.front().and_then(|key| {
self.tree_index
.get(key)
.and_then(|entry| entry.layers.front())
})
}
pub fn back(&self) -> Option<&IndexEntry> {
self.linear_index
.back()
.and_then(|key| self.tree_index.get(key).and_then(|entries| entries.back()))
self.linear_index.back().and_then(|key| {
self.tree_index
.get(key)
.and_then(|entry| entry.layers.back())
})
}
pub fn get_layers(&self, coords: &WorldTileCoords) -> Option<&VecDeque<IndexEntry>> {
coords
.build_quad_key()
.and_then(|key| self.tree_index.get(&key))
}
pub fn get_layers_fallback(&self, coords: &WorldTileCoords) -> Option<&VecDeque<IndexEntry>> {
let mut current = *coords;
loop {
if let Some(entries) = self.get_layers(&current) {
return Some(entries);
} else if let Some(parent) = current.get_parent() {
current = parent
} else {
return None;
}
}
.map(|entry| &entry.layers)
}
pub fn has_tile(&self, coords: &WorldTileCoords) -> bool {
coords
.build_quad_key()
.and_then(|key| self.tree_index.get(&key))
.is_some()
self.get_layers(coords).is_some()
}
pub fn get_tile_coords_fallback(&self, coords: &WorldTileCoords) -> Option<WorldTileCoords> {
pub fn get_available_parent(&self, coords: &WorldTileCoords) -> Option<WorldTileCoords> {
let mut current = *coords;
loop {
if self.has_tile(&current) {
@ -546,19 +541,45 @@ impl RingIndex {
}
}
pub fn get_available_children(
&self,
coords: &WorldTileCoords,
search_depth: usize,
) -> Option<Vec<WorldTileCoords>> {
let mut children = coords.get_children().to_vec();
let mut output = Vec::new();
for _ in 0..search_depth {
let mut new_children = Vec::with_capacity(children.len() * 4);
for child in children {
if self.has_tile(&child) {
output.push(child);
} else {
new_children.extend(child.get_children())
}
}
children = new_children;
}
Some(output)
}
pub fn iter(&self) -> impl Iterator<Item = impl Iterator<Item = &IndexEntry>> + '_ {
self.linear_index
.iter()
.flat_map(|key| self.tree_index.get(key).map(|entries| entries.iter()))
.flat_map(|key| self.tree_index.get(key).map(|entry| entry.layers.iter()))
}
fn pop_front(&mut self) -> Option<IndexEntry> {
if let Some(entries) = self
if let Some(entry) = self
.linear_index
.pop_front()
.and_then(|key| self.tree_index.get_mut(&key))
{
entries.pop_front()
entry.layers.pop_front()
} else {
None
}
@ -568,10 +589,12 @@ impl RingIndex {
if let Some(key) = entry.coords.build_quad_key() {
match self.tree_index.entry(key) {
btree_map::Entry::Vacant(index_entry) => {
index_entry.insert(VecDeque::from([entry]));
index_entry.insert(RingIndexEntry {
layers: VecDeque::from([entry]),
});
}
btree_map::Entry::Occupied(mut index_entry) => {
index_entry.get_mut().push_back(entry);
index_entry.get_mut().layers.push_back(entry);
}
}

View File

@ -278,7 +278,7 @@ impl Surface {
match &mut self.head {
Head::Headed(window) => {
if window.has_changed(&(self.size.width(), self.size.height())) {
window.resize_and_configure(self.size.height(), self.size.width(), device);
window.resize_and_configure(self.size.width(), self.size.height(), device);
}
}
Head::Headless(_) => {}

View File

@ -27,12 +27,17 @@ pub trait Shader {
pub struct TileMaskShader {
pub format: wgpu::TextureFormat,
pub draw_colors: bool,
pub debug_lines: bool,
}
impl Shader for TileMaskShader {
fn describe_vertex(&self) -> VertexState {
VertexState {
source: include_str!("tile_mask.vertex.wgsl"),
source: if self.debug_lines {
include_str!("tile_debug.vertex.wgsl")
} else {
include_str!("tile_mask.vertex.wgsl")
},
entry_point: "main",
buffers: vec![VertexBufferLayout {
array_stride: std::mem::size_of::<ShaderTileMetadata>() as u64,
@ -66,7 +71,7 @@ impl Shader for TileMaskShader {
fn describe_fragment(&self) -> FragmentState {
FragmentState {
source: include_str!("tile_mask.fragment.wgsl"),
source: include_str!("basic.fragment.wgsl"),
entry_point: "main",
targets: vec![Some(wgpu::ColorTargetState {
format: self.format,
@ -176,22 +181,10 @@ impl Shader for TileShader {
fn describe_fragment(&self) -> FragmentState {
FragmentState {
source: include_str!("tile.fragment.wgsl"),
source: include_str!("basic.fragment.wgsl"),
entry_point: "main",
targets: vec![Some(wgpu::ColorTargetState {
format: self.format,
/*blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),*/
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],

View File

@ -0,0 +1,75 @@
struct VertexOutput {
@location(0) v_color: vec4<f32>,
@builtin(position) position: vec4<f32>,
};
var<private> EXTENT: f32 = 4096.0;
var<private> DEBUG_COLOR: vec4<f32> = vec4<f32>(1.0, 0.0, 0.0, 1.0);
@vertex
fn main(
@location(4) translate1: vec4<f32>,
@location(5) translate2: vec4<f32>,
@location(6) translate3: vec4<f32>,
@location(7) translate4: vec4<f32>,
@builtin(vertex_index) vertex_idx: u32,
@builtin(instance_index) instance_idx: u32 // instance_index is used when we have multiple instances of the same "object"
) -> VertexOutput {
let z = 0.0;
let target_width = 1.0;
let target_height = 1.0;
let WIDTH = EXTENT / 1024.0;
var VERTICES: array<vec3<f32>, 24> = array<vec3<f32>, 24>(
// Debug lines vertices
// left
vec3<f32>(0.0, 0.0, z),
vec3<f32>(0.0, EXTENT, z),
vec3<f32>(WIDTH, EXTENT, z),
vec3<f32>(0.0, 0.0, z),
vec3<f32>(WIDTH, EXTENT, z),
vec3<f32>(WIDTH, 0.0, z),
// right
vec3<f32>(EXTENT - WIDTH, 0.0, z),
vec3<f32>(EXTENT - WIDTH, EXTENT, z),
vec3<f32>(EXTENT, EXTENT, z),
vec3<f32>(EXTENT - WIDTH, 0.0, z),
vec3<f32>(EXTENT, EXTENT, z),
vec3<f32>(EXTENT, 0.0, z),
// top
vec3<f32>(0.0, 0.0, z),
vec3<f32>(0.0, WIDTH, z),
vec3<f32>(EXTENT, WIDTH, z),
vec3<f32>(0.0, 0.0, z),
vec3<f32>(EXTENT, WIDTH, z),
vec3<f32>(EXTENT, 0.0, z),
// bottom
vec3<f32>(0.0, EXTENT - WIDTH, z),
vec3<f32>(0.0, EXTENT, z),
vec3<f32>(EXTENT, EXTENT, z),
vec3<f32>(0.0, EXTENT - WIDTH, z),
vec3<f32>(EXTENT, EXTENT, z),
vec3<f32>(EXTENT, EXTENT - WIDTH, z)
);
let a_position = VERTICES[vertex_idx];
let scaling: mat3x3<f32> = mat3x3<f32>(
vec3<f32>(target_width, 0.0, 0.0),
vec3<f32>(0.0, target_height, 0.0),
vec3<f32>(0.0, 0.0, 1.0)
);
var position = mat4x4<f32>(translate1, translate2, translate3, translate4) * vec4<f32>((scaling * a_position), 1.0);
position.z = 1.0;
return VertexOutput(DEBUG_COLOR, position);
}

View File

@ -1,8 +0,0 @@
struct Output {
@location(0) out_color: vec4<f32>,
};
@fragment
fn main(@location(0) v_color: vec4<f32>) -> Output {
return Output(v_color);
}

View File

@ -1,20 +1,10 @@
struct ShaderCamera {
view_proj: mat4x4<f32>,
view_position: vec4<f32>,
};
struct ShaderGlobal {
camera: ShaderCamera,
};
@group(0) @binding(0) var<uniform> globals: ShaderGlobal;
struct VertexOutput {
@location(0) v_color: vec4<f32>,
@location(0) v_color: vec4<f32>,
@builtin(position) position: vec4<f32>,
};
let EXTENT = 4096.0;
var<private> EXTENT: f32 = 4096.0;
var<private> DEBUG_COLOR: vec4<f32> = vec4<f32>(1.0, 0.0, 0.0, 1.0);
@vertex
fn main(
@ -29,15 +19,15 @@ fn main(
let target_width = 1.0;
let target_height = 1.0;
let debug_color = vec4<f32>(1.0, 0.0, 0.0, 1.0);
var VERTICES: array<vec3<f32>, 6> = array<vec3<f32>, 6>(
// Tile vertices
vec3<f32>(0.0, 0.0, z),
vec3<f32>(0.0, EXTENT, z),
vec3<f32>(EXTENT, 0.0, z),
vec3<f32>(EXTENT, 0.0, z),
vec3<f32>(0.0, EXTENT, z),
vec3<f32>(EXTENT, EXTENT, z)
vec3<f32>(EXTENT, EXTENT, z),
);
let a_position = VERTICES[vertex_idx];
@ -51,5 +41,5 @@ fn main(
// FIXME: how to fix z-fighting?
position.z = 1.0;
return VertexOutput(debug_color, position);
return VertexOutput(DEBUG_COLOR, position);
}

View File

@ -2,10 +2,7 @@
use crate::{
context::MapContext,
render::{
eventually::Eventually::Initialized, resource::IndexEntry, tile_view_pattern::TileInView,
RenderState, Renderer,
},
render::{eventually::Eventually::Initialized, resource::IndexEntry, RenderState, Renderer},
schedule::Stage,
};
@ -40,28 +37,28 @@ impl Stage for QueueStage {
let index = buffer_pool.index();
for tile_in_view in tile_view_pattern.iter() {
let TileInView { shape, fallback } = &tile_in_view;
let coords = shape.coords;
for view_tile in tile_view_pattern.iter() {
let coords = &view_tile.coords();
tracing::trace!("Drawing tile at {coords}");
let shape_to_render = fallback.as_ref().unwrap_or(shape);
// draw tile normal or the source e.g. parent or children
view_tile.render(|source_shape| {
// Draw masks for all source_shapes
mask_phase.add(source_shape.clone());
// Draw mask
mask_phase.add(tile_in_view.clone());
let Some(entries) = index.get_layers(&shape_to_render.coords) else {
tracing::trace!("No layers found at {}", &shape_to_render.coords);
continue;
let Some(entries) = index.get_layers(&source_shape.coords()) else {
tracing::trace!("No layers found at {}", &source_shape.coords());
return;
};
let mut layers_to_render: Vec<&IndexEntry> = Vec::from_iter(entries);
layers_to_render.sort_by_key(|entry| entry.style_layer.index);
let mut layers_to_render: Vec<&IndexEntry> = Vec::from_iter(entries);
layers_to_render.sort_by_key(|entry| entry.style_layer.index);
for entry in layers_to_render {
// Draw tile
tile_phase.add((entry.clone(), shape_to_render.clone()))
}
for entry in layers_to_render {
// Draw tile
tile_phase.add((entry.clone(), source_shape.clone()))
}
});
}
}
}

View File

@ -9,7 +9,7 @@ use crate::{
shaders,
shaders::{Shader, ShaderTileMetadata},
tile_pipeline::TilePipeline,
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_SIZE},
tile_view_pattern::{TileViewPattern, DEFAULT_TILE_VIEW_PATTERN_SIZE},
Renderer,
},
schedule::Stage,
@ -83,7 +83,7 @@ impl Stage for ResourceStage {
let tile_view_buffer_desc = wgpu::BufferDescriptor {
label: Some("tile view buffer"),
size: size_of::<ShaderTileMetadata>() as wgpu::BufferAddress
* DEFAULT_TILE_VIEW_SIZE,
* DEFAULT_TILE_VIEW_PATTERN_SIZE,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
};
@ -104,9 +104,11 @@ impl Stage for ResourceStage {
tile_shader.describe_vertex(),
tile_shader.describe_fragment(),
true,
true,
false,
false,
false,
true,
)
.describe_render_pipeline()
.initialize(device);
@ -122,6 +124,7 @@ impl Stage for ResourceStage {
let mask_shader = shaders::TileMaskShader {
format: surface.surface_format(),
draw_colors: false,
debug_lines: false,
};
TilePipeline::new(
@ -130,6 +133,30 @@ impl Stage for ResourceStage {
mask_shader.describe_fragment(),
false,
true,
true,
false,
false,
true,
)
.describe_render_pipeline()
.initialize(device)
});
state.debug_pipeline.initialize(|| {
let mask_shader = shaders::TileMaskShader {
format: surface.surface_format(),
draw_colors: true,
debug_lines: true,
};
TilePipeline::new(
*settings,
mask_shader.describe_vertex(),
mask_shader.describe_fragment(),
false,
false,
false,
true,
false,
false,
)

View File

@ -133,7 +133,7 @@ impl UploadStage {
})
.collect::<Vec<_>>();
buffer_pool.update_feature_metadata(&queue, entry, &feature_metadata);
buffer_pool.update_feature_metadata(queue, entry, &feature_metadata);
}
}
}
@ -163,15 +163,16 @@ impl UploadStage {
view_region: &ViewRegion,
) {
let Initialized(buffer_pool) = buffer_pool else { return; };
// Upload all tessellated layers which are in view
for coords in view_region.iter() {
let Some(available_layers) =
tile_repository.iter_loaded_layers_at(&buffer_pool, &coords) else { continue; };
tile_repository.iter_loaded_layers_at(buffer_pool, &coords) else { continue; };
for style_layer in &style.layers {
let source_layer = style_layer.source_layer.as_ref().unwrap(); // TODO: Remove unwrap
let Some(message) = available_layers
let Some(stored_layer) = available_layers
.iter()
.find(|layer| source_layer.as_str() == layer.layer_name()) else { continue; };
@ -181,7 +182,7 @@ impl UploadStage {
.and_then(|paint| paint.get_color())
.map(|color| color.into());
match message {
match stored_layer {
StoredLayer::UnavailableLayer { .. } => {}
StoredLayer::TessellatedLayer {
coords,
@ -190,7 +191,7 @@ impl UploadStage {
..
} => {
let allocate_feature_metadata =
tracing::span!(tracing::Level::TRACE, "allocate_layer_geometry");
tracing::span!(tracing::Level::TRACE, "allocate_feature_metadata");
let guard = allocate_feature_metadata.enter();
let feature_metadata = (0..feature_indices.len()) // FIXME: Iterate over actual featrues

View File

@ -13,9 +13,14 @@ use crate::{
pub struct TilePipeline {
bind_globals: bool,
/// Is the depth stencil used?
depth_stencil_enabled: bool,
/// This pipeline updates the stenctil
update_stencil: bool,
/// Force a write and ignore stencil
debug_stencil: bool,
wireframe: bool,
multisampling: bool,
settings: RendererSettings,
vertex_state: VertexState,
@ -28,15 +33,19 @@ impl TilePipeline {
vertex_state: VertexState,
fragment_state: FragmentState,
bind_globals: bool,
depth_stencil_enabled: bool,
update_stencil: bool,
debug_stencil: bool,
wireframe: bool,
multisampling: bool,
) -> Self {
TilePipeline {
bind_globals,
depth_stencil_enabled,
update_stencil,
debug_stencil,
wireframe,
multisampling,
settings,
vertex_state,
fragment_state,
@ -104,20 +113,28 @@ impl RenderPipeline for TilePipeline {
conservative: false,
unclipped_depth: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: self.settings.depth_texture_format,
depth_write_enabled: !self.update_stencil,
depth_compare: wgpu::CompareFunction::Greater,
stencil: wgpu::StencilState {
front: stencil_state,
back: stencil_state,
read_mask: 0xff, // Applied to stencil values being read from the stencil buffer
write_mask: 0xff, // Applied to fragment stencil values before being written to the stencil buffer
},
bias: wgpu::DepthBiasState::default(),
}),
depth_stencil: if !self.depth_stencil_enabled {
None
} else {
Some(wgpu::DepthStencilState {
format: self.settings.depth_texture_format,
depth_write_enabled: !self.update_stencil,
depth_compare: wgpu::CompareFunction::Greater,
stencil: wgpu::StencilState {
front: stencil_state,
back: stencil_state,
read_mask: 0xff, // Applied to stencil values being read from the stencil buffer
write_mask: 0xff, // Applied to fragment stencil values before being written to the stencil buffer
},
bias: wgpu::DepthBiasState::default(),
})
},
multisample: wgpu::MultisampleState {
count: self.settings.msaa.samples,
count: if self.multisampling {
self.settings.msaa.samples
} else {
1
},
mask: !0,
alpha_to_coverage_enabled: false,
},

View File

@ -15,42 +15,91 @@ use crate::{
tessellation::IndexDataType,
};
pub const DEFAULT_TILE_VIEW_SIZE: wgpu::BufferAddress = 32 * 4;
/// The tile mask pattern assigns each tile a value which can be used for stencil testing.
pub struct TileViewPattern<Q, B> {
in_view: Vec<TileInView>,
buffer: BackingBuffer<B>,
phantom_q: PhantomData<Q>,
}
pub const DEFAULT_TILE_VIEW_PATTERN_SIZE: wgpu::BufferAddress = 32 * 4;
pub const CHILDREN_SEARCH_DEPTH: usize = 4;
/// Defines the exact location where a specific tile on the map is rendered. It defines the shape
/// of the tile with its location for the current zoom factor.
#[derive(Clone)]
pub struct TileShape {
pub zoom_factor: f64,
coords: WorldTileCoords,
pub coords: WorldTileCoords,
// TODO: optimization, `zoom_factor` and `transform` are no longer required if `buffer_range` is Some()
zoom_factor: f64,
transform: Matrix4<f64>,
pub transform: Matrix4<f64>,
pub buffer_range: Range<wgpu::BufferAddress>,
buffer_range: Option<Range<wgpu::BufferAddress>>,
}
impl TileShape {
fn new(coords: WorldTileCoords, zoom: Zoom, index: u64) -> Self {
const STRIDE: u64 = size_of::<ShaderTileMetadata>() as u64;
fn new(coords: WorldTileCoords, zoom: Zoom) -> Self {
Self {
coords,
zoom_factor: zoom.scale_to_tile(&coords),
transform: coords.transform_for_zoom(zoom),
buffer_range: index * STRIDE..(index + 1) * STRIDE,
buffer_range: None,
}
}
fn set_buffer_range(&mut self, index: u64) {
const STRIDE: u64 = size_of::<ShaderTileMetadata>() as u64;
self.buffer_range = Some(index * STRIDE..(index + 1) * STRIDE);
}
pub fn buffer_range(&self) -> Range<wgpu::BufferAddress> {
self.buffer_range.as_ref().unwrap().clone()
}
pub fn coords(&self) -> WorldTileCoords {
self.coords
}
}
/// This defines the source tile shaped from which the content for the `target` is taken.
/// For example if the target is `(0, 0, 1)` (of [`ViewTile`]) , we might use
/// `SourceShapes::Parent((0, 0, 0))` as source.
/// Similarly if we have the target `(0, 0, 0)` we might use
/// `SourceShapes::Children((0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1))` as sources.
#[derive(Clone)]
pub struct TileInView {
pub shape: TileShape,
pub enum SourceShapes {
/// Parent tile is the source. We construct the `target` from parts of a parent.
Parent(TileShape),
/// Children are the source. We construct the `target` from multiple children.
Children(Vec<TileShape>),
/// Source and target are equal, so no need to differentiate. We render the `source` shape
/// exactly at the `target`.
SourceEqTarget(TileShape),
/// No data available so nothing to render
None,
}
pub fallback: Option<TileShape>,
/// Defines the `target` tile and its `source` from which data tile data comes.
#[derive(Clone)]
pub struct ViewTile {
target: WorldTileCoords,
source: SourceShapes,
}
impl ViewTile {
pub fn coords(&self) -> WorldTileCoords {
self.target
}
pub fn render<F>(&self, mut callback: F)
where
F: FnMut(&TileShape),
{
match &self.source {
SourceShapes::Parent(source_shape) => callback(source_shape),
SourceShapes::Children(source_shapes) => {
for shape in source_shapes {
callback(shape)
}
}
SourceShapes::SourceEqTarget(source_shape) => callback(source_shape),
SourceShapes::None => {}
}
}
}
#[derive(Debug)]
@ -67,6 +116,13 @@ impl<B> BackingBuffer<B> {
}
}
/// The tile mask pattern assigns each tile a value which can be used for stencil testing.
pub struct TileViewPattern<Q, B> {
in_view: Vec<ViewTile>,
buffer: BackingBuffer<B>,
phantom_q: PhantomData<Q>,
}
impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
pub fn new(buffer: BackingBufferDescriptor<B>) -> Self {
Self {
@ -92,8 +148,6 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
) {
self.in_view.clear();
let mut index = 0;
let pool_index = buffer_pool.index();
for coords in view_region.iter() {
@ -101,34 +155,39 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
continue;
}
let shape = TileShape::new(coords, zoom, index);
let source_shapes = {
if pool_index.has_tile(&coords) {
SourceShapes::SourceEqTarget(TileShape::new(coords, zoom))
} else if let Some(parent_coords) = pool_index.get_available_parent(&coords) {
log::info!("Could not find data at {coords}. Falling back to {parent_coords}");
index += 1;
SourceShapes::Parent(TileShape::new(parent_coords, zoom))
} else if let Some(children_coords) =
pool_index.get_available_children(&coords, CHILDREN_SEARCH_DEPTH)
{
log::info!(
"Could not find data at {coords}. Falling back children: {children_coords:?}"
);
let fallback = {
if !pool_index.has_tile(&coords) {
if let Some(fallback_coords) = pool_index.get_tile_coords_fallback(&coords) {
tracing::trace!(
"Could not find data at {coords}. Falling back to {fallback_coords}"
);
let shape = TileShape::new(fallback_coords, zoom, index);
index += 1;
Some(shape)
} else {
None
}
SourceShapes::Children(
children_coords
.iter()
.map(|child_coord| TileShape::new(*child_coord, zoom))
.collect(),
)
} else {
None
SourceShapes::None
}
};
self.in_view.push(TileInView { shape, fallback });
self.in_view.push(ViewTile {
target: coords,
source: source_shapes,
});
}
}
pub fn iter(&self) -> impl Iterator<Item = &TileInView> + '_ {
pub fn iter(&self) -> impl Iterator<Item = &ViewTile> + '_ {
self.in_view.iter()
}
@ -137,30 +196,34 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
}
#[tracing::instrument(skip_all)]
pub fn upload_pattern(&self, queue: &Q, view_proj: &ViewProjection) {
pub fn upload_pattern(&mut self, queue: &Q, view_proj: &ViewProjection) {
let mut buffer = Vec::with_capacity(self.in_view.len());
for tile in &self.in_view {
let mut add_to_buffer = |shape: &mut TileShape| {
shape.set_buffer_range(buffer.len() as u64);
buffer.push(ShaderTileMetadata {
// We are casting here from 64bit to 32bit, because 32bit is more performant and is
// better supported.
transform: view_proj
.to_model_view_projection(tile.shape.transform)
.to_model_view_projection(shape.transform)
.downcast()
.into(),
zoom_factor: tile.shape.zoom_factor as f32,
zoom_factor: shape.zoom_factor as f32,
});
};
if let Some(fallback_shape) = &tile.fallback {
buffer.push(ShaderTileMetadata {
// We are casting here from 64bit to 32bit, because 32bit is more performant and is
// better supported.
transform: view_proj
.to_model_view_projection(fallback_shape.transform)
.downcast()
.into(),
zoom_factor: fallback_shape.zoom_factor as f32,
});
for view_tile in &mut self.in_view {
match &mut view_tile.source {
SourceShapes::Parent(source_shape) => {
add_to_buffer(source_shape);
}
SourceShapes::Children(source_shapes) => {
for source_shape in source_shapes {
add_to_buffer(source_shape);
}
}
SourceShapes::SourceEqTarget(source_shape) => add_to_buffer(source_shape),
SourceShapes::None => {}
}
}
@ -173,7 +236,9 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
queue.write_buffer(&self.buffer.inner, 0, raw_buffer);
}
pub fn stencil_reference_value(&self, world_coords: &WorldTileCoords) -> u8 {
/// 2D version of [`TileViewPattern::stencil_reference_value_3d`]. This is kept for reference.
/// For the 2D case we do not take into account the Z value, so only 4 cases exist.
pub fn stencil_reference_value_2d(&self, world_coords: &WorldTileCoords) -> u8 {
match (world_coords.x, world_coords.y) {
(x, y) if x % 2 == 0 && y % 2 == 0 => 2,
(x, y) if x % 2 == 0 && y % 2 != 0 => 1,
@ -182,4 +247,18 @@ impl<Q: Queue<B>, B> TileViewPattern<Q, B> {
_ => unreachable!(),
}
}
/// Returns unique stencil reference values for WorldTileCoords which are 3D.
/// Tiles from arbitrary `z` can lie next to each other, because we mix tiles from
/// different levels based on availability.
pub fn stencil_reference_value_3d(&self, world_coords: &WorldTileCoords) -> u8 {
const CASES: u8 = 4;
match (world_coords.x, world_coords.y, u8::from(world_coords.z)) {
(x, y, z) if x % 2 == 0 && y % 2 == 0 => 0 + z * CASES,
(x, y, z) if x % 2 == 0 && y % 2 != 0 => 1 + z * CASES,
(x, y, z) if x % 2 != 0 && y % 2 == 0 => 2 + z * CASES,
(x, y, z) if x % 2 != 0 && y % 2 != 0 => 3 + z * CASES,
_ => unreachable!(),
}
}
}

View File

@ -39,7 +39,7 @@ pub struct HeadedPipelineProcessor<T: Transferables, HC: HttpClient, C: Context<
phantom_hc: PhantomData<HC>,
}
impl<'c, T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
impl<T: Transferables, HC: HttpClient, C: Context<T, HC>> PipelineProcessor
for HeadedPipelineProcessor<T, HC, C>
{
fn tile_finished(&mut self, coords: &WorldTileCoords) -> Result<(), PipelineError> {

View File

@ -42,6 +42,7 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
// available this might cause frame drops.
while let Some(result) = self.kernel.apc().receive() {
match result {
// TODO: deduplicate
Message::TileTessellated(message) => {
let coords = message.coords();
tracing::event!(tracing::Level::ERROR, %coords, "tile request done: {}", &coords);
@ -49,9 +50,8 @@ impl<E: Environment> Stage for PopulateTileStore<E> {
tracing::trace!("Tile at {} finished loading", coords);
log::warn!("Tile at {} finished loading", coords);
tile_repository.mark_tile_succeeded(&coords);
tile_repository.mark_tile_succeeded(&coords).unwrap(); // TODO: unwrap
}
// FIXME: deduplicate
Message::LayerUnavailable(message) => {
let layer: StoredLayer = message.to_stored_layer();

View File

@ -142,13 +142,8 @@ impl<E: Environment> RequestStage<E> {
coords: WorldTileCoords,
layers: &HashSet<String>,
) {
/* TODO: is this still required?
if !tile_repository.is_layers_missing(coords, layers) {
return Ok(false);
}*/
if tile_repository.has_tile(&coords) {
tile_repository.create_tile(coords);
if tile_repository.is_tile_pending_or_done(&coords) {
tile_repository.mark_tile_pending(coords).unwrap(); // TODO: Remove unwrap
tracing::event!(tracing::Level::ERROR, %coords, "tile request started: {}", &coords);

View File

@ -26,7 +26,6 @@
}
},
"../lib": {
"name": "maplibre-rs",
"version": "0.0.1",
"license": "MIT",
"dependencies": {
@ -262,13 +261,13 @@
"dev": true
},
"node_modules/@types/express": {
"version": "4.17.14",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz",
"integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==",
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
"integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==",
"dev": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.18",
"@types/express-serve-static-core": "^4.17.31",
"@types/qs": "*",
"@types/serve-static": "*"
}
@ -312,9 +311,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"version": "18.11.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz",
"integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==",
"dev": true
},
"node_modules/@types/qs": {
@ -621,9 +620,9 @@
}
},
"node_modules/ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
"integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
@ -702,9 +701,9 @@
}
},
"node_modules/anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": {
"normalize-path": "^3.0.0",
@ -896,9 +895,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001430",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001430.tgz",
"integrity": "sha512-IB1BXTZKPDVPM7cnV4iaKaHxckvdr/3xtctB3f7Hmenx3qYBhGtTZ//7EllK66aKXW98Lx0+7Yr0kxBtIt3tzg==",
"version": "1.0.30001439",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz",
"integrity": "sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==",
"dev": true,
"funding": [
{
@ -1398,9 +1397,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
"integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
"integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.4",
@ -1640,9 +1639,9 @@
}
},
"node_modules/fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
"dev": true,
"dependencies": {
"reusify": "^1.0.4"
@ -1907,9 +1906,9 @@
"dev": true
},
"node_modules/globby": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz",
"integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==",
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz",
"integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==",
"dev": true,
"dependencies": {
"dir-glob": "^3.0.1",
@ -2178,9 +2177,9 @@
}
},
"node_modules/ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
"integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
"dev": true,
"engines": {
"node": ">= 4"
@ -2449,9 +2448,9 @@
}
},
"node_modules/loader-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
@ -2521,9 +2520,9 @@
}
},
"node_modules/memfs": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.10.tgz",
"integrity": "sha512-0bCUP+L79P4am30yP1msPzApwuMQG23TjwlwdHeEV5MxioDR1a0AgB0T9FfggU52eJuDCq8WVwb5ekznFyWiTQ==",
"version": "3.4.12",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz",
"integrity": "sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw==",
"dev": true,
"dependencies": {
"fs-monkey": "^1.0.3"
@ -2689,9 +2688,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.7.tgz",
"integrity": "sha512-EJ3rzxL9pTWPjk5arA0s0dgXpnyiAbJDE6wHT62g7VsgrgQgmmZ+Ru++M1BFofncWja+Pnn3rEr3fieRySAdKQ==",
"dev": true
},
"node_modules/normalize-path": {
@ -3699,9 +3698,9 @@
}
},
"node_modules/terser": {
"version": "5.15.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz",
"integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==",
"version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
"integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
@ -3833,9 +3832,9 @@
}
},
"node_modules/ts-loader": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz",
"integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==",
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz",
"integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==",
"dev": true,
"dependencies": {
"chalk": "^4.1.0",
@ -3914,9 +3913,9 @@
}
},
"node_modules/typescript": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@ -4038,9 +4037,9 @@
}
},
"node_modules/webpack": {
"version": "5.74.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
"integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
"version": "5.75.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
"dev": true,
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@ -4340,9 +4339,9 @@
"dev": true
},
"node_modules/ws": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"engines": {
"node": ">=10.0.0"
@ -4574,13 +4573,13 @@
"dev": true
},
"@types/express": {
"version": "4.17.14",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.14.tgz",
"integrity": "sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==",
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz",
"integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==",
"dev": true,
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^4.17.18",
"@types/express-serve-static-core": "^4.17.31",
"@types/qs": "*",
"@types/serve-static": "*"
}
@ -4624,9 +4623,9 @@
"dev": true
},
"@types/node": {
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"version": "18.11.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.15.tgz",
"integrity": "sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==",
"dev": true
},
"@types/qs": {
@ -4906,9 +4905,9 @@
"dev": true
},
"ajv": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
"integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
@ -4957,9 +4956,9 @@
}
},
"anymatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
@ -5112,9 +5111,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001430",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001430.tgz",
"integrity": "sha512-IB1BXTZKPDVPM7cnV4iaKaHxckvdr/3xtctB3f7Hmenx3qYBhGtTZ//7EllK66aKXW98Lx0+7Yr0kxBtIt3tzg==",
"version": "1.0.30001439",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001439.tgz",
"integrity": "sha512-1MgUzEkoMO6gKfXflStpYgZDlFM7M/ck/bgfVCACO5vnAf0fXoNVHdWtqGU+MYca+4bL9Z5bpOVmR33cWW9G2A==",
"dev": true
},
"chalk": {
@ -5487,9 +5486,9 @@
"dev": true
},
"enhanced-resolve": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
"integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz",
"integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==",
"dev": true,
"requires": {
"graceful-fs": "^4.2.4",
@ -5684,9 +5683,9 @@
"dev": true
},
"fastq": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
"integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
"integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
@ -5873,9 +5872,9 @@
"dev": true
},
"globby": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz",
"integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==",
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz",
"integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==",
"dev": true,
"requires": {
"dir-glob": "^3.0.1",
@ -6079,9 +6078,9 @@
}
},
"ignore": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
"integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
"dev": true
},
"import-local": {
@ -6268,9 +6267,9 @@
"dev": true
},
"loader-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@ -6338,9 +6337,9 @@
"dev": true
},
"memfs": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.10.tgz",
"integrity": "sha512-0bCUP+L79P4am30yP1msPzApwuMQG23TjwlwdHeEV5MxioDR1a0AgB0T9FfggU52eJuDCq8WVwb5ekznFyWiTQ==",
"version": "3.4.12",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.12.tgz",
"integrity": "sha512-BcjuQn6vfqP+k100e0E9m61Hyqa//Brp+I3f0OBmN0ATHlFA8vx3Lt8z57R3u2bPqe3WGDBC+nF72fTH7isyEw==",
"dev": true,
"requires": {
"fs-monkey": "^1.0.3"
@ -6467,9 +6466,9 @@
"dev": true
},
"node-releases": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.7.tgz",
"integrity": "sha512-EJ3rzxL9pTWPjk5arA0s0dgXpnyiAbJDE6wHT62g7VsgrgQgmmZ+Ru++M1BFofncWja+Pnn3rEr3fieRySAdKQ==",
"dev": true
},
"normalize-path": {
@ -7219,9 +7218,9 @@
"dev": true
},
"terser": {
"version": "5.15.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.1.tgz",
"integrity": "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw==",
"version": "5.16.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.16.1.tgz",
"integrity": "sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",
@ -7311,9 +7310,9 @@
"dev": true
},
"ts-loader": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.1.tgz",
"integrity": "sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==",
"version": "9.4.2",
"resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz",
"integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==",
"dev": true,
"requires": {
"chalk": "^4.1.0",
@ -7360,9 +7359,9 @@
}
},
"typescript": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"version": "4.9.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
"dev": true
},
"unpipe": {
@ -7446,9 +7445,9 @@
}
},
"webpack": {
"version": "5.74.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
"integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
"version": "5.75.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz",
"integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==",
"dev": true,
"requires": {
"@types/eslint-scope": "^3.7.3",
@ -7648,9 +7647,9 @@
"dev": true
},
"ws": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.10.0.tgz",
"integrity": "sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==",
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"dev": true,
"requires": {}
},