microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
ring_buffer_sink.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
9#include "../microfmt.hpp"
10#include <atomic>
11#include <cstddef>
12#include <cstdint>
13#include <string_view>
14
15namespace microfmt {
16
17// ============================================================================
18// Zero-Allocation Ring Buffer Sink
19// ============================================================================
20
21template <size_t Capacity> class ring_buffer_sink {
22 static_assert(Capacity > 0, "Capacity must be greater than 0");
23 static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of 2 for fast bitmask wrapping");
24
25public:
26 constexpr ring_buffer_sink() noexcept = default;
27
28 // Non-copyable, non-movable to ensure safe raw pointer context binding
30 ring_buffer_sink &operator=(const ring_buffer_sink &) = delete;
31
32 // ==========================================================================
33 // microfmt::sink Bridge
34 // ==========================================================================
35
36 [[nodiscard]] sink as_sink() & noexcept RELOCO_LIFETIMEBOUND {
37 return sink{this,
38 [](void *ctx, microfmt::string_view sv) noexcept { static_cast<ring_buffer_sink *>(ctx)->write(sv); }};
39 }
40
41 // ==========================================================================
42 // Writing (Overwrite on overflow)
43 // ==========================================================================
44
45 void put(char c) noexcept {
46 const size_t pos = head_.fetch_add(1, std::memory_order_relaxed);
47 buffer_[pos & Mask] = c;
48 count_.fetch_add(1, std::memory_order_relaxed);
49 }
50
51 void write(microfmt::string_view sv) noexcept {
52 for (char c : sv) {
53 put(c);
54 }
55 }
56
57 // ==========================================================================
58 // Status & Sizing
59 // ==========================================================================
60
61 [[nodiscard]] size_t size() const noexcept {
62 const size_t c = count_.load(std::memory_order_relaxed);
63 return c > Capacity ? Capacity : c;
64 }
65
66 [[nodiscard]] constexpr size_t capacity() const noexcept { return Capacity; }
67 [[nodiscard]] bool empty() const noexcept { return size() == 0; }
68 [[nodiscard]] bool full() const noexcept { return count_.load(std::memory_order_relaxed) >= Capacity; }
69
70 void reset() noexcept {
71 head_.store(0, std::memory_order_relaxed);
72 count_.store(0, std::memory_order_relaxed);
73 }
74
75 // ==========================================================================
76 // Crash Dump / Post-Mortem Slices
77 // ==========================================================================
78
79 struct RELOCO_POINTER dump_slices {
80 microfmt::string_view first;
81 microfmt::string_view second; // Empty if buffer hasn't wrapped
82 };
83
84 // Inspects data chronologically from oldest to newest without copying
85 [[nodiscard]] dump_slices view() const noexcept RELOCO_LIFETIMEBOUND {
86 const size_t total_written = count_.load(std::memory_order_relaxed);
87 if (total_written == 0) {
88 return {};
89 }
90
91 if (total_written <= Capacity) {
92 // Linear layout (not yet wrapped)
93 return {microfmt::string_view(buffer_.data(), total_written), {}};
94 }
95
96 // Wrapped: oldest data starts at (head_ & Mask)
97 const size_t tail = head_.load(std::memory_order_relaxed) & Mask;
98 const size_t first_len = Capacity - tail;
99 const size_t second_len = tail;
100
101 RELOCO_BEGIN_UNSAFE_BUFFER_USAGE;
102
103 return {microfmt::string_view(buffer_.data() + tail, first_len), microfmt::string_view(buffer_.data(), second_len)};
104
105 RELOCO_END_UNSAFE_BUFFER_USAGE;
106 }
107
108 // Flushes full ring buffer content chronologically to an external sink
109 void dump_to(const sink &target) const noexcept {
110 const auto slices = view();
111 if (!slices.first.empty()) {
112 target.write(slices.first);
113 }
114 if (!slices.second.empty()) {
115 target.write(slices.second);
116 }
117 }
118
119private:
120 static constexpr size_t Mask = Capacity - 1;
121
122 alignas(void *) microfmt::array<char, Capacity> buffer_{};
123 std::atomic<size_t> head_{0};
124 std::atomic<size_t> count_{0};
125};
126
127} // namespace microfmt
Definition ring_buffer_sink.hpp:21
void dump_to(const sink &target) const noexcept
Definition ring_buffer_sink.hpp:109
bool full() const noexcept
Definition ring_buffer_sink.hpp:68
void write(microfmt::string_view sv) noexcept
Definition ring_buffer_sink.hpp:51
size_t size() const noexcept
Definition ring_buffer_sink.hpp:61
void put(char c) noexcept
Definition ring_buffer_sink.hpp:45
bool empty() const noexcept
Definition ring_buffer_sink.hpp:67
constexpr size_t capacity() const noexcept
Definition ring_buffer_sink.hpp:66
dump_slices view() const noexcept RELOCO_LIFETIMEBOUND
Definition ring_buffer_sink.hpp:85
sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Definition ring_buffer_sink.hpp:36
constexpr ring_buffer_sink() noexcept=default
void reset() noexcept
Definition ring_buffer_sink.hpp:70
Definition assert.hpp:19
reloco::sink sink
Type-erased, zero-allocation output sink.
Definition microfmt.hpp:46
Definition ring_buffer_sink.hpp:79
microfmt::string_view first
Definition ring_buffer_sink.hpp:80
microfmt::string_view second
Definition ring_buffer_sink.hpp:81