// Shared-memory log channel: the injected hook (coop_hook.dll) streams its log // lines to the host (coop_host.exe), which shows them in a Log window. Separate // mapping from the input/status SharedBlock, named coop_log_. // // Lossy multi-producer / single-consumer ring: the hook logs from several threads // (worker, audio render thread, window thread), so producers claim a slot with an // atomic fetch_add and publish each record with a release store of its sequence; // the host consumer reads in order and tolerates losing the oldest lines if it // ever falls a whole ring behind (fine for diagnostics). POD + version-locked. #pragma once #include #include #include #include namespace coop { // 'CLOG' little-endian. inline constexpr std::uint32_t kLogRingMagic = 0x474F4C43u; inline constexpr std::uint32_t kLogRingVersion = 1; // Per-pid mapping name, mirroring the other channels: coop_log_. inline constexpr wchar_t kLogRingPrefix[] = L"Local\\coop_log_"; inline constexpr std::uint32_t kLogMsgLen = 192; // chars per line (incl. NUL) inline constexpr std::uint32_t kLogCapacity = 1024; // ring records // Severity of a log line; drives the host Log window's colour. Stored in // LogRecord::level. Info is 0 so existing/zero-filled records read as Info. enum LogLevel : std::uint32_t { LogLevel_Info = 0, LogLevel_Warn = 1, LogLevel_Error = 2, }; struct LogRecord { std::atomic seq; // 0 = empty; else (global index + 1) once written std::uint32_t pid; std::uint32_t level; // LogLevel std::uint64_t millis; // producer timestamp (GetTickCount64) char text[kLogMsgLen]; }; struct LogRing { std::uint32_t magic; std::uint32_t version; std::uint32_t capacity; // number of records std::uint32_t msg_len; // kLogMsgLen (sanity) std::atomic write_index; // total records ever claimed (free-running) std::uint8_t reserved[32]; // LogRecord records[capacity] follows immediately. }; static_assert(std::atomic::is_always_lock_free, "log ring needs a lock-free 64-bit atomic for cross-process use"); inline constexpr std::size_t log_ring_total_size(std::uint32_t capacity) { return sizeof(LogRing) + static_cast(capacity) * sizeof(LogRecord); } inline LogRecord* log_ring_records(LogRing* r) { return reinterpret_cast(reinterpret_cast(r) + sizeof(LogRing)); } // Host: stamp a freshly created (zero-filled) mapping. Records start empty (seq 0). inline void log_ring_init(LogRing& r, std::uint32_t capacity) { r.capacity = capacity; r.msg_len = kLogMsgLen; r.write_index.store(0, std::memory_order_relaxed); std::memset(r.reserved, 0, sizeof(r.reserved)); r.version = kLogRingVersion; r.magic = kLogRingMagic; // last } inline bool log_ring_valid(const LogRing& r) { return r.magic == kLogRingMagic && r.version == kLogRingVersion && r.capacity != 0 && r.msg_len == kLogMsgLen; } // Producer (hook): append a line at severity `level` (LogLevel). Multi-producer safe. inline void log_ring_push(LogRing& r, std::uint32_t pid, std::uint32_t level, std::uint64_t millis, const char* text) { const std::uint64_t idx = r.write_index.fetch_add(1, std::memory_order_acq_rel); LogRecord& rec = log_ring_records(&r)[idx % r.capacity]; rec.pid = pid; rec.level = level; rec.millis = millis; std::strncpy(rec.text, text, kLogMsgLen - 1); rec.text[kLogMsgLen - 1] = '\0'; rec.seq.store(idx + 1, std::memory_order_release); // publish: record is ready } // Consumer (host): emit each new record since `cursor` (advanced in place). Skips // records lost to ring wrap; stops at an in-flight record and retries next call. template inline void log_ring_drain(LogRing& r, std::uint64_t& cursor, F&& emit) { const std::uint64_t w = r.write_index.load(std::memory_order_acquire); if (w <= cursor) { return; } const std::uint64_t lo = (w > r.capacity) ? (w - r.capacity) : 0; std::uint64_t i = cursor < lo ? lo : cursor; // skip records already overwritten LogRecord* recs = log_ring_records(&r); for (; i < w; ++i) { LogRecord& rec = recs[i % r.capacity]; const std::uint64_t s = rec.seq.load(std::memory_order_acquire); if (s == i + 1) { emit(rec); // ready } else if (s <= i) { break; // slot not written for this generation yet (in-flight); retry later } // s > i + 1: overwritten before we read it; skip (lost) } cursor = i; } inline std::wstring log_ring_name(unsigned long target_pid) { return std::wstring(kLogRingPrefix) + std::to_wstring(target_pid); } } // namespace coop