mirror of
https://github.com/morgan9e/VolumeControl
synced 2026-04-14 00:04:05 +09:00
Initial commit
This commit is contained in:
227
app/VCPlayThroughRTLogger.h
Normal file
227
app/VCPlayThroughRTLogger.h
Normal file
@@ -0,0 +1,227 @@
|
||||
// This file is part of Background Music.
|
||||
//
|
||||
// Background Music is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 2 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// Background Music is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//
|
||||
// VCPlayThroughRTLogger.h
|
||||
// VCApp
|
||||
//
|
||||
// Copyright © 2020 Kyle Neideck
|
||||
//
|
||||
// A real-time safe logger for VCPlayThrough. The messages are logged asynchronously by a
|
||||
// non-realtime thread.
|
||||
//
|
||||
// For the sake of simplicity, this class is very closely coupled with VCPlayThrough and its
|
||||
// methods make assumptions about where they will be called. Also, if the same logging method is
|
||||
// called multiple times before the logging thread next checks for messages, it will only log the
|
||||
// message for one of those calls and ignore the others.
|
||||
//
|
||||
// This class's methods are real-time safe in that they return in a bounded amount of time and we
|
||||
// think they're probably fast enough that the callers won't miss their deadlines, but we don't try
|
||||
// to guarantee it. Some of them should only be called in unusual cases where it's worth increasing
|
||||
// the risk of a thread missing its deadline.
|
||||
//
|
||||
|
||||
#ifndef VCApp__VCPlayThroughRTLogger
|
||||
#define VCApp__VCPlayThroughRTLogger
|
||||
|
||||
// PublicUtility Includes
|
||||
#include "CARingBuffer.h"
|
||||
|
||||
// STL Includes
|
||||
#include <thread>
|
||||
|
||||
// System Includes
|
||||
#include <mach/error.h>
|
||||
#include <mach/semaphore.h>
|
||||
|
||||
|
||||
#pragma clang assume_nonnull begin
|
||||
|
||||
class VCPlayThroughRTLogger
|
||||
{
|
||||
|
||||
#pragma mark Construction/Destruction
|
||||
|
||||
public:
|
||||
VCPlayThroughRTLogger();
|
||||
~VCPlayThroughRTLogger();
|
||||
VCPlayThroughRTLogger(const VCPlayThroughRTLogger&) = delete;
|
||||
VCPlayThroughRTLogger& operator=(
|
||||
const VCPlayThroughRTLogger&) = delete;
|
||||
private:
|
||||
static semaphore_t CreateSemaphore();
|
||||
|
||||
#pragma mark Log Messages
|
||||
|
||||
public:
|
||||
/*! For VCPlayThrough::ReleaseThreadsWaitingForOutputToStart. */
|
||||
void LogReleasingWaitingThreads();
|
||||
/*! For VCPlayThrough::ReleaseThreadsWaitingForOutputToStart. */
|
||||
void LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError);
|
||||
|
||||
/*! For VCPlayThrough::OutputDeviceIOProc. Not thread-safe. */
|
||||
void LogIfDroppedFrames(Float64 inFirstInputSampleTime,
|
||||
Float64 inLastInputSampleTime);
|
||||
/*! For VCPlayThrough::OutputDeviceIOProc. Not thread-safe. */
|
||||
void LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime,
|
||||
CARingBuffer::SampleTime inReadHeadSampleTime,
|
||||
Float64 inInToOutSampleOffset);
|
||||
|
||||
/*! For VCPlayThrough::UpdateIOProcState. Not thread-safe. */
|
||||
void LogExceptionStoppingIOProc(const char* inCallerName)
|
||||
{
|
||||
LogExceptionStoppingIOProc(inCallerName, noErr, false);
|
||||
}
|
||||
/*! For VCPlayThrough::UpdateIOProcState. Not thread-safe. */
|
||||
void LogExceptionStoppingIOProc(const char* inCallerName, OSStatus inError)
|
||||
{
|
||||
LogExceptionStoppingIOProc(inCallerName, inError, true);
|
||||
}
|
||||
|
||||
private:
|
||||
void LogExceptionStoppingIOProc(const char* inCallerName,
|
||||
OSStatus inError,
|
||||
bool inErrorKnown);
|
||||
|
||||
public:
|
||||
/*! For VCPlayThrough::UpdateIOProcState. Not thread-safe. */
|
||||
void LogUnexpectedIOStateAfterStopping(const char* inCallerName,
|
||||
int inIOState);
|
||||
/*! For VCPlayThrough::InputDeviceIOProc and VCPlayThrough::OutputDeviceIOProc. */
|
||||
void LogRingBufferUnavailable(const char* inCallerName, bool inGotLock);
|
||||
/*! For VCPlayThrough::OutputDeviceIOProc. */
|
||||
void LogIfRingBufferError_Fetch(CARingBufferError inError)
|
||||
{
|
||||
LogIfRingBufferError(inError, mRingBufferFetchError);
|
||||
}
|
||||
/*! For VCPlayThrough::InputDeviceIOProc. */
|
||||
void LogIfRingBufferError_Store(CARingBufferError inError)
|
||||
{
|
||||
LogIfRingBufferError(inError, mRingBufferStoreError);
|
||||
}
|
||||
|
||||
private:
|
||||
void LogIfRingBufferError(CARingBufferError inError,
|
||||
std::atomic<CARingBufferError>& outError);
|
||||
|
||||
template <typename T, typename F>
|
||||
void LogAsync(T& inMessageData, F&& inStoreMessageData);
|
||||
|
||||
// Wrapper methods used to mock out the logging for unit tests.
|
||||
void LogSync_Warning(const char* inFormat, ...) __printflike(2, 3);
|
||||
void LogSync_Error(const char* inFormat, ...) __printflike(2, 3);
|
||||
|
||||
#pragma mark Logging Thread
|
||||
|
||||
private:
|
||||
void WakeLoggingThread();
|
||||
|
||||
void LogMessages();
|
||||
void LogSync_ReleasingWaitingThreads();
|
||||
void LogSync_ReleaseWaitingThreadsSignalError();
|
||||
void LogSync_DroppedFrames();
|
||||
void LogSync_NoSamplesReady();
|
||||
void LogSync_ExceptionStoppingIOProc();
|
||||
void LogSync_UnexpectedIOStateAfterStopping();
|
||||
void LogSync_RingBufferUnavailable();
|
||||
void LogSync_RingBufferError(
|
||||
std::atomic<CARingBufferError>& ioRingBufferError,
|
||||
const char* inMethodName);
|
||||
|
||||
// The entry point of the logging thread (mLoggingThread).
|
||||
static void* __nullable LoggingThreadEntry(VCPlayThroughRTLogger* inRefCon);
|
||||
|
||||
#if VC_UnitTest
|
||||
|
||||
#pragma mark Test Helpers
|
||||
|
||||
public:
|
||||
/*!
|
||||
* @return True if the logger thread finished logging the requested messages. False if it still
|
||||
* had messages to log after 5 seconds.
|
||||
*/
|
||||
bool WaitUntilLoggerThreadIdle();
|
||||
|
||||
#endif /* VC_UnitTest */
|
||||
|
||||
private:
|
||||
// For VCPlayThrough::ReleaseThreadsWaitingForOutputToStart
|
||||
std::atomic<bool> mLogReleasingWaitingThreadsMsg { false };
|
||||
std::atomic<kern_return_t> mReleaseWaitingThreadsSignalError { KERN_SUCCESS };
|
||||
|
||||
// For VCPlayThrough::InputDeviceIOProc and VCPlayThrough::OutputDeviceIOProc
|
||||
struct {
|
||||
Float64 firstInputSampleTime;
|
||||
Float64 lastInputSampleTime;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mDroppedFrames;
|
||||
|
||||
struct {
|
||||
CARingBuffer::SampleTime lastInputSampleTime;
|
||||
CARingBuffer::SampleTime readHeadSampleTime;
|
||||
Float64 inToOutSampleOffset;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mNoSamplesReady;
|
||||
|
||||
struct {
|
||||
const char* callerName;
|
||||
bool gotLock;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mRingBufferUnavailable;
|
||||
|
||||
// For VCPlayThrough::UpdateIOProcState
|
||||
struct {
|
||||
const char* callerName;
|
||||
int ioState;
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mUnexpectedIOStateAfterStopping;
|
||||
|
||||
struct {
|
||||
const char* callerName;
|
||||
OSStatus error;
|
||||
bool errorKnown; // If false, we didn't get an error code from the exception.
|
||||
std::atomic<bool> shouldLogMessage { false };
|
||||
} mExceptionStoppingIOProc;
|
||||
|
||||
// For VCPlayThrough::OutputDeviceIOProc
|
||||
std::atomic<CARingBufferError> mRingBufferStoreError { kCARingBufferError_OK };
|
||||
// For VCPlayThrough::InputDeviceIOProc.
|
||||
std::atomic<CARingBufferError> mRingBufferFetchError { kCARingBufferError_OK };
|
||||
|
||||
// Signalled to wake up the mLoggingThread when it has messages to log.
|
||||
semaphore_t mWakeUpLoggingThreadSemaphore;
|
||||
std::atomic<bool> mLoggingThreadShouldExit { false };
|
||||
// The thread that actually logs the messages.
|
||||
std::thread mLoggingThread;
|
||||
|
||||
#if VC_UnitTest
|
||||
|
||||
public:
|
||||
// Tests normally crash (abort) if LogError is called. This flag lets us test the code that
|
||||
// would otherwise call LogError.
|
||||
bool mContinueOnErrorLogged { false };
|
||||
|
||||
int mNumDebugMessagesLogged { 0 };
|
||||
int mNumWarningMessagesLogged { 0 };
|
||||
int mNumErrorMessagesLogged { 0 };
|
||||
|
||||
#endif /* VC_UnitTest */
|
||||
|
||||
};
|
||||
|
||||
#pragma clang assume_nonnull end
|
||||
|
||||
#endif /* VCApp__VCPlayThroughRTLogger */
|
||||
|
||||
Reference in New Issue
Block a user