mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 17:43:05 +08:00
Merge branch 'realm-nested-writes' into metadata-client
This commit is contained in:
commit
79bed0abdf
@ -120,10 +120,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
lastHyperDashState = Catcher.HyperDashing;
|
lastHyperDashState = Catcher.HyperDashing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCatcherPosition(float X)
|
public void SetCatcherPosition(float x)
|
||||||
{
|
{
|
||||||
float lastPosition = Catcher.X;
|
float lastPosition = Catcher.X;
|
||||||
float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH);
|
float newPosition = Math.Clamp(x, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
Catcher.X = newPosition;
|
Catcher.X = newPosition;
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200));
|
complete.BindValueChanged(complete => updateDiscColour(complete.NewValue, 200));
|
||||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||||
|
|
||||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||||
@ -137,6 +137,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
this.ScaleTo(initial_scale);
|
this.ScaleTo(initial_scale);
|
||||||
this.RotateTo(0);
|
this.RotateTo(0);
|
||||||
|
|
||||||
|
updateDiscColour(false);
|
||||||
|
|
||||||
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
using (BeginDelayedSequence(spinner.TimePreempt / 2))
|
||||||
{
|
{
|
||||||
// constant ambient rotation to give the spinner "spinning" character.
|
// constant ambient rotation to give the spinner "spinning" character.
|
||||||
@ -177,12 +179,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
|
if (drawableSpinner.Result?.TimeCompleted is double completionTime)
|
||||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
{
|
||||||
updateComplete(state == ArmedState.Hit, 0);
|
using (BeginAbsoluteSequence(completionTime))
|
||||||
|
updateDiscColour(true, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateComplete(bool complete, double duration)
|
private void updateDiscColour(bool complete, double duration = 0)
|
||||||
{
|
{
|
||||||
var colour = complete ? completeColour : normalColour;
|
var colour = complete ? completeColour : normalColour;
|
||||||
|
|
||||||
|
@ -59,6 +59,25 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNestedWriteCalls()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realm, _) =>
|
||||||
|
{
|
||||||
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
|
|
||||||
|
var liveBeatmap = beatmap.ToLive(realm);
|
||||||
|
|
||||||
|
realm.Run(r =>
|
||||||
|
r.Write(_ =>
|
||||||
|
r.Write(_ =>
|
||||||
|
r.Add(beatmap)))
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAccessAfterAttach()
|
public void TestAccessAfterAttach()
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
@ -116,10 +115,10 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
|
AddStep($"set audio offset to {userOffset}", () => localConfig.SetValue(OsuSetting.AudioOffset, userOffset));
|
||||||
|
|
||||||
AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
|
AddStep("seek to 2500", () => gameplayClockContainer.Seek(2500));
|
||||||
AddAssert("gameplay clock time = 2500", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 2500, 10f));
|
AddStep("gameplay clock time = 2500", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 2500, 10f));
|
||||||
|
|
||||||
AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
|
AddStep("seek to 10000", () => gameplayClockContainer.Seek(10000));
|
||||||
AddAssert("gameplay clock time = 10000", () => Precision.AlmostEquals(gameplayClockContainer.CurrentTime, 10000, 10f));
|
AddStep("gameplay clock time = 10000", () => Assert.AreEqual(gameplayClockContainer.CurrentTime, 10000, 10f));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Models;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Leaderboards;
|
using osu.Game.Online.Leaderboards;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -100,6 +101,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Rank = ScoreRank.XH,
|
Rank = ScoreRank.XH,
|
||||||
User = new APIUser { Username = "TestUser" },
|
User = new APIUser { Username = "TestUser" },
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
Files = { new RealmNamedFileUsage(new RealmFile { Hash = $"{i}" }, string.Empty) }
|
||||||
};
|
};
|
||||||
|
|
||||||
importedScores.Add(scoreManager.Import(score).Value);
|
importedScores.Add(scoreManager.Import(score).Value);
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
Section section = Section.General;
|
Section section = Section.General;
|
||||||
|
|
||||||
string line;
|
string? line;
|
||||||
|
|
||||||
while ((line = stream.ReadLine()) != null)
|
while ((line = stream.ReadLine()) != null)
|
||||||
{
|
{
|
||||||
|
@ -137,8 +137,17 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path))))
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path);
|
||||||
return Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
|
var stream = GetStream(fileStorePath);
|
||||||
|
|
||||||
|
if (stream == null)
|
||||||
|
{
|
||||||
|
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var reader = new LineBufferedReader(stream))
|
||||||
|
return Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -154,7 +163,16 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return resources.LargeTextureStore.Get(BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile));
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.BackgroundFile);
|
||||||
|
var texture = resources.LargeTextureStore.Get(fileStorePath);
|
||||||
|
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -173,7 +191,16 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return resources.Tracks.Get(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile));
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.AudioFile);
|
||||||
|
var track = resources.Tracks.Get(fileStorePath);
|
||||||
|
|
||||||
|
if (track == null)
|
||||||
|
{
|
||||||
|
Logger.Log($"Beatmap failed to load (file {Metadata.AudioFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return track;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -192,8 +219,17 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var trackData = GetStream(BeatmapSetInfo.GetPathForFile(Metadata.AudioFile));
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(Metadata.AudioFile);
|
||||||
return trackData == null ? null : new Waveform(trackData);
|
|
||||||
|
var trackData = GetStream(fileStorePath);
|
||||||
|
|
||||||
|
if (trackData == null)
|
||||||
|
{
|
||||||
|
Logger.Log($"Beatmap waveform failed to load (file {Metadata.AudioFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Waveform(trackData);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -211,20 +247,38 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var stream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path))))
|
string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path);
|
||||||
{
|
var beatmapFileStream = GetStream(fileStorePath);
|
||||||
var decoder = Decoder.GetDecoder<Storyboard>(stream);
|
|
||||||
|
|
||||||
string storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
|
if (beatmapFileStream == null)
|
||||||
|
|
||||||
// todo: support loading from both set-wide storyboard *and* beatmap specific.
|
|
||||||
if (string.IsNullOrEmpty(storyboardFilename))
|
|
||||||
storyboard = decoder.Decode(stream);
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename))))
|
Logger.Log($"Beatmap failed to load (file {BeatmapInfo.Path} not found on disk at expected location {fileStorePath})", level: LogLevel.Error);
|
||||||
storyboard = decoder.Decode(stream, secondaryStream);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using (var reader = new LineBufferedReader(beatmapFileStream))
|
||||||
|
{
|
||||||
|
var decoder = Decoder.GetDecoder<Storyboard>(reader);
|
||||||
|
|
||||||
|
Stream storyboardFileStream = null;
|
||||||
|
|
||||||
|
if (BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename is string storyboardFilename)
|
||||||
|
{
|
||||||
|
string storyboardFileStorePath = BeatmapSetInfo?.GetPathForFile(storyboardFilename);
|
||||||
|
storyboardFileStream = GetStream(storyboardFileStorePath);
|
||||||
|
|
||||||
|
if (storyboardFileStream == null)
|
||||||
|
Logger.Log($"Storyboard failed to load (file {storyboardFilename} not found on disk at expected location {storyboardFileStorePath})", level: LogLevel.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storyboardFileStream != null)
|
||||||
|
{
|
||||||
|
// Stand-alone storyboard was found, so parse in addition to the beatmap's local storyboard.
|
||||||
|
using (var secondaryReader = new LineBufferedReader(storyboardFileStream))
|
||||||
|
storyboard = decoder.Decode(reader, secondaryReader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
storyboard = decoder.Decode(reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
@ -8,7 +8,9 @@ using System.Collections.Concurrent;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Statistics;
|
||||||
|
|
||||||
namespace osu.Game.Database
|
namespace osu.Game.Database
|
||||||
{
|
{
|
||||||
@ -20,8 +22,16 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<TLookup, TValue> cache = new ConcurrentDictionary<TLookup, TValue>();
|
private readonly ConcurrentDictionary<TLookup, TValue> cache = new ConcurrentDictionary<TLookup, TValue>();
|
||||||
|
|
||||||
|
private readonly GlobalStatistic<MemoryCachingStatistics> statistics;
|
||||||
|
|
||||||
protected virtual bool CacheNullValues => true;
|
protected virtual bool CacheNullValues => true;
|
||||||
|
|
||||||
|
protected MemoryCachingComponent()
|
||||||
|
{
|
||||||
|
statistics = GlobalStatistics.Get<MemoryCachingStatistics>(nameof(MemoryCachingComponent<TLookup, TValue>), GetType().ReadableName());
|
||||||
|
statistics.Value = new MemoryCachingStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve the cached value for the given lookup.
|
/// Retrieve the cached value for the given lookup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -30,12 +40,20 @@ namespace osu.Game.Database
|
|||||||
protected async Task<TValue> GetAsync([NotNull] TLookup lookup, CancellationToken token = default)
|
protected async Task<TValue> GetAsync([NotNull] TLookup lookup, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (CheckExists(lookup, out TValue performance))
|
if (CheckExists(lookup, out TValue performance))
|
||||||
|
{
|
||||||
|
statistics.Value.HitCount++;
|
||||||
return performance;
|
return performance;
|
||||||
|
}
|
||||||
|
|
||||||
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
|
var computed = await ComputeValueAsync(lookup, token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
statistics.Value.MissCount++;
|
||||||
|
|
||||||
if (computed != null || CacheNullValues)
|
if (computed != null || CacheNullValues)
|
||||||
|
{
|
||||||
cache[lookup] = computed;
|
cache[lookup] = computed;
|
||||||
|
statistics.Value.Usage = cache.Count;
|
||||||
|
}
|
||||||
|
|
||||||
return computed;
|
return computed;
|
||||||
}
|
}
|
||||||
@ -51,6 +69,8 @@ namespace osu.Game.Database
|
|||||||
if (matchKeyPredicate(kvp.Key))
|
if (matchKeyPredicate(kvp.Key))
|
||||||
cache.TryRemove(kvp.Key, out _);
|
cache.TryRemove(kvp.Key, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statistics.Value.Usage = cache.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool CheckExists([NotNull] TLookup lookup, out TValue value) =>
|
protected bool CheckExists([NotNull] TLookup lookup, out TValue value) =>
|
||||||
@ -63,5 +83,31 @@ namespace osu.Game.Database
|
|||||||
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
/// <param name="token">An optional <see cref="CancellationToken"/> to cancel the operation.</param>
|
||||||
/// <returns>The computed value.</returns>
|
/// <returns>The computed value.</returns>
|
||||||
protected abstract Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default);
|
protected abstract Task<TValue> ComputeValueAsync(TLookup lookup, CancellationToken token = default);
|
||||||
|
|
||||||
|
private class MemoryCachingStatistics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of cache hits.
|
||||||
|
/// </summary>
|
||||||
|
public int HitCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of cache misses.
|
||||||
|
/// </summary>
|
||||||
|
public int MissCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of cached entities.
|
||||||
|
/// </summary>
|
||||||
|
public int Usage;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
int totalAccesses = HitCount + MissCount;
|
||||||
|
double hitRate = totalAccesses == 0 ? 0 : (double)HitCount / totalAccesses;
|
||||||
|
|
||||||
|
return $"i:{Usage} h:{HitCount} m:{MissCount} {hitRate:0%}";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,18 +8,45 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
public static class RealmExtensions
|
public static class RealmExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a write operation against the provided realm instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will automatically start a transaction if not already in one.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="realm">The realm to operate on.</param>
|
||||||
|
/// <param name="function">The write operation to run.</param>
|
||||||
public static void Write(this Realm realm, Action<Realm> function)
|
public static void Write(this Realm realm, Action<Realm> function)
|
||||||
{
|
{
|
||||||
using var transaction = realm.BeginWrite();
|
Transaction? transaction = null;
|
||||||
|
|
||||||
|
if (!realm.IsInTransaction)
|
||||||
|
transaction = realm.BeginWrite();
|
||||||
|
|
||||||
function(realm);
|
function(realm);
|
||||||
transaction.Commit();
|
|
||||||
|
transaction?.Commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perform a write operation against the provided realm instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will automatically start a transaction if not already in one.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="realm">The realm to operate on.</param>
|
||||||
|
/// <param name="function">The write operation to run.</param>
|
||||||
public static T Write<T>(this Realm realm, Func<Realm, T> function)
|
public static T Write<T>(this Realm realm, Func<Realm, T> function)
|
||||||
{
|
{
|
||||||
using var transaction = realm.BeginWrite();
|
Transaction? transaction = null;
|
||||||
|
|
||||||
|
if (!realm.IsInTransaction)
|
||||||
|
transaction = realm.BeginWrite();
|
||||||
|
|
||||||
var result = function(realm);
|
var result = function(realm);
|
||||||
transaction.Commit();
|
|
||||||
|
transaction?.Commit();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
TimingControlPoint timingPoint;
|
TimingControlPoint timingPoint;
|
||||||
EffectControlPoint effectPoint;
|
EffectControlPoint effectPoint;
|
||||||
|
|
||||||
IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true;
|
IsBeatSyncedWithTrack = BeatSyncSource.Clock?.IsRunning == true && BeatSyncSource.ControlPoints != null;
|
||||||
|
|
||||||
double currentTrackTime;
|
double currentTrackTime;
|
||||||
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@ -17,34 +14,31 @@ namespace osu.Game.IO
|
|||||||
public class LineBufferedReader : IDisposable
|
public class LineBufferedReader : IDisposable
|
||||||
{
|
{
|
||||||
private readonly StreamReader streamReader;
|
private readonly StreamReader streamReader;
|
||||||
private readonly Queue<string> lineBuffer;
|
|
||||||
|
private string? peekedLine;
|
||||||
|
|
||||||
public LineBufferedReader(Stream stream, bool leaveOpen = false)
|
public LineBufferedReader(Stream stream, bool leaveOpen = false)
|
||||||
{
|
{
|
||||||
streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen);
|
streamReader = new StreamReader(stream, Encoding.UTF8, true, -1, leaveOpen);
|
||||||
lineBuffer = new Queue<string>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the next line from the stream without consuming it.
|
/// Reads the next line from the stream without consuming it.
|
||||||
/// Subsequent calls to <see cref="PeekLine"/> without a <see cref="ReadLine"/> will return the same string.
|
/// Subsequent calls to <see cref="PeekLine"/> without a <see cref="ReadLine"/> will return the same string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PeekLine()
|
public string? PeekLine() => peekedLine ??= streamReader.ReadLine();
|
||||||
{
|
|
||||||
if (lineBuffer.Count > 0)
|
|
||||||
return lineBuffer.Peek();
|
|
||||||
|
|
||||||
string line = streamReader.ReadLine();
|
|
||||||
if (line != null)
|
|
||||||
lineBuffer.Enqueue(line);
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the next line from the stream and consumes it.
|
/// Reads the next line from the stream and consumes it.
|
||||||
/// If a line was peeked, that same line will then be consumed and returned.
|
/// If a line was peeked, that same line will then be consumed and returned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ReadLine() => lineBuffer.Count > 0 ? lineBuffer.Dequeue() : streamReader.ReadLine();
|
public string? ReadLine()
|
||||||
|
{
|
||||||
|
string? line = peekedLine ?? streamReader.ReadLine();
|
||||||
|
|
||||||
|
peekedLine = null;
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads the stream to its end and returns the text read.
|
/// Reads the stream to its end and returns the text read.
|
||||||
@ -53,14 +47,13 @@ namespace osu.Game.IO
|
|||||||
public string ReadToEnd()
|
public string ReadToEnd()
|
||||||
{
|
{
|
||||||
string remainingText = streamReader.ReadToEnd();
|
string remainingText = streamReader.ReadToEnd();
|
||||||
if (lineBuffer.Count == 0)
|
if (peekedLine == null)
|
||||||
return remainingText;
|
return remainingText;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
// this might not be completely correct due to varying platform line endings
|
// this might not be completely correct due to varying platform line endings
|
||||||
while (lineBuffer.Count > 0)
|
builder.AppendLine(peekedLine);
|
||||||
builder.AppendLine(lineBuffer.Dequeue());
|
|
||||||
builder.Append(remainingText);
|
builder.Append(remainingText);
|
||||||
|
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
@ -68,7 +61,7 @@ namespace osu.Game.IO
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
streamReader?.Dispose();
|
streamReader.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -426,10 +426,10 @@ namespace osu.Game.Online.Leaderboards
|
|||||||
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = Score.Mods));
|
||||||
|
|
||||||
if (Score.Files.Count > 0)
|
if (Score.Files.Count > 0)
|
||||||
|
{
|
||||||
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage).Export(Score)));
|
items.Add(new OsuMenuItem("Export", MenuItemType.Standard, () => new LegacyScoreExporter(storage).Export(Score)));
|
||||||
|
|
||||||
if (!isOnlineScope)
|
|
||||||
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(Score))));
|
||||||
|
}
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
|
@ -597,7 +597,7 @@ namespace osu.Game
|
|||||||
Host.ExceptionThrown -= onExceptionThrown;
|
Host.ExceptionThrown -= onExceptionThrown;
|
||||||
}
|
}
|
||||||
|
|
||||||
ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.Beatmap.ControlPointInfo;
|
ControlPointInfo IBeatSyncProvider.ControlPoints => Beatmap.Value.BeatmapLoaded ? Beatmap.Value.Beatmap.ControlPointInfo : null;
|
||||||
IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null;
|
IClock IBeatSyncProvider.Clock => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track : (IClock)null;
|
||||||
ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null;
|
ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
@ -23,19 +21,19 @@ namespace osu.Game.Overlays.Login
|
|||||||
{
|
{
|
||||||
public class LoginForm : FillFlowContainer
|
public class LoginForm : FillFlowContainer
|
||||||
{
|
{
|
||||||
private TextBox username;
|
private TextBox username = null!;
|
||||||
private TextBox password;
|
private TextBox password = null!;
|
||||||
private ShakeContainer shakeSignIn;
|
private ShakeContainer shakeSignIn = null!;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
public Action RequestHide;
|
public Action? RequestHide;
|
||||||
|
|
||||||
private void performLogin()
|
private void performLogin()
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text))
|
if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text))
|
||||||
api?.Login(username.Text, password.Text);
|
api.Login(username.Text, password.Text);
|
||||||
else
|
else
|
||||||
shakeSignIn.Shake();
|
shakeSignIn.Shake();
|
||||||
}
|
}
|
||||||
@ -49,6 +47,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
ErrorTextFlowContainer errorText;
|
ErrorTextFlowContainer errorText;
|
||||||
|
LinkFlowContainer forgottenPaswordLink;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -56,7 +55,7 @@ namespace osu.Game.Overlays.Login
|
|||||||
{
|
{
|
||||||
PlaceholderText = UsersStrings.LoginUsername.ToLower(),
|
PlaceholderText = UsersStrings.LoginUsername.ToLower(),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Text = api?.ProvidedUsername ?? string.Empty,
|
Text = api.ProvidedUsername,
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
password = new OsuPasswordTextBox
|
password = new OsuPasswordTextBox
|
||||||
@ -80,6 +79,12 @@ namespace osu.Game.Overlays.Login
|
|||||||
LabelText = "Stay signed in",
|
LabelText = "Stay signed in",
|
||||||
Current = config.GetBindable<bool>(OsuSetting.SavePassword),
|
Current = config.GetBindable<bool>(OsuSetting.SavePassword),
|
||||||
},
|
},
|
||||||
|
forgottenPaswordLink = new LinkFlowContainer
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -103,15 +108,17 @@ namespace osu.Game.Overlays.Login
|
|||||||
Text = "Register",
|
Text = "Register",
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
RequestHide();
|
RequestHide?.Invoke();
|
||||||
accountCreation.Show();
|
accountCreation.Show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"{api.WebsiteRootUrl}/home/password-reset");
|
||||||
|
|
||||||
password.OnCommit += (_, _) => performLogin();
|
password.OnCommit += (_, _) => performLogin();
|
||||||
|
|
||||||
if (api?.LastLoginError?.Message is string error)
|
if (api.LastLoginError?.Message is string error)
|
||||||
errorText.AddErrors(new[] { error });
|
errorText.AddErrors(new[] { error });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
// TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch).
|
// TODO: Add realm queries to hint at which ruleset results are available in (and allow clicking to switch).
|
||||||
// TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting.
|
// TODO: Make this message more certain by ensuring the osu! beatmaps exist before suggesting.
|
||||||
if (filter?.Ruleset?.OnlineID > 0 && !filter.AllowConvertedBeatmaps)
|
if (filter?.Ruleset?.OnlineID != 0 && filter?.AllowConvertedBeatmaps == false)
|
||||||
{
|
{
|
||||||
textFlow.AddParagraph("- Try");
|
textFlow.AddParagraph("- Try");
|
||||||
textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
|
textFlow.AddLink(" enabling ", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
public FlakyTestAttribute(int tryCount)
|
public FlakyTestAttribute(int tryCount)
|
||||||
: base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 0 : tryCount)
|
: base(Environment.GetEnvironmentVariable("OSU_TESTS_FAIL_FLAKY") == "1" ? 1 : tryCount)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user