From b837f8d4938451888842ad05245c7427be97955d Mon Sep 17 00:00:00 2001 From: Mike Blouin Date: Sun, 10 Jun 2018 08:54:10 -0700 Subject: [PATCH 1/2] feat(control-updates): Handle control update event Add method handlers and update the scenes cache when receiving an onControlUpdate event --- Tests/Tests.cpp | 7 +++ source/interactivity.h | 13 +++- source/internal/interactive_control.cpp | 61 +++++++++++++++++++ source/internal/interactive_scene.cpp | 24 +++++++- source/internal/interactive_session.cpp | 41 +++++++++++++ source/internal/interactive_session.h | 4 ++ .../internal/interactive_session_internal.cpp | 2 +- 7 files changed, 149 insertions(+), 3 deletions(-) diff --git a/Tests/Tests.cpp b/Tests/Tests.cpp index 6861234..2e3218d 100644 --- a/Tests/Tests.cpp +++ b/Tests/Tests.cpp @@ -395,6 +395,13 @@ void handle_participants_changed(void* context, interactive_session session, int Logger::WriteMessage(s.str().c_str()); } +void handle_control_changed(void* context, interactive_session session, const interactive_control* control) +{ + std::stringstream s; + s << "'" << control->id << "' (" << control->kind << ")\r\n"; + Logger::WriteMessage(s.str().c_str()); +} + void handle_error_assert(void* context, interactive_session session, int errorCode, const char* errorMessage, size_t errorMessageLength) { Logger::WriteMessage(("[ERROR] (" + std::to_string(errorCode) + ")" + errorMessage).c_str()); diff --git a/source/interactivity.h b/source/interactivity.h index 61df101..e6b9fc6 100644 --- a/source/interactivity.h +++ b/source/interactivity.h @@ -435,6 +435,11 @@ extern "C" { /// typedef void(*on_transaction_complete)(void* context, interactive_session session, const char* transactionId, size_t transactionIdLength, unsigned int error, const char* errorMessage, size_t errorMessageLength); + /// + /// Callback when a control is updated. + /// + typedef void(*on_control_updated)(void* context, interactive_session session, interactive_control control); + /// /// Callback when any method is called that is not handled by the existing callbacks. This may be useful for more advanced scenarios or future protocol changes that may not have existed in this version of the library. /// @@ -465,6 +470,11 @@ extern "C" { /// int interactive_set_transaction_complete_handler(interactive_session session, on_transaction_complete onTransactionComplete); + /// + /// Set the handler function for control updates. This function is called by your own thread during interactive_run + /// + int interactive_set_control_update_handler(interactive_session session, on_control_updated onControlUpdated); + /// /// Set the handler function for unhandled methods. This may be useful for more advanced scenarios or future protocol changes that may not have existed in this version of the library. This function is called by your own thread during interactive_run /// @@ -607,7 +617,8 @@ extern "C" { MIXER_ERROR_WS_DISCONNECT_FAILED, MIXER_ERROR_WS_READ_FAILED, MIXER_ERROR_WS_SEND_FAILED, - MIXER_ERROR_NOT_CONNECTED + MIXER_ERROR_NOT_CONNECTED, + MIXER_ERROR_OBJECT_EXISTS } mixer_result_code; /** @} */ diff --git a/source/internal/interactive_control.cpp b/source/internal/interactive_control.cpp index d953434..e63e7d6 100644 --- a/source/internal/interactive_control.cpp +++ b/source/internal/interactive_control.cpp @@ -154,6 +154,67 @@ int verify_get_property_args_and_get_control_value(interactive_session session, return MIXER_OK; } +void parse_control(rapidjson::Value& controlJson, interactive_control& control) +{ + control.id = controlJson[RPC_CONTROL_ID].GetString(); + control.idLength = controlJson[RPC_CONTROL_ID].GetStringLength(); + control.kind = controlJson[RPC_CONTROL_KIND].GetString(); + control.kindLength = controlJson[RPC_CONTROL_KIND].GetStringLength(); +} + +int cache_new_control(interactive_session_internal& session, const char* sceneId, interactive_control& control, rapidjson::Value& controlJson) +{ + if (session.controls.find(control.id) != session.controls.end()) + { + return MIXER_ERROR_OBJECT_EXISTS; + } + + auto sceneItr = session.scenes.find(sceneId); + if (sceneItr == session.controls.end()) + { + return MIXER_ERROR_OBJECT_NOT_FOUND; + } + + std::string scenePtr = sceneItr->second; + rapidjson::Value* scene = rapidjson::Pointer(rapidjson::StringRef(scenePtr.c_str(), scenePtr.length())).Get(session.scenesRoot); + + rapidjson::Document::AllocatorType& allocator = session.scenesRoot.GetAllocator(); + auto controlsItr = scene->FindMember(RPC_PARAM_CONTROLS); + rapidjson::Value* controls; + if (controlsItr == scene->MemberEnd() || !controlsItr->value.IsArray()) + { + controls = &scene->AddMember(RPC_PARAM_CONTROLS, rapidjson::Value(rapidjson::kArrayType), allocator); + } + else + { + controls = &controlsItr->value; + } + + rapidjson::Value myControlJson(rapidjson::kObjectType); + myControlJson.CopyFrom(controlJson, session.scenesRoot.GetAllocator()); + controls->PushBack(myControlJson, allocator); + RETURN_IF_FAILED(update_control_pointers(session, sceneId)); + + return MIXER_OK; +} + +int update_cached_control(interactive_session_internal& session, interactive_control& control, rapidjson::Value& controlJson) +{ + auto itr = session.controls.find(control.id); + if (itr == session.controls.end()) + { + return MIXER_ERROR_OBJECT_NOT_FOUND; + } + + std::string controlPtr = itr->second; + rapidjson::Value myControlJson(rapidjson::kObjectType); + myControlJson.CopyFrom(controlJson, session.scenesRoot.GetAllocator()); + rapidjson::Pointer(rapidjson::StringRef(controlPtr.c_str(), controlPtr.length())) + .Swap(session.scenesRoot, myControlJson); + + return MIXER_OK; +} + } using namespace mixer_internal; diff --git a/source/internal/interactive_scene.cpp b/source/internal/interactive_scene.cpp index 281b148..ff8b9ee 100644 --- a/source/internal/interactive_scene.cpp +++ b/source/internal/interactive_scene.cpp @@ -24,11 +24,27 @@ int cache_scenes(interactive_session_internal& session) scenesArray.CopyFrom(replyScenesArray, session.scenesRoot.GetAllocator()); session.scenesRoot.AddMember(RPC_PARAM_SCENES, scenesArray, session.scenesRoot.GetAllocator()); + RETURN_IF_FAILED(update_control_pointers(session)); + + return MIXER_OK; +} + +int update_control_pointers(interactive_session_internal& session, const char* sceneId) +{ // Iterate through each scene and set up a pointer to each control. int sceneIndex = 0; for (auto& scene : session.scenesRoot[RPC_PARAM_SCENES].GetArray()) { std::string scenePointer = "/" + std::string(RPC_PARAM_SCENES) + "/" + std::to_string(sceneIndex++); + auto thisSceneId = scene[RPC_SCENE_ID].GetString(); + if (nullptr != sceneId) + { + if (0 != strcmp(sceneId, thisSceneId)) + { + continue; + } + } + auto controlsArray = scene.FindMember(RPC_PARAM_CONTROLS); if (controlsArray != scene.MemberEnd() && controlsArray->value.IsArray()) { @@ -39,7 +55,7 @@ int cache_scenes(interactive_session_internal& session) } } - session.scenes.emplace(scene[RPC_SCENE_ID].GetString(), scenePointer); + session.scenes.emplace(thisSceneId, scenePointer); } return MIXER_OK; @@ -136,6 +152,12 @@ int interactive_scene_get_controls(interactive_session session, const char* scen for (auto& controlObj : (*sceneVal)[RPC_PARAM_CONTROLS].GetArray()) { interactive_control control; + if (!controlObj.IsObject()) { + return MIXER_ERROR_UNRECOGNIZED_DATA_FORMAT; + } + if (!controlObj.HasMember(RPC_CONTROL_ID) || !controlObj[RPC_CONTROL_ID].IsString()) { + return MIXER_ERROR_UNRECOGNIZED_DATA_FORMAT; + } control.id = controlObj[RPC_CONTROL_ID].GetString(); control.idLength = controlObj[RPC_CONTROL_ID].GetStringLength(); control.kind = controlObj[RPC_CONTROL_KIND].GetString(); diff --git a/source/internal/interactive_session.cpp b/source/internal/interactive_session.cpp index 30f9cb7..daa5be3 100644 --- a/source/internal/interactive_session.cpp +++ b/source/internal/interactive_session.cpp @@ -387,6 +387,32 @@ int handle_scene_changed(interactive_session_internal& session, rapidjson::Docum return cache_scenes(session); } +int handle_control_changed(interactive_session_internal& session, rapidjson::Document& doc) +{ + if (!doc.HasMember(RPC_PARAMS) + || !doc[RPC_PARAMS].HasMember(RPC_PARAM_CONTROLS) || !doc[RPC_PARAMS][RPC_PARAM_CONTROLS].IsArray() + || !doc[RPC_PARAMS].HasMember(RPC_SCENE_ID) || !doc[RPC_PARAMS][RPC_SCENE_ID].IsString()) + { + return MIXER_ERROR_UNRECOGNIZED_DATA_FORMAT; + } + + const char * sceneId = doc[RPC_PARAMS][RPC_SCENE_ID].GetString(); + rapidjson::Value& controls = doc[RPC_PARAMS][RPC_PARAM_CONTROLS]; + for (auto itr = controls.Begin(); itr != controls.End(); ++itr) + { + interactive_control control; + parse_control(*itr, control); + RETURN_IF_FAILED(update_cached_control(session, control, *itr)); + + if (session.onControlChanged) + { + session.onControlChanged(session.callerContext, &session, control); + } + } + + return MIXER_OK; +} + int route_method(interactive_session_internal& session, rapidjson::Document& doc) { std::string method = doc[RPC_METHOD].GetString(); @@ -418,6 +444,7 @@ void register_method_handlers(interactive_session_internal& session) session.methodHandlers.emplace(RPC_METHOD_ON_PARTICIPANT_UPDATE, handle_participants_update); session.methodHandlers.emplace(RPC_METHOD_ON_GROUP_UPDATE, handle_group_changed); session.methodHandlers.emplace(RPC_METHOD_ON_GROUP_CREATE, handle_group_changed); + session.methodHandlers.emplace(RPC_METHOD_ON_CONTROL_UPDATE, handle_control_changed); session.methodHandlers.emplace(RPC_METHOD_UPDATE_SCENES, handle_scene_changed); } @@ -1004,6 +1031,20 @@ int interactive_set_transaction_complete_handler(interactive_session session, on return MIXER_OK; } + +int interactive_set_control_update_handler(interactive_session session, on_control_updated onControlUpdated) +{ + if (nullptr == session) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_session_internal* sessionInternal = reinterpret_cast(session); + sessionInternal->onControlChanged = onControlUpdated; + + return MIXER_OK; +} + int interactive_set_unhandled_method_handler(interactive_session session, on_unhandled_method onUnhandledMethod) { if (nullptr == session) diff --git a/source/internal/interactive_session.h b/source/internal/interactive_session.h index 648179d..308ce6a 100644 --- a/source/internal/interactive_session.h +++ b/source/internal/interactive_session.h @@ -70,6 +70,7 @@ struct interactive_session_internal on_error onError; on_state_changed onStateChanged; on_participants_changed onParticipantsChanged; + on_control_updated onControlChanged; on_transaction_complete onTransactionComplete; on_unhandled_method onUnhandledMethod; @@ -130,7 +131,10 @@ int receive_reply(interactive_session_internal& session, unsigned int id, std::s int cache_groups(interactive_session_internal& session); int cache_scenes(interactive_session_internal& session); +int update_cached_control(interactive_session_internal& session, interactive_control& control, rapidjson::Value& controlJson); +int update_control_pointers(interactive_session_internal& session, const char* sceneId = nullptr); void parse_participant(rapidjson::Value& participantJson, interactive_participant& participant); +void parse_control(rapidjson::Value& controlJson, interactive_control& control); // Common reply handler that checks a reply for errors and calls the session's error handler if it exists. int check_reply_errors(interactive_session_internal& session, rapidjson::Document& reply); diff --git a/source/internal/interactive_session_internal.cpp b/source/internal/interactive_session_internal.cpp index 1782910..284b914 100644 --- a/source/internal/interactive_session_internal.cpp +++ b/source/internal/interactive_session_internal.cpp @@ -6,7 +6,7 @@ namespace mixer_internal interactive_session_internal::interactive_session_internal() : callerContext(nullptr), isReady(false), state(interactive_disconnected), shutdownRequested(false), packetId(0), sequenceId(0), wsOpen(false), - onInput(nullptr), onError(nullptr), onStateChanged(nullptr), onParticipantsChanged(nullptr), onUnhandledMethod(nullptr) + onInput(nullptr), onError(nullptr), onStateChanged(nullptr), onParticipantsChanged(nullptr), onUnhandledMethod(nullptr), onControlChanged(nullptr) { scenesRoot.SetObject(); } From f85e44297f4b2d414b9f346b1008c963afd963d9 Mon Sep 17 00:00:00 2001 From: "Josh Snider (XBOX)" Date: Tue, 19 Jun 2018 18:22:11 -0700 Subject: [PATCH 2/2] feat: handle control creation/delete --- Tests/Tests.cpp | 16 ++++++- source/interactivity.h | 10 +++- source/internal/interactive_control.cpp | 61 ++++++++++++++++++++++--- source/internal/interactive_session.cpp | 28 ++++++++++-- source/internal/interactive_session.h | 4 +- 5 files changed, 105 insertions(+), 14 deletions(-) diff --git a/Tests/Tests.cpp b/Tests/Tests.cpp index 2e3218d..fc7e1c7 100644 --- a/Tests/Tests.cpp +++ b/Tests/Tests.cpp @@ -395,9 +395,23 @@ void handle_participants_changed(void* context, interactive_session session, int Logger::WriteMessage(s.str().c_str()); } -void handle_control_changed(void* context, interactive_session session, const interactive_control* control) +void handle_control_changed(void* context, interactive_session session, interactive_control_change_type changeType, const interactive_control* control) { std::stringstream s; + switch (changeType) + { + case interactive_control_created: + s << "Created "; + break; + case interactive_control_updated: + s << "Updated "; + break; + case interactive_control_deleted: + s << "Deleted "; + break; + default: + break; + } s << "'" << control->id << "' (" << control->kind << ")\r\n"; Logger::WriteMessage(s.str().c_str()); } diff --git a/source/interactivity.h b/source/interactivity.h index e6b9fc6..6517ee1 100644 --- a/source/interactivity.h +++ b/source/interactivity.h @@ -183,6 +183,12 @@ extern "C" { size_t kindLength; }; + enum interactive_control_change_type + { + interactive_control_created, + interactive_control_updated, + interactive_control_deleted + }; // Known control properties #define CONTROL_PROP_DISABLED "disabled" @@ -438,7 +444,7 @@ extern "C" { /// /// Callback when a control is updated. /// - typedef void(*on_control_updated)(void* context, interactive_session session, interactive_control control); + typedef void(*on_control_changed)(void* context, interactive_session session, interactive_control_change_type changeType, const interactive_control* control); /// /// Callback when any method is called that is not handled by the existing callbacks. This may be useful for more advanced scenarios or future protocol changes that may not have existed in this version of the library. @@ -473,7 +479,7 @@ extern "C" { /// /// Set the handler function for control updates. This function is called by your own thread during interactive_run /// - int interactive_set_control_update_handler(interactive_session session, on_control_updated onControlUpdated); + int interactive_set_control_changed_handler(interactive_session session, on_control_changed onControlUpdated); /// /// Set the handler function for unhandled methods. This may be useful for more advanced scenarios or future protocol changes that may not have existed in this version of the library. This function is called by your own thread during interactive_run diff --git a/source/internal/interactive_control.cpp b/source/internal/interactive_control.cpp index e63e7d6..05b59b0 100644 --- a/source/internal/interactive_control.cpp +++ b/source/internal/interactive_control.cpp @@ -155,22 +155,26 @@ int verify_get_property_args_and_get_control_value(interactive_session session, } void parse_control(rapidjson::Value& controlJson, interactive_control& control) -{ +{ control.id = controlJson[RPC_CONTROL_ID].GetString(); control.idLength = controlJson[RPC_CONTROL_ID].GetStringLength(); - control.kind = controlJson[RPC_CONTROL_KIND].GetString(); - control.kindLength = controlJson[RPC_CONTROL_KIND].GetStringLength(); + if (controlJson.HasMember(RPC_CONTROL_KIND)) + { + control.kind = controlJson[RPC_CONTROL_KIND].GetString(); + control.kindLength = controlJson[RPC_CONTROL_KIND].GetStringLength(); + } } int cache_new_control(interactive_session_internal& session, const char* sceneId, interactive_control& control, rapidjson::Value& controlJson) { + std::shared_lock readLock(session.scenesMutex); if (session.controls.find(control.id) != session.controls.end()) { return MIXER_ERROR_OBJECT_EXISTS; } auto sceneItr = session.scenes.find(sceneId); - if (sceneItr == session.controls.end()) + if (sceneItr == session.scenes.end()) { return MIXER_ERROR_OBJECT_NOT_FOUND; } @@ -183,7 +187,11 @@ int cache_new_control(interactive_session_internal& session, const char* sceneId rapidjson::Value* controls; if (controlsItr == scene->MemberEnd() || !controlsItr->value.IsArray()) { + readLock.unlock(); + std::unique_lock writeLock(session.scenesMutex); controls = &scene->AddMember(RPC_PARAM_CONTROLS, rapidjson::Value(rapidjson::kArrayType), allocator); + writeLock.unlock(); + readLock.lock(); } else { @@ -198,8 +206,10 @@ int cache_new_control(interactive_session_internal& session, const char* sceneId return MIXER_OK; } + int update_cached_control(interactive_session_internal& session, interactive_control& control, rapidjson::Value& controlJson) { + std::unique_lock writeLock(session.scenesMutex); auto itr = session.controls.find(control.id); if (itr == session.controls.end()) { @@ -215,6 +225,47 @@ int update_cached_control(interactive_session_internal& session, interactive_con return MIXER_OK; } +int delete_cached_control(interactive_session_internal& session, const char* sceneId, interactive_control& control) +{ + std::shared_lock readLock(session.scenesMutex); + if (session.controls.find(control.id) == session.controls.end()) + { + // This control doesn't exist, ignore this deletion. + return MIXER_OK; + } + + auto sceneItr = session.scenes.find(sceneId); + if (sceneItr == session.scenes.end()) + { + return MIXER_ERROR_OBJECT_NOT_FOUND; + } + + // Find the controls array on the scene. + std::string scenePtr = sceneItr->second; + rapidjson::Value* scene = rapidjson::Pointer(rapidjson::StringRef(scenePtr.c_str(), scenePtr.length())).Get(session.scenesRoot); + auto controlsItr = scene->FindMember(RPC_PARAM_CONTROLS); + if (controlsItr == scene->MemberEnd() || !controlsItr->value.IsArray()) + { + // If the scene has no controls on it, ignore this deletion. + return MIXER_OK; + } + + // Erase the value from the array. + rapidjson::Value* controls = &controlsItr->value; + for (auto controlItr = controls->Begin(); controlItr != controls->End(); ++controlItr) + { + if (0 == strcmp(controlItr->GetObject()[RPC_CONTROL_ID].GetString(), control.id)) + { + controls->Erase(controlItr); + break; + } + } + + RETURN_IF_FAILED(update_control_pointers(session, sceneId)); + + return MIXER_OK; +} + } using namespace mixer_internal; @@ -359,8 +410,6 @@ int interactive_control_get_meta_property_data(interactive_session session, cons return err; } - - int interactive_control_get_property_int(interactive_session session, const char* controlId, const char* key, int* property) { rapidjson::Value* controlValue; diff --git a/source/internal/interactive_session.cpp b/source/internal/interactive_session.cpp index daa5be3..79f3218 100644 --- a/source/internal/interactive_session.cpp +++ b/source/internal/interactive_session.cpp @@ -401,12 +401,32 @@ int handle_control_changed(interactive_session_internal& session, rapidjson::Doc for (auto itr = controls.Begin(); itr != controls.End(); ++itr) { interactive_control control; + memset(&control, 0, sizeof(interactive_control)); parse_control(*itr, control); - RETURN_IF_FAILED(update_cached_control(session, control, *itr)); + + interactive_control_change_type changeType = interactive_control_updated; + if (0 == strcmp(doc[RPC_METHOD].GetString(), RPC_METHOD_ON_CONTROL_UPDATE)) + { + RETURN_IF_FAILED(update_cached_control(session, control, *itr)); + } + else if (0 == strcmp(doc[RPC_METHOD].GetString(), RPC_METHOD_ON_CONTROL_CREATE)) + { + changeType = interactive_control_created; + RETURN_IF_FAILED(cache_new_control(session, sceneId, control, *itr)); + } + else if (0 == strcmp(doc[RPC_METHOD].GetString(), RPC_METHOD_ON_CONTROL_DELETE)) + { + changeType = interactive_control_deleted; + RETURN_IF_FAILED(delete_cached_control(session, sceneId, control)); + } + else + { + return MIXER_ERROR_UNKNOWN_METHOD; + } if (session.onControlChanged) { - session.onControlChanged(session.callerContext, &session, control); + session.onControlChanged(session.callerContext, &session, changeType, &control); } } @@ -1032,7 +1052,7 @@ int interactive_set_transaction_complete_handler(interactive_session session, on } -int interactive_set_control_update_handler(interactive_session session, on_control_updated onControlUpdated) +int interactive_set_control_changed_handler(interactive_session session, on_control_changed onControlChanged) { if (nullptr == session) { @@ -1040,7 +1060,7 @@ int interactive_set_control_update_handler(interactive_session session, on_contr } interactive_session_internal* sessionInternal = reinterpret_cast(session); - sessionInternal->onControlChanged = onControlUpdated; + sessionInternal->onControlChanged = onControlChanged; return MIXER_OK; } diff --git a/source/internal/interactive_session.h b/source/internal/interactive_session.h index 308ce6a..b5f8d65 100644 --- a/source/internal/interactive_session.h +++ b/source/internal/interactive_session.h @@ -70,7 +70,7 @@ struct interactive_session_internal on_error onError; on_state_changed onStateChanged; on_participants_changed onParticipantsChanged; - on_control_updated onControlChanged; + on_control_changed onControlChanged; on_transaction_complete onTransactionComplete; on_unhandled_method onUnhandledMethod; @@ -164,7 +164,9 @@ int check_reply_errors(interactive_session_internal& session, rapidjson::Documen #define RPC_METHOD_ON_READY_CHANGED "onReady" // called by server to both game and participant clients #define RPC_METHOD_ON_GROUP_CREATE "onGroupCreate" #define RPC_METHOD_ON_GROUP_UPDATE "onGroupUpdate" +#define RPC_METHOD_ON_CONTROL_CREATE "onControlCreate" #define RPC_METHOD_ON_CONTROL_UPDATE "onControlUpdate" +#define RPC_METHOD_ON_CONTROL_DELETE "onControlDelete" #define RPC_METHOD_GET_TIME "getTime" #define RPC_TIME "time" #define RPC_PARAM_IS_READY "isReady"