3#include <tess/core/shape.h>
4#include <tess/storage/world.h>
24enum class WritePolicy : std::uint8_t {
30static_assert(
sizeof(WritePolicy) ==
sizeof(std::uint8_t));
33[[nodiscard]]
constexpr bool is_valid_write_policy(
34 WritePolicy policy)
noexcept {
36 case WritePolicy::ReadOnly:
37 case WritePolicy::UniquePerTile:
38 case WritePolicy::UniquePerChunk:
39 case WritePolicy::Unsafe:
48 BlockScratch() =
default;
50 BlockScratch(BlockScratch&& other) noexcept
51 : storage_(std::move(other.storage_)),
52 capacity_bytes_(std::exchange(other.capacity_bytes_, 0)),
53 used_bytes_(std::exchange(other.used_bytes_, 0)) {}
55 auto operator=(BlockScratch&& other)
noexcept -> BlockScratch& {
56 storage_ = std::move(other.storage_);
57 capacity_bytes_ = std::exchange(other.capacity_bytes_, 0);
58 used_bytes_ = std::exchange(other.used_bytes_, 0);
62 BlockScratch(
const BlockScratch&) =
delete;
63 auto operator=(
const BlockScratch&) -> BlockScratch& =
delete;
65 ~BlockScratch() =
default;
70 void reserve_bytes(std::size_t bytes) {
71 const auto word_count =
72 bytes / word_size + (bytes % word_size == 0 ? 0 : 1);
73 if (word_count > std::numeric_limits<std::size_t>::max() / word_size) {
74 throw std::bad_alloc{};
76 const auto byte_capacity = word_count * word_size;
77 if (byte_capacity > capacity_bytes_) {
81 storage_ = std::make_unique_for_overwrite<std::byte[]>(byte_capacity);
82 capacity_bytes_ = byte_capacity;
86 constexpr void reset()
noexcept { used_bytes_ = 0; }
88 [[nodiscard]]
constexpr auto capacity_bytes()
const noexcept -> std::size_t {
89 return capacity_bytes_;
92 [[nodiscard]]
constexpr auto used_bytes()
const noexcept -> std::size_t {
96 [[nodiscard]]
constexpr auto remaining_bytes()
const noexcept -> std::size_t {
97 return capacity_bytes() - used_bytes_;
100 template <
typename T>
101 [[nodiscard]]
auto allocate(std::size_t count)
noexcept -> std::span<T> {
102 static_assert(!std::is_void_v<T>);
103 static_assert(
alignof(T) <=
alignof(std::max_align_t));
104 static_assert(std::is_trivially_default_constructible_v<T>);
105 static_assert(std::is_trivially_destructible_v<T>);
110 if (count > std::numeric_limits<std::size_t>::max() /
sizeof(T)) {
114 const auto byte_count = count *
sizeof(T);
115 const auto aligned_offset = align_offset(used_bytes_,
alignof(T));
116 if (aligned_offset > capacity_bytes() ||
117 byte_count > capacity_bytes() - aligned_offset) {
125 std::launder(
reinterpret_cast<T*
>(storage_.get() + aligned_offset));
126 used_bytes_ = aligned_offset + byte_count;
127 return std::span<T>{ptr, count};
131 static constexpr auto word_size =
sizeof(std::max_align_t);
135 static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ >=
alignof(std::max_align_t));
137 [[nodiscard]]
static constexpr auto align_offset(
138 std::size_t offset, std::size_t alignment)
noexcept -> std::size_t {
139 const auto remainder = offset % alignment;
140 if (remainder == 0) {
143 return offset + (alignment - remainder);
146 std::unique_ptr<std::byte[]> storage_;
147 std::size_t capacity_bytes_ = 0;
148 std::size_t used_bytes_ = 0;
154 constexpr void record_scratch_allocation_failure()
noexcept {
155 ++scratch_allocation_failures_;
158 constexpr void reset()
noexcept { scratch_allocation_failures_ = 0; }
160 [[nodiscard]]
constexpr auto scratch_allocation_failures()
const noexcept
162 return scratch_allocation_failures_;
166 std::size_t scratch_allocation_failures_ = 0;
172 constexpr ChunkDomain()
noexcept =
default;
174 constexpr explicit ChunkDomain(std::span<const ChunkKey> keys) noexcept
177 [[nodiscard]]
constexpr auto keys()
const noexcept
178 -> std::span<const ChunkKey> {
182 [[nodiscard]]
constexpr auto begin()
const noexcept {
return keys_.begin(); }
184 [[nodiscard]]
constexpr auto end()
const noexcept {
return keys_.end(); }
186 [[nodiscard]]
constexpr auto size()
const noexcept -> std::size_t {
190 [[nodiscard]]
constexpr bool empty()
const noexcept {
return keys_.empty(); }
193 std::span<const ChunkKey> keys_;
197class OwnedChunkDomain {
199 OwnedChunkDomain() =
default;
201 explicit OwnedChunkDomain(std::vector<ChunkKey> keys)
202 : keys_(std::move(keys)) {}
204 [[nodiscard]]
constexpr auto view()
const noexcept ->
ChunkDomain {
208 [[nodiscard]]
constexpr auto keys()
const noexcept
209 -> std::span<const ChunkKey> {
213 [[nodiscard]]
constexpr auto begin()
const noexcept {
return keys_.begin(); }
215 [[nodiscard]]
constexpr auto end()
const noexcept {
return keys_.end(); }
217 [[nodiscard]]
constexpr auto size()
const noexcept -> std::size_t {
221 [[nodiscard]]
constexpr bool empty()
const noexcept {
return keys_.empty(); }
224 std::vector<ChunkKey> keys_;
228[[nodiscard]]
constexpr auto chunk_domain(
229 std::span<const ChunkKey> keys)
noexcept ->
ChunkDomain {
234[[nodiscard]]
constexpr auto chunk_domain(
const OwnedChunkDomain& keys)
noexcept
243[[nodiscard]]
inline auto explicit_chunk_domain(std::span<const ChunkKey> keys)
245 std::vector<ChunkKey> domain{keys.begin(), keys.end()};
246 std::sort(domain.begin(), domain.end(),
251template <
typename World>
253[[nodiscard]]
auto dirty_chunk_domain(
const World& world, std::uint32_t flags)
258template <
typename World>
260[[nodiscard]]
auto active_chunk_domain(
const World& world, std::uint32_t flags)
265template <
typename World>
269 using world_type = std::remove_reference_t<World>;
270 using mutable_world_type = std::remove_cv_t<world_type>;
271 using shape_type = mutable_world_type::shape_type;
273 std::conditional_t<std::is_const_v<world_type>,
274 const typename mutable_world_type::page_type,
275 typename mutable_world_type::page_type>;
276 using meta_type = std::conditional_t<std::is_const_v<world_type>,
279 constexpr ChunkView(world_type& world,
ChunkKey key) noexcept
280 : page_(&world.chunk(key)),
281 meta_(&world.meta(key)),
283 coord_(chunk_coord<shape_type>(key)),
284 bounds_(chunk_bounds(coord_)) {}
286 [[nodiscard]]
constexpr auto page()
const noexcept -> page_type& {
290 [[nodiscard]]
constexpr auto meta()
const noexcept -> meta_type& {
294 [[nodiscard]]
constexpr auto key()
const noexcept ->
ChunkKey {
return key_; }
296 [[nodiscard]]
constexpr auto coord()
const noexcept ->
ChunkCoord3 {
300 [[nodiscard]]
constexpr auto bounds()
const noexcept ->
Box3 {
304 [[nodiscard]]
static constexpr auto local_bounds()
noexcept ->
Box3 {
305 return Box3{
Coord3{0, 0, 0}, ShapeTraits<shape_type>::chunk};
308 [[nodiscard]]
static constexpr bool contains_local(
Coord3 coord)
noexcept {
309 return tess::contains(local_bounds(), coord);
312 [[nodiscard]]
static constexpr auto try_local_coord(
Coord3 coord)
noexcept
313 -> std::optional<LocalCoord3> {
314 if (!contains_local(coord)) {
319 static_cast<std::uint64_t
>(coord.x),
320 static_cast<std::uint64_t
>(coord.y),
321 static_cast<std::uint64_t
>(coord.z),
325 [[nodiscard]]
static constexpr auto local_coord(
LocalTileId id)
noexcept
327 const auto chunk = ShapeTraits<shape_type>::chunk;
328 const auto local_xy = chunk.x * chunk.y;
329 const auto local_z =
id.value / local_xy;
330 const auto remainder =
id.value % local_xy;
331 const auto local_y = remainder / chunk.x;
332 const auto local_x = remainder % chunk.x;
337 [[nodiscard]]
static constexpr auto local_tile_id(
LocalCoord3 coord)
noexcept
339 return tess::local_tile_id<shape_type>(coord);
350 [[nodiscard]]
static constexpr bool is_boundary(
LocalCoord3 coord)
noexcept {
351 const auto chunk = ShapeTraits<shape_type>::chunk;
352 return (chunk.x > 1 && (coord.x == 0 || coord.x + 1 == chunk.x)) ||
353 (chunk.y > 1 && (coord.y == 0 || coord.y + 1 == chunk.y)) ||
354 (chunk.z > 1 && (coord.z == 0 || coord.z + 1 == chunk.z));
357 [[nodiscard]]
static constexpr bool is_interior(
LocalCoord3 coord)
noexcept {
358 return !is_boundary(coord);
361 [[nodiscard]]
constexpr auto world_coord(
363 const auto chunk = ShapeTraits<shape_type>::chunk;
365 static_cast<std::int64_t
>(coord_.x * chunk.x) + local_candidate.x,
366 static_cast<std::int64_t
>(coord_.y * chunk.y) + local_candidate.y,
367 static_cast<std::int64_t
>(coord_.z * chunk.z) + local_candidate.z,
371 [[nodiscard]]
constexpr auto world_coord(
LocalCoord3 coord)
const noexcept
373 return world_coord(
Coord3{
374 static_cast<std::int64_t
>(coord.x),
375 static_cast<std::int64_t
>(coord.y),
376 static_cast<std::int64_t
>(coord.z),
380 [[nodiscard]]
constexpr auto world_coord(
LocalTileId id)
const noexcept
382 return world_coord(local_coord(
id));
385 template <
typename Fn>
386 constexpr void for_each_tile(Fn&& fn)
const {
387 for (std::uint64_t i = 0; i < ShapeTraits<shape_type>::local_tile_count;
390 std::invoke(fn,
id, local_coord(
id));
394 template <
typename Tag>
395 [[nodiscard]]
constexpr auto field_span()
const noexcept {
396 return page_->template field_span<Tag>();
400 [[nodiscard]]
static constexpr auto chunk_bounds(
ChunkCoord3 coord)
noexcept
402 const auto chunk = ShapeTraits<shape_type>::chunk;
405 static_cast<std::int64_t
>(coord.x * chunk.x),
406 static_cast<std::int64_t
>(coord.y * chunk.y),
407 static_cast<std::int64_t
>(coord.z * chunk.z),
420template <
typename World, WritePolicy Policy>
424 static_assert(is_valid_write_policy(Policy));
426 using world_type = std::remove_reference_t<World>;
427 using view_world_type =
428 std::conditional_t<Policy == WritePolicy::ReadOnly,
429 const std::remove_const_t<world_type>, world_type>;
431 constexpr BlockCtx(world_type& world,
ChunkDomain domain,
437 diagnostics_(diagnostics) {}
439 [[nodiscard]]
constexpr auto world()
const noexcept -> view_world_type& {
443 [[nodiscard]]
constexpr auto domain()
const noexcept ->
ChunkDomain {
447 [[nodiscard]]
constexpr auto policy()
const noexcept -> WritePolicy {
451 [[nodiscard]]
constexpr auto size()
const noexcept -> std::size_t {
452 return domain_.size();
455 [[nodiscard]]
constexpr bool empty()
const noexcept {
456 return domain_.empty();
459 [[nodiscard]]
constexpr auto scratch()
noexcept ->
BlockScratch* {
463 [[nodiscard]]
constexpr auto scratch()
const noexcept ->
const BlockScratch* {
467 constexpr void reset_scratch()
const noexcept {
468 if (scratch_ !=
nullptr) {
477 [[nodiscard]]
constexpr auto diagnostics()
const noexcept
482 constexpr void reset_diagnostics()
const noexcept {
483 if (diagnostics_ !=
nullptr) {
484 diagnostics_->reset();
488 [[nodiscard]]
constexpr auto chunk_view(
ChunkKey key)
const noexcept
493 template <
typename Fn>
494 constexpr void for_each_chunk(Fn&& fn)
const {
495 for (
const auto key : domain_) {
496 std::invoke(fn, chunk_view(key));
507template <WritePolicy Policy,
typename World>
509[[nodiscard]]
constexpr auto block_ctx(
World& world,
515template <WritePolicy Policy,
typename World>
523template <WritePolicy Policy,
typename World>
531template <WritePolicy Policy,
typename World>
540template <WritePolicy Policy,
typename World,
typename Fn>
543 block_ctx<Policy>(world, domain).for_each_chunk(std::forward<Fn>(fn));
548template <WritePolicy Policy,
typename World,
typename Fn>
549constexpr void for_each_chunk_policy_view(World& world, ChunkDomain domain,
551 using world_type = std::remove_reference_t<World>;
552 using view_world_type =
553 std::conditional_t<Policy == WritePolicy::ReadOnly,
554 const std::remove_const_t<world_type>, world_type>;
556 if constexpr (std::is_invocable_v<Fn&, ChunkView<view_world_type>>) {
557 for (
const auto key : domain) {
558 std::invoke(fn, ChunkView<view_world_type>{world, key});
561 assert(
false &&
"callback cannot accept the selected block policy view");
568template <
typename World,
typename Fn>
571 WritePolicy policy, Fn&& fn) {
572 assert(is_valid_write_policy(policy));
574 case WritePolicy::ReadOnly:
575 detail::for_each_chunk_policy_view<WritePolicy::ReadOnly>(
576 world, domain, std::forward<Fn>(fn));
578 case WritePolicy::UniquePerTile:
579 detail::for_each_chunk_policy_view<WritePolicy::UniquePerTile>(
580 world, domain, std::forward<Fn>(fn));
582 case WritePolicy::UniquePerChunk:
583 detail::for_each_chunk_policy_view<WritePolicy::UniquePerChunk>(
584 world, domain, std::forward<Fn>(fn));
586 case WritePolicy::Unsafe:
587 detail::for_each_chunk_policy_view<WritePolicy::Unsafe>(
588 world, domain, std::forward<Fn>(fn));