[内存调试]解决延迟追踪和虚拟音轨的内存泄露风险。

This commit is contained in:
LA
2026-03-09 20:51:21 +08:00
parent 06b3f1fc82
commit c47726392a
10 changed files with 114 additions and 50 deletions

View File

@@ -95,5 +95,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
// }
// private void invalidateLayout() => layout.Invalidate();
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
noteSetName.UnbindBindings();
hitPositonBindable.UnbindBindings();
columnWidth.UnbindBindings();
}
base.Dispose(isDisposing);
}
}
}

View File

@@ -222,5 +222,17 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
TANOc2,
TECHNIKA,
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
stageName.UnbindBindings();
hitPositonBindable.UnbindBindings();
noteSize.UnbindBindings();
}
base.Dispose(isDisposing);
}
}
}

View File

@@ -217,6 +217,11 @@ namespace osu.Game.Rulesets.Mania.UI
NoteSizeBindable.ValueChanged -= onNoteSizeBindableChanged;
ezConfig.OnNoteColourChanged -= onNoteColourChanged;
}
NoteSetBindable.UnbindBindings();
NoteSizeBindable.UnbindBindings();
EzColumnColourBindable.UnbindBindings();
hitModeBindable.UnbindBindings();
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)

View File

@@ -161,13 +161,7 @@ namespace osu.Game.LAsEzExtensions.Audio
beatmapTrackMuteAdjustment = null;
mutedOriginalTrack = null;
// 停止并释放候选轨(若为独立实例)
if (activeCandidateTrack != null)
{
activeCandidateTrack.Stop();
activeCandidateTrack = null;
ownsCandidateTrack = false;
}
releaseActiveCandidateTrack();
lastLoopStartGameplayTime = null;
@@ -181,6 +175,36 @@ namespace osu.Game.LAsEzExtensions.Audio
base.Dispose(isDisposing);
}
private void releaseActiveCandidateTrack()
{
if (activeCandidateTrack == null)
return;
try
{
activeCandidateTrack.Stop();
}
catch (Exception ex)
{
log($"failed to stop active candidate track during release: {ex}");
}
if (ownsCandidateTrack)
{
try
{
activeCandidateTrack.Dispose();
}
catch (Exception ex)
{
log($"failed to dispose owned candidate track: {ex}");
}
}
activeCandidateTrack = null;
ownsCandidateTrack = false;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@@ -771,13 +795,7 @@ namespace osu.Game.LAsEzExtensions.Audio
log($"restored candidate track.Looping to {prevCandidateLooping.Value}");
}
if (ownsCandidateTrack)
{
// do any necessary cleanup for owned track instances if required
}
activeCandidateTrack = null;
ownsCandidateTrack = false;
releaseActiveCandidateTrack();
}
// 恢复之前添加的音量调整(如果存在)。

View File

@@ -33,6 +33,7 @@ namespace osu.Game.LAsEzExtensions.Audio
private readonly EzLatencyManager latencyManager;
private Bindable<bool>? inputAudioLatencyConfigBindable;
private Action<ValueChangedEvent<bool>>? inputAudioLatencyConfigHandler;
private Action<ValueChangedEvent<bool>>? enabledChangedHandler;
/// <summary>
/// Global instance for unified access
@@ -53,6 +54,9 @@ namespace osu.Game.LAsEzExtensions.Audio
Logger.Log("InputAudioLatencyTracker.Initialize called", LoggingTarget.Runtime, LogLevel.Debug);
scoreProcessor = processor;
// Start each gameplay session from a clean latency dataset.
latencyManager.ClearStatistics();
// 将 Ez2Setting 的启用状态绑定到 EzLatencyManager
inputAudioLatencyConfigBindable = ezConfig.GetBindable<bool>(Ez2Setting.InputAudioLatencyTracker);
// Avoid BindTo here to prevent repeated double-binding errors when Initialize is called multiple times.
@@ -64,13 +68,15 @@ namespace osu.Game.LAsEzExtensions.Audio
latencyManager.OnNewRecord += OnLatencyRecordGenerated;
// 绑定启用状态变化,控制生命周期
latencyManager.Enabled.BindValueChanged(enabled =>
enabledChangedHandler = enabled =>
{
if (enabled.NewValue)
Start();
else
Stop();
}, true);
};
latencyManager.Enabled.BindValueChanged(enabledChangedHandler, true);
}
private bool started;
@@ -156,6 +162,8 @@ namespace osu.Game.LAsEzExtensions.Audio
$"Latency analysis complete!\nInput→Judge: {stats.AvgInputToJudge:F1}ms\nInput→Audio: {stats.AvgInputToPlayback:F1}ms\nAudio→Judge: {stats.AvgPlaybackToJudge:F1}ms\nRecords: {stats.RecordCount}",
Icon = FontAwesome.Solid.ChartLine,
});
latencyManager.ClearStatistics();
}
private void OnNewJudgement(JudgementResult result)
@@ -185,11 +193,15 @@ namespace osu.Game.LAsEzExtensions.Audio
// 解绑事件
latencyManager.OnNewRecord -= OnLatencyRecordGenerated;
latencyManager.Dispose();
if (enabledChangedHandler != null)
latencyManager.Enabled.ValueChanged -= enabledChangedHandler;
if (inputAudioLatencyConfigBindable != null && inputAudioLatencyConfigHandler != null)
inputAudioLatencyConfigBindable.ValueChanged -= inputAudioLatencyConfigHandler;
latencyManager.ClearStatistics();
Instance = null;
}

View File

