Merge branch 'master' into watch-replays-4

This commit is contained in:
Dean Herbert
2019-07-03 11:47:10 +09:00
committed by GitHub
14 changed files with 219 additions and 88 deletions

View File

@ -30,9 +30,9 @@ namespace osu.Game.Tests
trackStore = audioManager.GetTrackStore(reader); trackStore = audioManager.GetTrackStore(reader);
} }
public override void Dispose() protected override void Dispose(bool isDisposing)
{ {
base.Dispose(); base.Dispose(isDisposing);
stream?.Dispose(); stream?.Dispose();
reader?.Dispose(); reader?.Dispose();
trackStore?.Dispose(); trackStore?.Dispose();

View File

@ -13,6 +13,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Lists;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Threading; using osu.Framework.Threading;
@ -159,6 +160,8 @@ namespace osu.Game.Beatmaps
/// <param name="beatmap">The beatmap difficulty to restore.</param> /// <param name="beatmap">The beatmap difficulty to restore.</param>
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
/// <summary> /// <summary>
/// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/> /// Retrieve a <see cref="WorkingBeatmap"/> instance for the provided <see cref="BeatmapInfo"/>
/// </summary> /// </summary>
@ -173,12 +176,18 @@ namespace osu.Game.Beatmaps
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap; return DefaultBeatmap;
var cached = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
if (cached != null)
return cached;
if (beatmapInfo.Metadata == null) if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager); WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager);
previous?.TransferTo(working); previous?.TransferTo(working);
workingCache.Add(working);
return working; return working;
} }

View File

