Skip to content
This repository has been archived by the owner on Jul 16, 2020. It is now read-only.

Commit

Permalink
Refactor connection (#79)
Browse files Browse the repository at this point in the history
* Split connection out of open session. Need to handle method calls with disconnected state and test connection error handling.

* Connection state checking

* Fix race in Xbox/UWP connection as messages can come in before the ConnectAsync callback finishes executing.

* Error handling samples
  • Loading branch information
JoshSnider authored Jun 2, 2018
1 parent 8d6313d commit 39fc8d4
Show file tree
Hide file tree
Showing 11 changed files with 365 additions and 114 deletions.
133 changes: 109 additions & 24 deletions Tests/Tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using namespace Microsoft::VisualStudio::CppUnitTestFramework;

#define ASSERT_NOERR(x) do {err = x; Assert::IsTrue(0 == err); if (err) return;} while (0)
#define ASSERT_ERR(x, y) do {err = y; Assert::IsTrue(x == err);} while (0)
#define ASSERT_RETERR(x) do {err = x; Assert::IsTrue(0 == err); if (err) return err;} while (0)

namespace MixerTests
Expand Down Expand Up @@ -394,9 +395,10 @@ void handle_participants_changed(void* context, interactive_session session, int
Logger::WriteMessage(s.str().c_str());
}

void handle_error(void* context, interactive_session session, int errorCode, const char* errorMessage, size_t errorMessageLength)
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());
Assert::Fail(L"Error.");
}

void handle_unhandled_method(void* context, interactive_session session, const char* methodJson, size_t methodJsonLength)
Expand Down Expand Up @@ -535,7 +537,9 @@ TEST_CLASS(Tests)

interactive_session session;
Logger::WriteMessage("Connecting...");
ASSERT_NOERR(interactive_open_session(auth.c_str(), versionId.c_str(), shareCode.c_str(), true, &session));
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));

// Simulate 60 frames/sec for 1 seconds.
const int fps = 60;
Expand Down Expand Up @@ -568,7 +572,9 @@ TEST_CLASS(Tests)

interactive_session session;
Logger::WriteMessage("Connecting...");
ASSERT_NOERR(interactive_open_session(auth.c_str(), versionId.c_str(), shareCode.c_str(), true, &session));
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));

// Simulate 60 frames/sec for 1 seconds.
const int fps = 60;
Expand Down Expand Up @@ -599,18 +605,20 @@ TEST_CLASS(Tests)
ASSERT_NOERR(do_auth(clientId, "", auth));

interactive_session session;
ASSERT_NOERR(interactive_open_session(auth.c_str(), versionId.c_str(), shareCode.c_str(), true, &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));
ASSERT_NOERR(interactive_set_state_changed_handler(session, handle_state_changed));
ASSERT_NOERR(interactive_set_error_handler(session, handle_error));
ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert));
ASSERT_NOERR(interactive_set_participants_changed_handler(session, handle_participants_changed));
ASSERT_NOERR(interactive_set_unhandled_method_handler(session, handle_unhandled_method));
ASSERT_NOERR(interactive_set_transaction_complete_handler(session, handle_transaction_complete));

ASSERT_NOERR(interactive_set_bandwidth_throttle(session, throttle_participant_leave, 0, 0));
ASSERT_NOERR(interactive_set_bandwidth_throttle(session, throttle_input, 2 * 1024 * 1024 /* 2mb */, 512 * 1024 /* 512kbps */));

ASSERT_NOERR(interactive_connect(session, auth.c_str(), versionId.c_str(), shareCode.c_str(), true));

// Simulate 60 frames/sec
const int fps = 60;
const int seconds = 15;
Expand Down Expand Up @@ -640,7 +648,8 @@ TEST_CLASS(Tests)
ASSERT_NOERR(do_auth(clientId, "", auth));

interactive_session session;
ASSERT_NOERR(interactive_open_session(auth.c_str(), versionId.c_str(), shareCode.c_str(), false, &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)
{
Logger::WriteMessage(("Interactive state changed: " + std::to_string(prevState) + " -> " + std::to_string(newState)).c_str());
Expand All @@ -659,6 +668,8 @@ TEST_CLASS(Tests)
}
});

ASSERT_NOERR(interactive_connect(session, auth.c_str(), versionId.c_str(), shareCode.c_str(), false));

ASSERT_NOERR(interactive_set_ready(session, true));
ASSERT_NOERR(interactive_set_ready(session, false));

Expand Down Expand Up @@ -692,8 +703,10 @@ TEST_CLASS(Tests)

interactive_session session;
Logger::WriteMessage("Connecting...");
ASSERT_NOERR(interactive_open_session(auth.c_str(), versionId.c_str(), shareCode.c_str(), true, &session));
ASSERT_NOERR(interactive_open_session(&session));
ASSERT_NOERR(interactive_set_error_handler(session, handle_error_assert));
ASSERT_NOERR(interactive_set_participants_changed_handler(session, handle_participants_changed));
ASSERT_NOERR(interactive_connect(session, auth.c_str(), versionId.c_str(), shareCode.c_str(), true));

