Merge branch 'master' into migration-backend

This commit is contained in:
Dean Herbert
2020-05-11 18:52:27 +09:00
27 changed files with 415 additions and 33 deletions

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -84,7 +85,7 @@ namespace osu.Game.Beatmaps
public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList<Mod> mods = null, TimeSpan? timeout = null)
{
using (var cancellationSource = new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10)))
using (var cancellationSource = createCancellationTokenSource(timeout))
{
mods ??= Array.Empty<Mod>();
@ -181,6 +182,15 @@ namespace osu.Game.Beatmaps
beatmapLoadTask = null;
}
private CancellationTokenSource createCancellationTokenSource(TimeSpan? timeout)
{
if (Debugger.IsAttached)
// ignore timeout when debugger is attached (may be breakpointing / debugging).
return new CancellationTokenSource();
return new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(10));
}
private Task<IBeatmap> loadBeatmapAsync() => beatmapLoadTask ??= Task.Factory.StartNew(() =>
{
// Todo: Handle cancellation during beatmap parsing

View File

@ -48,10 +48,18 @@ namespace osu.Game.IO
UnderlyingStorage.Delete(MutatePath(path));
public override IEnumerable<string> GetDirectories(string path) =>
UnderlyingStorage.GetDirectories(MutatePath(path));
ToLocalRelative(UnderlyingStorage.GetDirectories(MutatePath(path)));
public IEnumerable<string> ToLocalRelative(IEnumerable<string> paths)
{
string localRoot = GetFullPath(string.Empty);
foreach (var path in paths)
yield return Path.GetRelativePath(localRoot, UnderlyingStorage.GetFullPath(path));
}
public override IEnumerable<string> GetFiles(string path, string pattern = "*") =>
UnderlyingStorage.GetFiles(MutatePath(path), pattern);
ToLocalRelative(UnderlyingStorage.GetFiles(MutatePath(path), pattern));
public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate) =>
UnderlyingStorage.GetStream(MutatePath(path), access, mode);

View File

@ -18,6 +18,7 @@ using osu.Game.Screens.Menu;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Development;
@ -97,6 +98,7 @@ namespace osu.Game
private MainMenu menuScreen;
[CanBeNull]
private IntroScreen introScreen;
private Bindable<int> configRuleset;
@ -914,7 +916,7 @@ namespace osu.Game
if (ScreenStack.CurrentScreen is Loader)
return false;
if (introScreen.DidLoadMenu && !(ScreenStack.CurrentScreen is IntroScreen))
if (introScreen?.DidLoadMenu == true && !(ScreenStack.CurrentScreen is IntroScreen))
{
Scheduler.Add(introScreen.MakeCurrent);
return true;

View File

@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
[Cached(typeof(DrawableHitObject))]
public abstract class DrawableHitObject : SkinReloadableDrawable
{
public event Action<DrawableHitObject> DefaultsApplied;
public readonly HitObject HitObject;
/// <summary>
@ -148,7 +150,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
samplesBindable.CollectionChanged += (_, __) => loadSamples();
updateState(ArmedState.Idle, true);
onDefaultsApplied();
apply(HitObject);
}
private void loadSamples()
@ -175,7 +177,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
AddInternal(Samples);
}
private void onDefaultsApplied() => apply(HitObject);
private void onDefaultsApplied(HitObject hitObject)
{
apply(hitObject);
DefaultsApplied?.Invoke(this);
}
private void apply(HitObject hitObject)
{

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Objects
/// <summary>
/// Invoked after <see cref="ApplyDefaults"/> has completed on this <see cref="HitObject"/>.
/// </summary>
public event Action DefaultsApplied;
public event Action<HitObject> DefaultsApplied;
public readonly Bindable<double> StartTimeBindable = new BindableDouble();
@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Objects
foreach (var h in nestedHitObjects)
h.ApplyDefaults(controlPointInfo, difficulty);
DefaultsApplied?.Invoke();
DefaultsApplied?.Invoke(this);
}
protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)

View File

@ -487,6 +487,11 @@ namespace osu.Game.Rulesets.UI
protected virtual ResumeOverlay CreateResumeOverlay() => null;
/// <summary>
/// Whether to display gameplay overlays, such as <see cref="HUDOverlay"/> and <see cref="BreakOverlay"/>.
/// </summary>
public virtual bool AllowGameplayOverlays => true;
/// <summary>
/// Sets a replay to be used, overriding local input.
/// </summary>

View File

