Skip to content

Commit

Permalink
Organise code, still hook up to RoomChanged to update room privacy …
Browse files Browse the repository at this point in the history
…mode, and use `SkipIdenticalPresence` + scheduling to avoid potential rate-limits
  • Loading branch information
frenzibyte committed Mar 20, 2024
1 parent 1a08dba commit b11ae1c
Showing 1 changed file with 53 additions and 32 deletions.
85 changes: 53 additions & 32 deletions osu.Desktop/DiscordRichPresence.cs
Expand Up @@ -2,16 +2,16 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Diagnostics;
using System.Text;
using DiscordRPC;
using DiscordRPC.Message;
using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Game;
using osu.Game.Configuration;
using osu.Game.Extensions;
Expand Down Expand Up @@ -53,8 +53,6 @@ internal partial class DiscordRichPresence : Component

private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();

private int usersCurrentlyInLobby;

private readonly RichPresence presence = new RichPresence
{
Assets = new Assets { LargeImageKey = "osu_logo_lazer" },
Expand All @@ -72,7 +70,9 @@ private void load(OsuConfigManager config)

client = new DiscordRpcClient(client_id)
{
SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
// SkipIdenticalPresence allows us to fire SetPresence at any point and leave it to the underlying implementation
// to check whether a difference has actually occurred before sending a command to Discord (with a minor caveat that's handled in onReady).
SkipIdenticalPresence = true
};

client.OnReady += onReady;
Expand All @@ -95,35 +95,56 @@ private void load(OsuConfigManager config)
activity.BindTo(u.NewValue.Activity);
}, true);

ruleset.BindValueChanged(_ => updateStatus());
status.BindValueChanged(_ => updateStatus());
activity.BindValueChanged(_ => updateStatus());
privacyMode.BindValueChanged(_ => updateStatus());
ruleset.BindValueChanged(_ => updatePresence());
status.BindValueChanged(_ => updatePresence());
activity.BindValueChanged(_ => updatePresence());
privacyMode.BindValueChanged(_ => updatePresence());
multiplayerClient.RoomUpdated += onRoomUpdated;

client.Initialize();
}

private void onReady(object _, ReadyMessage __)
{
Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
Schedule(updateStatus);

// when RPC is lost and reconnected, we have to clear presence state for updatePresence to work (see DiscordRpcClient.SkipIdenticalPresence).
if (client.CurrentPresence != null)
client.SetPresence(null);

updatePresence();
}

private void updateStatus()
{
Debug.Assert(ThreadSafety.IsUpdateThread);
private void onRoomUpdated() => updatePresence();

if (!client.IsInitialized)
return;
private ScheduledDelegate? presenceUpdateDelegate;

if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off)
private void updatePresence()
{
presenceUpdateDelegate?.Cancel();
presenceUpdateDelegate = Scheduler.AddDelayed(() =>
{
client.ClearPresence();
return;
}
if (!client.IsInitialized)
return;
if (status.Value == UserStatus.Offline || privacyMode.Value == DiscordRichPresenceMode.Off)
{
client.ClearPresence();
return;
}
bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb;
bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb;
updatePresenceStatus(hideIdentifiableInformation);
updatePresenceParty(hideIdentifiableInformation);
updatePresenceAssets();
client.SetPresence(presence);
}, 200);
}

private void updatePresenceStatus(bool hideIdentifiableInformation)
{
if (activity.Value != null)
{
presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation));
Expand All @@ -150,14 +171,14 @@ private void updateStatus()
presence.State = "Idle";
presence.Details = string.Empty;
}
}

private void updatePresenceParty(bool hideIdentifiableInformation)
{
if (!hideIdentifiableInformation && multiplayerClient.Room != null)
{
MultiplayerRoom room = multiplayerClient.Room;

if (room.Users.Count == usersCurrentlyInLobby)
return;

presence.Party = new Party
{
Privacy = string.IsNullOrEmpty(room.Settings.Password) ? Party.PrivacySetting.Public : Party.PrivacySetting.Private,
Expand All @@ -175,17 +196,16 @@ private void updateStatus()
};

presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret);
usersCurrentlyInLobby = room.Users.Count;
}
else
{
presence.Party = null;
presence.Secrets.JoinSecret = null;
usersCurrentlyInLobby = 0;
}
}

Logger.Log($"Updating Discord RPC presence with activity status: {presence.State}, details: {presence.Details}", LoggingTarget.Network, LogLevel.Debug);

private void updatePresenceAssets()
{
// update user information
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
presence.Assets.LargeImageText = string.Empty;
Expand All @@ -200,17 +220,15 @@ private void updateStatus()
// update ruleset
presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom";
presence.Assets.SmallImageText = ruleset.Value.Name;

client.SetPresence(presence);
}

private void onJoin(object sender, JoinMessage args)
private void onJoin(object sender, JoinMessage args) => Scheduler.AddOnce(() =>
{
game.Window?.Raise();
if (!api.IsLoggedIn)
{
Schedule(() => login?.Show());
login?.Show();
return;
}
Expand All @@ -231,7 +249,7 @@ private void onJoin(object sender, JoinMessage args)
});
request.Failure += _ => Logger.Log($"Could not join multiplayer room, room could not be found (room ID: {roomId}).", LoggingTarget.Network, LogLevel.Important);
api.Queue(request);
}
});

private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });

Expand Down Expand Up @@ -294,6 +312,9 @@ private static bool tryParseRoomSecret(string secretJson, out long roomId, out s

protected override void Dispose(bool isDisposing)
{
if (multiplayerClient.IsNotNull())
multiplayerClient.RoomUpdated -= onRoomUpdated;

client.Dispose();
base.Dispose(isDisposing);
}
Expand Down

0 comments on commit b11ae1c

Please sign in to comment.