// Simulate 60fps
const int fps = 60;
Expand Down Expand Up @@ -756,21 +769,21 @@ TEST_CLASS(Tests)
Assert::IsTrue(0 == strcmp(participant->groupId, "Test group"));
});

ASSERT_NOERR(interactive_group_set_scene(session, "Test group", "Scene1"));

// 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));
}

ASSERT_NOERR(interactive_get_groups(session, [](void* context, interactive_session session, interactive_group* group)
{
if (0 == strcmp("Test group", group->id))
{
Assert::IsTrue(0 == strcmp("Scene1", group->sceneId));
}
ASSERT_NOERR(interactive_group_set_scene(session, "Test group", "Scene1"));

// 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));
}

ASSERT_NOERR(interactive_get_groups(session, [](void* context, interactive_session session, interactive_group* group)
{
if (0 == strcmp("Test group", group->id))
{
Assert::IsTrue(0 == strcmp("Scene1", group->sceneId));
}
}));

Logger::WriteMessage("Disconnecting...");
Expand All @@ -794,7 +807,9 @@ TEST_CLASS(Tests)

interactive_session session;
Logger::WriteMessage("Connecting...");
ASSERT_NOERR(interactive_open_session(auth.c_str(), versionId.c_str(), shareCode.c_str(), true, &session));
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;
Expand Down Expand Up @@ -838,5 +853,75 @@ TEST_CLASS(Tests)

Assert::IsTrue(0 == err);
}

TEST_METHOD(NotConnectedTest)
{
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));

interactive_session session;
ASSERT_NOERR(interactive_open_session(&session));

std::string controlId = "GiveHealth";
size_t size;
interactive_property_type type;
char prop[8];
size = 8;
int intProp;
long long int64Prop;
bool boolProp;
float floatProp;
unsigned int uInt;
unsigned long long ulong64;


ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_get_scenes(session, [](void* context, interactive_session session, interactive_scene* scene) {}));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_get_groups(session, [](void* context, interactive_session session, interactive_group* group) {}));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_get_participants(session, [](void* context, interactive_session session, interactive_participant* participant) {}));

ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_participant_get_user_id(session, "participant", &uInt));
size = 8;
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_participant_get_user_name(session, "participant", prop, &size));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_participant_get_level(session, "participant", &uInt));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_participant_get_last_input_at(session, "participant", &ulong64));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_participant_get_connected_at(session, "participant", &ulong64));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_participant_is_disabled(session, "participant", &boolProp));
size = 8;
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_participant_get_group(session, controlId.c_str(), prop, &size));

ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_property_count(session, controlId.c_str(), &size));
size = 8;
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_property_data(session, controlId.c_str(), 0, "id", &size, &type));
size = 8;
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_meta_property_count(session, controlId.c_str(), &size));
size = 8;
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_meta_property_data(session, controlId.c_str(), 0, "id", &size, &type));


ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_property_int(session, controlId.c_str(), "key", &intProp));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_property_int64(session, controlId.c_str(), "key", &int64Prop));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_property_bool(session, controlId.c_str(), "key", &boolProp));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_property_float(session, controlId.c_str(), "key", &floatProp));
size = 8;
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_property_string(session, controlId.c_str(), "key", prop, &size));

ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_meta_property_int(session, controlId.c_str(), "key", &intProp));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_meta_property_int64(session, controlId.c_str(), "key", &int64Prop));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_meta_property_bool(session, controlId.c_str(), "key", &boolProp));
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_meta_property_float(session, controlId.c_str(), "key", &floatProp));
size = 8;
ASSERT_ERR(MIXER_ERROR_NOT_CONNECTED, interactive_control_get_meta_property_string(session, controlId.c_str(), "key", prop, &size));

Logger::WriteMessage("Disconnecting...");
interactive_close_session(session);
}
};
}
16 changes: 15 additions & 1 deletion samples/InteractiveSample/InteractiveSample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ std::map<std::string, std::string> controlsByTransaction;
// Display an OAuth consent page to the user.
int authorize(std::string& authorization);

void handle_error(void* context, interactive_session session, int errorCode, const char* errorMessage, size_t errorMessageLength);

// Handle completed interactive transactions.
void handle_transaction_complete(void* context, interactive_session session, const char* transactionId, size_t transactionIdLength, unsigned int errorCode, const char* errorMessage, size_t errorMessageLength);

Expand All @@ -47,7 +49,11 @@ int main()

// Connect to the user's interactive channel, using the interactive project specified by the version ID.
interactive_session session;
err = interactive_open_session(authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, false, &session);
err = interactive_open_session(&session);
if (err) return err;

// Register a callback for errors.
err = interactive_set_error_handler(session, handle_error);
if (err) return err;

