mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
每列单例 watcher, O(columns)订阅,降低峰值GC,但invalidation显著升高,整体愿意接受
This commit is contained in:
@@ -11,7 +11,7 @@ namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkHitObject : BenchmarkTest
|
||||
{
|
||||
[Params(1, 100, 1000)]
|
||||
[Params(1, 1000, 5000)]
|
||||
public int Count { get; set; }
|
||||
|
||||
[Params(false, true)]
|
||||
|
||||
@@ -73,7 +73,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
|
||||
accentColour = column.AccentColour.GetBoundCopy();
|
||||
// Use shared column bindable to prevent per-instance allocation via GetBoundCopy().
|
||||
accentColour = column.AccentColour;
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
largeFaint.Colour = Interpolation.ValueAt(0.8f, colour.NewValue, Color4.White, 0, 1);
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
private Circle hitTargetLine = null!;
|
||||
|
||||
private CircularContainer? topIcon;
|
||||
private Box? topIconBox;
|
||||
private Bindable<Color4> accentColour = null!;
|
||||
|
||||
[Resolved]
|
||||
@@ -123,7 +124,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
|
||||
accentColour = column.AccentColour.GetBoundCopy();
|
||||
// Use the column's shared bindable to avoid per-instance allocations from GetBoundCopy()
|
||||
accentColour = column.AccentColour;
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
background.Colour = colour.NewValue.Darken(0.2f);
|
||||
@@ -142,13 +144,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
double bpm = beatmap.BeatmapInfo.BPM * gameplayClock.GetTrueGameplayRate();
|
||||
beatInterval = 60000 / bpm;
|
||||
// cache reference to inner box to avoid LINQ allocations during Update
|
||||
topIconBox = topIcon?.Children.OfType<Box>().FirstOrDefault();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (topIcon == null || !topIcon.Children.Any())
|
||||
if (topIconBox == null)
|
||||
return;
|
||||
|
||||
double progress = (gameplayClock.CurrentTime % beatInterval) / beatInterval;
|
||||
@@ -156,7 +160,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
if (progress < gameplayClock.ElapsedFrameTime / beatInterval)
|
||||
{
|
||||
double fadeTime = Math.Max(1, beatInterval / 2);
|
||||
var box = topIcon.Children.OfType<Box>().FirstOrDefault();
|
||||
var box = topIconBox;
|
||||
|
||||
box?.FadeTo(1, fadeTime)
|
||||
.Then()
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
private Container<Circle> bottomIcon = null!;
|
||||
private CircularContainer topIcon = null!;
|
||||
private Box? topIconBox;
|
||||
private Bindable<Color4> accentColour = null!;
|
||||
|
||||
[Resolved]
|
||||
@@ -162,12 +163,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
|
||||
// double bpm = beatmap.BeatmapInfo.BPM;
|
||||
double bpm = beatmap.ControlPointInfo.TimingPointAt(gameplayClock.CurrentTime).BPM * gameplayClock.GetTrueGameplayRate();
|
||||
applyBlinkingEffect(topIcon, bpm);
|
||||
// cache inner box once to avoid repeated LINQ allocation inside scheduled callback
|
||||
topIconBox = topIcon.Children.OfType<Box>().FirstOrDefault();
|
||||
applyBlinkingEffect(topIconBox, bpm);
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
|
||||
accentColour = column.AccentColour.GetBoundCopy();
|
||||
// Use the column's shared bindable to avoid per-instance allocations from GetBoundCopy()
|
||||
accentColour = column.AccentColour;
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
background.Colour = colour.NewValue.Darken(0.2f);
|
||||
@@ -178,13 +182,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
column.TopLevelContainer.Add(CreateProxy());
|
||||
}
|
||||
|
||||
private void applyBlinkingEffect(CircularContainer container, double bpm)
|
||||
private void applyBlinkingEffect(Box? box, double bpm)
|
||||
{
|
||||
if (box == null) return;
|
||||
|
||||
double interval = 60000 / bpm;
|
||||
|
||||
Scheduler.AddDelayed(() =>
|
||||
{
|
||||
container.Children.OfType<Box>().First().FadeTo(1, interval / 2).Then().FadeTo(0, interval / 2);
|
||||
box.FadeTo(1, interval / 2).Then().FadeTo(0, interval / 2);
|
||||
}, interval, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
private readonly Circle colouredBox;
|
||||
private Drawable iconContainer = null!;
|
||||
|
||||
public Ez2NotePiece()
|
||||
{
|
||||
@@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
CornerRadius = CORNER_RADIUS;
|
||||
// Masking = true;
|
||||
|
||||
iconContainer = CreateIcon();
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new Container
|
||||
@@ -83,7 +86,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 0,
|
||||
},
|
||||
CreateIcon(),
|
||||
iconContainer,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,7 +102,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Ez2
|
||||
// NoteHeight = (float)config.Get<double>(ManiaRulesetSetting.ColumnWidth);
|
||||
// specialFactor = (float)config.Get<double>(ManiaRulesetSetting.SpecialFactor);
|
||||
|
||||
CreateIcon().Size = new Vector2(DrawWidth / NoteHeight * 0.7f);
|
||||
if (iconContainer != null)
|
||||
iconContainer.Size = new Vector2(DrawWidth / NoteHeight * 0.7f);
|
||||
}
|
||||
|
||||
protected virtual Drawable CreateIcon() => new Container
|
||||
|
||||
@@ -87,7 +87,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||
// 计算 drawSeparator 结果(基于不变的列数和列索引)
|
||||
shouldDrawSeparator = drawSeparatorImpl(Column.Index, stageDefinition);
|
||||
|
||||
accentColour = new Bindable<Color4>(ezSkinConfig.GetColumnColor(stageDefinition.Columns, Column.Index));
|
||||
// 使用 Column 提供的共享 bindable,避免为每个列背景构建新的 Bindable 实例
|
||||
accentColour = Column.AccentColour;
|
||||
accentColour.BindValueChanged(colour =>
|
||||
{
|
||||
var baseCol = colour.NewValue;
|
||||
@@ -104,7 +105,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (Column.BackgroundContainer.Children.OfType<Box>().All(b => b.Name != "Separator"))
|
||||
bool hasSeparator = false;
|
||||
|
||||
foreach (var child in Column.BackgroundContainer.Children)
|
||||
{
|
||||
if (child is Box b && b.Name == "Separator")
|
||||
{
|
||||
hasSeparator = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSeparator)
|
||||
Column.BackgroundContainer.Add(separator);
|
||||
|
||||
if (!Column.BackgroundContainer.Children.Contains(hitOverlay))
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -101,9 +104,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||
NoteSetName = Column.NoteSetBindable;
|
||||
NoteSize = Column.NoteSizeBindable;
|
||||
|
||||
Column.NoteSetChanged += OnNoteSetChanged;
|
||||
Column.NoteColourChanged += OnColourChanged;
|
||||
Column.NoteSizeChanged += OnNoteSizeChanged;
|
||||
// Use a per-column watcher to avoid creating an event handler delegate per-note.
|
||||
ColumnWatcher.GetOrCreate(Column).Add(this);
|
||||
|
||||
UpdateSize();
|
||||
}
|
||||
@@ -153,7 +155,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||
sideLine.UpdateGlowEffect(NoteColor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnColourChanged()
|
||||
@@ -197,13 +198,122 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
Column.NoteSetChanged -= OnNoteSetChanged;
|
||||
Column.NoteColourChanged -= OnColourChanged;
|
||||
Column.NoteSizeChanged -= OnNoteSizeChanged;
|
||||
ColumnWatcher.Remove(Column, this);
|
||||
}
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
// Forwarders used by ColumnWatcher when broadcasting per-column changes to instances.
|
||||
internal void ForwardOnNoteSetChanged(ValueChangedEvent<string> e) => OnNoteSetChanged(e);
|
||||
internal void ForwardOnColourChanged() => OnColourChanged();
|
||||
internal void ForwardOnNoteSizeChanged() => OnNoteSizeChanged();
|
||||
|
||||
private class ColumnWatcher
|
||||
{
|
||||
private readonly Column column;
|
||||
private readonly List<WeakReference<EzNoteBase>> notes = new List<WeakReference<EzNoteBase>>();
|
||||
|
||||
public ColumnWatcher(Column column)
|
||||
{
|
||||
this.column = column;
|
||||
column.NoteSetChanged += onNoteSetChanged;
|
||||
column.NoteColourChanged += onNoteColourChanged;
|
||||
column.NoteSizeChanged += onNoteSizeChanged;
|
||||
}
|
||||
|
||||
public void Add(EzNoteBase note)
|
||||
{
|
||||
// store weak reference to avoid preventing note GC if removed elsewhere
|
||||
notes.Add(new WeakReference<EzNoteBase>(note));
|
||||
}
|
||||
|
||||
public void Remove(EzNoteBase note)
|
||||
{
|
||||
notes.RemoveAll(wr => !wr.TryGetTarget(out var target) || ReferenceEquals(target, note));
|
||||
}
|
||||
|
||||
private void onNoteSetChanged(ValueChangedEvent<string> e)
|
||||
{
|
||||
// iterate backwards and remove dead weak references in-place to avoid
|
||||
// allocating a large temporary array when the column has many notes.
|
||||
for (int i = notes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var wr = notes[i];
|
||||
if (wr.TryGetTarget(out var target))
|
||||
target.ForwardOnNoteSetChanged(e);
|
||||
else
|
||||
notes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void onNoteColourChanged()
|
||||
{
|
||||
for (int i = notes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var wr = notes[i];
|
||||
if (wr.TryGetTarget(out var target))
|
||||
target.ForwardOnColourChanged();
|
||||
else
|
||||
notes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
private void onNoteSizeChanged()
|
||||
{
|
||||
for (int i = notes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var wr = notes[i];
|
||||
if (wr.TryGetTarget(out var target))
|
||||
target.ForwardOnNoteSizeChanged();
|
||||
else
|
||||
notes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void Unsubscribe()
|
||||
{
|
||||
column.NoteSetChanged -= onNoteSetChanged;
|
||||
column.NoteColourChanged -= onNoteColourChanged;
|
||||
column.NoteSizeChanged -= onNoteSizeChanged;
|
||||
}
|
||||
|
||||
public bool IsEmpty => notes.All(wr => !wr.TryGetTarget(out _));
|
||||
|
||||
private static readonly Dictionary<Column, ColumnWatcher> watchers = new Dictionary<Column, ColumnWatcher>();
|
||||
private static readonly object watcher_lock = new object();
|
||||
|
||||
public static ColumnWatcher GetOrCreate(Column c)
|
||||
{
|
||||
lock (watcher_lock)
|
||||
{
|
||||
if (!watchers.TryGetValue(c, out var w))
|
||||
{
|
||||
w = new ColumnWatcher(c);
|
||||
watchers[c] = w;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Remove(Column c, EzNoteBase note)
|
||||
{
|
||||
lock (watcher_lock)
|
||||
{
|
||||
if (!watchers.TryGetValue(c, out var w))
|
||||
return;
|
||||
|
||||
w.Remove(note);
|
||||
|
||||
if (w.IsEmpty)
|
||||
{
|
||||
w.Unsubscribe();
|
||||
watchers.Remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
osu.sln
3
osu.sln
@@ -128,7 +128,6 @@ Global
|
||||
{F5037F74-E137-4940-8CE7-4D3E5DF0FB39}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F5037F74-E137-4940-8CE7-4D3E5DF0FB39}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
@@ -136,9 +135,7 @@ Global
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E8F3B2C4-9D7A-4E6B-8C2F-7A9B12345678}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
|
||||
Reference in New Issue
Block a user