mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 08:25:19 +01:00
297 lines
No EOL
7.3 KiB
JavaScript
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; |