Skip to content

Realm usage rules

Dean Herbert edited this page Jun 28, 2021 · 5 revisions

This is a working document and some portions represent future changes that are still in review state

Realm is intended to keep threading and consistency management simple for the developer. This constrains how we can make use of realm, likely for the best. As such, there are a few best practices (or workarounds) required to get the flexibility we require from database bound entities:

Consuming data

From the update thread

If you need temporary access to data from the update thread, access and directly interact with IRealmFactory.Context however you wish. This is a context always maintained on the update thread, getting refreshed each update frame:

RealmKeyBinding obj = realm.Context.Find<RealmKeyBinding>(lookup).First();
this.Tooltip = obj.Text;

Note that this is only valid in cases realm objects are not stored or passed by reference.

If you need to store or pass the retreived objects, bind to property/value changes or subscribe to a data source, use Live<T> (see below).

From a background thread (ie. inside BackgroundDependencyLoader methods)

  • If you need temporary access to data (which is resolved to members which are not managed, ie. strings being pulled out of a RealmObject) use IRealmFactory.GetForRead(). Never keep this context open beyond the local usage.
  • If you need to keep the RealmObjects around for use on the update thread in the future, a re-query will be required.

Modifying data

  • If you are modifying data which needs to also be queried from the database, or if you are able to query from detached data using the primary key, you should use IRealmFactory.GetForWrite():
using (var usage = realm.GetForWrite())
{
    var instance = usage.Realm.Find<RealmKeyBinding>(lookup).First();

    instance.Thing = "new thing";

    usage.Commit();
}

This applies to modifications from any thread.

Storing and passing data

Realm generally operates bound to a single thread. Any asynchronous usage requires a thread-local realm context. To keep things in line with the simplicity paradigm that realm stands for, minimal thread management is encapsulated within the Live<T> class.

Currently, this class has the following benefits:

  • Allows subscribing to collection changes (ie. via IRealmCollection.SubscribeForNotifications) or value changes (ie. via .PropertyChanged).
  • Handles thread/execution mode changes (if the game changes from single to multi-threaded, data and subscriptions will still be maintained).

The implementation has been simplified for now, with the following limitations:

  • Can only be used on the update thread.
  • Performs the full original query each time the realm context is re-fetched (ie. on a thread mode change), rather than using a primary key lookup or ThreadSafeReference.

Usage follows:

Storing a reference

private Live<RealmKeyBinding> keyBinding;

public override void LoadComplete()
{
    keyBinding = realm.CreateLive(context => context.Find<RealmKeyBinding>(lookup).First());
}

public override void Update()
{
    this.Tooltip = keyBinding.Value.Thing;
}

Passing a reference

private Live<RealmKeyBinding> keyBinding;

public override void LoadComplete()
{
    keyBinding = realm.CreateLive(context => context.Find<RealmKeyBinding>(lookup).First());

    Add(new SubComponent(keyBinding));
}

private class SubComponent : CompositeDrawable
{
    public SubComponent(Live<RealmKeyBinding> keyBinding)
    {
        // can be arbitrarily stored as long as reads are on the update thread.
        this.Tooltip = keyBinding.Value.Thing;
    }
}

Subscribing to a collection

private Live<IRealmCollection<RealmKeyBinding>> keyBindings; // a reference must be kept to keep the subscription alive.

private IDisposable realmSubscription;

public override void LoadComplete()
{
    keyBindings = realm.CreateLive(context =>
    {
        var bindings = context.All<RealmKeyBinding>().AsRealmCollection();

        // WARNING WARNING WARNING: OVERSIGHT this could still trigger after drawable is disposed due to WeakReference backing.
        realmSubscription = bindings.SubscribeForNotifications((sender, changes, error) =>
        {
            // callbacks will always be run on the update thread, but scheduling is recommended to maintain execution order.
            Scheduler(() => {
                // handle changes
            });
        });

        return bindings;
    });
}

protected override void Dispose(bool isDisposing)
{
    base.Dispose(isDisposing);
    realmSubscription?.Dispose();
}

Subscribing to a property

private Live<RealmKeyBinding> keyBinding;

public override void LoadComplete()
{
    keyBinding = realm.CreateLive(context =>
    {
        var binding = context.All<RealmKeyBinding>().FirstOrDefault(rkb => rkb.RulesetID == null && rkb.ActionInt == (int)Hotkey.Value);

        if (binding != null)
        {
            // WARNING WARNING WARNING: OVERSIGHT this is never unbound
            binding.PropertyChanged += (sender, args) =>
            {
                // callbacks will always be run on the update thread, but scheduling is recommended to maintain execution order.
                if (args.PropertyName == nameof(binding.KeyCombinationString))
                    Schedule(() => this.Tooltip = binding.Thing);
            };
        }

        return binding;
    });
}