Computation engine rewrite continues

This commit is contained in:
Stefan Zermatten
2021-09-10 19:51:03 +02:00
parent 28ec7082ee
commit b877a8b45f
15 changed files with 235 additions and 28 deletions

View File

@@ -3,3 +3,5 @@ import '/imports/ui/vueSetup.js';
import '/imports/ui/styles/stylesIndex.js';
import '/imports/client/config.js';
import '/imports/client/serviceWorker.js';
import 'ngraph.graph';

View File

@@ -59,12 +59,12 @@ function handleProp(prop, containerStack, data, dependencyGraph){
// Sum the item-specific data
if (prop.type === 'item'){
dependencyGraph.addLink('itemsAttuned', prop._id);
dependencyGraph.addLink('itemsAttuned', prop._id, 'inventory');
if (prop.attuned) data.itemsAttuned += 1;
if (prop.equipped){
dependencyGraph.addLink('weightEquipment', prop._id);
dependencyGraph.addLink('weightEquipment', prop._id, 'inventory');
data.weightEquipment += weight;
dependencyGraph.addLink('valueEquipment', prop._id);
dependencyGraph.addLink('valueEquipment', prop._id, 'inventory');
data.valueEquipment += value;
}
}
@@ -74,7 +74,7 @@ function handleProp(prop, containerStack, data, dependencyGraph){
if (container){
// The container depends on this prop for its contents data
dependencyGraph.addLink(container._id, prop._id);
dependencyGraph.addLink(container._id, prop._id, 'inventory');
// Add this property's weights and values to the container
if (!container.weightless){
container.contentsWeight += weight;
@@ -84,14 +84,14 @@ function handleProp(prop, containerStack, data, dependencyGraph){
if (carried) container.carriedValue += carriedValue;
} else {
// There is no parent container, add weights/value to the character data
dependencyGraph.addLink('weightTotal', prop._id);
dependencyGraph.addLink('weightTotal', prop._id, 'inventory');
data.weightTotal += weight;
dependencyGraph.addLink('valueTotal', prop._id);
dependencyGraph.addLink('valueTotal', prop._id, 'inventory');
data.valueTotal += value;
if (carried){
dependencyGraph.addLink('weightCarried', prop._id);
dependencyGraph.addLink('weightCarried', prop._id, 'inventory');
data.weightCarried += carriedWeight;
dependencyGraph.addLink('valueCarried', prop._id);
dependencyGraph.addLink('valueCarried', prop._id, 'inventory');
data.valueCarried += carriedValue;
}
}

View File

@@ -7,7 +7,7 @@ export default function computeSlotQuantityFilled(node, dependencyGraph){
slot.totalFilled = 0;
node.children.forEach(child => {
let childProp = child.node;
dependencyGraph.addLink(slot._id, childProp._id)
dependencyGraph.addLink(slot._id, childProp._id, 'slotFill')
if (childProp.type === 'slotFiller'){
slot.totalFilled += child.slotQuantityFilled;
} else {

View File

@@ -11,6 +11,6 @@ export default function computeToggleDependencies(node, dependencyGraph){
) return;
walkDown(node.children, child => {
child.node._computationDetails.toggleAncestors.push(prop._id);
dependencyGraph.addLink(child.node._id, prop._id, prop.condition);
dependencyGraph.addLink(child.node._id, prop._id, 'toggle');
});
}

View File

@@ -4,21 +4,40 @@ import findAncestorByType from 'imports/api/creature/computation/newEngine/utili
export default function linkCalculationDependencies(dependencyGraph, prop, propsById){
prop._computationDetails.calculations.forEach(calcObj => {
// Store resolved ancestors
let memo = {
// ancestors: {} //this gets added if there are resolved ancestors
};
// Traverse the parsed calculation looking for variable names
calcObj._parsedCalculation.travese(node => {
if (node instanceof SymbolNode || node instanceof AccessorNode){
// Link ancestor references as direct property dependencies
if (node.name[0] === '#'){
let ancestorProp = findAncestorByType(
prop, node.name.slice(1), propsById
);
if (!ancestorProp) return;
dependencyGraph.addLink(prop._id, ancestorProp._id, calcObj);
} else {
// Link variable name references as variable dependencies
dependencyGraph.addLink(prop._id, node.name, calcObj);
}
// Skip nodes that aren't symbols or accessors
if (!(node instanceof SymbolNode || node instanceof AccessorNode)) return;
// Link ancestor references as direct property dependencies
if (node.name[0] === '#'){
let ancestorProp = getAncestorProp(
node.name.slice(1), memo, prop, propsById
);
if (!ancestorProp) return;
dependencyGraph.addLink(prop._id, ancestorProp._id, calcObj);
} else {
// Link variable name references as variable dependencies
dependencyGraph.addLink(prop._id, node.name, calcObj);
}
});
// Store the resolved ancestors in this calculation's local scope
if (memo.ancestors) {
calcObj._localScope = { ...calcObj._localScope, ...memo.ancestors};
}
});
}
function getAncestorProp(type, memo, prop, propsById){
if (memo.ancestors && memo.ancestors['#' + type]){
return memo.ancestors['#' + type];
} else {
var ancestorProp = findAncestorByType( prop, type, propsById );
if (!memo.ancestors) memo.ancestors = {};
memo.ancestors['#' + type] = ancestorProp;
return ancestorProp;
}
}

View File

@@ -15,13 +15,13 @@ export default function linkTypeDependencies(dependencyGraph, prop){
function linkVariableName(dependencyGraph, prop){
// The variableName of the prop depends on the prop
if (prop.variableName){
dependencyGraph.addLink(prop.variableName, prop._id);
dependencyGraph.addLink(prop.variableName, prop._id, 'definition');
}
}
function linkDamageMultiplier(dependencyGraph, prop){
prop.damageTypes.forEach(damageType => {
dependencyGraph.addLink(`${damageType}Multiplier`, prop._id);
dependencyGraph.addLink(`${damageType}Multiplier`, prop._id, 'damageMultiplier');
});
}
@@ -29,12 +29,12 @@ function linkStats(dependencyGraph, prop){
// The stats a prop references depend on that prop
prop.stats.forEach(variableName => {
if (!variableName) return;
dependencyGraph.addLink(variableName, prop._id);
dependencyGraph.addLink(variableName, prop._id, 'statChange');
});
}
function linkSkill(dependencyGraph, prop){
linkVariableName(dependencyGraph, prop);
// The prop depends on the variable references as the ability
if (prop.ability) dependencyGraph.addLink(prop._id, prop.ability);
if (prop.ability) dependencyGraph.addLink(prop._id, prop.ability, 'skillAbilityScore');
}

View File

@@ -9,15 +9,20 @@ export default function parseCalculationFields(prop, schemas){
if (key.slice(-12) !== '.calculation') return;
const calcKey = key.sclice(0, -12);
// Determine the level the calculation should compute down to
let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel;
// For all fields matching they keys
// supports `keys.$.with.$.arrays`
applyFnToKey(prop, calcKey, calcObj => {
// Store a reference to all the calculations
prop._computationDetails.calculations.push(calcObj);
// Store the level to compute down to later
calcObj._parseLevel = parseLevel;
// Parse the calculation
parseCalculation(calcObj);
});
});
}

View File

@@ -0,0 +1,13 @@
/**
* Iterate through all the defining properties and choose the highest
* `baseValue.value`
*/
export default function aggregateBaseValue({node, linkedNode, link}){
if (link.data !== 'definition') return;
const propBaseValue = linkedNode.data.baseValue?.value;
if (propBaseValue === undefined) return;
if (node.baseValue === undefined || propBaseValue > node.baseValue){
node.baseValue = propBaseValue;
}
}

View File

@@ -0,0 +1,24 @@
export default function aggregateDefinitions({node, linkedNode, link}){
// Look at all definition links
if (link.data !== 'definition') return;
const prop = linkedNode.data;
// get current defining prop
const definingProp = node.data.definingProp;
// Find the last defining prop
if (!definingProp || prop.order > definingProp.order){
// override the current defining prop
overrideProp(definingProp, node);
// set this prop as the new defining prop
node.data.definingProp = prop;
} else {
overrideProp(prop, node);
}
}
function overrideProp(prop, node){
if (!prop) return;
prop.overriden = true;
if (!node.data.overridenProps) node.data.overridenProps = [];
node.data.overridenProp.push(prop);
}

View File

@@ -0,0 +1,17 @@
import definitions from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/aggregateDefinitions.js';
import baseValue from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/aggregateBaseValue.js';
import damageMultipliers from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/aggregateDamageMultipliers.js';
import effects from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/aggregateEffects.js';
import proficiencies from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/aggregateProficiencies.js';
import skills from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/aggregateProficiencies.js';
import toggles from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/aggregateToggles.js';
export default Object.freeze({
definitions,
baseValue,
damageMultipliers,
effects,
proficiencies,
skills,
toggles,
});

View File

@@ -0,0 +1,33 @@
import aggregate from '/imports/api/creature/computation/newEngine/computeComputation/aggregateProps/index.js';
export default function computeVariable(graph, node){
if (!node.data) node.data = {};
aggregateLinks(graph, node);
}
function aggregateLinks(graph, node){
let definingProp;
let overridenProps = [];
graph.forEachLinkedNode(
node.id,
(linkedNode, link) => {
if (!linkedNode.data) linkedNode.data = {};
// Ignore inactive props
if (linkedNode.data.inactive) return;
// Apply all the aggregations
let arg = {node, linkedNode, link};
aggregate.definitions(arg);
aggregate.baseValue(arg);
aggregate.damageMultipliers(arg);
aggregate.effects(arg);
aggregate.proficiencies(arg);
aggregate.skills(arg);
aggregate.toggles(arg);
},
true // enumerate only outbound links
);
// store the defining and overriden props on the node
if (!node.data) node.data = {};
node.data.definingProp = definingProp;
node.data.overridenProps = overridenProps;
}

View File

@@ -0,0 +1,11 @@
import { CompilationContext } from '/imports/parser/parser.js';
export default function evaluateCalculation(calculation, scope){
const context = new CompilationContext();
const parseNode = calculation._parsedCalculation;
const fn = calculation._parseLevel || 'reduce';
const calculationScope = {...calculation._localScope, ...scope};
const result = parseNode[fn](calculationScope, context);
calculation.value = result;
calculation.errors = context.errors;
}

View File

@@ -0,0 +1,74 @@
import evaluateCalculation from '/imports/api/creature/computation/newEngine/computeComputation/evaluateCalculation.js';
import computeVariable from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable.js';
export default function computeCreatureComputation(computation){
const stack = [];
// dict of computed nodes by id
const scope = {};
const graph = computation.dependencyGraph;
// Add all nodes to the stack
graph.forEachNode(node => stack.push({
node,
visited: false,
visitedChildren: false,
}));
// Depth first traversal of nodes
while (stack.length){
let top = stack[stack.length - 1];
if (top.visited){
// The object has already
stack.pop();
} else if (top.visitedChildren){
// Compute the top object of the stack
compute(graph, top.node, scope);
// If the node holds a variable, store it in the scope
if (!top.node.data?.type){
scope[top.node.id] = top.node.data;
}
// Mark the object as visited and remove from stack
top.visited = true;
stack.pop();
} else {
// Push children to graph
pushDependenciesToStack(top.node.id, graph, stack);
top.visitedChildren = true;
}
}
}
function compute(graph, node, scope){
// Get the property
let prop = node.data;
// evaluate all the calculations
if (prop?._computationDetails?.calculations){
prop._computationDetails.calculations.forEach(calcObj => {
evaluateCalculation(calcObj, scope)
});
}
// Compute the property by type
let typeCompute = propTypeComputations[prop?.type || '_variable'];
typeCompute?.(graph, node);
}
var propTypeComputations = {
'_variable': computeVariable,
};
function pushDependenciesToStack(nodeId, graph, stack){
graph.forEachLinkedNode(
nodeId,
(linkedNode, link) => {
// Ignore inventory links, they can't cause dependency loops
// and are already fully computed when they are created
if (link.data === 'inventory') return;
stack.push({
node: linkedNode,
visited: false,
visitedChildren: false,
});
},
true // enumerate only outbound links
);
}

View File

@@ -8,6 +8,15 @@ const ToggleSchema = createPropertySchema({
optional: true,
max: STORAGE_LIMITS.name,
},
variableName: {
type: String,
optional: true,
max: STORAGE_LIMITS.variableName,
},
showUI: {
type: Boolean,
optional: true,
},
disabled: {
type: Boolean,
optional: true,

2
app/package-lock.json generated
View File

@@ -2795,7 +2795,7 @@
},
"signal-exit": {
"version": "3.0.2",
"resolved": false,
"resolved": "",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simpl-schema": {