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"