Files
ppsspp/Core/Debugger/WebSocket/WebSocketUtils.h
T
libretroadmin f1208a7a5a Make threading lock-free
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.
2026-10-09 04:56:16 +02:00

178 lines
5.6 KiB
C++

// Copyright (c) 2018- PPSSPP 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 git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "ppsspp_config.h"
#include <string>
#include "Common/Log.h"
#include "Common/Data/Format/JSONReader.h"
#include "Common/Data/Format/JSONWriter.h"
#include "Common/Net/WebsocketServer.h"
#if PPSSPP_PLATFORM(UWP)
// Enum name overlapped with UWP macro, quick hack to disable it
#undef OPTIONAL
#endif
using namespace json;
struct WebSocketClientInfo {
WebSocketClientInfo() = default;
std::string name;
std::string version;
std::map<std::string, bool> disallowed;
// Whether to send a "deferred" event for requests that only finish later. Off by default,
// and it has to stay that way: an extra message would break a client that correlates purely
// by ticket, or one that waits for the bare event name a resume or a button press answers
// with. See client.config.set.
bool acknowledgeDeferred = false;
};
// Sent, if the client asked for it, when a handler accepted a request but arranged to finish it
// later - a resume, a step, a button held for some frames. Deliberately not named after the
// request: the event that reports the actual outcome uses that name, and for input.buttons.press
// it carries the same ticket too, so anything less distinct would be impossible to tell apart.
struct DebuggerDeferredEvent {
DebuggerDeferredEvent(const char *n, const JsonGet data) : name(n) {
const JsonNode *value = data ? data.get("ticket") : nullptr;
if (value)
ticketRaw = json_stringify(value);
}
const char *name;
std::string ticketRaw;
operator std::string() const {
JsonWriter j;
j.begin();
j.writeString("event", "deferred");
j.writeString("for", name);
if (!ticketRaw.empty()) {
j.writeRaw("ticket", ticketRaw);
}
j.end();
return j.str();
}
};
struct DebuggerErrorEvent {
DebuggerErrorEvent(const std::string m, LogLevel l, const JsonGet data = JsonValue(JSON_NULL))
: message(m), level(l) {
// Need to format right away, before it's out of scope.
if (data) {
const JsonNode *value = data.get("ticket");
if (value)
ticketRaw = json_stringify(value);
}
}
std::string message;
LogLevel level;
std::string ticketRaw;
operator std::string() const {
JsonWriter j;
j.begin();
j.writeString("event", "error");
j.writeString("message", message);
j.writeInt("level", (int)level);
if (!ticketRaw.empty()) {
j.writeRaw("ticket", ticketRaw);
}
j.end();
return j.str();
}
};
enum class DebuggerParamType {
REQUIRED,
OPTIONAL,
REQUIRED_LOOSE,
OPTIONAL_LOOSE,
};
struct DebuggerRequest {
DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d, WebSocketClientInfo *client_info)
: name(n), ws(w), data(d), client(client_info) {
}
const char *name;
net::WebSocketServer *ws;
const JsonGet data;
WebSocketClientInfo *client;
void Fail(const std::string &message) {
ws->Send(DebuggerErrorEvent(message, LogLevel::LERROR, data));
responseSent_ = true;
}
bool HasParam(const char *name, bool ignoreNull = false);
bool ParamU32(const char *name, uint32_t *out, bool allowFloatBits = false, DebuggerParamType type = DebuggerParamType::REQUIRED);
// For quantities that don't fit in 32 bits, like emulated microseconds - JSON numbers are
// doubles anyway, which is exact well past any plausible session length.
bool ParamF64(const char *name, double *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
bool ParamBool(const char *name, bool *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
bool ParamString(const char *name, std::string *out, DebuggerParamType type = DebuggerParamType::REQUIRED);
JsonWriter &Respond();
void Flush();
bool Finish();
private:
JsonWriter writer_;
bool responseBegun_ = false;
bool responseSent_ = false;
bool responsePartial_ = false;
};
// A connection's inbox for things other threads produce. It outlives the connection's
// subscribers, and stays valid for callbacks running on the CPU thread until that thread lets go.
class DebuggerMailbox {
public:
// Any thread. Queues json for the connection to send (unless the client disallowed category;
// nullptr means always send) and wakes it.
virtual void Post(const char *category, std::string json) = 0;
// Any thread. Wakes the connection so its subscribers' Broadcast() runs.
virtual void Wake() = 0;
// Any thread. Posts json once the sceCtrl timed press pressId is over.
virtual void WatchPress(int pressId, std::string json) = 0;
protected:
~DebuggerMailbox() {}
};
class DebuggerSubscriber {
public:
virtual ~DebuggerSubscriber() {}
// Runs on the connection's thread each time it wakes. Whatever feeds it from another thread
// calls mailbox->Wake() after publishing.
virtual void Broadcast(net::WebSocketServer *ws) {}
// Set by the connection right after the subscriber is created.
DebuggerMailbox *mailbox = nullptr;
};
typedef std::function<void(DebuggerRequest &req)> DebuggerEventHandler;
typedef std::unordered_map<std::string_view, DebuggerEventHandler> DebuggerEventHandlerMap;
uint32_t RoundMemAddressUp(uint32_t addr);