diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
index 344301d4a7..2735f4ceb3 100644
--- a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml
@@ -1,17 +1,20 @@
-
+
+
-
-
+
+
+
+
\ No newline at end of file
diff --git a/osu.Game.Tests/Visual/TestCasePollingComponent.cs b/osu.Game.Tests/Visual/TestCasePollingComponent.cs
new file mode 100644
index 0000000000..b4b9d465e5
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCasePollingComponent.cs
@@ -0,0 +1,143 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Threading.Tasks;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Logging;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCasePollingComponent : OsuTestCase
+ {
+ private Container pollBox;
+ private TestPoller poller;
+
+ private const float safety_adjust = 1f;
+ private int count;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ count = 0;
+
+ Children = new Drawable[]
+ {
+ pollBox = new Container
+ {
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(0.4f),
+ Colour = Color4.LimeGreen,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = "Poll!",
+ }
+ }
+ }
+ };
+ });
+
+ [Test]
+ public void TestInstantPolling()
+ {
+ createPoller(true);
+
+ AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
+ checkCount(1);
+ checkCount(2);
+ checkCount(3);
+
+ AddStep("set poll interval to 5", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
+ checkCount(4);
+ checkCount(4);
+ checkCount(4);
+
+ skip();
+
+ checkCount(5);
+ checkCount(5);
+
+ AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust);
+ checkCount(6);
+ checkCount(7);
+ }
+
+ [Test]
+ [Ignore("i have no idea how to fix the timing of this one")]
+ public void TestSlowPolling()
+ {
+ createPoller(false);
+
+ AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5);
+ checkCount(0);
+ skip();
+ checkCount(0);
+ skip();
+ skip();
+ checkCount(0);
+ skip();
+ skip();
+ checkCount(0);
+ }
+
+ private void skip() => AddStep("skip", () =>
+ {
+ // could be 4 or 5 at this point due to timing discrepancies (safety_adjust @ 0.2 * 5 ~= 1)
+ // easiest to just ignore the value at this point and move on.
+ });
+
+ private void checkCount(int checkValue)
+ {
+ Logger.Log($"value is {count}");
+ AddAssert($"count is {checkValue}", () => count == checkValue);
+ }
+
+ private void createPoller(bool instant) => AddStep("create poller", () =>
+ {
+ poller?.Expire();
+
+ Add(poller = instant ? new TestPoller() : new TestSlowPoller());
+ poller.OnPoll += () =>
+ {
+ pollBox.FadeOutFromOne(500);
+ count++;
+ };
+ });
+
+ protected override double TimePerAction => 500;
+
+ public class TestPoller : PollingComponent
+ {
+ public event Action OnPoll;
+
+ protected override Task Poll()
+ {
+ Schedule(() => OnPoll?.Invoke());
+ return base.Poll();
+ }
+ }
+
+ public class TestSlowPoller : TestPoller
+ {
+ protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls / 2f / Clock.Rate)).ContinueWith(_ => base.Poll());
+ }
+ }
+}
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 863ad3042f..a63af0f7a3 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -4,11 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
-using osu.Framework.Graphics;
using osu.Framework.Logging;
-using osu.Framework.Threading;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
@@ -18,7 +17,7 @@ namespace osu.Game.Online.Chat
///
/// Manages everything channel related
///
- public class ChannelManager : Component, IOnlineComponent
+ public class ChannelManager : PollingComponent
{
///
/// The channels the player joins on startup
@@ -49,11 +48,14 @@ namespace osu.Game.Online.Chat
public IBindableCollection AvailableChannels => availableChannels;
private IAPIProvider api;
- private ScheduledDelegate fetchMessagesScheduleder;
+
+ public readonly BindableBool HighPollRate = new BindableBool();
public ChannelManager()
{
CurrentChannel.ValueChanged += currentChannelChanged;
+
+ HighPollRate.BindValueChanged(high => TimeBetweenPolls = high ? 1000 : 6000, true);
}
///
@@ -360,73 +362,60 @@ namespace osu.Game.Online.Chat
}
}
- public void APIStateChanged(APIAccess api, APIState state)
- {
- switch (state)
- {
- case APIState.Online:
- fetchUpdates();
- break;
- default:
- fetchMessagesScheduleder?.Cancel();
- fetchMessagesScheduleder = null;
- break;
- }
- }
-
private long lastMessageId;
- private const int update_poll_interval = 1000;
private bool channelsInitialised;
- private void fetchUpdates()
+ protected override Task Poll()
{
- fetchMessagesScheduleder?.Cancel();
- fetchMessagesScheduleder = Scheduler.AddDelayed(() =>
+ if (!api.IsLoggedIn)
+ return base.Poll();
+
+ var fetchReq = new GetUpdatesRequest(lastMessageId);
+
+ var tcs = new TaskCompletionSource();
+
+ fetchReq.Success += updates =>
{
- var fetchReq = new GetUpdatesRequest(lastMessageId);
-
- fetchReq.Success += updates =>
+ if (updates?.Presence != null)
{
- if (updates?.Presence != null)
+ foreach (var channel in updates.Presence)
{
- foreach (var channel in updates.Presence)
- {
- // we received this from the server so should mark the channel already joined.
- JoinChannel(channel, true);
- }
-
- //todo: handle left channels
-
- handleChannelMessages(updates.Messages);
-
- foreach (var group in updates.Messages.GroupBy(m => m.ChannelId))
- JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
-
- lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId;
+ // we received this from the server so should mark the channel already joined.
+ JoinChannel(channel, true);
}
- if (!channelsInitialised)
- {
- channelsInitialised = true;
- // we want this to run after the first presence so we can see if the user is in any channels already.
- initializeChannels();
- }
+ //todo: handle left channels
- fetchUpdates();
- };
+ handleChannelMessages(updates.Messages);
- fetchReq.Failure += delegate { fetchUpdates(); };
+ foreach (var group in updates.Messages.GroupBy(m => m.ChannelId))
+ JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
- api.Queue(fetchReq);
- }, update_poll_interval);
+ lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId;
+ }
+
+ if (!channelsInitialised)
+ {
+ channelsInitialised = true;
+ // we want this to run after the first presence so we can see if the user is in any channels already.
+ initializeChannels();
+ }
+
+ tcs.SetResult(true);
+ };
+
+ fetchReq.Failure += _ => tcs.SetResult(false);
+
+ api.Queue(fetchReq);
+
+ return tcs.Task;
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
this.api = api;
- api.Register(this);
}
}
diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs
new file mode 100644
index 0000000000..9d0bed7595
--- /dev/null
+++ b/osu.Game/Online/PollingComponent.cs
@@ -0,0 +1,118 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Threading.Tasks;
+using osu.Framework.Graphics;
+using osu.Framework.Threading;
+
+namespace osu.Game.Online
+{
+ ///
+ /// A component which requires a constant polling process.
+ ///
+ public abstract class PollingComponent : Component
+ {
+ private double? lastTimePolled;
+
+ private ScheduledDelegate scheduledPoll;
+
+ private bool pollingActive;
+
+ private double timeBetweenPolls;
+
+ ///
+ /// The time in milliseconds to wait between polls.
+ /// Setting to zero stops all polling.
+ ///
+ public double TimeBetweenPolls
+ {
+ get => timeBetweenPolls;
+ set
+ {
+ timeBetweenPolls = value;
+ scheduledPoll?.Cancel();
+ pollIfNecessary();
+ }
+ }
+
+ ///
+ ///
+ ///
+ /// The initial time in milliseconds to wait between polls. Setting to zero stops al polling.
+ protected PollingComponent(double timeBetweenPolls = 0)
+ {
+ TimeBetweenPolls = timeBetweenPolls;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ pollIfNecessary();
+ }
+
+ private bool pollIfNecessary()
+ {
+ // we must be loaded so we have access to clock.
+ if (!IsLoaded) return false;
+
+ // there's already a poll process running.
+ if (pollingActive) return false;
+
+ // don't try polling if the time between polls hasn't been set.
+ if (timeBetweenPolls == 0) return false;
+
+ if (!lastTimePolled.HasValue)
+ {
+ doPoll();
+ return true;
+ }
+
+ if (Time.Current - lastTimePolled.Value > timeBetweenPolls)
+ {
+ doPoll();
+ return true;
+ }
+
+ // not ennough time has passed since the last poll. we do want to schedule a poll to happen, though.
+ scheduleNextPoll();
+ return false;
+ }
+
+ private void doPoll()
+ {
+ scheduledPoll = null;
+ pollingActive = true;
+ Poll().ContinueWith(_ => pollComplete());
+ }
+
+ ///
+ /// Perform the polling in this method. Call when done.
+ ///
+ protected virtual Task Poll()
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Call when a poll operation has completed.
+ ///
+ private void pollComplete()
+ {
+ lastTimePolled = Time.Current;
+ pollingActive = false;
+
+ if (scheduledPoll == null)
+ scheduleNextPoll();
+ }
+
+ private void scheduleNextPoll()
+ {
+ scheduledPoll?.Cancel();
+
+ double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0;
+
+ scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, timeBetweenPolls - lastPollDuration));
+ }
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 73ecbafb9e..31a00e68ac 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -418,6 +418,8 @@ namespace osu.Game
dependencies.Cache(notifications);
dependencies.Cache(dialogOverlay);
+ chatOverlay.StateChanged += state => channelManager.HighPollRate.Value = state == Visibility.Visible;
+
Add(externalLinkOpener = new ExternalLinkOpener());
var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications };