mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-15 15:30:29 +02:00
The core and the headless tools no longer use mutexes, condition variables, spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through libretro-common's retro_atomic and rthreads plus a few small pieces on top of them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and ReaderGate. Every wait blocks until it's woken; nothing polls on a timer. State that only one thread touches just loses its lock. The PSP filesystem, sceIo and the savedata dialog now belong to the emulation thread: async reads and writes run when they're queued and complete later in emulated time, and the IO and savedata threads are gone. The friend finder only talks to the adhoc server and hands what it gets to the emulation thread. GPU init always runs on the thread that starts the game. The Vulkan render and compile queues, pipeline and render pass caches, delete list and SPIR-V cache, the GL render queue, the software renderer's jit caches, the file loader caches, the web debugger, UPnP, input, savestates, logging and the GE debugger all hand off through queues and atomics. The IR jit's spinning profiler thread goes. A CPU thread with nothing to run now sleeps until work is queued instead of spinning. Headless with --debugger waits for a client while stopped, and ends the run once every client that connected has left. Web server shutdown keeps servicing the CPU queue until the server has finished.
226 lines
6.7 KiB
C++
226 lines
6.7 KiB
C++
// Copyright (C) 2003 Dolphin Project.
|
|
|
|
// This program 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, version 2.0 or later versions.
|
|
|
|
// This program 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 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official SVN repository and contact information can be found at
|
|
// http://code.google.com/p/dolphin-emu/
|
|
|
|
#pragma once
|
|
|
|
#include "ppsspp_config.h"
|
|
|
|
#include <vector>
|
|
#include <cstdio>
|
|
|
|
#include "Common/Common.h"
|
|
#include "Common/CommonFuncs.h"
|
|
#include "Common/Log.h"
|
|
#include "Common/File/Path.h"
|
|
#include "Common/Thread/ParkingLot.h"
|
|
|
|
#define MAX_MESSAGES 8000
|
|
|
|
// The running PSP thread, for log headers. Thread local: only the thread running the PSP sets it.
|
|
extern thread_local const char *hleCurrentThreadName;
|
|
|
|
// Struct that listeners can output how they want. For example, on Android we don't want to add
|
|
// timestamp or write the level as a string, those already exist.
|
|
struct LogMessage {
|
|
char timestamp[16];
|
|
char header[64]; // Filename/thread/etc. in front.
|
|
LogLevel level;
|
|
const char *log;
|
|
std::string msg; // The actual log message.
|
|
};
|
|
|
|
enum class LogOutput {
|
|
Stdio = (1 << 0), // Also android log.
|
|
DebugString = (1 << 1),
|
|
RingBuffer = (1 << 2),
|
|
File = (1 << 3),
|
|
Printf = (1 << 5),
|
|
ExternalCallback = (1 << 6),
|
|
};
|
|
ENUM_CLASS_BITOPS(LogOutput);
|
|
|
|
// Lock-free ring of recent messages. Writers claim a slot with fetch_add and
|
|
// guard it with a per-slot sequence (odd while being written). Readers that
|
|
// catch a slot mid-write get an empty string instead of waiting.
|
|
class RingbufferLog {
|
|
public:
|
|
RingbufferLog();
|
|
void Log(const LogMessage &msg);
|
|
int GetCount() const;
|
|
std::string TextAt(int i) const;
|
|
LogLevel LevelAt(int i) const;
|
|
void Clear();
|
|
|
|
private:
|
|
enum { MAX_LOGS = 256, MAX_TEXT = 512 };
|
|
struct Slot {
|
|
retro_atomic_int_t seq;
|
|
retro_atomic_int_t level;
|
|
char text[MAX_TEXT];
|
|
};
|
|
bool Read(int i, std::string *text, LogLevel *level) const;
|
|
Slot slots_[MAX_LOGS];
|
|
retro_atomic_int_t head_;
|
|
retro_atomic_int_t count_;
|
|
};
|
|
|
|
class Section;
|
|
|
|
typedef void (*LogCallback)(const LogMessage &message, void *userdata);
|
|
extern bool *g_bLogEnabledSetting;
|
|
|
|
class LogManager {
|
|
public:
|
|
LogManager();
|
|
~LogManager();
|
|
|
|
void SetOutputsEnabled(LogOutput outputs);
|
|
LogOutput GetOutputsEnabled() const {
|
|
return (LogOutput)retro_atomic_load_relaxed_int(const_cast<retro_atomic_int_t *>(&outputs_));
|
|
}
|
|
void EnableOutput(LogOutput output) {
|
|
retro_atomic_fetch_or_int(&outputs_, (int)output);
|
|
if (output & LogOutput::File)
|
|
SetFileLogPath(logFilename_);
|
|
}
|
|
void DisableOutput(LogOutput output) {
|
|
retro_atomic_fetch_and_int(&outputs_, ~(int)output);
|
|
}
|
|
void EnableOutput(LogOutput output, bool enabled) {
|
|
if (enabled) {
|
|
EnableOutput(output);
|
|
} else {
|
|
DisableOutput(output);
|
|
}
|
|
}
|
|
|
|
static u32 GetMaxLevel() { return (u32)MAX_LOGLEVEL; }
|
|
static int GetNumChannels() { return (int)Log::NUMBER_OF_LOGS; }
|
|
|
|
void LogLine(LogLevel level, Log type,
|
|
const char *file, int line, const char *fmt, va_list args);
|
|
|
|
LogChannel *GetLogChannel(Log type) {
|
|
return &g_log[(size_t)type];
|
|
}
|
|
|
|
void SetLogLevel(Log type, LogLevel level) {
|
|
g_log[(size_t)type].SetLevel(level);
|
|
}
|
|
|
|
void SetAllLogLevels(LogLevel level) {
|
|
for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; ++i) {
|
|
g_log[i].SetLevel(level);
|
|
}
|
|
}
|
|
|
|
void SetAllLogEnable(bool enable) {
|
|
for (int i = 0; i < (int)Log::NUMBER_OF_LOGS; ++i) {
|
|
g_log[i].SetEnabled(enable);
|
|
}
|
|
}
|
|
|
|
void SetEnabled(Log type, bool enable) {
|
|
g_log[(size_t)type].SetEnabled(enable);
|
|
}
|
|
|
|
LogLevel GetLogLevel(Log type) {
|
|
return g_log[(size_t)type].Level();
|
|
}
|
|
|
|
const RingbufferLog &GetRingbuffer() const {
|
|
return ringLog_;
|
|
}
|
|
|
|
void Init(bool *enabledSetting, bool headless = false);
|
|
void Shutdown();
|
|
|
|
// A list rather than a single slot, because the WebSocket debugger registers one callback per
|
|
// *connection* (LogBroadcaster is a local in the per-connection handler) and more than one
|
|
// client can be attached at once - the bundled JS debugger in a browser alongside Tools/wsdbg,
|
|
// say. With a single slot the second connection silently took the log stream away from the
|
|
// first, and then the first one to disconnect cleared the slot and stopped delivery to the
|
|
// other as well.
|
|
// Returns a handle to hand back to RemoveExternalLogCallback(), or -1 if it wasn't added.
|
|
// Add/Remove/SetFileLogPath/Shutdown are control-side calls: one thread at a time.
|
|
// Logging itself may happen from any thread, concurrently with them.
|
|
int AddExternalLogCallback(LogCallback callback, void *userdata);
|
|
void RemoveExternalLogCallback(int handle);
|
|
|
|
void SetFileLogPath(const Path &filename);
|
|
const Path &GetLogFilePath() const { return logFilename_; }
|
|
|
|
void SaveConfig(Section *section);
|
|
void LoadConfig(const Section *section);
|
|
|
|
// Channel level/enabled changes made through the WebSocket debugger (log.channel.set) are
|
|
// meant as temporary, session-only diagnostic tweaks - call this so SaveConfig() skips
|
|
// writing (and thus permanently overwriting) the user's real saved settings with them.
|
|
void NotifyChannelsChangedByDebugger() { channelsChangedByDebugger_ = true; }
|
|
|
|
static const char *GetLogTypeName(Log type);
|
|
|
|
static u32 GetLevelColor(LogLevel level) {
|
|
switch (level) {
|
|
case LogLevel::LDEBUG: return 0xE0E0E0;
|
|
case LogLevel::LWARNING: return 0x50FFFF;
|
|
case LogLevel::LERROR: return 0x5050FF;
|
|
case LogLevel::LNOTICE: return 0x30FF30;
|
|
case LogLevel::LINFO: return 0xFFFF60;
|
|
case LogLevel::LVERBOSE:
|
|
default: return 0xC0C0C0;
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Prevent copies.
|
|
LogManager(const LogManager &) = delete;
|
|
void operator=(const LogManager &) = delete;
|
|
|
|
bool initialized_ = false;
|
|
bool channelsChangedByDebugger_ = false;
|
|
|
|
// Stdio logging
|
|
void StdioLog(const LogMessage &message);
|
|
bool stdioUseColor_ = true;
|
|
|
|
retro_atomic_int_t outputs_;
|
|
|
|
// File logging. fp_ is published to loggers, closed only after a drain.
|
|
void CloseLogFile();
|
|
retro_atomic_ptr_t fp_;
|
|
bool logFileOpenFailed_ = false;
|
|
Path logFilename_;
|
|
|
|
// Ring buffer
|
|
RingbufferLog ringLog_;
|
|
|
|
// Callbacks: published entries, freed only after a drain.
|
|
struct ExternalCallbackEntry {
|
|
LogCallback callback;
|
|
void *userdata;
|
|
};
|
|
enum { MAX_EXTERNAL_CALLBACKS = 8 };
|
|
retro_atomic_ptr_t externalCallbacks_[MAX_EXTERNAL_CALLBACKS];
|
|
retro_atomic_int_t externalCount_;
|
|
|
|
// Loggers hold this while touching fp_ or a callback entry.
|
|
ReaderGate gate_;
|
|
};
|
|
|
|
extern LogManager g_logManager;
|