1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 14:32:55 +08:00

Merge branch 'master' into test-hyper-generation

This commit is contained in:
Bartłomiej Dach 2021-12-17 10:00:06 +01:00 committed by GitHub
commit 84c585ec29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 190 additions and 71 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.1217.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -90,7 +90,6 @@ namespace osu.Desktop
Logger.Log("Starting legacy IPC provider..."); Logger.Log("Starting legacy IPC provider...");
legacyIpc = new LegacyTcpIpcProvider(); legacyIpc = new LegacyTcpIpcProvider();
legacyIpc.Bind(); legacyIpc.Bind();
legacyIpc.StartAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -56,13 +56,13 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Test] [Test]
public void TestDefaultSkin() public void TestDefaultSkin()
{ {
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLive()); AddStep("set default skin", () => skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged());
} }
[Test] [Test]
public void TestLegacySkin() public void TestLegacySkin()
{ {
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLive()); AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged());
} }
} }
} }

View File

@ -3,8 +3,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges.Events;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
@ -39,6 +41,19 @@ namespace osu.Game.Rulesets.Osu
return base.Handle(e); return base.Handle(e);
} }
protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
{
if (!AllowUserCursorMovement)
{
// Still allow for forwarding of the "touch" part, but replace the positional data with that of the mouse.
// Primarily relied upon by the "autopilot" osu! mod.
var touch = new Touch(e.Touch.Source, CurrentState.Mouse.Position);
e = new TouchStateChangeEvent(e.State, e.Input, touch, e.IsActive, null);
}
return base.HandleMouseTouchStateChange(e);
}
private class OsuKeyBindingContainer : RulesetKeyBindingContainer private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{ {
public bool AllowUserPresses = true; public bool AllowUserPresses = true;

View File

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Models; using osu.Game.Models;
using Realms; using Realms;
@ -21,14 +22,41 @@ namespace osu.Game.Tests.Database
{ {
RunTestWithRealm((realmFactory, _) => RunTestWithRealm((realmFactory, _) =>
{ {
ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(); ILive<RealmBeatmap> beatmap = realmFactory.CreateContext().Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))).ToLive(realmFactory);
ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive(); ILive<RealmBeatmap> beatmap2 = realmFactory.CreateContext().All<RealmBeatmap>().First().ToLive(realmFactory);
Assert.AreEqual(beatmap, beatmap2); Assert.AreEqual(beatmap, beatmap2);
}); });
} }
[Test]
public void TestAccessAfterStorageMigrate()
{
RunTestWithRealm((realmFactory, storage) =>
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
ILive<RealmBeatmap> liveBeatmap;
using (var context = realmFactory.CreateContext())
{
context.Write(r => r.Add(beatmap));
liveBeatmap = beatmap.ToLive(realmFactory);
}
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
{
migratedStorage.DeleteDirectory(string.Empty);
storage.Migrate(migratedStorage);
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
}
});
}
[Test] [Test]
public void TestAccessAfterAttach() public void TestAccessAfterAttach()
{ {
@ -36,7 +64,7 @@ namespace osu.Game.Tests.Database
{ {
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive(); var liveBeatmap = beatmap.ToLive(realmFactory);
using (var context = realmFactory.CreateContext()) using (var context = realmFactory.CreateContext())
context.Write(r => r.Add(beatmap)); context.Write(r => r.Add(beatmap));
@ -49,7 +77,7 @@ namespace osu.Game.Tests.Database
public void TestAccessNonManaged() public void TestAccessNonManaged()
{ {
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive(); var liveBeatmap = beatmap.ToLiveUnmanaged();
Assert.IsFalse(beatmap.Hidden); Assert.IsFalse(beatmap.Hidden);
Assert.IsFalse(liveBeatmap.Value.Hidden); Assert.IsFalse(liveBeatmap.Value.Hidden);
@ -74,7 +102,7 @@ namespace osu.Game.Tests.Database
{ {
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive(); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -103,7 +131,7 @@ namespace osu.Game.Tests.Database
{ {
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive(); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -123,7 +151,7 @@ namespace osu.Game.Tests.Database
RunTestWithRealm((realmFactory, _) => RunTestWithRealm((realmFactory, _) =>
{ {
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()); var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive(); var liveBeatmap = beatmap.ToLive(realmFactory);
Assert.DoesNotThrow(() => Assert.DoesNotThrow(() =>
{ {
@ -145,7 +173,7 @@ namespace osu.Game.Tests.Database
{ {
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive(); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -183,7 +211,7 @@ namespace osu.Game.Tests.Database
{ {
var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); var beatmap = threadContext.Write(r => r.Add(new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive(); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();
@ -222,7 +250,7 @@ namespace osu.Game.Tests.Database
// not just a refresh from the resolved Live. // not just a refresh from the resolved Live.
threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata()))); threadContext.Write(r => r.Add(new RealmBeatmap(ruleset, new RealmBeatmapDifficulty(), new RealmBeatmapMetadata())));
liveBeatmap = beatmap.ToLive(); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait();

View File

@ -10,6 +10,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO;
using osu.Game.Models; using osu.Game.Models;
#nullable enable #nullable enable
@ -27,15 +28,16 @@ namespace osu.Game.Tests.Database
storage.DeleteDirectory(string.Empty); storage.DeleteDirectory(string.Empty);
} }
protected void RunTestWithRealm(Action<RealmContextFactory, Storage> testAction, [CallerMemberName] string caller = "") protected void RunTestWithRealm(Action<RealmContextFactory, OsuStorage> testAction, [CallerMemberName] string caller = "")
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller)) using (HeadlessGameHost host = new CleanRunHeadlessGameHost(caller))
{ {
host.Run(new RealmTestGame(() => host.Run(new RealmTestGame(() =>
{ {
var testStorage = storage.GetStorageForDirectory(caller); // ReSharper disable once AccessToDisposedClosure
var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));
using (var realmFactory = new RealmContextFactory(testStorage, caller)) using (var realmFactory = new RealmContextFactory(testStorage, "client"))
{ {
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
testAction(realmFactory, testStorage); testAction(realmFactory, testStorage);
@ -58,7 +60,7 @@ namespace osu.Game.Tests.Database
{ {
var testStorage = storage.GetStorageForDirectory(caller); var testStorage = storage.GetStorageForDirectory(caller);
using (var realmFactory = new RealmContextFactory(testStorage, caller)) using (var realmFactory = new RealmContextFactory(testStorage, "client"))
{ {
Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
await testAction(realmFactory, testStorage); await testAction(realmFactory, testStorage);

View File

@ -15,6 +15,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Database;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -220,6 +221,7 @@ namespace osu.Game.Tests.Gameplay
public AudioManager AudioManager => Audio; public AudioManager AudioManager => Audio;
public IResourceStore<byte[]> Files => null; public IResourceStore<byte[]> Files => null;
public new IResourceStore<byte[]> Resources => base.Resources; public new IResourceStore<byte[]> Resources => base.Resources;
public RealmContextFactory RealmContextFactory => null;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null; public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
#endregion #endregion

View File

@ -235,7 +235,7 @@ namespace osu.Game.Tests.Visual.Background
private void setCustomSkin() private void setCustomSkin()
{ {
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.
AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLive()); AddStep("set custom skin", () => skins.CurrentSkinInfo.Value = new SkinInfo().ToLiveUnmanaged());
} }
private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault()); private void setDefaultSkin() => AddStep("set default skin", () => skins.CurrentSkinInfo.SetDefault());

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("setup skins", () => AddStep("setup skins", () =>
{ {
skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLive(); skinManager.CurrentSkinInfo.Value = gameCurrentSkin.ToLiveUnmanaged();
currentBeatmapSkin = getBeatmapSkin(); currentBeatmapSkin = getBeatmapSkin();
}); });
}); });

View File

@ -15,6 +15,7 @@ using osu.Framework.Platform;
using osu.Framework.Statistics; using osu.Framework.Statistics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Storyboards; using osu.Game.Storyboards;
@ -108,6 +109,7 @@ namespace osu.Game.Beatmaps
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore; TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
ITrackStore IBeatmapResourceProvider.Tracks => trackStore; ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
AudioManager IStorageResourceProvider.AudioManager => audioManager; AudioManager IStorageResourceProvider.AudioManager => audioManager;
RealmContextFactory IStorageResourceProvider.RealmContextFactory => null;
IResourceStore<byte[]> IStorageResourceProvider.Files => files; IResourceStore<byte[]> IStorageResourceProvider.Files => files;
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources; IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore); IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);

View File

@ -24,13 +24,17 @@ namespace osu.Game.Database
/// </summary> /// </summary>
private readonly T data; private readonly T data;
private readonly RealmContextFactory realmFactory;
/// <summary> /// <summary>
/// Construct a new instance of live realm data. /// Construct a new instance of live realm data.
/// </summary> /// </summary>
/// <param name="data">The realm data.</param> /// <param name="data">The realm data.</param>
public RealmLive(T data) /// <param name="realmFactory">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
public RealmLive(T data, RealmContextFactory realmFactory)
{ {
this.data = data; this.data = data;
this.realmFactory = realmFactory;
ID = data.ID; ID = data.ID;
} }
@ -47,7 +51,7 @@ namespace osu.Game.Database
return; return;
} }
using (var realm = Realm.GetInstance(data.Realm.Config)) using (var realm = realmFactory.CreateContext())
perform(realm.Find<T>(ID)); perform(realm.Find<T>(ID));
} }
@ -58,12 +62,12 @@ namespace osu.Game.Database
public TReturn PerformRead<TReturn>(Func<T, TReturn> perform) public TReturn PerformRead<TReturn>(Func<T, TReturn> perform)
{ {
if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn))) if (typeof(RealmObjectBase).IsAssignableFrom(typeof(TReturn)))
throw new InvalidOperationException($"Realm live objects should not exit the scope of {nameof(PerformRead)}."); throw new InvalidOperationException(@$"Realm live objects should not exit the scope of {nameof(PerformRead)}.");
if (!IsManaged) if (!IsManaged)
return perform(data); return perform(data);
using (var realm = Realm.GetInstance(data.Realm.Config)) using (var realm = realmFactory.CreateContext())
return perform(realm.Find<T>(ID)); return perform(realm.Find<T>(ID));
} }
@ -74,7 +78,7 @@ namespace osu.Game.Database
public void PerformWrite(Action<T> perform) public void PerformWrite(Action<T> perform)
{ {
if (!IsManaged) if (!IsManaged)
throw new InvalidOperationException("Can't perform writes on a non-managed underlying value"); throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value");
PerformRead(t => PerformRead(t =>
{ {
@ -94,11 +98,7 @@ namespace osu.Game.Database
if (!ThreadSafety.IsUpdateThread) if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads");
// When using Value, we rely on garbage collection for the realm instance used to retrieve the instance. return realmFactory.Context.Find<T>(ID);
// As we are sure that this is on the update thread, there should always be an open and constantly refreshing realm instance to ensure file size growth is a non-issue.
var realm = Realm.GetInstance(data.Realm.Config);
return realm.Find<T>(ID);
} }
} }

View File

@ -0,0 +1,44 @@
// 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 Realms;
#nullable enable
namespace osu.Game.Database
{
/// <summary>
/// Provides a method of working with unmanaged realm objects.
/// Usually used for testing purposes where the instance is never required to be managed.
/// </summary>
/// <typeparam name="T">The underlying object type.</typeparam>
public class RealmLiveUnmanaged<T> : ILive<T> where T : RealmObjectBase, IHasGuidPrimaryKey
{
/// <summary>
/// Construct a new instance of live realm data.
/// </summary>
/// <param name="data">The realm data.</param>
public RealmLiveUnmanaged(T data)
{
Value = data;
}
public bool Equals(ILive<T>? other) => ID == other?.ID;
public Guid ID => Value.ID;
public void PerformRead(Action<T> perform) => perform(Value);
public TReturn PerformRead<TReturn>(Func<T, TReturn> perform) => perform(Value);
public void PerformWrite(Action<T> perform) => throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value");
public bool IsManaged => false;
/// <summary>
/// The original live data used to create this instance.
/// </summary>
public T Value { get; }
}
}

View File

@ -53,16 +53,28 @@ namespace osu.Game.Database
return mapper.Map<T>(item); return mapper.Map<T>(item);
} }
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList) public static List<ILive<T>> ToLiveUnmanaged<T>(this IEnumerable<T> realmList)
where T : RealmObject, IHasGuidPrimaryKey where T : RealmObject, IHasGuidPrimaryKey
{ {
return realmList.Select(l => new RealmLive<T>(l)).Cast<ILive<T>>().ToList(); return realmList.Select(l => new RealmLiveUnmanaged<T>(l)).Cast<ILive<T>>().ToList();
} }
public static ILive<T> ToLive<T>(this T realmObject) public static ILive<T> ToLiveUnmanaged<T>(this T realmObject)
where T : RealmObject, IHasGuidPrimaryKey where T : RealmObject, IHasGuidPrimaryKey
{ {
return new RealmLive<T>(realmObject); return new RealmLiveUnmanaged<T>(realmObject);
}
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList, RealmContextFactory realmContextFactory)
where T : RealmObject, IHasGuidPrimaryKey
{
return realmList.Select(l => new RealmLive<T>(l, realmContextFactory)).Cast<ILive<T>>().ToList();
}
public static ILive<T> ToLive<T>(this T realmObject, RealmContextFactory realmContextFactory)
where T : RealmObject, IHasGuidPrimaryKey
{
return new RealmLive<T>(realmObject, realmContextFactory);
} }
/// <summary> /// <summary>

View File

@ -72,18 +72,21 @@ namespace osu.Game.Graphics.Cursor
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
// only trigger animation for main mouse buttons if (State.Value == Visibility.Visible)
activeCursor.Scale = new Vector2(1);
activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
activeCursor.AdditiveLayer.Alpha = 0;
activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating)
{ {
// if cursor is already rotating don't reset its rotate origin // only trigger animation for main mouse buttons
dragRotationState = DragRotationState.DragStarted; activeCursor.Scale = new Vector2(1);
positionMouseDown = e.MousePosition; activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
activeCursor.AdditiveLayer.Alpha = 0;
activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating)
{
// if cursor is already rotating don't reset its rotate origin
dragRotationState = DragRotationState.DragStarted;
positionMouseDown = e.MousePosition;
}
} }
return base.OnMouseDown(e); return base.OnMouseDown(e);

View File

@ -65,8 +65,10 @@ namespace osu.Game.Graphics
public void SetContent(DateTimeOffset date) public void SetContent(DateTimeOffset date)
{ {
dateText.Text = $"{date:d MMMM yyyy} "; DateTimeOffset localDate = date.ToLocalTime();
timeText.Text = $"{date:HH:mm:ss \"UTC\"z}";
dateText.Text = $"{localDate:d MMMM yyyy} ";
timeText.Text = $"{localDate:HH:mm:ss \"UTC\"z}";
} }
public void Move(Vector2 pos) => Position = pos; public void Move(Vector2 pos) => Position = pos;

View File

@ -4,6 +4,7 @@
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Game.Database;
namespace osu.Game.IO namespace osu.Game.IO
{ {
@ -24,6 +25,11 @@ namespace osu.Game.IO
/// </summary> /// </summary>
IResourceStore<byte[]> Resources { get; } IResourceStore<byte[]> Resources { get; }
/// <summary>
/// Access realm.
/// </summary>
RealmContextFactory RealmContextFactory { get; }
/// <summary> /// <summary>
/// Create a texture loader store based on an underlying data store. /// Create a texture loader store based on an underlying data store.
/// </summary> /// </summary>

View File

@ -255,10 +255,10 @@ namespace osu.Game
if (skinInfo == null) if (skinInfo == null)
{ {
if (guid == SkinInfo.CLASSIC_SKIN) if (guid == SkinInfo.CLASSIC_SKIN)
skinInfo = DefaultLegacySkin.CreateInfo().ToLive(); skinInfo = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged();
} }
SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLive(); SkinManager.CurrentSkinInfo.Value = skinInfo ?? DefaultSkin.CreateInfo().ToLiveUnmanaged();
}; };
configSkin.TriggerChange(); configSkin.TriggerChange();
@ -1149,16 +1149,11 @@ namespace osu.Game
} }
} }
private void screenPushed(IScreen lastScreen, IScreen newScreen) private void screenPushed(IScreen lastScreen, IScreen newScreen) => ScreenChanged(lastScreen, newScreen);
{
ScreenChanged(lastScreen, newScreen);
Logger.Log($"Screen changed → {newScreen}");
}
private void screenExited(IScreen lastScreen, IScreen newScreen) private void screenExited(IScreen lastScreen, IScreen newScreen)
{ {
ScreenChanged(lastScreen, newScreen); ScreenChanged(lastScreen, newScreen);
Logger.Log($"Screen changed ← {newScreen}");
if (newScreen == null) if (newScreen == null)
Exit(); Exit();

View File

@ -32,14 +32,14 @@ namespace osu.Game.Overlays.Settings.Sections
Icon = FontAwesome.Solid.PaintBrush Icon = FontAwesome.Solid.PaintBrush
}; };
private readonly Bindable<ILive<SkinInfo>> dropdownBindable = new Bindable<ILive<SkinInfo>> { Default = DefaultSkin.CreateInfo().ToLive() }; private readonly Bindable<ILive<SkinInfo>> dropdownBindable = new Bindable<ILive<SkinInfo>> { Default = DefaultSkin.CreateInfo().ToLiveUnmanaged() };
private readonly Bindable<string> configBindable = new Bindable<string>(); private readonly Bindable<string> configBindable = new Bindable<string>();
private static readonly ILive<SkinInfo> random_skin_info = new SkinInfo private static readonly ILive<SkinInfo> random_skin_info = new SkinInfo
{ {
ID = SkinInfo.RANDOM_SKIN, ID = SkinInfo.RANDOM_SKIN,
Name = "<Random Skin>", Name = "<Random Skin>",
}.ToLive(); }.ToLiveUnmanaged();
private List<ILive<SkinInfo>> skinItems; private List<ILive<SkinInfo>> skinItems;
@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Settings.Sections
{ {
int protectedCount = realmSkins.Count(s => s.Protected); int protectedCount = realmSkins.Count(s => s.Protected);
skinItems = realmSkins.ToLive(); skinItems = realmSkins.ToLive(realmFactory);
skinItems.Insert(protectedCount, random_skin_info); skinItems.Insert(protectedCount, random_skin_info);

View File

@ -43,7 +43,11 @@ namespace osu.Game.Skinning
protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null) protected Skin(SkinInfo skin, IStorageResourceProvider resources, [CanBeNull] Stream configurationStream = null)
{ {
SkinInfo = skin.ToLive(); SkinInfo = resources?.RealmContextFactory != null
? skin.ToLive(resources.RealmContextFactory)
// This path should only be used in some tests.
: skin.ToLiveUnmanaged();
this.resources = resources; this.resources = resources;
configurationStream ??= getConfigurationStream(); configurationStream ??= getConfigurationStream();

View File

@ -47,9 +47,9 @@ namespace osu.Game.Skinning
public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>(); public readonly Bindable<Skin> CurrentSkin = new Bindable<Skin>();
public readonly Bindable<ILive<SkinInfo>> CurrentSkinInfo = new Bindable<ILive<SkinInfo>>(Skinning.DefaultSkin.CreateInfo().ToLive()) public readonly Bindable<ILive<SkinInfo>> CurrentSkinInfo = new Bindable<ILive<SkinInfo>>(Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged())
{ {
Default = Skinning.DefaultSkin.CreateInfo().ToLive() Default = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged()
}; };
private readonly SkinModelManager skinModelManager; private readonly SkinModelManager skinModelManager;
@ -119,13 +119,13 @@ namespace osu.Game.Skinning
if (randomChoices.Length == 0) if (randomChoices.Length == 0)
{ {
CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLive(); CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged();
return; return;
} }
var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length)); var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
CurrentSkinInfo.Value = chosen.ToLive(); CurrentSkinInfo.Value = chosen.ToLive(contextFactory);
} }
} }
@ -182,7 +182,7 @@ namespace osu.Game.Skinning
public ILive<SkinInfo> Query(Expression<Func<SkinInfo, bool>> query) public ILive<SkinInfo> Query(Expression<Func<SkinInfo, bool>> query)
{ {
using (var context = contextFactory.CreateContext()) using (var context = contextFactory.CreateContext())
return context.All<SkinInfo>().FirstOrDefault(query)?.ToLive(); return context.All<SkinInfo>().FirstOrDefault(query)?.ToLive(contextFactory);
} }
public event Action SourceChanged; public event Action SourceChanged;
@ -237,6 +237,7 @@ namespace osu.Game.Skinning
AudioManager IStorageResourceProvider.AudioManager => audio; AudioManager IStorageResourceProvider.AudioManager => audio;
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources; IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
IResourceStore<byte[]> IStorageResourceProvider.Files => userFiles; IResourceStore<byte[]> IStorageResourceProvider.Files => userFiles;
RealmContextFactory IStorageResourceProvider.RealmContextFactory => contextFactory;
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
#endregion #endregion
@ -302,7 +303,7 @@ namespace osu.Game.Skinning
Guid currentUserSkin = CurrentSkinInfo.Value.ID; Guid currentUserSkin = CurrentSkinInfo.Value.ID;
if (items.Any(s => s.ID == currentUserSkin)) if (items.Any(s => s.ID == currentUserSkin))
scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLive()); scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged());
skinModelManager.Delete(items.ToList(), silent); skinModelManager.Delete(items.ToList(), silent);
} }

