Computation engine rewrite continues
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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
2
app/package-lock.json
generated
@@ -2795,7 +2795,7 @@
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.2",
|
||||
"resolved": false,
|
||||
"resolved": "",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"simpl-schema": {
|
||||
|
||||
Reference in New Issue
Block a user