diff --git a/gamelink_single.hpp b/gamelink_single.hpp index cda251c..a16283f 100644 --- a/gamelink_single.hpp +++ b/gamelink_single.hpp @@ -27206,6 +27206,11 @@ namespace gamelink /// if endsAt is non-zero, this field has no effect. int64_t endsIn; + /// Arbitrary user data field. + /// For legacy reasons, this isn't part of the config schema, but rather + /// promoted to the request body when used as part of CreatePollWithConfigurationRequest + nlohmann::json userData; + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_9(PollConfiguration, "userIDVoting", userIdVoting, "distinctOptionsPerUser", distinctOptionsPerUser, @@ -27213,8 +27218,8 @@ namespace gamelink "votesPerOption", votesPerOption, "disabled", disabled, "startsAt", startsAt, - "endsAt", endsAt, - "startsIn", startsIn, + "endsAt", endsAt, + "startsIn", startsIn, "endsIn", endsIn); }; @@ -27260,35 +27265,18 @@ namespace gamelink /// A list of answers to the prompt. Maximum 64. std::vector options; + /// Configuration information PollConfiguration configuration; - MUXY_GAMELINK_SERIALIZE_INTRUSIVE_4(CreatePollWithConfigurationRequestBody, "poll_id", pollId, "prompt", prompt, "options", options, "config", configuration); - }; - - template - struct MUXY_GAMELINK_API CreatePollWithUserDataRequestBody - { - /// The Poll ID to create - string pollId; + // Arbitrary user data + nlohmann::json userData; - /// The prompt for the poll. - string prompt; - - /// An array of options for the poll - std::vector options; - - /// Arbitrary serializable user data. - T userData; - - MUXY_GAMELINK_SERIALIZE_INTRUSIVE_4(CreatePollWithUserDataRequestBody, - "poll_id", - pollId, - "prompt", - prompt, - "options", - options, - "user_data", - userData); + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_5(CreatePollWithConfigurationRequestBody, + "poll_id", pollId, + "prompt", prompt, + "options", options, + "config", configuration, + "user_data", userData); }; struct MUXY_GAMELINK_API PollResponseBody @@ -27417,29 +27405,6 @@ namespace gamelink CreatePollWithConfigurationRequest(const string& pollId, const string& prompt, const PollConfiguration& config, const std::vector& options); }; - template - struct CreatePollWithUserDataRequest : SendEnvelope> - { - /// Creates a CreatePoll request, but with user data. - /// @param[in] pollId The ID of the poll to create. This can overwrite existing polls if the same - /// id is specified. - /// @param[in] prompt The prompt for the poll to create. - /// @param[in] options vector of options for the poll. - /// @param[in] userData Arbitrary user data to attach to this poll. This type should be serializable. The fully marshalled - /// size of this type should be under 1kb. - CreatePollWithUserDataRequest(const string& pollId, const string& prompt, const std::vector& options, const T& userData) - { - this->action = string("create"); - this->params.target = string("poll"); - - this->data.pollId = pollId; - this->data.prompt = prompt; - this->data.options = options; - - this->data.userData = userData; - } - }; - struct MUXY_GAMELINK_API SubscribePollRequest : SendEnvelope { /// Creates a SubscribePollRequest. @@ -29910,6 +29875,9 @@ namespace gateway // to StopPoll int32_t Duration = 0; + // Arbitrary user data to send. Should be small. + nlohmann::json UserData; + // Called regularly as poll results are streamed in from the server std::function OnUpdate; @@ -29931,7 +29899,7 @@ namespace gateway Hidden = 2, }; - static const int32_t ACTION_INFINITE_USES = -1; + static const int32_t ACTION_INFINITE_USES = 0xFFFF; struct Action { string ID; @@ -30472,6 +30440,7 @@ namespace gamelink data.prompt = prompt; data.options = options; data.configuration = config; + data.userData = config.userData; } SubscribePollRequest::SubscribePollRequest(const string& pollId) @@ -31255,14 +31224,14 @@ namespace gamelink if (options.size() > gamelink::limits::POLL_MAX_OPTIONS) { - snprintf(buffer, BUF_SIZE, "Poll options size %zu is larger than the max allowed %u", options.size(), gamelink::limits::POLL_MAX_OPTIONS); + snprintf(buffer, BUF_SIZE, "Poll options size %u is larger than the max allowed %u", static_cast(options.size()), gamelink::limits::POLL_MAX_OPTIONS); InvokeOnDebugMessage(buffer); return false; } if (prompt.size() > gamelink::limits::POLL_MAX_PROMPT_SIZE) { - snprintf(buffer, BUF_SIZE, "Poll prompt size %zu is larger than the max allowed %u", prompt.size(), gamelink::limits::POLL_MAX_PROMPT_SIZE); + snprintf(buffer, BUF_SIZE, "Poll prompt size %u is larger than the max allowed %u", static_cast(prompt.size()), gamelink::limits::POLL_MAX_PROMPT_SIZE); InvokeOnDebugMessage(buffer); return false; } @@ -31271,7 +31240,7 @@ namespace gamelink { if (opt.size() > gamelink::limits::POLL_MAX_OPTION_NAME_SIZE) { - snprintf(buffer, BUF_SIZE, "Poll option name size %zu is larger than the max allowed %u", opt.size(), gamelink::limits::POLL_MAX_OPTION_NAME_SIZE); + snprintf(buffer, BUF_SIZE, "Poll option name size %u is larger than the max allowed %u", static_cast(opt.size()), gamelink::limits::POLL_MAX_OPTION_NAME_SIZE); InvokeOnDebugMessage(buffer); return false; } @@ -31287,13 +31256,13 @@ namespace gamelink if (meta.game_name.size() > gamelink::limits::METADATA_MAX_GAME_NAME_SIZE) { - snprintf(buffer, BUF_SIZE, "Game Metadata game name size %zu is larger than the max allowed %u", meta.game_name.size(), gamelink::limits::METADATA_MAX_GAME_NAME_SIZE); + snprintf(buffer, BUF_SIZE, "Game Metadata game name size %u is larger than the max allowed %u", static_cast(meta.game_name.size()), gamelink::limits::METADATA_MAX_GAME_NAME_SIZE); InvokeOnDebugMessage(buffer); return false; } if (meta.game_logo.size() > gamelink::limits::METADATA_MAX_GAME_LOGO_SIZE) { - snprintf(buffer, BUF_SIZE, "Game Metadata game logo size %zu is larger than the max allowed %u", meta.game_logo.size(), gamelink::limits::METADATA_MAX_GAME_LOGO_SIZE); + snprintf(buffer, BUF_SIZE, "Game Metadata game logo size %u is larger than the max allowed %u", static_cast(meta.game_logo.size()), gamelink::limits::METADATA_MAX_GAME_LOGO_SIZE); InvokeOnDebugMessage(buffer); return false; } @@ -31919,11 +31888,11 @@ namespace gamelink } return RunPoll( - pollId, - prompt, - config, + pollId, + prompt, + config, begin, end, - std::move(onUpdateCallback), + std::move(onUpdateCallback), std::move(onFinishCallback) ); } @@ -31938,15 +31907,15 @@ namespace gamelink void* user) { return RunPoll( - pollId, - prompt, - config, - optionsBegin, - optionsEnd, + pollId, + prompt, + config, + optionsBegin, + optionsEnd, [=](const schema::PollUpdateResponse& resp) { onUpdateCallback(user, resp); - }, + }, [=](const schema::PollUpdateResponse& resp) { onFinishCallback(user, resp); @@ -32728,6 +32697,8 @@ namespace gateway config.endsIn = cfg.Duration; } + config.userData = cfg.UserData; + Base.RunPoll( id, cfg.Prompt, diff --git a/include/gamelink.h b/include/gamelink.h index 0defdea..d0308b8 100644 --- a/include/gamelink.h +++ b/include/gamelink.h @@ -1023,7 +1023,6 @@ namespace gamelink /// @return RequestId of the generated request RequestId CreatePoll(const string& pollId, const string& prompt, const string* optionsBegin, const string* optionsEnd); - /// Queues a request to create a poll with configuration options. /// /// @param[in] pollId The Poll ID to create diff --git a/include/gateway.h b/include/gateway.h index 64107f1..89b3a28 100644 --- a/include/gateway.h +++ b/include/gateway.h @@ -106,6 +106,9 @@ namespace gateway // to StopPoll int32_t Duration = 0; + // Arbitrary user data to send. Should be small. + nlohmann::json UserData; + // Called regularly as poll results are streamed in from the server std::function OnUpdate; @@ -127,7 +130,7 @@ namespace gateway Hidden = 2, }; - static const int32_t ACTION_INFINITE_USES = -1; + static const int32_t ACTION_INFINITE_USES = 0xFFFF; struct Action { string ID; diff --git a/schema/poll.cpp b/schema/poll.cpp index 8791a43..02a2dff 100644 --- a/schema/poll.cpp +++ b/schema/poll.cpp @@ -51,6 +51,7 @@ namespace gamelink data.prompt = prompt; data.options = options; data.configuration = config; + data.userData = config.userData; } SubscribePollRequest::SubscribePollRequest(const string& pollId) diff --git a/schema/poll.h b/schema/poll.h index 42be511..a7f5bc0 100644 --- a/schema/poll.h +++ b/schema/poll.h @@ -51,6 +51,11 @@ namespace gamelink /// if endsAt is non-zero, this field has no effect. int64_t endsIn; + /// Arbitrary user data field. + /// For legacy reasons, this isn't part of the config schema, but rather + /// promoted to the request body when used as part of CreatePollWithConfigurationRequest + nlohmann::json userData; + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_9(PollConfiguration, "userIDVoting", userIdVoting, "distinctOptionsPerUser", distinctOptionsPerUser, @@ -58,8 +63,8 @@ namespace gamelink "votesPerOption", votesPerOption, "disabled", disabled, "startsAt", startsAt, - "endsAt", endsAt, - "startsIn", startsIn, + "endsAt", endsAt, + "startsIn", startsIn, "endsIn", endsIn); }; @@ -105,35 +110,18 @@ namespace gamelink /// A list of answers to the prompt. Maximum 64. std::vector options; + /// Configuration information PollConfiguration configuration; - MUXY_GAMELINK_SERIALIZE_INTRUSIVE_4(CreatePollWithConfigurationRequestBody, "poll_id", pollId, "prompt", prompt, "options", options, "config", configuration); - }; - - template - struct MUXY_GAMELINK_API CreatePollWithUserDataRequestBody - { - /// The Poll ID to create - string pollId; - - /// The prompt for the poll. - string prompt; - - /// An array of options for the poll - std::vector options; + // Arbitrary user data + nlohmann::json userData; - /// Arbitrary serializable user data. - T userData; - - MUXY_GAMELINK_SERIALIZE_INTRUSIVE_4(CreatePollWithUserDataRequestBody, - "poll_id", - pollId, - "prompt", - prompt, - "options", - options, - "user_data", - userData); + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_5(CreatePollWithConfigurationRequestBody, + "poll_id", pollId, + "prompt", prompt, + "options", options, + "config", configuration, + "user_data", userData); }; struct MUXY_GAMELINK_API PollResponseBody @@ -262,29 +250,6 @@ namespace gamelink CreatePollWithConfigurationRequest(const string& pollId, const string& prompt, const PollConfiguration& config, const std::vector& options); }; - template - struct CreatePollWithUserDataRequest : SendEnvelope> - { - /// Creates a CreatePoll request, but with user data. - /// @param[in] pollId The ID of the poll to create. This can overwrite existing polls if the same - /// id is specified. - /// @param[in] prompt The prompt for the poll to create. - /// @param[in] options vector of options for the poll. - /// @param[in] userData Arbitrary user data to attach to this poll. This type should be serializable. The fully marshalled - /// size of this type should be under 1kb. - CreatePollWithUserDataRequest(const string& pollId, const string& prompt, const std::vector& options, const T& userData) - { - this->action = string("create"); - this->params.target = string("poll"); - - this->data.pollId = pollId; - this->data.prompt = prompt; - this->data.options = options; - - this->data.userData = userData; - } - }; - struct MUXY_GAMELINK_API SubscribePollRequest : SendEnvelope { /// Creates a SubscribePollRequest. diff --git a/src/gamelink.cpp b/src/gamelink.cpp index 503178e..576744e 100644 --- a/src/gamelink.cpp +++ b/src/gamelink.cpp @@ -662,14 +662,14 @@ namespace gamelink if (options.size() > gamelink::limits::POLL_MAX_OPTIONS) { - snprintf(buffer, BUF_SIZE, "Poll options size %zu is larger than the max allowed %u", options.size(), gamelink::limits::POLL_MAX_OPTIONS); + snprintf(buffer, BUF_SIZE, "Poll options size %u is larger than the max allowed %u", static_cast(options.size()), gamelink::limits::POLL_MAX_OPTIONS); InvokeOnDebugMessage(buffer); return false; } if (prompt.size() > gamelink::limits::POLL_MAX_PROMPT_SIZE) { - snprintf(buffer, BUF_SIZE, "Poll prompt size %zu is larger than the max allowed %u", prompt.size(), gamelink::limits::POLL_MAX_PROMPT_SIZE); + snprintf(buffer, BUF_SIZE, "Poll prompt size %u is larger than the max allowed %u", static_cast(prompt.size()), gamelink::limits::POLL_MAX_PROMPT_SIZE); InvokeOnDebugMessage(buffer); return false; } @@ -678,7 +678,7 @@ namespace gamelink { if (opt.size() > gamelink::limits::POLL_MAX_OPTION_NAME_SIZE) { - snprintf(buffer, BUF_SIZE, "Poll option name size %zu is larger than the max allowed %u", opt.size(), gamelink::limits::POLL_MAX_OPTION_NAME_SIZE); + snprintf(buffer, BUF_SIZE, "Poll option name size %u is larger than the max allowed %u", static_cast(opt.size()), gamelink::limits::POLL_MAX_OPTION_NAME_SIZE); InvokeOnDebugMessage(buffer); return false; } @@ -694,13 +694,13 @@ namespace gamelink if (meta.game_name.size() > gamelink::limits::METADATA_MAX_GAME_NAME_SIZE) { - snprintf(buffer, BUF_SIZE, "Game Metadata game name size %zu is larger than the max allowed %u", meta.game_name.size(), gamelink::limits::METADATA_MAX_GAME_NAME_SIZE); + snprintf(buffer, BUF_SIZE, "Game Metadata game name size %u is larger than the max allowed %u", static_cast(meta.game_name.size()), gamelink::limits::METADATA_MAX_GAME_NAME_SIZE); InvokeOnDebugMessage(buffer); return false; } if (meta.game_logo.size() > gamelink::limits::METADATA_MAX_GAME_LOGO_SIZE) { - snprintf(buffer, BUF_SIZE, "Game Metadata game logo size %zu is larger than the max allowed %u", meta.game_logo.size(), gamelink::limits::METADATA_MAX_GAME_LOGO_SIZE); + snprintf(buffer, BUF_SIZE, "Game Metadata game logo size %u is larger than the max allowed %u", static_cast(meta.game_logo.size()), gamelink::limits::METADATA_MAX_GAME_LOGO_SIZE); InvokeOnDebugMessage(buffer); return false; } diff --git a/src/gamelink_poll.cpp b/src/gamelink_poll.cpp index 6f73917..5575b51 100644 --- a/src/gamelink_poll.cpp +++ b/src/gamelink_poll.cpp @@ -187,11 +187,11 @@ namespace gamelink } return RunPoll( - pollId, - prompt, - config, + pollId, + prompt, + config, begin, end, - std::move(onUpdateCallback), + std::move(onUpdateCallback), std::move(onFinishCallback) ); } @@ -206,15 +206,15 @@ namespace gamelink void* user) { return RunPoll( - pollId, - prompt, - config, - optionsBegin, - optionsEnd, + pollId, + prompt, + config, + optionsBegin, + optionsEnd, [=](const schema::PollUpdateResponse& resp) { onUpdateCallback(user, resp); - }, + }, [=](const schema::PollUpdateResponse& resp) { onFinishCallback(user, resp); diff --git a/src/gateway.cpp b/src/gateway.cpp index ee66533..f84f219 100644 --- a/src/gateway.cpp +++ b/src/gateway.cpp @@ -168,6 +168,8 @@ namespace gateway config.endsIn = cfg.Duration; } + config.userData = cfg.UserData; + Base.RunPoll( id, cfg.Prompt, diff --git a/test/gateway_actions.cpp b/test/gateway_actions.cpp index 06d46e9..911b53c 100644 --- a/test/gateway_actions.cpp +++ b/test/gateway_actions.cpp @@ -31,7 +31,7 @@ TEST_CASE("Set action list", "[gateway][actions]") "data": { "actions": [{ "category": "help", - "count": -1, + "count": 65535, "description": "Draw three cards", "icon": "mdi:draw", "id": "draw3", diff --git a/test/gateway_poll.cpp b/test/gateway_poll.cpp index 74ecbc6..775fa58 100644 --- a/test/gateway_poll.cpp +++ b/test/gateway_poll.cpp @@ -9,9 +9,9 @@ TEST_CASE("Run Poll in Gateway", "[gateway][poll]") gateway::SDK sdk("This is a game"); gateway::PollConfiguration config; - config.Prompt = "Pizza toppings"; + config.Prompt = "Pizza toppings"; config.Options = { - "Pepperoni", + "Pepperoni", "Cheese" }; @@ -54,12 +54,18 @@ TEST_CASE("Run Poll in Gateway with duration", "[gateway][poll]") gateway::SDK sdk("This is a game"); gateway::PollConfiguration config; - config.Prompt = "Pizza toppings"; + config.Prompt = "Pizza toppings"; config.Options = { - "Pepperoni", + "Pepperoni", "Cheese" }; + // Use std::map to demostrate. + // In real user-code, this should be a type marked up with the MUXY_SERIALIZE macros. + config.UserData = std::map{ + {"SauceType", "Red"} + }; + config.Duration = 20; sdk.StartPoll(config); @@ -85,15 +91,15 @@ TEST_CASE("Run Poll in Gateway with duration", "[gateway][poll]") sdk.ReceiveMessage(msg2, strlen(msg2)); const char* expected = R"({ - "action": "create", + "action": "create", "params": { "request_id": 65535, "target": "poll" }, "data": { "poll_id": "default", - "prompt": "Pizza toppings", - "options": ["Pepperoni", "Cheese"], + "prompt": "Pizza toppings", + "options": ["Pepperoni", "Cheese"], "config": { "disabled": false, "distinctOptionsPerUser": 1, @@ -104,6 +110,9 @@ TEST_CASE("Run Poll in Gateway with duration", "[gateway][poll]") "totalVotesPerUser": 1, "userIDVoting": true, "votesPerOption": 1 + }, + "user_data": { + "SauceType": "Red" } } })"; @@ -126,10 +135,10 @@ TEST_CASE("Run Poll in Gateway, C", "[gateway][poll][c]") MGW_SDK sdk = MGW_MakeSDK("gameid"); MGW_PollConfiguration cfg; - cfg.Prompt = "Pizza toppings"; + cfg.Prompt = "Pizza toppings"; const char* options[] = { - "Pepperoni", + "Pepperoni", "Cheese" }; @@ -146,7 +155,7 @@ TEST_CASE("Run Poll in Gateway, C", "[gateway][poll][c]") }; MGW_SDK_StartPoll(sdk, cfg); - + REQUIRE(MGW_SDK_HasPayloads(sdk)); const char* finishMessage = R"({ "meta": { diff --git a/test/poll.cpp b/test/poll.cpp index c2a2511..36d0a2d 100644 --- a/test/poll.cpp +++ b/test/poll.cpp @@ -4,6 +4,15 @@ #include "gamelink.h" #include +struct ArbitraryUserData +{ + double value; + + MUXY_GAMELINK_SERIALIZE_INTRUSIVE_1(ArbitraryUserData, + "value", value); +}; + + namespace gs = gamelink::schema; TEST_CASE("Poll Creation", "[poll][creation]") { @@ -22,13 +31,24 @@ TEST_CASE("Poll Creation", "[poll][creation]") } })"); - // Create poll with user data - std::map userData; - userData["showTitle"] = "true"; - userData["title"] = "Yes or No?"; + gamelink::PollConfiguration cfg; + cfg.disabled = true; + cfg.distinctOptionsPerUser = 2; + cfg.endsAt = 3; + cfg.endsIn = 4; + cfg.startsAt = 5; + cfg.startsIn = 6; + cfg.totalVotesPerUser = 7; + cfg.userIdVoting = true; + cfg.votesPerOption = 8; + + ArbitraryUserData userData; + userData.value = 123; + + cfg.userData = userData; - gs::CreatePollWithUserDataRequest> req2("poll-id", "Yes or No?", {"Yes", "No"}, userData); - SerializeEqual(req2, R"({ + gs::CreatePollWithConfigurationRequest withConfiguration("poll-id", "Yes or No?", cfg, {"Yes", "No"}); + SerializeEqual(withConfiguration, R"({ "action": "create", "params": { "request_id": 65535, @@ -38,9 +58,19 @@ TEST_CASE("Poll Creation", "[poll][creation]") "poll_id": "poll-id", "prompt": "Yes or No?", "options": ["Yes", "No"], + "config": { + "disabled": true, + "distinctOptionsPerUser": 2, + "endsAt": 3, + "endsIn": 4, + "startsAt": 5, + "startsIn": 6, + "totalVotesPerUser": 7, + "userIDVoting": true, + "votesPerOption": 8 + }, "user_data": { - "showTitle": "true", - "title": "Yes or No?" + "value": 123 } } })"); @@ -175,6 +205,10 @@ TEST_CASE("SDK Poll Creation With Options", "[sdk][poll][creation]") config.totalVotesPerUser = 1000; config.disabled = true; + ArbitraryUserData userData; + userData.value = 42.0; + config.userData = userData; + sdk.CreatePollWithConfiguration("test-poll", "Me or Them?", config, {"Me", "Them"}); REQUIRE(sdk.HasPayloads()); @@ -194,6 +228,9 @@ TEST_CASE("SDK Poll Creation With Options", "[sdk][poll][creation]") "endsAt": 20, "endsIn": 0, "startsIn": 0 + }, + "user_data": { + "value": 42 } }, "params":{ @@ -220,6 +257,9 @@ TEST_CASE("SDK Poll Creation With Options", "[sdk][poll][creation]") "endsAt": 20, "endsIn": 0, "startsIn": 0 + }, + "user_data": { + "value": 42 } }, "params":{ @@ -514,11 +554,11 @@ TEST_CASE("SDK Poll other operations", "[sdk][poll]") sdk.StopPoll("pizza-toppings"); validateSinglePayload(sdk, R"({ - "action": "reconfigure", + "action": "reconfigure", "params": { "request_id": 65535, "target": "poll" - }, + }, "data": { "poll_id": "pizza-toppings", "config": { @@ -529,13 +569,13 @@ TEST_CASE("SDK Poll other operations", "[sdk][poll]") sdk.SetPollDisabled("pizza-toppings", true); validateSinglePayload(sdk, R"({ - "action": "reconfigure", + "action": "reconfigure", "params": { "request_id": 65535, "target": "poll" - }, + }, "data": { - "poll_id": "pizza-toppings", + "poll_id": "pizza-toppings", "config": { "disabled": true } @@ -544,13 +584,13 @@ TEST_CASE("SDK Poll other operations", "[sdk][poll]") sdk.SetPollDisabled("pizza-toppings", false); validateSinglePayload(sdk, R"({ - "action": "reconfigure", + "action": "reconfigure", "params": { "request_id": 65535, "target": "poll" - }, + }, "data": { - "poll_id": "pizza-toppings", + "poll_id": "pizza-toppings", "config": { "disabled": false }