VLink  2.0.0
A high-performance communication middleware
terminal_stream.h
浏览该文件的文档.
1 /*
2  * Copyright (C) 2026 by Thun Lu. All rights reserved.
3  * Author: Thun Lu <thun.lu@zohomail.cn>
4  * Repo: https://github.com/thun-res/vlink
5  * _ __ __ _ __
6  * | | / / / / (_) ____ / /__
7  * | | / / / / / / / __ \ / //_/
8  * | |/ / / /___ / / / / / / / ,<
9  * |___/ /_____/ /_/ /_/ /_/ /_/|_|
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 
24 /**
25  * @file terminal_stream.h
26  * @brief Lock-protected buffered stdout writer with TTY detection and ANSI escape support.
27  *
28  * @details
29  * @c TerminalStream is a process singleton that funnels formatted output straight into a
30  * one-megabyte internal buffer and emits it through raw @c write() / @c _write() syscalls.
31  * It exists because @c std::cout's locale conversion, hidden allocations, and per-character
32  * mutex traffic dominate the cost of hot-path logging in the VLink runtime.
33  *
34  * @par Stream modes
35  *
36  * | Mode | Trigger | Behaviour |
37  * | ------------------ | --------------------------------------------- | -------------------------------------------- |
38  * | Buffered append | write fits in remaining buffer space | append to buffer, no syscall |
39  * | Auto flush | buffer fills or @c endl is used | drain buffer with @c write() / @c _write() |
40  * | Direct passthrough | single write >= @c kDefaultBufferSize | flush buffer first, then write directly |
41  * | Manual flush | @c flush() or @c flush_manip invoked | drain buffer (and @c tcdrain on POSIX TTYs) |
42  *
43  * @par ANSI colour cheat-sheet (foreground)
44  *
45  * | Sequence | Effect | | Sequence | Effect |
46  * | --------------- | ---------------------- | - | --------------- | ---------------------------- |
47  * | @c "\x1b[0m" | reset all attributes | | @c "\x1b[37m" | white |
48  * | @c "\x1b[1m" | bold / bright | | @c "\x1b[90m" | bright black (grey) |
49  * | @c "\x1b[30m" | black | | @c "\x1b[91m" | bright red |
50  * | @c "\x1b[31m" | red | | @c "\x1b[92m" | bright green |
51  * | @c "\x1b[32m" | green | | @c "\x1b[93m" | bright yellow |
52  * | @c "\x1b[33m" | yellow | | @c "\x1b[94m" | bright blue |
53  * | @c "\x1b[34m" | blue | | @c "\x1b[95m" | bright magenta |
54  * | @c "\x1b[35m" | magenta | | @c "\x1b[96m" | bright cyan |
55  * | @c "\x1b[36m" | cyan | | @c "\x1b[97m" | bright white |
56  *
57  * Background colours follow the same pattern shifted by 10 (@c "\x1b[40m" -> @c "\x1b[47m").
58  * Always gate colour emission behind @c is_tty() so redirected output stays clean.
59  *
60  * @par Thread safety
61  * Every public method takes the same internal mutex, so a single @c operator<< call cannot
62  * interleave with another thread's call. Chained writes (@c ts << a << b) acquire the mutex
63  * per token; wrap the chain in an external lock if cross-token atomicity is required.
64  *
65  * @par Example
66  * @code
67  * auto& ts = vlink::TerminalStream::get();
68  * ts.init();
69  *
70  * if (ts.is_tty()) {
71  * ts << "\x1b[32m[OK]\x1b[0m ";
72  * } else {
73  * ts << "[OK] ";
74  * }
75  * ts << "boot in " << 12.3 << " ms" << vlink::TerminalStream::endl;
76  * @endcode
77  */
78 
79 #pragma once
80 
81 #include <atomic>
82 #include <cstddef>
83 #include <iosfwd>
84 #include <mutex>
85 #include <string>
86 #include <string_view>
87 #include <vector>
88 
89 #include "../base/macros.h"
90 
91 namespace vlink {
92 
93 /**
94  * @class TerminalStream
95  * @brief Process-wide buffered stdout writer with TTY detection, ANSI support, and a 1 MiB ring.
96  *
97  * @details
98  * Construction is hidden behind @c get(); copy and move are disabled. Internally the class
99  * keeps the buffer, write cursor, file descriptor, and an atomic TTY flag set by @c init().
100  */
102  public:
103  /**
104  * @brief Default capacity of the internal buffer (1 MiB).
105  *
106  * @details
107  * Writes smaller than this accumulate in the buffer until @c flush() / a flushing manipulator
108  * fires or the buffer fills. Writes at or above this threshold bypass the buffer.
109  */
110  static constexpr size_t kDefaultBufferSize{1024 * 1024 * 1};
111 
112  /**
113  * @brief Function-pointer type accepted by @c operator<<(ManipType) for custom manipulators.
114  */
116 
117  /**
118  * @brief Returns the process-wide singleton, building it on the first call.
119  *
120  * @return Reference to the singleton instance.
121  */
122  static TerminalStream& get() noexcept;
123 
124  /**
125  * @brief Manipulator that appends a newline byte and flushes the buffer.
126  *
127  * @param stream Stream to write to.
128  * @return Reference to @p stream for chaining.
129  */
130  static TerminalStream& endl(TerminalStream& stream) noexcept;
131 
132  /**
133  * @brief Manipulator that flushes the buffer without appending any byte.
134  *
135  * @param stream Stream to flush.
136  * @return Reference to @p stream for chaining.
137  */
138  static TerminalStream& flush_manip(TerminalStream& stream) noexcept;
139 
140  /**
141  * @brief Constructs the stream with a @c kDefaultBufferSize buffer and platform stdout fd.
142  *
143  * @details
144  * No syscalls run at construction time; @c init() detects TTY status and enables Windows
145  * virtual-terminal processing when invoked separately.
146  */
147  TerminalStream() noexcept;
148 
149  /**
150  * @brief Flushes any pending bytes and releases internal storage.
151  */
152  ~TerminalStream() noexcept;
153 
154  TerminalStream(const TerminalStream&) noexcept = delete;
155 
156  TerminalStream& operator=(const TerminalStream&) noexcept = delete;
157 
158  TerminalStream(TerminalStream&&) noexcept = delete;
159 
160  TerminalStream& operator=(TerminalStream&&) noexcept = delete;
161 
162  /**
163  * @brief Detects the platform stdout descriptor, TTY status, and enables Windows ANSI handling.
164  *
165  * @details
166  * Idempotent and thread-safe; only the first call performs work. Call once early in
167  * @c main() before relying on @c is_tty() or emitting ANSI escape sequences on Windows.
168  */
169  void init() noexcept;
170 
171  /**
172  * @brief Reports whether stdout was a terminal when @c init() ran.
173  *
174  * @return @c true when @c init() saw a TTY; @c false otherwise.
175  */
176  [[nodiscard]] bool is_tty() const noexcept;
177 
178  /**
179  * @brief Reports whether @c init() has been invoked.
180  *
181  * @return @c true once @c init() completes for the first time.
182  */
183  [[nodiscard]] bool is_initialized() const noexcept;
184 
185  /**
186  * @brief Drains every buffered byte to stdout immediately.
187  *
188  * @details
189  * On POSIX TTYs the call additionally invokes @c tcdrain() so the kernel queue is empty
190  * before returning, providing a strong "user can see it now" guarantee.
191  */
192  void flush();
193 
194  /**
195  * @brief Appends a raw byte array verbatim to the buffer.
196  *
197  * @param data Pointer to @p len bytes; not retained beyond the call.
198  * @param len Number of bytes to append; @c 0 is a no-op.
199  * @return Reference to @c *this for chaining.
200  */
201  TerminalStream& write_raw(const char* data, size_t len) noexcept;
202 
203  /**
204  * @brief Appends a single character to the buffer.
205  *
206  * @param c Character to append.
207  * @return Reference to @c *this for chaining.
208  */
209  TerminalStream& operator<<(char c) noexcept;
210 
211  /**
212  * @brief Appends the bytes of a NUL-terminated C string (NUL excluded).
213  *
214  * @param str Source string or @c nullptr (no-op).
215  * @return Reference to @c *this for chaining.
216  */
217  TerminalStream& operator<<(const char* str) noexcept;
218 
219  /**
220  * @brief Appends the bytes of @p str verbatim, including embedded NULs.
221  *
222  * @param str Source string.
223  * @return Reference to @c *this for chaining.
224  */
225  TerminalStream& operator<<(const std::string& str) noexcept;
226 
227  /**
228  * @brief Appends the bytes of @p str verbatim, including embedded NULs.
229  *
230  * @param str Source view.
231  * @return Reference to @c *this for chaining.
232  */
233  TerminalStream& operator<<(std::string_view str) noexcept;
234 
235  /**
236  * @brief Appends the literal @c "true" or @c "false".
237  *
238  * @param value Boolean to print.
239  * @return Reference to @c *this for chaining.
240  */
241  TerminalStream& operator<<(bool value) noexcept;
242 
243  /**
244  * @name Integer overloads
245  * @brief Format a signed or unsigned integer as decimal and append it.
246  *
247  * @param value Integer to print.
248  * @return Reference to @c *this for chaining.
249  * @{
250  */
251  TerminalStream& operator<<(short value) noexcept; // NOLINT(runtime/int,google-runtime-int)
252  TerminalStream& operator<<(unsigned short value) noexcept; // NOLINT(runtime/int,google-runtime-int)
253  TerminalStream& operator<<(int value) noexcept;
254  TerminalStream& operator<<(unsigned int value) noexcept;
255  TerminalStream& operator<<(long value) noexcept; // NOLINT(runtime/int,google-runtime-int)
256  TerminalStream& operator<<(unsigned long value) noexcept; // NOLINT(runtime/int,google-runtime-int)
257  TerminalStream& operator<<(long long value) noexcept; // NOLINT(runtime/int,google-runtime-int)
258  TerminalStream& operator<<(unsigned long long value) noexcept; // NOLINT(runtime/int,google-runtime-int)
259  /** @} */
260 
261  /**
262  * @brief Formats a @c float through @c snprintf with the @c "%g" format.
263  *
264  * @param value Float to print.
265  * @return Reference to @c *this for chaining.
266  */
267  TerminalStream& operator<<(float value) noexcept;
268 
269  /**
270  * @brief Formats a @c double through @c snprintf with the @c "%g" format.
271  *
272  * @param value Double to print.
273  * @return Reference to @c *this for chaining.
274  */
275  TerminalStream& operator<<(double value) noexcept;
276 
277  /**
278  * @brief Formats a @c long @c double through @c snprintf with the @c "%Lg" format.
279  *
280  * @param value Long double to print.
281  * @return Reference to @c *this for chaining.
282  */
283  TerminalStream& operator<<(long double value) noexcept; // NOLINT(google-runtime-float)
284 
285  /**
286  * @brief Formats a pointer using @c snprintf with the @c "%p" format.
287  *
288  * @param ptr Pointer to format.
289  * @return Reference to @c *this for chaining.
290  */
291  TerminalStream& operator<<(const void* ptr) noexcept;
292 
293  /**
294  * @brief Applies a custom or built-in @c ManipType manipulator to the stream.
295  *
296  * @param manip Function pointer invoked with @c *this.
297  * @return Whatever @p manip returns, normally a reference to @c *this.
298  */
299  TerminalStream& operator<<(ManipType manip) noexcept;
300 
301  /**
302  * @brief Accepts @c std::endl and similar standard manipulators.
303  *
304  * @details
305  * Standard manipulators expect a @c std::ostream; this overload ignores the function
306  * pointer and emits a newline followed by a flush, matching @c TerminalStream::endl.
307  *
308  * @return Reference to @c *this for chaining.
309  */
310  TerminalStream& operator<<(std::ostream& (*)(std::ostream&)) noexcept;
311 
312  private:
313  static int default_stdout_fd() noexcept;
314 
315  void flush_unlocked() noexcept;
316 
317  void write_to_buffer(const char* data, size_t len) noexcept;
318 
319  TerminalStream& write_signed(long long value) noexcept; // NOLINT(runtime/int,google-runtime-int)
320  TerminalStream& write_unsigned(unsigned long long value) noexcept; // NOLINT(runtime/int,google-runtime-int)
321 
322  TerminalStream& write_double(double value) noexcept;
323  TerminalStream& write_long_double(long double value) noexcept; // NOLINT(google-runtime-float)
324 
325  std::atomic_bool initialized_{false};
326  std::atomic_bool is_tty_{false};
327 
328  mutable std::mutex mutex_;
329  std::vector<char> buffer_;
330  size_t write_pos_{0};
331  int fd_{default_stdout_fd()};
332 };
333 
334 } // namespace vlink
335 
336 #ifndef VLINK_TERM_OUT
337 #define VLINK_TERM_OUT vlink::TerminalStream::get()
338 #endif
#define VLINK_EXPORT
Definition: macros.h:81