1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:37:28 +08:00

Merge branch 'osu-editor-pooling' into osu-hitobject-pooling

This commit is contained in:
smoogipoo 2020-11-13 18:07:05 +09:00
commit cdbedbb8a4
18 changed files with 315 additions and 122 deletions

View File

@ -1,7 +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.
using System.Collections.Generic;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -12,8 +11,8 @@ namespace osu.Game.Rulesets.Mania.Edit
{ {
public class ManiaBlueprintContainer : ComposeBlueprintContainer public class ManiaBlueprintContainer : ComposeBlueprintContainer
{ {
public ManiaBlueprintContainer(IEnumerable<DrawableHitObject> drawableHitObjects) public ManiaBlueprintContainer(HitObjectComposer composer)
: base(drawableHitObjects) : base(composer)
{ {
} }

View File

@ -13,7 +13,6 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -89,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.Edit
return drawableRuleset; return drawableRuleset;
} }
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects) protected override ComposeBlueprintContainer CreateBlueprintContainer()
=> new ManiaBlueprintContainer(hitObjects); => new ManiaBlueprintContainer(this);
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[] protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{ {

View File

@ -2,13 +2,9 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osuTK; using osuTK;
@ -17,54 +13,13 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public class DrawableOsuEditRuleset : DrawableOsuRuleset public class DrawableOsuEditRuleset : DrawableOsuRuleset
{ {
/// <summary>
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
/// </summary>
private const double editor_hit_object_fade_out_extension = 700;
public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods) public DrawableOsuEditRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods)
: base(ruleset, beatmap, mods) : base(ruleset, beatmap, mods)
{ {
} }
public override DrawableHitObject<OsuHitObject> CreateDrawableRepresentation(OsuHitObject h) protected override DrawablePool<TDrawable> CreatePool<TDrawable>(int initialSize, int? maximumSize = null)
=> base.CreateDrawableRepresentation(h)?.With(d => d.ApplyCustomUpdateState += updateState); => new OsuEditDrawablePool<TDrawable>(Playfield.CheckHittable, Playfield.OnHitObjectLoaded, initialSize, maximumSize);
private void updateState(DrawableHitObject hitObject, ArmedState state)
{
if (state == ArmedState.Idle)
return;
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
switch (hitObject)
{
default:
// there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.)
return;
case DrawableSlider _:
// no specifics to sliders but let them fade slower below.
break;
case DrawableHitCircle circle: // also handles slider heads
circle.ApproachCircle
.FadeOutFromOne(editor_hit_object_fade_out_extension)
.Expire();
break;
}
// Get the existing fade out transform
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
if (existing == null)
return;
hitObject.RemoveTransform(existing);
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
}
protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor();

View File

@ -1,7 +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.
using System.Collections.Generic;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
@ -14,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public class OsuBlueprintContainer : ComposeBlueprintContainer public class OsuBlueprintContainer : ComposeBlueprintContainer
{ {
public OsuBlueprintContainer(IEnumerable<DrawableHitObject> drawableHitObjects) public OsuBlueprintContainer(HitObjectComposer composer)
: base(drawableHitObjects) : base(composer)
{ {
} }

View File

@ -16,7 +16,6 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons; using osu.Game.Screens.Edit.Components.TernaryButtons;
@ -80,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Edit
updateDistanceSnapGrid(); updateDistanceSnapGrid();
} }
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects) protected override ComposeBlueprintContainer CreateBlueprintContainer()
=> new OsuBlueprintContainer(hitObjects); => new OsuBlueprintContainer(this);
private DistanceSnapGrid distanceSnapGrid; private DistanceSnapGrid distanceSnapGrid;
private Container distanceSnapGridContainer; private Container distanceSnapGridContainer;

View File

@ -0,0 +1,63 @@
// 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.
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuEditDrawablePool<T> : OsuDrawablePool<T>
where T : DrawableHitObject, new()
{
/// <summary>
/// Hit objects are intentionally made to fade out at a constant slower rate than in gameplay.
/// This allows a mapper to gain better historical context and use recent hitobjects as reference / snap points.
/// </summary>
private const double editor_hit_object_fade_out_extension = 700;
public OsuEditDrawablePool(Func<DrawableHitObject, double, bool> checkHittable, Action<Drawable> onLoaded, int initialSize, int? maximumSize = null)
: base(checkHittable, onLoaded, initialSize, maximumSize)
{
}
protected override T CreateNewDrawable() => base.CreateNewDrawable().With(d => d.ApplyCustomUpdateState += updateState);
private void updateState(DrawableHitObject hitObject, ArmedState state)
{
if (state == ArmedState.Idle)
return;
// adjust the visuals of certain object types to make them stay on screen for longer than usual.
switch (hitObject)
{
default:
// there are quite a few drawable hit types we don't want to extent (spinners, ticks etc.)
return;
case DrawableSlider _:
// no specifics to sliders but let them fade slower below.
break;
case DrawableHitCircle circle: // also handles slider heads
circle.ApproachCircle
.FadeOutFromOne(editor_hit_object_fade_out_extension)
.Expire();
break;
}
// Get the existing fade out transform
var existing = hitObject.Transforms.LastOrDefault(t => t.TargetMember == nameof(Alpha));
if (existing == null)
return;
hitObject.RemoveTransform(existing);
using (hitObject.BeginAbsoluteSequence(existing.StartTime))
hitObject.FadeOut(editor_hit_object_fade_out_extension).Expire();
}
}
}

View File

@ -1,7 +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.
using System.Collections.Generic;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osu.Game.Rulesets.Taiko.Edit.Blueprints;
@ -11,8 +10,8 @@ namespace osu.Game.Rulesets.Taiko.Edit
{ {
public class TaikoBlueprintContainer : ComposeBlueprintContainer public class TaikoBlueprintContainer : ComposeBlueprintContainer
{ {
public TaikoBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects) public TaikoBlueprintContainer(HitObjectComposer composer)
: base(hitObjects) : base(composer)
{ {
} }

View File

@ -4,7 +4,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Edit
new SwellCompositionTool() new SwellCompositionTool()
}; };
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects) protected override ComposeBlueprintContainer CreateBlueprintContainer()
=> new TaikoBlueprintContainer(hitObjects); => new TaikoBlueprintContainer(this);
} }
} }

