diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzJudgementLine.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzJudgementLine.cs index 23aa45baf9..50be562507 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzJudgementLine.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzJudgementLine.cs @@ -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); + } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzKeyArea.cs index d7f564f824..969fe33de7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzKeyArea.cs @@ -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); + } } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 93d14e988f..5a745c673e 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -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) diff --git a/osu.Game/LAsEzExtensions/Audio/DuplicateVirtualTrack.cs b/osu.Game/LAsEzExtensions/Audio/DuplicateVirtualTrack.cs index a3d586cd96..97a8498cde 100644 --- a/osu.Game/LAsEzExtensions/Audio/DuplicateVirtualTrack.cs +++ b/osu.Game/LAsEzExtensions/Audio/DuplicateVirtualTrack.cs @@ -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(); } // 恢复之前添加的音量调整(如果存在)。 diff --git a/osu.Game/LAsEzExtensions/Audio/InputAudioLatencyTracker.cs b/osu.Game/LAsEzExtensions/Audio/InputAudioLatencyTracker.cs index cfc38505af..8398fb0a82 100644 --- a/osu.Game/LAsEzExtensions/Audio/InputAudioLatencyTracker.cs +++ b/osu.Game/LAsEzExtensions/Audio/InputAudioLatencyTracker.cs @@ -33,6 +33,7 @@ namespace osu.Game.LAsEzExtensions.Audio private readonly EzLatencyManager latencyManager; private Bindable? inputAudioLatencyConfigBindable; private Action>? inputAudioLatencyConfigHandler; + private Action>? enabledChangedHandler; /// /// 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(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; } diff --git a/osu.Game/LAsEzExtensions/EzLocalTextureFactory.cs b/osu.Game/LAsEzExtensions/EzLocalTextureFactory.cs index 8d1afe16e4..b8f0b6c7d8 100644 --- a/osu.Game/LAsEzExtensions/EzLocalTextureFactory.cs +++ b/osu.Game/LAsEzExtensions/EzLocalTextureFactory.cs @@ -181,7 +181,7 @@ namespace osu.Game.LAsEzExtensions noteSizeBindables[cacheKey] = source; } - return source.GetBoundCopy(); + return source; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index db0013f94f..0657b3372f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -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; } /// @@ -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(Ez2Setting.InputAudioLatencyTracker)) { - var ezConfig = dependencies.Get(); - 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().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); + } + /// /// Seek to a specific time in gameplay. /// @@ -1281,6 +1300,9 @@ namespace osu.Game.Screens.Play fadeOut(); + if (!isRestarting) + scheduleGameplayTextureCleanup(); + return base.OnExiting(e); } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index d3ea50bf7d..b7426db230 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -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 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; } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index d50fc2e3d0..8d98a3823f 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -166,6 +166,11 @@ namespace osu.Game.Skinning Samples = samples; } + public void RecycleTextures(bool disposeAtlas = false) + { + Textures?.ClearCache(disposeAtlas); + } + protected virtual IResourceStore CreateTextureLoaderStore(IStorageResourceProvider resources, IResourceStore storage) => new MaxDimensionLimitedTextureLoaderStore(resources.CreateTextureLoaderStore(storage)); diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index a7197a31d3..1cda8cf48d 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -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();