// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Threading; using Realms; #nullable enable namespace osu.Game.Database { /// /// Provides a method of passing realm live objects across threads in a safe fashion. /// /// /// To consume this as a live instance, the live object should be stored and accessed via each time. /// To consume this as a detached instance, assign to a variable of type . The implicit conversion will handle detaching an instance. /// /// The underlying object type. Should be a with a primary key provided via . public class Live : IEquatable>, IHasGuidPrimaryKey where T : RealmObject, IHasGuidPrimaryKey { /// /// The primary key of the object. /// public Guid Guid { get; } public string ID { get => Guid.ToString(); set => throw new NotImplementedException(); } private readonly ThreadLocal threadValues; private readonly T original; private readonly IRealmFactory contextFactory; public Live(T item, IRealmFactory contextFactory) { this.contextFactory = contextFactory; original = item; Guid = item.Guid; threadValues = new ThreadLocal(getThreadLocalValue); // 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(); // 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; return context.Find(ID); } /// /// Retrieve a live reference to the data. /// public T Get() => threadValues.Value; /// /// Retrieve a detached copy of the data. /// public T Detach() => Get().Detach(); /// /// Wrap a property of this instance as its own live access object. /// /// The child to return. /// The underlying child object type. Should be a with a primary key provided via . /// A wrapped instance of the child. public Live WrapChild(Func lookup) where TChild : RealmObject, IHasGuidPrimaryKey => new Live(lookup(Get()), contextFactory); /// /// Perform a write operation on this live object. /// /// The action to perform. public void PerformUpdate(Action perform) { using (contextFactory.GetForWrite()) perform(Get()); } public static implicit operator T?(Live? wrapper) => wrapper?.Detach() ?? null; public static implicit operator Live(T obj) => obj.WrapAsUnmanaged(); public bool Equals(Live? other) => other != null && other.Guid == Guid; public override string ToString() => Get().ToString(); } }