View File

@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture] [TestFixture]
public class TestSceneTimelineBlueprintContainer : TimelineTestScene public class TestSceneTimelineBlueprintContainer : TimelineTestScene
{ {
public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(); public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(null);
protected override void LoadComplete() protected override void LoadComplete()
{ {

View File

@ -1,7 +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.
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -65,17 +64,13 @@ namespace osu.Game.Rulesets.Edit
private void addHitObject(HitObject hitObject) private void addHitObject(HitObject hitObject)
{ {
var drawableObject = drawableRuleset.CreateDrawableRepresentation((TObject)hitObject); drawableRuleset.AddHitObject((TObject)hitObject);
drawableRuleset.Playfield.Add(drawableObject);
drawableRuleset.Playfield.PostProcess(); drawableRuleset.Playfield.PostProcess();
} }
private void removeHitObject(HitObject hitObject) private void removeHitObject(HitObject hitObject)
{ {
var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); drawableRuleset.RemoveHitObject((TObject)hitObject);
drawableRuleset.Playfield.Remove(drawableObject);
drawableRuleset.Playfield.PostProcess(); drawableRuleset.Playfield.PostProcess();
} }

View File

@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Edit
drawableRulesetWrapper, drawableRulesetWrapper,
// layers above playfield // layers above playfield
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer()
.WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects)) .WithChild(BlueprintContainer = CreateBlueprintContainer())
} }
}, },
new FillFlowContainer new FillFlowContainer
@ -182,9 +182,8 @@ namespace osu.Game.Rulesets.Edit
/// <summary> /// <summary>
/// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic.
/// </summary> /// </summary>
/// <param name="hitObjects">A live collection of all <see cref="DrawableHitObject"/>s in the editor beatmap.</param> protected virtual ComposeBlueprintContainer CreateBlueprintContainer()
protected virtual ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects) => new ComposeBlueprintContainer(this);
=> new ComposeBlueprintContainer(hitObjects);
/// <summary> /// <summary>
/// Construct a drawable ruleset for the provided ruleset. /// Construct a drawable ruleset for the provided ruleset.

