From dfd7ad4af56d9919009999a7b5bc072e1f7771a4 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Wed, 15 Sep 2021 15:15:18 +0200 Subject: [PATCH] Got tests running on single property character --- .../computation/engine/combineStat.js | 2 +- .../buildComputation/CreatureComputation.js | 36 ++++++++++ .../linkCalculationDependencies.js | 2 +- .../buildComputation/linkInventory.js | 2 + .../buildComputation/linkTypeDependencies.js | 2 +- .../parseCalculationFields.js | 49 ++++++++++--- .../buildComputation/removeSchemaFields.js | 13 ++++ .../newEngine/buildCreatureComputation.js | 71 +++++-------------- .../buildCreatureComputation.test.js | 12 +++- .../computeByType/computeVariable.js | 19 ++--- .../computeImplicitVariable.js | 2 +- .../computeVariableAsAttribute.js | 3 +- .../computeComputation/computeCalculations.js | 3 +- .../computeComputation/computeToggles.js | 1 + .../newEngine/computeCreatureComputation.js | 32 +++------ .../computeCreatureComputation.test.js | 34 +++++++++ .../newEngine/utility/applyFnToKey.js | 17 +++-- .../newEngine/utility/applyFnToKey.test.js | 60 ++++++++++++++++ app/imports/api/properties/Actions.js | 1 + .../subSchemas/inlineCalculationField.js | 26 +++++-- .../server/2.0-beta.33-dbv1.test.js | 3 - .../characterSheetTabs/InventoryTab.vue | 2 +- .../components/inventory/ContainerCard.vue | 2 +- .../ui/properties/viewers/ItemViewer.vue | 2 +- 24 files changed, 277 insertions(+), 119 deletions(-) create mode 100644 app/imports/api/creature/computation/newEngine/buildComputation/CreatureComputation.js create mode 100644 app/imports/api/creature/computation/newEngine/buildComputation/removeSchemaFields.js create mode 100644 app/imports/api/creature/computation/newEngine/computeCreatureComputation.test.js create mode 100644 app/imports/api/creature/computation/newEngine/utility/applyFnToKey.test.js diff --git a/app/imports/api/creature/computation/engine/combineStat.js b/app/imports/api/creature/computation/engine/combineStat.js index db612d78..f2c10e8f 100644 --- a/app/imports/api/creature/computation/engine/combineStat.js +++ b/app/imports/api/creature/computation/engine/combineStat.js @@ -1,7 +1,7 @@ import computeStat from '/imports/api/creature/computation/engine/computeStat.js'; import computeProficiency from '/imports/api/creature/computation/engine/computeProficiency.js'; import evaluateCalculation from '/imports/api/creature/computation/engine/evaluateCalculation.js'; -import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js'; +import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js'; import { union } from 'lodash'; export default function combineStat(stat, aggregator, memo){ diff --git a/app/imports/api/creature/computation/newEngine/buildComputation/CreatureComputation.js b/app/imports/api/creature/computation/newEngine/buildComputation/CreatureComputation.js new file mode 100644 index 00000000..d4836b47 --- /dev/null +++ b/app/imports/api/creature/computation/newEngine/buildComputation/CreatureComputation.js @@ -0,0 +1,36 @@ +import { cloneDeep } from 'lodash'; +import createGraph from 'ngraph.graph'; + +export default class CreatureComputation { + constructor(properties){ + // Set up fields + this.originalPropsById = {}; + this.propsById = {}; + this.propsByType = {}; + this.propsByVariableName = {}; + this.props = properties; + this.dependencyGraph = createGraph(); + + // Store properties for easy access later + properties.forEach(prop => { + // Store a copy of the unmodified prop + this.originalPropsById[prop._id] = cloneDeep(prop); + + // Store by id + this.propsById[prop._id] = prop; + + // Store by type + this.propsByType[prop.type] ? + this.propsByType[prop.type].push(prop) : + this.propsByType[prop.type] = [prop]; + + // Store by variableName + this.propsByVariableName[prop.variableName] ? + this.propsByVariableName[prop.variableName].push(prop) : + this.propsByVariableName[prop.variableName]= [prop]; + + // Store the prop in the dependency graph + this.dependencyGraph.addNode(prop._id, prop); + }); + } +} diff --git a/app/imports/api/creature/computation/newEngine/buildComputation/linkCalculationDependencies.js b/app/imports/api/creature/computation/newEngine/buildComputation/linkCalculationDependencies.js index 4240f1ad..e6081faa 100644 --- a/app/imports/api/creature/computation/newEngine/buildComputation/linkCalculationDependencies.js +++ b/app/imports/api/creature/computation/newEngine/buildComputation/linkCalculationDependencies.js @@ -9,7 +9,7 @@ export default function linkCalculationDependencies(dependencyGraph, prop, {prop // ancestors: {} //this gets added if there are resolved ancestors }; // Traverse the parsed calculation looking for variable names - calcObj._parsedCalculation.travese(node => { + calcObj._parsedCalculation.traverse(node => { // Skip nodes that aren't symbols or accessors if (!(node instanceof SymbolNode || node instanceof AccessorNode)) return; // Link ancestor references as direct property dependencies diff --git a/app/imports/api/creature/computation/newEngine/buildComputation/linkInventory.js b/app/imports/api/creature/computation/newEngine/buildComputation/linkInventory.js index d0b0f738..c0c9747c 100644 --- a/app/imports/api/creature/computation/newEngine/buildComputation/linkInventory.js +++ b/app/imports/api/creature/computation/newEngine/buildComputation/linkInventory.js @@ -27,6 +27,8 @@ export default function linkInventory(forest, dependencyGraph){ } function handleProp(prop, containerStack, dependencyGraph){ + // Skip props that aren't part of the inventory + if (prop.type !== 'inventory' && prop.type !== 'container') return; // Determine if this property is carried, items are carried by default let carried = prop.type === 'container' ? prop.carried : true; diff --git a/app/imports/api/creature/computation/newEngine/buildComputation/linkTypeDependencies.js b/app/imports/api/creature/computation/newEngine/buildComputation/linkTypeDependencies.js index a16ad83c..b6342447 100644 --- a/app/imports/api/creature/computation/newEngine/buildComputation/linkTypeDependencies.js +++ b/app/imports/api/creature/computation/newEngine/buildComputation/linkTypeDependencies.js @@ -13,7 +13,7 @@ const linkDependenciesByType = { } export default function linkTypeDependencies(dependencyGraph, prop){ - linkDependenciesByType[prop.type]?.(prop); + linkDependenciesByType[prop.type]?.(dependencyGraph, prop); } function linkClassLevel(dependencyGraph, prop){ diff --git a/app/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js b/app/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js index df451a3b..de80c789 100644 --- a/app/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js +++ b/app/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js @@ -1,20 +1,54 @@ +import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; import { prettifyParseError, parse } from '/imports/parser/parser.js'; import ErrorNode from '/imports/parser/parseTree/ErrorNode.js'; import applyFnToKey from '/imports/api/creature/computation/newEngine/utility/applyFnToKey.js'; +import { get } from 'lodash'; export default function parseCalculationFields(prop, schemas){ + parseInlineCalculationFields(prop, schemas); + parseDirectCalculationFields(prop, schemas); +} + +function parseInlineCalculationFields(prop, schemas){ + // For each key in the schema + schemas[prop.type]._schemaKeys.forEach( key => { + // That ends in .inlineCalculations + if (key.slice(-19) === '.inlineCalculations'){ + const inlineCalcKey = key.slice(0, -19); + applyFnToKey(prop, inlineCalcKey, (prop, key) => { + const inlineCalcObj = get(prop, key); + if (!inlineCalcObj) return; + // Store a reference to all the inline calculations + prop._computationDetails.inlineCalculations.push(inlineCalcObj); + // Extract the calculations and store them on the property + let string = inlineCalcObj.text; + inlineCalcObj.inlineCalculations = []; + let matches = string.matchAll(INLINE_CALCULATION_REGEX); + for (let match of matches){ + let calculation = match[1]; + inlineCalcObj.inlineCalculations.push({ + calculation, + }); + } + }); + } + }); +} + +function parseDirectCalculationFields(prop, schemas){ // For each key in the schema schemas[prop.type]._schemaKeys.forEach( key => { // that ends in '.calculation' if (key.slice(-12) === '.calculation'){ - const calcKey = key.sclice(0, -12); - + const calcKey = key.slice(0, -12); // Determine the level the calculation should compute down to - let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel; + let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel || 'reduce'; // For all fields matching they keys // supports `keys.$.with.$.arrays` - applyFnToKey(prop, calcKey, calcObj => { + applyFnToKey(prop, calcKey, (prop, key) => { + const calcObj = get(prop, key); + if (!calcObj) return; // Store a reference to all the calculations prop._computationDetails.calculations.push(calcObj); // Store the level to compute down to later @@ -23,14 +57,7 @@ export default function parseCalculationFields(prop, schemas){ parseCalculation(calcObj); }); // Or that ends in .inlineCalculations - } else if (key.slice(-19) === '.inlineCalculations'){ - const inlineCalcKey = key.sclice(0, -19); - applyFnToKey(prop, inlineCalcKey, inlineCalcObj => { - // Store a reference to all the inline calculations - prop._computationDetails.inlineCalculations.push(inlineCalcObj); - }); } - }); } diff --git a/app/imports/api/creature/computation/newEngine/buildComputation/removeSchemaFields.js b/app/imports/api/creature/computation/newEngine/buildComputation/removeSchemaFields.js new file mode 100644 index 00000000..ab1c3eb7 --- /dev/null +++ b/app/imports/api/creature/computation/newEngine/buildComputation/removeSchemaFields.js @@ -0,0 +1,13 @@ +import applyFnToKey from '../utility/applyFnToKey.js'; +import { unset } from 'lodash'; + +export default function removeSchemaFields(schemas, prop){ + schemas.forEach(schema => { + schema._schemaKeys.forEach(key => { + // Skip object keys + if (schema.getQuickTypeForKey(key) === 'object') return; + // Unset other computed only keys + applyFnToKey(prop, key, unset) + }); + }); +} diff --git a/app/imports/api/creature/computation/newEngine/buildCreatureComputation.js b/app/imports/api/creature/computation/newEngine/buildCreatureComputation.js index 65a20ce0..e699c8fc 100644 --- a/app/imports/api/creature/computation/newEngine/buildCreatureComputation.js +++ b/app/imports/api/creature/computation/newEngine/buildCreatureComputation.js @@ -4,17 +4,16 @@ import CreatureProperties, 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/utility/applyFnToKey.js'; -import { cloneDeep, unset } from 'lodash'; -import createGraph from 'ngraph.graph'; -import linkInventory from '/imports/api/creature/computation/newEngine/buildComputation/linkInventory.js'; -import walkDown from '/imports/api/creature/computation/newEngine/utility/walkdown.js'; -import parseCalculationFields from '/imports/api/creature/computation/newEngine/buildComputation/parseCalculationFields.js'; -import computeInactiveStatus from '/imports/api/creature/computation/newEngine/buildComputation/computeInactiveStatus.js'; -import computeToggleDependencies from '/imports/api/creature/computation/newEngine/buildComputation/computeToggleDependencies.js'; -import linkCalculationDependencies from '/imports/api/creature/computation/newEngine/buildComputation/linkCalculationDependencies.js'; -import linkTypeDependencies from '/imports/api/creature/computation/newEngine/buildComputation/linkTypeDependencies.js'; -import computeSlotQuantityFilled from '/imports/api/creature/computation/newEngine/buildComputation/computeSlotQuantityFilled.js'; +import linkInventory from './buildComputation/linkInventory.js'; +import walkDown from './utility/walkdown.js'; +import parseCalculationFields from './buildComputation/parseCalculationFields.js'; +import computeInactiveStatus from './buildComputation/computeInactiveStatus.js'; +import computeToggleDependencies from './buildComputation/computeToggleDependencies.js'; +import linkCalculationDependencies from './buildComputation/linkCalculationDependencies.js'; +import linkTypeDependencies from './buildComputation/linkTypeDependencies.js'; +import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js'; +import CreatureComputation from './buildComputation/CreatureComputation.js'; +import removeSchemaFields from './buildComputation/removeSchemaFields.js'; /** * Store index of properties @@ -50,50 +49,32 @@ function getProperties(creatureId){ } export function buildComputationFromProps(properties){ + + const computation = new CreatureComputation(properties); // Dependency graph where edge(a, b) means a depends on b // The graph includes all dependencies even of inactive properties // such that any properties changing without changing their dependencies // can limit the recompute to connected parts of the graph // Each node's data represents a prop or a virtual prop like a variable // Each link's data is a string representing the link type - const dependencyGraph = createGraph(); - - const computation = { - originalPropsById: {}, - propsById: {}, - propsByType: {}, - propsByVariableName: {}, - props: properties, - dependencyGraph, - }; + const dependencyGraph = computation.dependencyGraph; // Process the properties one by one properties.forEach(prop => { - // Store the prop in the memo by type, variableName and id - storePropInMemo(prop, computation) - - // Store the prop in the dependency graph - dependencyGraph.addNode(prop._id, prop); - - // Remove old computed only fields - computedOnlySchemas[prop.type]._schemaKeys.forEach(key => - applyFnToKey(prop, key, unset) - ); - - // Remove old denormalised fields - denormSchema._schemaKeys.forEach(key => - applyFnToKey(prop, key, unset) - ); + let computedSchema = computedOnlySchemas[prop.type]; + removeSchemaFields([computedSchema, denormSchema], prop); // Add a place to store all the computation details prop._computationDetails = { calculations: [], + inlineCalculations: [], toggleAncestors: [], }; // Parse all the calculations parseCalculationFields(prop, computedSchemas); + }); // Get all the properties as trees based on their ancestors @@ -108,7 +89,7 @@ export function buildComputationFromProps(properties){ // Link the inventory dependencies linkInventory(forest, dependencyGraph); - // Graph functions that rely on the props being stored first + // Link functions that require the above to be complete properties.forEach(prop => { linkTypeDependencies(dependencyGraph, prop, computation); linkCalculationDependencies(dependencyGraph, prop, computation); @@ -116,19 +97,3 @@ export function buildComputationFromProps(properties){ return computation; } - -function storePropInMemo(prop, memo){ - // Store dicts for easy access later - // Store a copy of the unmodified prop - memo.originalPropsById[prop._id] = cloneDeep(prop); - // Store by id - memo.propsById[prop._id] = prop; - // Store by type - memo.propsByType[prop.type] ? - memo.propsByType[prop.type].push(prop) : - memo.propsByType[prop.type] = [prop]; - // Store by variableName - memo.propsByVariableName[prop.variableName] ? - memo.propsByVariableName[prop.variableName].push(prop) : - memo.propsByVariableName[prop.variableName]= [prop]; -} diff --git a/app/imports/api/creature/computation/newEngine/buildCreatureComputation.test.js b/app/imports/api/creature/computation/newEngine/buildCreatureComputation.test.js index 2df7e2ce..4a99644d 100644 --- a/app/imports/api/creature/computation/newEngine/buildCreatureComputation.test.js +++ b/app/imports/api/creature/computation/newEngine/buildCreatureComputation.test.js @@ -3,9 +3,9 @@ import { assert } from 'chai'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; describe('buildComputation', function(){ - it('Builds something', function(){ + it('Builds something at all', function(){ let computation = buildComputationFromProps(testProperties); - console.log(computation); + assert.exists(computation); }); }); @@ -13,6 +13,14 @@ var testProperties = [ clean({ _id: 'attributeId123', type: 'attribute', + variableName: 'strength', + attributeType: 'ability', + baseValue: { + calculation: '1 + 2 + 3', + }, + description: { + text: 'strength is {strength}' + } }), ]; diff --git a/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable.js b/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable.js index c505e81b..e5f2041b 100644 --- a/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable.js +++ b/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable.js @@ -1,9 +1,9 @@ -import aggregate from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/aggregate/index.js'; -import computeVariableAsAttribute from '/imports/api/creature/computation/newEngine/computeComputation/computeVariableAsType/computeVariableAsAttribute.js'; -import computeVariableAsSkill from '/imports/api/creature/computation/newEngine/computeComputation/computeVariableAsType/computeVariableAsSkill.js'; -import computeVariableAsConstant from '/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeVariableAsConstant.js'; -import computeVariableAsClass from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/computeVariableAsClass.js'; -import computeImplicitVariable from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/computeImplicitVariable.js'; +import aggregate from './computeVariable/aggregate/index.js'; +import computeVariableAsAttribute from './computeVariable/computeVariableAsAttribute.js'; +import computeVariableAsSkill from './computeVariable/computeVariableAsSkill.js'; +import computeVariableAsConstant from './computeVariable/computeVariableAsConstant.js'; +import computeVariableAsClass from './computeVariable/computeVariableAsClass.js'; +import computeImplicitVariable from './computeVariable/computeImplicitVariable.js'; export default function computeVariable(graph, node, scope){ if (!node.data) node.data = {}; @@ -16,6 +16,7 @@ export default function computeVariable(graph, node, scope){ // Otherwise add an implicit variable to the scope scope[node.id] = computeImplicitVariable(node, scope); } + console.log('computed variable ', node); } function aggregateLinks(graph, node){ @@ -40,13 +41,14 @@ function aggregateLinks(graph, node){ function combineAggregations(node, scope){ combineMultiplierAggregator(node); - node.overridenProps.forEach(prop => { + node.data.overridenProps?.forEach(prop => { computeVariableProp(node, prop, scope); }); - computeVariableProp(node, node.definingProp, scope); + computeVariableProp(node, node.data.definingProp, scope); } function computeVariableProp(node, prop, scope){ + if (!prop) return; if (prop.type === 'attribute'){ computeVariableAsAttribute(node, prop, scope) } else if (prop.type === 'skill'){ @@ -61,6 +63,7 @@ function computeVariableProp(node, prop, scope){ function combineMultiplierAggregator(node){ // get a reference to the aggregator const aggregator = node.data.multiplierAggregator; + if (!aggregator) return; // Combine let value; diff --git a/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeImplicitVariable.js b/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeImplicitVariable.js index 1f15ff23..1ed6e530 100644 --- a/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeImplicitVariable.js +++ b/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeImplicitVariable.js @@ -1,4 +1,4 @@ -import getAggregatorResult from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/getAggregatorResult.js'; +import getAggregatorResult from './getAggregatorResult.js'; /* * Variables with effects, proficiencies, or damage multipliers but no defining diff --git a/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js b/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js index 81f4c73b..032b0840 100644 --- a/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js +++ b/app/imports/api/creature/computation/newEngine/computeComputation/computeByType/computeVariable/computeVariableAsAttribute.js @@ -1,7 +1,8 @@ -import getAggregatorResult from '/imports/api/creature/computation/newEngine/computeComputation/computeVariable/getAggregatorResult.js'; +import getAggregatorResult from './getAggregatorResult.js'; export default function computeVariableAsAttribute(node, prop, scope){ let result = getAggregatorResult(node); + console.log('computing variable as attribure ', node); prop.total = result; prop.value = prop.total - (prop.damage || 0); diff --git a/app/imports/api/creature/computation/newEngine/computeComputation/computeCalculations.js b/app/imports/api/creature/computation/newEngine/computeComputation/computeCalculations.js index 34a61124..7631dc9b 100644 --- a/app/imports/api/creature/computation/newEngine/computeComputation/computeCalculations.js +++ b/app/imports/api/creature/computation/newEngine/computeComputation/computeCalculations.js @@ -2,6 +2,7 @@ import { CompilationContext } from '/imports/parser/parser.js'; import INLINE_CALCULATION_REGEX from '/imports/constants/INLINE_CALCULTION_REGEX.js'; export default function computeCalculations(node, scope){ + if (!node.data) return; // evaluate all the calculations node.data._computationDetails?.calculations?.forEach(calcObj => { evaluateCalculation(calcObj, scope) @@ -14,7 +15,7 @@ export default function computeCalculations(node, scope){ function evaluateCalculation(calculation, scope){ const context = new CompilationContext(); const parseNode = calculation._parsedCalculation; - const fn = calculation._parseLevel || 'reduce'; + const fn = calculation._parseLevel; const calculationScope = {...calculation._localScope, ...scope}; calculation.value = parseNode[fn](calculationScope, context); calculation.errors = context.errors; diff --git a/app/imports/api/creature/computation/newEngine/computeComputation/computeToggles.js b/app/imports/api/creature/computation/newEngine/computeComputation/computeToggles.js index d9f0cb26..50fff1db 100644 --- a/app/imports/api/creature/computation/newEngine/computeComputation/computeToggles.js +++ b/app/imports/api/creature/computation/newEngine/computeComputation/computeToggles.js @@ -1,5 +1,6 @@ export default function evaluateToggles(node){ let prop = node.data; + if (!prop) return; let toggles = prop._computationDetails?.toggleAncestors; if (!toggles) return; toggles.forEach(toggle => { diff --git a/app/imports/api/creature/computation/newEngine/computeCreatureComputation.js b/app/imports/api/creature/computation/newEngine/computeCreatureComputation.js index b5261550..c819e7dc 100644 --- a/app/imports/api/creature/computation/newEngine/computeCreatureComputation.js +++ b/app/imports/api/creature/computation/newEngine/computeCreatureComputation.js @@ -8,27 +8,23 @@ export default function computeCreatureComputation(computation){ const scope = {}; const graph = computation.dependencyGraph; // Add all nodes to the stack - graph.forEachNode(node => stack.push({ - node, - visited: false, - visitedChildren: false, - })); + graph.forEachNode(node => stack.push(node)); // Depth first traversal of nodes while (stack.length){ let top = stack[stack.length - 1]; - if (top.visited){ + if (top._visited){ // The object has already been computed, skip stack.pop(); - } else if (top.visitedChildren){ - // Compute the top object of the stack - compute(graph, top.node, scope); + } else if (top._visitedChildren){ // Mark the object as visited and remove from stack - top.visited = true; + top._visited = true; stack.pop(); + // Compute the top object of the stack + compute(graph, top, scope); } else { + top._visitedChildren = true; // Push dependencies to graph to be computed first - pushDependenciesToStack(top.node.id, graph, stack); - top.visitedChildren = true; + pushDependenciesToStack(top.id, graph, stack); } } } @@ -42,15 +38,5 @@ function compute(graph, node, scope){ } function pushDependenciesToStack(nodeId, graph, stack){ - graph.forEachLinkedNode( - nodeId, - (linkedNode) => { - stack.push({ - node: linkedNode, - visited: false, - visitedChildren: false, - }); - }, - true // enumerate only outbound links - ); + graph.forEachLinkedNode(nodeId, linkedNode => stack.push(linkedNode), true); } diff --git a/app/imports/api/creature/computation/newEngine/computeCreatureComputation.test.js b/app/imports/api/creature/computation/newEngine/computeCreatureComputation.test.js new file mode 100644 index 00000000..6bdc1ac6 --- /dev/null +++ b/app/imports/api/creature/computation/newEngine/computeCreatureComputation.test.js @@ -0,0 +1,34 @@ +import computeCreatureComputation from './computeCreatureComputation.js'; +import { buildComputationFromProps } from './buildCreatureComputation.js'; +import { assert } from 'chai'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; + +describe('Compute compuation', function(){ + it('Computes something at all', function(){ + console.time('compute'); + let computation = buildComputationFromProps(testProperties); + computeCreatureComputation(computation); + console.timeEnd('compute'); + assert.exists(computation); + }); +}); + +var testProperties = [ + clean({ + _id: 'attributeId123', + type: 'attribute', + variableName: 'strength', + attributeType: 'ability', + baseValue: { + calculation: '1 + 2 + 3', + }, + description: { + text: 'strength is {strength}' + } + }), +]; + +function clean(prop){ + let schema = CreatureProperties.simpleSchema(prop); + return schema.clean(prop); +} diff --git a/app/imports/api/creature/computation/newEngine/utility/applyFnToKey.js b/app/imports/api/creature/computation/newEngine/utility/applyFnToKey.js index 3e3780b4..0d4005af 100644 --- a/app/imports/api/creature/computation/newEngine/utility/applyFnToKey.js +++ b/app/imports/api/creature/computation/newEngine/utility/applyFnToKey.js @@ -1,7 +1,7 @@ import { get } from 'lodash'; -export function applyFnToKey(doc, key, fn){ - if (key.includes('$.')){ +export default function applyFnToKey(doc, key, fn){ + if (key.includes('.$')){ applyToArrayKey(doc, key, fn); } else { applyToSingleKey(doc, key, fn); @@ -16,26 +16,31 @@ function applyToSingleKey(doc, key, fn){ /** * Applies the given function to all instances in a document key * key.$.with.$.subdocs will apply to all key[i...n].with[j...m].subdocs + * Warning: Order might be confusing, it will traverse the deepest array in order + * but the shallower arrays in reverse order */ function applyToArrayKey(doc, key, fn){ const keySplit = key.split('.$'); - // Stack based depth first traversal of arrays + const array = get(doc, keySplit[0]); + if (!array) return; const stack = [{ - array: get(doc, keySplit[0]), + array, paths: keySplit.slice(1), currentPath: keySplit[0], indices: [], }]; while(stack.length){ const state = stack.pop(); - for (let index in state.array.length){ + for (let index in state.array){ const currentPath = `${state.currentPath}[${index}]${state.paths[0]}` if (state.paths.length == 1){ applyToSingleKey(doc, currentPath, fn); } else { + const array = get(doc, currentPath); + if (!array) return; stack.push({ - array: get(doc, currentPath), + array, paths: state.paths.slice(1), currentPath, indices: [...state.indices, index], diff --git a/app/imports/api/creature/computation/newEngine/utility/applyFnToKey.test.js b/app/imports/api/creature/computation/newEngine/utility/applyFnToKey.test.js new file mode 100644 index 00000000..5e1b3a3c --- /dev/null +++ b/app/imports/api/creature/computation/newEngine/utility/applyFnToKey.test.js @@ -0,0 +1,60 @@ +import applyFnToKey from './applyFnToKey.js'; +import { assert } from 'chai'; +import { get } from 'lodash'; + +describe('apply function to key', function(){ + it('uses a basic key correctly', function(){ + let obj = getStartingObject(); + applyFnToKey(obj, 'fox.name', (doc, key) => { + assert.equal(obj, doc); + assert.equal(key, 'fox.name'); + assert.equal(get(doc, key), 'foxy'); + }); + }); + it('uses a single nested key correctly', function(){ + let obj = getStartingObject(); + let foxSounds = []; + applyFnToKey(obj, 'fox.sound.$', (doc, key) => { + foxSounds.push(get(doc, key)); + }); + assert.include(foxSounds, 'wah'); + assert.include(foxSounds, 'tjoef'); + assert.include(foxSounds, 'kek'); + }); + it('uses a double nested key correctly', function(){ + let obj = getStartingObject(); + let birdSounds = []; + applyFnToKey(obj, 'birds.$.sound.$', (doc, key) => { + birdSounds.push(get(doc, key)); + }); + assert.include(birdSounds, 'koer'); + assert.include(birdSounds, 'hello'); + assert.include(birdSounds, 'squawk'); + }); +}); + +function getStartingObject(){ + return { + fox: { + name: 'foxy', + sound: [ + 'tjoef', + 'kek', + 'wah' + ] + }, + birds: [{ + name: 'pigeon', + sound: [ + 'koer', + ] + },{ + name: 'parrot', + sound: [ + 'hello', + 'cracker?', + 'squawk', + ] + }] + } +} diff --git a/app/imports/api/properties/Actions.js b/app/imports/api/properties/Actions.js index 86453014..bfaf28a1 100644 --- a/app/imports/api/properties/Actions.js +++ b/app/imports/api/properties/Actions.js @@ -6,6 +6,7 @@ import { } from '/imports/api/properties/subSchemas/ResourcesSchema.js'; import createPropertySchema from '/imports/api/properties/subSchemas/createPropertySchema.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; +SimpleSchema.extendOptions(['parseLevel']); /* * Actions are things a character can do diff --git a/app/imports/api/properties/subSchemas/inlineCalculationField.js b/app/imports/api/properties/subSchemas/inlineCalculationField.js index 93bfbf23..f9f67242 100644 --- a/app/imports/api/properties/subSchemas/inlineCalculationField.js +++ b/app/imports/api/properties/subSchemas/inlineCalculationField.js @@ -1,5 +1,5 @@ import SimpleSchema from 'simpl-schema'; -import InlineComputationSchema from '/imports/api/properties/subSchemas/InlineComputationSchema.js'; +import ErrorSchema from '/imports/api/properties/subSchemas/ErrorSchema.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; // Get schemas that apply fields directly so they can be gracefully extended @@ -26,18 +26,36 @@ function computedOnlyInlineCalculationField(field){ type: Object, optional: true, }, + [`${field}.value`]: { + type: String, + optional: true, + max: STORAGE_LIMITS.inlineCalculationField, + }, [`${field}.inlineCalculations`]: { type: Array, defaultValue: [], maxCount: STORAGE_LIMITS.inlineCalculationCount, }, [`${field}.inlineCalculations.$`]: { - type: InlineComputationSchema, + type: Object, }, - [`${field}.value`]: { + // The part between bracers {} + [`${field}.inlineCalculations.$.calculation`]: { type: String, + max: STORAGE_LIMITS.calculation, + }, + [`${field}.inlineCalculations.$.value`]: { + type: SimpleSchema.oneOf(String, Number), optional: true, - max: STORAGE_LIMITS.inlineCalculationField, + max: STORAGE_LIMITS.calculation, + }, + [`${field}.inlineCalculations.$.errors`]: { + type: Array, + optional: true, + maxCount: STORAGE_LIMITS.errorCount, + }, + [`${field}.inlineCalculations.$.errors.$`]: { + type: ErrorSchema, }, }); } diff --git a/app/imports/migrations/server/2.0-beta.33-dbv1.test.js b/app/imports/migrations/server/2.0-beta.33-dbv1.test.js index d74c1896..ce3512a7 100644 --- a/app/imports/migrations/server/2.0-beta.33-dbv1.test.js +++ b/app/imports/migrations/server/2.0-beta.33-dbv1.test.js @@ -38,9 +38,6 @@ const exampleAction = { 'usesUsed':0, 'description':'Starting at 1st level, you gain the ability to place a baleful curse on someone. As a bonus action, choose one creature you can see within 30 feet of you. The target is cursed for 1 minute. The curse ends early if the target dies, you die, or you are incapacitated. Until the curse ends, you gain the following benefits:\n\n- You gain a bonus to damage rolls against the cursed target. The bonus equals your proficiency bonus.\n- Any attack roll you make against the cursed target is a critical hit on a roll of 19 or 20 on the d20.\n- If the cursed target dies, you regain hit points equal to your warlock level + your Charisma modifier (minimum of 1 hit point). \n{warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."} \nYou can\\\'t use this feature again until you finish a short or long rest.', 'color':'#8e24aa', - 'dependencies':[ - '4eM4YkgAaoCJfCfQ8', - ], 'descriptionCalculations':[ { 'calculation':'warlock.level <10 ? "" :"- If you are hit with an attack by your cursed target, use your reaction to roll a d6. On a 4 or higher, the attack instead misses."', diff --git a/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue b/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue index 4fb921e4..46a56e55 100644 --- a/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue +++ b/app/imports/ui/creature/character/characterSheetTabs/InventoryTab.vue @@ -107,7 +107,7 @@ import ItemList from '/imports/ui/properties/components/inventory/ItemList.vue'; import getParentRefByTag from '/imports/api/creature/creatureProperties/methods/getParentRefByTag.js'; import BUILT_IN_TAGS from '/imports/constants/BUILT_IN_TAGS.js'; import CoinValue from '/imports/ui/components/CoinValue.vue'; -import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js'; +import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js'; export default { components: { diff --git a/app/imports/ui/properties/components/inventory/ContainerCard.vue b/app/imports/ui/properties/components/inventory/ContainerCard.vue index 22492234..f95ab1bd 100644 --- a/app/imports/ui/properties/components/inventory/ContainerCard.vue +++ b/app/imports/ui/properties/components/inventory/ContainerCard.vue @@ -49,7 +49,7 @@ import ToolbarCard from '/imports/ui/components/ToolbarCard.vue'; import ItemList from '/imports/ui/properties/components/inventory/ItemList.vue'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import CoinValue from '/imports/ui/components/CoinValue.vue'; -import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js'; +import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js'; export default { components: { diff --git a/app/imports/ui/properties/viewers/ItemViewer.vue b/app/imports/ui/properties/viewers/ItemViewer.vue index 90af478d..99291985 100644 --- a/app/imports/ui/properties/viewers/ItemViewer.vue +++ b/app/imports/ui/properties/viewers/ItemViewer.vue @@ -134,7 +134,7 @@ import propertyViewerMixin from '/imports/ui/properties/viewers/shared/propertyV import CoinValue from '/imports/ui/components/CoinValue.vue'; import IncrementButton from '/imports/ui/components/IncrementButton.vue'; import adjustQuantity from '/imports/api/creature/creatureProperties/methods/adjustQuantity.js'; -import stripFloatingPointOddities from '/imports/ui/utility/stripFloatingPointOddities.js'; +import stripFloatingPointOddities from '/imports/api/creature/computation/newEngine/utility/stripFloatingPointOddities.js'; export default { components:{