2021-01-11 15:22:52 +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 ;
using System.Threading ;
using Realms ;
2021-01-11 15:26:45 +08:00
#nullable enable
2021-01-11 15:22:52 +08:00
namespace osu.Game.Database
{
2021-01-11 15:50:14 +08:00
/// <summary>
/// Provides a method of passing realm live objects across threads in a safe fashion.
/// </summary>
/// <remarks>
/// To consume this as a live instance, the live object should be stored and accessed via <see cref="Get"/> each time.
2021-01-11 16:27:21 +08:00
/// To consume this as a detached instance, assign to a variable of type <typeparam ref="T"/>. The implicit conversion will handle detaching an instance.
2021-01-11 15:50:14 +08:00
/// </remarks>
/// <typeparam name="T">The underlying object type. Should be a <see cref="RealmObject"/> with a primary key provided via <see cref="IHasGuidPrimaryKey"/>.</typeparam>
2021-01-11 15:57:40 +08:00
public class Live < T > : IEquatable < Live < T > > , IHasGuidPrimaryKey
2021-01-11 15:22:52 +08:00
where T : RealmObject , IHasGuidPrimaryKey
{
2021-01-11 15:50:14 +08:00
/// <summary>
/// The primary key of the object.
/// </summary>
2021-01-11 15:57:40 +08:00
public Guid Guid { get ; }
public string ID
{
get = > Guid . ToString ( ) ;
set = > throw new NotImplementedException ( ) ;
}
2021-01-11 15:22:52 +08:00
private readonly ThreadLocal < T > threadValues ;
2021-01-11 16:27:21 +08:00
private readonly T original ;
2021-01-11 15:50:14 +08:00
private readonly IRealmFactory contextFactory ;
2021-01-11 15:22:52 +08:00
2021-01-11 16:27:21 +08:00
public Live ( T item , IRealmFactory contextFactory )
2021-01-11 15:22:52 +08:00
{
2021-01-11 15:50:14 +08:00
this . contextFactory = contextFactory ;
2021-01-11 15:22:52 +08:00
2021-01-11 16:27:21 +08:00
original = item ;
Guid = item . Guid ;
threadValues = new ThreadLocal < T > ( getThreadLocalValue ) ;
2021-01-11 15:22:52 +08:00
2021-01-11 16:27:21 +08:00
// the instance passed in may not be in a managed state.
// for now let's immediately retrieve a managed object on the current thread.
// in the future we may want to delay this until the first access (only populating the Guid at construction time).
if ( ! item . IsManaged )
original = Get ( ) ;
}
private T getThreadLocalValue ( )
{
var context = contextFactory . Get ( ) ;
2021-01-11 15:22:52 +08:00
2021-01-11 16:27:21 +08:00
// only use the original if no context is available or the source realm is the same.
if ( context = = null | | original . Realm ? . IsSameInstance ( context ) = = true ) return original ;
2021-01-11 15:22:52 +08:00
2021-01-11 16:27:21 +08:00
return context . Find < T > ( ID ) ;
2021-01-11 15:22:52 +08:00
}
2021-01-11 15:50:14 +08:00
/// <summary>
/// Retrieve a live reference to the data.
/// </summary>
2021-01-11 15:22:52 +08:00
public T Get ( ) = > threadValues . Value ;
2021-01-11 18:28:07 +08:00
/// <summary>
/// Retrieve a detached copy of the data.
/// </summary>
public T Detach ( ) = > Get ( ) . Detach ( ) ;
2021-01-11 15:50:14 +08:00
/// <summary>
/// Wrap a property of this instance as its own live access object.
/// </summary>
/// <param name="lookup">The child to return.</param>
/// <typeparam name="TChild">The underlying child object type. Should be a <see cref="RealmObject"/> with a primary key provided via <see cref="IHasGuidPrimaryKey"/>.</typeparam>
/// <returns>A wrapped instance of the child.</returns>
2021-01-11 15:30:55 +08:00
public Live < TChild > WrapChild < TChild > ( Func < T , TChild > lookup )
2021-01-11 15:50:14 +08:00
where TChild : RealmObject , IHasGuidPrimaryKey = > new Live < TChild > ( lookup ( Get ( ) ) , contextFactory ) ;
2021-01-11 15:22:52 +08:00
2021-01-11 15:50:14 +08:00
/// <summary>
/// Perform a write operation on this live object.
/// </summary>
/// <param name="perform">The action to perform.</param>
2021-01-11 15:22:52 +08:00
public void PerformUpdate ( Action < T > perform )
{
2021-01-11 15:50:14 +08:00
using ( contextFactory . GetForWrite ( ) )
2021-01-11 15:26:45 +08:00
perform ( Get ( ) ) ;
2021-01-11 15:22:52 +08:00
}
2021-01-11 15:30:55 +08:00
public static implicit operator T ? ( Live < T > ? wrapper )
2021-01-11 18:28:07 +08:00
= > wrapper ? . Detach ( ) ? ? null ;
2021-01-11 15:22:52 +08:00
2021-01-11 15:30:55 +08:00
public static implicit operator Live < T > ( T obj ) = > obj . WrapAsUnmanaged ( ) ;
2021-01-11 15:22:52 +08:00
2021-01-11 15:57:40 +08:00
public bool Equals ( Live < T > ? other ) = > other ! = null & & other . Guid = = Guid ;
2021-01-11 15:22:52 +08:00
public override string ToString ( ) = > Get ( ) . ToString ( ) ;
}
}