/// @file dpf/net/dealer_cursor.hpp /// @brief Per-(round, index) correction words for a batch session. #ifndef LIBDPF_INCLUDE_DPF_NET_DEALER_CURSOR_HPP__ #define LIBDPF_INCLUDE_DPF_NET_DEALER_CURSOR_HPP__ #include #include #include #include #include #include #include #include "dpf/net/stream_array.hpp" namespace dpf { namespace net { /// @brief Empty correction / blind marker. Protocols that need no dealer /// material specialize on this type and skip the cursor. struct empty_pad { }; /// @brief Seekable table of dealer corrections, one lane per round. /// @details Size is fixed at construction: `count` instances and one slot /// width per round. Live dealers and inbox tapes both fill this /// table before the online session runs, or fill it lazily through /// `put`. Sessions read with `at(round, index)`. class dealer_cursor { public: dealer_cursor() = default; dealer_cursor(std::size_t count, std::vector slot_bytes) : count_(count), slot_bytes_(std::move(slot_bytes)), offsets_(slot_bytes_.size() + 1, 0) { for (std::size_t r = 0; r < slot_bytes_.size(); ++r) offsets_[r + 1] = offsets_[r] + count_ * slot_bytes_[r]; store_.assign(offsets_.back(), 0); ready_.assign(slot_bytes_.size() * count_, false); } std::size_t count() const noexcept { return count_; } std::size_t rounds() const noexcept { return slot_bytes_.size(); } std::size_t slot_bytes(std::uint16_t round) const { if (round >= slot_bytes_.size()) throw std::out_of_range("dealer_cursor round"); return slot_bytes_[round]; } void put(std::uint16_t round, std::size_t index, const std::uint8_t * bytes, std::size_t n) { check(round, index, n); std::memcpy(ptr(round, index), bytes, n); ready_[ready_index(round, index)] = true; } /// @brief Fill every index of `round` from a contiguous dealer tape. void put_round(std::uint16_t round, const std::uint8_t * bytes, std::size_t nbytes) { if (round >= slot_bytes_.size()) throw std::out_of_range("dealer_cursor round"); const std::size_t need = count_ * slot_bytes_[round]; if (nbytes != need) throw std::invalid_argument("dealer_cursor put_round size"); if (need != 0) std::memcpy(store_.data() + offsets_[round], bytes, need); for (std::size_t i = 0; i < count_; ++i) ready_[ready_index(round, i)] = true; } bool ready(std::uint16_t round, std::size_t index) const { if (round >= slot_bytes_.size() || index >= count_) return false; if (slot_bytes_[round] == 0) return true; return ready_[ready_index(round, index)]; } void at(std::uint16_t round, std::size_t index, std::uint8_t * out, std::size_t n) const { check(round, index, n); if (n == 0) return; if (!ready_[ready_index(round, index)]) throw std::logic_error("dealer_cursor not ready"); std::memcpy(out, ptr(round, index), n); } template T at(std::uint16_t round, std::size_t index) const { static_assert(std::is_trivially_copyable_v, "dealer_cursor::at requires a trivially copyable type"); if constexpr (std::is_same_v) { (void)round; (void)index; return empty_pad{}; } else { T out{}; at(round, index, reinterpret_cast(&out), sizeof(T)); return out; } } /// @brief Load each round from consecutive stream indexes (one tape per round). static dealer_cursor from_streams(stream_array & src, std::size_t count, std::vector slot_bytes, std::size_t first_stream = 0) { dealer_cursor out(count, std::move(slot_bytes)); for (std::uint16_t r = 0; r < out.rounds(); ++r) { const std::size_t need = count * out.slot_bytes(r); if (need == 0) continue; std::vector buf(need); src.read(first_stream + r, buf.data(), need); out.put_round(r, buf.data(), need); } return out; } private: void check(std::uint16_t round, std::size_t index, std::size_t n) const { if (round >= slot_bytes_.size() || index >= count_) throw std::out_of_range("dealer_cursor index"); if (n != slot_bytes_[round]) throw std::invalid_argument("dealer_cursor size"); } std::size_t ready_index(std::uint16_t round, std::size_t index) const noexcept { return static_cast(round) * count_ + index; } std::uint8_t * ptr(std::uint16_t round, std::size_t index) noexcept { return store_.data() + offsets_[round] + index * slot_bytes_[round]; } const std::uint8_t * ptr(std::uint16_t round, std::size_t index) const noexcept { return store_.data() + offsets_[round] + index * slot_bytes_[round]; } std::size_t count_ = 0; std::vector slot_bytes_; std::vector offsets_; std::vector store_; std::vector ready_; }; /// @brief Prefetch a full dealer table from `stream_array` tapes (see `from_streams`). class stream_dealer_cursor { public: stream_dealer_cursor(stream_array & src, std::size_t count, std::vector slot_bytes, std::size_t first_stream = 0) : inner_(dealer_cursor::from_streams(src, count, std::move(slot_bytes), first_stream)) { } std::size_t count() const noexcept { return inner_.count(); } std::size_t rounds() const noexcept { return inner_.rounds(); } std::size_t slot_bytes(std::uint16_t round) const { return inner_.slot_bytes(round); } bool ready(std::uint16_t round, std::size_t index) const { return inner_.ready(round, index); } void at(std::uint16_t round, std::size_t index, std::uint8_t * out, std::size_t n) const { inner_.at(round, index, out, n); } template T at(std::uint16_t round, std::size_t index) const { return inner_.at(round, index); } const dealer_cursor & cursor() const noexcept { return inner_; } dealer_cursor & cursor() noexcept { return inner_; } private: dealer_cursor inner_; }; /// @brief One-round table: `nbytes` must equal `count * slot_bytes[0]`. inline dealer_cursor dealer_cursor_from_stream(stream_array & a, std::size_t stream_index, std::size_t count, std::size_t slot_nbytes) { return dealer_cursor::from_streams(a, count, {slot_nbytes}, stream_index); } } // namespace net } // namespace dpf #endif // LIBDPF_INCLUDE_DPF_NET_DEALER_CURSOR_HPP__