diff --git a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js index 5d0bbfb4..d53ddc6e 100644 --- a/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/updateCreatureProperty.js @@ -3,7 +3,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; -import { computeCreature } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; const updateCreatureProperty = new ValidatedMethod({ name: 'creatureProperties.update', diff --git a/app/imports/api/creature/creatureProperties/recomputeCreaturesByProperty.js b/app/imports/api/creature/creatureProperties/recomputeCreaturesByProperty.js index ec8fb927..cee1237d 100644 --- a/app/imports/api/creature/creatureProperties/recomputeCreaturesByProperty.js +++ b/app/imports/api/creature/creatureProperties/recomputeCreaturesByProperty.js @@ -1,4 +1,4 @@ -import { computeCreature } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; /** * Recomputes all ancestor creatures of this property diff --git a/app/imports/api/creature/creatures/methods/restCreature.js b/app/imports/api/creature/creatures/methods/restCreature.js index a82f331b..07a607ee 100644 --- a/app/imports/api/creature/creatures/methods/restCreature.js +++ b/app/imports/api/creature/creatures/methods/restCreature.js @@ -4,7 +4,7 @@ import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import Creatures from '/imports/api/creature/creatures/Creatures.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; -import { computeCreature } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; const restCreature = new ValidatedMethod({ name: 'creature.methods.longRest', diff --git a/app/imports/api/creature/experience/Experiences.js b/app/imports/api/creature/experience/Experiences.js index 89304e2b..5f3a8ea2 100644 --- a/app/imports/api/creature/experience/Experiences.js +++ b/app/imports/api/creature/experience/Experiences.js @@ -3,7 +3,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method'; import { RateLimiterMixin } from 'ddp-rate-limiter-mixin'; import { assertEditPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; import Creatures from '/imports/api/creature/creatures/Creatures.js'; -import { computeCreature } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; import STORAGE_LIMITS from '/imports/constants/STORAGE_LIMITS.js'; let Experiences = new Mongo.Collection('experiences'); diff --git a/app/imports/api/creature/mixins/recomputeCreatureMixin.js b/app/imports/api/creature/mixins/recomputeCreatureMixin.js index 5aaf87ae..a6b991a7 100644 --- a/app/imports/api/creature/mixins/recomputeCreatureMixin.js +++ b/app/imports/api/creature/mixins/recomputeCreatureMixin.js @@ -1,4 +1,4 @@ -import { computeCreature } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; export default function recomputeCreatureMixin(methodOptions){ let runFunc = methodOptions.run; diff --git a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js index 6c55e290..518132fc 100644 --- a/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js +++ b/app/imports/api/engine/computation/buildComputation/parseCalculationFields.js @@ -22,6 +22,7 @@ function discoverInlineCalculationFields(prop, schemas){ 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){ diff --git a/app/imports/api/engine/computation/buildCreatureComputation.js b/app/imports/api/engine/computation/buildCreatureComputation.js index af4d7fbb..d1160ee8 100644 --- a/app/imports/api/engine/computation/buildCreatureComputation.js +++ b/app/imports/api/engine/computation/buildCreatureComputation.js @@ -45,7 +45,7 @@ function getProperties(creatureId){ 'removed': {$ne: true}, }, { sort: {order: 1} - }); + }).fetch(); } export function buildComputationFromProps(properties){ diff --git a/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js b/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js new file mode 100644 index 00000000..408d1a34 --- /dev/null +++ b/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js @@ -0,0 +1,117 @@ +import { Meteor } from 'meteor/meteor' +import { isEqual, forOwn } from 'lodash'; +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import propertySchemasIndex from '/imports/api/properties/computedOnlyPropertySchemasIndex.js'; + +export default function writeAlteredProperties(computation){ + let bulkWriteOperations = []; + // Loop through all properties on the memo + forOwn(computation.propsById, changed => { + let schema = propertySchemasIndex[changed.type]; + if (!schema){ + console.warn('No schema for ' + changed.type); + return; + } + let id = changed._id; + let op = undefined; + let original = computation.originalPropsById[id]; + let keys = [ + 'inactive', + 'deactivatedBySelf', + 'deactivatedByAncestor', + 'deactivatedByToggle', + 'damage', + ...schema.objectKeys(), + ]; + op = addChangedKeysToOp(op, keys, original, changed); + if (op){ + bulkWriteOperations.push(op); + } + }); + writePropertiesSequentially(bulkWriteOperations); +} + +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 (!op) op = newOperation(original._id, changed.type); + let value = changed[key]; + if (value === undefined){ + // Unset values that become undefined + addUnsetOp(op, key); + } else { + // Set values that changed to something else + addSetOp(op, key, value); + } + } + } + return op; +} + +function newOperation(_id, type){ + let newOp = { + updateOne: { + filter: {_id}, + update: {}, + } + }; + if (Meteor.isClient){ + newOp.type = type; + } + return newOp; +} + +function addSetOp(op, key, value){ + if (op.updateOne.update.$set){ + op.updateOne.update.$set[key] = value; + } else { + op.updateOne.update.$set = {[key]: value}; + } +} + +function addUnsetOp(op, key){ + if (op.updateOne.update.$unset){ + op.updateOne.update.$unset[key] = 1; + } else { + op.updateOne.update.$unset = {[key]: 1}; + } +} + +// We use this instead of bulkWriteProperties because it functions with latency +// compensation without needing to roll back changes, which causes multiple +// expensive redraws of the character sheet +function writePropertiesSequentially(bulkWriteOps){ + bulkWriteOps.forEach(op => { + let updateOneOrMany = op.updateOne || op.updateMany; + CreatureProperties.update(updateOneOrMany.filter, updateOneOrMany.update, { + // The bulk code is bypassing validation, so do the same here + // selector: {type: op.type} // include this if bypass is off + bypassCollection2: true, + }); + }); +} + +// This is more efficient on the database, but significantly less efficient +// in the UI because of incompatibility with latency compensation. If the +// duplicate redraws can be fixed, this is a strictly better way of processing +// writes +function bulkWriteProperties(bulkWriteOps){ + if (!bulkWriteOps.length) return; + // bulkWrite is only available on the server + if (Meteor.isServer){ + CreatureProperties.rawCollection().bulkWrite( + bulkWriteOps, + {ordered : false}, + function(e){ + if (e) { + console.error('Bulk write failed: '); + console.error(e); + } + } + ); + } else { + writePropertiesSequentially(bulkWriteOps); + } +} diff --git a/app/imports/api/engine/computeCreature.js b/app/imports/api/engine/computeCreature.js index 2a5192d5..9d404289 100644 --- a/app/imports/api/engine/computeCreature.js +++ b/app/imports/api/engine/computeCreature.js @@ -1,17 +1,16 @@ import buildCreatureComputation from './computation/buildCreatureComputation.js'; import computeCreatureComputation from './computation/computeCreatureComputation.js'; +import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties.js'; export default function computeCreature(creatureId){ const computation = buildCreatureComputation(creatureId); computeCreatureComputation(computation); - // TODO: writeCreatureComputation(computation); + writeAlteredProperties(computation); } -// For now just recompute the whole creature, later only recompute a single +// For now just recompute the whole creature, TODO only recompute a single // connected section of the depdendency graph export function computeCreatureDependencyGroup(property){ let creatureId = property.ancestors[0].id; - const computation = buildCreatureComputation(creatureId); - computeCreatureComputation(computation); - // TODO: writeCreatureComputation(computation); + computeCreature(creatureId); } diff --git a/app/imports/api/engine/oldActions/applyAdjustment.js b/app/imports/api/engine/oldActions/applyAdjustment.js index 5c106a73..5e0f50a3 100644 --- a/app/imports/api/engine/oldActions/applyAdjustment.js +++ b/app/imports/api/engine/oldActions/applyAdjustment.js @@ -1,4 +1,4 @@ -import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; import damagePropertiesByName from '/imports/api/creature/creatureProperties/methods/damagePropertiesByName.js'; export default function applyAdjustment({ diff --git a/app/imports/api/engine/oldActions/applyDamage.js b/app/imports/api/engine/oldActions/applyDamage.js index 04ca05d2..0e253050 100644 --- a/app/imports/api/engine/oldActions/applyDamage.js +++ b/app/imports/api/engine/oldActions/applyDamage.js @@ -1,4 +1,4 @@ -import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; import dealDamage from '/imports/api/creature/creatureProperties/methods/dealDamage.js'; import {insertCreatureLog} from '/imports/api/creature/log/CreatureLogs.js'; import { CompilationContext } from '/imports/parser/parser.js'; diff --git a/app/imports/api/engine/oldActions/applyRoll.js b/app/imports/api/engine/oldActions/applyRoll.js index 8653ba7a..b16240e3 100644 --- a/app/imports/api/engine/oldActions/applyRoll.js +++ b/app/imports/api/engine/oldActions/applyRoll.js @@ -1,4 +1,4 @@ -import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; export default function applyRoll({ prop, diff --git a/app/imports/api/engine/oldActions/applySave.js b/app/imports/api/engine/oldActions/applySave.js index bb6ce0d3..99b753b1 100644 --- a/app/imports/api/engine/oldActions/applySave.js +++ b/app/imports/api/engine/oldActions/applySave.js @@ -1,4 +1,4 @@ -import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; import CreaturesProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import roll from '/imports/parser/roll.js'; diff --git a/app/imports/api/engine/oldActions/applyToggle.js b/app/imports/api/engine/oldActions/applyToggle.js index 79d222ed..745d9edd 100644 --- a/app/imports/api/engine/oldActions/applyToggle.js +++ b/app/imports/api/engine/oldActions/applyToggle.js @@ -1,4 +1,4 @@ -import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; export default function applyToggle({ prop, diff --git a/app/imports/api/parenting/organizeMethods.js b/app/imports/api/parenting/organizeMethods.js index e67fdc45..f104154c 100644 --- a/app/imports/api/parenting/organizeMethods.js +++ b/app/imports/api/parenting/organizeMethods.js @@ -8,7 +8,7 @@ import { RefSchema } from '/imports/api/parenting/ChildSchema.js'; import { assertDocEditPermission } from '/imports/api/sharing/sharingPermissions.js'; import fetchDocByRef from '/imports/api/parenting/fetchDocByRef.js'; import getCollectionByName from '/imports/api/parenting/getCollectionByName.js'; -import { computeCreature } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; const organizeDoc = new ValidatedMethod({ name: 'organize.organizeDoc', diff --git a/app/imports/server/publications/singleCharacter.js b/app/imports/server/publications/singleCharacter.js index d879de52..ce1257fb 100644 --- a/app/imports/server/publications/singleCharacter.js +++ b/app/imports/server/publications/singleCharacter.js @@ -3,7 +3,7 @@ import Creatures from '/imports/api/creature/creatures/Creatures.js'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js'; import { assertViewPermission } from '/imports/api/creature/creatures/creaturePermissions.js'; -import { computeCreature } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; import VERSION from '/imports/constants/VERSION.js'; let schema = new SimpleSchema({ diff --git a/app/imports/ui/components/computation/EmbedInlineComputations.vue b/app/imports/ui/components/computation/EmbedInlineComputations.vue deleted file mode 100644 index c624c566..00000000 --- a/app/imports/ui/components/computation/EmbedInlineComputations.vue +++ /dev/null @@ -1,50 +0,0 @@ - - - - - diff --git a/app/imports/ui/creature/slots/SlotFillDialog.vue b/app/imports/ui/creature/slots/SlotFillDialog.vue index 40b2585b..d3c86223 100644 --- a/app/imports/ui/creature/slots/SlotFillDialog.vue +++ b/app/imports/ui/creature/slots/SlotFillDialog.vue @@ -187,7 +187,7 @@ import LibraryNodes from '/imports/api/library/LibraryNodes.js'; import DialogBase from '/imports/ui/dialogStack/DialogBase.vue'; import TreeNodeView from '/imports/ui/properties/treeNodeViews/TreeNodeView.vue'; import PropertyDescription from '/imports/ui/properties/viewers/shared/PropertyDescription.vue' -import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +// import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js' import Libraries from '/imports/api/library/Libraries.js'; import LibraryNodeExpansionContent from '/imports/ui/library/LibraryNodeExpansionContent.vue'; diff --git a/app/imports/ui/properties/components/features/FeatureCard.vue b/app/imports/ui/properties/components/features/FeatureCard.vue index 79062699..b4d086ea 100644 --- a/app/imports/ui/properties/components/features/FeatureCard.vue +++ b/app/imports/ui/properties/components/features/FeatureCard.vue @@ -11,10 +11,8 @@ - @@ -22,12 +20,12 @@ + +