@@ -181,7 +181,7 @@ namespace osu.Game.LAsEzExtensions
noteSizeBindables[cacheKey] = source;
}
return source.GetBoundCopy();
return source;
}
}

View File

@@ -213,6 +213,8 @@ namespace osu.Game.Screens.Play
protected GameplayClockContainer GameplayClockContainer { get; private set; }
protected InputAudioLatencyTracker LatencyTracker { get; private set; }
public DimmableStoryboard DimmableStoryboard { get; private set; }
/// <summary>
@@ -259,6 +261,13 @@ namespace osu.Game.Screens.Play
gameActive.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
}
private void scheduleGameplayTextureCleanup()
{
var beatmapSkin = Beatmap.Value?.Skin as Skin;
beatmapSkin?.RecycleTextures(disposeAtlas: true);
beatmapSkin?.RecycleSamples();
}
protected override void Update()
{
base.Update();
@@ -278,7 +287,7 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader(true)]
private void load(OsuConfigManager config, OsuGameBase game, CancellationToken cancellationToken)
private void load(OsuConfigManager config, OsuGameBase game, CancellationToken cancellationToken, Ez2ConfigManager ez2Config)
{
var gameplayMods = Mods.Value.Select(m => m.DeepClone()).ToArray();
@@ -317,14 +326,13 @@ namespace osu.Game.Screens.Play
dependencies.CacheAs(ScoreProcessor);
// Initialize InputAudioLatencyTracker if available in DI
// 初始化InputAudioLatencyTracker
if (GlobalConfigStore.EzConfig.Get<bool>(Ez2Setting.InputAudioLatencyTracker))
{
var ezConfig = dependencies.Get<Ez2ConfigManager>();
var tracker = new InputAudioLatencyTracker(ezConfig);
dependencies.CacheAs(tracker);
tracker.Initialize(ScoreProcessor);
tracker.Start();
LatencyTracker = new InputAudioLatencyTracker(ez2Config);
dependencies.CacheAs(LatencyTracker);
LatencyTracker.Initialize(ScoreProcessor);
LatencyTracker.Start();
}
HealthProcessor = gameplayMods.OfType<IApplicableHealthProcessor>().FirstOrDefault()?.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
@@ -792,6 +800,17 @@ namespace osu.Game.Screens.Play
updateSampleDisabledState();
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
LatencyTracker?.Dispose();
LatencyTracker = null;
}
base.Dispose(isDisposing);
}
/// <summary>
/// Seek to a specific time in gameplay.
/// </summary>
@@ -1281,6 +1300,9 @@ namespace osu.Game.Screens.Play
fadeOut();
if (!isRestarting)
scheduleGameplayTextureCleanup();
return base.OnExiting(e);
}

View File

@@ -55,9 +55,6 @@ namespace osu.Game.Screens.Play
[Resolved]
private Ez2ConfigManager ezConfig { get; set; }
[CanBeNull]
private InputAudioLatencyTracker latencyTracker;
private readonly object scoreSubmissionLock = new object();
private TaskCompletionSource<bool> scoreSubmissionSource;
@@ -85,20 +82,6 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
});
// 保存配置实例并初始化延迟追踪
latencyTracker = new InputAudioLatencyTracker(ezConfig);
latencyTracker?.Initialize(ScoreProcessor);
// Ensure tracker is started for testing scenarios in SubmittingPlayer
try
{
latencyTracker?.Start();
}
catch (Exception ex)
{
Logger.Log($"InputAudioLatencyTracker failed to Start: {ex.Message}", level: LogLevel.Error);
}
}
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) => new MasterGameplayClockContainer(beatmap, gameplayStart)
@@ -284,7 +267,7 @@ namespace osu.Game.Screens.Play
statics.SetValue(Static.LastLocalUserScore, Score?.ScoreInfo.DeepClone());
// 生成延迟报告
latencyTracker?.GenerateLatencyReport();
LatencyTracker?.GenerateLatencyReport();
return exiting;
}
@@ -348,13 +331,13 @@ namespace osu.Game.Screens.Play
if (Ruleset.Value.OnlineID == 3 && !offsetManiaBindable.IsDefault)
{
Logger.Log($"[EzMania]Score submission blocked by offset settings.", Ez2ConfigManager.LOGGER_NAME, LogLevel.Important);
Logger.Log("[EzMania]Score submission blocked by offset settings.", Ez2ConfigManager.LOGGER_NAME, LogLevel.Important);
return Task.CompletedTask;
}
if (Ruleset.Value.OnlineID != 3 && !offsetNonStdBindable.IsDefault)
{
Logger.Log($"[EzNoMania]Score submission blocked by offset settings.", Ez2ConfigManager.LOGGER_NAME, LogLevel.Important);
Logger.Log("[EzNoMania]Score submission blocked by offset settings.", Ez2ConfigManager.LOGGER_NAME, LogLevel.Important);
return Task.CompletedTask;
}

View File

@@ -166,6 +166,11 @@ namespace osu.Game.Skinning
Samples = samples;
}
public void RecycleTextures(bool disposeAtlas = false)
{
Textures?.ClearCache(disposeAtlas);
}
protected virtual IResourceStore<TextureUpload> CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore<byte[]> storage)
=> new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage));

View File

@@ -100,11 +100,6 @@ namespace osu.Game.Storyboards.Drawables
if (clock != null)
Clock = clock;
dependencies.CacheAs(typeof(TextureStore),
new TextureStore(host.Renderer, host.CreateTextureLoaderStore(
CreateResourceLookupStore()
), false, scaleAdjust: 1));
foreach (var layer in Storyboard.Layers)
{
cancellationToken?.ThrowIfCancellationRequested();