Mutagen-Modding / Mutagen

A .NET library for analyzing, creating, and manipulating Bethesda mods
GNU General Public License v3.0
117 stars 32 forks source link

Referenced By Functionality #496

Open Noggog opened 8 months ago

Noggog commented 8 months ago

Add a convenience class similar to LinkCache that provides ReferencedBy functionality. It's not too bad to homebrew, but might as well offer via mutagen directly

public IReadOnlyDictionary<FormKey, HashSet<FormKey>> GetReferencedByLookups(IModGetter mod)
{
    var lookup = new Dictionary<FormKey, HashSet<FormKey>>();
    foreach (var rec in mod.EnumerateMajorRecords())
    {
        foreach (var link in rec.EnumerateFormLinks())
        {
            lookup.GetOrAdd(link.FormKey).Add(rec.FormKey);
        }
    }
    return lookup;
}

Most of the work would just be deciding how to expose it. Add it to some existing object like LinkCache? Add a new ReferencedByLookup class? Just an extension function?

Probably a new dedicated interface/class like LinkCache would be most appropriate

Elscrux commented 5 months ago

I think something similar to LinkCache would be great, a ReferenceCache or RecordReferenceCache if you want to be more precise where there are also other types of references (I've run into this with an AssetReferenceCache).

Here is my implementation for a record reference cache which I'd love to integrate into Mutagen https://github.com/Elscrux/Creation-Companion/tree/master/CreationEditor/Services/Mutagen/References/Record In essence a record reference cache wraps a Dictionary<FormKey, HashSet<IFormLinkIdentifier>> for every mod. The key being the referenced record and the hash set being the set of record that reference to the record in the key.

There is also a part which is responsible for loading all references in a mod, either from a cache if there is a cache for this plugin that matches the plugin file's hash or by building the cache from the mod data. If there is any point where we could improve the performance, that would be here. It's not too bad as it is right now, but it could definitely be improved if possible.

This is quite simple and can give us a good system to have reference caches for immutable mods and we can also have a function to get only the relevant references in a load order that the winning override has (if a mod later in the load order adds or removes a references for example).

But this doesn't cover mutable mods right, but there are further parts in my implementation which enable this. However, there are some project-specific things in my implementation that require a way to monitor changes to record. This is how I am able to create a dynamically adapting cache with mutable and immutable caches for mods depending if they are mutable in the current load order or not.

Basically this change monitor is something that implements this function.

void RegisterUpdate(IMajorRecordGetter record, Action updateAction);

First we check what a record references before the update, then we call the update and check if there are any changes to the records the updated record references. An example: We have an IronSword record which currently doesn't reference anything. In the update action we add the keyword KeywordSword to the it so we will add IronSword as reference to KeywordSword. I also introduced a wrapper IReferencedRecord which holds an IObservableCollection<IFormLinkIdentifier> which can be very useful.

Potentially this could also be implemented into Mutagen as an optional behavior people have to opt into. Then they'd have to call RegisterUpdate from above for every change to a record, but they would get an live cache for record references.