The type system starts to infect the computation engine

This commit is contained in:
Thaum Rystra
2025-01-15 18:36:26 +02:00
parent 12789f1c5e
commit 15ecc05e21
10 changed files with 106 additions and 148 deletions

View File

@@ -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;
});
}
}

View File

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

View File

@@ -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 => {