View File

@ -42,6 +42,32 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public event Action<DrawableHitObject, JudgementResult> RevertResult; public event Action<DrawableHitObject, JudgementResult> RevertResult;
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this represents the time when the <see cref="HitObject"/>s become alive.
/// </remarks>
public event Action<HitObject> HitObjectUsageBegan;
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes unused by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this represents the time when the <see cref="HitObject"/>s become dead.
/// </remarks>
public event Action<HitObject> HitObjectUsageFinished;
/// <summary>
/// The amount of time prior to the current time within which <see cref="HitObject"/>s should be considered alive.
/// </summary>
public double PastLifetimeExtension { get; set; }
/// <summary>
/// The amount of time after the current time within which <see cref="HitObject"/>s should be considered alive.
/// </summary>
public double FutureLifetimeExtension { get; set; }
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>(); private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> drawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>(); private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> drawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager(); private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
@ -88,6 +114,8 @@ namespace osu.Game.Rulesets.UI
bindStartTime(drawable); bindStartTime(drawable);
AddInternal(drawableMap[entry] = drawable, false); AddInternal(drawableMap[entry] = drawable, false);
HitObjectUsageBegan?.Invoke(entry.HitObject);
} }
private void removeDrawable(HitObjectLifetimeEntry entry) private void removeDrawable(HitObjectLifetimeEntry entry)
@ -103,6 +131,8 @@ namespace osu.Game.Rulesets.UI
unbindStartTime(drawable); unbindStartTime(drawable);
RemoveInternal(drawable); RemoveInternal(drawable);
HitObjectUsageFinished?.Invoke(entry.HitObject);
} }
#endregion #endregion
@ -159,7 +189,7 @@ namespace osu.Game.Rulesets.UI
protected override bool CheckChildrenLife() protected override bool CheckChildrenLife()
{ {
bool aliveChanged = base.CheckChildrenLife(); bool aliveChanged = base.CheckChildrenLife();
aliveChanged |= lifetimeManager.Update(Time.Current, Time.Current); aliveChanged |= lifetimeManager.Update(Time.Current - PastLifetimeExtension, Time.Current + FutureLifetimeExtension);
return aliveChanged; return aliveChanged;
} }

View File