@ -11,7 +11,9 @@ using osu.Framework.IO.File;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Statistics;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -31,6 +33,8 @@ namespace osu.Game.Beatmaps
protected AudioManager AudioManager { get; } protected AudioManager AudioManager { get; }
private static readonly GlobalStatistic<int> total_count = GlobalStatistics.Get<int>(nameof(Beatmaps), $"Total {nameof(WorkingBeatmap)}s");
protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager)
{ {
AudioManager = audioManager; AudioManager = audioManager;
@ -38,24 +42,13 @@ namespace osu.Game.Beatmaps
BeatmapSetInfo = beatmapInfo.BeatmapSet; BeatmapSetInfo = beatmapInfo.BeatmapSet;
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
beatmap = new RecyclableLazy<IBeatmap>(() =>
{
var b = GetBeatmap() ?? new Beatmap();
// The original beatmap version needs to be preserved as the database doesn't contain it
BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
// Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
b.BeatmapInfo = BeatmapInfo;
return b;
});
track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack()); track = new RecyclableLazy<Track>(() => GetTrack() ?? GetVirtualTrack());
background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid); background = new RecyclableLazy<Texture>(GetBackground, BackgroundStillValid);
waveform = new RecyclableLazy<Waveform>(GetWaveform); waveform = new RecyclableLazy<Waveform>(GetWaveform);
storyboard = new RecyclableLazy<Storyboard>(GetStoryboard); storyboard = new RecyclableLazy<Storyboard>(GetStoryboard);
skin = new RecyclableLazy<Skin>(GetSkin); skin = new RecyclableLazy<Skin>(GetSkin);
total_count.Value++;
} }
protected virtual Track GetVirtualTrack() protected virtual Track GetVirtualTrack()
@ -153,10 +146,40 @@ namespace osu.Game.Beatmaps
public override string ToString() => BeatmapInfo.ToString(); public override string ToString() => BeatmapInfo.ToString();
public bool BeatmapLoaded => beatmap.IsResultAvailable; public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
public IBeatmap Beatmap => beatmap.Value;
public Task<IBeatmap> LoadBeatmapAsync() => (beatmapLoadTask ?? (beatmapLoadTask = Task.Factory.StartNew(() =>
{
// Todo: Handle cancellation during beatmap parsing
var b = GetBeatmap() ?? new Beatmap();
// The original beatmap version needs to be preserved as the database doesn't contain it
BeatmapInfo.BeatmapVersion = b.BeatmapInfo.BeatmapVersion;
// Use the database-backed info for more up-to-date values (beatmap id, ranked status, etc)
b.BeatmapInfo = BeatmapInfo;
return b;
}, beatmapCancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)));
public IBeatmap Beatmap
{
get
{
try
{
return LoadBeatmapAsync().Result;
}
catch (TaskCanceledException)
{
return null;
}
}
}
private readonly CancellationTokenSource beatmapCancellation = new CancellationTokenSource();
protected abstract IBeatmap GetBeatmap(); protected abstract IBeatmap GetBeatmap();
private readonly RecyclableLazy<IBeatmap> beatmap; private Task<IBeatmap> beatmapLoadTask;
public bool BackgroundLoaded => background.IsResultAvailable; public bool BackgroundLoaded => background.IsResultAvailable;
public Texture Background => background.Value; public Texture Background => background.Value;
@ -195,20 +218,46 @@ namespace osu.Game.Beatmaps
other.track = track; other.track = track;
} }
public virtual void Dispose()
{
background.Recycle();
waveform.Recycle();
storyboard.Recycle();
skin.Recycle();
}
/// <summary> /// <summary>
/// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any). /// Eagerly dispose of the audio track associated with this <see cref="WorkingBeatmap"/> (if any).
/// Accessing track again will load a fresh instance. /// Accessing track again will load a fresh instance.
/// </summary> /// </summary>
public virtual void RecycleTrack() => track.Recycle(); public virtual void RecycleTrack() => track.Recycle();
#region Disposal
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool isDisposed;
protected virtual void Dispose(bool isDisposing)
{
if (isDisposed)
return;
isDisposed = true;
// recycling logic is not here for the time being, as components which use
// retrieved objects from WorkingBeatmap may not hold a reference to the WorkingBeatmap itself.
// this should be fine as each retrieved component do have their own finalizers.
// cancelling the beatmap load is safe for now since the retrieval is a synchronous
// operation. if we add an async retrieval method this may need to be reconsidered.
beatmapCancellation.Cancel();
total_count.Value--;
}
~WorkingBeatmap()
{
Dispose(false);
}
#endregion
public class RecyclableLazy<T> public class RecyclableLazy<T>
{ {
private Lazy<T> lazy; private Lazy<T> lazy;

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Statistics;
namespace osu.Game.Database namespace osu.Game.Database
{ {
@ -31,11 +32,20 @@ namespace osu.Game.Database
recycleThreadContexts(); recycleThreadContexts();
} }
private static readonly GlobalStatistic<int> reads = GlobalStatistics.Get<int>("Database", "Get (Read)");
private static readonly GlobalStatistic<int> writes = GlobalStatistics.Get<int>("Database", "Get (Write)");
private static readonly GlobalStatistic<int> commits = GlobalStatistics.Get<int>("Database", "Commits");
private static readonly GlobalStatistic<int> rollbacks = GlobalStatistics.Get<int>("Database", "Rollbacks");
/// <summary> /// <summary>
/// Get a context for the current thread for read-only usage. /// Get a context for the current thread for read-only usage.
/// If a <see cref="DatabaseWriteUsage"/> is in progress, the existing write-safe context will be returned. /// If a <see cref="DatabaseWriteUsage"/> is in progress, the existing write-safe context will be returned.
/// </summary> /// </summary>
public OsuDbContext Get() => threadContexts.Value; public OsuDbContext Get()
{
reads.Value++;
return threadContexts.Value;
}
/// <summary> /// <summary>
/// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context).
@ -45,6 +55,7 @@ namespace osu.Game.Database
/// <returns>A usage containing a usable context.</returns> /// <returns>A usage containing a usable context.</returns>
public DatabaseWriteUsage GetForWrite(bool withTransaction = true) public DatabaseWriteUsage GetForWrite(bool withTransaction = true)
{ {
writes.Value++;
Monitor.Enter(writeLock); Monitor.Enter(writeLock);
OsuDbContext context; OsuDbContext context;
@ -90,9 +101,15 @@ namespace osu.Game.Database
if (usages == 0) if (usages == 0)
{ {
if (currentWriteDidError) if (currentWriteDidError)
{
rollbacks.Value++;
currentWriteTransaction?.Rollback(); currentWriteTransaction?.Rollback();
}
else else
{
commits.Value++;
currentWriteTransaction?.Commit(); currentWriteTransaction?.Commit();
}
if (currentWriteDidWrite || currentWriteDidError) if (currentWriteDidWrite || currentWriteDidError)
{ {

View File

@ -6,6 +6,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Statistics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.IO; using osu.Game.IO;
@ -34,6 +35,8 @@ namespace osu.Game.Database
private static readonly Lazy<OsuDbLoggerFactory> logger = new Lazy<OsuDbLoggerFactory>(() => new OsuDbLoggerFactory()); private static readonly Lazy<OsuDbLoggerFactory> logger = new Lazy<OsuDbLoggerFactory>(() => new OsuDbLoggerFactory());
private static readonly GlobalStatistic<int> contexts = GlobalStatistics.Get<int>("Database", "Contexts");
static OsuDbContext() static OsuDbContext()
{ {
// required to initialise native SQLite libraries on some platforms. // required to initialise native SQLite libraries on some platforms.
@ -76,6 +79,8 @@ namespace osu.Game.Database
connection.Close(); connection.Close();
throw; throw;
} }
contexts.Value++;
} }
~OsuDbContext() ~OsuDbContext()
@ -85,6 +90,20 @@ namespace osu.Game.Database
Dispose(); Dispose();
} }
private bool isDisposed;
public override void Dispose()
{
if (isDisposed) return;
isDisposed = true;
base.Dispose();
contexts.Value--;
GC.SuppressFinalize(this);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{ {
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);

View File

@ -6,23 +6,28 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Transforms;
using osuTK;
namespace osu.Game.Graphics.Backgrounds namespace osu.Game.Graphics.Backgrounds
{ {
public class Background : BufferedContainer /// <summary>
/// A background which offers blurring via a <see cref="BufferedContainer"/> on demand.
/// </summary>
public class Background : CompositeDrawable
{ {
public Sprite Sprite; public Sprite Sprite;
private readonly string textureName; private readonly string textureName;
private BufferedContainer bufferedContainer;
public Background(string textureName = @"") public Background(string textureName = @"")
{ {
CacheDrawnFrameBuffer = true;
this.textureName = textureName; this.textureName = textureName;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Add(Sprite = new Sprite AddInternal(Sprite = new Sprite
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -37,5 +42,28 @@ namespace osu.Game.Graphics.Backgrounds
if (!string.IsNullOrEmpty(textureName)) if (!string.IsNullOrEmpty(textureName))
Sprite.Texture = textures.Get(textureName); Sprite.Texture = textures.Get(textureName);
} }
public Vector2 BlurSigma => bufferedContainer?.BlurSigma ?? Vector2.Zero;
/// <summary>
/// Smoothly adjusts <see cref="IBufferedContainer.BlurSigma"/> over time.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public void BlurTo(Vector2 newBlurSigma, double duration = 0, Easing easing = Easing.None)
{
if (bufferedContainer == null)
{
RemoveInternal(Sprite);
AddInternal(bufferedContainer = new BufferedContainer
{
CacheDrawnFrameBuffer = true,
RelativeSizeAxes = Axes.Both,
Child = Sprite
});
}
bufferedContainer.BlurTo(newBlurSigma, duration, easing);
}
} }
} }

View File

@ -297,6 +297,10 @@ namespace osu.Game
var nextBeatmap = beatmap.NewValue; var nextBeatmap = beatmap.NewValue;
if (nextBeatmap?.Track != null) if (nextBeatmap?.Track != null)
nextBeatmap.Track.Completed += currentTrackCompleted; nextBeatmap.Track.Completed += currentTrackCompleted;
beatmap.OldValue?.Dispose();
nextBeatmap?.LoadBeatmapAsync();
} }
private void currentTrackCompleted() private void currentTrackCompleted()

View File

@ -66,24 +66,64 @@ namespace osu.Game.Overlays
} }
}; };
Header.Tabs.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); Header.Tabs.Current.ValueChanged += _ => queueUpdate();
Filter.Tabs.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); Filter.Tabs.Current.ValueChanged += _ => queueUpdate();
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue);
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdate();
currentQuery.BindTo(Filter.Search.Current);
currentQuery.ValueChanged += query => currentQuery.ValueChanged += query =>
{ {
queryChangedDebounce?.Cancel(); queryChangedDebounce?.Cancel();
if (string.IsNullOrEmpty(query.NewValue)) if (string.IsNullOrEmpty(query.NewValue))
Scheduler.AddOnce(updateSearch); queueUpdate();
else else
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500);
}; };
}
currentQuery.BindTo(Filter.Search.Current); private APIRequest getUsersRequest;
private readonly Bindable<string> currentQuery = new Bindable<string>();
private ScheduledDelegate queryChangedDebounce;
private void queueUpdate() => Scheduler.AddOnce(updateSearch);
private void updateSearch()
{
queryChangedDebounce?.Cancel();
if (!IsLoaded)
return;
Users = null;
clearPanels();
loading.Hide();
getUsersRequest?.Cancel();
if (API?.IsLoggedIn != true)
return;
switch (Header.Tabs.Current.Value)
{
case SocialTab.Friends:
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
friendRequest.Success += updateUsers;
API.Queue(getUsersRequest = friendRequest);
break;
default:
var userRequest = new GetUsersRequest(); // TODO filter arguments!
userRequest.Success += response => updateUsers(response.Select(r => r.User));
API.Queue(getUsersRequest = userRequest);
break;
}
loading.Show();
} }
private void recreatePanels(PanelDisplayStyle displayStyle) private void recreatePanels(PanelDisplayStyle displayStyle)
@ -133,45 +173,6 @@ namespace osu.Game.Overlays
}); });
} }
private APIRequest getUsersRequest;
private readonly Bindable<string> currentQuery = new Bindable<string>();
private ScheduledDelegate queryChangedDebounce;
private void updateSearch()
{
queryChangedDebounce?.Cancel();
if (!IsLoaded)
return;
Users = null;
clearPanels();
loading.Hide();
getUsersRequest?.Cancel();
if (API?.IsLoggedIn != true)
return;
switch (Header.Tabs.Current.Value)
{
case SocialTab.Friends:
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
friendRequest.Success += updateUsers;
API.Queue(getUsersRequest = friendRequest);
break;
default:
var userRequest = new GetUsersRequest(); // TODO filter arguments!
userRequest.Success += response => updateUsers(response.Select(r => r.User));
API.Queue(getUsersRequest = userRequest);
break;
}
loading.Show();
}
private void updateUsers(IEnumerable<User> newUsers) private void updateUsers(IEnumerable<User> newUsers)
{ {
Users = newUsers; Users = newUsers;
@ -193,7 +194,7 @@ namespace osu.Game.Overlays
switch (state) switch (state)
{ {
case APIState.Online: case APIState.Online:
Scheduler.AddOnce(updateSearch); queueUpdate();
break; break;
default: default:

View File

@ -18,7 +18,7 @@ namespace osu.Game.Screens.Backgrounds
private Background background; private Background background;
private int currentDisplay; private int currentDisplay;
private const int background_count = 5; private const int background_count = 7;
private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}"; private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}";

View File

@ -86,6 +86,7 @@ namespace osu.Game.Screens.Menu
if (!resuming) if (!resuming)
{ {
Beatmap.Value = introBeatmap; Beatmap.Value = introBeatmap;
introBeatmap = null;
if (menuVoice.Value) if (menuVoice.Value)
welcome.Play(); welcome.Play();
@ -94,7 +95,10 @@ namespace osu.Game.Screens.Menu
{ {
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu.
if (menuMusic.Value) if (menuMusic.Value)
{
track.Start(); track.Start();
track = null;
}
LoadComponentAsync(mainMenu = new MainMenu()); LoadComponentAsync(mainMenu = new MainMenu());

View File

@ -96,13 +96,13 @@ namespace osu.Game.Screens.Menu
var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null;
var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null;
float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes ?? new float[256]; float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes;
for (int i = 0; i < bars_per_visualiser; i++) for (int i = 0; i < bars_per_visualiser; i++)
{ {
if (track?.IsRunning ?? false) if (track?.IsRunning ?? false)
{ {
float targetAmplitude = temporalAmplitudes[(i + indexOffset) % bars_per_visualiser] * (effect?.KiaiMode == true ? 1 : 0.5f); float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f);
if (targetAmplitude > frequencyAmplitudes[i]) if (targetAmplitude > frequencyAmplitudes[i])
frequencyAmplitudes[i] = targetAmplitude; frequencyAmplitudes[i] = targetAmplitude;
} }
@ -115,7 +115,6 @@ namespace osu.Game.Screens.Menu
} }
indexOffset = (indexOffset + index_change) % bars_per_visualiser; indexOffset = (indexOffset + index_change) % bars_per_visualiser;
Scheduler.AddDelayed(updateAmplitudes, time_between_updates);
} }
private void updateColour() private void updateColour()
@ -131,7 +130,8 @@ namespace osu.Game.Screens.Menu
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
updateAmplitudes();
Scheduler.AddDelayed(updateAmplitudes, time_between_updates, true);
} }
protected override void Update() protected override void Update()

View File

@ -137,9 +137,9 @@ namespace osu.Game.Tests.Visual
track = audio?.Tracks.GetVirtual(length); track = audio?.Tracks.GetVirtual(length);
} }
public override void Dispose() protected override void Dispose(bool isDisposing)
{ {
base.Dispose(); base.Dispose(isDisposing);
store?.Dispose(); store?.Dispose();
} }

View File

@ -14,8 +14,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.701.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.702.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.703.0" />
<PackageReference Include="SharpCompress" Version="0.23.0" /> <PackageReference Include="SharpCompress" Version="0.23.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -104,9 +104,9 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.701.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.702.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.702.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.703.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.702.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.703.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" /> <PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />