// 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 osu.Framework.Development; using Realms; #nullable enable namespace osu.Game.Database { /// /// Provides a method of working with realm objects over longer application lifetimes. /// /// The underlying object type. public class RealmLive : ILive where T : RealmObject, IHasGuidPrimaryKey { public Guid ID { get; } public bool IsManaged => data.IsManaged; /// /// The original live data used to create this instance. /// private readonly T data; private readonly RealmContextFactory realmFactory; /// /// Construct a new instance of live realm data. /// /// The realm data. /// The realm factory the data was sourced from. May be null for an unmanaged object. public RealmLive(T data, RealmContextFactory realmFactory) { this.data = data; this.realmFactory = realmFactory; ID = data.ID; } /// /// Perform a read operation on this live object. /// /// The action to perform. public void PerformRead(Action perform) { if (!IsManaged) { perform(data); return; } realmFactory.Run(realm => { perform(retrieveFromID(realm, ID)); }); } /// /// Perform a read operation on this live object. /// /// The action to perform. public TReturn PerformRead(Func perform) { if (!IsManaged) return perform(data); return realmFactory.Run(realm => { var returnData = perform(retrieveFromID(realm, ID)); if (returnData is RealmObjectBase realmObject && realmObject.IsManaged) throw new InvalidOperationException(@$"Managed realm objects should not exit the scope of {nameof(PerformRead)}."); return returnData; }); } /// /// Perform a write operation on this live object. /// /// The action to perform. public void PerformWrite(Action perform) { if (!IsManaged) throw new InvalidOperationException(@"Can't perform writes on a non-managed underlying value"); PerformRead(t => { var transaction = t.Realm.BeginWrite(); perform(t); transaction.Commit(); }); } public T Value { get { if (!IsManaged) return data; if (!ThreadSafety.IsUpdateThread) throw new InvalidOperationException($"Can't use {nameof(Value)} on managed objects from non-update threads"); return realmFactory.Context.Find(ID); } } private T retrieveFromID(Realm realm, Guid id) { var found = realm.Find(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(ID); } return found; } public bool Equals(ILive? other) => ID == other?.ID; public override string ToString() => PerformRead(i => i.ToString()); } }