1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 20:33:35 +08:00

Debounce user offline notifications (#37028)

This commit is contained in:
Dan Balasescu
2026-03-19 03:41:58 +09:00
committed by GitHub
Unverified
parent bd30f70045
commit ed6ec8b417
2 changed files with 89 additions and 7 deletions
@@ -25,6 +25,7 @@ namespace osu.Game.Tests.Visual.Components
private NotificationOverlay notificationOverlay = null!;
private ChatOverlay chatOverlay = null!;
private TestMetadataClient metadataClient = null!;
private FriendPresenceNotifier notifier = null!;
[SetUp]
public void Setup() => Schedule(() =>
@@ -45,7 +46,11 @@ namespace osu.Game.Tests.Visual.Components
notificationOverlay,
chatOverlay,
metadataClient,
new FriendPresenceNotifier()
notifier = new FriendPresenceNotifier
{
// Speeds up tests that don't rely on this debounce a little bit.
OfflineDebounceTime = 0
}
}
};
@@ -127,5 +132,27 @@ namespace osu.Game.Tests.Visual.Components
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(2));
}
[Test]
public void TestOfflineDebounce()
{
AddStep("set debounce time", () =>
{
notifier.NotificationDebounceTime = 0;
notifier.OfflineDebounceTime = 5000;
});
AddStep("bring friend online", () => metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online }));
AddUntilStep("online notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
for (int i = 0; i < 3; i++)
{
AddStep("bring friend online", () => metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online }));
AddStep("bring friend offline", () => metadataClient.FriendPresenceUpdated(1, null));
}
AddUntilStep("online notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
AddUntilStep("offline notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(2));
}
}
}
+61 -6
View File
@@ -25,6 +25,16 @@ namespace osu.Game.Online
{
public partial class FriendPresenceNotifier : Component
{
/// <summary>
/// Minimum time between subsequent online/offline notifications.
/// </summary>
public double NotificationDebounceTime { get; set; } = 1000;
/// <summary>
/// Minimum time after a user has gone offline, before they're added to the offline alert queue.
/// </summary>
public double OfflineDebounceTime { get; set; } = 15000;
[Resolved]
private INotificationOverlay notifications { get; set; } = null!;
@@ -42,13 +52,31 @@ namespace osu.Game.Online
private readonly IBindableList<APIRelation> friends = new BindableList<APIRelation>();
private readonly IBindableDictionary<int, UserPresence> friendPresences = new BindableDictionary<int, UserPresence>();
/// <summary>
/// List of users that will be notified as having come online with the next notification.
/// </summary>
private readonly HashSet<APIUser> onlineAlertQueue = new HashSet<APIUser>();
/// <summary>
/// List of users that will be notified as having gone offline with the next notification.
/// </summary>
private readonly HashSet<APIUser> offlineAlertQueue = new HashSet<APIUser>();
private double? nextOnlineAlertTime;
private double? nextOfflineAlertTime;
/// <summary>
/// List of users that have gone offline, but we're waiting for them to potentially come online again before queueing them for notification.
/// For example, if a user is quickly toggling between the "Online" and "Appear Offline" states.
/// </summary>
private readonly HashSet<APIUser> pendingOfflineUsers = new HashSet<APIUser>();
private const double debounce_time_before_notification = 1000;
/// <summary>
/// The post time for the next online notification.
/// </summary>
private double? nextOnlineAlertTime;
/// <summary>
/// The post time for the next offline notification.
/// </summary>
private double? nextOfflineAlertTime;
protected override void LoadComplete()
{
@@ -133,28 +161,55 @@ namespace osu.Game.Online
APIRelation? friend = friends.FirstOrDefault(f => f.TargetID == friendId);
if (friend?.TargetUser is APIUser user)
markUserOffline(user);
markUserOfflineDebounced(user);
}
break;
}
}
/// <summary>
/// Immediately registers a user for the next online notification alert.
/// </summary>
private void markUserOnline(APIUser user)
{
if (pendingOfflineUsers.Remove(user))
return;
if (!offlineAlertQueue.Remove(user))
{
onlineAlertQueue.Add(user);
nextOnlineAlertTime ??= Time.Current + debounce_time_before_notification;
nextOnlineAlertTime ??= Time.Current + NotificationDebounceTime;
}
}
/// <summary>
/// Waits <see cref="OfflineDebounceTime"/> before adding a user to the next offline notification alert.
/// </summary>
/// <param name="user"></param>
private void markUserOfflineDebounced(APIUser user)
{
pendingOfflineUsers.Add(user);
Scheduler.AddDelayed(() =>
{
// Check if the friend has come back online.
if (!pendingOfflineUsers.Remove(user))
return;
markUserOffline(user);
}, OfflineDebounceTime);
}
/// <summary>
/// Immediately registers a user for the next offline notification alert.
/// </summary>
private void markUserOffline(APIUser user)
{
if (!onlineAlertQueue.Remove(user))
{
offlineAlertQueue.Add(user);
nextOfflineAlertTime ??= Time.Current + debounce_time_before_notification;
nextOfflineAlertTime ??= Time.Current + NotificationDebounceTime;
}
}