@ -29,6 +29,22 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public event Action<DrawableHitObject, JudgementResult> RevertResult; public event Action<DrawableHitObject, JudgementResult> RevertResult;
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this represents the time when the <see cref="HitObject"/>s become alive.
/// </remarks>
public event Action<HitObject> HitObjectUsageBegan;
/// <summary>
/// Invoked when a <see cref="HitObject"/> becomes unused by a <see cref="DrawableHitObject"/>.
/// </summary>
/// <remarks>
/// If this <see cref="HitObjectContainer"/> uses pooled objects, this represents the time when the <see cref="HitObject"/>s become dead.
/// </remarks>
public event Action<HitObject> HitObjectUsageFinished;
/// <summary> /// <summary>
/// The <see cref="DrawableHitObject"/> contained in this Playfield. /// The <see cref="DrawableHitObject"/> contained in this Playfield.
/// </summary> /// </summary>
@ -88,6 +104,8 @@ namespace osu.Game.Rulesets.UI
{ {
h.NewResult += (d, r) => NewResult?.Invoke(d, r); h.NewResult += (d, r) => NewResult?.Invoke(d, r);
h.RevertResult += (d, r) => RevertResult?.Invoke(d, r); h.RevertResult += (d, r) => RevertResult?.Invoke(d, r);
h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o);
h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o);
})); }));
} }
@ -136,6 +154,8 @@ namespace osu.Game.Rulesets.UI
return false; return false;
} }
private readonly Dictionary<HitObject, HitObjectLifetimeEntry> lifetimeEntryMap = new Dictionary<HitObject, HitObjectLifetimeEntry>();
/// <summary> /// <summary>
/// Adds a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> to this <see cref="Playfield"/>. /// Adds a <see cref="HitObjectLifetimeEntry"/> for a pooled <see cref="HitObject"/> to this <see cref="Playfield"/>.
/// </summary> /// </summary>
@ -143,6 +163,7 @@ namespace osu.Game.Rulesets.UI
public virtual void Add(HitObjectLifetimeEntry entry) public virtual void Add(HitObjectLifetimeEntry entry)
{ {
HitObjectContainer.Add(entry); HitObjectContainer.Add(entry);
lifetimeEntryMap[entry.HitObject] = entry;
OnHitObjectAdded(entry.HitObject); OnHitObjectAdded(entry.HitObject);
} }
@ -155,6 +176,7 @@ namespace osu.Game.Rulesets.UI
{ {
if (HitObjectContainer.Remove(entry)) if (HitObjectContainer.Remove(entry))
{ {
lifetimeEntryMap.Remove(entry.HitObject);
OnHitObjectRemoved(entry.HitObject); OnHitObjectRemoved(entry.HitObject);
return true; return true;
} }
@ -183,6 +205,77 @@ namespace osu.Game.Rulesets.UI
{ {
} }
/// <summary>
/// Sets whether to keep a given <see cref="HitObject"/> always alive within this or any nested <see cref="Playfield"/>.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> to set.</param>
/// <param name="keepAlive">Whether to keep <paramref name="hitObject"/> always alive.</param>
public void SetKeepAlive(HitObject hitObject, bool keepAlive)
{
if (lifetimeEntryMap.TryGetValue(hitObject, out var entry))
{
entry.KeepAlive = keepAlive;
return;
}
if (!nestedPlayfields.IsValueCreated)
return;
foreach (var p in nestedPlayfields.Value)
p.SetKeepAlive(hitObject, keepAlive);
}
/// <summary>
/// Keeps all <see cref="HitObject"/>s alive within this and all nested <see cref="Playfield"/>s.
/// </summary>
public void KeepAllAlive()
{
foreach (var (_, entry) in lifetimeEntryMap)
entry.KeepAlive = true;
if (!nestedPlayfields.IsValueCreated)
return;
foreach (var p in nestedPlayfields.Value)
p.KeepAllAlive();
}
/// <summary>
/// The amount of time prior to the current time within which <see cref="HitObject"/>s should be considered alive.
/// </summary>
public double PastLifetimeExtension
{
get => HitObjectContainer.PastLifetimeExtension;
set
{
HitObjectContainer.PastLifetimeExtension = value;
if (!nestedPlayfields.IsValueCreated)
return;
foreach (var nested in nestedPlayfields.Value)
nested.PastLifetimeExtension = value;
}
}
/// <summary>
/// The amount of time after the current time within which <see cref="HitObject"/>s should be considered alive.
/// </summary>
public double FutureLifetimeExtension
{
get => HitObjectContainer.FutureLifetimeExtension;
set
{
HitObjectContainer.FutureLifetimeExtension = value;
if (!nestedPlayfields.IsValueCreated)
return;
foreach (var nested in nestedPlayfields.Value)
nested.FutureLifetimeExtension = value;
}
}
/// <summary> /// <summary>
/// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided. /// The cursor currently being used by this <see cref="Playfield"/>. May be null if no cursor is provided.
/// </summary> /// </summary>
@ -208,6 +301,8 @@ namespace osu.Game.Rulesets.UI
otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r); otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r);
otherPlayfield.RevertResult += (d, r) => RevertResult?.Invoke(d, r); otherPlayfield.RevertResult += (d, r) => RevertResult?.Invoke(d, r);
otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h);
otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h);
nestedPlayfields.Value.Add(otherPlayfield); nestedPlayfields.Value.Add(otherPlayfield);
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -34,6 +35,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected SelectionHandler SelectionHandler { get; private set; } protected SelectionHandler SelectionHandler { get; private set; }
protected readonly HitObjectComposer Composer;
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; } private IEditorChangeHandler changeHandler { get; set; }
@ -44,12 +47,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected EditorBeatmap Beatmap { get; private set; } protected EditorBeatmap Beatmap { get; private set; }
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>(); private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
private readonly Dictionary<HitObject, SelectionBlueprint> blueprintMap = new Dictionary<HitObject, SelectionBlueprint>();
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private IPositionSnapProvider snapProvider { get; set; } private IPositionSnapProvider snapProvider { get; set; }
protected BlueprintContainer() protected BlueprintContainer(HitObjectComposer composer)
{ {
Composer = composer;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
@ -68,8 +74,12 @@ namespace osu.Game.Screens.Edit.Compose.Components
DragBox.CreateProxy().With(p => p.Depth = float.MinValue) DragBox.CreateProxy().With(p => p.Depth = float.MinValue)
}); });
foreach (var obj in Beatmap.HitObjects) // For non-pooled rulesets, hitobjects are already present in the playfield which allows the blueprints to be loaded in the async context.
AddBlueprintFor(obj); if (Composer != null)
{
foreach (var obj in Composer.HitObjects)
addBlueprintFor(obj.HitObject);
}
selectedHitObjects.BindTo(Beatmap.SelectedHitObjects); selectedHitObjects.BindTo(Beatmap.SelectedHitObjects);
selectedHitObjects.CollectionChanged += (selectedObjects, args) => selectedHitObjects.CollectionChanged += (selectedObjects, args) =>
@ -94,8 +104,18 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
base.LoadComplete(); base.LoadComplete();
Beatmap.HitObjectAdded += AddBlueprintFor; Beatmap.HitObjectAdded += addBlueprintFor;
Beatmap.HitObjectRemoved += removeBlueprintFor; Beatmap.HitObjectRemoved += removeBlueprintFor;
if (Composer != null)
{
// For pooled rulesets, blueprints must be added for hitobjects already "current" as they would've not been "current" during the async load addition process above.
foreach (var obj in Composer.HitObjects)
addBlueprintFor(obj.HitObject);
Composer.Playfield.HitObjectUsageBegan += addBlueprintFor;
Composer.Playfield.HitObjectUsageFinished += removeBlueprintFor;
}
} }
protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() => protected virtual Container<SelectionBlueprint> CreateSelectionBlueprintContainer() =>
@ -247,29 +267,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
#region Blueprint Addition/Removal #region Blueprint Addition/Removal
private void removeBlueprintFor(HitObject hitObject) private void addBlueprintFor(HitObject hitObject)
{ {
var blueprint = SelectionBlueprints.SingleOrDefault(m => m.HitObject == hitObject); if (blueprintMap.ContainsKey(hitObject))
if (blueprint == null)
return; return;
blueprint.Deselect();
blueprint.Selected -= onBlueprintSelected;
blueprint.Deselected -= onBlueprintDeselected;
SelectionBlueprints.Remove(blueprint);
if (movementBlueprint == blueprint)
finishSelectionMovement();
}
protected virtual void AddBlueprintFor(HitObject hitObject)
{
var blueprint = CreateBlueprintFor(hitObject); var blueprint = CreateBlueprintFor(hitObject);
if (blueprint == null) if (blueprint == null)
return; return;
blueprintMap[hitObject] = blueprint;
blueprint.Selected += onBlueprintSelected; blueprint.Selected += onBlueprintSelected;
blueprint.Deselected += onBlueprintDeselected; blueprint.Deselected += onBlueprintDeselected;
@ -277,6 +285,41 @@ namespace osu.Game.Screens.Edit.Compose.Components
blueprint.Select(); blueprint.Select();
SelectionBlueprints.Add(blueprint); SelectionBlueprints.Add(blueprint);
OnBlueprintAdded(hitObject);
}
private void removeBlueprintFor(HitObject hitObject)
{
if (!blueprintMap.Remove(hitObject, out var blueprint))
return;
blueprint.Deselect();
blueprint.Selected -= onBlueprintSelected;
blueprint.Deselected -= onBlueprintDeselected;
SelectionBlueprints.Remove(blueprint);
if (movementBlueprint == blueprint)
finishSelectionMovement();
OnBlueprintRemoved(hitObject);
}
/// <summary>
/// Called after a <see cref="HitObject"/> blueprint has been added.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> for which the blueprint has been added.</param>
protected virtual void OnBlueprintAdded(HitObject hitObject)
{
}
/// <summary>
/// Called after a <see cref="HitObject"/> blueprint has been removed.
/// </summary>
/// <param name="hitObject">The <see cref="HitObject"/> for which the blueprint has been removed.</param>
protected virtual void OnBlueprintRemoved(HitObject hitObject)
{
} }
#endregion #endregion
@ -349,7 +392,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <summary> /// <summary>
/// Selects all <see cref="SelectionBlueprint"/>s. /// Selects all <see cref="SelectionBlueprint"/>s.
/// </summary> /// </summary>
private void selectAll() => SelectionBlueprints.ToList().ForEach(m => m.Select()); private void selectAll()
{
Composer.Playfield.KeepAllAlive();
// Scheduled to allow the change in lifetime to take place.
Schedule(() => SelectionBlueprints.ToList().ForEach(m => m.Select()));
}
/// <summary> /// <summary>
/// Deselects all selected <see cref="SelectionBlueprint"/>s. /// Deselects all selected <see cref="SelectionBlueprint"/>s.
@ -360,12 +409,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
SelectionHandler.HandleSelected(blueprint); SelectionHandler.HandleSelected(blueprint);
SelectionBlueprints.ChangeChildDepth(blueprint, 1); SelectionBlueprints.ChangeChildDepth(blueprint, 1);
Composer.Playfield.SetKeepAlive(blueprint.HitObject, true);
} }
private void onBlueprintDeselected(SelectionBlueprint blueprint) private void onBlueprintDeselected(SelectionBlueprint blueprint)
{ {
SelectionHandler.HandleDeselected(blueprint); SelectionHandler.HandleDeselected(blueprint);
SelectionBlueprints.ChangeChildDepth(blueprint, 0); SelectionBlueprints.ChangeChildDepth(blueprint, 0);
Composer.Playfield.SetKeepAlive(blueprint.HitObject, false);
} }
#endregion #endregion
@ -456,9 +509,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (Beatmap != null) if (Beatmap != null)
{ {
Beatmap.HitObjectAdded -= AddBlueprintFor; Beatmap.HitObjectAdded -= addBlueprintFor;
Beatmap.HitObjectRemoved -= removeBlueprintFor; Beatmap.HitObjectRemoved -= removeBlueprintFor;
} }
if (Composer != null)
{
Composer.Playfield.HitObjectUsageBegan -= addBlueprintFor;
Composer.Playfield.HitObjectUsageFinished -= removeBlueprintFor;
}
} }
} }
} }

