2021-09-30 22:46:16 +08:00
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
|
|
|
|
using System;
|
2022-01-17 17:32:57 +08:00
|
|
|
using System.Diagnostics;
|
2021-11-30 10:47:29 +08:00
|
|
|
using osu.Framework.Development;
|
2022-01-26 11:42:24 +08:00
|
|
|
using osu.Framework.Statistics;
|
2021-09-30 22:46:16 +08:00
|
|
|
using Realms;
|
|
|
|
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
namespace osu.Game.Database
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// Provides a method of working with realm objects over longer application lifetimes.
|
|
|
|
/// </summary>
|
|
|
|
/// <typeparam name="T">The underlying object type.</typeparam>
|
2022-01-26 12:29:12 +08:00
|
|
|
public class RealmLive<T> : ILive<T> where T : RealmObject, IHasGuidPrimaryKey
|
2021-09-30 22:46:16 +08:00
|
|
|
{
|
|
|
|
public Guid ID { get; }
|
|
|
|
|
2021-11-29 16:21:51 +08:00
|
|
|
public bool IsManaged => data.IsManaged;
|
2021-11-26 13:39:35 +08:00
|
|
|
|
2021-09-30 22:46:16 +08:00
|
|
|
/// <summary>
|
|
|
|
/// The original live data used to create this instance.
|
|
|
|
/// </summary>
|
2022-01-17 17:32:57 +08:00
|
|
|
private T data;
|
|
|
|
|
|
|
|
private bool dataIsFromUpdateThread;
|
2021-09-30 22:46:16 +08:00
|
|
|
|
2022-01-24 18:59:58 +08:00
|
|
|
private readonly RealmAccess realm;
|
2021-12-14 13:21:23 +08:00
|
|
|
|
2021-09-30 22:46:16 +08:00
|
|
|
/// <summary>
|
|
|
|
/// Construct a new instance of live realm data.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="data">The realm data.</param>
|
2022-01-24 18:59:58 +08:00
|
|
|
/// <param name="realm">The realm factory the data was sourced from. May be null for an unmanaged object.</param>
|
|
|
|
public RealmLive(T data, RealmAccess realm)
|
2021-09-30 22:46:16 +08:00
|
|
|
{
|
|
|
|
this.data = data;
|
2022-01-24 18:59:58 +08:00
|
|
|
this.realm = realm;
|
2021-12-14 13:21:23 +08:00
|
|
|
|
2021-09-30 22:46:16 +08:00
|
|
|
ID = data.ID;
|
2022-01-17 17:32:57 +08:00
|
|
|
dataIsFromUpdateThread = ThreadSafety.IsUpdateThread;
|
2021-09-30 22:46:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Perform a read operation on this live object.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="perform">The action to perform.</param>
|
|
|
|
public void PerformRead(Action<T> perform)
|
|
|
|
{
|
2021-11-30 10:47:29 +08:00
|
|
|
if (!IsManaged)
|
2021-09-30 22:46:16 +08:00
|
|
|
{
|
|
|
|
perform(data);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-17 17:32:57 +08:00
|
|
|
realm.Run(r =>
|
|
|
|
{
|
|
|
|
if (ThreadSafety.IsUpdateThread)
|
|
|
|
{
|
|
|
|
ensureDataIsFromUpdateThread();
|
|
|
|
perform(data);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
perform(retrieveFromID(r, ID));
|
2022-01-26 12:29:12 +08:00
|
|
|
RealmLiveStatistics.USAGE_ASYNC.Value++;
|
2022-01-17 17:32:57 +08:00
|
|
|
});
|
2021-09-30 22:46:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Perform a read operation on this live object.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="perform">The action to perform.</param>
|
|
|
|
public TReturn PerformRead<TReturn>(Func<T, TReturn> perform)
|
|
|
|
{
|
2021-11-30 10:47:29 +08:00
|
|
|
if (!IsManaged)
|
2021-09-30 22:46:16 +08:00
|
|
|
return perform(data);
|
|
|
|
|
2022-01-17 17:32:57 +08:00
|
|
|
if (ThreadSafety.IsUpdateThread)
|
|
|
|
{
|
|
|
|
ensureDataIsFromUpdateThread();
|
|
|
|
return perform(data);
|
|
|
|
}
|
|
|
|
|
2022-01-25 12:04:05 +08:00
|
|
|
return realm.Run(r =>
|
2022-01-13 12:14:44 +08:00
|
|
|
{
|
2022-01-25 12:04:05 +08:00
|
|
|
var returnData = perform(retrieveFromID(r, ID));
|
2022-01-26 12:29:12 +08:00
|
|
|
RealmLiveStatistics.USAGE_ASYNC.Value++;
|
2022-01-13 12:14:44 +08:00
|
|
|
|
|
|
|
if (returnData is RealmObjectBase realmObject && realmObject.IsManaged)
|
|
|
|
throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}.");
|
|
|
|
|
|
|
|
return returnData;
|
2022-01-21 16:08:20 +08:00
|
|
|
});
|
2021-09-30 22:46:16 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Perform a write operation on this live object.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="perform">The action to perform.</param>
|
2021-11-26 13:39:35 +08:00
|
|
|
public void PerformWrite(Action<T> perform)
|
|
|
|
{
|
|
|
|
if (!IsManaged)
|
2021-12-14 13:21:23 +08:00
|
|
|
throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value");
|
2021-11-26 13:39:35 +08:00
|
|
|
|
2021-09-30 22:46:16 +08:00
|
|
|
PerformRead(t =>
|
|
|
|
{
|
|
|
|
var transaction = t.Realm.BeginWrite();
|
|
|
|
perform(t);
|
|
|
|
transaction.Commit();
|
2022-01-26 12:29:12 +08:00
|
|
|
RealmLiveStatistics.WRITES.Value++;
|
2021-09-30 22:46:16 +08:00
|
|
|
});
|
2021-11-26 13:39:35 +08:00
|
|
|
}
|
2021-09-30 22:46:16 +08:00
|
|
|
|
|
|
|
public T Value
|
|
|
|
{
|
|
|
|
get
|
|
|
|
{
|
2021-11-30 10:47:29 +08:00
|
|
|
if (!IsManaged)
|
2021-09-30 22:46:16 +08:00
|
|
|
return data;
|
|
|
|
|
2021-11-30 10:47:29 +08:00
|
|
|
if (!ThreadSafety.IsUpdateThread)
|
|
|
|
throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads");
|
2021-09-30 22:46:16 +08:00
|
|
|
|
2022-01-17 17:32:57 +08:00
|
|
|
ensureDataIsFromUpdateThread();
|
|
|
|
return data;
|
2021-09-30 22:46:16 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-17 17:32:57 +08:00
|
|
|
private void ensureDataIsFromUpdateThread()
|
|
|
|
{
|
|
|
|
Debug.Assert(ThreadSafety.IsUpdateThread);
|
|
|
|
|
2022-01-21 01:10:14 +08:00
|
|
|
if (dataIsFromUpdateThread && !data.Realm.IsClosed)
|
2022-01-26 11:42:24 +08:00
|
|
|
{
|
2022-01-26 12:29:12 +08:00
|
|
|
RealmLiveStatistics.USAGE_UPDATE_IMMEDIATE.Value++;
|
2022-01-17 17:32:57 +08:00
|
|
|
return;
|
2022-01-26 11:42:24 +08:00
|
|
|
}
|
2022-01-17 17:32:57 +08:00
|
|
|
|
|
|
|
dataIsFromUpdateThread = true;
|
|
|
|
data = retrieveFromID(realm.Realm, ID);
|
2022-01-26 12:29:12 +08:00
|
|
|
RealmLiveStatistics.USAGE_UPDATE_REFETCH.Value++;
|
2022-01-17 17:32:57 +08:00
|
|
|
}
|
|
|
|
|
2022-01-21 16:33:03 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-11-26 13:38:39 +08:00
|
|
|
public bool Equals(ILive<T>? other) => ID == other?.ID;
|
2021-11-29 17:00:03 +08:00
|
|
|
|
|
|
|
public override string ToString() => PerformRead(i => i.ToString());
|
2021-09-30 22:46:16 +08:00
|
|
|
}
|
2022-01-26 11:42:24 +08:00
|
|
|
|
2022-01-26 12:29:12 +08:00
|
|
|
internal static class RealmLiveStatistics
|
2022-01-26 11:42:24 +08:00
|
|
|
{
|
2022-01-26 12:29:12 +08:00
|
|
|
public static readonly GlobalStatistic<int> WRITES = GlobalStatistics.Get<int>(@"Realm", @"Live writes");
|
|
|
|
public static readonly GlobalStatistic<int> USAGE_UPDATE_IMMEDIATE = GlobalStatistics.Get<int>(@"Realm", @"Live update read (fast)");
|
|
|
|
public static readonly GlobalStatistic<int> USAGE_UPDATE_REFETCH = GlobalStatistics.Get<int>(@"Realm", @"Live update read (slow)");
|
|
|
|
public static readonly GlobalStatistic<int> USAGE_ASYNC = GlobalStatistics.Get<int>(@"Realm", @"Live async read");
|
2022-01-26 11:42:24 +08:00
|
|
|
}
|
2021-09-30 22:46:16 +08:00
|
|
|
}
|