jsketcher/web/app/sketcher/system.js
2022-08-15 23:47:20 -07:00

297 lines
No EOL
7.3 KiB
JavaScript

import {addToSetInMap, removeFromSetInMap, removeInPlace} from 'gems/iterables';
import {ParametricManager} from './parametric';
let SUB_SYSTEM_ORDER = 0;
class SubSystem {
constructor() {
this.alg = 1;
this.error = 0;
this.reduce = false;
this.constraints = [];
this.dependencies = [];
this.nativeParams = new Set();
this._internaOrder = SUB_SYSTEM_ORDER++;
}
mergeWith(other) {
other.constraints.forEach(c => this.constraints.push(c));
other.dependencies.forEach(d => {
if (this.dependencies.indexOf(d) === -1) {
this.dependencies.push(d);
}
});
other.nativeParams.forEach(p => this.nativeParams.add(p));
}
prepare() {
}
}
class Index {
constructor() {
this.constraints = [];
this.paramToConstraintsIndex = new Map();
this.paramToConstraintsGraph = new Map();
this.generatorConstraints = [];
this.generatedParams = new Map();
}
_reset() {
this.constraints = [];
this.paramToConstraintsIndex.clear();
this.paramToConstraintsGraph.clear();
this.generatorConstraints = [];
this.generatedParams.clear();
}
_pushConstraint(constr) {
this.constraints.push(constr);
visitParams(constr, true, p => addToSetInMap(this.paramToConstraintsGraph, p, constr));
visitParams(constr, false, p => addToSetInMap(this.paramToConstraintsIndex, p, constr));
if (constr.GENERATOR) {
this.generatorConstraints.push(constr);
constr.visitGeneratedParams(p => this.generatedParams.set(p, constr));
}
}
_popConstraint(constr) {
removeInPlace(this.constraints, constr);
visitParams(constr, true, p => removeFromSetInMap(this.paramToConstraintsGraph, p, constr));
visitParams(constr, false, p => removeFromSetInMap(this.paramToConstraintsIndex, p, constr));
if (constr.GENERATOR) {
removeInPlace(this.generatorConstraints, constr);
constr.visitGeneratedParams(p => this.generatedParams.delete(p));
}
}
}
export class System extends Index{
constructor() {
super();
this.subSystems = [];
this.constraintToSubSystem = new Map();
this.paramToSubSystem = new Map();
}
_reset() {
super._reset();
this.subSystems = [];
this.constraintToSubSystem.clear();
this.paramToSubSystem.clear();
COUNTER = 0;
}
_collectDependenciesForSubSystemFromConstraint(subSystem, constr) {
visitParams(constr, false, p => {
const generator = this.generatedParams.get(p);
if (generator) {
const generatorSS = this.constraintToSubSystem.get(generator);
if (generatorSS) {
if (subSystem.dependencies.indexOf(generatorSS) === -1) {
subSystem.dependencies.push(generatorSS);
}
}
}
});
}
_rebuildDependencies() {
this.subSystems.forEach(ss => {
if (ss.dependencies.length !== 0) {
ss.dependencies = [];
}
});
this.subSystems.forEach(subSystem => {
subSystem.constraints.forEach(constr => {
this._collectDependenciesForSubSystemFromConstraint(subSystem, constr);
});
});
}
_groupBySubsystems() {
if (this.subSystems.length !== 0) {
this.subSystems = [];
}
this.constraintToSubSystem.clear();
const visited = new Set();
this.constraints.forEach(constr => {
if (visited.has(constr)) {
return;
}
const subSystem = this.createSubSystem();
const stack = [constr];
while (stack.length) {
const workingConstr = stack.pop();
if (visited.has(workingConstr)) {
continue;
}
this._assignConstraint(workingConstr, subSystem);
visited.add(workingConstr);
visitParams(workingConstr, true, p => {
const constrs = this.paramToConstraintsGraph.get(p);
if (constrs) {
constrs.forEach(constrToAdvance => {
if (constrToAdvance !== workingConstr) {
stack.push(constrToAdvance);
}
})
}
});
}
});
}
_rebuild() {
this._groupBySubsystems();
this._rebuildDependencies();
}
_assignConstraint(constr, subSystem) {
subSystem.constraints.push(constr);
this.constraintToSubSystem.set(constr, subSystem);
}
add(constr) {
constr.id = "C_" + (COUNTER ++) ; //fixme
const affectedSubsystems = new Set();
const freeParams = [];
visitParams(constr, false, p => {
const subSystem = this.paramToSubSystem.get(p);
if (subSystem) {
affectedSubsystems.add(subSystem);
} else {
if (!isAuxParam(p) && !this.generatedParams.has(p)) {
freeParams.push(p);
}
}
});
affectedSubsystems.forEach(ss => {
ss.dependencies.forEach(d => affectedSubsystems.delete(d));
});
let toMerge = Array.from(affectedSubsystems).sort((a, b) => a._internaOrder - b._internaOrder);
let master;
if (toMerge.length === 0 ) {
// console.error("system has circular dependencies");
master = this.createSubSystem();
} else {
[master, ...toMerge] = toMerge;
}
toMerge.forEach(s => {
master.mergeWith(s);
s.nativeParams.forEach(p => this.paramToSubSystem.set(p, master));
removeInPlace(this.subSystems, s);
});
freeParams.forEach(p => {
master.nativeParams.add(p);
this.paramToSubSystem.set(p, master)
});
master.constraints.push(constr);
this.constraintToSubSystem.set(constr, master);
if (constr.GENERATOR) {
const dependant = this.createSubSystem();
dependant.dependencies.push(master);
constr.visitGeneratedParams(p => {
this.generatedParams.set(p, constr)
this.paramToSubSystem.set(p, dependant);
});
}
this._pushConstraint(constr);
}
remove(constr) {
removeInPlace(this.constraints, constr);
this.setConstraints(this.constraints);
}
setConstraints(constraints) {
this._reset();
constraints.forEach(c => this.add(c));
}
subSystemsByParam(param, callback) {
const constraints = this.paramToConstraintsIndex.get(param);
if (constraints) {
constraints.forEach(c => callback(this.constraintToSubSystem.get(c)));
}
}
traverse(callback, onCircular) {
const visited = new Set();
const loop = new Set();
function doVisit(subSystem) {
if (loop.has(subSystem)) {
onCircular(subSystem);
return;
}
loop.add(subSystem);
subSystem.dependencies.forEach(dep => {
if (!visited.has(dep)) {
doVisit(dep);
}
});
callback(subSystem);
visited.add(subSystem);
loop.delete(subSystem)
}
this.subSystems.forEach(doVisit);
}
createSubSystem() {
const subSystem = new SubSystem();
this.subSystems.push(subSystem);
return subSystem;
}
}
function visitParams(constraint, skipAux, callback) {
if (skipAux) {
const delegate = callback;
callback = p => {
if (!isAuxParam(p)) {
delegate(p)
}
};
}
if (constraint.visitParams) {
constraint.visitParams(callback);
} else {
constraint.getSolveData(FAKE_RESOLVER).forEach(([, sParams]) => sParams.forEach(callback));
}
}
function isAuxParam(param) {
return ParametricManager.isAux(param.obj, GOT_NOTHING);
}
const GOT_NOTHING = {
has: () => false
};
const FAKE_RESOLVER = () => 0;
let COUNTER = 0;