View File

@ -27,23 +27,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
public class ComposeBlueprintContainer : BlueprintContainer public class ComposeBlueprintContainer : BlueprintContainer
{ {
[Resolved] public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
private HitObjectComposer composer { get; set; }
private PlacementBlueprint currentPlacement;
private readonly Container<PlacementBlueprint> placementBlueprintContainer; private readonly Container<PlacementBlueprint> placementBlueprintContainer;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private PlacementBlueprint currentPlacement;
private InputManager inputManager; private InputManager inputManager;
private readonly IEnumerable<DrawableHitObject> drawableHitObjects; public ComposeBlueprintContainer(HitObjectComposer composer)
: base(composer)
public ComposeBlueprintContainer(IEnumerable<DrawableHitObject> drawableHitObjects)
{ {
this.drawableHitObjects = drawableHitObjects;
placementBlueprintContainer = new Container<PlacementBlueprint> placementBlueprintContainer = new Container<PlacementBlueprint>
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
@ -162,7 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementPosition() private void updatePlacementPosition()
{ {
var snapResult = composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); var snapResult = Composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position);
currentPlacement.UpdatePosition(snapResult); currentPlacement.UpdatePosition(snapResult);
} }
@ -173,7 +166,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
base.Update(); base.Update();
if (composer.CursorInPlacementArea) if (Composer.CursorInPlacementArea)
createPlacement(); createPlacement();
else if (currentPlacement?.PlacementActive == false) else if (currentPlacement?.PlacementActive == false)
removePlacement(); removePlacement();
@ -186,7 +179,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject)
{ {
var drawable = drawableHitObjects.FirstOrDefault(d => d.HitObject == hitObject); var drawable = Composer.HitObjects.FirstOrDefault(d => d.HitObject == hitObject);
if (drawable == null) if (drawable == null)
return null; return null;
@ -196,11 +189,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; public virtual OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null;
protected override void AddBlueprintFor(HitObject hitObject) protected override void OnBlueprintAdded(HitObject hitObject)
{ {
refreshTool(); base.OnBlueprintAdded(hitObject);
base.AddBlueprintFor(hitObject); refreshTool();
// on successful placement, the new combo button should be reset as this is the most common user interaction. // on successful placement, the new combo button should be reset as this is the most common user interaction.
if (Beatmap.SelectedHitObjects.Count == 0) if (Beatmap.SelectedHitObjects.Count == 0)

View File

@ -219,6 +219,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved] [Resolved]
private IBeatSnapProvider beatSnapProvider { get; set; } private IBeatSnapProvider beatSnapProvider { get; set; }
/// <summary>
/// The total amount of time visible on the timeline.
/// </summary>
public double VisibleRange => track.Length / Zoom;
public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) =>
new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition))));

View File

@ -26,12 +26,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private EditorBeatmap beatmap { get; set; } private EditorBeatmap beatmap { get; set; }
private DragEvent lastDragEvent; private DragEvent lastDragEvent;
private Bindable<HitObject> placement; private Bindable<HitObject> placement;
private SelectionBlueprint placementBlueprint; private SelectionBlueprint placementBlueprint;
public TimelineBlueprintContainer() public TimelineBlueprintContainer(HitObjectComposer composer)
: base(composer)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
@ -97,6 +96,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
if (lastDragEvent != null) if (lastDragEvent != null)
OnDrag(lastDragEvent); OnDrag(lastDragEvent);
if (Composer != null)
{
Composer.Playfield.PastLifetimeExtension = timeline.VisibleRange / 2;
Composer.Playfield.FutureLifetimeExtension = timeline.VisibleRange / 2;
}
base.Update(); base.Update();
} }

View File

@ -53,6 +53,6 @@ namespace osu.Game.Screens.Edit.Compose
return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer)); return beatmapSkinProvider.WithChild(rulesetSkinProvider.WithChild(composer));
} }
protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(); protected override Drawable CreateTimelineContent() => composer == null ? base.CreateTimelineContent() : new TimelineBlueprintContainer(composer);
} }
} }