base: Pause requests and events during start, SC change, and shutdown

This implements the functionality described by the new NotReady request
status. Behavior should now be *much* more reliable.
This commit is contained in:
tt2468 2023-05-09 22:48:07 -07:00
parent e3d0751385
commit a0ffe16e91
7 changed files with 98 additions and 132 deletions

View File

@ -20,11 +20,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h"
EventHandler::EventHandler()
: _obsLoaded(false),
_inputVolumeMetersRef(0),
_inputActiveStateChangedRef(0),
_inputShowStateChangedRef(0),
_sceneItemTransformChangedRef(0)
{
blog_debug("[EventHandler::EventHandler] Setting up...");
@ -67,9 +62,9 @@ void EventHandler::SetBroadcastCallback(EventHandler::BroadcastCallback cb)
_broadcastCallback = cb;
}
void EventHandler::SetObsLoadedCallback(EventHandler::ObsLoadedCallback cb)
void EventHandler::SetObsReadyCallback(EventHandler::ObsReadyCallback cb)
{
_obsLoadedCallback = cb;
_obsReadyCallback = cb;
}
// Function to increment refcounts for high volume event subscriptions
@ -261,9 +256,6 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
{
auto eventHandler = static_cast<EventHandler *>(private_data);
if (!eventHandler->_obsLoaded.load() && event != OBS_FRONTEND_EVENT_FINISHED_LOADING)
return;
switch (event) {
// General
case OBS_FRONTEND_EVENT_FINISHED_LOADING:
@ -283,7 +275,11 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
}
obs_frontend_source_list_free(&transitions);
}
// Before ready update to allow event to broadcast
eventHandler->HandleCurrentSceneCollectionChanging();
eventHandler->_obsReady = false;
if (eventHandler->_obsReadyCallback)
eventHandler->_obsReadyCallback(false);
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: {
obs_frontend_source_list transitions = {};
@ -294,6 +290,9 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
}
obs_frontend_source_list_free(&transitions);
}
eventHandler->_obsReady = true;
if (eventHandler->_obsReadyCallback)
eventHandler->_obsReadyCallback(true);
eventHandler->HandleCurrentSceneCollectionChanged();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
@ -430,30 +429,6 @@ void EventHandler::FrontendFinishedLoadingMultiHandler()
blog_debug(
"[EventHandler::FrontendFinishedLoadingMultiHandler] OBS has finished loading. Connecting final handlers and enabling events...");
// Connect source signals and enable events only after OBS has fully loaded (to reduce extra logging).
_obsLoaded.store(true);
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
// Enumerate inputs and connect each one
{
auto enumInputs = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->ConnectSourceSignals(source);
return true;
};
obs_enum_sources(enumInputs, this);
}
// Enumerate scenes and connect each one
{
auto enumScenes = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->ConnectSourceSignals(source);
return true;
};
obs_enum_scenes(enumScenes, this);
}
// Enumerate all scene transitions and connect each one
{
obs_frontend_source_list transitions = {};
@ -465,41 +440,23 @@ void EventHandler::FrontendFinishedLoadingMultiHandler()
obs_frontend_source_list_free(&transitions);
}
blog_debug("[EventHandler::FrontendFinishedLoadingMultiHandler] Finished.");
_obsReady = true;
if (_obsReadyCallback)
_obsReadyCallback(true);
if (_obsLoadedCallback)
_obsLoadedCallback();
blog_debug("[EventHandler::FrontendFinishedLoadingMultiHandler] Finished.");
}
void EventHandler::FrontendExitMultiHandler()
{
HandleExitStarted();
blog_debug("[EventHandler::FrontendExitMultiHandler] OBS is unloading. Disabling events...");
HandleExitStarted();
// Disconnect source signals and disable events when OBS starts unloading (to reduce extra logging).
_obsLoaded.store(false);
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()`
// Enumerate inputs and disconnect each one
{
auto enumInputs = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->DisconnectSourceSignals(source);
return true;
};
obs_enum_sources(enumInputs, this);
}
// Enumerate scenes and disconnect each one
{
auto enumScenes = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->DisconnectSourceSignals(source);
return true;
};
obs_enum_scenes(enumScenes, this);
}
_obsReady = false;
if (_obsReadyCallback)
_obsReadyCallback(false);
// Enumerate all scene transitions and disconnect each one
{
@ -520,10 +477,6 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler *>(param);
// Don't react to signals until OBS has finished loading
if (!eventHandler->_obsLoaded.load())
return;
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
return;
@ -556,10 +509,6 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
// Disconnect all signals from the source
eventHandler->DisconnectSourceSignals(source);
// Don't react to signals if OBS is unloading
if (!eventHandler->_obsLoaded.load())
return;
switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT:
// Only emit removed if the input has not already been removed. This is the case when removing the last scene item of an input.
@ -582,9 +531,6 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_obsLoaded.load())
return;
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
return;
@ -605,9 +551,6 @@ void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_obsLoaded.load())
return;
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
return;

View File

@ -34,25 +34,26 @@ public:
EventHandler();
~EventHandler();
typedef std::function<void(uint64_t, std::string, json, uint8_t)> BroadcastCallback;
typedef std::function<void(uint64_t, std::string, json, uint8_t)>
BroadcastCallback; // uint64_t requiredIntent, std::string eventType, json eventData, uint8_t rpcVersion
void SetBroadcastCallback(BroadcastCallback cb);
typedef std::function<void()> ObsLoadedCallback;
void SetObsLoadedCallback(ObsLoadedCallback cb);
typedef std::function<void(bool)> ObsReadyCallback; // bool ready
void SetObsReadyCallback(ObsReadyCallback cb);
void ProcessSubscription(uint64_t eventSubscriptions);
void ProcessUnsubscription(uint64_t eventSubscriptions);
private:
BroadcastCallback _broadcastCallback;
ObsLoadedCallback _obsLoadedCallback;
ObsReadyCallback _obsReadyCallback;
std::atomic<bool> _obsLoaded;
std::atomic<bool> _obsReady = false;
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler;
std::atomic<uint64_t> _inputVolumeMetersRef;
std::atomic<uint64_t> _inputActiveStateChangedRef;
std::atomic<uint64_t> _inputShowStateChangedRef;
std::atomic<uint64_t> _sceneItemTransformChangedRef;
std::atomic<uint64_t> _inputVolumeMetersRef = 0;
std::atomic<uint64_t> _inputActiveStateChangedRef = 0;
std::atomic<uint64_t> _inputShowStateChangedRef = 0;
std::atomic<uint64_t> _sceneItemTransformChangedRef = 0;
void ConnectSourceSignals(obs_source_t *source);
void DisconnectSourceSignals(obs_source_t *source);

View File

@ -89,7 +89,24 @@ bool obs_module_load(void)
return true;
}
void obs_module_unload()
#ifdef PLUGIN_TESTS
void test_register_vendor();
#endif
void obs_module_post_load(void)
{
#ifdef PLUGIN_TESTS
test_register_vendor();
#endif
// Server will accept clients, but requests and events will not be served until FINISHED_LOADING occurs
if (_config->ServerEnabled) {
blog(LOG_INFO, "[obs_module_post_load] WebSocket server is enabled, starting...");
_webSocketServer->Start();
}
}
void obs_module_unload(void)
{
blog(LOG_INFO, "[obs_module_unload] Shutting down...");
@ -193,18 +210,18 @@ static void test_vendor_request_cb(obs_data_t *requestData, obs_data_t *response
obs_websocket_vendor_emit_event(priv_data, "TestEvent", requestData);
}
void obs_module_post_load()
void test_register_vendor()
{
blog(LOG_INFO, "[obs_module_post_load] Post load started.");
blog(LOG_INFO, "[test_register_vendor] Registering test vendor...");
// Test plugin API version fetch
uint apiVersion = obs_websocket_get_api_version();
blog(LOG_INFO, "[obs_module_post_load] obs-websocket plugin API version: %u", apiVersion);
blog(LOG_INFO, "[test_register_vendor] obs-websocket plugin API version: %u", apiVersion);
// Test calling obs-websocket requests
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
if (response) {
blog(LOG_INFO, "[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
blog(LOG_INFO, "[test_register_vendor] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
response->status_code, response->comment, response->response_data);
obs_websocket_request_response_free(response);
}
@ -212,17 +229,17 @@ void obs_module_post_load()
// Test vendor creation
auto vendor = obs_websocket_register_vendor("obs-websocket-test");
if (!vendor) {
blog(LOG_WARNING, "[obs_module_post_load] Failed to create vendor!");
blog(LOG_WARNING, "[test_register_vendor] Failed to create vendor!");
return;
}
// Test vendor request registration
if (!obs_websocket_vendor_register_request(vendor, "TestRequest", test_vendor_request_cb, vendor)) {
blog(LOG_WARNING, "[obs_module_post_load] Failed to register vendor request!");
blog(LOG_WARNING, "[test_register_vendor] Failed to register vendor request!");
return;
}
blog(LOG_INFO, "[obs_module_post_load] Post load completed.");
blog(LOG_INFO, "[test_register_vendor] Post load completed.");
}
#endif

View File

@ -31,17 +31,13 @@ struct SerialFrameBatch {
json &variables;
bool haltOnFailure;
size_t frameCount;
size_t sleepUntilFrame;
size_t frameCount = 0;
size_t sleepUntilFrame = 0;
std::mutex conditionMutex;
std::condition_variable condition;
SerialFrameBatch(RequestHandler &requestHandler, json &variables, bool haltOnFailure)
: requestHandler(requestHandler),
variables(variables),
haltOnFailure(haltOnFailure),
frameCount(0),
sleepUntilFrame(0)
: requestHandler(requestHandler), variables(variables), haltOnFailure(haltOnFailure)
{
}
};

View File

@ -31,7 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../utils/Platform.h"
#include "../utils/Compat.h"
WebSocketServer::WebSocketServer() : QObject(nullptr), _sessions()
WebSocketServer::WebSocketServer() : QObject(nullptr)
{
_server.get_alog().clear_channels(websocketpp::log::alevel::all);
_server.get_elog().clear_channels(websocketpp::log::elevel::all);
@ -52,7 +52,7 @@ WebSocketServer::WebSocketServer() : QObject(nullptr), _sessions()
eventHandler->SetBroadcastCallback(std::bind(&WebSocketServer::BroadcastEvent, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
eventHandler->SetObsLoadedCallback(std::bind(&WebSocketServer::onObsLoaded, this));
eventHandler->SetObsReadyCallback(std::bind(&WebSocketServer::onObsReady, this, std::placeholders::_1));
}
WebSocketServer::~WebSocketServer()
@ -205,18 +205,9 @@ std::vector<WebSocketServer::WebSocketSessionState> WebSocketServer::GetWebSocke
return webSocketSessions;
}
void WebSocketServer::onObsLoaded()
void WebSocketServer::onObsReady(bool ready)
{
auto conf = GetConfig();
if (!conf) {
blog(LOG_ERROR, "[WebSocketServer::onObsLoaded] Unable to retreive config!");
return;
}
if (conf->ServerEnabled) {
blog(LOG_INFO, "[WebSocketServer::onObsLoaded] WebSocket server is enabled, starting...");
Start();
}
_obsReady = ready;
}
bool WebSocketServer::onValidate(websocketpp::connection_hdl hdl)

View File

@ -77,7 +77,7 @@ private:
void ServerRunner();
void onObsLoaded();
void onObsReady(bool loaded);
bool onValidate(websocketpp::connection_hdl hdl);
void onOpen(websocketpp::connection_hdl hdl);
void onClose(websocketpp::connection_hdl hdl);
@ -96,4 +96,6 @@ private:
std::mutex _sessionMutex;
std::map<websocketpp::connection_hdl, SessionPtr, std::owner_less<websocketpp::connection_hdl>> _sessions;
std::atomic<bool> _obsReady = false;
};

View File

@ -209,13 +209,17 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
return;
}
RequestHandler requestHandler(session);
std::string requestType = payloadData["requestType"];
json requestData = payloadData["requestData"];
Request request(requestType, requestData);
RequestResult requestResult;
if (_obsReady) {
json requestData = payloadData["requestData"];
Request request(requestType, requestData);
RequestResult requestResult = requestHandler.ProcessRequest(request);
RequestHandler requestHandler(session);
requestResult = requestHandler.ProcessRequest(request);
} else {
requestResult = RequestResult::Error(RequestStatus::NotReady, "OBS is not ready to perform the request.");
}
json resultPayloadData;
resultPayloadData["requestType"] = requestType;
@ -303,22 +307,34 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
}
std::vector<json> requests = payloadData["requests"];
std::vector<RequestResult> resultsVector;
if (_obsReady) {
std::vector<RequestBatchRequest> requestsVector;
for (auto &requestJson : requests) {
if (!requestJson["requestType"].is_string())
requestJson["requestType"] =
""; // Workaround for what would otherwise be extensive additional logic for a rare edge case
std::string requestType = requestJson["requestType"];
json requestData = requestJson["requestData"];
json inputVariables = requestJson["inputVariables"];
json outputVariables = requestJson["outputVariables"];
requestsVector.emplace_back(requestType, requestData, executionType, inputVariables,
outputVariables);
}
std::vector<RequestBatchRequest> requestsVector;
for (auto &requestJson : requests) {
if (!requestJson["requestType"].is_string())
requestJson["requestType"] =
""; // Workaround for what would otherwise be extensive additional logic for a rare edge case
std::string requestType = requestJson["requestType"];
json requestData = requestJson["requestData"];
json inputVariables = requestJson["inputVariables"];
json outputVariables = requestJson["outputVariables"];
requestsVector.emplace_back(requestType, requestData, executionType, inputVariables, outputVariables);
resultsVector = RequestBatchHandler::ProcessRequestBatch(
_threadPool, session, executionType, requestsVector, payloadData["variables"], haltOnFailure);
} else {
// I lowkey hate this, but whatever
if (haltOnFailure) {
resultsVector.emplace_back(RequestStatus::NotReady, "OBS is not ready to perform the request.");
} else {
for (size_t i = 0; i < requests.size(); i++)
resultsVector.emplace_back(RequestStatus::NotReady,
"OBS is not ready to perform the request.");
}
}
auto resultsVector = RequestBatchHandler::ProcessRequestBatch(_threadPool, session, executionType, requestsVector,
payloadData["variables"], haltOnFailure);
size_t i = 0;
std::vector<json> results;
for (auto &requestResult : resultsVector) {
@ -342,7 +358,7 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData,
uint8_t rpcVersion)
{
if (!_server.is_listening())
if (!_server.is_listening() || !_obsReady)
return;
_threadPool.start(Utils::Compat::CreateFunctionRunnable([=]() {