// Register a callback for button presses.
Expand All @@ -58,6 +64,9 @@ int main()
err = interactive_set_transaction_complete_handler(session, handle_transaction_complete);
if (err) return err;

err = interactive_connect(session, authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, false);
if (err) return err;

// Create a group for participants to view the joystick scene.
err = interactive_create_group(session, "JoystickGroup", "Joystick");
if (err) return err;
Expand Down Expand Up @@ -151,6 +160,11 @@ int get_participant_name(interactive_session session, const char* participantId,
return 0;
}

void handle_error(void* context, interactive_session session, int errorCode, const char* errorMessage, size_t errorMessageLength)
{
std::cerr << "Unexpected Mixer interactive error: " << errorMessage;
}

void handle_interactive_input(void* context, interactive_session session, const interactive_input* input)
{
// Get the participant's Mixer name to give them attribution.
Expand Down
34 changes: 31 additions & 3 deletions samples/InteractiveSampleUWP/InteractiveSampleUWP/App.xaml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
#define INTERACTIVE_ID "135704"
#define SHARE_CODE "xe7dpqd5"

#define MIXER_DEBUG 0

// Called ~60 times per second
int update(interactive_session session)
{
Expand Down Expand Up @@ -77,6 +79,12 @@ int authorize(std::string& authorization)
return 0;
}

void handle_error(void* context, interactive_session session, int errorCode, const char* errorMessage, size_t errorMessageLength)
{
std::string debugLine = "Mixer error " + std::to_string(errorCode) + ": " + errorMessage + "\r\n";
OutputDebugStringA(debugLine.c_str());
}

using namespace InteractiveSampleUWP;

using namespace Platform;
Expand Down Expand Up @@ -161,6 +169,17 @@ void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEvent
}

m_appIsRunning = true;

#if MIXER_DEBUG

interactive_config_debug(interactive_debug_trace, [](interactive_debug_level level, const char* dbgMessage, size_t dbgMessageSize)
{
std::string dbgLine = "[MIXER] " + std::string(dbgMessage, dbgMessageSize) + "\r\n";
OutputDebugStringA(dbgLine.c_str());
});

#endif

// Simulate game update loop. All callbacks will be called from this thread.
m_interactiveThread = std::make_unique<std::thread>(std::thread([&]
{
Expand All @@ -177,7 +196,10 @@ void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEvent

// Connect to the user's interactive channel, using the interactive project specified by the version ID.
interactive_session session;
err = interactive_open_session(authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, true, &session);
err = interactive_open_session(&session);
if (err) throw err;

err = interactive_set_error_handler(session, handle_error);
if (err) throw err;

err = interactive_set_session_context(session, &m_controlsByTransaction);
Expand All @@ -190,8 +212,11 @@ void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEvent
if ((input_type_key == input->type || input_type_click == input->type) && interactive_button_action_down == input->buttonData.action)
{
// Capture the transaction on button down to deduct sparks
controlsByTransaction[input->transactionId] = input->control.id;
interactive_capture_transaction(session, input->transactionId);
if (input->transactionIdLength > 0)
{
controlsByTransaction[input->transactionId] = input->control.id;
interactive_capture_transaction(session, input->transactionId);
}
}
});
if (err) throw err;
Expand All @@ -217,6 +242,9 @@ void App::OnLaunched(Windows::ApplicationModel::Activation::LaunchActivatedEvent
});
if (err) throw err;

err = interactive_connect(session, authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, true);
if (err) throw err;

while (m_appIsRunning)
{
int err = update(session);
Expand Down
16 changes: 15 additions & 1 deletion samples/InteractiveXboxSample/Game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ int GetXToken(std::string& token)
return S_OK;
}

void handle_error(void* context, interactive_session session, int errorCode, const char* errorMessage, size_t errorMessageLength)
{
(context);
(session);
(errorMessageLength);
std::string debugLine = "Mixer error " + std::to_string(errorCode) + ": " + errorMessage + "\r\n";
OutputDebugStringA(debugLine.c_str());
}

// Initialize the Direct3D resources required to run.
void Game::Initialize(IUnknown* window)
Expand Down Expand Up @@ -142,7 +150,10 @@ void Game::Initialize(IUnknown* window)
}

// Connect to the user's interactive channel, using the interactive project specified by the version ID.
err = interactive_open_session(authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, true, &m_interactiveSession);
err = interactive_open_session(&m_interactiveSession);
if (err) throw err;

err = interactive_set_error_handler(m_interactiveSession, handle_error);
if (err) throw err;

err = interactive_set_session_context(m_interactiveSession, this);
Expand Down Expand Up @@ -188,6 +199,9 @@ void Game::Initialize(IUnknown* window)

game->m_controlsById.erase(transactionId);
});

err = interactive_connect(m_interactiveSession, authorization.c_str(), INTERACTIVE_ID, SHARE_CODE, true);
if (err) throw err;
}

#pragma region Frame Update
Expand Down
Loading

0 comments on commit 39fc8d4

Please sign in to comment.