51 OperationStatus status = OperationStatus::Planned;
52 OperationFailure failure = OperationFailure::None;
53 PlannedExecutionStatus execution = PlannedExecutionStatus::Executed;
54 std::size_t chunk_count = 0;
55 std::source_location source = std::source_location::current();
56 bool completed =
false;
58 [[nodiscard]]
constexpr bool ok()
const noexcept {
59 return completed && status == OperationStatus::Planned &&
60 failure == OperationFailure::None &&
61 execution == PlannedExecutionStatus::Executed;
93 static_assert(std::is_default_constructible_v<T>,
94 "result payloads are default-constructed into slots");
97 ResultChannel() =
default;
98 ResultChannel(
const ResultChannel&) =
delete;
99 auto operator=(
const ResultChannel&) -> ResultChannel& =
delete;
100 ResultChannel(ResultChannel&&) =
delete;
101 auto operator=(ResultChannel&&) -> ResultChannel& =
delete;
105 void reserve_operations(std::size_t count) { slots_.reserve(count); }
109 void clear()
noexcept {
114 [[nodiscard]]
auto state(
OpHandle handle)
const noexcept -> OpResultState {
115 if (handle.value >= slots_.size()) {
116 return OpResultState::Unbound;
118 return slots_[
static_cast<std::size_t
>(handle.value)].state;
123 [[nodiscard]]
auto completion(
OpHandle handle)
const noexcept
125 if (handle.value >= slots_.size()) {
128 return slots_[
static_cast<std::size_t
>(handle.value)].completion;
131 [[nodiscard]]
auto size()
const noexcept -> std::size_t {
132 return slots_.size();
137 [[nodiscard]]
auto generation()
const noexcept -> std::uint64_t {
150 template <
typename Visitor>
151 auto drain_results(Visitor&& visit) -> std::size_t {
152 std::size_t visited = 0;
153 const auto drain_generation = generation_;
154 for (std::size_t index = 0;
155 generation_ == drain_generation && index < slots_.size(); ++index) {
156 if (slots_[index].drained ||
157 (slots_[index].state != OpResultState::Ready &&
158 slots_[index].state != OpResultState::Failed)) {
161 const auto has_value = slots_[index].state == OpResultState::Ready;
166 slots_[index].drained =
true;
167 constexpr auto no_throw_visitor =
170 const auto visit_slot = [&]()
noexcept(no_throw_visitor) {
171 visit(
OpHandle{
static_cast<std::uint64_t
>(index)},
172 slots_[index].completion,
173 has_value ? &slots_[index].value :
nullptr);
175 if constexpr (!has_exceptions || no_throw_visitor) {
178#if TESS_HAS_EXCEPTIONS
182 if (generation_ == drain_generation && index < slots_.size()) {
183 slots_[index].drained =
false;
198 OpResultState state = OpResultState::Unbound;
199 bool drained =
false;
207 if (handle.value >= slots_.size()) {
208 slots_.resize(
static_cast<std::size_t
>(handle.value) + 1);
212 void prepare_operation(
OpHandle handle, std::source_location source) {
214 auto& slot = slots_[
static_cast<std::size_t
>(handle.value)];
217 slot.completion.source = source;
218 slot.state = OpResultState::Pending;
219 slot.drained =
false;
229 [[nodiscard]]
auto value_for(
OpHandle handle)
noexcept -> T& {
230 if (handle.value >= slots_.size()) {
232 "ResultChannel::value_for called with a handle that has no slot");
234 return slots_[
static_cast<std::size_t
>(handle.value)].value;
238 std::source_location source)
noexcept {
239 TESS_ASSERT(handle.value < slots_.size());
240 auto& slot = slots_[
static_cast<std::size_t
>(handle.value)];
241 slot.completion.status = OperationStatus::Planned;
242 slot.completion.failure = OperationFailure::None;
243 slot.completion.execution = result.status;
244 slot.completion.chunk_count = result.chunk_count;
245 slot.completion.source = source;
246 slot.completion.completed =
true;
247 slot.state = result.status == PlannedExecutionStatus::Executed
248 ? OpResultState::Ready
249 : OpResultState::Failed;
254 auto& slot = slots_[
static_cast<std::size_t
>(handle.value)];
257 slot.completion.status = report.status;
258 slot.completion.failure = report.failure;
259 slot.completion.chunk_count = report.chunk_count;
260 slot.completion.source = report.source;
261 slot.completion.completed =
true;
262 slot.state = OpResultState::Failed;
263 slot.drained =
false;
266 template <
typename U>
268 ResultChannel<U>& channel) -> std::size_t;
270 template <WritePolicy Policy,
typename Executor,
typename World,
typename U,
272 friend auto execute_phase_partitioned_dirty_with_results(
277 template <WritePolicy Policy,
typename World,
typename U,
typename Fn>
278 friend auto execute_plan_deferred_dirty_with_results(
282 std::vector<Slot> slots_;
283 std::uint64_t generation_ = 0;
319 const auto operations = plan.operations();
323 const auto phase_validation =
324 detail::execution_phase_validation_status<Policy>(world, plan, phase);
325 if (phase_validation != PlannedExecutionStatus::Executed) {
326 detail::record_execution_phase_validation_failure(phase_validation);
333 TESS_DIAG_EVENT_VALUE(queued_phase_execute, phase.operation_count());
334 TESS_DIAG_EVENT_VALUE(queued_partitioned_phase, phase.operation_count());
335 for (
const auto& operation :
336 operations.subspan(phase.first_operation(), phase.operation_count())) {
337 channel.prepare_operation(operation.handle, operation.source);
339 scratch.prepare(world, phase.operation_count());
340 auto&& callback = fn;
341 using View = detail::PlannedChunkView<Policy, World>;
342 constexpr auto no_throw_callback =
343 std::is_nothrow_invocable_v<
decltype(callback)&, View&, T&>;
344 for (std::size_t offset = 0; offset < phase.operation_count(); ++offset) {
345 const auto index = phase.first_operation() + offset;
346 scratch.dirty_for_operation(offset).reserve(
347 !operations[index].field_access.dirty_mask
349 : operations[index].chunks().size());
351 auto result = execute_operation_index_range(
352 std::forward<Executor>(executor), executor_phase_range(phase),
357 [&](std::size_t index)
noexcept(no_throw_callback) {
358 const auto offset = index - phase.first_operation();
359 const auto& operation = operations[index];
360 auto& value = channel.value_for(operation.handle);
361 auto operation_result =
362 detail::execute_validated_phase_operation_deferred_dirty<Policy>(
363 world, operation, scratch.dirty_for_operation(offset),
364 [&](
auto view)
noexcept(no_throw_callback) {
365 callback(view, value);
367 channel.complete(operation.handle, operation_result, operation.source);
368 scratch.record_result(offset, operation_result);
369 return operation_result;
372 std::size_t chunk_count = 0;
373 for (
const auto operation_result : scratch.results()) {
374 if (operation_result.status != PlannedExecutionStatus::Executed) {
375 TESS_DIAG_EVENT(queued_phase_failure);
377 operation_result.status,
381 chunk_count += operation_result.chunk_count;
384 if (result.status != PlannedExecutionStatus::Executed) {
385 TESS_DIAG_EVENT(queued_phase_failure);
393 PlannedExecutionStatus::Executed,
415 world, operation, dirty, [&](
auto view) { callback(view, value); });
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:315