molstar
Version:
A comprehensive macromolecular library.
291 lines (290 loc) • 10.5 kB
JavaScript
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Adam Midlik <midlik@gmail.com>
*/
import { OrderedSet } from 'immutable';
import { StateTransform } from '../transform';
import { StateTree } from './immutable';
import { shallowEqual } from '../../mol-util/object';
import { arrayEqual } from '../../mol-util/array';
export { TransientTree };
class TransientTree {
get childMutations() {
if (this._childMutations)
return this._childMutations;
this._childMutations = new Map();
return this._childMutations;
}
get dependencyMutations() {
if (this._dependencyMutations)
return this._dependencyMutations;
this._dependencyMutations = new Map();
return this._dependencyMutations;
}
changeNodes() {
if (this.changedNodes)
return;
this.changedNodes = true;
this.transforms = this.transforms.asMutable();
}
changeChildren() {
if (this.changedChildren)
return;
this.changedChildren = true;
this.children = this.children.asMutable();
}
changeDependencies() {
if (this.changedDependencies)
return;
this.changedDependencies = true;
this.dependencies = this.dependencies.asMutable();
}
get root() { return this.transforms.get(StateTransform.RootRef); }
asTransient() {
return this.asImmutable().asTransient();
}
addChild(parent, child) {
this.changeChildren();
if (this.childMutations.has(parent)) {
this.childMutations.get(parent).add(child);
}
else {
const set = this.children.get(parent).asMutable();
set.add(child);
this.children.set(parent, set);
this.childMutations.set(parent, set);
}
}
removeChild(parent, child) {
this.changeChildren();
if (this.childMutations.has(parent)) {
this.childMutations.get(parent).remove(child);
}
else {
const set = this.children.get(parent).asMutable();
set.remove(child);
this.children.set(parent, set);
this.childMutations.set(parent, set);
}
}
clearRoot() {
const parent = StateTransform.RootRef;
if (this.children.get(parent).size === 0)
return;
this.changeChildren();
const set = OrderedSet();
this.children.set(parent, set);
this.childMutations.set(parent, set);
}
mutateDependency(parent, child, action) {
let set = this.dependencyMutations.get(parent);
if (!set) {
const src = this.dependencies.get(parent);
if (!src && action === 'remove')
return;
this.changeDependencies();
set = src ? src.asMutable() : OrderedSet().asMutable();
this.dependencyMutations.set(parent, set);
this.dependencies.set(parent, set);
}
if (action === 'add') {
set.add(child);
}
else {
set.remove(child);
}
}
changeParent(ref, newParent) {
ensurePresent(this.transforms, ref);
const old = this.transforms.get(ref);
this.removeChild(old.parent, ref);
this.addChild(newParent, ref);
this.changeNodes();
this.transforms.set(ref, StateTransform.withParent(old, newParent));
}
add(transform) {
const ref = transform.ref;
if (this.transforms.has(transform.ref)) {
const node = this.transforms.get(transform.ref);
if (node.parent !== transform.parent)
alreadyPresent(transform.ref);
}
const children = this.children.get(transform.parent);
if (!children)
parentNotPresent(transform.parent);
if (!children.has(transform.ref)) {
this.addChild(transform.parent, transform.ref);
}
if (!this.children.has(transform.ref)) {
if (!this.changedChildren) {
this.changedChildren = true;
this.children = this.children.asMutable();
}
this.children.set(transform.ref, OrderedSet());
}
this.changeNodes();
this.transforms.set(ref, transform);
if (transform.dependsOn) {
for (const d of transform.dependsOn) {
this.mutateDependency(d, ref, 'add');
}
}
return this;
}
/** Calls Transform.definition.params.areEqual if available, otherwise uses shallowEqual to check if the params changed */
setParams(ref, params) {
ensurePresent(this.transforms, ref);
const transform = this.transforms.get(ref);
// TODO: should this be here?
if (shallowEqual(transform.params, params)) {
return false;
}
if (!this.changedNodes) {
this.changedNodes = true;
this.transforms = this.transforms.asMutable();
}
this.transforms.set(transform.ref, StateTransform.withParams(transform, params));
return true;
}
/** Calls Transform.definition.params.areEqual if available, otherwise uses shallowEqual to check if the params changed */
setTags(ref, tags) {
ensurePresent(this.transforms, ref);
const transform = this.transforms.get(ref);
const withTags = StateTransform.withTags(transform, tags);
// TODO: should this be here?
if (arrayEqual(transform.tags, withTags.tags)) {
return false;
}
if (!this.changedNodes) {
this.changedNodes = true;
this.transforms = this.transforms.asMutable();
}
this.transforms.set(transform.ref, withTags);
return true;
}
setDependsOn(ref, dependsOn) {
ensurePresent(this.transforms, ref);
const transform = this.transforms.get(ref);
const withDependsOn = StateTransform.withDependsOn(transform, dependsOn);
if (arrayEqual(transform.dependsOn, withDependsOn.dependsOn)) {
return false;
}
if (!this.changedNodes) {
this.changedNodes = true;
this.transforms = this.transforms.asMutable();
}
this.transforms.set(transform.ref, withDependsOn);
return true;
}
assignState(ref, state) {
ensurePresent(this.transforms, ref);
const old = this.transforms.get(ref);
if (this._stateUpdates && this._stateUpdates.has(ref)) {
StateTransform.assignState(old.state, state);
return old;
}
else {
if (!this._stateUpdates)
this._stateUpdates = new Set();
this._stateUpdates.add(old.ref);
this.changeNodes();
const updated = StateTransform.withState(old, state);
this.transforms.set(ref, updated);
return updated;
}
}
remove(ref) {
const node = this.transforms.get(ref);
if (!node)
return [];
const st = StateTree.subtreePostOrder(this, node);
if (ref === StateTransform.RootRef) {
st.pop();
if (st.length === 0)
return st;
this.clearRoot();
}
else {
if (st.length === 0)
return st;
this.removeChild(node.parent, node.ref);
}
this.changeNodes();
this.changeChildren();
for (const n of st) {
this.transforms.delete(n.ref);
this.children.delete(n.ref);
if (this._childMutations)
this._childMutations.delete(n.ref);
}
const depRemoves = [];
for (const n of st) {
if (n.dependsOn) {
for (const d of n.dependsOn) {
if (!this.transforms.has(d))
continue;
this.mutateDependency(d, n.ref, 'remove');
}
}
if (this.dependencies.has(n.ref)) {
const deps = this.dependencies.get(n.ref).toArray();
this.changeDependencies();
this.dependencies.delete(n.ref);
if (this._dependencyMutations)
this._dependencyMutations.delete(n.ref);
for (const dep of deps) {
if (!this.transforms.has(dep))
continue;
for (const del of this.remove(dep))
depRemoves[depRemoves.length] = del;
}
}
}
for (const dep of depRemoves)
st[st.length] = dep;
return st;
}
asImmutable() {
if (!this.changedNodes && !this.changedChildren && !this._childMutations)
return this.tree;
if (this._childMutations)
this._childMutations.forEach(fixChildMutations, this.children);
if (this._dependencyMutations)
this._dependencyMutations.forEach(fixDependencyMutations, this.dependencies);
return StateTree.create(this.changedNodes ? this.transforms.asImmutable() : this.transforms, this.changedChildren ? this.children.asImmutable() : this.children, this.changedDependencies ? this.dependencies.asImmutable() : this.dependencies);
}
constructor(tree) {
this.tree = tree;
this.transforms = this.tree.transforms;
this.children = this.tree.children;
this.dependencies = this.tree.dependencies;
this.changedNodes = false;
this.changedChildren = false;
this.changedDependencies = false;
this._childMutations = void 0;
this._dependencyMutations = void 0;
this._stateUpdates = void 0;
}
}
function fixChildMutations(m, k) {
this.set(k, m.asImmutable());
}
function fixDependencyMutations(m, k) {
if (m.size === 0)
this.delete(k);
else
this.set(k, m.asImmutable());
}
function alreadyPresent(ref) {
throw new Error(`Transform '${ref}' is already present in the tree.`);
}
function parentNotPresent(ref) {
throw new Error(`Parent '${ref}' must be present in the tree.`);
}
function ensurePresent(nodes, ref) {
if (!nodes.has(ref)) {
throw new Error(`Node '${ref}' is not present in the tree.`);
}
}