3#include <tess/block/block.h>
4#include <tess/core/shape.h>
5#include <tess/core/tag_identity.h>
6#include <tess/diagnostics/diagnostics.h>
7#include <tess/diagnostics/trace.h>
8#include <tess/ops/phase_executor.h>
9#include <tess/storage/world.h>
16#include <source_location>
27struct PlannedWorldStamp {
28 std::uintptr_t shape_identity = 0;
29 std::uint64_t chunk_limit = 0;
32template <
typename Shape, std::u
int64_t ChunkLimit>
33[[nodiscard]]
inline auto planned_world_stamp() noexcept
34 -> const PlannedWorldStamp* {
35 static const auto stamp = PlannedWorldStamp{
36 tag_identity<Shape>(),
42template <
typename World>
43[[nodiscard]]
inline auto planned_world_stamp() noexcept
44 -> const PlannedWorldStamp* {
45 return planned_world_stamp<typename World::shape_type, World::chunk_count>();
48template <
typename World>
49[[nodiscard]]
inline auto validate_planned_world_stamp(
50 const PlannedWorldStamp* stamp)
noexcept -> PlannedExecutionStatus {
51 const auto* expected = planned_world_stamp<World>();
52 if (stamp == expected) {
53 return PlannedExecutionStatus::Executed;
55 if (stamp ==
nullptr || stamp->shape_identity != expected->shape_identity) {
56 return PlannedExecutionStatus::InvalidShape;
58 if (stamp->chunk_limit != expected->chunk_limit) {
59 return PlannedExecutionStatus::InvalidChunk;
61 return PlannedExecutionStatus::Executed;
68 std::uint64_t value = 0;
70 friend constexpr bool operator==(
OpId lhs,
OpId rhs)
noexcept =
default;
75 std::uint64_t value = 0;
77 friend constexpr bool operator==(
OpHandle lhs,
82enum class OperationKind : std::uint8_t {
85static_assert(
sizeof(OperationKind) ==
sizeof(std::uint8_t));
88enum class Priority : std::uint8_t {
95static_assert(
sizeof(Priority) ==
sizeof(std::uint8_t));
98enum class BudgetPolicy : std::uint8_t {
104static_assert(
sizeof(BudgetPolicy) ==
sizeof(std::uint8_t));
107enum class OperationStatus : std::uint8_t {
115static_assert(
sizeof(OperationStatus) ==
sizeof(std::uint8_t));
118enum class OperationFailure : std::uint8_t {
122 InvalidWritePolicyValue,
123 ExplicitChunkOutOfRange,
127static_assert(
sizeof(OperationFailure) ==
sizeof(std::uint8_t));
130enum class ExecutionPhaseStatus : std::uint8_t {
132 UnsupportedWritePolicy,
134static_assert(
sizeof(ExecutionPhaseStatus) ==
sizeof(std::uint8_t));
137enum class DomainKind : std::uint8_t {
143static_assert(
sizeof(DomainKind) ==
sizeof(std::uint8_t));
152 [[nodiscard]]
static auto explicit_chunks(std::span<const ChunkKey> keys)
154 DomainDesc desc{DomainKind::ExplicitChunks};
155 desc.explicit_chunks_.assign(keys.begin(), keys.end());
156 std::sort(desc.explicit_chunks_.begin(), desc.explicit_chunks_.end(),
158 desc.explicit_chunks_.erase(
159 std::unique(desc.explicit_chunks_.begin(), desc.explicit_chunks_.end()),
160 desc.explicit_chunks_.end());
164 [[nodiscard]]
static constexpr auto dirty_chunks(std::uint32_t flags)
noexcept
166 DomainDesc desc{DomainKind::DirtyChunks};
171 [[nodiscard]]
static constexpr auto active_chunks(
172 std::uint32_t flags)
noexcept -> DomainDesc {
173 DomainDesc desc{DomainKind::ActiveChunks};
178 [[nodiscard]]
static constexpr auto resident_chunks()
noexcept -> DomainDesc {
179 return DomainDesc{DomainKind::ResidentChunks};
182 [[nodiscard]]
constexpr auto kind()
const noexcept -> DomainKind {
186 [[nodiscard]]
constexpr auto mask()
const noexcept -> std::uint32_t {
190 [[nodiscard]]
constexpr auto explicit_chunks()
const noexcept
191 -> std::span<const ChunkKey> {
192 return {explicit_chunks_.data(), explicit_chunks_.size()};
196 constexpr explicit DomainDesc(DomainKind kind) noexcept : kind_(kind) {}
199 std::uint32_t mask_ = 0;
200 std::vector<ChunkKey> explicit_chunks_;
205 std::uint32_t read_mask = 0;
206 std::uint32_t write_mask = 0;
207 std::uint32_t dirty_mask = 0;
215 OperationKind kind = OperationKind::UpdateField;
218 DomainDesc domain = DomainDesc::resident_chunks();
220 WritePolicy write_policy = WritePolicy::ReadOnly;
221 Priority priority = Priority::GameplayCritical;
222 BudgetPolicy budget_policy = BudgetPolicy::MustRun;
223 std::source_location source = std::source_location::current();
228 WritePolicy write_policy = WritePolicy::ReadOnly;
229 DomainKind domain_kind = DomainKind::ResidentChunks;
230 std::uint32_t domain_mask = 0;
234enum class PlannedOperationCreateStatus : std::uint8_t {
238static_assert(
sizeof(PlannedOperationCreateStatus) ==
sizeof(std::uint8_t));
240struct PlannedOperationCreateResult;
241class ExecutionReport;
245class PlannedOperation {
247 OperationKind kind = OperationKind::UpdateField;
252 WritePolicy write_policy = WritePolicy::ReadOnly;
253 Priority priority = Priority::GameplayCritical;
254 BudgetPolicy budget_policy = BudgetPolicy::MustRun;
257 std::source_location source = std::source_location::current();
265 template <
typename World>
266 [[nodiscard]]
static auto create(
const World& world,
268 std::span<const ChunkKey>
chunks)
272 [[nodiscard]]
constexpr auto chunks() const noexcept
278 template <
typename World>
280 const World& )
const noexcept -> PlannedExecutionStatus {
282 std::is_same_v<typename World::residency_type, AlwaysResident>,
283 "Queued-op validation requires an AlwaysResidentWorld; sparse "
284 "queued-ops support is deferred to a later slice.");
285 return detail::validate_planned_world_stamp<World>(world_stamp_);
293 std::vector<ChunkKey>&&
chunks,
294 const detail::PlannedWorldStamp* world_stamp) noexcept
295 : kind(operation.kind),
296 handle(operation.handle),
298 access(
OperationAccess{operation.write_policy, operation.domain.kind(),
299 operation.domain.mask()}),
300 field_access(operation.field_access),
301 write_policy(operation.write_policy),
302 priority(operation.priority),
303 budget_policy(operation.budget_policy),
304 source(operation.source),
305 chunks_(std::move(
chunks)),
306 world_stamp_(world_stamp) {}
308 std::vector<ChunkKey> chunks_;
309 const detail::PlannedWorldStamp* world_stamp_ =
nullptr;
314 PlannedOperationCreateStatus status =
315 PlannedOperationCreateStatus::InvalidChunk;
316 std::optional<PlannedOperation> operation;
320template <
typename World>
323 std::span<const ChunkKey>
chunks)
326 std::is_same_v<typename World::residency_type, AlwaysResident>,
327 "Queued operations require an AlwaysResidentWorld; sparse queued-ops "
328 "support is deferred to a later slice.");
330 for (
const auto key :
chunks) {
331 if (key.value >= World::chunk_count) {
333 PlannedOperationCreateStatus::InvalidChunk,
340 auto validated = std::vector<ChunkKey>{
chunks.begin(),
chunks.end()};
341 std::sort(validated.begin(), validated.end(),
343 validated.erase(std::unique(validated.begin(), validated.end()),
345 auto planned = PlannedOperation{
347 std::move(validated),
348 detail::planned_world_stamp<World>(),
351 PlannedOperationCreateStatus::Created,
352 std::optional<PlannedOperation>{std::move(planned)},
360 [[nodiscard]]
constexpr auto operations()
const noexcept
361 -> std::span<const PlannedOperation> {
362 return {operations_.data(), operations_.size()};
365 [[nodiscard]]
constexpr bool empty()
const noexcept {
366 return operations_.empty();
369 [[nodiscard]]
constexpr auto size()
const noexcept -> std::size_t {
370 return operations_.size();
374 friend class ExecutionReport;
375 friend class ExecutionPhase;
377 ExecutionPlan()
noexcept =
default;
378 ExecutionPlan(
const ExecutionPlan&) =
default;
379 ExecutionPlan(ExecutionPlan&&)
noexcept =
default;
384 auto operator=(
const ExecutionPlan& other) -> ExecutionPlan& {
385 if (
this != &other) {
390 operations_ = other.operations_;
395 auto operator=(ExecutionPlan&& other)
noexcept -> ExecutionPlan& {
396 if (
this != &other) {
397 operations_ = std::move(other.operations_);
403 constexpr void bump_generation()
noexcept { ++generation_; }
405 std::vector<PlannedOperation> operations_;
406 std::uint64_t generation_ = 0;
416class ExecutionPhase {
418 ExecutionPhase(
const ExecutionPhase&)
noexcept =
default;
419 ExecutionPhase(ExecutionPhase&&)
noexcept =
default;
420 auto operator=(
const ExecutionPhase&)
noexcept -> ExecutionPhase& =
default;
421 auto operator=(ExecutionPhase&&)
noexcept -> ExecutionPhase& =
default;
423 [[nodiscard]]
constexpr auto first_operation()
const noexcept -> std::size_t {
424 return first_operation_;
427 [[nodiscard]]
constexpr auto operation_count()
const noexcept -> std::size_t {
428 return operation_count_;
434 return plan_ == &plan && plan_generation_ == plan.generation_;
438 template <
typename World>
440 const World& )
const noexcept -> PlannedExecutionStatus {
442 std::is_same_v<typename World::residency_type, AlwaysResident>,
443 "Queued-op validation requires an AlwaysResidentWorld; sparse "
444 "queued-ops support is deferred to a later slice.");
445 return detail::validate_planned_world_stamp<World>(world_stamp_);
449 template <WritePolicy Policy>
451 static_assert(is_valid_write_policy(Policy));
452 return write_policy_mask_ == policy_bit(Policy);
458 [[nodiscard]]
static constexpr auto policy_bit(WritePolicy policy)
noexcept
460 return static_cast<std::uint8_t
>(std::uint8_t{1}
461 <<
static_cast<std::uint8_t
>(policy));
464 constexpr ExecutionPhase(
const ExecutionPlan& plan,
465 std::size_t first_operation,
466 std::size_t operation_count,
467 const PlannedOperation& operation) noexcept
469 first_operation_(first_operation),
470 operation_count_(operation_count),
471 plan_generation_(plan.generation_),
472 world_stamp_(operation.world_stamp_),
473 write_policy_mask_(policy_bit(operation.write_policy)) {}
475 constexpr void extend(
const PlannedOperation& operation)
noexcept {
477 write_policy_mask_ |= policy_bit(operation.write_policy);
480 const ExecutionPlan* plan_;
481 std::size_t first_operation_;
482 std::size_t operation_count_;
483 std::uint64_t plan_generation_;
484 const detail::PlannedWorldStamp* world_stamp_;
485 std::uint8_t write_policy_mask_;
489[[nodiscard]]
constexpr auto executor_phase_range(
492 phase.first_operation(),
493 phase.operation_count(),
500 [[nodiscard]]
constexpr auto phases()
const noexcept
501 -> std::span<const ExecutionPhase> {
502 return {phases_.data(), phases_.size()};
505 [[nodiscard]]
constexpr auto status()
const noexcept -> ExecutionPhaseStatus {
509 [[nodiscard]]
constexpr bool ok()
const noexcept {
510 return status_ == ExecutionPhaseStatus::Ready;
513 [[nodiscard]]
constexpr auto failed_operation_index()
const noexcept
515 return failed_operation_index_;
518 [[nodiscard]]
constexpr auto failed_write_policy()
const noexcept
520 return failed_write_policy_;
527 void reserve(std::size_t size) { phases_.reserve(size); }
529 void push_phase(
const ExecutionPlan& plan, std::size_t first_operation,
530 std::size_t operation_count,
533 ExecutionPhase{plan, first_operation, operation_count, operation});
537 phases_.back().extend(operation);
540 std::vector<ExecutionPhase> phases_;
541 ExecutionPhaseStatus status_ = ExecutionPhaseStatus::Ready;
542 std::size_t failed_operation_index_ = 0;
543 WritePolicy failed_write_policy_ = WritePolicy::ReadOnly;
548[[nodiscard]]
constexpr bool execution_phase_valid_for(
550 const auto operations = plan.operations();
551 const auto first = phase.first_operation();
552 const auto count = phase.operation_count();
553 return phase.belongs_to(plan) && first <= operations.size() &&
554 count <= operations.size() - first;
557template <WritePolicy Policy,
typename World>
558[[nodiscard]]
auto execution_phase_validation_status(
559 const World& world,
const ExecutionPlan& plan,
560 const ExecutionPhase& phase)
noexcept -> PlannedExecutionStatus {
561 if (!execution_phase_valid_for(plan, phase)) {
562 return PlannedExecutionStatus::InvalidPhase;
564 const auto world_status = phase.world_validation_status(world);
565 if (world_status != PlannedExecutionStatus::Executed) {
568 if (!phase.template policy_matches<Policy>()) {
569 return PlannedExecutionStatus::PolicyMismatch;
571 return PlannedExecutionStatus::Executed;
574inline void record_execution_phase_validation_failure(
575 PlannedExecutionStatus status)
noexcept {
576#if TESS_DIAGNOSTICS_ENABLED
577 if (status == PlannedExecutionStatus::InvalidPhase) {
578 TESS_DIAG_EVENT(queued_phase_invalid_range);
580 TESS_DIAG_EVENT(queued_phase_failure);
593 OperationStatus status = OperationStatus::Planned;
594 OperationFailure failure = OperationFailure::None;
600 std::uint32_t conflict_mask = 0;
601 bool has_detail_chunk =
false;
602 bool has_conflict =
false;
603 std::size_t chunk_count = 0;
604 std::source_location source = std::source_location::current();
610 std::uint32_t dirty_mask = 0;
615enum class PlannedDirtyRecordStatus : std::uint8_t {
621static_assert(
sizeof(PlannedDirtyRecordStatus) ==
sizeof(std::uint8_t));
624enum class PlannedDirtyMergeStatus : std::uint8_t {
629static_assert(
sizeof(PlannedDirtyMergeStatus) ==
sizeof(std::uint8_t));
632enum class PlannedDirtyCollectStatus : std::uint8_t {
637static_assert(
sizeof(PlannedDirtyCollectStatus) ==
sizeof(std::uint8_t));
641 PlannedDirtyCollectStatus status = PlannedDirtyCollectStatus::Collected;
642 std::size_t record_count = 0;
644 [[nodiscard]]
constexpr bool ok()
const noexcept {
645 return status == PlannedDirtyCollectStatus::Collected;
651 PlannedDirtyMergeStatus status = PlannedDirtyMergeStatus::Merged;
652 std::size_t merged_chunk_count = 0;
654 [[nodiscard]]
constexpr bool ok()
const noexcept {
655 return status == PlannedDirtyMergeStatus::Merged;
664template <
bool BindWorld, WritePolicy Policy,
typename World,
typename Fn>
665auto execute_validated_planned_operation_deferred_dirty(
669template <
typename World>
670auto merge_planned_dirty_after_exception(
676class PlannedDirtyPartitions;
681 void reserve(std::size_t count) { records_.reserve(count); }
683 void clear()
noexcept {
685 world_stamp_ =
nullptr;
689 template <
typename World>
691 Box3 bounds) -> PlannedDirtyRecordStatus {
693 std::is_same_v<typename World::residency_type, AlwaysResident>,
694 "Queued-op dirty recording requires an AlwaysResidentWorld; sparse "
695 "queued-ops support is deferred to a later slice.");
696 if (dirty_mask == 0) {
697 return PlannedDirtyRecordStatus::IgnoredEmptyMask;
699 if (chunk.value >= World::chunk_count) {
700 return PlannedDirtyRecordStatus::InvalidChunk;
703 if (world_stamp_ !=
nullptr) {
704 const auto validation =
705 detail::validate_planned_world_stamp<World>(world_stamp_);
706 if (validation == PlannedExecutionStatus::InvalidShape) {
707 return PlannedDirtyRecordStatus::InvalidShape;
709 if (validation == PlannedExecutionStatus::InvalidChunk) {
710 return PlannedDirtyRecordStatus::InvalidChunk;
715 world_stamp_ = detail::planned_world_stamp<World>();
716 return PlannedDirtyRecordStatus::Recorded;
719 [[nodiscard]]
auto records() const noexcept
725 template <
typename World>
727 -> PlannedDirtyMergeStatus {
729 std::is_same_v<typename World::residency_type, AlwaysResident>,
730 "Queued-op dirty validation requires an AlwaysResidentWorld; sparse "
731 "queued-ops support is deferred to a later slice.");
732 if (world_stamp_ ==
nullptr) {
733 return PlannedDirtyMergeStatus::Merged;
735 const auto validation =
736 detail::validate_planned_world_stamp<World>(world_stamp_);
737 if (validation == PlannedExecutionStatus::InvalidShape) {
738 return PlannedDirtyMergeStatus::InvalidShape;
740 if (validation == PlannedExecutionStatus::InvalidChunk) {
741 return PlannedDirtyMergeStatus::InvalidChunk;
743 return PlannedDirtyMergeStatus::Merged;
749 template <
bool BindWorld, WritePolicy Policy,
typename World,
typename Fn>
750 friend auto detail::execute_validated_planned_operation_deferred_dirty(
754 template <WritePolicy Policy,
typename World,
typename Fn>
758 template <
typename World>
762 template <
typename World>
770 template <
typename World>
771 void bind_validated_world(
const World& )
noexcept {
772 if (world_stamp_ ==
nullptr) {
773 world_stamp_ = detail::planned_world_stamp<World>();
777 template <
typename World>
778 void prepare_for_validated_world(
const World& )
noexcept {
780 world_stamp_ = detail::planned_world_stamp<World>();
783 void record_validated(ChunkKey chunk, std::uint32_t dirty_mask, Box3 bounds) {
784 if (dirty_mask == 0) {
787 records_.push_back(PlannedDirtyRecord{chunk, dirty_mask, bounds});
790 std::vector<PlannedDirtyRecord> records_;
791 const detail::PlannedWorldStamp* world_stamp_ =
nullptr;
794template <
bool BindWorld, WritePolicy Policy,
typename World,
typename Fn>
795auto detail::execute_validated_planned_operation_deferred_dirty(
798 if constexpr (BindWorld) {
799 if (operation.field_access.dirty_mask != 0) {
800 dirty.bind_validated_world(world);
803 auto ctx = block_ctx<Policy>(world, chunk_domain(operation.chunks()));
805 std::size_t chunk_count = 0;
806 auto&& callback = fn;
807 ctx.for_each_chunk([&](
auto view) {
808 dirty.record_validated(view.key(), operation.field_access.dirty_mask,
815 PlannedExecutionStatus::Executed,
823 void reserve(std::size_t count) { partitions_.reserve(count); }
825 void resize(std::size_t count) { partitions_.resize(count); }
827 void clear()
noexcept { partitions_.clear(); }
829 void clear_records()
noexcept {
830 for (
auto& partition : partitions_) {
835 void reserve_records_per_partition(std::size_t count) {
836 records_per_partition_reserve_ = count;
837 for (
auto& partition : partitions_) {
838 partition.reserve(count);
842 [[nodiscard]]
auto size()
const noexcept -> std::size_t {
843 return partitions_.size();
846 [[nodiscard]]
auto partition(std::size_t index)
noexcept
848 return partitions_[index];
851 [[nodiscard]]
auto partition(std::size_t index)
const noexcept
853 return partitions_[index];
856 [[nodiscard]]
auto partitions()
const noexcept
857 -> std::span<const PlannedDirtyAccumulator> {
865 friend class PlannedPhaseExecutionScratch;
867 void prepare(std::size_t count) {
868 partitions_.resize(count);
869 for (
auto& partition : partitions_) {
871 partition.reserve(records_per_partition_reserve_);
875 template <
typename World>
876 void prepare(
const World& world, std::size_t count) {
877 partitions_.resize(count);
878 for (
auto& partition : partitions_) {
879 partition.prepare_for_validated_world(world);
880 partition.reserve(records_per_partition_reserve_);
884 std::vector<PlannedDirtyAccumulator> partitions_;
885 std::size_t records_per_partition_reserve_ = 0;
893class PhaseDirtyPartition {
895 void reserve(std::size_t count) { records_.reserve(count); }
897 void clear() noexcept { records_.clear(); }
899 void record(ChunkKey chunk, std::uint32_t dirty_mask, Box3 bounds) {
900 if (dirty_mask != 0) {
901 records_.push_back(PlannedDirtyRecord{chunk, dirty_mask, bounds});
905 [[nodiscard]]
auto records() const noexcept
906 -> std::span<const PlannedDirtyRecord> {
911 std::vector<PlannedDirtyRecord> records_;
914template <WritePolicy Policy,
typename World,
typename Fn>
915auto execute_validated_phase_operation_deferred_dirty(
916 World& world,
const PlannedOperation& operation, PhaseDirtyPartition& dirty,
917 Fn&& fn) -> PlannedExecutionResult {
918 auto ctx = block_ctx<Policy>(world, chunk_domain(operation.chunks()));
920 std::size_t chunk_count = 0;
921 auto&& callback = fn;
922 ctx.for_each_chunk([&](
auto view) {
923 dirty.record(view.key(), operation.field_access.dirty_mask, view.bounds());
928 return PlannedExecutionResult{
929 PlannedExecutionStatus::Executed,
943 void reserve_operations(std::size_t count) {
944 dirty_partitions_.reserve(count);
945 results_.reserve(count);
948 void reserve_dirty_records_per_operation(std::size_t count) {
949 records_per_partition_reserve_ = count;
950 for (
auto& partition : dirty_partitions_) {
951 partition.reserve(count);
955 void reserve_merged_dirty_records(std::size_t count) {
956 merged_dirty_.reserve(count);
959 void prepare_for_operation_count(std::size_t count) { prepare(count); }
961 void clear()
noexcept {
962 for (
auto& partition : dirty_partitions_) {
966 merged_dirty_.clear();
967 world_stamp_ =
nullptr;
970 [[nodiscard]]
auto operation_count()
const noexcept -> std::size_t {
971 return results_.size();
974 [[nodiscard]]
auto dirty_partitions()
const noexcept
975 -> std::span<const detail::PhaseDirtyPartition> {
976 return dirty_partitions_;
980 template <WritePolicy Policy,
typename Executor,
typename World,
typename Fn>
986 template <WritePolicy Policy,
typename Executor,
typename World,
typename T,
993 template <
typename World>
997 template <
typename World>
998 friend auto detail::merge_planned_dirty_after_exception(
1002 void prepare(std::size_t operation_count) {
1003 prepare_partitions(operation_count);
1005 merged_dirty_.clear();
1006 world_stamp_ =
nullptr;
1009 template <
typename World>
1010 void prepare(
const World& , std::size_t operation_count) {
1011 prepare_partitions(operation_count);
1013 merged_dirty_.clear();
1014 world_stamp_ = detail::planned_world_stamp<World>();
1017 [[nodiscard]]
auto dirty_for_operation(std::size_t index)
noexcept
1018 -> detail::PhaseDirtyPartition& {
1019 return dirty_partitions_[index];
1022 void prepare_partitions(std::size_t operation_count) {
1023 dirty_partitions_.resize(operation_count);
1024 for (
auto& partition : dirty_partitions_) {
1026 partition.reserve(records_per_partition_reserve_);
1031 results_[index] = result;
1034 [[nodiscard]]
auto results()
const noexcept
1035 -> std::span<const PlannedExecutionResult> {
1039 std::vector<detail::PhaseDirtyPartition> dirty_partitions_;
1040 std::vector<PlannedExecutionResult> results_;
1042 std::size_t records_per_partition_reserve_ = 0;
1043 const detail::PlannedWorldStamp* world_stamp_ =
nullptr;
1049 [[nodiscard]]
constexpr auto operations()
const noexcept
1050 -> std::span<const OperationReport> {
1051 return {operations_.data(), operations_.size()};
1054 [[nodiscard]]
constexpr auto plan()
const noexcept ->
const ExecutionPlan& {
1058 [[nodiscard]]
constexpr auto find(
OpHandle handle)
const noexcept
1060 for (
const auto& op : operations_) {
1061 if (op.handle == handle) {
1068 [[nodiscard]]
constexpr bool ok()
const noexcept {
1069 return failed_count() == 0;
1072 [[nodiscard]]
constexpr bool failed()
const noexcept {
1073 return failed_count() != 0;
1076 [[nodiscard]]
constexpr auto planned_count()
const noexcept -> std::size_t {
1077 return plan_.size();
1080 [[nodiscard]]
constexpr auto failed_count()
const noexcept -> std::size_t {
1081 std::size_t count = 0;
1082 for (
const auto& op : operations_) {
1083 if (op.status != OperationStatus::Planned) {
1095 plan_.bump_generation();
1096 for (
auto& planned : plan_.operations_) {
1097 planned.chunks_.clear();
1098 chunk_pool_.push_back(std::move(planned.chunks_));
1100 plan_.operations_.clear();
1101 operations_.clear();
1105 template <
typename World>
1107 std::span<const QueuedOperation> operations,
1111 void reserve(std::size_t size) {
1112 operations_.reserve(size);
1113 plan_.operations_.reserve(size);
1116 void push_report(
OperationReport report) { operations_.push_back(report); }
1119 plan_.operations_.push_back(std::move(planned));
1122 template <
typename World>
1124 std::vector<ChunkKey>&& chunks)
1129 detail::planned_world_stamp<World>(),
1133 [[nodiscard]]
auto acquire_chunks() -> std::vector<ChunkKey> {
1134 if (chunk_pool_.empty()) {
1137 auto chunks = std::move(chunk_pool_.back());
1138 chunk_pool_.pop_back();
1142 void recycle_chunks(std::vector<ChunkKey>&& chunks) {
1144 chunk_pool_.push_back(std::move(chunks));
1148 recycle_chunks(std::move(planned.chunks_));
1151 std::vector<OperationReport> operations_;
1153 std::vector<std::vector<ChunkKey>> chunk_pool_;
1159 [[nodiscard]]
auto update_field(
1161 Priority priority = Priority::GameplayCritical,
1162 BudgetPolicy budget_policy = BudgetPolicy::MustRun,
1163 std::source_location source = std::source_location::current())
1165 const auto id =
OpId{
static_cast<std::uint64_t
>(operations_.size())};
1166 const auto handle =
OpHandle{
id.value};
1168 OperationKind::UpdateField,
1181 [[nodiscard]]
auto update_field(
1183 Priority priority = Priority::GameplayCritical,
1184 BudgetPolicy budget_policy = BudgetPolicy::MustRun,
1185 std::source_location source = std::source_location::current())
1187 return update_field(std::move(domain),
FieldAccessDesc{}, write_policy,
1188 priority, budget_policy, source);
1191 [[nodiscard]]
constexpr auto operations()
const noexcept
1192 -> std::span<const QueuedOperation> {
1193 return {operations_.data(), operations_.size()};
1196 [[nodiscard]]
constexpr auto operation(
OpHandle handle)
const noexcept
1198 if (handle.value >= operations_.size()) {
1201 return &operations_[
static_cast<std::size_t
>(handle.value)];
1204 [[nodiscard]]
constexpr bool empty()
const noexcept {
1205 return operations_.empty();
1208 [[nodiscard]]
constexpr auto size()
const noexcept -> std::size_t {
1209 return operations_.size();
1216 void clear()
noexcept { operations_.clear(); }
1219 std::vector<QueuedOperation> operations_;
1224[[nodiscard]]
constexpr auto operation_access(
1233[[nodiscard]]
constexpr bool is_valid_field_access(
1234 WritePolicy write_policy, FieldAccessDesc field_access)
noexcept {
1235 if (write_policy == WritePolicy::ReadOnly && field_access.write_mask != 0) {
1241template <
typename World>
1242[[nodiscard]]
auto validate_explicit_chunks(
const World& world,
1243 std::span<const ChunkKey> chunks,
1244 ChunkKey& invalid_chunk)
noexcept
1246 for (
const auto key : chunks) {
1247 if (world.try_chunk(key) ==
nullptr) {
1248 invalid_chunk = key;
1258template <
typename World>
1259[[nodiscard]]
auto expand_domain(
const World& world,
const DomainDesc& domain,
1260 std::vector<ChunkKey>& chunks,
1261 ChunkKey& invalid_chunk) ->
bool {
1263 switch (domain.kind()) {
1264 case DomainKind::ExplicitChunks:
1265 if (!validate_explicit_chunks(world, domain.explicit_chunks(),
1269 chunks.assign(domain.explicit_chunks().begin(),
1270 domain.explicit_chunks().end());
1272 case DomainKind::DirtyChunks:
1273 world.collect_dirty_chunks(domain.mask(), chunks);
1275 case DomainKind::ActiveChunks:
1276 world.collect_active_chunks(domain.mask(), chunks);
1278 case DomainKind::ResidentChunks:
1279 chunks.reserve(
static_cast<std::size_t
>(World::chunk_count));
1280 for (std::uint64_t key = 0; key < World::chunk_count; ++key) {
1281 chunks.push_back(ChunkKey{key});
1288[[nodiscard]]
constexpr auto hazard_mask(FieldAccessDesc earlier,
1289 FieldAccessDesc later)
noexcept
1291 return (earlier.write_mask & later.write_mask) |
1292 (earlier.write_mask & later.read_mask) |
1293 (earlier.read_mask & later.write_mask);
1296[[nodiscard]]
constexpr bool chunks_overlap(
1297 std::span<const ChunkKey> lhs, std::span<const ChunkKey> rhs)
noexcept {
1298 std::size_t lhs_index = 0;
1299 std::size_t rhs_index = 0;
1300 while (lhs_index < lhs.size() && rhs_index < rhs.size()) {
1301 const auto lhs_key = lhs[lhs_index].value;
1302 const auto rhs_key = rhs[rhs_index].value;
1303 if (lhs_key == rhs_key) {
1306 if (lhs_key < rhs_key) {
1315[[nodiscard]]
constexpr auto find_hazard(
1316 std::span<const PlannedOperation> earlier_ops,
1317 const PlannedOperation& later)
noexcept ->
const PlannedOperation* {
1318 for (
const auto& earlier : earlier_ops) {
1319 if (hazard_mask(earlier.field_access, later.field_access) == 0) {
1322 if (chunks_overlap(earlier.chunks(), later.chunks())) {
1330[[nodiscard]]
constexpr bool is_parallel_supported_policy(
1331 WritePolicy policy)
noexcept {
1332 return policy == WritePolicy::ReadOnly ||
1333 policy == WritePolicy::UniquePerChunk;
1336[[nodiscard]]
constexpr bool is_mutating_policy(WritePolicy policy)
noexcept {
1337 return policy != WritePolicy::ReadOnly;
1340[[nodiscard]]
constexpr bool parallel_phase_conflict(
1341 const PlannedOperation& lhs,
const PlannedOperation& rhs)
noexcept {
1342 if (!chunks_overlap(lhs.chunks(), rhs.chunks())) {
1345 if (is_mutating_policy(lhs.write_policy) ||
1346 is_mutating_policy(rhs.write_policy)) {
1349 return hazard_mask(lhs.field_access, rhs.field_access) != 0;
1352[[nodiscard]]
constexpr auto dirty_axis_end(std::int64_t origin,
1353 std::uint64_t extent)
noexcept
1358 return detail::box_axis_end(origin, extent);
1361[[nodiscard]]
constexpr auto dirty_min(std::int64_t lhs,
1362 std::int64_t rhs)
noexcept
1364 return lhs < rhs ? lhs : rhs;
1367[[nodiscard]]
constexpr auto dirty_max(std::int64_t lhs,
1368 std::int64_t rhs)
noexcept
1370 return lhs < rhs ? rhs : lhs;
1373[[nodiscard]]
constexpr auto dirty_union_extent(std::int64_t origin,
1374 std::int64_t end)
noexcept
1379 return abs_delta(end, origin);
1382[[nodiscard]]
constexpr auto union_dirty_bounds(Box3 lhs, Box3 rhs)
noexcept
1384 const auto min_x = dirty_min(lhs.origin.x, rhs.origin.x);
1385 const auto min_y = dirty_min(lhs.origin.y, rhs.origin.y);
1386 const auto min_z = dirty_min(lhs.origin.z, rhs.origin.z);
1387 const auto max_x = dirty_max(dirty_axis_end(lhs.origin.x, lhs.extent.x),
1388 dirty_axis_end(rhs.origin.x, rhs.extent.x));
1389 const auto max_y = dirty_max(dirty_axis_end(lhs.origin.y, lhs.extent.y),
1390 dirty_axis_end(rhs.origin.y, rhs.extent.y));
1391 const auto max_z = dirty_max(dirty_axis_end(lhs.origin.z, lhs.extent.z),
1392 dirty_axis_end(rhs.origin.z, rhs.extent.z));
1395 Coord3{min_x, min_y, min_z},
1397 dirty_union_extent(min_x, max_x),
1398 dirty_union_extent(min_y, max_y),
1399 dirty_union_extent(min_z, max_z),
1410template <
typename World>
1412 std::span<const QueuedOperation> operations,
1421 std::is_same_v<typename World::residency_type, AlwaysResident>,
1422 "Queued operations require an AlwaysResidentWorld; sparse queued-ops "
1423 "support is deferred to a later slice.");
1425 report.reserve(operations.size());
1427 for (std::size_t op_index = 0; op_index < operations.size(); ++op_index) {
1428 const auto& op = operations[op_index];
1429 const auto canonical_handle =
1430 OpHandle{
static_cast<std::uint64_t
>(op_index)};
1431 const auto canonical_id =
OpId{
static_cast<std::uint64_t
>(op_index)};
1435 OperationStatus::Planned,
1436 OperationFailure::None,
1437 detail::operation_access(op),
1449 if (op.handle != canonical_handle || op.id != canonical_id) {
1450 op_report.status = OperationStatus::InvalidIdentity;
1451 op_report.failure = op.handle != canonical_handle
1452 ? OperationFailure::NonDenseHandle
1453 : OperationFailure::NonDenseId;
1454 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
1455 "invalid_identity", op_index);
1456 report.push_report(op_report);
1460 if (!is_valid_write_policy(op.write_policy)) {
1461 op_report.status = OperationStatus::InvalidWritePolicy;
1462 op_report.failure = OperationFailure::InvalidWritePolicyValue;
1463 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
1464 "invalid_write_policy", op_index);
1465 report.push_report(op_report);
1469 if (!detail::is_valid_field_access(op.write_policy, op.field_access)) {
1470 op_report.status = OperationStatus::InvalidFieldAccess;
1471 op_report.failure = OperationFailure::ReadOnlyWriteMask;
1472 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
1473 "invalid_field_access", op_index);
1474 report.push_report(op_report);
1478 auto planned_chunks = report.acquire_chunks();
1480 if (!detail::expand_domain(world, op.domain, planned_chunks,
1482 op_report.status = OperationStatus::InvalidDomain;
1483 op_report.failure = OperationFailure::ExplicitChunkOutOfRange;
1484 op_report.detail_chunk = invalid_chunk;
1485 op_report.has_detail_chunk =
true;
1486 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
1487 "invalid_domain", op_index);
1488 report.recycle_chunks(std::move(planned_chunks));
1489 report.push_report(op_report);
1494 report.template make_planned<World>(op, std::move(planned_chunks));
1496 if (
const auto* conflict =
1497 detail::find_hazard(report.plan().operations(), planned);
1498 conflict !=
nullptr) {
1499 op_report.status = OperationStatus::HazardConflict;
1500 op_report.failure = OperationFailure::FieldHazardConflict;
1501 op_report.conflict_handle = conflict->handle;
1502 op_report.conflict_id = conflict->id;
1503 op_report.conflict_mask =
1504 detail::hazard_mask(conflict->field_access, planned.field_access);
1505 op_report.has_conflict =
true;
1506 op_report.chunk_count = planned.chunks().size();
1507 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
"conflict",
1509 report.recycle_chunks(std::move(planned));
1510 report.push_report(op_report);
1514 op_report.chunk_count = planned.chunks().size();
1515 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
"planned",
1517 report.push_planned(std::move(planned));
1518 report.push_report(op_report);
1524template <
typename World>
1526[[nodiscard]]
auto plan_operations(
const World& world,
1527 std::span<const QueuedOperation> operations)
1530 plan_operations(world, operations, report);
1534template <
typename World>
1536auto plan_operations(
const World& world,
const FrameOps& ops,
1537 ExecutionReport& report) ->
const ExecutionReport& {
1538 return plan_operations(world, ops.operations(), report);
1541template <
typename World>
1543[[nodiscard]]
auto plan_operations(
const World& world,
const FrameOps& ops)
1545 return plan_operations(world, ops.operations());
1549[[nodiscard]]
constexpr auto planned_chunk_domain(
1551 return chunk_domain(operation.chunks());
1557 const auto operations = plan.operations();
1559 phases.reserve(operations.size());
1561 for (std::size_t i = 0; i < operations.size(); ++i) {
1562 const auto& operation = operations[i];
1563 if (!detail::is_parallel_supported_policy(operation.write_policy)) {
1564 phases.status_ = ExecutionPhaseStatus::UnsupportedWritePolicy;
1565 phases.failed_operation_index_ = i;
1566 phases.failed_write_policy_ = operation.write_policy;
1567 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
1568 "unsupported_write_policy", i);
1572 if (phases.phases_.empty()) {
1573 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
"new_phase",
1575 phases.push_phase(plan, i, 1, operation);
1579 const auto& phase = phases.phases_.back();
1580 auto conflicts =
false;
1581 const auto end = phase.first_operation() + phase.operation_count();
1582 for (std::size_t j = phase.first_operation(); j < end; ++j) {
1583 if (detail::parallel_phase_conflict(operations[j], operation)) {
1590 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
"new_phase",
1592 phases.push_phase(plan, i, 1, operation);
1594 TESS_DIAG_TRACE_VALUE(diagnostics::TraceCategory::Planner,
"merged", i);
1595 phases.extend_last_phase(operation);
1606template <
typename World>
1609 static_assert(std::is_same_v<typename World::residency_type, AlwaysResident>,
1610 "Queued-op dirty merge requires an AlwaysResidentWorld; sparse "
1611 "queued-ops support is deferred to a later slice.");
1613 const auto validation = dirty.validation_status(world);
1614 if (validation != PlannedDirtyMergeStatus::Merged) {
1621 auto& records = dirty.records_;
1622 std::sort(records.begin(), records.end(),
1624 return lhs.chunk.value < rhs.chunk.value;
1630 auto merged_count = std::size_t{0};
1631 for (std::size_t i = 0; i < records.size();) {
1632 auto chunk = records[i].chunk;
1633 auto dirty_mask = records[i].dirty_mask;
1634 auto bounds = records[i].bounds;
1637 while (i < records.size() && records[i].chunk == chunk) {
1638 dirty_mask |= records[i].dirty_mask;
1639 bounds = detail::union_dirty_bounds(bounds, records[i].bounds);
1643 world.mark_dirty(chunk, dirty_mask, bounds);
1647 TESS_DIAG_EVENT_VALUE(queued_dirty_merge, merged_count);
1650 PlannedDirtyMergeStatus::Merged,
1657 PlannedDirtyPartitions& partitions)
1659 auto* world_stamp = dirty.world_stamp_;
1660 for (
const auto& partition : partitions.partitions_) {
1661 const auto* partition_stamp = partition.world_stamp_;
1662 if (partition_stamp ==
nullptr) {
1665 if (world_stamp ==
nullptr) {
1666 world_stamp = partition_stamp;
1669 if (world_stamp->shape_identity != partition_stamp->shape_identity) {
1671 PlannedDirtyCollectStatus::InvalidShape,
1675 if (world_stamp->chunk_limit != partition_stamp->chunk_limit) {
1677 PlannedDirtyCollectStatus::InvalidChunk,
1683 auto required_capacity = dirty.records_.size();
1684 auto record_count = std::size_t{0};
1685 for (
const auto& partition : partitions.partitions_) {
1686 const auto partition_size = partition.records_.size();
1687 if (partition_size > dirty.records_.max_size() - required_capacity) {
1688 throw std::length_error{
"planned dirty record count exceeds max_size"};
1690 required_capacity += partition_size;
1691 record_count += partition_size;
1695 dirty.records_.reserve(required_capacity);
1697 for (
auto& partition : partitions.partitions_) {
1698 if (partition.world_stamp_ !=
nullptr) {
1699 dirty.world_stamp_ = partition.world_stamp_;
1701 dirty.records_.insert(dirty.records_.end(), partition.records_.begin(),
1702 partition.records_.end());
1705 TESS_DIAG_EVENT_VALUE(queued_dirty_collect, record_count);
1707 PlannedDirtyCollectStatus::Collected,
1712template <
typename World>
1717 for (
const auto& partition : partitions.partitions()) {
1718 const auto validation = partition.validation_status(world);
1719 if (validation != PlannedDirtyMergeStatus::Merged) {
1723 dirty_scratch.clear();
1724 const auto collected = collect_planned_dirty(dirty_scratch, partitions);
1725 if (!collected.ok()) {
1727 collected.status == PlannedDirtyCollectStatus::InvalidShape
1728 ? PlannedDirtyMergeStatus::InvalidShape
1729 : PlannedDirtyMergeStatus::InvalidChunk,
1733 return merge_planned_dirty(world, dirty_scratch);
1736template <
typename World>
1740 if (scratch.world_stamp_ ==
nullptr) {
1742 PlannedDirtyMergeStatus::Merged,
1746 const auto validation =
1747 detail::validate_planned_world_stamp<World>(scratch.world_stamp_);
1748 if (validation != PlannedExecutionStatus::Executed) {
1750 validation == PlannedExecutionStatus::InvalidShape
1751 ? PlannedDirtyMergeStatus::InvalidShape
1752 : PlannedDirtyMergeStatus::InvalidChunk,
1757 auto& merged = scratch.merged_dirty_;
1758 auto record_count = std::size_t{0};
1759 for (
const auto& partition : scratch.dirty_partitions_) {
1760 const auto partition_size = partition.records().size();
1761 if (partition_size > merged.records_.max_size() - record_count) {
1762 throw std::length_error{
"planned dirty record count exceeds max_size"};
1764 record_count += partition_size;
1769 merged.records_.reserve(record_count);
1770 merged.world_stamp_ = scratch.world_stamp_;
1771 for (
auto& partition : scratch.dirty_partitions_) {
1772 const auto records = partition.records();
1773 merged.records_.insert(merged.records_.end(), records.begin(),
1777 TESS_DIAG_EVENT_VALUE(queued_dirty_collect, record_count);
1783template <
typename World>
1784auto detail::merge_planned_dirty_after_exception(
1787 if (scratch.world_stamp_ ==
nullptr) {
1789 PlannedDirtyMergeStatus::Merged,
1793 const auto validation =
1794 detail::validate_planned_world_stamp<World>(scratch.world_stamp_);
1795 if (validation != PlannedExecutionStatus::Executed) {
1797 validation == PlannedExecutionStatus::InvalidShape
1798 ? PlannedDirtyMergeStatus::InvalidShape
1799 : PlannedDirtyMergeStatus::InvalidChunk,
1807 auto record_count = std::size_t{0};
1808 for (
const auto& partition : scratch.dirty_partitions_) {
1809 const auto partition_size = partition.records().size();
1810 if (partition_size >
1811 std::numeric_limits<std::size_t>::max() - record_count) {
1812 record_count = std::numeric_limits<std::size_t>::max();
1815 record_count += partition_size;
1817 auto merged_count = std::size_t{0};
1818 for (std::size_t partition_index = 0;
1819 partition_index < scratch.dirty_partitions_.size(); ++partition_index) {
1820 const auto records = scratch.dirty_partitions_[partition_index].records();
1821 for (std::size_t record_index = 0; record_index < records.size();
1823 const auto record = records[record_index];
1824 auto appeared_earlier =
false;
1825 for (std::size_t earlier_partition = 0;
1826 earlier_partition <= partition_index && !appeared_earlier;
1827 ++earlier_partition) {
1828 const auto earlier_records =
1829 scratch.dirty_partitions_[earlier_partition].records();
1830 const auto earlier_count = earlier_partition == partition_index
1832 : earlier_records.size();
1833 for (std::size_t earlier_index = 0; earlier_index < earlier_count;
1835 if (earlier_records[earlier_index].chunk == record.chunk) {
1836 appeared_earlier =
true;
1841 if (appeared_earlier) {
1845 auto dirty_mask = record.dirty_mask;
1846 auto bounds = record.bounds;
1847 for (std::size_t later_partition = partition_index;
1848 later_partition < scratch.dirty_partitions_.size();
1849 ++later_partition) {
1850 const auto later_records =
1851 scratch.dirty_partitions_[later_partition].records();
1852 const auto first_later = later_partition == partition_index
1855 for (std::size_t later_index = first_later;
1856 later_index < later_records.size(); ++later_index) {
1857 const auto later = later_records[later_index];
1858 if (later.chunk == record.chunk) {
1859 dirty_mask |= later.dirty_mask;
1860 bounds = detail::union_dirty_bounds(bounds, later.bounds);
1864 world.mark_dirty(record.chunk, dirty_mask, bounds);
1868 for (
auto& partition : scratch.dirty_partitions_) {
1871 TESS_DIAG_EVENT_VALUE(queued_dirty_collect, record_count);
1872 TESS_DIAG_EVENT_VALUE(queued_dirty_merge, merged_count);
1876 PlannedDirtyMergeStatus::Merged,
1882template <WritePolicy Policy>
1883[[nodiscard]]
constexpr bool planned_policy_matches(
1885 return operation.write_policy == Policy;
1889template <WritePolicy Policy,
typename World>
1890[[nodiscard]]
auto validate_planned_operation(
1892 -> PlannedExecutionStatus {
1893 const auto world_status = operation.world_validation_status(world);
1894 if (world_status != PlannedExecutionStatus::Executed) {
1895 return world_status;
1897 if (!planned_policy_matches<Policy>(operation)) {
1898 return PlannedExecutionStatus::PolicyMismatch;
1900 return PlannedExecutionStatus::Executed;
1904template <WritePolicy Policy,
typename World>
1905[[nodiscard]]
constexpr auto try_planned_block_ctx(
1907 -> std::optional<BlockCtx<World, Policy>> {
1909 std::is_same_v<typename World::residency_type, AlwaysResident>,
1910 "Queued-op execution requires an AlwaysResidentWorld; sparse queued-ops "
1911 "support is deferred to a later slice.");
1912 if (validate_planned_operation<Policy>(world, operation) !=
1913 PlannedExecutionStatus::Executed) {
1914 return std::nullopt;
1916 return block_ctx<Policy>(world, planned_chunk_domain(operation));
1920template <WritePolicy Policy,
typename World,
typename Fn>
1923 const auto validation = validate_planned_operation<Policy>(world, operation);
1924 if (validation != PlannedExecutionStatus::Executed) {
1930 auto ctx = block_ctx<Policy>(world, planned_chunk_domain(operation));
1932 std::size_t chunk_count = 0;
1933 auto&& callback = fn;
1934 ctx.for_each_chunk([&](
auto view) {
1935 if (operation.field_access.dirty_mask != 0) {
1936 world.mark_dirty(view.key(), operation.field_access.dirty_mask,
1944 PlannedExecutionStatus::Executed,
1950template <WritePolicy Policy,
typename World,
typename Fn>
1956 const auto validation = validate_planned_operation<Policy>(world, operation);
1957 if (validation != PlannedExecutionStatus::Executed) {
1963 if (operation.field_access.dirty_mask != 0) {
1964 const auto dirty_validation = dirty.validation_status(world);
1965 if (dirty_validation != PlannedDirtyMergeStatus::Merged) {
1967 dirty_validation == PlannedDirtyMergeStatus::InvalidShape
1968 ? PlannedExecutionStatus::InvalidShape
1969 : PlannedExecutionStatus::InvalidChunk,
1974 return detail::execute_validated_planned_operation_deferred_dirty<
true,
1976 world, operation, dirty, std::forward<Fn>(fn));
1983template <WritePolicy Policy,
typename World,
typename Fn>
1986 std::size_t chunk_count = 0;
1987 auto&& callback = fn;
1988 for (
const auto& operation : plan.operations()) {
1989 auto result = execute_planned_operation<Policy>(world, operation, callback);
1990 if (result.status != PlannedExecutionStatus::Executed) {
1993 chunk_count + result.chunk_count,
1996 chunk_count += result.chunk_count;
1999 PlannedExecutionStatus::Executed,
2005template <WritePolicy Policy,
typename World,
typename Fn>
2009 std::size_t chunk_count = 0;
2010 auto&& callback = fn;
2011 for (
const auto& operation : plan.operations()) {
2012 auto result = execute_planned_operation_deferred_dirty<Policy>(
2013 world, operation, dirty, callback);
2014 if (result.status != PlannedExecutionStatus::Executed) {
2017 chunk_count + result.chunk_count,
2020 chunk_count += result.chunk_count;
2023 PlannedExecutionStatus::Executed,
2028template <WritePolicy Policy,
typename Executor,
typename World,
typename Fn>
2031auto execute_phase_deferred_dirty_with(Executor&& executor,
World& world,
2036 const auto operations = plan.operations();
2037 const auto phase_validation =
2038 detail::execution_phase_validation_status<Policy>(world, plan, phase);
2039 if (phase_validation != PlannedExecutionStatus::Executed) {
2040 detail::record_execution_phase_validation_failure(phase_validation);
2046 const auto dirty_validation = dirty.validation_status(world);
2047 if (dirty_validation != PlannedDirtyMergeStatus::Merged) {
2048 TESS_DIAG_EVENT(queued_phase_failure);
2050 dirty_validation == PlannedDirtyMergeStatus::InvalidShape
2051 ? PlannedExecutionStatus::InvalidShape
2052 : PlannedExecutionStatus::InvalidChunk,
2057 TESS_DIAG_EVENT_VALUE(queued_phase_execute, phase.operation_count());
2058 std::size_t chunk_count = 0;
2059 auto&& callback = fn;
2060 auto result = execute_operation_index_range(
2061 std::forward<Executor>(executor), executor_phase_range(phase),
2062 [&](std::size_t index) {
2063 auto operation_result =
2064 detail::execute_validated_planned_operation_deferred_dirty<
true,
2066 world, operations[index], dirty, callback);
2067 if (operation_result.status == PlannedExecutionStatus::Executed) {
2068 chunk_count += operation_result.chunk_count;
2070 return operation_result;
2072 if (result.status != PlannedExecutionStatus::Executed) {
2073 TESS_DIAG_EVENT(queued_phase_failure);
2074 result.chunk_count = chunk_count;
2079 PlannedExecutionStatus::Executed,
2085template <WritePolicy Policy,
typename Executor,
typename World,
typename Fn>
2091 const auto operations = plan.operations();
2092 const auto phase_validation =
2093 detail::execution_phase_validation_status<Policy>(world, plan, phase);
2094 if (phase_validation != PlannedExecutionStatus::Executed) {
2095 detail::record_execution_phase_validation_failure(phase_validation);
2102 TESS_DIAG_EVENT_VALUE(queued_phase_execute, phase.operation_count());
2103 TESS_DIAG_EVENT_VALUE(queued_partitioned_phase, phase.operation_count());
2104 scratch.prepare(world, phase.operation_count());
2105 auto&& callback = fn;
2106 auto result = execute_operation_index_range(
2107 std::forward<Executor>(executor), executor_phase_range(phase),
2108 [&](std::size_t index) {
2109 const auto offset = index - phase.first_operation();
2110 auto operation_result =
2111 detail::execute_validated_phase_operation_deferred_dirty<Policy>(
2112 world, operations[index], scratch.dirty_for_operation(offset),
2114 scratch.record_result(offset, operation_result);
2115 return operation_result;
2118 std::size_t chunk_count = 0;
2119 for (
const auto operation_result : scratch.results()) {
2120 if (operation_result.status != PlannedExecutionStatus::Executed) {
2121 TESS_DIAG_EVENT(queued_phase_failure);
2123 operation_result.status,
2127 chunk_count += operation_result.chunk_count;
2130 if (result.status != PlannedExecutionStatus::Executed) {
2131 TESS_DIAG_EVENT(queued_phase_failure);
2139 PlannedExecutionStatus::Executed,
2145template <WritePolicy Policy,
typename World,
typename Fn>
2151 return execute_phase_deferred_dirty_with<Policy>(executor, world, plan, phase,
2152 dirty, std::forward<Fn>(fn));
friend auto plan_parallel_execution_phases(const ExecutionPlan &plan) -> ExecutionPhasePlan
Definition queued.h:1555
constexpr bool policy_matches() const noexcept
Definition queued.h:450
auto world_validation_status(const World &) const noexcept -> PlannedExecutionStatus
Definition queued.h:439
constexpr bool belongs_to(const ExecutionPlan &plan) const noexcept
Definition queued.h:432
friend auto plan_operations(const World &world, std::span< const QueuedOperation > operations, ExecutionReport &report) -> const ExecutionReport &
Definition queued.h:1411
friend auto collect_planned_dirty(PlannedDirtyAccumulator &dirty, PlannedDirtyPartitions &partitions) -> PlannedDirtyCollectResult
Definition queued.h:1656
auto validation_status(const World &) const noexcept -> PlannedDirtyMergeStatus
Definition queued.h:726
friend auto merge_planned_dirty(World &world, PlannedDirtyAccumulator &dirty) noexcept -> PlannedDirtyMergeResult
Definition queued.h:1607
auto record(const World &, ChunkKey chunk, std::uint32_t dirty_mask, Box3 bounds) -> PlannedDirtyRecordStatus
Definition queued.h:690
friend auto execute_planned_operation_deferred_dirty(World &world, const PlannedOperation &operation, PlannedDirtyAccumulator &dirty, Fn &&fn) -> PlannedExecutionResult
Definition queued.h:1951
friend auto collect_planned_dirty(PlannedDirtyAccumulator &dirty, PlannedDirtyPartitions &partitions) -> PlannedDirtyCollectResult
Definition queued.h:1656
constexpr auto chunks() const noexcept -> std::span< const ChunkKey >
Definition queued.h:272
auto world_validation_status(const World &) const noexcept -> PlannedExecutionStatus
Definition queued.h:279
static auto create(const World &world, const QueuedOperation &operation, std::span< const ChunkKey > chunks) -> PlannedOperationCreateResult
Definition queued.h:321
friend auto execute_phase_partitioned_dirty_with_results(Executor &&executor, World &world, const ExecutionPlan &plan, const ExecutionPhase &phase, PlannedPhaseExecutionScratch &scratch, ResultChannel< T > &channel, Fn &&fn) -> PlannedExecutionResult
Executes one phase while publishing per-operation payloads and completions.
Definition result_channel.h:293
friend auto execute_phase_partitioned_dirty_with(Executor &&executor, World &world, const ExecutionPlan &plan, const ExecutionPhase &phase, PlannedPhaseExecutionScratch &scratch, Fn &&fn) -> PlannedExecutionResult
Definition queued.h:2086
friend auto merge_planned_dirty(World &world, PlannedPhaseExecutionScratch &scratch) -> PlannedDirtyMergeResult
Definition queued.h:1738
Dense per-operation completion and payload channel.
Definition result_channel.h:92
Definition phase_executor.h:126
Definition phase_executor.h:52
Definition phase_executor.h:63
Definition phase_executor.h:95