Optimized some slow parts of the engine.

Last low hanging fruit: parsing is slow, cache parsed calculations
This commit is contained in:
Stefan Zermatten
2021-09-29 15:54:14 +02:00
parent cb10b53a10
commit cb1fd38df3
21 changed files with 151 additions and 96 deletions

View File

@@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash';
import { EJSON } from 'meteor/ejson';
import createGraph from 'ngraph.graph';
export default class CreatureComputation {
@@ -6,8 +6,6 @@ export default class CreatureComputation {
// Set up fields
this.originalPropsById = {};
this.propsById = {};
this.propsByType = {};
this.propsByVariableName = {};
this.scope = {};
this.props = properties;
this.dependencyGraph = createGraph();
@@ -15,21 +13,11 @@ export default class CreatureComputation {
// Store properties for easy access later
properties.forEach(prop => {
// Store a copy of the unmodified prop
this.originalPropsById[prop._id] = cloneDeep(prop);
// EJSON clone is ~4x faster than lodash cloneDeep for EJSONable objects
this.originalPropsById[prop._id] = EJSON.clone(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);
});

View File

@@ -11,66 +11,53 @@ export default function parseCalculationFields(prop, schemas){
function discoverInlineCalculationFields(prop, schemas){
// For each key in the schema
schemas[prop.type]._schemaKeys.forEach( key => {
schemas[prop.type].inlineCalculationFields().forEach( calcKey => {
// 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;
if (!string) return;
inlineCalcObj.inlineCalculations = [];
let matches = string.matchAll(INLINE_CALCULATION_REGEX);
for (let match of matches){
let calculation = match[1];
inlineCalcObj.inlineCalculations.push({
calculation,
});
}
});
}
applyFnToKey(prop, calcKey, (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;
if (!string) return;
inlineCalcObj.inlineCalculations = [];
let matches = string.matchAll(INLINE_CALCULATION_REGEX);
for (let match of matches){
let calculation = match[1];
inlineCalcObj.inlineCalculations.push({
calculation,
});
}
});
});
}
function parseAllCalculationFields(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.slice(0, -12);
// Determine the level the calculation should compute down to
let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel || 'reduce';
// For each computed key in the schema
schemas[prop.type].computedFields().forEach( calcKey => {
// Determine the level the calculation should compute down to
let parseLevel = schemas[prop.type].getDefinition(calcKey).parseLevel || 'reduce';
// For all fields matching they keys
// supports `keys.$.with.$.arrays`
applyFnToKey(prop, calcKey, (prop, key) => {
const calcObj = get(prop, key);
if (!calcObj) return;
// If the calculation isn't set, delete the whole object
if (!calcObj.calculation){
unset(prop, key);
return;
}
// 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);
});
// Or that ends in .inlineCalculations
}
// For all fields matching they keys
// supports `keys.$.with.$.arrays`
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
calcObj._parseLevel = parseLevel;
// Parse the calculation
parseCalculation(calcObj);
});
});
}
function parseCalculation(calcObj){
let calculation = calcObj.calculation || '';
if (!calcObj.calculation) return;
try {
calcObj._parsedCalculation = parse(calculation);
calcObj._parsedCalculation = parse(calcObj.calculation);
} catch (e) {
let error = {
type: 'evaluation',

View File

@@ -3,17 +3,8 @@ import { unset } from 'lodash';
export default function removeSchemaFields(schemas, prop){
schemas.forEach(schema => {
schema._schemaKeys.forEach(key => {
// Skip object and array keys, except the errors array
if (
schema.getQuickTypeForKey(key) === 'object' ||
(
schema.getQuickTypeForKey(key) === 'objectArray' &&
key.slice(-6)!== 'errors'
)
) return;
// Unset other computed only keys
applyFnToKey(prop, key, unset)
});
schema.removeBeforeComputeFields().forEach(
key => applyFnToKey(prop, key, unset)
);
});
}

View File

@@ -29,14 +29,10 @@ import removeSchemaFields from './buildComputation/removeSchemaFields.js';
* computed toggles
*/
/**
* TODO
* compute class levels
*/
export default function buildCreatureComputation(creatureId){
const properties = getProperties(creatureId);
return buildComputationFromProps(properties);
const computation = buildComputationFromProps(properties);
return computation;
}
function getProperties(creatureId){

View File

@@ -1,3 +1,4 @@
import '/imports/api/simpleSchemaConfig.js';
import { buildComputationFromProps } from './buildCreatureComputation.js';
import { assert } from 'chai';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';

View File

@@ -7,9 +7,12 @@ import getAggregatorResult from './getAggregatorResult.js';
export default function computeImplicitVariable(node){
const prop = {};
const result = getAggregatorResult(node);
prop.total = result;
prop.value = result;
prop.proficiency = node.data.proficiency;
if (result !== undefined){
prop.value = result;
}
if (node.data.proficiency !== undefined){
prop.proficiency = node.data.proficiency;
}
// denormalise class level aggregator
let classLevelAgg = node.data.classLevelAggregator;

View File

@@ -35,6 +35,7 @@ function evaluateCalculation(calculation, scope){
// remove the working fields
delete calculation._parseLevel;
delete calculation._parsedCalculation;
delete calculation._localScope;
}
function embedInlineCalculations(inlineCalcObj){

View File

@@ -1,5 +1,5 @@
import { Meteor } from 'meteor/meteor'
import { isEqual } from 'lodash';
import { EJSON } from 'meteor/ejson';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import propertySchemasIndex from '/imports/api/properties/computedOnlyPropertySchemasIndex.js';
@@ -35,7 +35,7 @@ function addChangedKeysToOp(op, keys, original, changed) {
// Loop through all keys that can be changed by computation
// and compile an operation that sets all those keys
for (let key of keys){
if (!isEqual(original[key], changed[key])){
if (!EJSON.equals(original[key], changed[key])){
if (!op) op = newOperation(original._id, changed.type);
let value = changed[key];
if (value === undefined){
@@ -91,7 +91,7 @@ function writePropertiesSequentially(bulkWriteOps){
bypassCollection2: true,
});
});
if (bulkWriteOps.length) console.log(`Wrote ${bulkWriteOps.length} props`);
//if (bulkWriteOps.length) console.log(`Wrote ${bulkWriteOps.length} props`);
}
// This is more efficient on the database, but significantly less efficient