13 Commits

Author SHA1 Message Date
64027b336f Adding user object to READY event (#159)
* Pass the READY event data down in onConnect

* Changes made for UE4 and Unity wrappers

* Changing object name from joinRequest to DiscordUser
2018-04-16 10:25:44 -07:00
2ce9fe068b Syntax change to avoid gcc 4.8 segfaulting (#162) 2018-04-04 10:00:24 -07:00
be8a8e9380 ACTUALLY register the handlers on init 2018-03-29 14:33:46 -07:00
c70acbe7d1 Fix Unity buildhelper for linux
- Fixes #157
2018-03-26 10:56:05 -07:00
d97e6b48ed Note to install cmake
- Fixes #149
2018-03-26 10:37:03 -07:00
087282cd4b Dynamic Event Handler Registration (#135)
- Discord_RegisterHandlers() exported
- C# wrapper updated
- Dynamically sub/unsub to events
- Better mutex locking, for safety!
2018-03-23 10:25:28 -07:00
7e5d57e6fd Update cert to use new name (#158) 2018-03-23 10:18:46 -07:00
f3bd411b99 Update README.md 2018-03-19 10:29:11 -07:00
8e0c7848a6 Added more hard mode documentation (#148)
* ACTIVITY_JOIN_REQUEST does not have a secret

There is no secret passed to a Join Request

* Added how to respond

* Update hard-mode.md

* Added some tips

Added some helpful tips. Plan to add more as I go along.
2018-03-19 10:27:29 -07:00
e7f9396807 Fix a typo in send-presence.c (#144) 2018-03-13 16:58:14 -07:00
ad0b844672 Changed CMAKE_SOURCE_DIR to CMAKE_CURRENT_SOURCE_DIR (#143)
Helps with submodule implementations
2018-03-13 13:13:38 -07:00
d279c24c6a Add advapi32 to linked libraries (#140)
Required by `RegCreateKeyExW` and others.
2018-03-13 13:00:47 -07:00
d9caf72e9a Add missing timestamps in UE4 example
Fixes #137
2018-03-06 09:51:31 -08:00
19 changed files with 264 additions and 92 deletions

View File

@ -28,23 +28,23 @@ endif(CLANG_FORMAT_CMD)
# thirdparty stuff
execute_process(
COMMAND mkdir ${CMAKE_SOURCE_DIR}/thirdparty
COMMAND mkdir ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
ERROR_QUIET
)
find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
find_file(RAPIDJSONTEST NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
if (NOT RAPIDJSONTEST)
message("no rapidjson, download")
set(RJ_TAR_FILE ${CMAKE_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz)
set(RJ_TAR_FILE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/v1.1.0.tar.gz)
file(DOWNLOAD https://github.com/miloyip/rapidjson/archive/v1.1.0.tar.gz ${RJ_TAR_FILE})
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf ${RJ_TAR_FILE}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/thirdparty
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty
)
file(REMOVE ${RJ_TAR_FILE})
endif(NOT RAPIDJSONTEST)
find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
find_file(RAPIDJSON NAMES rapidjson rapidjson-1.1.0 PATHS ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty CMAKE_FIND_ROOT_PATH_BOTH)
add_library(rapidjson STATIC IMPORTED ${RAPIDJSON})

View File

@ -7,7 +7,7 @@ have callbacks for where a more complete game would do more things (joining, spe
## Documentation
The most up to date documentation for Rich Presence can always be found in our [developer site](https://discordapp.com/developers/docs/rich-presence/how-to)!
The most up to date documentation for Rich Presence can always be found on our [developer site](https://discordapp.com/developers/docs/rich-presence/how-to)! If you're interested in rolling your own native implementation of Rich Presence via IPC sockets instead of using our SDK—hey, you've got free time, right?—check out the ["Hard Mode" documentation](https://github.com/discordapp/discord-rpc/blob/master/documentation/hard-mode.md).
## Basic Usage
@ -21,6 +21,10 @@ Download a release package for your platform(s) -- they have subdirs with variou
### From repo
First-eth, you'll want `CMake`. There's a few different ways to install it on your system, and you should refer to [their website](https://cmake.org/install/). Many package managers provide ways of installing CMake as well.
To make sure it's installed correctly, type `cmake --version` into your flavor of terminal/cmd. If you get a response with a version number, you're good to go!
There's a [CMake](https://cmake.org/download/) file that should be able to generate the lib for you; Sometimes I use it like this:
```sh
cd <path to discord-rpc>

View File

@ -224,7 +224,7 @@ def sign():
sign_command_base = [
tool,
'sign',
'/n', 'Hammer & Chisel Inc.',
'/n', 'Discord Inc.',
'/a',
'/tr', 'http://timestamp.digicert.com/rfc3161',
'/as',

View File

@ -93,8 +93,7 @@ And third is the `ACTIVITY_JOIN_REQUEST` event:
"username": "Mason",
"discriminator": "1337",
"avatar": "a_bab14f271d565501444b2ca3be944b25"
},
"secret": "e459ca99273f59909dd16ed97865f3ad"
}
},
"evt": "ACTIVITY_JOIN_REQUEST"
}
@ -125,3 +124,41 @@ In order to receive these events, you need to [subscribe](https://discordapp.com
"cmd": "SUBSCRIBE"
}
```
To unsubscribe from these events, resend with the command `UNSUBSCRIBE`
## Responding
A discord user will request access to the game. If the ACTIVITY_JOIN_REQUEST has been subscribed too, the ACTIVITY_JOIN_REQUEST event will be sent to the host's game. Accept it with following model:
```json
{
"nonce": "5dc0c062-98c6-47a0-8922-15aerg126",
"cmd": "SEND_ACTIVITY_JOIN_INVITE",
"args":
{
"user_id": "53908232506183680"
}
}
```
To reject the request, use `CLOSE_ACTIVITY_REQUEST`:
```json
{
"nonce": "5dc0c062-98c6-47a0-8922-dasg256eafg",
"cmd": "CLOSE_ACTIVITY_REQUEST",
"args":
{
"user_id": "53908232506183680"
}
}
```
## Notes
Here are just some quick notes to help with some common troubleshooting problems.
* IPC will echo back every command you send as a response. Use this as a lock-step feature to avoid flooding messages. Can be used to validate messages such as the Presence or Subscribes.
* The pipe expects for frames to be written in a single byte array. You cannot do multiple `stream.Write(opcode);` `stream.Write(length);` as it will break the pipe. Instead create a buffer, write the data to the buffer, then send the entire buffer to the stream.
* Discord can be on any pipe ranging from `discord-ipc-0` to `discord-ipc-9`. It is a good idea to try and connect to each one and keeping the first one you connect too. For multiple clients (eg Discord and Canary), you might want to add a feature to manually select the pipe so you can more easily debug the application.
* All enums are `lower_snake_case`.
* The opcode and length in the header are `Little Endian Unsigned Integers (32bits)`. In some languages, you must convert them as they can be architecture specific.
* [Discord Rich Presence How-To](https://discordapp.com/developers/docs/rich-presence/how-to) contains a lot of the information this document doesn't. For example, it will tell you about the response payload.
* In the documentation, DISCORD_REPLY_IGNORE is just implemented the same as DISCORD_REPLY_NO.
* You can test the Join / Spectate feature by enabling them in your profile and whitelisting a test account. Use Canary to run 2 accounts on the same machine.

View File

@ -7,7 +7,7 @@ public class DiscordJoinEvent : UnityEngine.Events.UnityEvent<string> { }
public class DiscordSpectateEvent : UnityEngine.Events.UnityEvent<string> { }
[System.Serializable]
public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.JoinRequest> { }
public class DiscordJoinRequestEvent : UnityEngine.Events.UnityEvent<DiscordRpc.DiscordUser> { }
public class DiscordController : MonoBehaviour
{
@ -16,7 +16,7 @@ public class DiscordController : MonoBehaviour
public string optionalSteamId;
public int callbackCalls;
public int clickCounter;
public DiscordRpc.JoinRequest joinRequest;
public DiscordRpc.DiscordUser joinRequest;
public UnityEngine.Events.UnityEvent onConnect;
public UnityEngine.Events.UnityEvent onDisconnect;
public UnityEngine.Events.UnityEvent hasResponded;
@ -50,10 +50,10 @@ public class DiscordController : MonoBehaviour
hasResponded.Invoke();
}
public void ReadyCallback()
public void ReadyCallback(ref DiscordRpc.DiscordUser connectedUser)
{
++callbackCalls;
Debug.Log("Discord: ready");
Debug.Log(string.Format("Discord: connected to {0}#{1}: {2}", connectedUser.username, connectedUser.discriminator, connectedUser.userId));
onConnect.Invoke();
}
@ -84,7 +84,7 @@ public class DiscordController : MonoBehaviour
onSpectate.Invoke(secret);
}
public void RequestCallback(ref DiscordRpc.JoinRequest request)
public void RequestCallback(ref DiscordRpc.DiscordUser request)
{
++callbackCalls;
Debug.Log(string.Format("Discord: join request {0}#{1}: {2}", request.username, request.discriminator, request.userId));

View File

@ -6,7 +6,7 @@ using System.Text;
public class DiscordRpc
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ReadyCallback();
public delegate void ReadyCallback(ref DiscordUser connectedUser);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DisconnectedCallback(int errorCode, string message);
@ -21,7 +21,7 @@ public class DiscordRpc
public delegate void SpectateCallback(string secret);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void RequestCallback(ref JoinRequest request);
public delegate void RequestCallback(ref DiscordUser request);
public struct EventHandlers
{
@ -54,7 +54,7 @@ public class DiscordRpc
}
[Serializable]
public struct JoinRequest
public struct DiscordUser
{
public string userId;
public string username;
@ -87,6 +87,9 @@ public class DiscordRpc
[DllImport("discord-rpc", EntryPoint = "Discord_Respond", CallingConvention = CallingConvention.Cdecl)]
public static extern void Respond(string userId, Reply reply);
[DllImport("discord-rpc", EntryPoint = "Discord_UpdateHandlers", CallingConvention = CallingConvention.Cdecl)]
public static extern void UpdateHandlers(ref EventHandlers handlers);
public static void UpdatePresence(RichPresence presence)
{
var presencestruct = presence.GetStruct();

View File

@ -46,8 +46,8 @@ public class ScriptBatch
string[] srcDlls = { "../../builds/install/osx-dynamic/lib/libdiscord-rpc.dylib" };
#else
string[] dstDirs = { "Assets/Plugins", "Assets/Plugins/x86", "Assets/Plugins/x86_64" };
string[] dstDlls = { "Assets/Plugins/x86/discord-rpc.so", "Assets/Plugins/x86_64/discord-rpc.so" };
string[] srcDlls = { "../../builds/install/linux-dynamic/bin/discord-rpc.dll", "../../builds/install/win64-dynamic/bin/discord-rpc.dll" };
string[] dstDlls = { "Assets/Plugins/discord-rpc.so" };
string[] srcDlls = { "../../builds/install/linux-dynamic/lib/libdiscord-rpc.so" };
#endif
Debug.Assert(dstDlls.Length == srcDlls.Length);

View File

@ -1,5 +1,5 @@
/*
This is a simple example in C of using the rich presence API asyncronously.
This is a simple example in C of using the rich presence API asynchronously.
*/
#define _CRT_SECURE_NO_WARNINGS /* thanks Microsoft */
@ -52,14 +52,18 @@ static void updateDiscordPresence()
discordPresence.spectateSecret = "look";
discordPresence.instance = 0;
Discord_UpdatePresence(&discordPresence);
} else {
}
else {
Discord_ClearPresence();
}
}
static void handleDiscordReady(void)
static void handleDiscordReady(const DiscordUser* connectedUser)
{
printf("\nDiscord: ready\n");
printf("\nDiscord: connected to user %s#%s - %s\n",
connectedUser->username,
connectedUser->discriminator,
connectedUser->userId);
}
static void handleDiscordDisconnected(int errcode, const char* message)
@ -82,13 +86,13 @@ static void handleDiscordSpectate(const char* secret)
printf("\nDiscord: spectate (%s)\n", secret);
}
static void handleDiscordJoinRequest(const DiscordJoinRequest* request)
static void handleDiscordJoinRequest(const DiscordUser* request)
{
int response = -1;
char yn[4];
printf("\nDiscord: join request from %s - %s - %s\n",
printf("\nDiscord: join request from %s#%s - %s\n",
request->username,
request->avatar,
request->discriminator,
request->userId);
do {
printf("Accept? (y/n)");
@ -152,7 +156,8 @@ static void gameLoop()
if (SendPresence) {
printf("Clearing presence information.\n");
SendPresence = 0;
} else {
}
else {
printf("Restoring presence information.\n");
SendPresence = 1;
}

View File

@ -6,12 +6,22 @@ DEFINE_LOG_CATEGORY(Discord)
static UDiscordRpc* self = nullptr;
static void ReadyHandler()
static void ReadyHandler(const DiscordUser* connectedUser)
{
UE_LOG(Discord, Log, TEXT("Discord connected"));
FDiscordUserData ud;
ud.userId = ANSI_TO_TCHAR(connectedUser->userId);
ud.username = ANSI_TO_TCHAR(connectedUser->username);
ud.discriminator = ANSI_TO_TCHAR(connectedUser->discriminator);
ud.avatar = ANSI_TO_TCHAR(connectedUser->avatar);
UE_LOG(Discord,
Log,
TEXT("Discord connected to %s - %s#%s"),
*ud.userId,
*ud.username,
*ud.discriminator);
if (self) {
self->IsConnected = true;
self->OnConnected.Broadcast();
self->OnConnected.Broadcast(ud);
}
}
@ -52,22 +62,27 @@ static void SpectateGameHandler(const char* spectateSecret)
}
}
static void JoinRequestHandler(const DiscordJoinRequest* request)
static void JoinRequestHandler(const DiscordUser* request)
{
FDiscordJoinRequestData jr;
jr.userId = ANSI_TO_TCHAR(request->userId);
jr.username = ANSI_TO_TCHAR(request->username);
jr.discriminator = ANSI_TO_TCHAR(request->discriminator);
jr.avatar = ANSI_TO_TCHAR(request->avatar);
UE_LOG(Discord, Log, TEXT("Discord join request from %s - %s#%s"), *jr.userId, *jr.username, *jr.discriminator);
FDiscordUserData ud;
ud.userId = ANSI_TO_TCHAR(request->userId);
ud.username = ANSI_TO_TCHAR(request->username);
ud.discriminator = ANSI_TO_TCHAR(request->discriminator);
ud.avatar = ANSI_TO_TCHAR(request->avatar);
UE_LOG(Discord,
Log,
TEXT("Discord join request from %s - %s#%s"),
*ud.userId,
*ud.username,
*ud.discriminator);
if (self) {
self->OnJoinRequest.Broadcast(jr);
self->OnJoinRequest.Broadcast(ud);
}
}
void UDiscordRpc::Initialize(const FString& applicationId,
bool autoRegister,
const FString& optionalSteamId)
bool autoRegister,
const FString& optionalSteamId)
{
self = this;
IsConnected = false;
@ -87,7 +102,7 @@ void UDiscordRpc::Initialize(const FString& applicationId,
auto appId = StringCast<ANSICHAR>(*applicationId);
auto steamId = StringCast<ANSICHAR>(*optionalSteamId);
Discord_Initialize(
(const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get());
(const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get());
}
void UDiscordRpc::Shutdown()
@ -134,6 +149,8 @@ void UDiscordRpc::UpdatePresence()
auto spectateSecret = StringCast<ANSICHAR>(*RichPresence.spectateSecret);
rp.spectateSecret = spectateSecret.Get();
rp.startTimestamp = RichPresence.startTimestamp;
rp.endTimestamp = RichPresence.endTimestamp;
rp.partySize = RichPresence.partySize;
rp.partyMax = RichPresence.partyMax;
rp.instance = RichPresence.instance;

View File

@ -11,7 +11,7 @@
* Ask to join callback data
*/
USTRUCT(BlueprintType)
struct FDiscordJoinRequestData {
struct FDiscordUserData {
GENERATED_USTRUCT_BODY()
UPROPERTY(BlueprintReadOnly)
@ -27,12 +27,12 @@ struct FDiscordJoinRequestData {
DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordConnected, const FDiscordUserData&, joinRequest);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordJoinRequestData&, joinRequest);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordUserData&, joinRequest);
// clang-format on

View File

@ -41,20 +41,20 @@ typedef struct DiscordRichPresence {
int8_t instance;
} DiscordRichPresence;
typedef struct DiscordJoinRequest {
typedef struct DiscordUser {
const char* userId;
const char* username;
const char* discriminator;
const char* avatar;
} DiscordJoinRequest;
} DiscordUser;
typedef struct DiscordEventHandlers {
void (*ready)(void);
void (*ready)(const DiscordUser* request);
void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordJoinRequest* request);
void (*joinRequest)(const DiscordUser* request);
} DiscordEventHandlers;
#define DISCORD_REPLY_NO 0
@ -80,6 +80,8 @@ DISCORD_EXPORT void Discord_ClearPresence(void);
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
#ifdef __cplusplus
} /* extern "C" */
#endif

View File

@ -55,7 +55,7 @@ if(WIN32)
/wd5027 # move assignment operator was implicitly defined as deleted
)
endif(MSVC)
target_link_libraries(discord-rpc PRIVATE psapi)
target_link_libraries(discord-rpc PRIVATE psapi advapi32)
endif(WIN32)
if(UNIX)

View File

@ -32,7 +32,7 @@ struct QueuedMessage {
}
};
struct JoinRequest {
struct User {
// snowflake (64bit int), turned into a ascii decimal string, at most 20 chars +1 null
// terminator = 21
char userId[32];
@ -47,6 +47,7 @@ struct JoinRequest {
};
static RpcConnection* Connection{nullptr};
static DiscordEventHandlers QueuedHandlers{};
static DiscordEventHandlers Handlers{};
static std::atomic_bool WasJustConnected{false};
static std::atomic_bool WasJustDisconnected{false};
@ -60,9 +61,11 @@ static char LastErrorMessage[256];
static int LastDisconnectErrorCode{0};
static char LastDisconnectErrorMessage[256];
static std::mutex PresenceMutex;
static std::mutex HandlerMutex;
static QueuedMessage QueuedPresence{};
static MsgQueue<QueuedMessage, MessageQueueSize> SendQueue;
static MsgQueue<JoinRequest, JoinQueueSize> JoinAskQueue;
static MsgQueue<User, JoinQueueSize> JoinAskQueue;
static User connectedUser;
// We want to auto connect, and retry on failure, but not as fast as possible. This does expoential
// backoff from 0.5 seconds to 1 minute
@ -212,15 +215,15 @@ static void Discord_UpdateConnection(void)
// writes
if (QueuedPresence.length) {
QueuedMessage local;
PresenceMutex.lock();
local.Copy(QueuedPresence);
QueuedPresence.length = 0;
PresenceMutex.unlock();
{
std::lock_guard<std::mutex> guard(PresenceMutex);
local.Copy(QueuedPresence);
QueuedPresence.length = 0;
}
if (!Connection->Write(local.buffer, local.length)) {
// if we fail to send, requeue
PresenceMutex.lock();
std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.Copy(local);
PresenceMutex.unlock();
}
}
@ -250,6 +253,19 @@ static bool RegisterForEvent(const char* evtName)
return false;
}
static bool DeregisterForEvent(const char* evtName)
{
auto qmessage = SendQueue.GetNextAddMessage();
if (qmessage) {
qmessage->length =
JsonWriteUnsubscribeCommand(qmessage->buffer, sizeof(qmessage->buffer), Nonce++, evtName);
SendQueue.CommitAdd();
SignalIOActivity();
return true;
}
return false;
}
extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
@ -266,10 +282,16 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
Pid = GetProcessId();
if (handlers) {
Handlers = *handlers;
}
else {
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (handlers) {
QueuedHandlers = *handlers;
}
else {
QueuedHandlers = {};
}
Handlers = {};
}
@ -278,25 +300,37 @@ extern "C" DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
}
Connection = RpcConnection::Create(applicationId);
Connection->onConnect = []() {
Connection->onConnect = [](JsonDocument& readyMessage) {
Discord_UpdateHandlers(&QueuedHandlers);
auto data = GetObjMember(&readyMessage, "data");
auto user = GetObjMember(data, "user");
auto userId = GetStrMember(user, "id");
auto username = GetStrMember(user, "username");
auto avatar = GetStrMember(user, "avatar");
if (userId && username) {
StringCopy(connectedUser.userId, userId);
StringCopy(connectedUser.username, username);
auto discriminator = GetStrMember(user, "discriminator");
if (discriminator) {
StringCopy(connectedUser.discriminator, discriminator);
}
if (avatar) {
StringCopy(connectedUser.avatar, avatar);
}
else {
connectedUser.avatar[0] = 0;
}
}
WasJustConnected.exchange(true);
ReconnectTimeMs.reset();
if (Handlers.joinGame) {
RegisterForEvent("ACTIVITY_JOIN");
}
if (Handlers.spectateGame) {
RegisterForEvent("ACTIVITY_SPECTATE");
}
if (Handlers.joinRequest) {
RegisterForEvent("ACTIVITY_JOIN_REQUEST");
}
};
Connection->onDisconnect = [](int err, const char* message) {
LastDisconnectErrorCode = err;
StringCopy(LastDisconnectErrorMessage, message);
{
std::lock_guard<std::mutex> guard(HandlerMutex);
Handlers = {};
}
WasJustDisconnected.exchange(true);
UpdateReconnectTime();
};
@ -318,10 +352,11 @@ extern "C" DISCORD_EXPORT void Discord_Shutdown(void)
extern "C" DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence)
{
PresenceMutex.lock();
QueuedPresence.length = JsonWriteRichPresenceObj(
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
PresenceMutex.unlock();
{
std::lock_guard<std::mutex> guard(PresenceMutex);
QueuedPresence.length = JsonWriteRichPresenceObj(
QueuedPresence.buffer, sizeof(QueuedPresence.buffer), Nonce++, Pid, presence);
}
SignalIOActivity();
}
@ -360,25 +395,42 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
if (isConnected) {
// if we are connected, disconnect cb first
std::lock_guard<std::mutex> guard(HandlerMutex);
if (wasDisconnected && Handlers.disconnected) {
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
}
}
if (WasJustConnected.exchange(false) && Handlers.ready) {
Handlers.ready();
if (WasJustConnected.exchange(false)) {
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.ready) {
DiscordUser du{connectedUser.userId,
connectedUser.username,
connectedUser.discriminator,
connectedUser.avatar};
Handlers.ready(&du);
}
}
if (GotErrorMessage.exchange(false) && Handlers.errored) {
Handlers.errored(LastErrorCode, LastErrorMessage);
if (GotErrorMessage.exchange(false)) {
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.errored) {
Handlers.errored(LastErrorCode, LastErrorMessage);
}
}
if (WasJoinGame.exchange(false) && Handlers.joinGame) {
Handlers.joinGame(JoinGameSecret);
if (WasJoinGame.exchange(false)) {
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.joinGame) {
Handlers.joinGame(JoinGameSecret);
}
}
if (WasSpectateGame.exchange(false) && Handlers.spectateGame) {
Handlers.spectateGame(SpectateGameSecret);
if (WasSpectateGame.exchange(false)) {
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.spectateGame) {
Handlers.spectateGame(SpectateGameSecret);
}
}
// Right now this batches up any requests and sends them all in a burst; I could imagine a world
@ -388,17 +440,48 @@ extern "C" DISCORD_EXPORT void Discord_RunCallbacks(void)
// not it should be trivial for the implementer to make a queue themselves.
while (JoinAskQueue.HavePendingSends()) {
auto req = JoinAskQueue.GetNextSendMessage();
if (Handlers.joinRequest) {
DiscordJoinRequest djr{req->userId, req->username, req->discriminator, req->avatar};
Handlers.joinRequest(&djr);
{
std::lock_guard<std::mutex> guard(HandlerMutex);
if (Handlers.joinRequest) {
DiscordUser du{req->userId, req->username, req->discriminator, req->avatar};
Handlers.joinRequest(&du);
}
}
JoinAskQueue.CommitSend();
}
if (!isConnected) {
// if we are not connected, disconnect message last
std::lock_guard<std::mutex> guard(HandlerMutex);
if (wasDisconnected && Handlers.disconnected) {
Handlers.disconnected(LastDisconnectErrorCode, LastDisconnectErrorMessage);
}
}
}
extern "C" DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* newHandlers)
{
if (newHandlers) {
#define HANDLE_EVENT_REGISTRATION(handler_name, event) \
if (!Handlers.handler_name && newHandlers->handler_name) { \
RegisterForEvent(event); \
} \
else if (Handlers.handler_name && !newHandlers->handler_name) { \
DeregisterForEvent(event); \
}
std::lock_guard<std::mutex> guard(HandlerMutex);
HANDLE_EVENT_REGISTRATION(joinGame, "ACTIVITY_JOIN")
HANDLE_EVENT_REGISTRATION(spectateGame, "ACTIVITY_SPECTATE")
HANDLE_EVENT_REGISTRATION(joinRequest, "ACTIVITY_JOIN_REQUEST")
#undef HANDLE_EVENT_REGISTRATION
Handlers = *newHandlers;
}
else {
std::lock_guard<std::mutex> guard(HandlerMutex);
Handlers = {};
}
return;
}

View File

@ -7,7 +7,7 @@
template <typename ElementType, size_t QueueSize>
class MsgQueue {
ElementType queue_[QueueSize]{};
ElementType queue_[QueueSize];
std::atomic_uint nextAdd_{0};
std::atomic_uint nextSend_{0};
std::atomic_uint pendingSends_{0};

View File

@ -42,7 +42,7 @@ void RpcConnection::Open()
if (cmd && evt && !strcmp(cmd, "DISPATCH") && !strcmp(evt, "READY")) {
state = State::Connected;
if (onConnect) {
onConnect();
onConnect(message);
}
}
}

View File

@ -40,7 +40,7 @@ struct RpcConnection {
BaseConnection* connection{nullptr};
State state{State::Disconnected};
void (*onConnect)(){nullptr};
void (*onConnect)(JsonDocument& message){nullptr};
void (*onDisconnect)(int errorCode, const char* message){nullptr};
char appId[64]{};
int lastErrorCode{0};

View File

@ -197,6 +197,25 @@ size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const cha
return writer.Size();
}
size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName)
{
JsonWriter writer(dest, maxLen);
{
WriteObject obj(writer);
JsonWriteNonce(writer, nonce);
WriteKey(writer, "cmd");
writer.String("UNSUBSCRIBE");
WriteKey(writer, "evt");
writer.String(evtName);
}
return writer.Size();
}
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce)
{
JsonWriter writer(dest, maxLen);

View File

@ -47,6 +47,8 @@ size_t JsonWriteRichPresenceObj(char* dest,
const DiscordRichPresence* presence);
size_t JsonWriteSubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
size_t JsonWriteUnsubscribeCommand(char* dest, size_t maxLen, int nonce, const char* evtName);
size_t JsonWriteJoinReply(char* dest, size_t maxLen, const char* userId, int reply, int nonce);
// I want to use as few allocations as I can get away with, and to do that with RapidJson, you need