chenyc
2025-12-09 545c24c6a711d71b65f3d4e8122fee3837fb1edc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import conversions from './conversions.js';
 
/*
    This function routes a model to all other models.
 
    all functions that are routed have a property `.conversion` attached
    to the returned synthetic function. This property is an array
    of strings, each with the steps in between the 'from' and 'to'
    color models (inclusive).
 
    conversions that are not possible simply are not included.
*/
 
function buildGraph() {
    const graph = {};
    // https://jsperf.com/object-keys-vs-for-in-with-closure/3
    const models = Object.keys(conversions);
 
    for (let {length} = models, i = 0; i < length; i++) {
        graph[models[i]] = {
            // http://jsperf.com/1-vs-infinity
            // micro-opt, but this is simple.
            distance: -1,
            parent: null,
        };
    }
 
    return graph;
}
 
// https://en.wikipedia.org/wiki/Breadth-first_search
function deriveBFS(fromModel) {
    const graph = buildGraph();
    const queue = [fromModel]; // Unshift -> queue -> pop
 
    graph[fromModel].distance = 0;
 
    while (queue.length > 0) {
        const current = queue.pop();
        const adjacents = Object.keys(conversions[current]);
 
        for (let {length} = adjacents, i = 0; i < length; i++) {
            const adjacent = adjacents[i];
            const node = graph[adjacent];
 
            if (node.distance === -1) {
                node.distance = graph[current].distance + 1;
                node.parent = current;
                queue.unshift(adjacent);
            }
        }
    }
 
    return graph;
}
 
function link(from, to) {
    return function (args) {
        return to(from(args));
    };
}
 
function wrapConversion(toModel, graph) {
    const path = [graph[toModel].parent, toModel];
    let fn = conversions[graph[toModel].parent][toModel];
 
    let cur = graph[toModel].parent;
    while (graph[cur].parent) {
        path.unshift(graph[cur].parent);
        fn = link(conversions[graph[cur].parent][cur], fn);
        cur = graph[cur].parent;
    }
 
    fn.conversion = path;
    return fn;
}
 
function route(fromModel) {
    const graph = deriveBFS(fromModel);
    const conversion = {};
 
    const models = Object.keys(graph);
    for (let {length} = models, i = 0; i < length; i++) {
        const toModel = models[i];
        const node = graph[toModel];
 
        if (node.parent === null) {
            // No possible conversion, or this node is the source model.
            continue;
        }
 
        conversion[toModel] = wrapConversion(toModel, graph);
    }
 
    return conversion;
}
 
export default route;