@ -16,17 +16,23 @@ namespace osu.Game.Rulesets.UI.Scrolling
{
private readonly IBindable<double> timeRange = new BindableDouble();
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly Dictionary<DrawableHitObject, Cached> hitObjectInitialStateCache = new Dictionary<DrawableHitObject, Cached>();
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
private readonly LayoutValue initialStateCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
// Responds to changes in the layout. When the layout changes, all hit object states must be recomputed.
private readonly LayoutValue layoutCache = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo);
// A combined cache across all hit object states to reduce per-update iterations.
// When invalidated, one or more (but not necessarily all) hitobject states must be re-validated.
private readonly Cached combinedObjCache = new Cached();
public ScrollingHitObjectContainer()
{
RelativeSizeAxes = Axes.Both;
AddLayout(initialStateCache);
AddLayout(layoutCache);
}
[BackgroundDependencyLoader]
@ -35,13 +41,14 @@ namespace osu.Game.Rulesets.UI.Scrolling
direction.BindTo(scrollingInfo.Direction);
timeRange.BindTo(scrollingInfo.TimeRange);
direction.ValueChanged += _ => initialStateCache.Invalidate();
timeRange.ValueChanged += _ => initialStateCache.Invalidate();
direction.ValueChanged += _ => layoutCache.Invalidate();
timeRange.ValueChanged += _ => layoutCache.Invalidate();
}
public override void Add(DrawableHitObject hitObject)
{
initialStateCache.Invalidate();
combinedObjCache.Invalidate();
hitObject.DefaultsApplied += onDefaultsApplied;
base.Add(hitObject);
}
@ -51,8 +58,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
if (result)
{
initialStateCache.Invalidate();
combinedObjCache.Invalidate();
hitObjectInitialStateCache.Remove(hitObject);
hitObject.DefaultsApplied -= onDefaultsApplied;
}
return result;
@ -60,23 +69,45 @@ namespace osu.Game.Rulesets.UI.Scrolling
public override void Clear(bool disposeChildren = true)
{
foreach (var h in Objects)
h.DefaultsApplied -= onDefaultsApplied;
base.Clear(disposeChildren);
initialStateCache.Invalidate();
combinedObjCache.Invalidate();
hitObjectInitialStateCache.Clear();
}
private void onDefaultsApplied(DrawableHitObject drawableObject)
{
// The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame).
// In such a case, combinedObjCache will take care of updating the hitobject.
if (hitObjectInitialStateCache.TryGetValue(drawableObject, out var objCache))
{
combinedObjCache.Invalidate();
objCache.Invalidate();
}
}
private float scrollLength;
protected override void Update()
{
base.Update();
if (!initialStateCache.IsValid)
if (!layoutCache.IsValid)
{
foreach (var cached in hitObjectInitialStateCache.Values)
cached.Invalidate();
combinedObjCache.Invalidate();
scrollingInfo.Algorithm.Reset();
layoutCache.Validate();
}
if (!combinedObjCache.IsValid)
{
switch (direction.Value)
{
case ScrollingDirection.Up:
@ -89,15 +120,21 @@ namespace osu.Game.Rulesets.UI.Scrolling
break;
}
scrollingInfo.Algorithm.Reset();
foreach (var obj in Objects)
{
if (!hitObjectInitialStateCache.TryGetValue(obj, out var objCache))
objCache = hitObjectInitialStateCache[obj] = new Cached();
if (objCache.IsValid)
continue;
computeLifetimeStartRecursive(obj);
computeInitialStateRecursive(obj);
objCache.Validate();
}
initialStateCache.Validate();
combinedObjCache.Validate();
}
}
@ -109,8 +146,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
computeLifetimeStartRecursive(obj);
}
private readonly Dictionary<DrawableHitObject, Cached> hitObjectInitialStateCache = new Dictionary<DrawableHitObject, Cached>();
private double computeOriginAdjustedLifetimeStart(DrawableHitObject hitObject)
{
float originAdjustment = 0.0f;
@ -142,12 +177,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Cant use AddOnce() since the delegate is re-constructed every invocation
private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() =>
{
if (!hitObjectInitialStateCache.TryGetValue(hitObject, out var cached))
cached = hitObjectInitialStateCache[hitObject] = new Cached();
if (cached.IsValid)
return;
if (hitObject.HitObject is IHasEndTime e)
{
switch (direction.Value)
@ -171,8 +200,6 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Nested hitobjects don't need to scroll, but they do need accurate positions
updatePosition(obj, hitObject.HitObject.StartTime);
}
cached.Validate();
});
protected override void UpdateAfterChildrenLife()

View File

@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -201,6 +202,25 @@ namespace osu.Game.Screens.Edit
updateHitObject(null, true);
}
/// <summary>
/// Clears all <see cref="HitObjects"/> from this <see cref="EditorBeatmap"/>.
/// </summary>
public void Clear()
{
var removed = HitObjects.ToList();
mutableHitObjects.Clear();
foreach (var b in startTimeBindables)
b.Value.UnbindAll();
startTimeBindables.Clear();
foreach (var h in removed)
HitObjectRemoved?.Invoke(h);
updateHitObject(null, true);
}
private void trackStartTime(HitObject hitObject)
{
startTimeBindables[hitObject] = hitObject.StartTimeBindable.GetBoundCopy();

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Screens.Play
@ -38,5 +40,11 @@ namespace osu.Game.Screens.Play
public IEnumerable<BeatmapStatistic> GetStatistics() => PlayableBeatmap.GetStatistics();
public IBeatmap Clone() => PlayableBeatmap.Clone();
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
public IBindable<JudgementResult> LastJudgementResult => lastJudgementResult;
public void ApplyResult(JudgementResult result) => lastJudgementResult.Value = result;
}
}

View File

@ -184,6 +184,13 @@ namespace osu.Game.Screens.Play
addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap);
addOverlayComponents(GameplayClockContainer, Beatmap.Value);
if (!DrawableRuleset.AllowGameplayOverlays)
{
HUDOverlay.ShowHud.Value = false;
HUDOverlay.ShowHud.Disabled = true;
BreakOverlay.Hide();
}
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
// bind clock into components that require it
@ -193,6 +200,7 @@ namespace osu.Game.Screens.Play
{
HealthProcessor.ApplyResult(r);
ScoreProcessor.ApplyResult(r);
gameplayBeatmap.ApplyResult(r);
};
DrawableRuleset.OnRevertResult += r =>