View File

@ -352,7 +352,7 @@ namespace osu.Game.Stores
transaction.Commit(); transaction.Commit();
} }
return existing.ToLive(); return existing.ToLive(ContextFactory);
} }
LogForModel(item, @"Found existing (optimised) but failed pre-check."); LogForModel(item, @"Found existing (optimised) but failed pre-check.");
@ -387,7 +387,7 @@ namespace osu.Game.Stores
existing.DeletePending = false; existing.DeletePending = false;
transaction.Commit(); transaction.Commit();
return existing.ToLive(); return existing.ToLive(ContextFactory);
} }
LogForModel(item, @"Found existing but failed re-use check."); LogForModel(item, @"Found existing but failed re-use check.");
@ -416,7 +416,7 @@ namespace osu.Game.Stores
throw; throw;
} }
return item.ToLive(); return item.ToLive(ContextFactory);
} }
} }

View File

@ -14,6 +14,7 @@ using osu.Framework.Testing;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Database;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -118,6 +119,7 @@ namespace osu.Game.Tests.Beatmaps
public IResourceStore<byte[]> Files => userSkinResourceStore; public IResourceStore<byte[]> Files => userSkinResourceStore;
public new IResourceStore<byte[]> Resources => base.Resources; public new IResourceStore<byte[]> Resources => base.Resources;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null; public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => null;
RealmContextFactory IStorageResourceProvider.RealmContextFactory => null;
#endregion #endregion

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -158,6 +159,7 @@ namespace osu.Game.Tests.Visual
public IResourceStore<byte[]> Files => null; public IResourceStore<byte[]> Files => null;
public new IResourceStore<byte[]> Resources => base.Resources; public new IResourceStore<byte[]> Resources => base.Resources;
public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore); public IResourceStore<TextureUpload> CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host.CreateTextureLoaderStore(underlyingStore);
RealmContextFactory IStorageResourceProvider.RealmContextFactory => null;
#endregion #endregion

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.7.1" /> <PackageReference Include="Realm" Version="10.7.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1217.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" />
<PackageReference Include="Sentry" Version="3.12.1" /> <PackageReference Include="Sentry" Version="3.12.1" />
<PackageReference Include="SharpCompress" Version="0.30.1" /> <PackageReference Include="SharpCompress" Version="0.30.1" />

View File

@ -60,7 +60,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1217.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -83,7 +83,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1217.0" />
<PackageReference Include="SharpCompress" Version="0.30.0" /> <PackageReference Include="SharpCompress" Version="0.30.0" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />