2022-06-20 09:39:53 +00:00
// 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.
2022-06-30 08:03:19 +00:00
using System ;
2022-06-24 10:04:20 +00:00
using System.Diagnostics ;
2022-06-20 09:39:53 +00:00
using System.Linq ;
using System.Threading.Tasks ;
2022-06-30 08:03:19 +00:00
using osu.Framework.Extensions.ObjectExtensions ;
2022-06-20 09:40:05 +00:00
using osu.Framework.Logging ;
2022-06-30 08:03:19 +00:00
using osu.Framework.Platform ;
2022-07-28 07:08:27 +00:00
using osu.Framework.Threading ;
2022-06-20 09:39:53 +00:00
using osu.Game.Database ;
2022-06-30 08:03:19 +00:00
using osu.Game.Online.API ;
2022-06-20 09:39:53 +00:00
using osu.Game.Rulesets.Objects ;
namespace osu.Game.Beatmaps
{
/// <summary>
/// Handles all processing required to ensure a local beatmap is in a consistent state with any changes.
/// </summary>
2022-06-30 08:03:19 +00:00
public class BeatmapUpdater : IDisposable
2022-06-20 09:39:53 +00:00
{
2022-06-20 10:25:18 +00:00
private readonly IWorkingBeatmapCache workingBeatmapCache ;
2022-07-28 07:08:27 +00:00
2022-06-20 09:39:53 +00:00
private readonly BeatmapDifficultyCache difficultyCache ;
2022-07-28 07:08:27 +00:00
private readonly BeatmapUpdaterMetadataLookup metadataLookup ;
private const int update_queue_request_concurrency = 4 ;
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler ( update_queue_request_concurrency , nameof ( BeatmapUpdaterMetadataLookup ) ) ;
2022-06-30 08:03:19 +00:00
public BeatmapUpdater ( IWorkingBeatmapCache workingBeatmapCache , BeatmapDifficultyCache difficultyCache , IAPIProvider api , Storage storage )
2022-06-20 09:39:53 +00:00
{
2022-06-20 10:25:18 +00:00
this . workingBeatmapCache = workingBeatmapCache ;
2022-06-20 09:39:53 +00:00
this . difficultyCache = difficultyCache ;
2022-06-30 08:03:19 +00:00
2022-07-28 07:08:27 +00:00
metadataLookup = new BeatmapUpdaterMetadataLookup ( api , storage ) ;
2022-06-20 09:39:53 +00:00
}
/// <summary>
/// Queue a beatmap for background processing.
/// </summary>
2022-07-28 07:55:46 +00:00
/// <param name="beatmapSet">The managed beatmap set to update. A transaction will be opened to apply changes.</param>
/// <param name="preferOnlineFetch">Whether metadata from an online source should be preferred. If <c>true</c>, the local cache will be skipped to ensure the freshest data state possible.</param>
public void Queue ( Live < BeatmapSetInfo > beatmapSet , bool preferOnlineFetch = false )
2022-06-20 09:39:53 +00:00
{
2022-07-28 07:55:46 +00:00
Logger . Log ( $"Queueing change for local beatmap {beatmapSet}" ) ;
Task . Factory . StartNew ( ( ) = > beatmapSet . PerformRead ( b = > Process ( b , preferOnlineFetch ) ) , default , TaskCreationOptions . HideScheduler | TaskCreationOptions . RunContinuationsAsynchronously , updateScheduler ) ;
2022-06-20 09:39:53 +00:00
}
/// <summary>
/// Run all processing on a beatmap immediately.
/// </summary>
2022-07-28 07:55:46 +00:00
/// <param name="beatmapSet">The managed beatmap set to update. A transaction will be opened to apply changes.</param>
/// <param name="preferOnlineFetch">Whether metadata from an online source should be preferred. If <c>true</c>, the local cache will be skipped to ensure the freshest data state possible.</param>
public void Process ( BeatmapSetInfo beatmapSet , bool preferOnlineFetch = false ) = > beatmapSet . Realm . Write ( r = >
2022-06-20 09:39:53 +00:00
{
2022-06-24 09:27:47 +00:00
// Before we use below, we want to invalidate.
workingBeatmapCache . Invalidate ( beatmapSet ) ;
2022-06-20 09:39:53 +00:00
2022-07-28 07:55:46 +00:00
metadataLookup . Update ( beatmapSet , preferOnlineFetch ) ;
2022-06-20 09:39:53 +00:00
2022-06-24 09:27:47 +00:00
foreach ( var beatmap in beatmapSet . Beatmaps )
{
2022-06-24 10:04:20 +00:00
difficultyCache . Invalidate ( beatmap ) ;
2022-06-24 12:02:14 +00:00
var working = workingBeatmapCache . GetWorkingBeatmap ( beatmap ) ;
2022-06-24 10:04:20 +00:00
var ruleset = working . BeatmapInfo . Ruleset . CreateInstance ( ) ;
Debug . Assert ( ruleset ! = null ) ;
2022-06-20 10:25:18 +00:00
2022-06-24 10:04:20 +00:00
var calculator = ruleset . CreateDifficultyCalculator ( working ) ;
2022-06-20 10:25:18 +00:00
2022-06-24 10:04:20 +00:00
beatmap . StarRating = calculator . Calculate ( ) . StarRating ;
2022-06-24 09:27:47 +00:00
beatmap . Length = calculateLength ( working . Beatmap ) ;
beatmap . BPM = 60000 / working . Beatmap . GetMostCommonBeatLength ( ) ;
}
2022-06-24 12:02:14 +00:00
// And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required.
workingBeatmapCache . Invalidate ( beatmapSet ) ;
2022-07-07 08:37:46 +00:00
} ) ;
2022-06-20 09:39:53 +00:00
private double calculateLength ( IBeatmap b )
{
if ( ! b . HitObjects . Any ( ) )
return 0 ;
var lastObject = b . HitObjects . Last ( ) ;
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
double endTime = lastObject . GetEndTime ( ) ;
double startTime = b . HitObjects . First ( ) . StartTime ;
return endTime - startTime ;
}
2022-06-30 08:03:19 +00:00
#region Implementation of IDisposable
public void Dispose ( )
{
2022-07-28 07:08:27 +00:00
if ( metadataLookup . IsNotNull ( ) )
metadataLookup . Dispose ( ) ;
if ( updateScheduler . IsNotNull ( ) )
updateScheduler . Dispose ( ) ;
2022-06-30 08:03:19 +00:00
}
#endregion
2022-06-20 09:39:53 +00:00
}
}