diff --git a/components/visualize/PageOverview.tsx b/components/visualize/PageOverview.tsx
index d48090a..c56ea79 100644
--- a/components/visualize/PageOverview.tsx
+++ b/components/visualize/PageOverview.tsx
@@ -34,7 +34,12 @@ export const PageOverview: FC = () => {
Factorio Microservices
-
+
>
diff --git a/components/visualize/ProducingGraph/ProducingGraph.tsx b/components/visualize/ProducingGraph/ProducingGraph.tsx
index ba1054e..8ae51a4 100644
--- a/components/visualize/ProducingGraph/ProducingGraph.tsx
+++ b/components/visualize/ProducingGraph/ProducingGraph.tsx
@@ -16,17 +16,19 @@ interface Props> {
inputs: string[]
outputs?: string[]
childType: FC & { node: GraphNode }>
+ mergeHidden?: boolean
}
export const ProducingGraph = >({
nodes,
inputs,
outputs,
- childType: ChildType
+ childType: ChildType,
+ mergeHidden
}: PropsWithChildren>) => {
const planeRef = useRef(null)
const [[rows, nodeMap]] = useState<[string[][], Dict>]>(
- graphUntangled(nodes, inputs, outputs ?? [], 3000)
+ graphUntangled(nodes, inputs, outputs ?? [], { timeLimit: 3000, mergeHidden })
)
useEffect(() => {
diff --git a/src/graph-untangle/index.ts b/src/graph-untangle/index.ts
index a481f9e..29237f3 100644
--- a/src/graph-untangle/index.ts
+++ b/src/graph-untangle/index.ts
@@ -1,9 +1,18 @@
-import { AdditionalNode, GraphNode, GraphNodeWithIds } from './types'
+import { AdditionalNode, GraphNode, GraphNodeWithIds, isAdditionalNode } from './types'
import { Dict } from '../types'
import deepcopy from 'deepcopy'
-import { isNonNullable, shuffleInplace, sortByProperty, uniquify } from '../utils'
+import {
+ filterInPlace,
+ groupBy,
+ isNonNullable,
+ shuffleInplace,
+ sortByProperty,
+ uniquify
+} from '../utils'
import seedrandom from 'seedrandom'
+export type NodeList> = Dict>
+
function generateIds>(
node: GraphNode,
nodeUid: () => string
@@ -13,7 +22,7 @@ function generateIds>(
___uid: `${node.name
.replace(/[^a-z]/gi, '')
.toLowerCase()
- .substring(0, 3)}_${nodeUid()}`,
+ .substring(0, 10)}_${nodeUid()}`,
___uidInputs: [],
___uidOutputs: []
}
@@ -32,7 +41,7 @@ function generateAdditional(
___uid: `${uid
.replace(/[^a-z]/gi, '')
.toLowerCase()
- .substring(0, 3)}_${nodeUid()}_${type}`,
+ .substring(0, 10)}_${nodeUid()}_${type}`,
___uidInputs: [],
___uidOutputs: []
}
@@ -79,10 +88,10 @@ function addAdditionalNodes>(
inputs: string[],
outputs: string[],
nodeUid: () => string
-): [string[][], Dict>] {
+): [string[][], NodeList] {
const addedInputs: Dict = {}
const res: string[][] = deepcopy([[], ...rowsRaw, []])
- const resNodes: Dict> = deepcopy(nodesRaw)
+ const resNodes: NodeList = deepcopy(nodesRaw)
for (let i = 0; i < rowsRaw.length; i++) {
const rowInputs = uniquify(rowsRaw[i].flatMap(row => resNodes[row].inputs))
for (const rowInput of rowInputs) {
@@ -126,15 +135,12 @@ function addAdditionalNodes>(
return [res, resNodes]
}
-function linkNodes>(
- rowsWithInOut: string[][],
- nodesWithInOut: Dict>
-): Dict> {
+function linkNodes>(rows: string[][], nodes: NodeList): NodeList {
type Store = { input: Dict[], output: Dict[] }
const store: Store = {
- input: Array.from(rowsWithInOut, Object),
- output: Array.from(rowsWithInOut, Object)
+ input: Array.from(rows, () => ({})),
+ output: Array.from(rows, () => ({}))
}
const nodesPremapping = (node: GraphNodeWithIds, rowIdx: number) => {
node.inputs.forEach(
@@ -150,23 +156,30 @@ function linkNodes>(
node.___uidInputs = uniquify(
node.inputs.flatMap(input => store.output[rowIdx - 1][input]).filter(isNonNullable)
)
- if (rowIdx < rowsWithInOut.length - 1)
+ if (rowIdx < rows.length - 1)
node.___uidOutputs = uniquify(
node.outputs.flatMap(output => store.input[rowIdx + 1][output]).filter(isNonNullable)
)
}
- rowsWithInOut.forEach((row, rowIdx) =>
- row.forEach(uid => nodesPremapping(nodesWithInOut[uid], rowIdx))
- )
- rowsWithInOut.forEach((row, rowIdx) => row.forEach(uid => linkInOut(nodesWithInOut[uid], rowIdx)))
- return nodesWithInOut
+ rows.forEach((row, rowIdx) => {
+ row.forEach(uid => {
+ /*if (isAdditionalNode(nodes[uid]) && nodes[uid].___type === 'h' && (nodes[uid].inputs.length > 1 || nodes[uid].___uidInputs.length > 1))
+ console.log(rowIdx, nodes[uid])*/
+ nodesPremapping(nodes[uid], rowIdx)
+ })
+ })
+ /*console.table(store.input)
+ console.table(store.output)
+ console.table(nodes)*/
+ rows.forEach((row, rowIdx) => row.forEach(uid => linkInOut(nodes[uid], rowIdx)))
+ return nodes
}
function crossingsOf>(
fixed: string[],
flex: string[],
- nodes: Dict>,
+ nodes: NodeList,
isDown: boolean
): [number[][], number] {
const result = Array.from(flex, () => Array.from(flex, () => 9999999))
@@ -199,8 +212,8 @@ function crossingsOf>(
}
function optimizeOrder>(
- rowsWithInOut: string[][],
- nodesWithInOut: Dict>
+ rows: string[][],
+ nodes: NodeList
): [string[][], number] {
function addCosts(
costsUp: [number[][], number],
@@ -213,8 +226,8 @@ function optimizeOrder>(
}
function improveRow(top: string[] | undefined, mid: string[], bot: string[] | undefined) {
- const costsUp = top ? crossingsOf(top, mid, nodesWithInOut, true) : undefined
- const costsDown = bot ? crossingsOf(bot, mid, nodesWithInOut, false) : undefined
+ const costsUp = top ? crossingsOf(top, mid, nodes, true) : undefined
+ const costsDown = bot ? crossingsOf(bot, mid, nodes, false) : undefined
const [costs, scoreConst] =
costsUp && costsDown ? addCosts(costsUp, costsDown) : costsDown ?? costsUp ?? [undefined, 0]
let score = scoreConst
@@ -272,21 +285,21 @@ function optimizeOrder>(
const scores: number[] = []
while (improvement) {
improvement = false
- rowsWithInOut.reduce((top, mid, idx) => {
- scores[idx] = improveRow(top, mid, rowsWithInOut[idx + 1])
+ rows.reduce((top, mid, idx) => {
+ scores[idx] = improveRow(top, mid, rows[idx + 1])
return mid
}, undefined as string[] | undefined)
- rowsWithInOut.reduceRight((bot, mid, idx) => {
- scores[idx] = improveRow(rowsWithInOut[idx - 1], mid, bot)
+ rows.reduceRight((bot, mid, idx) => {
+ scores[idx] = improveRow(rows[idx - 1], mid, bot)
return mid
}, undefined as string[] | undefined)
}
- return [rowsWithInOut, scores.reduce((a, b) => a + b)]
+ return [rows, scores.reduce((a, b) => a + b)]
}
export function findBest>(
rowsWithInOut: string[][],
- nodesWithInOut: Dict>,
+ nodesWithInOut: NodeList,
timeLimit: number
) {
let bestScore = Infinity
@@ -310,19 +323,51 @@ export function findBest>(
return bestRows
}
+function mergeHiddenNodes>(
+ rows: string[][],
+ nodes: NodeList
+): [string[][], NodeList] {
+ for (const row of rows) {
+ const groups = groupBy(row, elem => {
+ const node = nodes[elem]
+ if (isAdditionalNode(node) && node.___type === 'h') return node.___uidInputs[0]
+ else return node.___uid
+ })
+ const changes = Object.values(groups).filter(group => group.length > 1)
+ for (const change of changes) {
+ const [firstUid, ...otherUids] = change
+ const first = nodes[firstUid]
+ first.___uidOutputs = uniquify(change.flatMap(c => nodes[c].___uidOutputs))
+ first.outputs = uniquify(change.flatMap(c => nodes[c].outputs))
+ const input = nodes[first.___uidInputs[0]]
+ input.___uidOutputs.filter(uid => !otherUids.includes(uid))
+ for (const otherUid of otherUids) {
+ const outputs = nodes[otherUid].___uidOutputs
+ for (const output of outputs) {
+ nodes[output].___uidInputs = uniquify(
+ nodes[output].___uidInputs.map(uid => (uid === otherUid ? firstUid : uid))
+ )
+ }
+ delete nodes[otherUid]
+ }
+ filterInPlace(row, uid => !otherUids.includes(uid))
+ }
+ }
+ return [rows, nodes]
+}
+
export function graphUntangled>(
nodes: GraphNode[],
inputs: string[],
outputs: string[],
- timeLimit = 0
-): [string[][], Dict>] {
+ options: {
+ timeLimit?: number
+ mergeHidden?: boolean
+ } = {}
+): [string[][], NodeList] {
const nodeSeed = seedrandom.alea('node')
const nodeUid = () => Math.abs(nodeSeed.int32()).toString(16).substring(0, 3)
- //console.log('---------------')
const [rowsRaw, nodesRaw] = splitIntoRows(inputs, nodes, nodeUid)
- //console.log("Step 1")
- //console.table(rowsRaw)
- //console.table(nodesRaw)
const [rowsWithInOut, nodesWithInOut] = addAdditionalNodes(
rowsRaw,
nodesRaw,
@@ -330,13 +375,10 @@ export function graphUntangled>(
outputs,
nodeUid
)
- //console.log("Step 2")
- //console.table(rowsWithInOut)
- //console.table(nodesWithInOut)
const nodesLinked = linkNodes(rowsWithInOut, nodesWithInOut)
- //console.log("Step 3")
- //console.table(rowsWithInOut)
- //console.table(nodesLinked)
- const bestRows = findBest(rowsWithInOut, nodesWithInOut, timeLimit)
+ const [rowsMergedHidden, nodesMergedHidden] = options.mergeHidden
+ ? mergeHiddenNodes(rowsWithInOut, nodesLinked)
+ : [rowsWithInOut, nodesLinked]
+ const bestRows = findBest(rowsMergedHidden, nodesMergedHidden, options.timeLimit ?? 0)
return [bestRows, nodesLinked]
}
diff --git a/src/utils.ts b/src/utils.ts
index 3ab953c..cc015bb 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -35,6 +35,23 @@ export function shuffleInplace(array: T[], rng: PRNG): T[] {
return array
}
+export function filterInPlace(
+ array: T[],
+ condition: (elem: T, idx: number, arr: T[]) => boolean
+) {
+ let i = 0,
+ j = 0
+
+ while (i < array.length) {
+ const val = array[i]
+ if (condition(val, i, array)) array[j++] = val
+ i++
+ }
+
+ array.length = j
+ return array
+}
+
export function fixedEncodeURIComponent(str: string): string {
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16))
}