/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Grug.cpp Date: 2022-01-03 File: Flusher.cpp Date: 2021-8-27 Author: Reece Note: Grug couples async telemetry, console flushing, and log dispatching to one lazy working thread w/ high performance thread primitives for when urgent telemetry events are raised. ***/ #include #include "Grug.hpp" #include #include #include #include #if defined(AURORA_IS_LINUX_DERIVED) void LinuxSuperSecretIOTick(); void LinuxSuperSecretFuckGlibc(); #endif namespace Aurora::Grug { static const auto kGrugSleepMs = 100; static const auto kGrugFlushMs = 500; static AuThreads::ThreadUnique_t gGrugsBigWorld; static AuThreadPrimitives::ConditionVariableUnique_t gCondVar; // slow logger work queue static AuThreadPrimitives::ConditionMutexUnique_t gMutex; // ^ that static AuThreadPrimitives::SemaphoreUnique_t gArrows; static void SlowStartupTasks() { Console::ConsoleFIO::FIOCleanup(); } // grug require only 1 strand static void GrugWorld() { #if defined(AURORA_IS_LINUX_DERIVED) LinuxSuperSecretFuckGlibc(); #endif // grug surive first night SlowStartupTasks(); AU_LOCK_GUARD(gMutex); Utility::RateLimiter limiter; limiter.SetNextStep(kGrugFlushMs); // grug has cave while (AuIsThreadRunning()) { // * -> grug wake up from 100ms (kGrugSleepMs) // grug smashy alarm gCondVar->WaitForSignal(kGrugFlushMs); // grug anoy if (gArrows && gArrows->TryLock()) { // grug yeet DequeueOneArrow(); } // grug pump all async log messages GrugFlushWrites(); // adhd grug wonder around the log pool every 500ms (rate limited to 100ms sleeps) if (limiter.CheckExchangePass()) { GrugFlushFlushs(); } // grug give up // grug yield for at least 100ms -> * if (gArrows) { // grug sleep for 100ms or until poked if (gArrows->Lock(kGrugSleepMs)) { DequeueOneArrow(); } } else { // grug sleep 100ms AuThreading::Sleep(kGrugSleepMs); } #if defined(AURORA_IS_LINUX_DERIVED) ::LinuxSuperSecretIOTick(); #endif } } static void DestroyFlushThread() { gGrugsBigWorld.reset(); } static void InitFlushThread() { // Startup a runner thread that will take care of all the stress of triggering IO every so often on a remote thread gGrugsBigWorld = AuThreads::ThreadUnique(AuThreads::ThreadInfo( AuMakeShared(AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind(GrugWorld)), AuThreads::IThreadVectorsFunctional::OnExit_t{}), "Cave Grug" )); if (!gGrugsBigWorld) { return; } // he's a lazy bastard (ira type insult) gGrugsBigWorld->SetThrottle(AuThreads::EThreadThrottle::eEfficient); gGrugsBigWorld->SetPriority(AuThreads::EThreadPriority::ePrioLow); gGrugsBigWorld->Run(); } void InitGrug() { gMutex = AuThreadPrimitives::ConditionMutexUnique(); SysAssert(gMutex, "Couldn't allocate a unique condition variable mutex for grug"); auto shared = AuUnsafeRaiiToShared(gMutex); gCondVar = AuThreadPrimitives::ConditionVariableUnique(shared); SysAssert(gCondVar, "Couldn't allocate a unique condition variable for grug"); gArrows = AuThreadPrimitives::SemaphoreUnique(); SysAssert(gArrows, "Couldn't allocate an arrow counter for grug"); InitFlushThread(); } void DeinitGrug() { if (bool(gGrugsBigWorld) && gGrugsBigWorld.get() != AuThreads::GetThread()) { gGrugsBigWorld.reset(); } GrugFlushWrites(); GrugFlushFlushs(); gMutex.reset(); gCondVar.reset(); gArrows.reset(); } void NotifyGrugOfTelemetry() { if (gArrows) { gArrows->Unlock(1); } DrachenlordScreech(); } void DrachenlordScreech() { if (!gCondVar) { return; } gCondVar->Signal(); } bool IsGrug() { if (!gGrugsBigWorld) return false; return AuThreads::GetThread() == gGrugsBigWorld.get(); } void NotifyGrugOfLogs() { if (Grug::IsGrug()) { Grug::GrugFlushWrites(); } else { Grug::DrachenlordScreech(); } } void GrugFlushWrites() { Logging::ForceFlushLoggers(); } void GrugFlushFlushs() { Logging::ForceFlushFlush(); Console::ForceFlush(); } }