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 @@
+
+