From 24ee0dfcb9ef0c1abd1b6031366dc81c8b99da6d Mon Sep 17 00:00:00 2001 From: Basit Ayantunde Date: Sat, 30 Nov 2024 23:38:41 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20new=20core=20types=20+=20refactorin?= =?UTF-8?q?g=20&=20API=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-tidy | 2 - ashura/engine/canvas.cc | 6 +- ashura/engine/engine.cc | 57 ++- ashura/engine/font_impl.h | 2 +- ashura/engine/scene.h | 2 +- ashura/engine/view.h | 8 +- ashura/engine/view_system.h | 56 +-- ashura/engine/window.cc | 1 - ashura/std/async.cc | 4 +- ashura/std/buffer.h | 2 +- ashura/std/enum.py | 7 + ashura/std/format.h | 2 + ashura/std/lambda.h | 77 +--- ashura/std/math.h | 136 ++++--- ashura/std/mem.h | 16 +- ashura/std/obj.h | 90 +++-- ashura/std/sparse_vec.h | 301 ---------------- ashura/std/super.h | 103 ++++++ ashura/std/tests/sparse_vec.cc | 2 +- ashura/std/text.h | 4 +- ashura/std/types.h | 6 - ashura/std/vec.h | 639 ++++++++++++++++++++++++++++----- 22 files changed, 900 insertions(+), 623 deletions(-) delete mode 100644 ashura/std/sparse_vec.h create mode 100644 ashura/std/super.h diff --git a/.clang-tidy b/.clang-tidy index cfaba1e2..8e5b6b98 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -15,8 +15,6 @@ CheckOptions: bugprone-dangling-handle.HandleClasses: > std::basic_string_view std::experimental::basic_string_view - ash::Span - ash::Buffer WarningsAsErrors: "" HeaderFilterRegex: "" FormatStyle: file diff --git a/ashura/engine/canvas.cc b/ashura/engine/canvas.cc index b9602cc2..65d4c395 100644 --- a/ashura/engine/canvas.cc +++ b/ashura/engine/canvas.cc @@ -429,7 +429,7 @@ static inline void flush_batch(Canvas & c) .params_ssbo = ctx.ngons.descriptor, .textures = ctx.gpu.texture_views, .index_counts = - span(ctx.canvas.ngon_index_counts).slice(batch.objects)}; + ctx.canvas.ngon_index_counts.span().slice(batch.objects)}; ctx.passes.ngon->encode(ctx.gpu, ctx.enc, params); }); return; @@ -666,7 +666,7 @@ Canvas & Canvas::text(ShapeInfo const & info, TextBlock const & block, f32 cursor = space_align(block_width, ln.metrics.width, alignment) - ln.metrics.width * 0.5F; for (TextRun const & run : - span(layout.runs).slice(ln.first_run, ln.num_runs)) + layout.runs.span().slice(ln.first_run, ln.num_runs)) { FontStyle const & font_style = block.fonts[run.style]; TextStyle const & run_style = style.runs[run.style]; @@ -822,7 +822,7 @@ Canvas & Canvas::triangles(ShapeInfo const & info, Span points, ngon_vertices.extend_copy(points).unwrap(); ngon_indices.extend_copy(idx).unwrap(); - for (u32 & v : span(ngon_indices).slice(first_index)) + for (u32 & v : ngon_indices.span().slice(first_index)) { v += first_vertex; } diff --git a/ashura/engine/engine.cc b/ashura/engine/engine.cc index bfa8f183..95a84620 100644 --- a/ashura/engine/engine.cc +++ b/ashura/engine/engine.cc @@ -75,8 +75,8 @@ EngineCfg EngineCfg::parse(AllocatorImpl allocator, Span json) auto id = entry.escaped_key().value(); auto path = entry.value().get_string().value(); out.shaders - .insert(vec(allocator, span(id)).unwrap(), - vec(allocator, span(path)).unwrap()) + .insert(vec(span(id), allocator).unwrap(), + vec(span(path), allocator).unwrap()) .unwrap(); } @@ -86,14 +86,14 @@ EngineCfg EngineCfg::parse(AllocatorImpl allocator, Span json) auto id = entry.escaped_key().value(); auto path = entry.value().get_string().value(); out.fonts - .insert(vec(allocator, span(id)).unwrap(), - vec(allocator, span(path)).unwrap()) + .insert(vec(span(id), allocator).unwrap(), + vec(span(path), allocator).unwrap()) .unwrap(); } std::string_view default_font_sv = config["default_font"].get_string().value(); - out.default_font = vec(allocator, default_font_sv).unwrap(); + out.default_font = vec(default_font_sv, allocator).unwrap(); // check that it is a valid entry fonts[default_font_sv].get_string().value(); @@ -104,8 +104,8 @@ EngineCfg EngineCfg::parse(AllocatorImpl allocator, Span json) auto id = entry.escaped_key().value(); auto path = entry.value().get_string().value(); out.images - .insert(vec(allocator, span(id)).unwrap(), - vec(allocator, span(path)).unwrap()) + .insert(vec(span(id), allocator).unwrap(), + vec(span(path), allocator).unwrap()) .unwrap(); } @@ -238,20 +238,19 @@ void Engine::init(AllocatorImpl allocator, void * app, .unwrap(); cfg.shaders.iter([&](Vec & id, Vec & path) { - Vec resolved_path = vec(allocator, assets_dir).unwrap(); + Vec resolved_path = vec(assets_dir, allocator).unwrap(); path_append(resolved_path, path).unwrap(); - async::once([shader_id = vec(allocator, span(id)).unwrap(), + async::once([shader_id = vec(id, allocator).unwrap(), shader_path = std::move(resolved_path), sem = sem.alias(), allocator]() mutable { - logger->trace("Loading shader ", span(shader_id), " from ", - span(shader_path)); + logger->trace("Loading shader ", shader_id, " from ", shader_path); Vec data{allocator}; if (Result result = read_file(shader_path, data); !result) { - logger->error("Unable to load shader at ", span(shader_path), + logger->error("Unable to load shader at ", shader_path, ", IO Error: ", result.err()); sem->increment(1); return; @@ -265,14 +264,14 @@ void Engine::init(AllocatorImpl allocator, void * app, data_u32.resize_uninit(data.size() >> 2).unwrap(); - mem::copy(span(data), span(data_u32).as_u8()); + mem::copy(data.span(), data_u32.span().as_u8()); - logger->trace("Loaded shader ", span(shader_id), " from file"); + logger->trace("Loaded shader ", shader_id, " from file"); async::once( [shader_id = std::move(shader_id), sem = std::move(sem), data_u32 = std::move(data_u32)]() mutable { - logger->trace("Sending shader ", span(shader_id), " to GPU"); + logger->trace("Sending shader ", shader_id, " to GPU"); gpu::Shader shader = engine->device @@ -290,13 +289,13 @@ void Engine::init(AllocatorImpl allocator, void * app, }); cfg.fonts.iter([&](Vec & id, Vec & path) { - Vec resolved_path = vec(allocator, assets_dir).unwrap(); + Vec resolved_path = vec(assets_dir, allocator).unwrap(); path_append(resolved_path, path).unwrap(); - async::once([font_id = vec(allocator, span(id)).unwrap(), + async::once([font_id = vec(id, allocator).unwrap(), font_path = std::move(resolved_path), sem = sem.alias(), allocator]() mutable { - logger->trace("Loading font ", span(font_id), " from ", span(font_path)); + logger->trace("Loading font ", font_id, " from ", font_path); Vec data{allocator}; @@ -304,7 +303,7 @@ void Engine::init(AllocatorImpl allocator, void * app, if (!read_result) { - logger->error("Unable to load font at ", span(font_path), + logger->error("Unable to load font at ", font_path, ", IO Error: ", read_result.err()); sem->increment(1); return; @@ -314,7 +313,7 @@ void Engine::init(AllocatorImpl allocator, void * app, if (!decode_result) { - logger->error("Unable to decode font at ", span(font_path), + logger->error("Unable to decode font at ", font_path, "Error: ", decode_result.err()); sem->increment(1); return; @@ -322,21 +321,20 @@ void Engine::init(AllocatorImpl allocator, void * app, Dyn font = decode_result.unwrap(); - logger->trace("Loaded font ", span(font_id), " from file"); + logger->trace("Loaded font ", font_id, " from file"); u32 const font_height = 64; - logger->trace("Rasterizing font ", span(font_id), " @", font_height, - "px "); + logger->trace("Rasterizing font ", font_id, " @", font_height, "px "); font->rasterize(font_height, allocator).unwrap(); - logger->trace("Rasterized font ", span(font_id)); + logger->trace("Rasterized font ", font_id); async::once( [font_id = std::move(font_id), sem = std::move(sem), font = std::move(font), allocator]() mutable { - logger->trace("Uploading font ", span(font_id), " to GPU"); + logger->trace("Uploading font ", font_id, " to GPU"); font->upload_to_device(engine->gpu_ctx, allocator); @@ -354,8 +352,7 @@ void Engine::init(AllocatorImpl allocator, void * app, scheduler->execute_main_thread_loop(1ms, 2ms); } - engine->default_font_name = - vec(allocator, span(cfg.default_font)).unwrap(); + engine->default_font_name = vec(cfg.default_font, allocator).unwrap(); engine->default_font = engine->assets.fonts[engine->default_font_name].get(); engine->renderer.acquire(engine->gpu_ctx, engine->assets); @@ -437,7 +434,7 @@ void Engine::recreate_swapchain_() for (gpu::ColorSpace cp : preferred_color_spaces) { - Span sel = find_if(span(formats), [&](gpu::SurfaceFormat a) { + Span sel = find_if(formats.span(), [&](gpu::SurfaceFormat a) { return a.color_space == cp; }); if (!sel.is_empty()) @@ -455,7 +452,7 @@ void Engine::recreate_swapchain_() for (gpu::PresentMode pm : preferred_present_modes) { - if (!find(span(present_modes), pm).is_empty()) + if (!find(present_modes.span(), pm).is_empty()) { found_present_mode = true; present_mode = pm; @@ -567,7 +564,7 @@ void Engine::run(View & view) .render_area = {.offset = {}, .extent = gpu_ctx.screen_fb.extent}, .num_layers = 1, - .color_attachments = span(attachments), + .color_attachments = attachments, .depth_attachment = {}, .stencil_attachment = {}}, .viewport = gpu::Viewport{.offset = {0, 0}, diff --git a/ashura/engine/font_impl.h b/ashura/engine/font_impl.h index c46002f7..1b61badc 100644 --- a/ashura/engine/font_impl.h +++ b/ashura/engine/font_impl.h @@ -234,7 +234,7 @@ struct FontImpl : Font rect_pack::pack_rects(pack_context, rects.data() + num_packed, num_rasterized_glyphs - num_packed); auto [just_packed, unpacked] = - partition(span(rects).slice(num_packed), + partition(rects.span().slice(num_packed), [](rect_pack::rect const & r) { return r.was_packed; }); for (u32 i = num_packed; i < (num_packed + just_packed.span); i++) { diff --git a/ashura/engine/scene.h b/ashura/engine/scene.h index 3ce0e8d4..44818a49 100644 --- a/ashura/engine/scene.h +++ b/ashura/engine/scene.h @@ -4,8 +4,8 @@ #include "ashura/std/math.h" #include "ashura/std/option.h" #include "ashura/std/result.h" -#include "ashura/std/sparse_vec.h" #include "ashura/std/types.h" +#include "ashura/std/vec.h" namespace ash { diff --git a/ashura/engine/view.h b/ashura/engine/view.h index 2c278ac1..bad90ff5 100644 --- a/ashura/engine/view.h +++ b/ashura/engine/view.h @@ -324,8 +324,8 @@ struct ViewContext /// @brief makes a zoom transform matrix relative to the center of a viewport. /// defines the translation and scaling components. /// @return zoom transform matrix -constexpr Mat3Affine scroll_transform(Vec2 viewport_extent, Vec2 view_extent, - Vec2 t, f32 scale) +constexpr Affine3 scroll_transform(Vec2 viewport_extent, Vec2 view_extent, + Vec2 t, f32 scale) { Vec2 const low = -0.5F * viewport_extent + 0.5F * view_extent; Vec2 const high = 0.5F * viewport_extent - 0.5F * view_extent; @@ -439,7 +439,7 @@ struct ViewLayout { Vec2 extent = {}; Vec2 viewport_extent = {}; - Mat3Affine viewport_transform = Mat3Affine::identity(); + Affine3 viewport_transform = Affine3::identity(); Option fixed_position = None; }; @@ -584,7 +584,7 @@ struct View /// @brief Called when the viewport is needed to zoom itself, scaling its /// inner extent /// @param zoom zoom to apply to the inner extent - constexpr virtual void zoom(Mat3Affine const & transform) + constexpr virtual void zoom(Affine3 const & transform) { (void) transform; } diff --git a/ashura/engine/view_system.h b/ashura/engine/view_system.h index b13f6c8d..de0b52a2 100644 --- a/ashura/engine/view_system.h +++ b/ashura/engine/view_system.h @@ -89,19 +89,19 @@ struct ViewSystem BitVec is_esc_input; BitVec is_viewport; - Vec centers; - Vec extents; - Vec viewport_extents; - Vec viewport_transforms; - BitVec is_fixed_positioned; - Vec fixed_positions; - Vec z_indices; - Vec stacking_contexts; - - Vec transforms; - Vec clips; - Vec z_ordering; - Vec focus_ordering; + Vec centers; + Vec extents; + Vec viewport_extents; + Vec viewport_transforms; + BitVec is_fixed_positioned; + Vec fixed_positions; + Vec z_indices; + Vec stacking_contexts; + + Vec transforms; + Vec clips; + Vec z_ordering; + Vec focus_ordering; explicit ViewSystem(AllocatorImpl allocator) : views{allocator}, @@ -322,9 +322,9 @@ struct ViewSystem void focus_order() { - iota(span(focus_ordering), 0U); + iota(focus_ordering.span(), 0U); - indirect_sort(span(focus_ordering), [&](u32 a, u32 b) { + indirect_sort(focus_ordering.span(), [&](u32 a, u32 b) { return tab_indices[a] < tab_indices[b]; }); @@ -349,7 +349,7 @@ struct ViewSystem { ViewNode const & node = nodes[i]; views[i]->size(extents[i], - span(extents).slice(node.first_child, node.num_children)); + extents.span().slice(node.first_child, node.num_children)); } centers[0] = Vec2::splat(0); @@ -361,8 +361,8 @@ struct ViewSystem i--; ViewNode const & node = nodes[i]; ViewLayout layout = views[i]->fit( - extents[i], span(extents).slice(node.first_child, node.num_children), - span(centers).slice(node.first_child, node.num_children)); + extents[i], extents.span().slice(node.first_child, node.num_children), + centers.span().slice(node.first_child, node.num_children)); extents[i] = layout.extent; viewport_extents[i] = layout.viewport_extent; viewport_transforms[i] = layout.viewport_transform; @@ -375,16 +375,16 @@ struct ViewSystem // transform views to canvas-space - transforms[0] = Mat3Affine::identity(); + transforms[0] = Affine3::identity(); for (u32 i = 0; i < n; i++) { - ViewNode const & node = nodes[i]; + ViewNode const & node = nodes[i]; // parent-space to local viewport-space transformation matrix - Mat3Affine const & viewport_transform = viewport_transforms[i]; + Affine3 const & viewport_transform = viewport_transforms[i]; // accumulated transform of all ancestors, determines position until this // parent - Mat3Affine const & ancestor_transform = transforms[i]; + Affine3 const & ancestor_transform = transforms[i]; for (u32 c = node.first_child; c < (node.first_child + node.num_children); c++) { @@ -402,8 +402,8 @@ struct ViewSystem for (u32 i = 0; i < n; i++) { - Mat3Affine const & transform = transforms[i]; - f32 const zoom = transform[0][0]; + Affine3 const & transform = transforms[i]; + f32 const zoom = transform[0][0]; centers[i] = ash::transform(transform, Vec2{0, 0}) + viewport_extent * 0.5F; extents[i] = extents[i] * zoom; @@ -418,7 +418,7 @@ struct ViewSystem } } - fill(span(clips), CRect::from_offset({0, 0}, viewport_extent)); + fill(clips.span(), CRect::from_offset({0, 0}, viewport_extent)); /// recursive view clipping for (u32 i = 0; i < n; i++) @@ -471,7 +471,7 @@ struct ViewSystem ViewNode const & node = nodes[i]; z_indices[i] = views[i]->z_index( z_indices[i], - span(z_indices).slice(node.first_child, node.num_children)); + z_indices.span().slice(node.first_child, node.num_children)); } stacking_contexts[0] = 0; @@ -484,10 +484,10 @@ struct ViewSystem } } - iota(span(z_ordering), 0U); + iota(z_ordering.span(), 0U); // sort layers with priority: stacking_context, z_index, node depth - indirect_sort(span(z_ordering), [&](u32 a, u32 b) { + indirect_sort(z_ordering.span(), [&](u32 a, u32 b) { if (stacking_contexts[a] < stacking_contexts[b]) { return true; diff --git a/ashura/engine/window.cc b/ashura/engine/window.cc index 40863387..bd7bef4b 100644 --- a/ashura/engine/window.cc +++ b/ashura/engine/window.cc @@ -4,7 +4,6 @@ #include "SDL3/SDL_vulkan.h" #include "ashura/gpu/vulkan.h" #include "ashura/std/error.h" -#include "ashura/std/sparse_vec.h" #include "ashura/std/vec.h" namespace ash diff --git a/ashura/std/async.cc b/ashura/std/async.cc index cb1ba513..5de4bcae 100644 --- a/ashura/std/async.cc +++ b/ashura/std/async.cc @@ -616,10 +616,10 @@ void Scheduler::init(AllocatorImpl allocator, std::thread::id main_thread_id, u32 const num_worker_threads = worker_thread_sleep.size32(); impl->dedicated_threads = - pin_vec(allocator, num_dedicated_threads).unwrap(); + pin_vec(num_dedicated_threads, allocator).unwrap(); impl->worker_threads = - pin_vec(allocator, num_worker_threads).unwrap(); + pin_vec(num_worker_threads, allocator).unwrap(); for (u32 i = 0; i < num_dedicated_threads; i++) { diff --git a/ashura/std/buffer.h b/ashura/std/buffer.h index 3ae9f90b..43ebd299 100644 --- a/ashura/std/buffer.h +++ b/ashura/std/buffer.h @@ -42,7 +42,7 @@ struct [[nodiscard]] Buffer return false; } - obj::copy(in, data_ + size_); + obj::copy_assign(in, data_ + size_); size_ += in.size(); return true; diff --git a/ashura/std/enum.py b/ashura/std/enum.py index 79235248..73715bd1 100644 --- a/ashura/std/enum.py +++ b/ashura/std/enum.py @@ -258,6 +258,13 @@ def out(code): return file.write(code) {{ }} + +template +constexpr decltype(auto) visit(Visitors && ... visitors); + +template +constexpr decltype(auto) visit(Visitors && ... visitors) const; + """ } }}; diff --git a/ashura/std/format.h b/ashura/std/format.h index bb292901..be848119 100644 --- a/ashura/std/format.h +++ b/ashura/std/format.h @@ -26,6 +26,8 @@ struct Spec { Style style = Style::Decimal; i32 precision = 0; + // [ ] implement N-ary list printing + // u64 list_limit = U64_MAX; }; /// @param push function to be called to insert text into the format context. diff --git a/ashura/std/lambda.h b/ashura/std/lambda.h index 12cfb492..e06dc11a 100644 --- a/ashura/std/lambda.h +++ b/ashura/std/lambda.h @@ -7,33 +7,8 @@ namespace ash { -typedef void (*ErasedDestroy)(void * storage); - -typedef void (*ErasedRelocate)(void * src_storage, void * dst_storage); - -template -inline constexpr ErasedDestroy pFn_DESTRUCT = - [](void * storage) { reinterpret_cast(storage)->~T(); }; - -template -inline constexpr ErasedRelocate pFn_RELOCATE = - [](void * src_storage, void * dst_storage) { - T * src = reinterpret_cast(src_storage); - T * dst = reinterpret_cast(dst_storage); - obj::relocate(Span{src, 1}, dst); - }; - -template -inline constexpr ErasedRelocate pFn_RELOCATE_NON_OVERLAPPING = - [](void * src_storage, void * dst_storage) { - T * src = reinterpret_cast(src_storage); - T * dst = reinterpret_cast(dst_storage); - obj::relocate_non_overlapping(Span{src, 1}, dst); - }; - -static constexpr usize DEFAULT_LAMBDA_CAPACITY = 40; - -static constexpr usize DEFAULT_LAMBDA_ALIGNMENT = 64; +static constexpr usize DEFAULT_LAMBDA_ALIGNMENT = 32; +static constexpr usize DEFAULT_LAMBDA_CAPACITY = 48; template @@ -43,8 +18,8 @@ struct Lambda; /// It only requires that the erased type be relocatable (moved and destroyed). /// In order to prevent accessing elements from dynamic offsets, we require that the Type be placeable at the start of the storage. /// -/// x64 minimum size of Lambda = 4 Pointers = 32-bytes -/// x64 ideal configuration: Alignment = 64 bytes, Capacity = 40 bytes (Gives 64 bytes total. Fits exactly into one cacheline. +/// x64 minimum size of Lambda = 3 Pointers = 24-bytes +/// x64 ideal configuration: Alignment = 32 bytes, Capacity = 48 bytes (Gives 64 bytes total. Fits exactly into one cacheline. /// /// @tparam R return type /// @tparam Args argument types @@ -58,13 +33,13 @@ struct Lambda using Thunk = R (*)(void *, Args...); + using Lifecycle = PFnLifecycle; + alignas(ALIGNMENT) mutable u8 storage[CAPACITY]; Thunk thunk = nullptr; - ErasedRelocate relocator = noop; - - ErasedDestroy destructor = noop; + Lifecycle lifecycle = noop; explicit constexpr Lambda() = default; @@ -75,8 +50,7 @@ struct Lambda constexpr Lambda(Functor && functor, Thunk thunk = &FunctorThunk::thunk) : thunk{thunk}, - relocator{pFn_RELOCATE_NON_OVERLAPPING}, - destructor{pFn_DESTRUCT} + lifecycle{pFn_LIFECYCLE} { new (storage) Functor{static_cast(functor)}; } @@ -89,13 +63,11 @@ struct Lambda requires (ALIGNMENT >= SrcAlignment && CAPACITY >= SrcCapacity) constexpr Lambda(Lambda && other) : thunk{other.thunk}, - relocator{other.relocator}, - destructor{other.destructor} + lifecycle{other.lifecycle} { - other.relocator(other.storage, storage); - other.thunk = nullptr; - other.relocator = noop; - other.destructor = noop; + other.lifecycle(other.storage, storage); + other.thunk = nullptr; + other.lifecycle = noop; } template @@ -111,21 +83,19 @@ struct Lambda } } - destructor(storage); - other.relocator(other.storage, storage); - relocator = other.relocator; - other.relocator = noop; - destructor = other.destructor; - other.destructor = noop; - thunk = other.thunk; - other.thunk = nullptr; + lifecycle(storage, nullptr); + other.lifecycle(other.storage, storage); + lifecycle = other.lifecycle; + other.lifecycle = noop; + thunk = other.thunk; + other.thunk = nullptr; return *this; } constexpr ~Lambda() { - destructor(storage); + lifecycle(storage, nullptr); } constexpr R operator()(Args... args) const @@ -134,13 +104,4 @@ struct Lambda } }; -// TODO (lamarrr): -template -constexpr auto lambda(Functor && functor) -{ - using Sig = int(int, int); - return Lambda{ - static_cast(functor)}; -} - } // namespace ash diff --git a/ashura/std/math.h b/ashura/std/math.h index 2c0c4c2a..75a7a831 100644 --- a/ashura/std/math.h +++ b/ashura/std/math.h @@ -1733,7 +1733,7 @@ constexpr Mat3 & operator/=(Mat3 & a, Mat3 const & b) return a; } -struct Mat3Affine +struct Affine3 { static constexpr Vec3 trailing_row = Vec3{0, 0, 1}; Vec3 rows[2] = {}; @@ -1755,9 +1755,9 @@ struct Mat3Affine }; } - static constexpr Mat3Affine identity() + static constexpr Affine3 identity() { - return Mat3Affine{ + return Affine3{ .rows = {{1, 0, 0}, {0, 1, 0}} }; } @@ -1778,49 +1778,48 @@ struct Mat3Affine } }; -constexpr bool operator==(Mat3Affine const & a, Mat3Affine const & b) +constexpr bool operator==(Affine3 const & a, Affine3 const & b) { return a[0] == b[0] && a[1] == b[1]; } -constexpr bool operator!=(Mat3Affine const & a, Mat3Affine const & b) +constexpr bool operator!=(Affine3 const & a, Affine3 const & b) { return a[0] != b[0] || a[1] != b[1]; } -constexpr Mat3Affine operator+(Mat3Affine const & a, Mat3Affine const & b) +constexpr Affine3 operator+(Affine3 const & a, Affine3 const & b) { - return Mat3Affine{ + return Affine3{ .rows = {a[0] + b[0], a[1] + b[1]} }; } -constexpr Mat3Affine operator-(Mat3Affine const & a, Mat3Affine const & b) +constexpr Affine3 operator-(Affine3 const & a, Affine3 const & b) { - return Mat3Affine{ + return Affine3{ .rows = {a[0] - b[0], a[1] - b[1]} }; } -constexpr Vec3 operator*(Mat3Affine const & a, Vec3 const & b) +constexpr Vec3 operator*(Affine3 const & a, Vec3 const & b) { - return Vec3{dot(a[0], b), dot(a[1], b), dot(Mat3Affine::trailing_row, b)}; + return Vec3{dot(a[0], b), dot(a[1], b), dot(Affine3::trailing_row, b)}; } -constexpr Mat3 operator*(Mat3Affine const & a, Mat3 const & b) +constexpr Mat3 operator*(Affine3 const & a, Mat3 const & b) { return Mat3{ .rows = { {dot(a[0], b.x()), dot(a[0], b.y()), dot(a[0], b.z())}, {dot(a[1], b.x()), dot(a[1], b.y()), dot(a[1], b.z())}, - {dot(Mat3Affine::trailing_row, b.x()), - dot(Mat3Affine::trailing_row, b.y()), - dot(Mat3Affine::trailing_row, b.z())}, + {dot(Affine3::trailing_row, b.x()), dot(Affine3::trailing_row, b.y()), + dot(Affine3::trailing_row, b.z())}, } }; } -constexpr Mat3 operator*(Mat3 const & a, Mat3Affine const & b) +constexpr Mat3 operator*(Mat3 const & a, Affine3 const & b) { return Mat3{ .rows = {{dot(a[0], b.x()), dot(a[0], b.y()), dot(a[0], b.z())}, @@ -1829,40 +1828,40 @@ constexpr Mat3 operator*(Mat3 const & a, Mat3Affine const & b) }; } -constexpr Mat3Affine operator*(Mat3Affine const & a, Mat3Affine const & b) +constexpr Affine3 operator*(Affine3 const & a, Affine3 const & b) { - return Mat3Affine{ + return Affine3{ .rows = {{dot(a[0], b.x()), dot(a[0], b.y()), dot(a[0], b.z())}, {dot(a[1], b.x()), dot(a[1], b.y()), dot(a[1], b.z())}} }; } -constexpr Mat3Affine operator/(Mat3Affine const & a, Mat3Affine const & b) +constexpr Affine3 operator/(Affine3 const & a, Affine3 const & b) { - return Mat3Affine{ + return Affine3{ .rows = {a[0] / b[0], a[1] / b[1]} }; } -constexpr Mat3Affine & operator+=(Mat3Affine & a, Mat3Affine const & b) +constexpr Affine3 & operator+=(Affine3 & a, Affine3 const & b) { a = a + b; return a; } -constexpr Mat3Affine & operator-=(Mat3Affine & a, Mat3Affine const & b) +constexpr Affine3 & operator-=(Affine3 & a, Affine3 const & b) { a = a - b; return a; } -constexpr Mat3Affine & operator*=(Mat3Affine & a, Mat3Affine const & b) +constexpr Affine3 & operator*=(Affine3 & a, Affine3 const & b) { a = a * b; return a; } -constexpr Mat3Affine & operator/=(Mat3Affine & a, Mat3Affine const & b) +constexpr Affine3 & operator/=(Affine3 & a, Affine3 const & b) { a = a / b; return a; @@ -2002,7 +2001,7 @@ constexpr Mat4 & operator/=(Mat4 & a, Mat4 const & b) return a; } -struct Mat4Affine +struct Affine4 { static constexpr Vec4 trailing_row = Vec4{0, 0, 0, 1}; Vec4 rows[3] = {}; @@ -2024,9 +2023,9 @@ struct Mat4Affine }; } - static constexpr Mat4Affine identity() + static constexpr Affine4 identity() { - return Mat4Affine{ + return Affine4{ .rows = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}} }; } @@ -2052,37 +2051,37 @@ struct Mat4Affine } }; -constexpr bool operator==(Mat4Affine const & a, Mat4Affine const & b) +constexpr bool operator==(Affine4 const & a, Affine4 const & b) { return a[0] == b[0] && a[1] == b[1] && a[2] == b[2]; } -constexpr bool operator!=(Mat4Affine const & a, Mat4Affine const & b) +constexpr bool operator!=(Affine4 const & a, Affine4 const & b) { return a[0] != b[0] || a[1] != b[1] || a[2] != b[2]; } -constexpr Mat4Affine operator+(Mat4Affine const & a, Mat4Affine const & b) +constexpr Affine4 operator+(Affine4 const & a, Affine4 const & b) { - return Mat4Affine{ + return Affine4{ .rows = {a[0] + b[0], a[1] + b[1], a[2] + b[2]} }; } -constexpr Mat4Affine operator-(Mat4Affine const & a, Mat4Affine const & b) +constexpr Affine4 operator-(Affine4 const & a, Affine4 const & b) { - return Mat4Affine{ + return Affine4{ .rows = {a[0] - b[0], a[1] - b[1], a[2] - b[2]} }; } -constexpr Vec4 operator*(Mat4Affine const & a, Vec4 const & b) +constexpr Vec4 operator*(Affine4 const & a, Vec4 const & b) { return Vec4{dot(a[0], b), dot(a[1], b), dot(a[2], b), - dot(Mat4Affine::trailing_row, b)}; + dot(Affine4::trailing_row, b)}; } -constexpr Mat4 operator*(Mat4Affine const & a, Mat4 const & b) +constexpr Mat4 operator*(Affine4 const & a, Mat4 const & b) { return Mat4{ .rows = { @@ -2092,15 +2091,14 @@ constexpr Mat4 operator*(Mat4Affine const & a, Mat4 const & b) dot(a[1], b.w())}, {dot(a[2], b.x()), dot(a[2], b.y()), dot(a[2], b.z()), dot(a[2], b.w())}, - {dot(Mat4Affine::trailing_row, b.x()), - dot(Mat4Affine::trailing_row, b.y()), - dot(Mat4Affine::trailing_row, b.z()), - dot(Mat4Affine::trailing_row, b.w())}, + {dot(Affine4::trailing_row, b.x()), dot(Affine4::trailing_row, b.y()), + dot(Affine4::trailing_row, b.z()), + dot(Affine4::trailing_row, b.w())}, } }; } -constexpr Mat4 operator*(Mat4 const & a, Mat4Affine const & b) +constexpr Mat4 operator*(Mat4 const & a, Affine4 const & b) { return Mat4{ .rows = { @@ -2116,9 +2114,9 @@ constexpr Mat4 operator*(Mat4 const & a, Mat4Affine const & b) }; } -constexpr Mat4Affine operator*(Mat4Affine const & a, Mat4Affine const & b) +constexpr Affine4 operator*(Affine4 const & a, Affine4 const & b) { - return Mat4Affine{ + return Affine4{ .rows = {{dot(a[0], b.x()), dot(a[0], b.y()), dot(a[0], b.z()), dot(a[0], b.w())}, {dot(a[1], b.x()), dot(a[1], b.y()), dot(a[1], b.z()), @@ -2128,32 +2126,32 @@ constexpr Mat4Affine operator*(Mat4Affine const & a, Mat4Affine const & b) }; } -constexpr Mat4Affine operator/(Mat4Affine const & a, Mat4Affine const & b) +constexpr Affine4 operator/(Affine4 const & a, Affine4 const & b) { - return Mat4Affine{ + return Affine4{ .rows = {a[0] / b[0], a[1] / b[1], a[2] / b[2]} }; } -constexpr Mat4Affine & operator+=(Mat4Affine & a, Mat4Affine const & b) +constexpr Affine4 & operator+=(Affine4 & a, Affine4 const & b) { a = a + b; return a; } -constexpr Mat4Affine & operator-=(Mat4Affine & a, Mat4Affine const & b) +constexpr Affine4 & operator-=(Affine4 & a, Affine4 const & b) { a = a - b; return a; } -constexpr Mat4Affine & operator*=(Mat4Affine & a, Mat4Affine const & b) +constexpr Affine4 & operator*=(Affine4 & a, Affine4 const & b) { a = a * b; return a; } -constexpr Mat4Affine & operator/=(Mat4Affine & a, Mat4Affine const & b) +constexpr Affine4 & operator/=(Affine4 & a, Affine4 const & b) { a = a / b; return a; @@ -2347,63 +2345,63 @@ constexpr Mat4 inverse(Mat4 const & a) return Mat4::splat(1.0F / determinant(a)) * adjoint(a); } -constexpr Mat3Affine translate2d(Vec2 t) +constexpr Affine3 translate2d(Vec2 t) { - return Mat3Affine{ + return Affine3{ .rows = {{1, 0, t.x}, {0, 1, t.y}} }; } -constexpr Mat4Affine translate3d(Vec3 t) +constexpr Affine4 translate3d(Vec3 t) { - return Mat4Affine{ + return Affine4{ .rows = {{1, 0, 0, t.x}, {0, 1, 0, t.y}, {0, 0, 1, t.z}} }; } -constexpr Mat3Affine scale2d(Vec2 s) +constexpr Affine3 scale2d(Vec2 s) { - return Mat3Affine{ + return Affine3{ .rows = {{s.x, 0, 0}, {0, s.y, 0}} }; } -constexpr Mat4Affine scale3d(Vec3 s) +constexpr Affine4 scale3d(Vec3 s) { - return Mat4Affine{ + return Affine4{ .rows = {{s.x, 0, 0, 0}, {0, s.y, 0, 0}, {0, 0, s.z, 0}} }; } -inline Mat3Affine rotate2d(f32 radians) +inline Affine3 rotate2d(f32 radians) { - return Mat3Affine{ + return Affine3{ .rows = {{cos(radians), -sin(radians), 0}, {sin(radians), cos(radians), 0}} }; } -inline Mat4Affine rotate3d_x(f32 radians) +inline Affine4 rotate3d_x(f32 radians) { - return Mat4Affine{ + return Affine4{ .rows = {{1, 0, 0, 0}, {0, cos(radians), -sin(radians), 0}, {0, sin(radians), cos(radians), 0}} }; } -inline Mat4Affine rotate3d_y(f32 radians) +inline Affine4 rotate3d_y(f32 radians) { - return Mat4Affine{ + return Affine4{ .rows = {{cos(radians), 0, sin(radians), 0}, {0, 1, 0, 0}, {-sin(radians), 0, cos(radians), 0}} }; } -inline Mat4Affine rotate3d_z(f32 radians) +inline Affine4 rotate3d_z(f32 radians) { - return Mat4Affine{ + return Affine4{ .rows = {{cos(radians), -sin(radians), 0, 0}, {sin(radians), cos(radians), 0, 0}, {0, 0, 1, 0}} @@ -2416,7 +2414,7 @@ constexpr Vec2 transform(Mat3 const & t, Vec2 value) return Vec2{v.x, v.y}; } -constexpr Vec2 transform(Mat3Affine const & t, Vec2 value) +constexpr Vec2 transform(Affine3 const & t, Vec2 value) { Vec3 v = t * Vec3{value.x, value.y, 1}; return Vec2{v.x, v.y}; @@ -2428,7 +2426,7 @@ constexpr Vec3 transform(Mat4 const & t, Vec3 value) return Vec3{v.x, v.y, v.z}; } -constexpr Vec3 transform(Mat4Affine const & t, Vec3 value) +constexpr Vec3 transform(Affine4 const & t, Vec3 value) { Vec4 v = t * Vec4{value.x, value.y, 1}; return Vec3{v.x, v.y, v.z}; @@ -2865,10 +2863,10 @@ constexpr bool overlaps(Box const & a, Box const & b) /// @param z_near The distance to the near clipping plane. /// @param z_far The distance to the far clipping plane. This value /// MUST NOT be equal to zero. zfar MUST be greater than znear. -constexpr Mat4Affine orthographic(f32 x_mag, f32 y_mag, f32 z_near, f32 z_far) +constexpr Affine4 orthographic(f32 x_mag, f32 y_mag, f32 z_near, f32 z_far) { f32 const z_diff = z_near - z_far; - return Mat4Affine{ + return Affine4{ {{1 / x_mag, 0, 0, 0}, {0, 1 / y_mag, 0, 0}, {0, 0, 2 / z_diff, (z_far + z_near) / z_diff}} diff --git a/ashura/std/mem.h b/ashura/std/mem.h index c7cf7005..66414ba0 100644 --- a/ashura/std/mem.h +++ b/ashura/std/mem.h @@ -46,7 +46,7 @@ bool is_ptr_aligned(usize alignment, T * p) namespace mem { -template +template void copy(Span src, U * dst) { if (src.is_empty()) [[unlikely]] @@ -57,13 +57,13 @@ void copy(Span src, U * dst) std::memcpy(dst, src.data(), src.size_bytes()); } -template +template void copy(Span src, Span dst) { copy(src, dst.data()); } -template +template void move(Span src, U * dst) { if (src.is_empty()) [[unlikely]] @@ -74,13 +74,13 @@ void move(Span src, U * dst) std::memmove(dst, src.data(), src.size_bytes()); } -template +template void move(Span src, Span dst) { move(src, dst.data()); } -template +template void zero(T * dst, usize n) { if (n == 0) [[unlikely]] @@ -91,13 +91,13 @@ void zero(T * dst, usize n) std::memset(dst, 0, sizeof(T) * n); } -template +template void zero(Span dst) { zero(dst.data(), dst.size()); } -template +template void fill(T * dst, usize n, u8 byte) { if (n == 0) [[unlikely]] @@ -108,7 +108,7 @@ void fill(T * dst, usize n, u8 byte) std::memset(dst, byte, sizeof(T) * n); } -template +template void fill(Span dst, u8 byte) { fill(dst.data(), dst.size(), byte); diff --git a/ashura/std/obj.h b/ashura/std/obj.h index 42899597..20918adf 100644 --- a/ashura/std/obj.h +++ b/ashura/std/obj.h @@ -9,7 +9,7 @@ namespace ash namespace obj { -template +template constexpr void default_construct(Span dst) { for (T * iter = dst.begin(); iter != dst.end(); iter++) @@ -18,7 +18,7 @@ constexpr void default_construct(Span dst) } } -template +template constexpr void move_construct(Span src, U * dst) { for (T * in = src.begin(); in != src.end(); in++, dst++) @@ -27,13 +27,13 @@ constexpr void move_construct(Span src, U * dst) } } -template +template constexpr void move_construct(Span src, Span dst) { move_construct(src, dst.data()); } -template +template constexpr void copy_construct(Span src, U * dst) { for (T * in = src.begin(); in != src.end(); in++, dst++) @@ -42,7 +42,7 @@ constexpr void copy_construct(Span src, U * dst) } } -template +template constexpr void copy_construct(Span src, Span dst) { copy_construct(src, dst.data()); @@ -60,23 +60,23 @@ constexpr void destruct(Span src) } } -template -constexpr void move(Span src, U * dst) +template +constexpr void move_assign(Span src, U * dst) { for (T * in = src.begin(); in != src.end(); in++, dst++) { - *in = (T &&) (*dst); + *in = static_cast(*dst); } } -template -constexpr void move(Span src, Span dst) +template +constexpr void move_assign(Span src, Span dst) { - move(src, dst.data()); + move_assign(src, dst.data()); } -template -constexpr void copy(Span src, U * dst) +template +constexpr void copy_assign(Span src, U * dst) { for (T * in = src.begin(); in != src.end(); in++, dst++) { @@ -84,16 +84,16 @@ constexpr void copy(Span src, U * dst) } } -template -constexpr void copy(Span src, Span dst) +template +constexpr void copy_assign(Span src, Span dst) { - copy(src, dst.data()); + copy_assign(src, dst.data()); } /// @brief move-construct object from src to an uninitialized memory range /// dst_mem and destroy object at src_mem, leaving src's objects uninitialized. -template -constexpr void relocate(Span src, U * dst) +template +constexpr void relocate(Span src, T * dst) { if constexpr (TriviallyRelocatable) { @@ -106,8 +106,8 @@ constexpr void relocate(Span src, U * dst) } } -template -constexpr void relocate(Span src, Span dst) +template +constexpr void relocate(Span src, Span dst) { relocate(src, dst.data()); } @@ -115,8 +115,8 @@ constexpr void relocate(Span src, Span dst) /// @brief same as relocate but for non-overlapping memory placements /// /// @note src_mem and dst_mem must not be same nor overlapping. -template -constexpr void relocate_non_overlapping(Span src, U * dst) +template +constexpr void relocate_non_overlapping(Span src, T * dst) { if constexpr (TriviallyRelocatable) { @@ -129,11 +129,53 @@ constexpr void relocate_non_overlapping(Span src, U * dst) } } -template -constexpr void relocate_non_overlapping(Span src, Span dst) +template +constexpr void relocate_non_overlapping(Span src, Span dst) { relocate_non_overlapping(src, dst.data()); } } // namespace obj + +using PFnDestruct = void (*)(void *); + +using PFnRelocate = void (*)(void *, void *); + +using PFnLifecycle = void (*)(void *, void *); + +template +inline constexpr PFnDestruct pFn_DESTRUCT = [](void * mem) { + T * obj = reinterpret_cast(mem); + + obj::destruct(Span{obj, 1}); +}; + +template +inline constexpr PFnRelocate pFn_RELOCATE = [](void * src_mem, void * dst_mem) { + T * src = reinterpret_cast(src_mem); + T * dst = reinterpret_cast(dst_mem); + + obj::relocate_non_overlapping(Span{src, 1}, dst); +}; + +/// @brief An object lifecycle function that relocates and destroys an object. +/// When the destination memory is nullptr, +/// the object is to be destroyed. +/// Otherwise, it should relocate itself to the destination memory. +template +inline constexpr PFnLifecycle pFn_LIFECYCLE = + [](void * src_mem, void * dst_mem) { + T * src = reinterpret_cast(src_mem); + T * dst = reinterpret_cast(dst_mem); + + if (dst_mem == nullptr) [[unlikely]] + { + src->~T(); + } + else + { + obj::relocate_non_overlapping(Span{src, 1}, dst); + } + }; + } // namespace ash diff --git a/ashura/std/sparse_vec.h b/ashura/std/sparse_vec.h deleted file mode 100644 index 083b0ee0..00000000 --- a/ashura/std/sparse_vec.h +++ /dev/null @@ -1,301 +0,0 @@ -/// SPDX-License-Identifier: MIT -#pragma once - -#include "ashura/std/types.h" -#include "ashura/std/vec.h" - -namespace ash -{ - -/// @brief Sparse Vector (a.k.a Sparse Set) are used for stable ID-tagging of -/// objects in high-perf scenarious i.e. ECS, where a stable identity is needed -/// for objects and they need to be processed in batches for efficiency. They -/// have an indirect index into their elements, although they don't guarantee -/// stability of the addresses of the elements they guarantee that the IDs -/// persist until the id is released. Unlike typical Sparse Sets, Sparse Vec's -/// elements are always contiguous without holes in them, making them suitable -/// for operations like batch-processing and branchless SIMD. -/// -/// @tparam V dense containers for the properties, i.e. Vec, Vec -/// @param index_to_id id of data, ordered relative to {data} -/// @param id_to_index map of id to index in {data} -/// @param size the number of valid elements in the sparse set -/// @param capacity the number of elements the sparse set has capacity for, -/// includes reserved but unallocated ids pointing to valid but uninitialized -/// memory -/// -/// The index and id either point to valid indices/ids or are an implicit free -/// list of ids and indices masked by RELEASE_MASK -/// -/// -template -struct SparseVec -{ - static constexpr u64 RELEASE_MASK = U64_MAX >> 1; - static constexpr u64 STUB = U64_MAX; - - using Dense = Tuple; - - Vec index_to_id = {}; - Vec id_to_index = {}; - Dense dense = {}; - u64 free_id_head = STUB; - - explicit SparseVec(AllocatorImpl allocator) : - index_to_id{allocator}, - id_to_index{allocator}, - dense{}, - free_id_head{STUB} - { - } - - SparseVec() : SparseVec{default_allocator} - { - } - - SparseVec(SparseVec const &) = delete; - - SparseVec & operator=(SparseVec const &) = delete; - - SparseVec(SparseVec && other) : - index_to_id{std::move(other.index_to_id)}, - id_to_index{std::move(other.id_to_index)}, - dense{std::move(other.dense)}, - free_id_head{other.free_id_head} - { - other.free_id_head = STUB; - } - - SparseVec & operator=(SparseVec && other) - { - if (this == &other) [[unlikely]] - { - return *this; - } - index_to_id = std::move(other.index_to_id); - id_to_index = std::move(other.id_to_index); - dense = std::move(other.dense); - free_id_head = other.free_id_head; - return *this; - } - - constexpr bool is_empty() const - { - return size() == 0; - } - - constexpr u64 size() const - { - return static_cast(index_to_id.size()); - } - - constexpr void clear() - { - apply([](auto &... d) { (d.clear(), ...); }, dense); - id_to_index.clear(); - index_to_id.clear(); - free_id_head = STUB; - } - - constexpr void reset() - { - apply([](auto &... d) { (d.reset(), ...); }, dense); - id_to_index.reset(); - index_to_id.reset(); - free_id_head = STUB; - } - - constexpr void uninit() - { - apply([](auto &... d) { (d.uninit(), ...); }, dense); - id_to_index.uninit(); - index_to_id.uninit(); - } - - constexpr bool is_valid_id(u64 id) const - { - return id < id_to_index.size() && !(id_to_index[id] & RELEASE_MASK); - } - - constexpr bool is_valid_index(u64 index) const - { - return index < size(); - } - - constexpr u64 operator[](u64 id) const - { - return id_to_index[id]; - } - - constexpr u64 to_index(u64 id) const - { - return id_to_index[id]; - } - - constexpr Result try_to_index(u64 id) const - { - if (!is_valid_id(id)) [[unlikely]] - { - return Err{}; - } - - return Ok{to_index(id)}; - } - - constexpr u64 to_id(u64 index) const - { - return index_to_id[index]; - } - - constexpr Result try_to_id(u64 index) const - { - if (!is_valid_index(index)) [[unlikely]] - { - return Err{}; - } - - return Ok{to_id(index)}; - } - - constexpr void erase(u64 id) - { - u64 const index = id_to_index[id]; - u64 const last = size() - 1; - - if (index != last) - { - apply([index, last](auto &... d) { (d.swap(index, last), ...); }, dense); - } - - apply([](auto &... d) { (d.pop(), ...); }, dense); - - // adjust id and index mapping - if (index != last) - { - id_to_index[index_to_id[last]] = index; - index_to_id[index] = index_to_id[last]; - } - - id_to_index[id] = free_id_head | RELEASE_MASK; - free_id_head = id; - index_to_id.pop(); - } - - constexpr Result<> try_erase(u64 id) - { - if (!is_valid_id(id)) [[unlikely]] - { - return Err{}; - } - erase(id); - return Ok{}; - } - - constexpr Result<> reserve(u64 target_capacity) - { - if (!(id_to_index.reserve(target_capacity) && - index_to_id.reserve(target_capacity))) [[unlikely]] - { - return Err{}; - } - - bool failed = apply( - [&](auto &... d) { - return (false || ... || !d.reserve(target_capacity)); - }, - dense); - - if (failed) [[unlikely]] - { - return Err{}; - } - - return Ok{}; - } - - constexpr Result<> grow(u64 target_size) - { - if (!(id_to_index.grow(target_size) && index_to_id.grow(target_size))) - [[unlikely]] - { - return Err{}; - } - - bool failed = apply( - [target_size](auto &... d) { - return (false || ... || !d.grow(target_size)); - }, - dense); - - if (failed) [[unlikely]] - { - return Err{}; - } - - return Ok{}; - } - - /// make new id and map the unique id to the unique index - constexpr Result make_id(u64 index) - { - if (free_id_head != STUB) - { - u64 id = free_id_head; - id_to_index[id] = index; - free_id_head = ~RELEASE_MASK & id_to_index[free_id_head]; - return Ok{id}; - } - else - { - if (!id_to_index.push(index)) [[unlikely]] - { - return Err{}; - } - u64 id = static_cast(id_to_index.size() - 1); - return Ok{id}; - } - } - - template - struct Pusher - { - template - static constexpr void push(Tuple & t, Head && head, Tail &&... tail) - { - get(t).push(static_cast(head)).unwrap(); - if constexpr (sizeof...(tail) != 0) - { - Pusher::push(t, static_cast(tail)...); - } - } - }; - - template - constexpr Result push(Args &&... args) - requires (sizeof...(Args) == sizeof...(V)) - { - u64 const index = size(); - - if (!grow(size() + 1)) [[unlikely]] - { - return Err{}; - } - - Result id = make_id(index); - - if (!id) [[unlikely]] - { - return Err{}; - } - - if (!index_to_id.push(id.unwrap())) [[unlikely]] - { - return Err{}; - } - - Pusher<0>::push(dense, static_cast(args)...); - return id; - } -}; - -} // namespace ash diff --git a/ashura/std/super.h b/ashura/std/super.h new file mode 100644 index 00000000..1d9e3718 --- /dev/null +++ b/ashura/std/super.h @@ -0,0 +1,103 @@ +/// SPDX-License-Identifier: MIT +#pragma once +#include "ashura/std/log.h" +#include "ashura/std/obj.h" +#include "ashura/std/types.h" + +namespace ash +{ + +static constexpr usize DEFAULT_SUPER_ALIGNMENT = 32; +static constexpr usize DEFAULT_SUPER_CAPACITY = 48; + +template +requires (Alignment >= alignof(Base) && Capacity >= sizeof(Base)) +struct Super +{ + static constexpr usize ALIGNMENT = Alignment; + static constexpr usize CAPACITY = Capacity; + + using Lifecycle = PFnLifecycle; + using Slicer = Base * (*) (void *); + + static Base * noop_slicer(void *) + { + logger->panic("Tried to slice an empty Super type"); + } + + alignas(ALIGNMENT) mutable u8 storage[CAPACITY]; + + Slicer slicer = noop_slicer; + + Lifecycle lifecycle = noop; + + explicit constexpr Super() = default; + + template + requires (Derives && ALIGNMENT >= alignof(Object) && + CAPACITY >= sizeof(Object)) + constexpr Super(Object && object) : + slicer{+[](void * storage) -> Base * { + Object * ptr = reinterpret_cast(storage); + return ptr; + }}, + lifecycle{pFn_LIFECYCLE} + { + new (storage) Object{static_cast(object)}; + } + + constexpr Super(Super const &) = delete; + + constexpr Super & operator=(Super const &) = delete; + + template + requires (ALIGNMENT >= SrcAlignment && CAPACITY >= SrcCapacity) + constexpr Super(Super && other) : + slicer{other.slicer}, + lifecycle{other.lifecycle} + { + other.lifecycle(other.storage, storage); + other.lifecycle = noop; + other.slicer = noop_slicer; + } + + template + requires (ALIGNMENT >= SrcAlignment && CAPACITY >= SrcCapacity) + constexpr Super & operator=(Super && other) + { + if constexpr (ALIGNMENT == SrcAlignment && CAPACITY == SrcCapacity) + { + if (this == &other) [[unlikely]] + { + return *this; + } + } + + lifecycle(storage, nullptr); + other.lifecycle(other.storage, storage); + lifecycle = other.lifecycle; + other.lifecycle = noop; + slicer = other.slicer; + other.slicer = noop_slicer; + + return *this; + } + + constexpr operator Base &() const + { + return get(); + } + + constexpr Base & get() const + { + return *slicer(storage); + } + + constexpr ~Super() + { + lifecycle(storage, nullptr); + } +}; + +} // namespace ash diff --git a/ashura/std/tests/sparse_vec.cc b/ashura/std/tests/sparse_vec.cc index 0c92f26f..ff51ed12 100644 --- a/ashura/std/tests/sparse_vec.cc +++ b/ashura/std/tests/sparse_vec.cc @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: MIT #include "gtest/gtest.h" -#include "ashura/std/sparse_vec.h" +#include "ashura/std/vec.h" #include #include diff --git a/ashura/std/text.h b/ashura/std/text.h index e5dabbb1..0d6e94a2 100644 --- a/ashura/std/text.h +++ b/ashura/std/text.h @@ -116,7 +116,7 @@ inline Result<> utf8_decode(Span encoded, Vec & decoded) { return Err{}; } - (void) utf8_decode(encoded, span(decoded).slice(first, count)); + (void) utf8_decode(encoded, decoded.span().slice(first, count)); return Ok{}; } @@ -132,7 +132,7 @@ inline Result<> utf8_decode(Span encoded, Vec & decoded) return Err{}; } usize const count = - utf8_encode(decoded, span(encoded).slice(first, max_count)); + utf8_encode(decoded, encoded.span().slice(first, max_count)); encoded.resize_uninit(first + count).unwrap(); return Ok{}; } diff --git a/ashura/std/types.h b/ashura/std/types.h index a7240346..7cef9fe2 100644 --- a/ashura/std/types.h +++ b/ashura/std/types.h @@ -1481,12 +1481,6 @@ struct BitSpan } }; -template -constexpr BitSpan bit_span(Span span, usize num_bits) -{ - return BitSpan{.repr_ = span, .bit_size_ = num_bits}; -} - template struct defer { diff --git a/ashura/std/vec.h b/ashura/std/vec.h index ff3f5ad3..f18a80ac 100644 --- a/ashura/std/vec.h +++ b/ashura/std/vec.h @@ -12,6 +12,7 @@ namespace ash { template +requires (NonConst) struct [[nodiscard]] Vec { using Type = T; @@ -75,6 +76,37 @@ struct [[nodiscard]] Vec uninit(); } + static constexpr Result make(usize capacity, + AllocatorImpl allocator = {}) + { + T * storage; + if (!allocator.nalloc(capacity, storage)) [[unlikely]] + { + return Err{}; + } + + return Ok{ + Vec{allocator, storage, capacity, 0} + }; + } + + constexpr Result clone(AllocatorImpl allocator) const + { + Vec out{allocator}; + + if (!out.extend_copy(*this)) + { + return Err{}; + } + + return Ok{static_cast(out)}; + } + + constexpr Result> clone() const + { + return clone(allocator_); + } + constexpr bool is_empty() const { return size_ == 0; @@ -265,8 +297,8 @@ struct [[nodiscard]] Vec } else { - obj::move(Span{data() + slice.end(), size_ - slice.end()}, - data() + slice.begin()); + obj::move_assign(Span{data() + slice.end(), size_ - slice.end()}, + data() + slice.begin()); obj::destruct(Span{data() + size_ - slice.span, slice.span}); } @@ -329,8 +361,8 @@ struct [[nodiscard]] Vec data() + tail_first + distance); // move non-tail elements towards end - obj::move(Span{data() + first, tail_first - first}, - data() + first + distance); + obj::move_assign(Span{data() + first, tail_first - first}, + data() + first + distance); // destruct previous placements of non-tail elements obj::destruct(Span{data() + first, tail_first - first}); @@ -370,7 +402,7 @@ struct [[nodiscard]] Vec } else { - obj::copy(span, data() + pos); + obj::copy_assign(span, data() + pos); } return Ok{}; @@ -391,7 +423,7 @@ struct [[nodiscard]] Vec } else { - obj::move(span, data() + pos); + obj::move_assign(span, data() + pos); } return Ok{}; @@ -440,7 +472,7 @@ struct [[nodiscard]] Vec } else { - obj::copy(span, data() + pos); + obj::copy_assign(span, data() + pos); } return Ok{}; @@ -462,7 +494,7 @@ struct [[nodiscard]] Vec } else { - obj::move(span, data() + pos); + obj::move_assign(span, data() + pos); } return Ok{}; @@ -494,52 +526,77 @@ struct [[nodiscard]] Vec return extend_defaulted(new_size - size_); } + + constexpr Span span() const + { + return Span{data(), size()}; + } }; template -constexpr Result> vec(AllocatorImpl allocator, usize capacity) +constexpr Result> vec(usize capacity, AllocatorImpl allocator = {}) +{ + return Vec::make(capacity, allocator); +} + +template +constexpr Result> vec(T (&data)[N], AllocatorImpl allocator = {}) { - T * storage; - if (!allocator.nalloc(capacity, storage)) [[unlikely]] + Result out = Vec::make(N, allocator); + + if (!out) { - return Err{}; + return out; } - return Ok{ - Vec{allocator, storage, capacity, 0} - }; + out.value().extend_copy(data).unwrap(); + + return out; +} + +template +constexpr Result> vec(Span data, AllocatorImpl allocator = {}) +{ + Result out = Vec::make(data.size(), allocator); + + if (!out) + { + return out; + } + + out.value().extend_copy(data).unwrap(); + + return out; } template -constexpr Result> vec(AllocatorImpl allocator, T (&&data)[N]) +constexpr Result> vec_move(T (&&data)[N], AllocatorImpl allocator = {}) { - T * storage; - if (!allocator.nalloc(N, storage)) [[unlikely]] + Result out = Vec::make(N, allocator); + + if (!out) { - return Err{}; + return out; } - obj::relocate_non_overlapping(data, storage); + out.value().extend_move(data).unwrap(); - return Ok{ - Vec{allocator, storage, N, N} - }; + return out; } -template -constexpr Result> vec(AllocatorImpl allocator, Span data) +template +constexpr Result> vec_move(Span data, AllocatorImpl allocator = {}) { - T * storage; - if (!allocator.nalloc(data.size(), storage)) [[unlikely]] + Result out = Vec::make(data.size(), allocator); + + if (!out) { - return Err{}; + return out; } - obj::copy_construct(data, storage); + out.value().extend_move(data).unwrap(); - return Ok{ - Vec{allocator, storage, data.size(), data.size()} - }; + return out; } /// @brief A vector with elements pinned to memory, The address of the vector is @@ -548,6 +605,7 @@ constexpr Result> vec(AllocatorImpl allocator, Span data) /// only pop elements and add elements while within its capacity. It also never /// reallocates nor grow in capacity. template +requires (NonConst) struct [[nodiscard]] PinVec { T * storage_; @@ -606,6 +664,20 @@ struct [[nodiscard]] PinVec uninit(); } + static constexpr Result make(usize capacity, + AllocatorImpl allocator = {}) + { + T * storage; + if (!allocator.nalloc(capacity, storage)) [[unlikely]] + { + return Err{}; + } + + return Ok{ + PinVec{allocator, storage, capacity, 0} + }; + } + constexpr void uninit() { obj::destruct(Span{data(), size_}); @@ -621,6 +693,25 @@ struct [[nodiscard]] PinVec allocator_ = {}; } + constexpr Result clone(AllocatorImpl allocator) const + { + Result out = PinVec::make(allocator, capacity_); + + if (!out) + { + return out; + } + + obj::copy_construct(span(), out.value().span()); + + return out; + } + + Result clone() const + { + return clone(allocator_); + } + constexpr bool is_empty() const { return size_ == 0; @@ -725,23 +816,93 @@ struct [[nodiscard]] PinVec return Ok{}; } -}; -template -constexpr Result> pin_vec(AllocatorImpl allocator, usize capacity) -{ - T * storage; - if (!allocator.nalloc(capacity, storage)) [[unlikely]] + constexpr Result<> extend_uninit(usize extension) { - return Err{}; + if ((size_ + extension) > capacity_) [[unlikely]] + { + return Err{}; + } + + size_ += extension; + + return Ok{}; } - return Ok{ - PinVec{allocator, storage, capacity, 0} - }; + constexpr Result<> extend_defaulted(usize extension) + { + usize const pos = size_; + + if (!extend_uninit(extension)) [[unlikely]] + { + return Err{}; + } + + obj::default_construct(Span{data() + pos, extension}); + + return Ok{}; + } + + constexpr Result<> extend_copy(Span span) + { + usize const pos = size_; + + if (!extend_uninit(span.size())) [[unlikely]] + { + return Err{}; + } + + // free to use memcpy because the source range is not overlapping with this + // anyway + if constexpr (TriviallyCopyConstructible) + { + mem::copy(span, data() + pos); + } + else + { + obj::copy_assign(span, data() + pos); + } + + return Ok{}; + } + + constexpr Result<> extend_move(Span span) + { + usize const pos = size_; + + if (!extend_uninit(span.size())) [[unlikely]] + { + return Err{}; + } + + // non-overlapping, use memcpy + if constexpr (TriviallyMoveConstructible) + { + mem::copy(span, data() + pos); + } + else + { + obj::move_assign(span, data() + pos); + } + + return Ok{}; + } + + constexpr Span span() const + { + return Span{data(), size()}; + } +}; + +template +constexpr Result> pin_vec(usize capacity, + AllocatorImpl allocator = {}) +{ + return PinVec::make(capacity, allocator); } template +requires (NonConst) struct [[nodiscard]] BitVec { using Type = bool; @@ -846,12 +1007,12 @@ struct [[nodiscard]] BitVec constexpr bool get(usize index) const { - return ash::get_bit(span(repr_), index); + return ash::get_bit(repr_.span(), index); } constexpr void set(usize index, bool value) const { - ash::assign_bit(span(repr_), index, value); + ash::assign_bit(repr_.span(), index, value); } constexpr bool get_bit(usize index) const @@ -861,17 +1022,17 @@ struct [[nodiscard]] BitVec constexpr bool set_bit(usize index) const { - return ash::set_bit(span(repr_), index); + return ash::set_bit(repr_.span(), index); } constexpr bool clear_bit(usize index) const { - return ash::clear_bit(span(repr_), index); + return ash::clear_bit(repr_.span(), index); } constexpr void flip_bit(usize index) const { - ash::flip_bit(span(repr_), index); + ash::flip_bit(repr_.span(), index); } constexpr Result<> reserve(usize target_capacity) @@ -1011,33 +1172,15 @@ struct [[nodiscard]] BitVec set(a, bv); set(b, av); } -}; - -template -constexpr auto bit_span(BitVec & container) -> BitSpan -{ - return BitSpan{container.data(), container.size()}; -} - -template -constexpr auto bit_span(BitVec const & container) -> BitSpan -{ - return BitSpan{container.data(), container.size()}; -} -template -constexpr auto span(BitVec & container) -> BitSpan -{ - return BitSpan{container.data(), container.size()}; -} - -template -constexpr auto span(BitVec const & container) -> BitSpan -{ - return BitSpan{container.data(), container.size()}; -} + constexpr BitSpan span() const + { + return BitSpan{repr_, bit_size_}; + } +}; template +requires (NonConst) struct [[nodiscard]] InplaceVec { using Type = T; @@ -1248,8 +1391,8 @@ struct [[nodiscard]] InplaceVec } else { - obj::move(Span{data() + slice.end(), size_ - slice.end()}, - data() + slice.begin()); + obj::move_assign(Span{data() + slice.end(), size_ - slice.end()}, + data() + slice.begin()); obj::destruct(Span{data() + size_ - slice.span, slice.span}); } @@ -1314,8 +1457,8 @@ struct [[nodiscard]] InplaceVec data() + tail_first + distance); // move non-tail elements towards end - obj::move(Span{data() + first, tail_first - first}, - data() + first + distance); + obj::move_assign(Span{data() + first, tail_first - first}, + data() + first + distance); // destruct previous placements of non-tail elements obj::destruct(Span{data() + first, tail_first - first}); @@ -1356,7 +1499,7 @@ struct [[nodiscard]] InplaceVec } else { - obj::copy(span, data() + pos); + obj::copy_assign(span, data() + pos); } return Ok{}; @@ -1378,7 +1521,7 @@ struct [[nodiscard]] InplaceVec } else { - obj::move(span, data() + pos); + obj::move_assign(span, data() + pos); } return Ok{}; @@ -1426,7 +1569,7 @@ struct [[nodiscard]] InplaceVec } else { - obj::copy(span, data() + pos); + obj::copy_assign(span, data() + pos); } return Ok{}; @@ -1448,7 +1591,7 @@ struct [[nodiscard]] InplaceVec } else { - obj::move(span, data() + pos); + obj::move_assign(span, data() + pos); } return Ok{}; @@ -1480,6 +1623,321 @@ struct [[nodiscard]] InplaceVec return extend_defaulted(new_size - size_); } + + constexpr Span span() + { + return Span{data(), size()}; + } + + constexpr Span span() const + { + return Span{data(), size()}; + } +}; + +/// @brief Sparse Vector (a.k.a Sparse Set) are used for stable ID-tagging of +/// objects in high-perf scenarious i.e. ECS, where a stable identity is needed +/// for objects and they need to be processed in batches for efficiency. They +/// have an indirect index into their elements, although they don't guarantee +/// stability of the addresses of the elements they guarantee that the IDs +/// persist until the id is released. Unlike typical Sparse Sets, Sparse Vec's +/// elements are always contiguous without holes in them, making them suitable +/// for operations like batch-processing and branchless SIMD. +/// +/// @tparam V dense containers for the properties, i.e. Vec, Vec +/// @param index_to_id id of data, ordered relative to {data} +/// @param id_to_index map of id to index in {data} +/// @param size the number of valid elements in the sparse set +/// @param capacity the number of elements the sparse set has capacity for, +/// includes reserved but unallocated ids pointing to valid but uninitialized +/// memory +/// +/// The index and id either point to valid indices/ids or are an implicit free +/// list of ids and indices masked by RELEASE_MASK +/// +/// +template +requires (true && ... && NonConst) +struct SparseVec +{ + static constexpr u64 RELEASE_MASK = U64_MAX >> 1; + static constexpr u64 STUB = U64_MAX; + + using Dense = Tuple; + using Id = u64; + using Ids = Vec; + + Ids index_to_id = {}; + Ids id_to_index = {}; + Dense dense = {}; + u64 free_id_head = STUB; + + explicit constexpr SparseVec(Ids index_to_id, Ids id_to_index, Dense dense, + u64 free_id_head) : + index_to_id{static_cast(index_to_id)}, + id_to_index{static_cast(id_to_index)}, + dense{static_cast(dense)}, + free_id_head{free_id_head} + { + } + + explicit constexpr SparseVec(AllocatorImpl allocator) : + index_to_id{allocator}, + id_to_index{allocator}, + dense{}, + free_id_head{STUB} + { + } + + constexpr SparseVec() : SparseVec{default_allocator} + { + } + + constexpr SparseVec(SparseVec const &) = delete; + + constexpr SparseVec & operator=(SparseVec const &) = delete; + + constexpr SparseVec(SparseVec && other) : + index_to_id{static_cast(other.index_to_id)}, + id_to_index{static_cast(other.id_to_index)}, + dense{static_cast(other.dense)}, + free_id_head{other.free_id_head} + { + other.free_id_head = STUB; + } + + constexpr SparseVec & operator=(SparseVec && other) + { + if (this == &other) [[unlikely]] + { + return *this; + } + index_to_id = static_cast(other.index_to_id); + id_to_index = static_cast(other.id_to_index); + dense = static_cast(other.dense); + free_id_head = other.free_id_head; + return *this; + } + + constexpr ~SparseVec() = default; + + constexpr bool is_empty() const + { + return size() == 0; + } + + constexpr u64 size() const + { + return static_cast(index_to_id.size()); + } + + constexpr void clear() + { + apply([](auto &... d) { (d.clear(), ...); }, dense); + id_to_index.clear(); + index_to_id.clear(); + free_id_head = STUB; + } + + constexpr void reset() + { + apply([](auto &... d) { (d.reset(), ...); }, dense); + id_to_index.reset(); + index_to_id.reset(); + free_id_head = STUB; + } + + constexpr void uninit() + { + apply([](auto &... d) { (d.uninit(), ...); }, dense); + id_to_index.uninit(); + index_to_id.uninit(); + } + + constexpr bool is_valid_id(u64 id) const + { + return id < id_to_index.size() && !(id_to_index[id] & RELEASE_MASK); + } + + constexpr bool is_valid_index(u64 index) const + { + return index < size(); + } + + constexpr u64 operator[](u64 id) const + { + return id_to_index[id]; + } + + constexpr u64 to_index(u64 id) const + { + return id_to_index[id]; + } + + constexpr Result try_to_index(u64 id) const + { + if (!is_valid_id(id)) [[unlikely]] + { + return Err{}; + } + + return Ok{to_index(id)}; + } + + constexpr u64 to_id(u64 index) const + { + return index_to_id[index]; + } + + constexpr Result try_to_id(u64 index) const + { + if (!is_valid_index(index)) [[unlikely]] + { + return Err{}; + } + + return Ok{to_id(index)}; + } + + constexpr void erase(u64 id) + { + u64 const index = id_to_index[id]; + u64 const last = size() - 1; + + if (index != last) + { + apply([index, last](auto &... d) { (d.swap(index, last), ...); }, dense); + } + + apply([](auto &... d) { (d.pop(), ...); }, dense); + + // adjust id and index mapping + if (index != last) + { + id_to_index[index_to_id[last]] = index; + index_to_id[index] = index_to_id[last]; + } + + id_to_index[id] = free_id_head | RELEASE_MASK; + free_id_head = id; + index_to_id.pop(); + } + + constexpr Result<> try_erase(u64 id) + { + if (!is_valid_id(id)) [[unlikely]] + { + return Err{}; + } + erase(id); + return Ok{}; + } + + constexpr Result<> reserve(u64 target_capacity) + { + if (!(id_to_index.reserve(target_capacity) && + index_to_id.reserve(target_capacity))) [[unlikely]] + { + return Err{}; + } + + bool failed = apply( + [&](auto &... d) { + return (false || ... || !d.reserve(target_capacity)); + }, + dense); + + if (failed) [[unlikely]] + { + return Err{}; + } + + return Ok{}; + } + + constexpr Result<> grow(u64 target_size) + { + if (!(id_to_index.grow(target_size) && index_to_id.grow(target_size))) + [[unlikely]] + { + return Err{}; + } + + bool failed = apply( + [target_size](auto &... d) { + return (false || ... || !d.grow(target_size)); + }, + dense); + + if (failed) [[unlikely]] + { + return Err{}; + } + + return Ok{}; + } + + /// make new id and map the unique id to the unique index + constexpr Result make_id(u64 index) + { + if (free_id_head != STUB) + { + u64 id = free_id_head; + id_to_index[id] = index; + free_id_head = ~RELEASE_MASK & id_to_index[free_id_head]; + return Ok{id}; + } + else + { + if (!id_to_index.push(index)) [[unlikely]] + { + return Err{}; + } + u64 id = static_cast(id_to_index.size() - 1); + return Ok{id}; + } + } + + template + struct Pusher + { + template + static constexpr void push(Tuple & t, Head && head, Tail &&... tail) + { + get(t).push(static_cast(head)).unwrap(); + if constexpr (sizeof...(tail) != 0) + { + Pusher::push(t, static_cast(tail)...); + } + } + }; + + template + constexpr Result push(Args &&... args) + requires (sizeof...(Args) == sizeof...(V)) + { + u64 const index = size(); + + if (!grow(size() + 1)) [[unlikely]] + { + return Err{}; + } + + Result id = make_id(index); + + if (!id) [[unlikely]] + { + return Err{}; + } + + if (!index_to_id.push(id.unwrap())) [[unlikely]] + { + return Err{}; + } + + Pusher<0>::push(dense, static_cast(args)...); + return id; + } }; template @@ -1506,11 +1964,30 @@ struct IsTriviallyRelocatable> static constexpr bool value = TriviallyRelocatable; }; +template +struct IsTriviallyRelocatable> +{ + static constexpr bool value = (true && ... && TriviallyRelocatable>); +}; + namespace fmt { -inline bool push(Context const & ctx, Spec const & spec, Vec str) +inline bool push(Context const & ctx, Spec const & spec, Vec const & str) +{ + return push(ctx, spec, str.span()); +} + +inline bool push(Context const & ctx, Spec const & spec, + PinVec const & str) +{ + return push(ctx, spec, str.span()); +} + +template +inline bool push(Context const & ctx, Spec const & spec, + InplaceVec const & str) { - return push(ctx, spec, span(str)); + return push(ctx, spec, str.span()); } } // namespace fmt