forked from ppy/osu
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHitObjectContainer.cs
More file actions
229 lines (179 loc) · 8.23 KB
/
Copy pathHitObjectContainer.cs
File metadata and controls
229 lines (179 loc) · 8.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Pooling;
namespace osu.Game.Rulesets.UI
{
public partial class HitObjectContainer : PooledDrawableWithLifetimeContainer<HitObjectLifetimeEntry, DrawableHitObject>, IHitObjectContainer
{
/// <summary>
/// All <see cref="DrawableHitObject"/>s in this container, sorted by ascending <see cref="HitObject.StartTime"/>.
/// </summary>
/// <remarks>
/// Since internal children are already sorted by descending <see cref="HitObject.StartTime"/>
/// (via <see cref="Compare"/>), we reverse-enumerate to avoid an O(n log n) sort on every access.
/// </remarks>
public IEnumerable<DrawableHitObject> Objects => enumerateByStartTimeAscending();
/// <summary>
/// All alive <see cref="DrawableHitObject"/>s in this container, sorted by ascending <see cref="HitObject.StartTime"/>.
/// </summary>
/// <remarks>
/// The alive entries dictionary is unordered, so we must sort.
/// However, the alive set is typically much smaller than the full set, making this cheaper
/// than sorting all children. We use a List + Sort (in-place) to avoid LINQ iterator allocations.
/// </remarks>
public IEnumerable<DrawableHitObject> AliveObjects => getSortedAliveObjects();
private IEnumerable<DrawableHitObject> enumerateByStartTimeAscending()
{
var children = InternalChildren;
for (int i = children.Count - 1; i >= 0; i--)
{
if (children[i] is DrawableHitObject hitObject)
yield return hitObject;
}
}
private List<DrawableHitObject> getSortedAliveObjects()
{
var list = new List<DrawableHitObject>(AliveEntries.Values);
list.Sort(static (a, b) => a.HitObject.StartTime.CompareTo(b.HitObject.StartTime));
return list;
}
/// <summary>
/// Invoked when a <see cref="DrawableHitObject"/> is judged.
/// </summary>
public event Action<DrawableHitObject, JudgementResult> NewResult;
/// <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>
internal 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>
internal event Action<HitObject> HitObjectUsageFinished;
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> nonPooledDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
[Resolved(CanBeNull = true)]
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
public HitObjectContainer()
{
RelativeSizeAxes = Axes.Both;
}
protected override void LoadAsyncComplete()
{
base.LoadAsyncComplete();
// Application of hitobjects during load() may have changed their start times, so ensure the correct sorting order.
SortInternal();
}
#region Pooling support
public override bool Remove(HitObjectLifetimeEntry entry)
{
if (!base.Remove(entry)) return false;
// This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry.
if (nonPooledDrawableMap.Remove(entry, out var drawable))
removeDrawable(drawable);
return true;
}
protected sealed override DrawableHitObject GetDrawable(HitObjectLifetimeEntry entry)
{
if (nonPooledDrawableMap.TryGetValue(entry, out var drawable))
return drawable;
return pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null) ??
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
}
protected override void AddDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
{
if (nonPooledDrawableMap.ContainsKey(entry)) return;
addDrawable(drawable);
HitObjectUsageBegan?.Invoke(entry.HitObject);
}
protected override void RemoveDrawable(HitObjectLifetimeEntry entry, DrawableHitObject drawable)
{
drawable.OnKilled();
if (nonPooledDrawableMap.ContainsKey(entry)) return;
removeDrawable(drawable);
HitObjectUsageFinished?.Invoke(entry.HitObject);
}
private void addDrawable(DrawableHitObject drawable)
{
drawable.OnNewResult += onNewResult;
bindStartTime(drawable);
AddInternal(drawable);
}
private void removeDrawable(DrawableHitObject drawable)
{
drawable.OnNewResult -= onNewResult;
unbindStartTime(drawable);
RemoveInternal(drawable, false);
}
#endregion
#region Non-pooling support
public virtual void Add(DrawableHitObject drawable)
{
if (drawable.Entry == null)
throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated");
nonPooledDrawableMap.Add(drawable.Entry, drawable);
addDrawable(drawable);
Add(drawable.Entry);
}
public virtual bool Remove(DrawableHitObject drawable)
{
if (drawable.Entry == null)
return false;
return Remove(drawable.Entry);
}
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
#endregion
private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r);
#region Comparator + StartTime tracking
private void bindStartTime(DrawableHitObject hitObject)
{
var bindable = hitObject.StartTimeBindable.GetBoundCopy();
bindable.BindValueChanged(_ =>
{
if (LoadState >= LoadState.Ready)
SortInternal();
});
startTimeMap[hitObject] = bindable;
}
private void unbindStartTime(DrawableHitObject hitObject)
{
startTimeMap[hitObject].UnbindAll();
startTimeMap.Remove(hitObject);
}
private void unbindAllStartTimes()
{
foreach (var kvp in startTimeMap)
kvp.Value.UnbindAll();
startTimeMap.Clear();
}
protected override int Compare(Drawable x, Drawable y)
{
if (!(x is DrawableHitObject xObj) || !(y is DrawableHitObject yObj))
return base.Compare(x, y);
// Put earlier hitobjects towards the end of the list, so they handle input first
int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime);
return i == 0 ? CompareReverseChildID(x, y) : i;
}
#endregion
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
unbindAllStartTimes();
}
}
}