Merge branch 'master' into master

This commit is contained in:
Dean Herbert 2018-12-14 19:49:08 +09:00 committed by GitHub
commit 2fe642a12b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 388 additions and 102 deletions

View File

@ -1,17 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<method />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<method v="2">
<option name="Build" enabled="true" />
</method>
</configuration>
</component>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project">
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
<OutputType>WinExe</OutputType>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />

View File

@ -9,7 +9,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />

View File

@ -0,0 +1,143 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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());
}
}
}

View File

@ -10,7 +10,7 @@
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />

View File

@ -199,6 +199,12 @@ namespace osu.Game.Online.API
try
{
Logger.Log($@"Performing request {req}", LoggingTarget.Network);
req.Failure += ex =>
{
if (ex is WebException we)
handleWebException(we);
};
req.Perform(this);
//we could still be in initialisation, at which point we don't want to say we're Online yet.
@ -210,37 +216,12 @@ namespace osu.Game.Online.API
}
catch (WebException we)
{
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
var removeFromQueue = handleWebException(we);
// special cases for un-typed but useful message responses.
switch (we.Message)
{
case "Unauthorized":
statusCode = HttpStatusCode.Unauthorized;
break;
}
if (removeFromQueue)
req.Fail(we);
switch (statusCode)
{
case HttpStatusCode.Unauthorized:
Logout(false);
return true;
case HttpStatusCode.RequestTimeout:
failureCount++;
log.Add($@"API failure count is now {failureCount}");
if (failureCount < 3)
//we might try again at an api level.
return false;
State = APIState.Failing;
flushQueue();
return true;
}
req.Fail(we);
return true;
return removeFromQueue;
}
catch (Exception e)
{
@ -276,6 +257,41 @@ namespace osu.Game.Online.API
}
}
private bool handleWebException(WebException we)
{
HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode
?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout);
// special cases for un-typed but useful message responses.
switch (we.Message)
{
case "Unauthorized":
case "Forbidden":
statusCode = HttpStatusCode.Unauthorized;
break;
}
switch (statusCode)
{
case HttpStatusCode.Unauthorized:
Logout(false);
return true;
case HttpStatusCode.RequestTimeout:
failureCount++;
log.Add($@"API failure count is now {failureCount}");
if (failureCount < 3)
//we might try again at an api level.
return false;
State = APIState.Failing;
flushQueue();
return true;
}
return true;
}
public bool IsLoggedIn => LocalUser.Value.Id > 1;
public void Queue(APIRequest request)

View File

@ -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
/// <summary>
/// Manages everything channel related
/// </summary>
public class ChannelManager : Component, IOnlineComponent
public class ChannelManager : PollingComponent
{
/// <summary>
/// The channels the player joins on startup
@ -49,11 +48,14 @@ namespace osu.Game.Online.Chat
public IBindableCollection<Channel> 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);
}
/// <summary>
@ -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<bool>();
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);
}
}

View File

@ -0,0 +1,118 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// A component which requires a constant polling process.
/// </summary>
public abstract class PollingComponent : Component
{
private double? lastTimePolled;
private ScheduledDelegate scheduledPoll;
private bool pollingActive;
private double timeBetweenPolls;
/// <summary>
/// The time in milliseconds to wait between polls.
/// Setting to zero stops all polling.
/// </summary>
public double TimeBetweenPolls
{
get => timeBetweenPolls;
set
{
timeBetweenPolls = value;
scheduledPoll?.Cancel();
pollIfNecessary();
}
}
/// <summary>
///
/// </summary>
/// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops al polling.</param>
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());
}
/// <summary>
/// Perform the polling in this method. Call <see cref="pollComplete"/> when done.
/// </summary>
protected virtual Task Poll()
{
return Task.CompletedTask;
}
/// <summary>
/// Call when a poll operation has completed.
/// </summary>
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));
}
}
}

View File

@ -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 };

View File

@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections
public override FontAwesome Icon => FontAwesome.fa_paint_brush;
private readonly Bindable<SkinInfo> dropdownBindable = new Bindable<SkinInfo>();
private readonly Bindable<SkinInfo> dropdownBindable = new Bindable<SkinInfo> { Default = SkinInfo.Default };
private readonly Bindable<int> configBindable = new Bindable<int>();
private SkinManager skins;

View File

@ -25,13 +25,20 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
base.PopIn();
Waves.Show();
this.FadeIn();
}
protected override void PopOut()
{
base.PopOut();
Waves.Hide();
// this is required or we will remain present even though our waves are hidden.
this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut();
}
}
}

View File

@ -170,7 +170,7 @@ namespace osu.Game.Screens.Play
{
Retries = RestartCount,
OnRetry = Restart,
OnQuit = Exit,
OnQuit = performUserRequestedExit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
Children = new[]
{
@ -211,7 +211,7 @@ namespace osu.Game.Screens.Play
failOverlay = new FailOverlay
{
OnRetry = Restart,
OnQuit = Exit,
OnQuit = performUserRequestedExit,
},
new HotkeyRetryOverlay
{
@ -225,7 +225,7 @@ namespace osu.Game.Screens.Play
}
};
hudOverlay.HoldToQuit.Action = Exit;
hudOverlay.HoldToQuit.Action = performUserRequestedExit;
hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused);
@ -250,8 +250,16 @@ namespace osu.Game.Screens.Play
mod.ApplyToClock(sourceClock);
}
private void performUserRequestedExit()
{
if (!IsCurrentScreen) return;
Exit();
}
public void Restart()
{
if (!IsCurrentScreen) return;
sampleRestart?.Play();
ValidForResume = false;
RestartRequested?.Invoke();

View File

@ -16,8 +16,8 @@ namespace osu.Game.Skinning
{
public event Action SourceChanged;
private Bindable<bool> beatmapSkins = new Bindable<bool>();
private Bindable<bool> beatmapHitsounds = new Bindable<bool>();
private readonly Bindable<bool> beatmapSkins = new Bindable<bool>();
private readonly Bindable<bool> beatmapHitsounds = new Bindable<bool>();
public Drawable GetDrawableComponent(string componentName)
{
@ -84,11 +84,8 @@ namespace osu.Game.Skinning
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
beatmapSkins = config.GetBindable<bool>(OsuSetting.BeatmapSkins);
beatmapSkins.BindValueChanged(_ => onSourceChanged());
beatmapHitsounds = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);
beatmapHitsounds.BindValueChanged(_ => onSourceChanged(), true);
config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins);
config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds);
}
protected override void LoadComplete()
@ -97,6 +94,9 @@ namespace osu.Game.Skinning
if (fallbackSource != null)
fallbackSource.SourceChanged += onSourceChanged;
beatmapSkins.BindValueChanged(_ => onSourceChanged());
beatmapHitsounds.BindValueChanged(_ => onSourceChanged(), true);
}
protected override void Dispose(bool isDisposing)

View File

@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2018.1207.0" />
<PackageReference Include="ppy.osu.Framework" Version="2018.1214.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />