-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Realm usage rules
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:
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).
- If you need temporary access to data (which is resolved to members which are not managed, ie. strings being pulled out of a
RealmObject
) useIRealmFactory.GetForRead()
. Never keep this context open beyond the local usage. - If you need to keep the
RealmObject
s around for use on the update thread in the future, a re-query will be required.
- 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.
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:
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;
}
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;
}
}
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();
}
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;
});
}