mirror of
https://github.com/ppy/osu.git
synced 2025-01-19 10:12:53 +08:00
Merge branch 'master' into realm-migration-ui
This commit is contained in:
commit
1b2cca4a0d
141
osu.Game.Benchmarks/BenchmarkRealmReads.cs
Normal file
141
osu.Game.Benchmarks/BenchmarkRealmReads.cs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkRealmReads : BenchmarkTest
|
||||||
|
{
|
||||||
|
private TemporaryNativeStorage storage;
|
||||||
|
private RealmContextFactory realmFactory;
|
||||||
|
private UpdateThread updateThread;
|
||||||
|
|
||||||
|
[Params(1, 100, 1000)]
|
||||||
|
public int ReadsPerFetch { get; set; }
|
||||||
|
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
storage = new TemporaryNativeStorage("realm-benchmark");
|
||||||
|
storage.DeleteDirectory(string.Empty);
|
||||||
|
|
||||||
|
realmFactory = new RealmContextFactory(storage, "client");
|
||||||
|
|
||||||
|
realmFactory.Run(realm =>
|
||||||
|
{
|
||||||
|
realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
|
||||||
|
});
|
||||||
|
|
||||||
|
updateThread = new UpdateThread(() => { }, null);
|
||||||
|
updateThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkDirectPropertyRead()
|
||||||
|
{
|
||||||
|
realmFactory.Run(realm =>
|
||||||
|
{
|
||||||
|
var beatmapSet = realm.All<BeatmapSetInfo>().First();
|
||||||
|
|
||||||
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
|
{
|
||||||
|
string _ = beatmapSet.Beatmaps.First().Hash;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkDirectPropertyReadUpdateThread()
|
||||||
|
{
|
||||||
|
var done = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
updateThread.Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var beatmapSet = realmFactory.Context.All<BeatmapSetInfo>().First();
|
||||||
|
|
||||||
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
|
{
|
||||||
|
string _ = beatmapSet.Beatmaps.First().Hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
done.Set();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
done.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkRealmLivePropertyRead()
|
||||||
|
{
|
||||||
|
realmFactory.Run(realm =>
|
||||||
|
{
|
||||||
|
var beatmapSet = realm.All<BeatmapSetInfo>().First().ToLive(realmFactory);
|
||||||
|
|
||||||
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
|
{
|
||||||
|
string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkRealmLivePropertyReadUpdateThread()
|
||||||
|
{
|
||||||
|
var done = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
updateThread.Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var beatmapSet = realmFactory.Context.All<BeatmapSetInfo>().First().ToLive(realmFactory);
|
||||||
|
|
||||||
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
|
{
|
||||||
|
string _ = beatmapSet.PerformRead(b => b.Beatmaps.First().Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
done.Set();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
done.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkDetachedPropertyRead()
|
||||||
|
{
|
||||||
|
realmFactory.Run(realm =>
|
||||||
|
{
|
||||||
|
var beatmapSet = realm.All<BeatmapSetInfo>().First().Detach();
|
||||||
|
|
||||||
|
for (int i = 0; i < ReadsPerFetch; i++)
|
||||||
|
{
|
||||||
|
string _ = beatmapSet.Beatmaps.First().Hash;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[GlobalCleanup]
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
realmFactory?.Dispose();
|
||||||
|
storage?.Dispose();
|
||||||
|
updateThread?.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,8 +55,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
{
|
{
|
||||||
var realmContextFactory = osu.Dependencies.Get<RealmContextFactory>();
|
var realmContextFactory = osu.Dependencies.Get<RealmContextFactory>();
|
||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
realmContextFactory.Run(realm => BeatmapImporterTests.EnsureLoaded(realm, timeout));
|
||||||
BeatmapImporterTests.EnsureLoaded(realm, timeout);
|
|
||||||
|
|
||||||
// TODO: add back some extra checks outside of the realm ones?
|
// TODO: add back some extra checks outside of the realm ones?
|
||||||
// var set = queryBeatmapSets().First();
|
// var set = queryBeatmapSets().First();
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Database
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestConstructRealm()
|
public void TestConstructRealm()
|
||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) => { realmFactory.CreateContext().Refresh(); });
|
RunTestWithRealm((realmFactory, _) => { realmFactory.Run(realm => realm.Refresh()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -46,23 +46,21 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
bool callbackRan = false;
|
bool callbackRan = false;
|
||||||
|
|
||||||
using (var context = realmFactory.CreateContext())
|
realmFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var subscription = context.All<BeatmapInfo>().QueryAsyncWithNotifications((sender, changes, error) =>
|
var subscription = realm.All<BeatmapInfo>().QueryAsyncWithNotifications((sender, changes, error) =>
|
||||||
{
|
{
|
||||||
using (realmFactory.CreateContext())
|
realmFactory.Run(_ =>
|
||||||
{
|
{
|
||||||
callbackRan = true;
|
callbackRan = true;
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Force the callback above to run.
|
// Force the callback above to run.
|
||||||
using (realmFactory.CreateContext())
|
realmFactory.Run(r => r.Refresh());
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
subscription?.Dispose();
|
subscription?.Dispose();
|
||||||
}
|
});
|
||||||
|
|
||||||
Assert.IsTrue(callbackRan);
|
Assert.IsTrue(callbackRan);
|
||||||
});
|
});
|
||||||
@ -78,12 +76,12 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (realmFactory.CreateContext())
|
realmFactory.Run(_ =>
|
||||||
{
|
{
|
||||||
hasThreadedUsage.Set();
|
hasThreadedUsage.Set();
|
||||||
|
|
||||||
stopThreadedUsage.Wait();
|
stopThreadedUsage.Wait();
|
||||||
}
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler);
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler);
|
||||||
|
|
||||||
hasThreadedUsage.Wait();
|
hasThreadedUsage.Wait();
|
||||||
|
@ -23,9 +23,9 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
RunTestWithRealm((realmFactory, _) =>
|
RunTestWithRealm((realmFactory, _) =>
|
||||||
{
|
{
|
||||||
ILive<BeatmapInfo> beatmap = realmFactory.CreateContext().Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory);
|
ILive<BeatmapInfo> beatmap = realmFactory.Run(realm => realm.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()))).ToLive(realmFactory));
|
||||||
|
|
||||||
ILive<BeatmapInfo> beatmap2 = realmFactory.CreateContext().All<BeatmapInfo>().First().ToLive(realmFactory);
|
ILive<BeatmapInfo> beatmap2 = realmFactory.Run(realm => realm.All<BeatmapInfo>().First().ToLive(realmFactory));
|
||||||
|
|
||||||
Assert.AreEqual(beatmap, beatmap2);
|
Assert.AreEqual(beatmap, beatmap2);
|
||||||
});
|
});
|
||||||
@ -38,13 +38,18 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||||
|
|
||||||
ILive<BeatmapInfo> liveBeatmap;
|
ILive<BeatmapInfo>? liveBeatmap = null;
|
||||||
|
|
||||||
using (var context = realmFactory.CreateContext())
|
realmFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
context.Write(r => r.Add(beatmap));
|
realm.Write(r => r.Add(beatmap));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realmFactory);
|
||||||
|
});
|
||||||
|
|
||||||
|
using (realmFactory.BlockAllOperations())
|
||||||
|
{
|
||||||
|
// recycle realm before migrating
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target"))
|
||||||
@ -53,7 +58,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
storage.Migrate(migratedStorage);
|
storage.Migrate(migratedStorage);
|
||||||
|
|
||||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -67,8 +72,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var liveBeatmap = beatmap.ToLive(realmFactory);
|
var liveBeatmap = beatmap.ToLive(realmFactory);
|
||||||
|
|
||||||
using (var context = realmFactory.CreateContext())
|
realmFactory.Run(realm => realm.Write(r => r.Add(beatmap)));
|
||||||
context.Write(r => r.Add(beatmap));
|
|
||||||
|
|
||||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||||
});
|
});
|
||||||
@ -99,12 +103,12 @@ namespace osu.Game.Tests.Database
|
|||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
ILive<BeatmapInfo>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
realmFactory.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realmFactory);
|
||||||
}
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
Debug.Assert(liveBeatmap != null);
|
Debug.Assert(liveBeatmap != null);
|
||||||
@ -128,12 +132,12 @@ namespace osu.Game.Tests.Database
|
|||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
ILive<BeatmapInfo>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
realmFactory.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realmFactory);
|
||||||
}
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
Debug.Assert(liveBeatmap != null);
|
Debug.Assert(liveBeatmap != null);
|
||||||
@ -170,12 +174,12 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
realmFactory.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realmFactory);
|
||||||
}
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
Debug.Assert(liveBeatmap != null);
|
Debug.Assert(liveBeatmap != null);
|
||||||
@ -189,13 +193,13 @@ namespace osu.Game.Tests.Database
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Can't be used, even from within a valid context.
|
// Can't be used, even from within a valid context.
|
||||||
using (realmFactory.CreateContext())
|
realmFactory.Run(threadContext =>
|
||||||
{
|
{
|
||||||
Assert.Throws<InvalidOperationException>(() =>
|
Assert.Throws<InvalidOperationException>(() =>
|
||||||
{
|
{
|
||||||
var __ = liveBeatmap.Value;
|
var __ = liveBeatmap.Value;
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -208,12 +212,12 @@ namespace osu.Game.Tests.Database
|
|||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
ILive<BeatmapInfo>? liveBeatmap = null;
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
realmFactory.Run(threadContext =>
|
||||||
{
|
{
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realmFactory);
|
||||||
}
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
Debug.Assert(liveBeatmap != null);
|
Debug.Assert(liveBeatmap != null);
|
||||||
@ -235,50 +239,50 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
int changesTriggered = 0;
|
int changesTriggered = 0;
|
||||||
|
|
||||||
using (var updateThreadContext = realmFactory.CreateContext())
|
realmFactory.Run(outerRealm =>
|
||||||
{
|
{
|
||||||
updateThreadContext.All<BeatmapInfo>().QueryAsyncWithNotifications(gotChange);
|
outerRealm.All<BeatmapInfo>().QueryAsyncWithNotifications(gotChange);
|
||||||
ILive<BeatmapInfo>? liveBeatmap = null;
|
ILive<BeatmapInfo>? liveBeatmap = null;
|
||||||
|
|
||||||
Task.Factory.StartNew(() =>
|
Task.Factory.StartNew(() =>
|
||||||
{
|
{
|
||||||
using (var threadContext = realmFactory.CreateContext())
|
realmFactory.Run(innerRealm =>
|
||||||
{
|
{
|
||||||
var ruleset = CreateRuleset();
|
var ruleset = CreateRuleset();
|
||||||
var beatmap = threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
var beatmap = innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
// add a second beatmap to ensure that a full refresh occurs below.
|
// add a second beatmap to ensure that a full refresh occurs below.
|
||||||
// not just a refresh from the resolved Live.
|
// not just a refresh from the resolved Live.
|
||||||
threadContext.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
innerRealm.Write(r => r.Add(new BeatmapInfo(ruleset, new BeatmapDifficulty(), new BeatmapMetadata())));
|
||||||
|
|
||||||
liveBeatmap = beatmap.ToLive(realmFactory);
|
liveBeatmap = beatmap.ToLive(realmFactory);
|
||||||
}
|
});
|
||||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||||
|
|
||||||
Debug.Assert(liveBeatmap != null);
|
Debug.Assert(liveBeatmap != null);
|
||||||
|
|
||||||
// not yet seen by main context
|
// not yet seen by main context
|
||||||
Assert.AreEqual(0, updateThreadContext.All<BeatmapInfo>().Count());
|
Assert.AreEqual(0, outerRealm.All<BeatmapInfo>().Count());
|
||||||
Assert.AreEqual(0, changesTriggered);
|
Assert.AreEqual(0, changesTriggered);
|
||||||
|
|
||||||
liveBeatmap.PerformRead(resolved =>
|
liveBeatmap.PerformRead(resolved =>
|
||||||
{
|
{
|
||||||
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
|
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
Assert.AreEqual(2, updateThreadContext.All<BeatmapInfo>().Count());
|
Assert.AreEqual(2, outerRealm.All<BeatmapInfo>().Count());
|
||||||
Assert.AreEqual(1, changesTriggered);
|
Assert.AreEqual(1, changesTriggered);
|
||||||
|
|
||||||
// can access properties without a crash.
|
// can access properties without a crash.
|
||||||
Assert.IsFalse(resolved.Hidden);
|
Assert.IsFalse(resolved.Hidden);
|
||||||
|
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
updateThreadContext.Write(r =>
|
outerRealm.Write(r =>
|
||||||
{
|
{
|
||||||
// can use with the main context.
|
// can use with the main context.
|
||||||
r.Remove(resolved);
|
r.Remove(resolved);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
void gotChange(IRealmCollection<BeatmapInfo> sender, ChangeSet changes, Exception error)
|
void gotChange(IRealmCollection<BeatmapInfo> sender, ChangeSet changes, Exception error)
|
||||||
{
|
{
|
||||||
|
@ -60,15 +60,12 @@ namespace osu.Game.Tests.Database
|
|||||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||||
|
|
||||||
// Add some excess bindings for an action which only supports 1.
|
// Add some excess bindings for an action which only supports 1.
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
realmContextFactory.Write(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
|
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
|
||||||
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
|
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
|
||||||
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
|
realm.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
|
||||||
|
});
|
||||||
transaction.Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
|
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
|
||||||
|
|
||||||
@ -79,13 +76,13 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
private int queryCount(GlobalAction? match = null)
|
private int queryCount(GlobalAction? match = null)
|
||||||
{
|
{
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
return realmContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var results = realm.All<RealmKeyBinding>();
|
var results = realm.All<RealmKeyBinding>();
|
||||||
if (match.HasValue)
|
if (match.HasValue)
|
||||||
results = results.Where(k => k.ActionInt == (int)match.Value);
|
results = results.Where(k => k.ActionInt == (int)match.Value);
|
||||||
return results.Count();
|
return results.Count();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -95,26 +92,26 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||||
|
|
||||||
using (var primaryRealm = realmContextFactory.CreateContext())
|
realmContextFactory.Run(outerRealm =>
|
||||||
{
|
{
|
||||||
var backBinding = primaryRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
var backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
||||||
|
|
||||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
|
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
|
||||||
|
|
||||||
var tsr = ThreadSafeReference.Create(backBinding);
|
var tsr = ThreadSafeReference.Create(backBinding);
|
||||||
|
|
||||||
using (var threadedContext = realmContextFactory.CreateContext())
|
realmContextFactory.Run(innerRealm =>
|
||||||
{
|
{
|
||||||
var binding = threadedContext.ResolveReference(tsr);
|
var binding = innerRealm.ResolveReference(tsr);
|
||||||
threadedContext.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
|
innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
|
||||||
}
|
});
|
||||||
|
|
||||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
||||||
|
|
||||||
// check still correct after re-query.
|
// check still correct after re-query.
|
||||||
backBinding = primaryRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
||||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
|
@ -60,8 +60,8 @@ namespace osu.Game.Tests.Online
|
|||||||
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
|
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
|
||||||
testBeatmapSet = testBeatmapInfo.BeatmapSet;
|
testBeatmapSet = testBeatmapInfo.BeatmapSet;
|
||||||
|
|
||||||
ContextFactory.Context.Write(r => r.RemoveAll<BeatmapSetInfo>());
|
ContextFactory.Write(r => r.RemoveAll<BeatmapSetInfo>());
|
||||||
ContextFactory.Context.Write(r => r.RemoveAll<BeatmapInfo>());
|
ContextFactory.Write(r => r.RemoveAll<BeatmapInfo>());
|
||||||
|
|
||||||
selectedItem.Value = new PlaylistItem
|
selectedItem.Value = new PlaylistItem
|
||||||
{
|
{
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
realmContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var beatmapInfo = realm.All<BeatmapInfo>()
|
var beatmapInfo = realm.All<BeatmapInfo>()
|
||||||
.Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0)
|
.Filter($"{nameof(BeatmapInfo.Ruleset)}.{nameof(RulesetInfo.OnlineID)} = $0", 0)
|
||||||
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
|
|
||||||
if (beatmapInfo != null)
|
if (beatmapInfo != null)
|
||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -122,11 +122,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
using (var realm = realmFactory.CreateContext())
|
realmFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
// Due to soft deletions, we can re-use deleted scores between test runs
|
// Due to soft deletions, we can re-use deleted scores between test runs
|
||||||
scoreManager.Undelete(realm.All<ScoreInfo>().Where(s => s.DeletePending).ToList());
|
scoreManager.Undelete(realm.All<ScoreInfo>().Where(s => s.DeletePending).ToList());
|
||||||
}
|
});
|
||||||
|
|
||||||
leaderboard.Scores = null;
|
leaderboard.Scores = null;
|
||||||
leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables
|
leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables
|
||||||
|
@ -119,15 +119,17 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="beatmapInfo">The beatmap difficulty to hide.</param>
|
/// <param name="beatmapInfo">The beatmap difficulty to hide.</param>
|
||||||
public void Hide(BeatmapInfo beatmapInfo)
|
public void Hide(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
using (var realm = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
if (!beatmapInfo.IsManaged)
|
using (var transaction = realm.BeginWrite())
|
||||||
beatmapInfo = realm.Find<BeatmapInfo>(beatmapInfo.ID);
|
{
|
||||||
|
if (!beatmapInfo.IsManaged)
|
||||||
|
beatmapInfo = realm.Find<BeatmapInfo>(beatmapInfo.ID);
|
||||||
|
|
||||||
beatmapInfo.Hidden = true;
|
beatmapInfo.Hidden = true;
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -136,27 +138,31 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <param name="beatmapInfo">The beatmap difficulty to restore.</param>
|
/// <param name="beatmapInfo">The beatmap difficulty to restore.</param>
|
||||||
public void Restore(BeatmapInfo beatmapInfo)
|
public void Restore(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
using (var realm = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
if (!beatmapInfo.IsManaged)
|
using (var transaction = realm.BeginWrite())
|
||||||
beatmapInfo = realm.Find<BeatmapInfo>(beatmapInfo.ID);
|
{
|
||||||
|
if (!beatmapInfo.IsManaged)
|
||||||
|
beatmapInfo = realm.Find<BeatmapInfo>(beatmapInfo.ID);
|
||||||
|
|
||||||
beatmapInfo.Hidden = false;
|
beatmapInfo.Hidden = false;
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RestoreAll()
|
public void RestoreAll()
|
||||||
{
|
{
|
||||||
using (var realm = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
foreach (var beatmap in realm.All<BeatmapInfo>().Where(b => b.Hidden))
|
using (var transaction = realm.BeginWrite())
|
||||||
beatmap.Hidden = false;
|
{
|
||||||
|
foreach (var beatmap in realm.All<BeatmapInfo>().Where(b => b.Hidden))
|
||||||
|
beatmap.Hidden = false;
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -165,8 +171,11 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
||||||
public List<BeatmapSetInfo> GetAllUsableBeatmapSets()
|
public List<BeatmapSetInfo> GetAllUsableBeatmapSets()
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
return contextFactory.Run(realm =>
|
||||||
return context.All<BeatmapSetInfo>().Where(b => !b.DeletePending).Detach();
|
{
|
||||||
|
realm.Refresh();
|
||||||
|
return realm.All<BeatmapSetInfo>().Where(b => !b.DeletePending).Detach();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -176,8 +185,7 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||||
public ILive<BeatmapSetInfo>? QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query)
|
public ILive<BeatmapSetInfo>? QueryBeatmapSet(Expression<Func<BeatmapSetInfo, bool>> query)
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
return contextFactory.Run(realm => realm.All<BeatmapSetInfo>().FirstOrDefault(query)?.ToLive(contextFactory));
|
||||||
return context.All<BeatmapSetInfo>().FirstOrDefault(query)?.ToLive(contextFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Delegation to BeatmapModelManager (methods which previously existed locally).
|
#region Delegation to BeatmapModelManager (methods which previously existed locally).
|
||||||
@ -232,21 +240,20 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public void Delete(Expression<Func<BeatmapSetInfo, bool>>? filter = null, bool silent = false)
|
public void Delete(Expression<Func<BeatmapSetInfo, bool>>? filter = null, bool silent = false)
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var items = context.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
var items = realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
||||||
|
|
||||||
if (filter != null)
|
if (filter != null)
|
||||||
items = items.Where(filter);
|
items = items.Where(filter);
|
||||||
|
|
||||||
beatmapModelManager.Delete(items.ToList(), silent);
|
beatmapModelManager.Delete(items.ToList(), silent);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UndeleteAll()
|
public void UndeleteAll()
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
contextFactory.Run(realm => beatmapModelManager.Undelete(realm.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
||||||
beatmapModelManager.Undelete(context.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Undelete(List<BeatmapSetInfo> items, bool silent = false)
|
public void Undelete(List<BeatmapSetInfo> items, bool silent = false)
|
||||||
@ -305,13 +312,13 @@ namespace osu.Game.Beatmaps
|
|||||||
// If we seem to be missing files, now is a good time to re-fetch.
|
// If we seem to be missing files, now is a good time to re-fetch.
|
||||||
if (importedBeatmap?.BeatmapSet?.Files.Count == 0)
|
if (importedBeatmap?.BeatmapSet?.Files.Count == 0)
|
||||||
{
|
{
|
||||||
using (var realm = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var refetch = realm.Find<BeatmapInfo>(importedBeatmap.ID)?.Detach();
|
var refetch = realm.Find<BeatmapInfo>(importedBeatmap.ID)?.Detach();
|
||||||
|
|
||||||
if (refetch != null)
|
if (refetch != null)
|
||||||
importedBeatmap = refetch;
|
importedBeatmap = refetch;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap);
|
return workingBeatmapCache.GetWorkingBeatmap(importedBeatmap);
|
||||||
|
@ -98,17 +98,16 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||||
public BeatmapInfo? QueryBeatmap(Expression<Func<BeatmapInfo, bool>> query)
|
public BeatmapInfo? QueryBeatmap(Expression<Func<BeatmapInfo, bool>> query)
|
||||||
{
|
{
|
||||||
using (var context = ContextFactory.CreateContext())
|
return ContextFactory.Run(realm => realm.All<BeatmapInfo>().FirstOrDefault(query)?.Detach());
|
||||||
return context.All<BeatmapInfo>().FirstOrDefault(query)?.Detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(BeatmapSetInfo item)
|
public void Update(BeatmapSetInfo item)
|
||||||
{
|
{
|
||||||
using (var realm = ContextFactory.CreateContext())
|
ContextFactory.Write(realm =>
|
||||||
{
|
{
|
||||||
var existing = realm.Find<BeatmapSetInfo>(item.ID);
|
var existing = realm.Find<BeatmapSetInfo>(item.ID);
|
||||||
realm.Write(r => item.CopyChangesToRealm(existing));
|
item.CopyChangesToRealm(existing);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -142,7 +141,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
int count = existingBeatmapSets.Count();
|
int count = existingBeatmapSets.Count();
|
||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
realmContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
log($"Found {count} beatmaps in EF");
|
log($"Found {count} beatmaps in EF");
|
||||||
|
|
||||||
@ -227,7 +226,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
log($"Successfully migrated {count} beatmaps to realm");
|
log($"Successfully migrated {count} beatmaps to realm");
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeatmapMetadata getBestMetadata(EFBeatmapMetadata? beatmapMetadata, EFBeatmapMetadata? beatmapSetMetadata)
|
private BeatmapMetadata getBestMetadata(EFBeatmapMetadata? beatmapMetadata, EFBeatmapMetadata? beatmapSetMetadata)
|
||||||
@ -273,7 +272,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
int count = existingScores.Count();
|
int count = existingScores.Count();
|
||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
realmContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
log($"Found {count} scores in EF");
|
log($"Found {count} scores in EF");
|
||||||
|
|
||||||
@ -341,7 +340,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
log($"Successfully migrated {count} scores to realm");
|
log($"Successfully migrated {count} scores to realm");
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void migrateSkins(OsuDbContext db)
|
private void migrateSkins(OsuDbContext db)
|
||||||
@ -370,37 +369,39 @@ namespace osu.Game.Database
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
realmContextFactory.Run(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
// only migrate data if the realm database is empty.
|
using (var transaction = realm.BeginWrite())
|
||||||
// note that this cannot be written as: `realm.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
|
||||||
if (!realm.All<SkinInfo>().Any(s => !s.Protected))
|
|
||||||
{
|
{
|
||||||
log($"Migrating {existingSkins.Count} skins");
|
// only migrate data if the realm database is empty.
|
||||||
|
// note that this cannot be written as: `realm.All<SkinInfo>().All(s => s.Protected)`, because realm does not support `.All()`.
|
||||||
foreach (var skin in existingSkins)
|
if (!realm.All<SkinInfo>().Any(s => !s.Protected))
|
||||||
{
|
{
|
||||||
var realmSkin = new SkinInfo
|
log($"Migrating {existingSkins.Count} skins");
|
||||||
|
|
||||||
|
foreach (var skin in existingSkins)
|
||||||
{
|
{
|
||||||
Name = skin.Name,
|
var realmSkin = new SkinInfo
|
||||||
Creator = skin.Creator,
|
{
|
||||||
Hash = skin.Hash,
|
Name = skin.Name,
|
||||||
Protected = false,
|
Creator = skin.Creator,
|
||||||
InstantiationInfo = skin.InstantiationInfo,
|
Hash = skin.Hash,
|
||||||
};
|
Protected = false,
|
||||||
|
InstantiationInfo = skin.InstantiationInfo,
|
||||||
|
};
|
||||||
|
|
||||||
migrateFiles(skin, realm, realmSkin);
|
migrateFiles(skin, realm, realmSkin);
|
||||||
|
|
||||||
realm.Add(realmSkin);
|
realm.Add(realmSkin);
|
||||||
|
|
||||||
if (skin.ID == userSkinInt)
|
if (skin.ID == userSkinInt)
|
||||||
userSkinChoice.Value = realmSkin.ID.ToString();
|
userSkinChoice.Value = realmSkin.ID.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void migrateFiles<T>(IHasFiles<T> fileSource, Realm realm, IHasRealmFiles realmObject) where T : INamedFileInfo
|
private static void migrateFiles<T>(IHasFiles<T> fileSource, Realm realm, IHasRealmFiles realmObject) where T : INamedFileInfo
|
||||||
@ -427,36 +428,38 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
log("Beginning settings migration to realm");
|
log("Beginning settings migration to realm");
|
||||||
|
|
||||||
using (var realm = realmContextFactory.CreateContext())
|
realmContextFactory.Run(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
// only migrate data if the realm database is empty.
|
using (var transaction = realm.BeginWrite())
|
||||||
if (!realm.All<RealmRulesetSetting>().Any())
|
|
||||||
{
|
{
|
||||||
log($"Migrating {existingSettings.Count} settings");
|
// only migrate data if the realm database is empty.
|
||||||
|
if (!realm.All<RealmRulesetSetting>().Any())
|
||||||
foreach (var dkb in existingSettings)
|
|
||||||
{
|
{
|
||||||
if (dkb.RulesetID == null)
|
log($"Migrating {existingSettings.Count} settings");
|
||||||
continue;
|
|
||||||
|
|
||||||
string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value);
|
foreach (var dkb in existingSettings)
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(shortName))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
realm.Add(new RealmRulesetSetting
|
|
||||||
{
|
{
|
||||||
Key = dkb.Key,
|
if (dkb.RulesetID == null)
|
||||||
Value = dkb.StringValue,
|
continue;
|
||||||
RulesetName = shortName,
|
|
||||||
Variant = dkb.Variant ?? 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
transaction.Commit();
|
string? shortName = getRulesetShortNameFromLegacyID(dkb.RulesetID.Value);
|
||||||
}
|
|
||||||
|
if (string.IsNullOrEmpty(shortName))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
realm.Add(new RealmRulesetSetting
|
||||||
|
{
|
||||||
|
Key = dkb.Key,
|
||||||
|
Value = dkb.StringValue,
|
||||||
|
RulesetName = shortName,
|
||||||
|
Variant = dkb.Variant ?? 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? getRulesetShortNameFromLegacyID(long rulesetId) =>
|
private string? getRulesetShortNameFromLegacyID(long rulesetId) =>
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
// 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 Realms;
|
|
||||||
|
|
||||||
namespace osu.Game.Database
|
|
||||||
{
|
|
||||||
public interface IRealmFactory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The main realm context, bound to the update thread.
|
|
||||||
/// </summary>
|
|
||||||
Realm Context { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new realm context for use on the current thread.
|
|
||||||
/// </summary>
|
|
||||||
Realm CreateContext();
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RealmContextFactory : IDisposable, IRealmFactory
|
public class RealmContextFactory : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Storage storage;
|
private readonly Storage storage;
|
||||||
|
|
||||||
@ -72,13 +72,13 @@ namespace osu.Game.Database
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!ThreadSafety.IsUpdateThread)
|
if (!ThreadSafety.IsUpdateThread)
|
||||||
throw new InvalidOperationException(@$"Use {nameof(CreateContext)} when performing realm operations from a non-update thread");
|
throw new InvalidOperationException(@$"Use {nameof(Run)}/{nameof(Write)} when performing realm operations from a non-update thread");
|
||||||
|
|
||||||
lock (contextLock)
|
lock (contextLock)
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
context = CreateContext();
|
context = createContext();
|
||||||
Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}");
|
Logger.Log(@$"Opened realm ""{context.Config.DatabasePath}"" at version {context.Config.SchemaVersion}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
private void cleanupPendingDeletions()
|
private void cleanupPendingDeletions()
|
||||||
{
|
{
|
||||||
using (var realm = CreateContext())
|
using (var realm = createContext())
|
||||||
using (var transaction = realm.BeginWrite())
|
using (var transaction = realm.BeginWrite())
|
||||||
{
|
{
|
||||||
var pendingDeleteScores = realm.All<ScoreInfo>().Where(s => s.DeletePending);
|
var pendingDeleteScores = realm.All<ScoreInfo>().Where(s => s.DeletePending);
|
||||||
@ -169,7 +169,60 @@ namespace osu.Game.Database
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool Compact() => Realm.Compact(getConfiguration());
|
public bool Compact() => Realm.Compact(getConfiguration());
|
||||||
|
|
||||||
public Realm CreateContext()
|
/// <summary>
|
||||||
|
/// Run work on realm with a return value.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Handles correct context management automatically.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="action">The work to run.</param>
|
||||||
|
/// <typeparam name="T">The return type.</typeparam>
|
||||||
|
public T Run<T>(Func<Realm, T> action)
|
||||||
|
{
|
||||||
|
if (ThreadSafety.IsUpdateThread)
|
||||||
|
return action(Context);
|
||||||
|
|
||||||
|
using (var realm = createContext())
|
||||||
|
return action(realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Run work on realm.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Handles correct context management automatically.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="action">The work to run.</param>
|
||||||
|
public void Run(Action<Realm> action)
|
||||||
|
{
|
||||||
|
if (ThreadSafety.IsUpdateThread)
|
||||||
|
action(Context);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var realm = createContext())
|
||||||
|
action(realm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write changes to realm.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Handles correct context management and transaction committing automatically.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="action">The work to run.</param>
|
||||||
|
public void Write(Action<Realm> action)
|
||||||
|
{
|
||||||
|
if (ThreadSafety.IsUpdateThread)
|
||||||
|
Context.Write(action);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (var realm = createContext())
|
||||||
|
realm.Write(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Realm createContext()
|
||||||
{
|
{
|
||||||
if (isDisposed)
|
if (isDisposed)
|
||||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||||
|
@ -51,8 +51,10 @@ namespace osu.Game.Database
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var realm = realmFactory.CreateContext())
|
realmFactory.Run(realm =>
|
||||||
perform(realm.Find<T>(ID));
|
{
|
||||||
|
perform(retrieveFromID(realm, ID));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -64,15 +66,15 @@ namespace osu.Game.Database
|
|||||||
if (!IsManaged)
|
if (!IsManaged)
|
||||||
return perform(data);
|
return perform(data);
|
||||||
|
|
||||||
using (var realm = realmFactory.CreateContext())
|
return realmFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var returnData = perform(realm.Find<T>(ID));
|
var returnData = perform(retrieveFromID(realm, ID));
|
||||||
|
|
||||||
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
|
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
|
||||||
throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}.");
|
throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}.");
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -106,6 +108,22 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private T retrieveFromID(Realm realm, Guid id)
|
||||||
|
{
|
||||||
|
var found = realm.Find<T>(ID);
|
||||||
|
|
||||||
|
if (found == null)
|
||||||
|
{
|
||||||
|
// It may be that we access this from the update thread before a refresh has taken place.
|
||||||
|
// To ensure that behaviour matches what we'd expect (the object *is* available), force
|
||||||
|
// a refresh to bring in any off-thread changes immediately.
|
||||||
|
realm.Refresh();
|
||||||
|
found = realm.Find<T>(ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(ILive<T>? other) => ID == other?.ID;
|
public bool Equals(ILive<T>? other) => ID == other?.ID;
|
||||||
|
|
||||||
public override string ToString() => PerformRead(i => i.ToString());
|
public override string ToString() => PerformRead(i => i.ToString());
|
||||||
|
@ -21,6 +21,9 @@ namespace osu.Game.Database
|
|||||||
/// <param name="data">The realm data.</param>
|
/// <param name="data">The realm data.</param>
|
||||||
public RealmLiveUnmanaged(T data)
|
public RealmLiveUnmanaged(T data)
|
||||||
{
|
{
|
||||||
|
if (data.IsManaged)
|
||||||
|
throw new InvalidOperationException($"Cannot use {nameof(RealmLiveUnmanaged<T>)} with managed instances");
|
||||||
|
|
||||||
Value = data;
|
Value = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Input
|
|||||||
{
|
{
|
||||||
List<string> combinations = new List<string>();
|
List<string> combinations = new List<string>();
|
||||||
|
|
||||||
using (var context = realmFactory.CreateContext())
|
realmFactory.Run(context =>
|
||||||
{
|
{
|
||||||
foreach (var action in context.All<RealmKeyBinding>().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction))
|
foreach (var action in context.All<RealmKeyBinding>().Where(b => string.IsNullOrEmpty(b.RulesetName) && (GlobalAction)b.ActionInt == globalAction))
|
||||||
{
|
{
|
||||||
@ -44,7 +44,7 @@ namespace osu.Game.Input
|
|||||||
if (str.Length > 0)
|
if (str.Length > 0)
|
||||||
combinations.Add(str);
|
combinations.Add(str);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return combinations;
|
return combinations;
|
||||||
}
|
}
|
||||||
@ -56,24 +56,26 @@ namespace osu.Game.Input
|
|||||||
/// <param name="rulesets">The rulesets to populate defaults from.</param>
|
/// <param name="rulesets">The rulesets to populate defaults from.</param>
|
||||||
public void Register(KeyBindingContainer container, IEnumerable<RulesetInfo> rulesets)
|
public void Register(KeyBindingContainer container, IEnumerable<RulesetInfo> rulesets)
|
||||||
{
|
{
|
||||||
using (var realm = realmFactory.CreateContext())
|
realmFactory.Run(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
// intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed.
|
using (var transaction = realm.BeginWrite())
|
||||||
// this is much faster as a result.
|
|
||||||
var existingBindings = realm.All<RealmKeyBinding>().ToList();
|
|
||||||
|
|
||||||
insertDefaults(realm, existingBindings, container.DefaultKeyBindings);
|
|
||||||
|
|
||||||
foreach (var ruleset in rulesets)
|
|
||||||
{
|
{
|
||||||
var instance = ruleset.CreateInstance();
|
// intentionally flattened to a list rather than querying against the IQueryable, as nullable fields being queried against aren't indexed.
|
||||||
foreach (int variant in instance.AvailableVariants)
|
// this is much faster as a result.
|
||||||
insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant);
|
var existingBindings = realm.All<RealmKeyBinding>().ToList();
|
||||||
}
|
|
||||||
|
|
||||||
transaction.Commit();
|
insertDefaults(realm, existingBindings, container.DefaultKeyBindings);
|
||||||
}
|
|
||||||
|
foreach (var ruleset in rulesets)
|
||||||
|
{
|
||||||
|
var instance = ruleset.CreateInstance();
|
||||||
|
foreach (int variant in instance.AvailableVariants)
|
||||||
|
insertDefaults(realm, existingBindings, instance.GetDefaultKeyBindings(variant), ruleset.ShortName, variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertDefaults(Realm realm, List<RealmKeyBinding> existingBindings, IEnumerable<IKeyBinding> defaults, string? rulesetName = null, int? variant = null)
|
private void insertDefaults(Realm realm, List<RealmKeyBinding> existingBindings, IEnumerable<IKeyBinding> defaults, string? rulesetName = null, int? variant = null)
|
||||||
|
@ -386,11 +386,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
|
|
||||||
private void updateStoreFromButton(KeyButton button)
|
private void updateStoreFromButton(KeyButton button)
|
||||||
{
|
{
|
||||||
using (var realm = realmFactory.CreateContext())
|
realmFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var binding = realm.Find<RealmKeyBinding>(((IHasGuidPrimaryKey)button.KeyBinding).ID);
|
var binding = realm.Find<RealmKeyBinding>(((IHasGuidPrimaryKey)button.KeyBinding).ID);
|
||||||
realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString);
|
realm.Write(() => binding.KeyCombinationString = button.KeyBinding.KeyCombinationString);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateIsDefaultValue()
|
private void updateIsDefaultValue()
|
||||||
|
@ -34,10 +34,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
{
|
{
|
||||||
string rulesetName = Ruleset?.ShortName;
|
string rulesetName = Ruleset?.ShortName;
|
||||||
|
|
||||||
List<RealmKeyBinding> bindings;
|
var bindings = realmFactory.Run(realm => realm.All<RealmKeyBinding>()
|
||||||
|
.Where(b => b.RulesetName == rulesetName && b.Variant == variant)
|
||||||
using (var realm = realmFactory.CreateContext())
|
.Detach());
|
||||||
bindings = realm.All<RealmKeyBinding>().Where(b => b.RulesetName == rulesetName && b.Variant == variant).Detach();
|
|
||||||
|
|
||||||
foreach (var defaultGroup in Defaults.GroupBy(d => d.Action))
|
foreach (var defaultGroup in Defaults.GroupBy(d => d.Action))
|
||||||
{
|
{
|
||||||
|
@ -56,21 +56,15 @@ namespace osu.Game.Rulesets.Configuration
|
|||||||
pendingWrites.Clear();
|
pendingWrites.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (realmFactory == null)
|
realmFactory?.Write(realm =>
|
||||||
return true;
|
|
||||||
|
|
||||||
using (var context = realmFactory.CreateContext())
|
|
||||||
{
|
{
|
||||||
context.Write(realm =>
|
foreach (var c in changed)
|
||||||
{
|
{
|
||||||
foreach (var c in changed)
|
var setting = realm.All<RealmRulesetSetting>().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString());
|
||||||
{
|
|
||||||
var setting = realm.All<RealmRulesetSetting>().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString());
|
|
||||||
|
|
||||||
setting.Value = ConfigStore[c].ToString();
|
setting.Value = ConfigStore[c].ToString();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -100,74 +100,71 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
private void addMissingRulesets()
|
private void addMissingRulesets()
|
||||||
{
|
{
|
||||||
using (var context = realmFactory.CreateContext())
|
realmFactory.Write(realm =>
|
||||||
{
|
{
|
||||||
context.Write(realm =>
|
var rulesets = realm.All<RulesetInfo>();
|
||||||
|
|
||||||
|
List<Ruleset> instances = loadedAssemblies.Values
|
||||||
|
.Select(r => Activator.CreateInstance(r) as Ruleset)
|
||||||
|
.Where(r => r != null)
|
||||||
|
.Select(r => r.AsNonNull())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
||||||
|
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
||||||
{
|
{
|
||||||
var rulesets = realm.All<RulesetInfo>();
|
if (realm.All<RulesetInfo>().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null)
|
||||||
|
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
||||||
|
}
|
||||||
|
|
||||||
List<Ruleset> instances = loadedAssemblies.Values
|
// add any other rulesets which have assemblies present but are not yet in the database.
|
||||||
.Select(r => Activator.CreateInstance(r) as Ruleset)
|
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
||||||
.Where(r => r != null)
|
{
|
||||||
.Select(r => r.AsNonNull())
|
if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
|
||||||
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
|
||||||
{
|
{
|
||||||
if (realm.All<RulesetInfo>().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null)
|
var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName);
|
||||||
|
|
||||||
|
if (existingSameShortName != null)
|
||||||
|
{
|
||||||
|
// even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName.
|
||||||
|
// this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one.
|
||||||
|
// in such cases, update the instantiation info of the existing entry to point to the new one.
|
||||||
|
existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add any other rulesets which have assemblies present but are not yet in the database.
|
List<RulesetInfo> detachedRulesets = new List<RulesetInfo>();
|
||||||
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
|
||||||
|
// perform a consistency check and detach final rulesets from realm for cross-thread runtime usage.
|
||||||
|
foreach (var r in rulesets.OrderBy(r => r.OnlineID))
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
var resolvedType = Type.GetType(r.InstantiationInfo)
|
||||||
{
|
?? throw new RulesetLoadException(@"Type could not be resolved");
|
||||||
var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName);
|
|
||||||
|
|
||||||
if (existingSameShortName != null)
|
var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo
|
||||||
{
|
?? throw new RulesetLoadException(@"Instantiation failure");
|
||||||
// even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName.
|
|
||||||
// this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one.
|
r.Name = instanceInfo.Name;
|
||||||
// in such cases, update the instantiation info of the existing entry to point to the new one.
|
r.ShortName = instanceInfo.ShortName;
|
||||||
existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo;
|
r.InstantiationInfo = instanceInfo.InstantiationInfo;
|
||||||
}
|
r.Available = true;
|
||||||
else
|
|
||||||
realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
|
detachedRulesets.Add(r.Clone());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
List<RulesetInfo> detachedRulesets = new List<RulesetInfo>();
|
|
||||||
|
|
||||||
// perform a consistency check and detach final rulesets from realm for cross-thread runtime usage.
|
|
||||||
foreach (var r in rulesets.OrderBy(r => r.OnlineID))
|
|
||||||
{
|
{
|
||||||
try
|
r.Available = false;
|
||||||
{
|
Logger.Log($"Could not load ruleset {r}: {ex.Message}");
|
||||||
var resolvedType = Type.GetType(r.InstantiationInfo)
|
|
||||||
?? throw new RulesetLoadException(@"Type could not be resolved");
|
|
||||||
|
|
||||||
var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo
|
|
||||||
?? throw new RulesetLoadException(@"Instantiation failure");
|
|
||||||
|
|
||||||
r.Name = instanceInfo.Name;
|
|
||||||
r.ShortName = instanceInfo.ShortName;
|
|
||||||
r.InstantiationInfo = instanceInfo.InstantiationInfo;
|
|
||||||
r.Available = true;
|
|
||||||
|
|
||||||
detachedRulesets.Add(r.Clone());
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
r.Available = false;
|
|
||||||
Logger.Log($"Could not load ruleset {r}: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
availableRulesets.AddRange(detachedRulesets);
|
availableRulesets.AddRange(detachedRulesets);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFromAppDomain()
|
private void loadFromAppDomain()
|
||||||
|
@ -51,8 +51,7 @@ namespace osu.Game.Scoring
|
|||||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||||
public ScoreInfo Query(Expression<Func<ScoreInfo, bool>> query)
|
public ScoreInfo Query(Expression<Func<ScoreInfo, bool>> query)
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
return contextFactory.Run(realm => realm.All<ScoreInfo>().FirstOrDefault(query)?.Detach());
|
||||||
return context.All<ScoreInfo>().FirstOrDefault(query)?.Detach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -255,16 +254,16 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
public void Delete([CanBeNull] Expression<Func<ScoreInfo, bool>> filter = null, bool silent = false)
|
public void Delete([CanBeNull] Expression<Func<ScoreInfo, bool>> filter = null, bool silent = false)
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var items = context.All<ScoreInfo>()
|
var items = realm.All<ScoreInfo>()
|
||||||
.Where(s => !s.DeletePending);
|
.Where(s => !s.DeletePending);
|
||||||
|
|
||||||
if (filter != null)
|
if (filter != null)
|
||||||
items = items.Where(filter);
|
items = items.Where(filter);
|
||||||
|
|
||||||
scoreModelManager.Delete(items.ToList(), silent);
|
scoreModelManager.Delete(items.ToList(), silent);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Delete(List<ScoreInfo> items, bool silent = false)
|
public void Delete(List<ScoreInfo> items, bool silent = false)
|
||||||
|
@ -74,8 +74,7 @@ namespace osu.Game.Scoring
|
|||||||
|
|
||||||
public override bool IsAvailableLocally(ScoreInfo model)
|
public override bool IsAvailableLocally(ScoreInfo model)
|
||||||
{
|
{
|
||||||
using (var context = ContextFactory.CreateContext())
|
return ContextFactory.Run(realm => realm.All<ScoreInfo>().Any(s => s.OnlineID == model.OnlineID));
|
||||||
return context.All<ScoreInfo>().Any(b => b.OnlineID == model.OnlineID);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,9 +114,10 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
CarouselRoot newRoot = new CarouselRoot(this);
|
CarouselRoot newRoot = new CarouselRoot(this);
|
||||||
|
|
||||||
newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null));
|
newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null));
|
||||||
|
|
||||||
root = newRoot;
|
root = newRoot;
|
||||||
|
|
||||||
if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet))
|
if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet))
|
||||||
selectedBeatmapSet = null;
|
selectedBeatmapSet = null;
|
||||||
|
|
||||||
@ -178,8 +179,7 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
if (!loadedTestBeatmaps)
|
if (!loadedTestBeatmaps)
|
||||||
{
|
{
|
||||||
using (var realm = realmFactory.CreateContext())
|
realmFactory.Run(realm => loadBeatmapSets(getBeatmapSets(realm)));
|
||||||
loadBeatmapSets(getBeatmapSets(realm));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +209,7 @@ namespace osu.Game.Screens.Select
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (int i in changes.InsertedIndices)
|
foreach (int i in changes.InsertedIndices)
|
||||||
RemoveBeatmapSet(sender[i]);
|
removeBeatmapSet(sender[i].ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet changes, Exception error)
|
private void beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeSet changes, Exception error)
|
||||||
@ -223,24 +223,21 @@ namespace osu.Game.Screens.Select
|
|||||||
// During initial population, we must manually account for the fact that our original query was done on an async thread.
|
// During initial population, we must manually account for the fact that our original query was done on an async thread.
|
||||||
// Since then, there may have been imports or deletions.
|
// Since then, there may have been imports or deletions.
|
||||||
// Here we manually catch up on any changes.
|
// Here we manually catch up on any changes.
|
||||||
var populatedSets = new HashSet<Guid>();
|
|
||||||
foreach (var s in beatmapSets)
|
|
||||||
populatedSets.Add(s.BeatmapSet.ID);
|
|
||||||
|
|
||||||
var realmSets = new HashSet<Guid>();
|
var realmSets = new HashSet<Guid>();
|
||||||
foreach (var s in sender)
|
|
||||||
realmSets.Add(s.ID);
|
|
||||||
|
|
||||||
foreach (var s in realmSets)
|
for (int i = 0; i < sender.Count; i++)
|
||||||
|
realmSets.Add(sender[i].ID);
|
||||||
|
|
||||||
|
foreach (var id in realmSets)
|
||||||
{
|
{
|
||||||
if (!populatedSets.Contains(s))
|
if (!root.BeatmapSetsByID.ContainsKey(id))
|
||||||
UpdateBeatmapSet(realmFactory.Context.Find<BeatmapSetInfo>(s));
|
UpdateBeatmapSet(realmFactory.Context.Find<BeatmapSetInfo>(id).Detach());
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var s in populatedSets)
|
foreach (var id in root.BeatmapSetsByID.Keys)
|
||||||
{
|
{
|
||||||
if (!realmSets.Contains(s))
|
if (!realmSets.Contains(id))
|
||||||
RemoveBeatmapSet(realmFactory.Context.Find<BeatmapSetInfo>(s));
|
removeBeatmapSet(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
signalBeatmapsLoaded();
|
signalBeatmapsLoaded();
|
||||||
@ -248,10 +245,10 @@ namespace osu.Game.Screens.Select
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (int i in changes.NewModifiedIndices)
|
foreach (int i in changes.NewModifiedIndices)
|
||||||
UpdateBeatmapSet(sender[i]);
|
UpdateBeatmapSet(sender[i].Detach());
|
||||||
|
|
||||||
foreach (int i in changes.InsertedIndices)
|
foreach (int i in changes.InsertedIndices)
|
||||||
UpdateBeatmapSet(sender[i]);
|
UpdateBeatmapSet(sender[i].Detach());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beatmapsChanged(IRealmCollection<BeatmapInfo> sender, ChangeSet changes, Exception error)
|
private void beatmapsChanged(IRealmCollection<BeatmapInfo> sender, ChangeSet changes, Exception error)
|
||||||
@ -261,16 +258,30 @@ namespace osu.Game.Screens.Select
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (int i in changes.InsertedIndices)
|
foreach (int i in changes.InsertedIndices)
|
||||||
UpdateBeatmapSet(sender[i].BeatmapSet);
|
{
|
||||||
|
var beatmapInfo = sender[i];
|
||||||
|
var beatmapSet = beatmapInfo.BeatmapSet;
|
||||||
|
|
||||||
|
Debug.Assert(beatmapSet != null);
|
||||||
|
|
||||||
|
// Only require to action here if the beatmap is missing.
|
||||||
|
// This avoids processing these events unnecessarily when new beatmaps are imported, for example.
|
||||||
|
if (root.BeatmapSetsByID.TryGetValue(beatmapSet.ID, out var existingSet)
|
||||||
|
&& existingSet.BeatmapSet.Beatmaps.All(b => b.ID != beatmapInfo.ID))
|
||||||
|
{
|
||||||
|
UpdateBeatmapSet(beatmapSet.Detach());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IRealmCollection<BeatmapSetInfo> getBeatmapSets(Realm realm) => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection();
|
private IRealmCollection<BeatmapSetInfo> getBeatmapSets(Realm realm) => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection();
|
||||||
|
|
||||||
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() =>
|
public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) =>
|
||||||
{
|
removeBeatmapSet(beatmapSet.ID);
|
||||||
var existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet));
|
|
||||||
|
|
||||||
if (existingSet == null)
|
private void removeBeatmapSet(Guid beatmapSetID) => Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
root.RemoveChild(existingSet);
|
root.RemoveChild(existingSet);
|
||||||
@ -281,35 +292,32 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
Guid? previouslySelectedID = null;
|
Guid? previouslySelectedID = null;
|
||||||
|
|
||||||
CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.Equals(beatmapSet));
|
|
||||||
|
|
||||||
// If the selected beatmap is about to be removed, store its ID so it can be re-selected if required
|
// If the selected beatmap is about to be removed, store its ID so it can be re-selected if required
|
||||||
if (existingSet?.State?.Value == CarouselItemState.Selected)
|
if (selectedBeatmapSet?.BeatmapSet.ID == beatmapSet.ID)
|
||||||
previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID;
|
previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID;
|
||||||
|
|
||||||
var newSet = createCarouselSet(beatmapSet);
|
var newSet = createCarouselSet(beatmapSet);
|
||||||
|
|
||||||
if (existingSet != null)
|
root.RemoveChild(beatmapSet.ID);
|
||||||
root.RemoveChild(existingSet);
|
|
||||||
|
|
||||||
if (newSet == null)
|
if (newSet != null)
|
||||||
{
|
{
|
||||||
itemsCache.Invalidate();
|
root.AddChild(newSet);
|
||||||
return;
|
|
||||||
|
// check if we can/need to maintain our current selection.
|
||||||
|
if (previouslySelectedID != null)
|
||||||
|
select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
root.AddChild(newSet);
|
|
||||||
|
|
||||||
// only reset scroll position if already near the scroll target.
|
|
||||||
// without this, during a large beatmap import it is impossible to navigate the carousel.
|
|
||||||
applyActiveCriteria(false, alwaysResetScrollPosition: false);
|
|
||||||
|
|
||||||
// check if we can/need to maintain our current selection.
|
|
||||||
if (previouslySelectedID != null)
|
|
||||||
select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet);
|
|
||||||
|
|
||||||
itemsCache.Invalidate();
|
itemsCache.Invalidate();
|
||||||
Schedule(() => BeatmapSetsChanged?.Invoke());
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (!Scroll.UserScrolling)
|
||||||
|
ScrollToSelected(true);
|
||||||
|
|
||||||
|
BeatmapSetsChanged?.Invoke();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -711,8 +719,6 @@ namespace osu.Game.Screens.Select
|
|||||||
|
|
||||||
private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet)
|
private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet)
|
||||||
{
|
{
|
||||||
beatmapSet = beatmapSet.Detach();
|
|
||||||
|
|
||||||
// This can be moved to the realm query if required using:
|
// This can be moved to the realm query if required using:
|
||||||
// .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false")
|
// .Filter("DeletePending == false && Protected == false && ANY Beatmaps.Hidden == false")
|
||||||
//
|
//
|
||||||
@ -913,6 +919,8 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
private readonly BeatmapCarousel carousel;
|
private readonly BeatmapCarousel carousel;
|
||||||
|
|
||||||
|
public readonly Dictionary<Guid, CarouselBeatmapSet> BeatmapSetsByID = new Dictionary<Guid, CarouselBeatmapSet>();
|
||||||
|
|
||||||
public CarouselRoot(BeatmapCarousel carousel)
|
public CarouselRoot(BeatmapCarousel carousel)
|
||||||
{
|
{
|
||||||
// root should always remain selected. if not, PerformSelection will not be called.
|
// root should always remain selected. if not, PerformSelection will not be called.
|
||||||
@ -922,6 +930,28 @@ namespace osu.Game.Screens.Select
|
|||||||
this.carousel = carousel;
|
this.carousel = carousel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void AddChild(CarouselItem i)
|
||||||
|
{
|
||||||
|
CarouselBeatmapSet set = (CarouselBeatmapSet)i;
|
||||||
|
BeatmapSetsByID.Add(set.BeatmapSet.ID, set);
|
||||||
|
|
||||||
|
base.AddChild(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveChild(Guid beatmapSetID)
|
||||||
|
{
|
||||||
|
if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet))
|
||||||
|
RemoveChild(carouselBeatmapSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void RemoveChild(CarouselItem i)
|
||||||
|
{
|
||||||
|
CarouselBeatmapSet set = (CarouselBeatmapSet)i;
|
||||||
|
BeatmapSetsByID.Remove(set.BeatmapSet.ID);
|
||||||
|
|
||||||
|
base.RemoveChild(i);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void PerformSelection()
|
protected override void PerformSelection()
|
||||||
{
|
{
|
||||||
if (LastSelected == null || LastSelected.Filtered.Value)
|
if (LastSelected == null || LastSelected.Filtered.Value)
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Screens.Select.Carousel
|
namespace osu.Game.Screens.Select.Carousel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -11,7 +13,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CarouselGroup : CarouselItem
|
public class CarouselGroup : CarouselItem
|
||||||
{
|
{
|
||||||
public override DrawableCarouselItem CreateDrawableRepresentation() => null;
|
public override DrawableCarouselItem? CreateDrawableRepresentation() => null;
|
||||||
|
|
||||||
public IReadOnlyList<CarouselItem> Children => InternalChildren;
|
public IReadOnlyList<CarouselItem> Children => InternalChildren;
|
||||||
|
|
||||||
@ -23,6 +25,10 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private ulong currentChildID;
|
private ulong currentChildID;
|
||||||
|
|
||||||
|
private Comparer<CarouselItem>? criteriaComparer;
|
||||||
|
|
||||||
|
private FilterCriteria? lastCriteria;
|
||||||
|
|
||||||
public virtual void RemoveChild(CarouselItem i)
|
public virtual void RemoveChild(CarouselItem i)
|
||||||
{
|
{
|
||||||
InternalChildren.Remove(i);
|
InternalChildren.Remove(i);
|
||||||
@ -36,10 +42,24 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
{
|
{
|
||||||
i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue);
|
i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue);
|
||||||
i.ChildID = ++currentChildID;
|
i.ChildID = ++currentChildID;
|
||||||
InternalChildren.Add(i);
|
|
||||||
|
if (lastCriteria != null)
|
||||||
|
{
|
||||||
|
i.Filter(lastCriteria);
|
||||||
|
|
||||||
|
int index = InternalChildren.BinarySearch(i, criteriaComparer);
|
||||||
|
if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement.
|
||||||
|
|
||||||
|
InternalChildren.Insert(index, i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// criteria may be null for initial population. the filtering will be applied post-add.
|
||||||
|
InternalChildren.Add(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CarouselGroup(List<CarouselItem> items = null)
|
public CarouselGroup(List<CarouselItem>? items = null)
|
||||||
{
|
{
|
||||||
if (items != null) InternalChildren = items;
|
if (items != null) InternalChildren = items;
|
||||||
|
|
||||||
@ -67,9 +87,12 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
base.Filter(criteria);
|
base.Filter(criteria);
|
||||||
|
|
||||||
InternalChildren.ForEach(c => c.Filter(criteria));
|
InternalChildren.ForEach(c => c.Filter(criteria));
|
||||||
|
|
||||||
// IEnumerable<T>.OrderBy() is used instead of List<T>.Sort() to ensure sorting stability
|
// IEnumerable<T>.OrderBy() is used instead of List<T>.Sort() to ensure sorting stability
|
||||||
var criteriaComparer = Comparer<CarouselItem>.Create((x, y) => x.CompareTo(criteria, y));
|
criteriaComparer = Comparer<CarouselItem>.Create((x, y) => x.CompareTo(criteria, y));
|
||||||
InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList();
|
InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList();
|
||||||
|
|
||||||
|
lastCriteria = criteria;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value)
|
protected virtual void ChildItemStateChanged(CarouselItem item, CarouselItemState value)
|
||||||
|
@ -55,10 +55,16 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
updateSelectedIndex();
|
updateSelectedIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool addingChildren;
|
||||||
|
|
||||||
public void AddChildren(IEnumerable<CarouselItem> items)
|
public void AddChildren(IEnumerable<CarouselItem> items)
|
||||||
{
|
{
|
||||||
|
addingChildren = true;
|
||||||
|
|
||||||
foreach (var i in items)
|
foreach (var i in items)
|
||||||
base.AddChild(i);
|
AddChild(i);
|
||||||
|
|
||||||
|
addingChildren = false;
|
||||||
|
|
||||||
attemptSelection();
|
attemptSelection();
|
||||||
}
|
}
|
||||||
@ -66,7 +72,8 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
public override void AddChild(CarouselItem i)
|
public override void AddChild(CarouselItem i)
|
||||||
{
|
{
|
||||||
base.AddChild(i);
|
base.AddChild(i);
|
||||||
attemptSelection();
|
if (!addingChildren)
|
||||||
|
attemptSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value)
|
protected override void ChildItemStateChanged(CarouselItem item, CarouselItemState value)
|
||||||
|
@ -147,7 +147,7 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
|
|
||||||
if (Scope == BeatmapLeaderboardScope.Local)
|
if (Scope == BeatmapLeaderboardScope.Local)
|
||||||
{
|
{
|
||||||
using (var realm = realmFactory.CreateContext())
|
realmFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var scores = realm.All<ScoreInfo>()
|
var scores = realm.All<ScoreInfo>()
|
||||||
.AsEnumerable()
|
.AsEnumerable()
|
||||||
@ -171,9 +171,9 @@ namespace osu.Game.Screens.Select.Leaderboards
|
|||||||
|
|
||||||
scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken)
|
scoreManager.OrderByTotalScoreAsync(scores.ToArray(), cancellationToken)
|
||||||
.ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion);
|
.ContinueWith(ordered => scoresCallback?.Invoke(ordered.GetResultSafely()), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api?.IsLoggedIn != true)
|
if (api?.IsLoggedIn != true)
|
||||||
|
@ -87,17 +87,14 @@ namespace osu.Game.Skinning
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Ensure the default entries are present.
|
// Ensure the default entries are present.
|
||||||
using (var context = contextFactory.CreateContext())
|
contextFactory.Write(realm =>
|
||||||
using (var transaction = context.BeginWrite())
|
|
||||||
{
|
{
|
||||||
foreach (var skin in defaultSkins)
|
foreach (var skin in defaultSkins)
|
||||||
{
|
{
|
||||||
if (context.Find<SkinInfo>(skin.SkinInfo.ID) == null)
|
if (realm.Find<SkinInfo>(skin.SkinInfo.ID) == null)
|
||||||
context.Add(skin.SkinInfo.Value);
|
realm.Add(skin.SkinInfo.Value);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
transaction.Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin);
|
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin);
|
||||||
|
|
||||||
@ -113,10 +110,10 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public void SelectRandomSkin()
|
public void SelectRandomSkin()
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
// choose from only user skins, removing the current selection to ensure a new one is chosen.
|
// choose from only user skins, removing the current selection to ensure a new one is chosen.
|
||||||
var randomChoices = context.All<SkinInfo>().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray();
|
var randomChoices = realm.All<SkinInfo>().Where(s => !s.DeletePending && s.ID != CurrentSkinInfo.Value.ID).ToArray();
|
||||||
|
|
||||||
if (randomChoices.Length == 0)
|
if (randomChoices.Length == 0)
|
||||||
{
|
{
|
||||||
@ -127,7 +124,7 @@ namespace osu.Game.Skinning
|
|||||||
var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
|
var chosen = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
|
||||||
|
|
||||||
CurrentSkinInfo.Value = chosen.ToLive(contextFactory);
|
CurrentSkinInfo.Value = chosen.ToLive(contextFactory);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -182,8 +179,7 @@ namespace osu.Game.Skinning
|
|||||||
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
/// <returns>The first result for the provided query, or null if no results were found.</returns>
|
||||||
public ILive<SkinInfo> Query(Expression<Func<SkinInfo, bool>> query)
|
public ILive<SkinInfo> Query(Expression<Func<SkinInfo, bool>> query)
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
return contextFactory.Run(realm => realm.All<SkinInfo>().FirstOrDefault(query)?.ToLive(contextFactory));
|
||||||
return context.All<SkinInfo>().FirstOrDefault(query)?.ToLive(contextFactory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public event Action SourceChanged;
|
public event Action SourceChanged;
|
||||||
@ -293,10 +289,10 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public void Delete([CanBeNull] Expression<Func<SkinInfo, bool>> filter = null, bool silent = false)
|
public void Delete([CanBeNull] Expression<Func<SkinInfo, bool>> filter = null, bool silent = false)
|
||||||
{
|
{
|
||||||
using (var context = contextFactory.CreateContext())
|
contextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var items = context.All<SkinInfo>()
|
var items = realm.All<SkinInfo>()
|
||||||
.Where(s => !s.Protected && !s.DeletePending);
|
.Where(s => !s.Protected && !s.DeletePending);
|
||||||
if (filter != null)
|
if (filter != null)
|
||||||
items = items.Where(filter);
|
items = items.Where(filter);
|
||||||
|
|
||||||
@ -307,7 +303,7 @@ namespace osu.Game.Skinning
|
|||||||
scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged());
|
scheduler.Add(() => CurrentSkinInfo.Value = Skinning.DefaultSkin.CreateInfo().ToLiveUnmanaged());
|
||||||
|
|
||||||
skinModelManager.Delete(items.ToList(), silent);
|
skinModelManager.Delete(items.ToList(), silent);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -205,7 +205,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
private void populateMissingHashes()
|
private void populateMissingHashes()
|
||||||
{
|
{
|
||||||
using (var realm = ContextFactory.CreateContext())
|
ContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
var skinsWithoutHashes = realm.All<SkinInfo>().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray();
|
var skinsWithoutHashes = realm.All<SkinInfo>().Where(i => !i.Protected && string.IsNullOrEmpty(i.Hash)).ToArray();
|
||||||
|
|
||||||
@ -221,7 +221,7 @@ namespace osu.Game.Skinning
|
|||||||
Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid");
|
Logger.Error(e, $"Existing skin {skin} has been deleted during hash recomputation due to being invalid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources);
|
private Skin createInstance(SkinInfo item) => item.CreateInstance(skinResources);
|
||||||
|
@ -165,8 +165,7 @@ namespace osu.Game.Stores
|
|||||||
|
|
||||||
public override bool IsAvailableLocally(BeatmapSetInfo model)
|
public override bool IsAvailableLocally(BeatmapSetInfo model)
|
||||||
{
|
{
|
||||||
using (var context = ContextFactory.CreateContext())
|
return ContextFactory.Run(realm => realm.All<BeatmapInfo>().Any(b => b.OnlineID == model.OnlineID));
|
||||||
return context.All<BeatmapInfo>().Any(b => b.OnlineID == model.OnlineID);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string HumanisedModelName => "beatmap";
|
public override string HumanisedModelName => "beatmap";
|
||||||
|
@ -320,7 +320,7 @@ namespace osu.Game.Stores
|
|||||||
/// <param name="cancellationToken">An optional cancellation token.</param>
|
/// <param name="cancellationToken">An optional cancellation token.</param>
|
||||||
public virtual Task<ILive<TModel>?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
public virtual Task<ILive<TModel>?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using (var realm = ContextFactory.CreateContext())
|
return ContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
@ -414,7 +414,7 @@ namespace osu.Game.Stores
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Task.FromResult((ILive<TModel>?)item.ToLive(ContextFactory));
|
return Task.FromResult((ILive<TModel>?)item.ToLive(ContextFactory));
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private string computeHashFast(ArchiveReader reader)
|
private string computeHashFast(ArchiveReader reader)
|
||||||
|
@ -165,7 +165,7 @@ namespace osu.Game.Stores
|
|||||||
|
|
||||||
public bool Delete(TModel item)
|
public bool Delete(TModel item)
|
||||||
{
|
{
|
||||||
using (var realm = ContextFactory.CreateContext())
|
return ContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
if (!item.IsManaged)
|
if (!item.IsManaged)
|
||||||
item = realm.Find<TModel>(item.ID);
|
item = realm.Find<TModel>(item.ID);
|
||||||
@ -175,12 +175,12 @@ namespace osu.Game.Stores
|
|||||||
|
|
||||||
realm.Write(r => item.DeletePending = true);
|
realm.Write(r => item.DeletePending = true);
|
||||||
return true;
|
return true;
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Undelete(TModel item)
|
public void Undelete(TModel item)
|
||||||
{
|
{
|
||||||
using (var realm = ContextFactory.CreateContext())
|
ContextFactory.Run(realm =>
|
||||||
{
|
{
|
||||||
if (!item.IsManaged)
|
if (!item.IsManaged)
|
||||||
item = realm.Find<TModel>(item.ID);
|
item = realm.Find<TModel>(item.ID);
|
||||||
@ -189,7 +189,7 @@ namespace osu.Game.Stores
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
realm.Write(r => item.DeletePending = false);
|
realm.Write(r => item.DeletePending = false);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract bool IsAvailableLocally(TModel model);
|
public abstract bool IsAvailableLocally(TModel model);
|
||||||
|
@ -92,8 +92,7 @@ namespace osu.Game.Stores
|
|||||||
int removedFiles = 0;
|
int removedFiles = 0;
|
||||||
|
|
||||||
// can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal.
|
// can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal.
|
||||||
using (var realm = realmFactory.CreateContext())
|
realmFactory.Write(realm =>
|
||||||
using (var transaction = realm.BeginWrite())
|
|
||||||
{
|
{
|
||||||
// TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707)
|
// TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707)
|
||||||
var files = realm.All<RealmFile>().ToList();
|
var files = realm.All<RealmFile>().ToList();
|
||||||
@ -116,9 +115,7 @@ namespace osu.Game.Stores
|
|||||||
Logger.Error(e, $@"Could not delete databased file {file.Hash}");
|
Logger.Error(e, $@"Could not delete databased file {file.Hash}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
transaction.Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)");
|
Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user