import { tryReference, types } from "mobx-state-tree";
|
import Types from "../../core/Types";
|
import { SharedStoreModel } from "./model";
|
|
/**
|
* StoreIds and Stores act as a cache.
|
*
|
* The reason behind those is that we're creating a new store on the `preProcessSnapshot` when there's no
|
* access to the State Tree. When the store is created, it's put into the cache and retrieved back in the
|
* `afterCreate` hook of the model.
|
*
|
* StoreIds is just a map of existing store IDs to reference to during the `preProcessSnapshot`.
|
*/
|
export const Stores = new Map();
|
const StoreIds = new Set();
|
|
/**
|
* Defines the ID to group SharedStores by.
|
*/
|
const SharedStoreID = types.optional(types.maybeNull(types.string), null);
|
|
/**
|
* Defines the Store model referenced from the Annotation Store
|
*/
|
const Store = types.optional(types.maybeNull(types.late(() => types.reference(SharedStoreModel))), null);
|
|
/**
|
* SharedStoreMixin, when injected into the model, provides an AnnotationStore level shared storages to
|
* reduce the memory footprint and computation time.
|
*
|
* It was specifically designed to be used with Repeater tag where the memory issues are the most sound.
|
*
|
* This mixin provides a `sharedStore` property to the model which is a reference to the shared store.
|
*
|
* The concept behind it is that whenever a model is parsing a snapshot, children are subtracted from the
|
* initial snapshot, and put into the newly created SharedStore.
|
*
|
* The store is then put into the cache and attached to the model in the `afterCreate` hook. Any subsequent
|
* models lookup the store in the cache first and use its id instead of creating a new one.
|
*
|
* When the store is fullfilled with children, it's locked and cannot be modified anymore. The allows the model
|
* not to process children anymore and just use the store.
|
*
|
* Shared Stores live on the AnnotationStore level meaning that even if the user switches between annotations or
|
* create new ones, they will all use the same shared store decreasing the memory footprint and computation time.
|
*/
|
export const SharedStoreMixin = types
|
.model("SharedStoreMixin", {
|
sharedstore: SharedStoreID,
|
store: Store,
|
})
|
.views((self) => ({
|
get children() {
|
return self.sharedChildren;
|
},
|
|
get locked() {
|
return self.store?.locked ?? false;
|
},
|
|
set children(val) {
|
self.store?.lock();
|
self.store.setChildren(val);
|
},
|
|
get sharedChildren() {
|
return self.store.children ?? [];
|
},
|
|
get storeId() {
|
return self.sharedstore ?? self.name;
|
},
|
}))
|
.actions((self) => ({
|
afterCreate() {
|
const currentStore = tryReference(() => self.store);
|
|
if (!currentStore) {
|
const store = Stores.get(self.storeId);
|
const annotationStore = Types.getParentOfTypeString(self, "AnnotationStore");
|
|
// It means that an element is not connected to the store tree,
|
// most probably as it is a temporal clone of the model
|
if (!annotationStore) return;
|
annotationStore.addSharedStore(store);
|
StoreIds.add(self.storeId);
|
self.store = self.storeId;
|
}
|
},
|
}))
|
.preProcessSnapshot((sn) => {
|
const storeId = sn.sharedstore ?? sn.name;
|
|
if (StoreIds.has(storeId)) {
|
sn.store = storeId;
|
} else {
|
Stores.set(
|
storeId,
|
SharedStoreModel.create({
|
id: storeId,
|
children: sn._children ?? sn.children ?? [],
|
}),
|
);
|
}
|
|
return sn;
|
});
|
|
export const destroy = () => {
|
Stores.clear();
|
StoreIds.clear();
|
};
|