Skip to content
This repository has been archived by the owner on Jan 18, 2018. It is now read-only.

Commit

Permalink
refactor: rework algo, now using topo walk after grouping
Browse files Browse the repository at this point in the history
  • Loading branch information
qfox committed Feb 8, 2017
1 parent 1884260 commit 535f329
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 69 deletions.
65 changes: 2 additions & 63 deletions lib/bem-graph.js
Expand Up @@ -3,13 +3,9 @@
const debug = require('debug')('bem-graph');
const BemCell = require('@bem/cell');
const BemEntityName = require('@bem/entity-name');
const hoi = require('ho-iter');
const series = hoi.series;
const reverse = hoi.reverse;

const VertexSet = require('./vertex-set');
const MixedGraph = require('./mixed-graph');
const CircularDependencyError = require('./circular-dependency-error');
const resolve = require('./mixed-graph-resolve');

class BemGraph {
constructor() {
Expand Down Expand Up @@ -43,11 +39,7 @@ class BemGraph {
return res;
}, []);

// Recommended order
const _positions = vertices.reduce((res, e, pos) => { res[e.id] = pos; return res; }, {});
const _sort = (a, b) => _positions[a.id] - _positions[b.id];

const iter = series.apply(null, vertices.map(vertex => this._dependenciesOf(vertex, tech, _sort)));
const iter = resolve(this._mixedGraph, vertices, tech);
const arr = Array.from(iter);

// TODO: returns iterator
Expand All @@ -65,59 +57,6 @@ class BemGraph {
return obj;
}).filter(Boolean);
}
_dependenciesOf(startVertex, tech, backsort) {
const mixedGraph = this._mixedGraph;
const orderedSuccessors = [];
const unorderedSuccessors = new VertexSet();
let crumbs = [startVertex];

function step(fromVertex) {
const orderedDirectSuccessors = mixedGraph.directSuccessors(fromVertex, { ordered: true, tech });
const unorderedDirectSuccessors = mixedGraph.directSuccessors(fromVertex, { ordered: false, tech });

for (let successor of reverse(orderedDirectSuccessors)) {
if (successor.id === fromVertex.id) {
continue;
}

if (crumbs.indexOf(successor) !== -1) {
// TODO: check loop specs later please.
throw new CircularDependencyError(crumbs); // TODO: правильно считать цикл
}

orderedSuccessors.push(successor);
crumbs.push(successor);

step(successor);

crumbs.pop();
}

for (let successor of unorderedDirectSuccessors) {
if (successor.id === fromVertex.id ||
unorderedSuccessors.has(successor) ||
orderedSuccessors.indexOf(successor) !== -1) {
continue;
}

unorderedSuccessors.add(successor);

const _crumbs = crumbs;
crumbs = [successor];

step(successor);

crumbs = _crumbs;
}
}

step(startVertex);

const _orderedSuccessors = new VertexSet(orderedSuccessors.reverse());
const _unorderedSuccessors = backsort ? Array.from(unorderedSuccessors).sort(backsort) : unorderedSuccessors;

return series(_orderedSuccessors, [startVertex], _unorderedSuccessors);
}
naturalize() {
const mixedGraph = this._mixedGraph;

Expand Down
140 changes: 140 additions & 0 deletions lib/mixed-graph-resolve.js
@@ -0,0 +1,140 @@
'use strict';

const hoi = require('ho-iter');
const series = hoi.series;

const VertexSet = require('./vertex-set');
const CircularDependencyError = require('./circular-dependency-error');

module.exports = resolve;

class TopoGroups {
constructor() {
this._groups = [];
this._index = Object.create(null);
}
lookup(id) {
return this._index[id];
}
lookupCreate(id) {
let group = this.lookup(id);
if (!group) {
group = new Set([id]);
this._index[id] = group;
this._groups.push(group);
}
return group;
}
merge(vertexId, parentId) {
const parentGroup = this.lookupCreate(parentId);
const vertexGroup = this.lookup(vertexId);

if (parentGroup !== vertexGroup) {
for (let id of vertexGroup) {
this._index[id] = parentGroup;
vertexGroup.delete(id);
parentGroup.add(id);
}
}
}
}

function resolve(mixedGraph, startVertices, tech) {
const _positions = startVertices.reduce((res, e, pos) => { res[e.id] = pos; return res; }, {});
const backsort = (a, b) => _positions[a.id] - _positions[b.id];

const orderedSuccessors = []; // L ← Empty list that will contain the sorted nodes
const _orderedVisits = {}; // Hash with visiting flags: temporary - false, permanently - true
const unorderedSuccessors = new VertexSet(); // The rest nodes
let crumbs = [];
const topo = new TopoGroups();

// ... while there are unmarked nodes do
for (let v of startVertices) {
visit(v, false);
}

const _orderedSuccessors = Array.from(new VertexSet(orderedSuccessors.reverse()));
const _unorderedSuccessors = Array.from(unorderedSuccessors).sort(backsort);

return series(_orderedSuccessors, _unorderedSuccessors);

function visit(fromVertex, isWeak) {
// ... if n has a temporary mark then stop (not a DAG)
if (!isWeak && _orderedVisits[fromVertex.id] === false) {
if (crumbs.filter(c => (c.entity.id === fromVertex.entity.id) &&
(!c.tech || c.tech === fromVertex.tech)).length) {
throw new CircularDependencyError(crumbs.concat(fromVertex)); // TODO: правильно считать цикл
}
}

// ... if n is marked (i.e. has been visited yet)
if (_orderedVisits[fromVertex.id] !== undefined) {
// ... then already visited
return;
}

crumbs.push(fromVertex);

// ... else mark n temporarily.
_orderedVisits[fromVertex.id] = false;

topo.lookupCreate(fromVertex.id);

// ... for each node m with an edge from n to m do
const orderedDirectSuccessors = mixedGraph.directSuccessors(fromVertex, { ordered: true, tech });

for (let successor of orderedDirectSuccessors) {
// TODO: Try to filter loops earlier
if (successor.id === fromVertex.id) {
continue;
}

if (isWeak) {
// TODO: Try to speed up this slow piece of shit
const topogroup = topo.lookup(successor.id);
if (topogroup && !topogroup.has(fromVertex.id)) {
// Drop all entities for the current topogroup if came from unordered
for (let id of topo.lookup(successor.id)) {
_orderedVisits[id] = undefined;
}
}
}

// Add to topogroup for ordered dependencies to sort them later in groups
topo.merge(fromVertex.id, successor.id);

visit(successor, false);
}

// ... mark n permanently
// ... unmark n temporarily
_orderedVisits[fromVertex.id] = true;

// ... add n to head of L (L = ordered, or to tail of unordered)
isWeak
? unorderedSuccessors.add(fromVertex)
: orderedSuccessors.unshift(fromVertex);

const unorderedDirectSuccessors = mixedGraph.directSuccessors(fromVertex, { ordered: false, tech });

for (let successor of unorderedDirectSuccessors) {
// TODO: Try to filter loops earlier
if (successor.id === fromVertex.id ||
_orderedVisits[successor.id] ||
unorderedSuccessors.has(successor) ||
orderedSuccessors.indexOf(successor) !== -1) {
continue;
}

let _crumbs = crumbs;
crumbs = [];

visit(successor, true);

crumbs = _crumbs;
}

crumbs.pop();
}
}
9 changes: 6 additions & 3 deletions spec/loops/tech-loops.spec.js
Expand Up @@ -23,7 +23,8 @@ test('should throw error if detected ordered loop between same techs', t => {
t.deepEqual(error.loop, [
{ entity: { block: 'A' }/*, tech: 'css'*/ },
{ entity: { block: 'B' }, tech: 'css' },
{ entity: { block: 'A' }, tech: 'css' }
{ entity: { block: 'A' }, tech: 'css' },
{ entity: { block: 'B' }, tech: 'css' }
]);
}
});
Expand Down Expand Up @@ -61,7 +62,8 @@ test('should throw error if detected loop between common and specific techs', t
t.deepEqual(error.loop, [
{ entity: { block: 'A' } },
{ entity: { block: 'B' } },
{ entity: { block: 'A' }, tech: 'css' }
{ entity: { block: 'A' }, tech: 'css' },
{ entity: { block: 'B' } }
]);
}
});
Expand All @@ -85,7 +87,8 @@ test('should throw error if detected loop between common and other techs', t =>
t.deepEqual(error.loop, [
{ entity: { block: 'A' } },
{ entity: { block: 'B' } },
{ entity: { block: 'A' }, tech: 'css' }
{ entity: { block: 'A' }, tech: 'css' },
{ entity: { block: 'B' } }
]);
}
});
Expand Down
3 changes: 1 addition & 2 deletions spec/ordering-priority/ordered-vs-decl.spec.js
Expand Up @@ -26,8 +26,7 @@ test('should resolve ordered dependencies independently for each declaration ent
]);
});

// TODO: NADO STREMITSYA CHTOBY DECLARATSIA BYLA POVYSHE
test.failing('should resolve ordered dependencies independently of declaration entity', t => {
test('should resolve ordered dependencies independently of declaration entity', t => {
const graph = new BemGraph();

graph
Expand Down
9 changes: 8 additions & 1 deletion test/utils/create-graph.test.js
Expand Up @@ -14,7 +14,14 @@ test('should create simple graph', t => {
});

test('should create and resolve cyclic graph', t => {
t.deepEqual(depsOfGraph('a => b -> c -> d => a', {block: 'a'}), ['b', 'a', 'c', 'd']);
const decl = depsOfGraph('a => b -> c -> d => a', {block: 'a'});

const indexA = decl.indexOf('a');
const indexB = decl.indexOf('b');
const indexD = decl.indexOf('d');

t.true(indexB < indexA);
t.true(indexA < indexD);
});

test('should create and resolve another cyclic graph', t => {
Expand Down

0 comments on commit 535f329

Please sign in to comment.