The type system starts to infect the computation engine
This commit is contained in:
@@ -1,40 +1,53 @@
|
||||
import { EJSON } from 'meteor/ejson';
|
||||
import createGraph, { Graph } from 'ngraph.graph';
|
||||
import getEffectivePropTags from '/imports/api/engine/computation/utility/getEffectivePropTags';
|
||||
import type { Creature } from '/imports/api/creature/creatures/Creatures';
|
||||
import type { CreatureProperty } from '/imports/api/creature/creatureProperties/CreatureProperties';
|
||||
|
||||
interface CreatureProperty {
|
||||
_id: string;
|
||||
type: string;
|
||||
}
|
||||
type ComputationProperty = CreatureProperty & {
|
||||
_computationDetails: {
|
||||
calculations: any[],
|
||||
emptyCalculations: any[],
|
||||
inlineCalculations: any[],
|
||||
toggleAncestors: any[],
|
||||
}
|
||||
};
|
||||
|
||||
export default class CreatureComputation {
|
||||
originalPropsById: Record<string, CreatureProperty>;
|
||||
propsById: Record<string, CreatureProperty>;
|
||||
propsWithTag: Record<string, string[]>;
|
||||
scope: Record<string, any>;
|
||||
props: Array<CreatureProperty>;
|
||||
props: ComputationProperty[];
|
||||
dependencyGraph: Graph<any, string>;
|
||||
errors: Array<object>;
|
||||
creature: object;
|
||||
creature: Creature;
|
||||
variables: object;
|
||||
|
||||
constructor(properties: Array<CreatureProperty>, creature: object, variables: object) {
|
||||
constructor(properties: Array<CreatureProperty>, creature: Creature, variables: object) {
|
||||
// Set up fields
|
||||
this.originalPropsById = {};
|
||||
this.propsById = {};
|
||||
this.propsWithTag = {};
|
||||
this.scope = {};
|
||||
this.props = properties;
|
||||
this.dependencyGraph = createGraph();
|
||||
this.errors = [];
|
||||
this.creature = creature;
|
||||
this.variables = variables;
|
||||
|
||||
// Store properties for easy access later
|
||||
properties.forEach(prop => {
|
||||
// Store properties and index for easy access later
|
||||
this.props = properties.map(originalProp => {
|
||||
const prop: ComputationProperty = Object.assign(EJSON.clone(originalProp), {
|
||||
_computationDetails: {
|
||||
calculations: [],
|
||||
emptyCalculations: [],
|
||||
inlineCalculations: [],
|
||||
toggleAncestors: [],
|
||||
}
|
||||
});
|
||||
// Store a copy of the unmodified prop
|
||||
// EJSON clone is ~4x faster than lodash cloneDeep for EJSONable objects
|
||||
this.originalPropsById[prop._id] = EJSON.clone(prop);
|
||||
this.originalPropsById[prop._id] = originalProp;
|
||||
// Store by id
|
||||
this.propsById[prop._id] = prop;
|
||||
|
||||
@@ -50,6 +63,8 @@ export default class CreatureComputation {
|
||||
|
||||
// Store the prop in the dependency graph
|
||||
this.dependencyGraph.addNode(prop._id, prop);
|
||||
|
||||
return prop;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import linkTypeDependencies from './buildComputation/linkTypeDependencies';
|
||||
import computeSlotQuantityFilled from './buildComputation/computeSlotQuantityFilled';
|
||||
import CreatureComputation from './CreatureComputation';
|
||||
import removeSchemaFields from './buildComputation/removeSchemaFields';
|
||||
import type { Creature } from '/imports/api/creature/creatures/Creatures';
|
||||
|
||||
/**
|
||||
* Store index of properties
|
||||
@@ -31,6 +32,7 @@ import removeSchemaFields from './buildComputation/removeSchemaFields';
|
||||
|
||||
export default function buildCreatureComputation(creatureId: string) {
|
||||
const creature = getCreature(creatureId);
|
||||
if (!creature) return;
|
||||
const variables = getVariables(creatureId);
|
||||
const properties = getProperties(creatureId);
|
||||
const computation = buildComputationFromProps(properties, creature, variables);
|
||||
@@ -38,7 +40,7 @@ export default function buildCreatureComputation(creatureId: string) {
|
||||
}
|
||||
|
||||
export function buildComputationFromProps(
|
||||
properties: CreatureProperty[], creature, variables
|
||||
properties: CreatureProperty[], creature: Creature, variables: any
|
||||
) {
|
||||
|
||||
const computation = new CreatureComputation(properties, creature, variables);
|
||||
@@ -67,24 +69,15 @@ export function buildComputationFromProps(
|
||||
}
|
||||
|
||||
// Process the properties one by one
|
||||
properties.forEach(prop => {
|
||||
computation.props.forEach(prop => {
|
||||
// The prop has been processed, it's no longer dirty
|
||||
delete prop.dirty;
|
||||
|
||||
const computedSchema = computedOnlySchemas[prop.type];
|
||||
removeSchemaFields([computedSchema, denormSchema], prop);
|
||||
|
||||
// Add a place to store all the computation details
|
||||
prop._computationDetails = {
|
||||
calculations: [],
|
||||
emptyCalculations: [],
|
||||
inlineCalculations: [],
|
||||
toggleAncestors: [],
|
||||
};
|
||||
|
||||
// Parse all the calculations
|
||||
parseCalculationFields(prop, computedSchemas);
|
||||
|
||||
});
|
||||
|
||||
// Get all the properties as a forest, with their nested set properties set
|
||||
|
||||
@@ -3,13 +3,20 @@ import computeByType from '/imports/api/engine/computation/computeComputation/co
|
||||
import embedInlineCalculations from './utility/embedInlineCalculations';
|
||||
import { removeEmptyCalculations } from './buildComputation/parseCalculationFields';
|
||||
import path from 'ngraph.path';
|
||||
import type CreatureComputation from './CreatureComputation';
|
||||
import type { Graph, Node, NodeId } from 'ngraph.graph';
|
||||
|
||||
export default async function computeCreatureComputation(computation) {
|
||||
const stack = [];
|
||||
type TraversedNode = Node<any> & {
|
||||
_visited?: boolean,
|
||||
_visitedChildren?: boolean,
|
||||
}
|
||||
|
||||
export default async function computeCreatureComputation(computation: CreatureComputation) {
|
||||
const stack: (TraversedNode)[] = [];
|
||||
// Computation scope of {variableName: prop}
|
||||
const graph = computation.dependencyGraph;
|
||||
// Add all nodes to the stack
|
||||
graph.forEachNode(node => {
|
||||
graph.forEachNode((node: TraversedNode) => {
|
||||
node._visited = false;
|
||||
node._visitedChildren = false;
|
||||
stack.push(node)
|
||||
@@ -22,7 +29,7 @@ export default async function computeCreatureComputation(computation) {
|
||||
|
||||
// Depth first traversal of nodes
|
||||
while (stack.length) {
|
||||
let top = stack[stack.length - 1];
|
||||
const top = stack[stack.length - 1];
|
||||
if (top._visited) {
|
||||
// The object has already been computed, skip
|
||||
stack.pop();
|
||||
@@ -45,21 +52,21 @@ export default async function computeCreatureComputation(computation) {
|
||||
}
|
||||
}
|
||||
|
||||
async function compute(computation, node) {
|
||||
async function compute(computation: CreatureComputation, node: TraversedNode) {
|
||||
// Determine the prop's active status by its toggles
|
||||
computeToggles(computation, node);
|
||||
// Compute the property by type
|
||||
await computeByType[node.data?.type || '_variable']?.(computation, node);
|
||||
}
|
||||
|
||||
function pushDependenciesToStack(nodeId, graph, stack, computation) {
|
||||
graph.forEachLinkedNode(nodeId, linkedNode => {
|
||||
function pushDependenciesToStack(nodeId: NodeId, graph: Graph, stack: TraversedNode[], computation: CreatureComputation) {
|
||||
graph.forEachLinkedNode(nodeId, (linkedNode: TraversedNode) => {
|
||||
if (linkedNode._visitedChildren && !linkedNode._visited) {
|
||||
// This is a dependency loop, find a path from the node to itself
|
||||
// and store that path as a dependency loop error
|
||||
const pather = path.nba(graph, { oriented: true });
|
||||
let loop = [];
|
||||
// Pather doesn't like going from a node to iteself, so find all the
|
||||
const loop: TraversedNode[] = [];
|
||||
// Pather doesn't like going from a node to itself, so find all the
|
||||
// paths going from the next node back to the original node
|
||||
// and return the shortest one
|
||||
graph.forEachLinkedNode(nodeId, nextNode => {
|
||||
Reference in New Issue
Block a user