integration/cauldron/CauldronDatastore.js

/**
 *  CauldronDatastore
 *     A datastore that allows mapping parts of concepts so that they show up in 
 *     the Cauldron TreeBrowser and are inspectable with the inspector
 *  
 *  This code is licensed under the MIT License (MIT).
 *  
 *  Copyright 2020, 2021, 2022 Rolf Bagge, Janus B. Kristensen, CAVI,
 *  Center for Advanced Visualization and Interaction, Aarhus University
 *  
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the “Software”), to deal
 *  in the Software without restriction, including without limitation the rights 
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
 *  copies of the Software, and to permit persons to whom the Software is 
 *  furnished to do so, subject to the following conditions:
 *  
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software.
 *  
 *  THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
 *  THE SOFTWARE.
 *  
 */

/**
 *
 */
class CauldronDatastore extends DirectDatastore {
    constructor(name, options = {}) {        
        super(name, options);
        let self = this;
        
        this.destroyCallbacks = [];
        this.conceptAddedCallbacks = [];
        this.instanceAddedCallbacks = new Map();
        this.instanceRemovedCallbacks = new Map();
        
        this.appearCallback = VarvEngine.registerEventCallback("appeared", async (context)=> {
            let mark = VarvPerformance.start();
            if (self.isConceptMapped(context.concept)){
                let callbacks = self.instanceAddedCallbacks.get(context.concept.name);
                if (callbacks){
                    for (let callback of callbacks){
                        callback(context.target);
                    }
                }
            }
            VarvPerformance.stop("CauldronDatastore.registerEventCallback.appeared", mark);
        });
        this.disappearCallback = VarvEngine.registerEventCallback("disappeared", async (context)=> {
            if (self.isConceptMapped(context.concept)){
                let callbacks = self.instanceRemovedCallbacks.get(context.concept.name);
                if (callbacks){
                    for (let callback of callbacks){
                        callback(context.target);
                    }
                }
            }
        });                        
    }

    isShared() {
        return false;
    }

    destroy() {
        this.appearCallback.delete();
        this.disappearCallback.delete();
        this.engineReloadedCallback.delete();
        for (let callback of this.destroyCallbacks){
            callback(this);
        }
    }
    
    registerDestroyCallback(callback){
        this.destroyCallbacks.push(callback);
        return callback;
    }

    async init() {        
        let self = this;

        this.engineReloadedCallback = VarvEngine.registerEventCallback("engineReloaded", ()=>{
            console.log("Engine reloaded, register on trees!");
            // Inform trees about us
            for (let tree of window.ConceptTreeGenerator.instances){
                tree.onAddDatastore(this);
            }

            // Listen for new ones
            EventSystem.registerEventCallback("Varv.ConceptTreeGeneratorSpawned", (evt)=>{
                evt.detail.onAddDatastore(self);
            });
        });
    }
    
    registerConceptAddedCallback(callback){
        this.conceptAddedCallbacks.push(callback);

        // Pre-feed with currently mapped
        for (const conceptName of this.mappedConcepts.keys()){
            callback(VarvEngine.getConceptFromType(conceptName));
        }
        
        return callback;        
    }
    
    async registerConceptInstanceAddedCallback(concept, callback){
        if (!this.instanceAddedCallbacks.has(concept.name)){
            this.instanceAddedCallbacks.set(concept.name, []);
        }
        
        let callbacks = this.instanceAddedCallbacks.get(concept.name);
        callbacks.push(callback);

        // Pre-feed with currently mapped
        for (const conceptUUID of await VarvEngine.getAllUUIDsFromType(concept.name)){
            callback(conceptUUID);
        }

        return callback;
    }    
    
    removeConceptInstanceAddedCallback(concept, callback){
        let callbacks = this.instanceAddedCallbacks.get(concept.name);
        if (!callbacks) {
            console.log("Cauldron: Tried to remove a concept callback but couldn't", concept.name, callback);
            return;
        };
        this.instanceAddedCallbacks.set(concept.name, callbacks.filter(e => e !== callback));
    }
    
    registerConceptInstanceRemovedCallback(concept, callback){
        if (!this.instanceRemovedCallbacks.has(concept.name)){
            this.instanceRemovedCallbacks.set(concept.name, []);
        }
        
        let callbacks = this.instanceRemovedCallbacks.get(concept.name);
        callbacks.push(callback);     
        
        return callback;               
    }  
    
    removeConceptInstanceRemovedCallback(concept, callback){
        let callbacks = this.instanceRemovedCallbacks.get(concept.name);
        if (!callbacks) {
            console.log("Cauldron: Tried to remove a concept removed callback but couldn't", concept.name, callback);
            return;
        };
        this.instanceRemovedCallbacks.set(concept.name, callbacks.filter(e => e !== callback));
    }    

    createBackingStore(concept, property) {
        const self = this;
        
        if (this.isPropertyMapped(concept,property)){
            console.log("Already mapped property");
            return;
        }
        
        if (!this.isConceptMapped(concept)){
            // Concept add update
            if(CauldronDatastore.DEBUG) {
                console.log("New concept", concept);
            }
            for (let callback of self.conceptAddedCallbacks){
                callback(concept);
            }                    
        }

        // Check if concept already is mapped, if not, register it
        this.internalAddPropertyMapping(concept, property, {});
    }    
    
    removeBackingStore(concept, property) {
        if (!this.isPropertyMapped(concept, property))
            throw 'Cannot unmap property from memory because the property was not mapped: ' + concept + "." + property;
       
        // TODO: Remove concept update
        
        this.internalRemovePropertyMapping(concept, property);
    }

    loadBackingStore() {
        // No storage, do nothing
    }
}

window.CauldronDatastore = CauldronDatastore;

// Register default cauldron datastore
Datastore.registerDatastoreType("cauldron", CauldronDatastore);