54#define VLINK_TERM_STDOUT_FILENO _fileno(stdout)
55#define VLINK_TERM_WRITE _write
56#define VLINK_TERM_ISATTY _isatty
60#define VLINK_TERM_STDOUT_FILENO STDOUT_FILENO
61#define VLINK_TERM_WRITE ::write
62#define VLINK_TERM_ISATTY ::isatty
138 void init() noexcept;
148 [[nodiscard]]
bool is_tty() const noexcept;
327 void flush_unlocked() noexcept;
329 void write_to_buffer(const
char* data,
size_t len) noexcept;
331 template <typename T>
334 template <typename T>
337 std::atomic_bool initialized_{
false};
338 std::atomic_bool is_tty_{
false};
340 mutable std::mutex mutex_;
341 std::vector<char> buffer_;
342 size_t write_pos_{0};
356 std::lock_guard lock(stream.mutex_);
357 stream.write_to_buffer(
"\n", 1);
358 stream.flush_unlocked();
363 std::lock_guard lock(stream.mutex_);
364 stream.flush_unlocked();
371 std::lock_guard lock(mutex_);
376 bool expected =
false;
378 if (!initialized_.compare_exchange_strong(expected,
true)) {
382 std::lock_guard lock(mutex_);
387 HANDLE h_out = GetStdHandle(STD_OUTPUT_HANDLE);
389 if (h_out != INVALID_HANDLE_VALUE) {
392 if (GetConsoleMode(h_out, &dw_mode)) {
394 SetConsoleMode(h_out, dw_mode);
407 std::lock_guard lock(mutex_);
412 std::lock_guard lock(mutex_);
413 write_to_buffer(data, len);
418 std::lock_guard lock(mutex_);
419 write_to_buffer(&c, 1);
424 if (str ==
nullptr) {
428 std::lock_guard lock(mutex_);
429 write_to_buffer(str, std::strlen(str));
434 std::lock_guard lock(mutex_);
435 write_to_buffer(str.data(), str.size());
440 std::lock_guard lock(mutex_);
441 write_to_buffer(str.data(), str.size());
472 std::lock_guard lock(mutex_);
476 int len = std::snprintf(buf,
sizeof(buf),
"%p", ptr);
479 write_to_buffer(buf,
static_cast<size_t>(len));
490 std::lock_guard lock(mutex_);
492 write_to_buffer(
"\n", 1);
498inline void TerminalStream::flush_unlocked() noexcept {
499 if (write_pos_ == 0) {
503 const char* data = buffer_.data();
504 size_t remaining = write_pos_;
506 while (remaining > 0) {
507 auto written =
VLINK_TERM_WRITE(fd_, data,
static_cast<unsigned int>(remaining));
514 remaining -=
static_cast<size_t>(written);
520 if (is_tty_.load(std::memory_order_acquire)) {
526inline void TerminalStream::write_to_buffer(
const char* data,
size_t len)
noexcept {
531 if (len >= buffer_.size()) {
534 size_t remaining = len;
535 while (remaining > 0) {
536 auto written =
VLINK_TERM_WRITE(fd_, data,
static_cast<unsigned int>(remaining));
543 remaining -=
static_cast<size_t>(written);
549 if (write_pos_ + len > buffer_.size()) {
553 std::memcpy(buffer_.data() + write_pos_, data, len);
558inline TerminalStream& TerminalStream::write_integer(T value)
noexcept {
559 std::lock_guard lock(mutex_);
562 char* ptr = buf +
sizeof(buf);
563 bool negative =
false;
565 if constexpr (std::is_signed_v<T>) {
569 if (value == std::numeric_limits<T>::min()) {
570 int len = std::snprintf(buf,
sizeof(buf),
"%lld",
571 static_cast<long long>(value));
574 write_to_buffer(buf,
static_cast<size_t>(len));
588 *--ptr =
'0' +
static_cast<char>(value % 10);
597 write_to_buffer(ptr,
static_cast<size_t>(buf +
sizeof(buf) - ptr));
603inline TerminalStream& TerminalStream::write_float(T value)
noexcept {
604 std::lock_guard lock(mutex_);
610 if constexpr (std::is_same_v<T, long double>) {
611 len = std::snprintf(buf,
sizeof(buf),
"%Lg", value);
613 len = std::snprintf(buf,
sizeof(buf),
"%g",
static_cast<double>(value));
617 write_to_buffer(buf,
static_cast<size_t>(len));
625#ifndef VLINK_TERM_OUT
626#define VLINK_TERM_OUT vlink::TerminalStream::get()
Singleton buffered stdout writer with thread-safety and TTY detection.
Definition terminal_stream.h:74
TerminalStream & operator<<(char c) noexcept
Writes a single character to the buffer.
Definition terminal_stream.h:417
bool is_tty() const noexcept
Returns true if stdout is connected to a terminal (TTY).
Definition terminal_stream.h:402
TerminalStream & write_raw(const char *data, size_t len) noexcept
Writes a raw byte array of len bytes to the buffer.
Definition terminal_stream.h:411
bool is_initialized() const noexcept
Returns true if init() has been called.
Definition terminal_stream.h:404
TerminalStream &(*)(TerminalStream &) ManipType
Type alias for stream manipulator functions.
Definition terminal_stream.h:84
static constexpr size_t kDefaultBufferSize
Default internal buffer size (1 MiB).
Definition terminal_stream.h:79
static TerminalStream & get() noexcept
Returns the process-global TerminalStream singleton.
Definition terminal_stream.h:350
void init() noexcept
Initialises the stream: detects the file descriptor and TTY status.
Definition terminal_stream.h:375
void flush()
Flushes all buffered data to stdout immediately.
Definition terminal_stream.h:406
static TerminalStream & endl(TerminalStream &stream) noexcept
Stream manipulator that appends a newline and flushes the buffer.
Definition terminal_stream.h:355
static TerminalStream & flush_manip(TerminalStream &stream) noexcept
Stream manipulator that flushes the buffer without appending a newline.
Definition terminal_stream.h:362
~TerminalStream() noexcept
Destructor – flushes any remaining buffered data to stdout.
Definition terminal_stream.h:370
TerminalStream() noexcept
Constructs the stream with a kDefaultBufferSize internal buffer.
Definition terminal_stream.h:368
#define VLINK_TERM_WRITE
Definition terminal_stream.h:61
#define VLINK_TERM_STDOUT_FILENO
Definition terminal_stream.h:60
#define VLINK_TERM_ISATTY
Definition terminal_stream.h:62