diff --git a/app/imports/api/creature/creatureProperties/CreatureProperties.js b/app/imports/api/creature/creatureProperties/CreatureProperties.js index cccf715c..33183ddc 100644 --- a/app/imports/api/creature/creatureProperties/CreatureProperties.js +++ b/app/imports/api/creature/creatureProperties/CreatureProperties.js @@ -82,14 +82,6 @@ const DenormalisedOnlyCreaturePropertySchema = new SimpleSchema({ index: 1, removeBeforeCompute: true, }, - // Dependency tree, the ID of the lowest ordered doc connected to this doc - // via dependencies - depGroupId: { - type: String, - regEx: SimpleSchema.RegEx.Id, - index: 1, - removeBeforeCompute: true, - }, }); CreaturePropertySchema.extend(DenormalisedOnlyCreaturePropertySchema); diff --git a/app/imports/api/creature/creatureProperties/methods/damageProperty.js b/app/imports/api/creature/creatureProperties/methods/damageProperty.js index 2c648c9c..51c37749 100644 --- a/app/imports/api/creature/creatureProperties/methods/damageProperty.js +++ b/app/imports/api/creature/creatureProperties/methods/damageProperty.js @@ -4,7 +4,7 @@ import SimpleSchema from 'simpl-schema'; import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; import getRootCreatureAncestor from '/imports/api/creature/creatureProperties/getRootCreatureAncestor.js'; import { assertEditPermission } from '/imports/api/sharing/sharingPermissions.js'; -import computeCreature, { computeCreatureDependencyGroup } from '/imports/api/engine/computeCreature.js'; +import computeCreature from '/imports/api/engine/computeCreature.js'; const damageProperty = new ValidatedMethod({ name: 'creatureProperties.damage', @@ -38,12 +38,7 @@ const damageProperty = new ValidatedMethod({ ); } let result = damagePropertyWork({ property, operation, value }); - if (property.depGroupId) { - // Dependencies can't be changed through damage, only recompute deps - computeCreatureDependencyGroup([property.depGroupId], rootCreature._id); - } else { - computeCreature(rootCreature._id); - } + computeCreature(rootCreature._id); return result; }, }); diff --git a/app/imports/api/engine/computation/buildCreatureComputation.js b/app/imports/api/engine/computation/buildCreatureComputation.js index f86e610b..f2b07158 100644 --- a/app/imports/api/engine/computation/buildCreatureComputation.js +++ b/app/imports/api/engine/computation/buildCreatureComputation.js @@ -2,6 +2,7 @@ import { nodeArrayToTree } from '/imports/api/parenting/nodesToTree.js'; import CreatureProperties, { DenormalisedOnlyCreaturePropertySchema as denormSchema } from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import { loadedCreatures } from '../loadCreatures.js'; import Creatures from '/imports/api/creature/creatures/Creatures.js'; import computedOnlySchemas from '/imports/api/properties/computedOnlyPropertySchemasIndex.js'; import computedSchemas from '/imports/api/properties/computedPropertySchemasIndex.js'; @@ -15,7 +16,6 @@ import linkTypeDependencies from './buildComputation/linkTypeDependencies.js'; import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled.js'; import CreatureComputation from './CreatureComputation.js'; import removeSchemaFields from './buildComputation/removeSchemaFields.js'; -import assignDependencyGroups from '/imports/api/engine/computation/utility/assignDependencyGroups.js'; /** * Store index of properties @@ -38,35 +38,22 @@ export default function buildCreatureComputation(creatureId){ return computation; } -export function buildDependencyGroupComputation(depGroupIds, creatureId) { - const creature = getCreature(creatureId); - const properties = getGroupProperties(depGroupIds); - const computation = buildComputationFromProps(properties, creature); - return computation; -} - function getProperties(creatureId) { - return CreatureProperties.find({ + if (loadedCreatures.has(creatureId)) { + const creature = loadedCreatures.get(creatureId); + const props = Array.from(creature.properties.values()); + return props; + } + console.time(`fetching from db: ${creatureId}`) + const props = CreatureProperties.find({ 'ancestors.id': creatureId, 'removed': {$ne: true}, }, { sort: { order: 1 }, fields: { icon: 0 }, }).fetch(); -} - -function getGroupProperties(depGroupIds) { - console.log({ depGroupIds }); - if (!depGroupIds || depGroupIds.includes(undefined)) { - throw `Expected array full of ids, got ${depGroupIds}` - } - return CreatureProperties.find({ - depGroupId: { $in: depGroupIds }, - 'removed': { $ne: true }, - }, { - sort: { order: 1 }, - fields: { icon: 0 }, - }).fetch(); + console.timeEnd(`fetching from db: ${creatureId}`); + return props; } function getCreature(creatureId){ @@ -138,8 +125,5 @@ export function buildComputationFromProps(properties, creature){ linkCalculationDependencies(dependencyGraph, prop, computation); }); - // Store the connected groups of the dependency graph - assignDependencyGroups(dependencyGraph); - return computation; } diff --git a/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js b/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js index 20703856..ccf8246e 100644 --- a/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js +++ b/app/imports/api/engine/computation/writeComputation/writeAlteredProperties.js @@ -20,7 +20,6 @@ export default function writeAlteredProperties(computation){ 'deactivatedBySelf', 'deactivatedByAncestor', 'deactivatedByToggle', - 'depGroupId', 'damage', ...schema.objectKeys(), ]; diff --git a/app/imports/api/engine/computeCreature.js b/app/imports/api/engine/computeCreature.js index 55de9359..84c90e8a 100644 --- a/app/imports/api/engine/computeCreature.js +++ b/app/imports/api/engine/computeCreature.js @@ -1,4 +1,4 @@ -import buildCreatureComputation, { buildDependencyGroupComputation } from './computation/buildCreatureComputation.js'; +import buildCreatureComputation from './computation/buildCreatureComputation.js'; import computeCreatureComputation from './computation/computeCreatureComputation.js'; import writeAlteredProperties from './computation/writeComputation/writeAlteredProperties.js'; import writeScope from './computation/writeComputation/writeScope.js'; @@ -6,14 +6,10 @@ import writeErrors from './computation/writeComputation/writeErrors.js'; export default function computeCreature(creatureId){ if (Meteor.isClient) return; + console.time('Compute'); const computation = buildCreatureComputation(creatureId); computeComputation(computation, creatureId); -} - -// Recompute only some groups of the dependency tree -export function computeCreatureDependencyGroup(depGroupIds, creatureId) { - const computation = buildDependencyGroupComputation(depGroupIds, creatureId); - computeComputation(computation, creatureId); + console.timeEnd('Compute'); } function computeComputation(computation, creatureId) { diff --git a/app/imports/api/engine/loadCreatures.js b/app/imports/api/engine/loadCreatures.js new file mode 100644 index 00000000..46090140 --- /dev/null +++ b/app/imports/api/engine/loadCreatures.js @@ -0,0 +1,117 @@ +import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js'; +import Creatures from '/imports/api/creature/creatures/Creatures.js'; + +export const loadedCreatures = new Map(); // creatureId => {creature, properties, etc.} + +export function loadCreature(creatureId, subscription) { + if (!creatureId) throw 'creatureId is required'; + let creature = loadedCreatures.get(creatureId); + if (loadedCreatures.has(creatureId)) { + creature.subs.add(subscription); + } else { + console.time(`loading to memory ${creatureId}`); + creature = new LoadedCreature(subscription, creatureId); + loadedCreatures.set(creatureId, creature); + console.timeEnd(`loading to memory ${creatureId}`); + console.log('Creatures in memory: ', loadedCreatures.size); + } + subscription.onStop(() => { + unloadCreature(creatureId, subscription); + }); +} + +function unloadCreature(creatureId, subscription) { + if (!creatureId) throw 'creatureId is required'; + const creature = loadedCreatures.get(creatureId); + if (!creature) return; + creature.subs.delete(subscription); + if (creature.subs.size === 0) { + creature.stop(); + loadedCreatures.delete(creatureId); + } + console.log('Creatures in memory: ', loadedCreatures.size); +} + +class LoadedCreature { + constructor(sub, creatureId) { + // This may be called from a subscription, but we don't want the observers + // to be destroyed with it, so use a non-reactive context to observe + // the required documents + const self = this; + Tracker.nonreactive(() => { + + self.subs = new Set([sub]); + + self.properties = new Map(); + // Observe all creature properties which are needed for computation + self.propertyObserver = CreatureProperties.find({ + 'ancestors.id': creatureId, + removed: { $ne: true }, + }, { + // sort: { order: 1 }, + fields: { icon: 0 }, + }).observeChanges({ + added(id, fields) { + fields._id = id; + return self.addProperty(fields) + }, + changed(id, fields) { + return self.changeProperty(id, fields); + }, + removed(id) { + return self.removeProperty(id); + }, + }); + + self.creatures = new Map(); + // Observe the creature itself + self.creatureObserver = Creatures.find({ + _id: creatureId, + }).observeChanges({ + added(id, fields) { + fields._id = id; + self.addCreature(fields) + }, + changed(id, fields) { + return self.changeCreature(id, fields); + }, + removed(id) { + return self.removeCreature(id); + }, + }); + + }); + } + stop() { + this.creatureObserver.stop(); + this.propertyObserver.stop(); + } + addProperty(prop) { + this.properties.set(prop._id, prop); + } + addCreature(creature) { + this.creatures.set(creature._id, creature); + } + changeProperty(id, fields) { + this.changeMap(id, fields, this.properties); + } + changeCreature(id, fields) { + this.changeMap(id, fields, this.creatures); + } + changeMap(id, fields, map) { + const doc = map.get(id); + for (let key in fields) { + if (key === undefined) { + delete doc[key]; + } else { + doc[key] = fields[key]; + } + } + } + removeProperty(id) { + this.properties.delete(id) + } + removeCreature(id) { + this.creatures.delete(id) + } +} diff --git a/app/imports/parser/TextField.vue b/app/imports/parser/TextField.vue deleted file mode 100644 index 9c9f0919..00000000 --- a/app/imports/parser/TextField.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - - - diff --git a/app/imports/server/publications/singleCharacter.js b/app/imports/server/publications/singleCharacter.js index 611f1d23..c2ca8c0c 100644 --- a/app/imports/server/publications/singleCharacter.js +++ b/app/imports/server/publications/singleCharacter.js @@ -5,6 +5,7 @@ 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 VERSION from '/imports/constants/VERSION.js'; +import { loadCreature } from '/imports/api/engine/loadCreatures.js'; let schema = new SimpleSchema({ creatureId: { @@ -13,7 +14,8 @@ let schema = new SimpleSchema({ }, }); -Meteor.publish('singleCharacter', function(creatureId){ +Meteor.publish('singleCharacter', function (creatureId) { + const self = this; try { schema.validate({ creatureId }); } catch (e){ @@ -21,21 +23,24 @@ Meteor.publish('singleCharacter', function(creatureId){ } this.autorun(function (computation){ let userId = this.userId; - let creatureCursor - creatureCursor = Creatures.find({ + let permissionCreature = Creatures.findOne({ _id: creatureId, + }, { + fields: { owner: 1, readers: 1, writers: 1, public: 1, computeVersion: 1 } }); - let creature = creatureCursor.fetch()[0]; - try { assertViewPermission(creature, userId) } - catch(e){ return [] } - if (creature.computeVersion !== VERSION && computation.firstRun){ + try { assertViewPermission(permissionCreature, userId) } + catch (e) { return [] } + loadCreature(creatureId, self); + if (permissionCreature.computeVersion !== VERSION && computation.firstRun){ try { computeCreature(creatureId) } catch(e){ console.error(e) } } return [ - creatureCursor, + Creatures.find({ + _id: creatureId, + }), CreatureProperties.find({ 'ancestors.id': creatureId, }),