Skip to content
This repository was archived by the owner on Jul 16, 2020. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Tests/Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,27 @@ 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, 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());
}

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());
Expand Down
19 changes: 18 additions & 1 deletion source/interactivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -435,6 +441,11 @@ extern "C" {
/// </summary>
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);

/// <summary>
/// Callback when a control is updated.
/// </summary>
typedef void(*on_control_changed)(void* context, interactive_session session, interactive_control_change_type changeType, const interactive_control* control);

/// <summary>
/// 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.
/// </summary>
Expand Down Expand Up @@ -465,6 +476,11 @@ extern "C" {
/// </summary>
int interactive_set_transaction_complete_handler(interactive_session session, on_transaction_complete onTransactionComplete);

/// <summary>
/// Set the handler function for control updates. This function is called by your own thread during <c>interactive_run</c>
/// </summary>
int interactive_set_control_changed_handler(interactive_session session, on_control_changed onControlUpdated);

/// <summary>
/// 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 <c>interactive_run</c>
/// </summary>
Expand Down Expand Up @@ -607,7 +623,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;
/** @} */

Expand Down
114 changes: 112 additions & 2 deletions source/internal/interactive_control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,118 @@ 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();
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<std::shared_mutex> 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.scenes.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())
{
readLock.unlock();
std::unique_lock<std::shared_mutex> writeLock(session.scenesMutex);
controls = &scene->AddMember(RPC_PARAM_CONTROLS, rapidjson::Value(rapidjson::kArrayType), allocator);
writeLock.unlock();
readLock.lock();
}
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)
{
std::unique_lock<std::shared_mutex> writeLock(session.scenesMutex);
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;
}

int delete_cached_control(interactive_session_internal& session, const char* sceneId, interactive_control& control)
{
std::shared_lock<std::shared_mutex> 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;
Expand Down Expand Up @@ -298,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;
Expand Down
24 changes: 23 additions & 1 deletion source/internal/interactive_scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this nullptr check because SCENE_ID wouldn't exist for some reason? If that's at all possible you should actually check if the property exists first using HasMember.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry - confusing names. sceneId is an argument to a function, thisSceneId is a local variable within the for loop.

Purpose is that you can optionally specify a specific scene to update the control pointers for instead of blindly updating them for all scenes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also with regards to the HasMember check it depends how defensive we want to be - in other places in the code we're trusting these fields to be there (and according to the current protocol they should) so I don't check that the member exists here because this value only gets updated with an object from the socket

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right yeah I misread that.
Great, if the protocol should have the field we don't need an extra check.

{
if (0 != strcmp(sceneId, thisSceneId))
{
continue;
}
}

auto controlsArray = scene.FindMember(RPC_PARAM_CONTROLS);
if (controlsArray != scene.MemberEnd() && controlsArray->value.IsArray())
{
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
61 changes: 61 additions & 0 deletions source/internal/interactive_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,52 @@ 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;
memset(&control, 0, sizeof(interactive_control));
parse_control(*itr, control);

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, changeType, &control);
}
}

return MIXER_OK;
}

int route_method(interactive_session_internal& session, rapidjson::Document& doc)
{
std::string method = doc[RPC_METHOD].GetString();
Expand Down Expand Up @@ -418,6 +464,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);
}

Expand Down Expand Up @@ -1004,6 +1051,20 @@ int interactive_set_transaction_complete_handler(interactive_session session, on
return MIXER_OK;
}


int interactive_set_control_changed_handler(interactive_session session, on_control_changed onControlChanged)
{
if (nullptr == session)
{
return MIXER_ERROR_INVALID_POINTER;
}

interactive_session_internal* sessionInternal = reinterpret_cast<interactive_session_internal*>(session);
sessionInternal->onControlChanged = onControlChanged;

return MIXER_OK;
}

int interactive_set_unhandled_method_handler(interactive_session session, on_unhandled_method onUnhandledMethod)
{
if (nullptr == session)
Expand Down
6 changes: 6 additions & 0 deletions source/internal/interactive_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ struct interactive_session_internal
on_error onError;
on_state_changed onStateChanged;
on_participants_changed onParticipantsChanged;
on_control_changed onControlChanged;
on_transaction_complete onTransactionComplete;
on_unhandled_method onUnhandledMethod;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -160,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"
Expand Down
2 changes: 1 addition & 1 deletion source/internal/interactive_session_internal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down