Began rebuilding computation engine to be dependency graph centric
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js';
|
||||
import CreatureProperties,
|
||||
{ DenormalisedOnlyCreaturePropertySchema as denormSchema }
|
||||
from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
||||
import computedOnlySchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
|
||||
import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js';
|
||||
import applyFnToKey from '/imports/api/creature/computation/newEngine/applyFnToKey.js';
|
||||
import { cloneDeep, unset } from 'lodash';
|
||||
import { prettifyParseError, parse } from '/imports/parser/parser.js';
|
||||
import ErrorNode from '/imports/parser/parseTree/ErrorNode.js';
|
||||
import SymbolNode from '/imports/parser/parseTree/SymbolNode.js';
|
||||
import AccessorNode from '/imports/parser/parseTree/AccessorNode.js';
|
||||
import createGraph from 'ngraph.graph';
|
||||
import findAncestorByType from 'imports/api/creature/computation/newEngine/findAncestorByType.js';
|
||||
|
||||
/**
|
||||
* Store index of properties
|
||||
* recompute static tree-based enabled/disabled status
|
||||
* Build a dependency graph
|
||||
* id -> id dependencies for docs that rely on other docs directly
|
||||
* id -> variable deps for docs that rely on a variable's value
|
||||
* TODO:
|
||||
* variable -> id deps for variables that are impacted by docs
|
||||
* Depth first traversal or dependency graph to:
|
||||
* Find loops in the dependency graph
|
||||
* resolve variables in dependency order
|
||||
*/
|
||||
|
||||
export default function computeCreature(creatureId){
|
||||
let properties = CreatureProperties.find({
|
||||
'ancestors.id': creatureId,
|
||||
'removed': {$ne: true},
|
||||
}, {
|
||||
sort: {order: 1}
|
||||
});
|
||||
|
||||
const originalPropsById = {};
|
||||
const propsById = {};
|
||||
const propsByType = {};
|
||||
|
||||
// Process the properties one by one
|
||||
properties.forEach(prop => {
|
||||
// Store the prop by Id and Type
|
||||
originalPropsById[prop._id] = cloneDeep(prop);
|
||||
propsById[prop._id] = prop;
|
||||
if (!propsByType[prop.type]) propsByType[prop.type] = [];
|
||||
propsByType[prop.type].push(prop);
|
||||
|
||||
// Store the prop in the dependency graph
|
||||
dependencyGraph.addNode(prop._id, prop);
|
||||
|
||||
// Remove all computed only fields
|
||||
computedOnlySchemas[prop.type]._schemaKeys.forEach(key =>
|
||||
applyFnToKey(prop, key, unset)
|
||||
);
|
||||
|
||||
// Remove all denormalised fields
|
||||
denormSchema._schemaKeys.forEach(key =>
|
||||
applyFnToKey(prop, key, unset)
|
||||
);
|
||||
|
||||
// Add a place to store all the computation details
|
||||
prop._computationDetails = {
|
||||
calculations: [],
|
||||
toggleAncestors: [],
|
||||
};
|
||||
|
||||
// parse every calculation field
|
||||
computedSchemas[prop.type]._schemaKeys.forEach( key => {
|
||||
if (key.slice(-11) !== 'calculation') return;
|
||||
const calcKey = key.sclice(0, -11);
|
||||
applyFnToKey(prop, calcKey, calcObj => {
|
||||
// Store a reference to all the calculations
|
||||
prop._computationDetails.calculations.push(calcObj);
|
||||
// Parse the calculation
|
||||
parseCalculation(calcObj);
|
||||
return calcObj;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Dependency graph where edge(a, b) means a depends on b
|
||||
const dependencyGraph = createGraph();
|
||||
// Build graph now that all props are stored
|
||||
properties.forEach(prop => {
|
||||
linkDependencies(dependencyGraph, prop, propsById);
|
||||
});
|
||||
|
||||
// Process the properties in tree format
|
||||
let creatureTree = nodeArrayToTree(properties);
|
||||
walkDown(creatureTree, node => {
|
||||
denormaliseInactiveStatus(node);
|
||||
inheritToggleDependencies(node);
|
||||
});
|
||||
}
|
||||
|
||||
function walkDown(tree, callback){
|
||||
let stack = [...tree];
|
||||
while(stack.length){
|
||||
let node = stack.pop();
|
||||
callback(node);
|
||||
stack.push(...node.children);
|
||||
}
|
||||
}
|
||||
|
||||
function denormaliseInactiveStatus(node){
|
||||
const prop = node.node;
|
||||
if (isActive(prop)) return;
|
||||
prop.inactive = true;
|
||||
prop.deactivatedBySelf = true;
|
||||
// Mark children as inactive due to ancestor
|
||||
walkDown(node.children, child => {
|
||||
child.node.inactive = true;
|
||||
child.node.deactivatedByAncestor = true;
|
||||
});
|
||||
}
|
||||
|
||||
function isActive(prop){
|
||||
if (prop.disabled) return false;
|
||||
switch (prop.type){
|
||||
case 'buff': return !!prop.applied;
|
||||
case 'item': return !!prop.equipped;
|
||||
case 'spell': return !!prop.prepared || !!prop.alwaysPrepared;
|
||||
default: return true;
|
||||
}
|
||||
}
|
||||
|
||||
function inheritToggleDependencies(node, dependencyGraph){
|
||||
const prop = node.node;
|
||||
// Only for toggles that aren't inactive and aren't set to enabled or disabled
|
||||
if (
|
||||
prop.inactive ||
|
||||
prop.type !== 'toggle' ||
|
||||
prop.disabled ||
|
||||
prop.enabled
|
||||
) return;
|
||||
walkDown(node.children, child => {
|
||||
child.node._computationDetails.toggleAncestors.push(prop._id);
|
||||
dependencyGraph.addLink(child.node._id, prop._id, prop.condition);
|
||||
});
|
||||
}
|
||||
|
||||
function parseCalculation(calcObj){
|
||||
let calculation = calcObj.calculation || '';
|
||||
try {
|
||||
calcObj._parsedCalculation = parse(calculation);
|
||||
} catch (e) {
|
||||
let error = prettifyParseError(e);
|
||||
calcObj.errors ?
|
||||
calcObj.errors.push(error) :
|
||||
calcObj.errors = [error];
|
||||
calcObj._parsedCalculation = new ErrorNode({error});
|
||||
}
|
||||
}
|
||||
|
||||
function linkDependencies(dependencyGraph, prop, propsById){
|
||||
let variableNames = [];
|
||||
prop._computationDetails.calculations.forEach(calcObj => {
|
||||
calcObj._parsedCalculation.travese(node => {
|
||||
if (node instanceof SymbolNode || node instanceof AccessorNode){
|
||||
if (node.name[0] !== '#'){
|
||||
dependencyGraph.addLink(prop._id, node.name, calcObj);
|
||||
} else {
|
||||
let ancestorProp = findAncestorByType(
|
||||
prop, node.name.slice(1), propsById
|
||||
);
|
||||
if (!ancestorProp) return;
|
||||
dependencyGraph.addLink(prop._id, ancestorProp._id, calcObj);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return variableNames;
|
||||
}
|
||||
Reference in New Issue
Block a user