diff --git a/Tests/Tests.cpp b/Tests/Tests.cpp index 2e3218d..6841b6c 100644 --- a/Tests/Tests.cpp +++ b/Tests/Tests.cpp @@ -542,7 +542,6 @@ TEST_CLASS(Tests) ASSERT_NOERR(do_auth(clientId, "", auth)); - interactive_session session; Logger::WriteMessage("Connecting..."); ASSERT_NOERR(interactive_open_session(&session)); ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert)); @@ -559,6 +558,7 @@ TEST_CLASS(Tests) Logger::WriteMessage("Disconnecting..."); interactive_close_session(session); + session = nullptr; Assert::IsTrue(0 == err); } @@ -577,7 +577,6 @@ TEST_CLASS(Tests) ASSERT_NOERR(do_auth(clientId, clientSecret, auth)); - interactive_session session; Logger::WriteMessage("Connecting..."); ASSERT_NOERR(interactive_open_session(&session)); ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert)); @@ -594,6 +593,7 @@ TEST_CLASS(Tests) Logger::WriteMessage("Disconnecting..."); interactive_close_session(session); + session = nullptr; Assert::IsTrue(0 == err); } @@ -611,7 +611,6 @@ TEST_CLASS(Tests) ASSERT_NOERR(do_auth(clientId, "", auth)); - interactive_session session; ASSERT_NOERR(interactive_open_session(&session)); ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert)); ASSERT_NOERR(interactive_set_input_handler(session, handle_input)); @@ -637,6 +636,7 @@ TEST_CLASS(Tests) Logger::WriteMessage("Disconnecting..."); interactive_close_session(session); + session = nullptr; Assert::IsTrue(0 == err); } @@ -654,7 +654,6 @@ TEST_CLASS(Tests) ASSERT_NOERR(do_auth(clientId, "", auth)); - interactive_session session; ASSERT_NOERR(interactive_open_session(&session)); ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert)); interactive_set_state_changed_handler(session, [](void* context, interactive_session session, interactive_state prevState, interactive_state newState) @@ -691,6 +690,7 @@ TEST_CLASS(Tests) Logger::WriteMessage("Disconnecting..."); interactive_close_session(session); + session = nullptr; Assert::IsTrue(0 == err); } @@ -708,7 +708,6 @@ TEST_CLASS(Tests) ASSERT_NOERR(do_auth(clientId, "", auth)); - interactive_session session; Logger::WriteMessage("Connecting..."); ASSERT_NOERR(interactive_open_session(&session)); ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert)); @@ -795,6 +794,7 @@ TEST_CLASS(Tests) Logger::WriteMessage("Disconnecting..."); interactive_close_session(session); + session = nullptr; Assert::IsTrue(0 == err); } @@ -812,7 +812,6 @@ TEST_CLASS(Tests) ASSERT_NOERR(do_auth(clientId, "", auth)); - interactive_session session; Logger::WriteMessage("Connecting..."); ASSERT_NOERR(interactive_open_session(&session)); ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert)); @@ -857,6 +856,7 @@ TEST_CLASS(Tests) Logger::WriteMessage("Disconnecting..."); interactive_close_session(session); + session = nullptr; Assert::IsTrue(0 == err); } @@ -874,7 +874,6 @@ TEST_CLASS(Tests) ASSERT_NOERR(do_auth(clientId, "", auth)); - interactive_session session; ASSERT_NOERR(interactive_open_session(&session)); std::string controlId = "GiveHealth"; @@ -929,6 +928,167 @@ TEST_CLASS(Tests) Logger::WriteMessage("Disconnecting..."); interactive_close_session(session); + session = nullptr; } + + TEST_METHOD(ControlBatchTest) + { + g_start = std::chrono::high_resolution_clock::now(); + interactive_config_debug(interactive_debug_trace, handle_debug_message); + + int err = 0; + std::string clientId = CLIENT_ID; + std::string versionId = VERSION_ID; + std::string shareCode = SHARE_CODE; + std::string auth; + + ASSERT_NOERR(do_auth(clientId, "", auth)); + + Logger::WriteMessage("Connecting..."); + ASSERT_NOERR(interactive_open_session(&session)); + ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert)); + ASSERT_NOERR(interactive_connect(session, auth.c_str(), versionId.c_str(), shareCode.c_str(), true)); + ASSERT_NOERR(interactive_set_participants_changed_handler(session, handle_participants_changed)); + + // Simulate 60 frames/sec for 1 second. + const int fps = 60; + const int seconds = 1; + for (int i = 0; i < fps * seconds; ++i) + { + ASSERT_NOERR(interactive_run(session, 1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1000 / fps)); + } + + interactive_batch_op batch; + ASSERT_NOERR(interactive_control_batch_begin(session, "default", &batch)); + + interactive_batch_entry entry; + ASSERT_NOERR(interactive_control_batch_add(batch, "GiveHealth", &entry)); + ASSERT_NOERR(interactive_batch_add_param_str(&entry.obj, "foo", "bar")); + ASSERT_NOERR(interactive_batch_add_param_uint(&entry.obj, "number", 42)); + + { + // Invalid Entry + interactive_batch_entry entryDupe; + ASSERT_ERR(MIXER_ERROR_DUPLICATE_ENTRY, interactive_control_batch_add(batch, "GiveHealth", &entryDupe)); + ASSERT_ERR(MIXER_ERROR_INVALID_BATCH_TYPE, interactive_participant_batch_add(batch, "GiveHealth", &entryDupe)); + } + + interactive_batch_array entryArray; + interactive_batch_add_param_array(&entry.obj, "array", &entryArray); + + interactive_batch_object entryArrayObject; + interactive_batch_array_push_object(&entryArray, &entryArrayObject); + ASSERT_NOERR(interactive_batch_add_param_str(&entryArrayObject, "foo", "bar")); + ASSERT_NOERR(interactive_batch_add_param_uint(&entryArrayObject, "number", 42)); + + ASSERT_NOERR(interactive_batch_array_push_str(&entryArray, "bar")); + ASSERT_NOERR(interactive_batch_array_push_uint(&entryArray, 42)); + + interactive_batch_array entryArrayArray; + interactive_batch_array_push_array(&entryArray, &entryArrayArray); + + interactive_batch_object entryArrayArrayObject; + interactive_batch_array_push_object(&entryArrayArray, &entryArrayArrayObject); + ASSERT_NOERR(interactive_batch_add_param_str(&entryArrayArrayObject, "foo", "bar")); + ASSERT_NOERR(interactive_batch_add_param_uint(&entryArrayArrayObject, "number", 42)); + + ASSERT_NOERR(interactive_batch_array_push_str(&entryArrayArray, "bar")); + ASSERT_NOERR(interactive_batch_array_push_uint(&entryArrayArray, 42)); + + interactive_batch_object entryObject; + ASSERT_NOERR(interactive_batch_add_param_object(&entry.obj, "object", &entryObject)); + ASSERT_NOERR(interactive_batch_add_param_str(&entryObject, "foo", "bar")); + ASSERT_NOERR(interactive_batch_add_param_uint(&entryObject, "number", 42)); + ASSERT_NOERR(interactive_control_batch_commit(batch)); + ASSERT_NOERR(interactive_batch_close(batch)); + + // Simulate 60 frames/sec for 1 second. + for (int i = 0; i < fps * seconds; ++i) + { + ASSERT_NOERR(interactive_run(session, 1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1000 / fps)); + } + + Logger::WriteMessage("Validating custom properties"); + ASSERT_NOERR(interactive_get_scenes(session, [](void* context, interactive_session session, interactive_scene* scene) + { + char foo[4]; + size_t nameLength = 4; + int number = 0; + memset(foo, 0, sizeof(char[4])); + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_string(session, "GiveHealth", "foo", foo, &nameLength)); + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_int(session, "GiveHealth", "number", &number)); + Assert::AreEqual("bar", foo); + Assert::AreEqual(42, number); + memset(foo, 0, sizeof(char[4])); + number = 0; + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_string(session, "GiveHealth", "array/0/foo", foo, &nameLength)); + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_int(session, "GiveHealth", "array/0/number", &number)); + Assert::AreEqual("bar", foo); + Assert::AreEqual(42, number); + memset(foo, 0, sizeof(char[4])); + number = 0; + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_string(session, "GiveHealth", "array/1", foo, &nameLength)); + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_int(session, "GiveHealth", "array/2", &number)); + Assert::AreEqual("bar", foo); + Assert::AreEqual(42, number); + memset(foo, 0, sizeof(char[4])); + number = 0; + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_string(session, "GiveHealth", "array/3/0/foo", foo, &nameLength)); + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_int(session, "GiveHealth", "array/3/0/number", &number)); + Assert::AreEqual("bar", foo); + Assert::AreEqual(42, number); + memset(foo, 0, sizeof(char[4])); + number = 0; + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_string(session, "GiveHealth", "array/3/1", foo, &nameLength)); + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_int(session, "GiveHealth", "array/3/2", &number)); + Assert::AreEqual("bar", foo); + Assert::AreEqual(42, number); + memset(foo, 0, sizeof(char[4])); + number = 0; + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_string(session, "GiveHealth", "object/foo", foo, &nameLength)); + Assert::AreEqual((int)MIXER_OK, interactive_control_get_property_int(session, "GiveHealth", "object/number", &number)); + Assert::AreEqual("bar", foo); + Assert::AreEqual(42, number); + })); + + for (int i = 0; i < fps * seconds; ++i) + { + ASSERT_NOERR(interactive_run(session, 1)); + std::this_thread::sleep_for(std::chrono::milliseconds(1000 / fps)); + } + + Logger::WriteMessage("Disconnecting..."); + interactive_close_session(session); + session = nullptr; + + Assert::IsTrue(0 == err); + } + + TEST_METHOD_CLEANUP(CleanupSession) { + if (nullptr != session) + { + try { + Logger::WriteMessage("Disconnecting in cleanup..."); + interactive_close_session(session); + } + catch (std::exception e) { + Logger::WriteMessage((std::string("Error in cleanup handler: ") + e.what()).c_str()); + } + catch (std::string e) { + Logger::WriteMessage(("Error in cleanup handler: " + e).c_str()); + } + catch (...) + { + Logger::WriteMessage("Unknown error in cleanup handler. You should debug this."); + } + } + + session = nullptr; + } + +private: + interactive_session session = nullptr; }; } \ No newline at end of file diff --git a/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj b/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj index 4603de2..e91146c 100644 --- a/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj +++ b/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj @@ -70,6 +70,12 @@ true true true + + + true + true + true + true true @@ -125,6 +131,7 @@ + diff --git a/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj.filters b/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj.filters index 5caadba..71e7aaf 100644 --- a/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj.filters +++ b/builds/Interactivity.UWP.Cpp/Interactivity.UWP.Cpp.vcxproj.filters @@ -45,6 +45,9 @@ C++ Source + + C++ Source + @@ -68,5 +71,8 @@ Includes + + Includes + \ No newline at end of file diff --git a/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj b/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj index 954b4b1..b32bbe8 100644 --- a/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj +++ b/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj @@ -66,6 +66,12 @@ true true true + + + true + true + true + true true @@ -121,6 +127,7 @@ + diff --git a/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj.filters b/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj.filters index 4372f6e..cdd9f77 100644 --- a/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj.filters +++ b/builds/Interactivity.Win32.Cpp/Interactivity.Win32.Cpp.vcxproj.filters @@ -45,6 +45,9 @@ C++ Source + + C++ Source + @@ -71,5 +74,8 @@ Includes + + Includes + \ No newline at end of file diff --git a/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj b/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj index 6350480..1fcdccf 100644 --- a/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj +++ b/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj @@ -64,6 +64,10 @@ true true + + + true + true true @@ -98,6 +102,7 @@ + diff --git a/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj.filters b/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj.filters index b345173..70e2a87 100644 --- a/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj.filters +++ b/builds/Interactivity.Xbox.Cpp/Interactivity.Xbox.Cpp.vcxproj.filters @@ -42,6 +42,9 @@ C++ Source + + C++ Source + @@ -59,5 +62,8 @@ Includes + + Includes + \ No newline at end of file diff --git a/source/interactivity.cpp b/source/interactivity.cpp index 9f2e805..1264f6e 100644 --- a/source/interactivity.cpp +++ b/source/interactivity.cpp @@ -1,6 +1,7 @@ #include "internal/common.cpp" #include "internal/http_client.cpp" #include "internal/interactive_auth.cpp" +#include "internal/interactive_batch.cpp" #include "internal/interactive_control.cpp" #include "internal/interactive_group.cpp" #include "internal/interactive_participant.cpp" diff --git a/source/interactivity.h b/source/interactivity.h index e6b9fc6..fa7349c 100644 --- a/source/interactivity.h +++ b/source/interactivity.h @@ -173,6 +173,143 @@ extern "C" { void interactive_close_session(interactive_session session); /** @} */ + /** @name Batch Requests + * @{ + */ + /// + /// Opaque handle for a batch update object + /// + struct interactive_batch_op_public {}; + + typedef interactive_batch_op_public* interactive_batch_op; + + /// + /// Opaque handle for a batch object + /// + struct interactive_batch_object + { + void* _a; + void* _b; + }; + + /// + /// Opaque handle for a batch array + /// + struct interactive_batch_array + { + void* _a; + void* _b; + }; + + /// + /// Handle for a batch entry + /// + struct interactive_batch_entry + { + interactive_batch_object obj; + }; + + /// + /// Closes an interactive batch request. Should be called after committing or aborting a batch request to free memory. + /// + int interactive_batch_close(interactive_batch_op batch); + + /// + /// Nullifies a field on a batch update object. + /// + /// The Mixer interactive protocol implements the JSON Merge Patch algorithm (RFC 7386) for updates on protocol objects - meaning that to delete an object property you need to set it to null rather than just omit it in an update. + /// + /// + int interactive_batch_add_param_null(interactive_batch_object* obj, const char* name); + + /// + /// Adds a string field on a batch update object. + /// + int interactive_batch_add_param_str(interactive_batch_object* obj, const char* name, const char* value); + + /// + /// Adds a uint field on a batch update object. + /// + int interactive_batch_add_param_uint(interactive_batch_object* obj, const char* name, unsigned int value); + + /// + /// Adds a bool field on a batch update object. + /// + int interactive_batch_add_param_bool(interactive_batch_object* obj, const char* name, bool value); + + /// + /// Adds an object field on a batch update object. + /// + /// Object properties must be added before the supplied callback returns using the `interactive_batch_add_param_*` family of methods. + /// + /// + int interactive_batch_add_param_object(interactive_batch_object* obj, const char* name, interactive_batch_object* paramObj); + + /// + /// Adds an array field on a batch update object. + /// + /// Array items must be added before the supplied callback returns using the `interactive_batch_array_push_*` family of methods. + /// + /// + int interactive_batch_add_param_array(interactive_batch_object* obj, const char* name, interactive_batch_array* paramArr); + + /// + /// Pushes a null item to a batch update array. + /// + /// Must be called within the callback from `interactive_batch_add_param_array` or `interactive_batch_array_push_object`. + /// + /// + int interactive_batch_array_push_null(interactive_batch_array* array); + + /// + /// Pushes a string item to a batch update array. + /// + /// Must be called within the callback from `interactive_batch_add_param_array` or `interactive_batch_array_push_object`. + /// + /// + int interactive_batch_array_push_str(interactive_batch_array* array, const char* value); + + /// + /// Pushes a uint item to a batch update array. + /// + /// Must be called within the callback from `interactive_batch_add_param_array` or `interactive_batch_array_push_object`. + /// + /// + int interactive_batch_array_push_uint(interactive_batch_array* array, unsigned int value); + + /// + /// Pushes a bool item to a batch update array. + /// + /// Must be called within the callback from `interactive_batch_add_param_array` or `interactive_batch_array_push_object`. + /// + /// + int interactive_batch_array_push_bool(interactive_batch_array* array, bool value); + + /// + /// Pushes an object on to a batch update array. + /// + /// Must be called within the callback from `interactive_batch_add_param_array` or `interactive_batch_array_push_object`. + /// Object properties must be added before the supplied callback returns using the `interactive_batch_add_param_*` family of methods. + /// + /// + int interactive_batch_array_push_object(interactive_batch_array* array, interactive_batch_object* pushObj); + + /// + /// Pushes an array on to a batch update array. + /// + /// Must be called within the callback from `interactive_batch_add_param_array` or `interactive_batch_array_push_object`. + /// Array items must be added before the supplied callback returns using the `interactive_batch_array_push_*` family of methods. + /// + /// + int interactive_batch_array_push_array(interactive_batch_array* array, interactive_batch_array* pushArr); + + /// + /// Sets the update priority of this batch update. + /// + int interactive_batch_set_priority(interactive_batch_op batch, int priority); + + /** @} */ + /** @name Controls * @{ */ @@ -286,6 +423,28 @@ extern "C" { /// Get a char* meta property value by name. /// int interactive_control_get_meta_property_string(interactive_session session, const char* controlId, const char* key, char* property, size_t* propertyLength); + + /// + /// Starts a batch update of interactive control objects. + /// + /// Note that batch requests created with this method will only support updates to control objects. + /// Batch requests are finalized with the `interactive_control_batch_commit` method after using `interactive_control_batch_add` to add the individual updates. + /// + /// + int interactive_control_batch_begin(interactive_session session, const char* sceneId, interactive_batch_op* batchPtr); + + /// + /// Adds an update to the control batch request for the control with the given id. + /// + /// Note that a batch request should only contain a single entry per control ID. + /// + /// + int interactive_control_batch_add(interactive_batch_op batch, const char* controlId, interactive_batch_entry* entry); + + /// + /// Commits an interactive control batch update against the interactive service. + /// + int interactive_control_batch_commit(interactive_batch_op batch); /** @} */ /** @name Groups @@ -531,6 +690,33 @@ extern "C" { /// Get the participant's group name. /// int interactive_participant_get_group(interactive_session session, const char* participantId, char* group, size_t* groupLength); + + /// + /// Starts a batch update of participant objects. + /// + /// Note that batch requests created with this method will only support updates to participant objects. + /// Batch requests are finalized with the `interactive_participant_batch_commit` method after using `interactive_participant_batch_add` to add the individual updates. + /// + /// + int interactive_participant_batch_begin(interactive_session session, interactive_batch_op* batchPtr); + + /// + /// Adds an update to the participant batch request for the participant with the given id. + /// + /// Note that a batch request should only contain a single entry per participant ID. + /// + /// + int interactive_participant_batch_add(interactive_batch_op batch, const char* sessionId, interactive_batch_entry* entry); + + /// + /// Commits an interactive participant batch update against the interactive service. + /// + int interactive_participant_batch_commit(interactive_batch_op batch); + + /// + /// Reads a string value from the participant object. + /// + int interactive_participant_get_param_string(interactive_session session, const char * participantId, const char *paramName, char* value, size_t* valueLength); /** @} */ /** @name Manual Protocol Integration @@ -618,7 +804,9 @@ extern "C" { MIXER_ERROR_WS_READ_FAILED, MIXER_ERROR_WS_SEND_FAILED, MIXER_ERROR_NOT_CONNECTED, - MIXER_ERROR_OBJECT_EXISTS + MIXER_ERROR_OBJECT_EXISTS, + MIXER_ERROR_INVALID_BATCH_TYPE, + MIXER_ERROR_DUPLICATE_ENTRY, } mixer_result_code; /** @} */ diff --git a/source/internal/interactive_batch.cpp b/source/internal/interactive_batch.cpp new file mode 100644 index 0000000..c16964b --- /dev/null +++ b/source/internal/interactive_batch.cpp @@ -0,0 +1,340 @@ +#include "interactive_batch.h" +#include "interactive_session.h" +#include "common.h" + +#include +#include + +#include "rapidjson/pointer.h" + +#define OBJECT_BATCH(o) ((o)->_a) +#define OBJECT_VALUE(o) ((o)->_b) + +namespace mixer_internal +{ + +inline interactive_batch_op_internal* cast_batch(interactive_batch_op batch) +{ + return reinterpret_cast(batch); +} + +inline interactive_batch_op_internal* object_get_batch(interactive_batch_object* obj) +{ + return reinterpret_cast(OBJECT_BATCH(obj)); +} + +inline rapidjson::Value* object_get_value(interactive_batch_object* obj) +{ + return reinterpret_cast(OBJECT_VALUE(obj)); +} + +inline interactive_batch_op_internal* array_get_batch(interactive_batch_array* obj) +{ + return reinterpret_cast(OBJECT_BATCH(obj)); +} + +inline rapidjson::Value* array_get_value(interactive_batch_array* obj) +{ + return reinterpret_cast(OBJECT_VALUE(obj)); +} + +int interactive_batch_begin(interactive_session session, char *methodName, interactive_batch_type type, interactive_batch_op* batchPtr) +{ + if (nullptr == session || nullptr == batchPtr || nullptr == methodName) + { + return MIXER_ERROR_INVALID_POINTER; + } + + std::auto_ptr batchInternal(new interactive_batch_op_internal()); + batchInternal->session = reinterpret_cast(session); + batchInternal->method = methodName; + batchInternal->type = type; + batchInternal->document = std::make_shared(); + *batchPtr = reinterpret_cast(batchInternal.release()); + + interactive_batch_set_priority(*batchPtr, RPC_PRIORITY_DEFAULT); + + return MIXER_OK; +} + +int interactive_batch_add_entry(interactive_batch_op batch, const char * paramsKey, const char* entryId, interactive_batch_entry* entry) +{ + if (nullptr == batch || nullptr == entry) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batchInternal = cast_batch(batch); + std::stringstream ss; + ss << "/" << RPC_PARAMS << "/" << paramsKey; + size_t pos = batchInternal->entryIds.size(); + if (0 == pos) + { + rapidjson::Pointer(ss.str().c_str()) + .Set(*batchInternal->document, rapidjson::Value(rapidjson::kArrayType)); + } + + auto result = batchInternal->entryIds.emplace(entryId); + if (!result.second) + { + return MIXER_ERROR_DUPLICATE_ENTRY; + } + + ss << "/" << pos; + OBJECT_BATCH(&entry->obj) = batchInternal; + OBJECT_VALUE(&entry->obj) = + &rapidjson::Pointer(ss.str().c_str()) + .Set(*batchInternal->document, rapidjson::Value(rapidjson::kObjectType)); + + return MIXER_OK; +} + +int interactive_batch_free(interactive_batch_op_internal* batch) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + delete batch; + + return MIXER_OK; +} + +int interactive_batch_commit(interactive_batch_op_internal* batch) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + return queue_method_prebuilt(*batch->session, batch->method.c_str(), nullptr, batch->document); +} + +int interactive_batch_add_param(interactive_batch_op_internal* batch, rapidjson::Pointer& ptr, rapidjson::Value& value) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + ptr.Set(*batch->document, value); + + return MIXER_OK; +} + +int interactive_batch_add_param(interactive_batch_object* obj, const char *paramName, rapidjson::Value& value) +{ + if (nullptr == obj || nullptr == paramName) + { + return MIXER_ERROR_INVALID_POINTER; + } + + object_get_value(obj)->AddMember( + rapidjson::Value(std::string(paramName).c_str(), object_get_batch(obj)->document->GetAllocator()), + value, + object_get_batch(obj)->document->GetAllocator()); + + return MIXER_OK; +} + +int interactive_batch_array_push(interactive_batch_array* array, rapidjson::Value& value) +{ + if (nullptr == array) + { + return MIXER_ERROR_INVALID_POINTER; + } + + array_get_value(array)->PushBack(value, array_get_batch(array)->document->GetAllocator()); + + return MIXER_OK; +} + +int interactive_batch_array_push(interactive_batch_array* array, rapidjson::Pointer& ptr, rapidjson::Value& value) +{ + if (nullptr == array) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batch = array_get_batch(array); + ptr.Set(*batch->document.get(), value); + + return MIXER_OK; +} + +} + +using namespace mixer_internal; + +int interactive_batch_close(interactive_batch_op batch) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + return interactive_batch_free(cast_batch(batch)); +} + +int interactive_batch_set_priority(interactive_batch_op batch, int priority) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batchInternal = cast_batch(batch); + std::stringstream ss; + ss << "/" << RPC_PARAMS << "/" << RPC_PRIORITY; + rapidjson::Pointer(ss.str().c_str()).Set(*batchInternal->document, priority); + return MIXER_OK; +} + +int interactive_batch_add_param_null(interactive_batch_object* obj, const char* name) +{ + return interactive_batch_add_param( + obj, + name, + rapidjson::Value(rapidjson::kNullType).Move()); +} + +int interactive_batch_add_param_str(interactive_batch_object* obj, const char* name, const char* value) +{ + if (nullptr == obj || nullptr == value) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batch = object_get_batch(obj); + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + return interactive_batch_add_param( + obj, + name, + rapidjson::Value(std::string(value).c_str(), batch->document->GetAllocator()).Move()); +} + +int interactive_batch_add_param_uint(interactive_batch_object* obj, const char* name, unsigned int value) +{ + return interactive_batch_add_param( + obj, + name, + rapidjson::Value(value).Move()); +} + +int interactive_batch_add_param_bool(interactive_batch_object* obj, const char* name, bool value) +{ + return interactive_batch_add_param( + obj, + name, + rapidjson::Value(value).Move()); +} + +int interactive_batch_add_param_object(interactive_batch_object* obj, const char* name, interactive_batch_object* paramObj) +{ + if (nullptr == obj || nullptr == name || nullptr == paramObj) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batch = object_get_batch(obj); + auto jsonObj = object_get_value(obj)->GetObject(); + jsonObj.AddMember( + rapidjson::Value(std::string(name).c_str(), object_get_batch(obj)->document->GetAllocator()).Move(), + rapidjson::Value(rapidjson::kObjectType).Move(), + object_get_batch(obj)->document->GetAllocator()); + OBJECT_BATCH(paramObj) = batch; + OBJECT_VALUE(paramObj) = &jsonObj.FindMember(name)->value; + + return MIXER_OK; +} + +int interactive_batch_add_param_array(interactive_batch_object* obj, const char* name, interactive_batch_array* paramArr) +{ + if (nullptr == obj || nullptr == name || nullptr == paramArr) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batch = object_get_batch(obj); + auto jsonObj = object_get_value(obj)->GetObject(); + jsonObj.AddMember( + rapidjson::Value(std::string(name).c_str(), object_get_batch(obj)->document->GetAllocator()).Move(), + rapidjson::Value(rapidjson::kArrayType).Move(), + object_get_batch(obj)->document->GetAllocator()); + OBJECT_BATCH(paramArr) = batch; + OBJECT_VALUE(paramArr) = &jsonObj.FindMember(name)->value; + + return MIXER_OK; +} + +int interactive_batch_array_push_null(interactive_batch_array* array) +{ + return interactive_batch_array_push(array, rapidjson::Value(rapidjson::kNullType).Move()); +} + +int interactive_batch_array_push_str(interactive_batch_array* array, const char* value) +{ + if (nullptr == value) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batch = array_get_batch(array); + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + return interactive_batch_array_push(array, rapidjson::Value(std::string(value).c_str(), batch->document->GetAllocator()).Move()); +} + +int interactive_batch_array_push_uint(interactive_batch_array* array, unsigned int value) +{ + return interactive_batch_array_push(array, rapidjson::Value(value).Move()); +} + +int interactive_batch_array_push_bool(interactive_batch_array* array, bool value) +{ + return interactive_batch_array_push(array, rapidjson::Value(value).Move()); +} + +int interactive_batch_array_push_object(interactive_batch_array* array, interactive_batch_object* pushObj) +{ + if (nullptr == array || nullptr == pushObj) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batch = array_get_batch(array); + auto arr = array_get_value(array)->GetArray(); + arr.PushBack( + rapidjson::Value(rapidjson::kObjectType).Move(), + array_get_batch(array)->document->GetAllocator()); + OBJECT_BATCH(pushObj) = batch; + OBJECT_VALUE(pushObj) = &arr[arr.Size() - 1]; + + return MIXER_OK; +} + +int interactive_batch_array_push_array(interactive_batch_array* array, interactive_batch_array* pushArr) +{ + if (nullptr == array || nullptr == pushArr) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batch = array_get_batch(array); + auto arr = array_get_value(array)->GetArray(); + arr.PushBack( + rapidjson::Value(rapidjson::kArrayType).Move(), + array_get_batch(array)->document->GetAllocator()); + OBJECT_BATCH(pushArr) = batch; + OBJECT_VALUE(pushArr) = &arr[arr.Size() - 1]; + + return MIXER_OK; +} diff --git a/source/internal/interactive_batch.h b/source/internal/interactive_batch.h new file mode 100644 index 0000000..80d5b59 --- /dev/null +++ b/source/internal/interactive_batch.h @@ -0,0 +1,36 @@ +#pragma once +#include "interactivity.h" +#include "interactive_session.h" +#include "rapidjson\document.h" + +#include + +namespace mixer_internal +{ + +enum interactive_batch_type +{ + INTERACTIVE_BATCH_TYPE_CONTROL, + INTERACTIVE_BATCH_TYPE_PARTICIPANT, + INTERACTIVE_BATCH_TYPE_SCENE +}; + +struct interactive_batch_op_internal; +struct interactive_batch_op_internal +{ + interactive_session_internal* session; + std::string method; + std::shared_ptr document; + std::unordered_set entryIds; + interactive_batch_type type; +}; + +int interactive_batch_begin(interactive_session session, char *methodName, interactive_batch_type type, interactive_batch_op* batchPtr); +int interactive_batch_add_entry(interactive_batch_op batch, const char * paramsKey, const char* entryId, interactive_batch_entry* entry); +int interactive_batch_free(interactive_batch_op_internal* batch); +int interactive_batch_commit(interactive_batch_op_internal* batch); +int interactive_batch_add_param(interactive_batch_op_internal* batch, rapidjson::Pointer& ptr, rapidjson::Value& value); +int interactive_batch_array_push(interactive_batch_array* array, rapidjson::Value& value); +int interactive_batch_array_push(interactive_batch_array* array, rapidjson::Pointer& ptr, rapidjson::Value& value); + +} \ No newline at end of file diff --git a/source/internal/interactive_control.cpp b/source/internal/interactive_control.cpp index e63e7d6..b0bbc3d 100644 --- a/source/internal/interactive_control.cpp +++ b/source/internal/interactive_control.cpp @@ -1,6 +1,9 @@ #include "interactive_session.h" +#include "interactive_batch.h" #include "common.h" +#include + namespace mixer_internal { @@ -538,4 +541,60 @@ int interactive_control_get_meta_property_string(interactive_session session, co property[*propertyLength - 1] = '\0'; return MIXER_OK; +} + +int interactive_control_batch_begin(interactive_session session, const char* sceneId, interactive_batch_op* batchPtr) +{ + if (nullptr == session || nullptr == batchPtr) + { + return MIXER_ERROR_INVALID_POINTER; + } + + RETURN_IF_FAILED(interactive_batch_begin(session, RPC_METHOD_UPDATE_CONTROLS, INTERACTIVE_BATCH_TYPE_CONTROL, batchPtr)); + + interactive_batch_op_internal* batchInternal = reinterpret_cast(*batchPtr); + std::stringstream ss; + ss << "/" << RPC_PARAMS << "/" RPC_SCENE_ID; + rapidjson::Pointer ptr(ss.str().c_str()); + interactive_batch_add_param( + batchInternal, + ptr, + rapidjson::Value(std::string(sceneId).c_str(), batchInternal->document->GetAllocator()).Move()); + + return MIXER_OK; +} + +int interactive_control_batch_add(interactive_batch_op batch, const char* controlId, interactive_batch_entry* entry) +{ + if (nullptr == batch) { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batchInternal = reinterpret_cast(batch); + if (batchInternal->type != INTERACTIVE_BATCH_TYPE_CONTROL) + { + return MIXER_ERROR_INVALID_BATCH_TYPE; + } + + RETURN_IF_FAILED(interactive_batch_add_entry(batch, RPC_PARAM_CONTROLS, controlId, entry)); + + RETURN_IF_FAILED(interactive_batch_add_param_str(&entry->obj, RPC_CONTROL_ID, controlId)); + + return MIXER_OK; +} + +int interactive_control_batch_commit(interactive_batch_op batch) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batchInternal = reinterpret_cast(batch); + if (batchInternal->type != INTERACTIVE_BATCH_TYPE_CONTROL) + { + return MIXER_ERROR_INVALID_BATCH_TYPE; + } + + return interactive_batch_commit(batchInternal); } \ No newline at end of file diff --git a/source/internal/interactive_participant.cpp b/source/internal/interactive_participant.cpp index a4c4a42..72457a7 100644 --- a/source/internal/interactive_participant.cpp +++ b/source/internal/interactive_participant.cpp @@ -1,4 +1,5 @@ #include "interactive_session.h" +#include "interactive_batch.h" #include "common.h" namespace mixer_internal @@ -262,4 +263,87 @@ int interactive_participant_get_group(interactive_session session, const char* p group[actualLength] = 0; *groupLength = actualLength + 1; return MIXER_OK; +} + +int interactive_participant_batch_begin(interactive_session session, interactive_batch_op* batchPtr) +{ + if (nullptr == session || nullptr == batchPtr) + { + return MIXER_ERROR_INVALID_POINTER; + } + + RETURN_IF_FAILED(interactive_batch_begin(session, RPC_METHOD_UPDATE_PARTICIPANTS, INTERACTIVE_BATCH_TYPE_PARTICIPANT, batchPtr)); + + return MIXER_OK; +} + +int interactive_participant_batch_add(interactive_batch_op batch, const char* sessionId, interactive_batch_entry* entry) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batchInternal = reinterpret_cast(batch); + if (batchInternal->type != INTERACTIVE_BATCH_TYPE_PARTICIPANT) + { + return MIXER_ERROR_INVALID_BATCH_TYPE; + } + + RETURN_IF_FAILED(interactive_batch_add_entry(batch, RPC_PARAM_PARTICIPANTS, sessionId, entry)); + + RETURN_IF_FAILED(interactive_batch_add_param_str(&entry->obj, RPC_SESSION_ID, sessionId)); + + return MIXER_OK; +} + +int interactive_participant_batch_commit(interactive_batch_op batch) +{ + if (nullptr == batch) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_batch_op_internal* batchInternal = reinterpret_cast(batch); + if (batchInternal->type != INTERACTIVE_BATCH_TYPE_PARTICIPANT) + { + return MIXER_ERROR_INVALID_BATCH_TYPE; + } + + return interactive_batch_commit(batchInternal); +} + +int interactive_participant_get_param_string(interactive_session session, const char * participantId, const char *paramName, char* value, size_t* valueLength) +{ + if (nullptr == session || nullptr == participantId || nullptr == valueLength) + { + return MIXER_ERROR_INVALID_POINTER; + } + + interactive_session_internal* sessionInternal = reinterpret_cast(session); + // Validate connection state. + if (interactive_disconnected == sessionInternal->state) + { + return MIXER_ERROR_NOT_CONNECTED; + } + + auto participantItr = sessionInternal->participants.find(std::string(participantId)); + if (sessionInternal->participants.end() == participantItr) + { + return MIXER_ERROR_OBJECT_NOT_FOUND; + } + + std::shared_ptr participantDoc = participantItr->second; + + size_t actualLength = (*participantDoc)[paramName].GetStringLength(); + if (nullptr == value || *valueLength < actualLength + 1) + { + *valueLength = actualLength + 1; + return MIXER_ERROR_BUFFER_SIZE; + } + + memcpy(value, (*participantDoc)[paramName].GetString(), actualLength); + value[actualLength] = 0; + *valueLength = actualLength + 1; + return MIXER_OK; } \ No newline at end of file diff --git a/source/internal/interactive_session.cpp b/source/internal/interactive_session.cpp index daa5be3..3240585 100644 --- a/source/internal/interactive_session.cpp +++ b/source/internal/interactive_session.cpp @@ -8,10 +8,8 @@ namespace mixer_internal typedef std::function on_get_params; -int create_method_json(interactive_session_internal& session, const std::string& method, on_get_params getParams, bool discard, unsigned int* id, std::shared_ptr& methodDoc) +int set_packet_info(interactive_session_internal& session, const std::string& method, bool discard, unsigned int* id, std::shared_ptr& doc) { - std::shared_ptr doc(std::make_shared()); - doc->SetObject(); rapidjson::Document::AllocatorType& allocator = doc->GetAllocator(); unsigned int packetID = session.packetId++; @@ -20,6 +18,23 @@ int create_method_json(interactive_session_internal& session, const std::string& doc->AddMember(RPC_DISCARD, discard, allocator); doc->AddMember(RPC_SEQUENCE, session.sequenceId, allocator); + if (nullptr != id) + { + *id = packetID; + } + + return MIXER_OK; +} + +int create_method_json(on_get_params getParams, std::shared_ptr& methodDoc) +{ + std::shared_ptr doc(nullptr == methodDoc ? std::make_shared() : methodDoc); + if (!doc->IsObject()) { + doc->SetObject(); + } + + rapidjson::Document::AllocatorType& allocator = doc->GetAllocator(); + // Get the parameters from the caller. rapidjson::Value params(rapidjson::kObjectType); if (getParams) @@ -28,10 +43,6 @@ int create_method_json(interactive_session_internal& session, const std::string& } doc->AddMember(RPC_PARAMS, params, allocator); - if (nullptr != id) - { - *id = packetID; - } methodDoc = doc; return MIXER_OK; } @@ -39,7 +50,8 @@ int create_method_json(interactive_session_internal& session, const std::string& int send_method(interactive_session_internal& session, const std::string& method, on_get_params getParams, bool discard, unsigned int* id) { std::shared_ptr methodDoc; - RETURN_IF_FAILED(create_method_json(session, method, getParams, discard, id, methodDoc)); + RETURN_IF_FAILED(create_method_json(getParams, methodDoc)); + RETURN_IF_FAILED(set_packet_info(session, method, discard, id, methodDoc)); // Synchronize access to the websocket. std::string methodJson = jsonStringify(*methodDoc); @@ -51,9 +63,22 @@ int send_method(interactive_session_internal& session, const std::string& method int queue_method(interactive_session_internal& session, const std::string& method, on_get_params getParams, method_handler onReply) { - std::shared_ptr methodDoc; + std::shared_ptr methodDoc(std::make_shared()); + return queue_method(session, method, getParams, onReply, methodDoc); +} + +int queue_method(interactive_session_internal& session, const std::string& method, on_get_params getParams, method_handler onReply, std::shared_ptr& methodDoc) +{ + std::shared_ptr doc(methodDoc); + RETURN_IF_FAILED(create_method_json(getParams, methodDoc)); + return queue_method_prebuilt(session, method, onReply, doc); +} + +int queue_method_prebuilt(interactive_session_internal& session, const std::string& method, method_handler onReply, std::shared_ptr& methodDoc) +{ + std::shared_ptr doc(methodDoc); unsigned int packetId = 0; - RETURN_IF_FAILED(create_method_json(session, method, getParams, nullptr == onReply, &packetId, methodDoc)); + RETURN_IF_FAILED(set_packet_info(session, method, nullptr == onReply, &packetId, methodDoc)); DEBUG_TRACE(std::string("Queueing method: ") + jsonStringify(*methodDoc)); if (onReply) { @@ -396,7 +421,6 @@ int handle_control_changed(interactive_session_internal& session, rapidjson::Doc 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) { diff --git a/source/internal/interactive_session.h b/source/internal/interactive_session.h index 308ce6a..f485790 100644 --- a/source/internal/interactive_session.h +++ b/source/internal/interactive_session.h @@ -127,6 +127,8 @@ typedef std::function& methodDoc); +int queue_method_prebuilt(interactive_session_internal& session, const std::string& method, method_handler onReply, std::shared_ptr& methodDoc); int receive_reply(interactive_session_internal& session, unsigned int id, std::shared_ptr& replyPtr, unsigned int timeoutMs = 5000); int cache_groups(interactive_session_internal& session); @@ -157,6 +159,8 @@ int check_reply_errors(interactive_session_internal& session, rapidjson::Documen #define RPC_ERROR_PATH "path" #define RPC_DISABLED "disabled" #define RPC_SEQUENCE "seq" +#define RPC_PRIORITY "priority" +#define RPC_PRIORITY_DEFAULT 0 // RPC methods and replies #define RPC_METHOD_HELLO "hello"