Bug fixing
This commit is contained in:
@@ -34,7 +34,12 @@ export const PageOverview: FC = () => {
|
|||||||
<main>
|
<main>
|
||||||
<ScrollContainer>
|
<ScrollContainer>
|
||||||
<h1>Factorio Microservices</h1>
|
<h1>Factorio Microservices</h1>
|
||||||
<ProducingGraph nodes={producingNodes} inputs={baseFactories} childType={NodeOverview} />
|
<ProducingGraph
|
||||||
|
nodes={producingNodes}
|
||||||
|
inputs={baseFactories}
|
||||||
|
childType={NodeOverview}
|
||||||
|
mergeHidden={true}
|
||||||
|
/>
|
||||||
</ScrollContainer>
|
</ScrollContainer>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -16,17 +16,19 @@ interface Props<T extends Dict<unknown>> {
|
|||||||
inputs: string[]
|
inputs: string[]
|
||||||
outputs?: string[]
|
outputs?: string[]
|
||||||
childType: FC<HTMLProps<HTMLDivElement> & { node: GraphNode<T> }>
|
childType: FC<HTMLProps<HTMLDivElement> & { node: GraphNode<T> }>
|
||||||
|
mergeHidden?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProducingGraph = <T extends Dict<unknown>>({
|
export const ProducingGraph = <T extends Dict<unknown>>({
|
||||||
nodes,
|
nodes,
|
||||||
inputs,
|
inputs,
|
||||||
outputs,
|
outputs,
|
||||||
childType: ChildType
|
childType: ChildType,
|
||||||
|
mergeHidden
|
||||||
}: PropsWithChildren<Props<T>>) => {
|
}: PropsWithChildren<Props<T>>) => {
|
||||||
const planeRef = useRef<HTMLDivElement>(null)
|
const planeRef = useRef<HTMLDivElement>(null)
|
||||||
const [[rows, nodeMap]] = useState<[string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>]>(
|
const [[rows, nodeMap]] = useState<[string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>]>(
|
||||||
graphUntangled(nodes, inputs, outputs ?? [], 3000)
|
graphUntangled(nodes, inputs, outputs ?? [], { timeLimit: 3000, mergeHidden })
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import { AdditionalNode, GraphNode, GraphNodeWithIds } from './types'
|
import { AdditionalNode, GraphNode, GraphNodeWithIds, isAdditionalNode } from './types'
|
||||||
import { Dict } from '../types'
|
import { Dict } from '../types'
|
||||||
import deepcopy from 'deepcopy'
|
import deepcopy from 'deepcopy'
|
||||||
import { isNonNullable, shuffleInplace, sortByProperty, uniquify } from '../utils'
|
import {
|
||||||
|
filterInPlace,
|
||||||
|
groupBy,
|
||||||
|
isNonNullable,
|
||||||
|
shuffleInplace,
|
||||||
|
sortByProperty,
|
||||||
|
uniquify
|
||||||
|
} from '../utils'
|
||||||
import seedrandom from 'seedrandom'
|
import seedrandom from 'seedrandom'
|
||||||
|
|
||||||
|
export type NodeList<T extends Dict<unknown>> = Dict<GraphNodeWithIds<T | AdditionalNode>>
|
||||||
|
|
||||||
function generateIds<T extends Dict<unknown>>(
|
function generateIds<T extends Dict<unknown>>(
|
||||||
node: GraphNode<T>,
|
node: GraphNode<T>,
|
||||||
nodeUid: () => string
|
nodeUid: () => string
|
||||||
@@ -13,7 +22,7 @@ function generateIds<T extends Dict<unknown>>(
|
|||||||
___uid: `${node.name
|
___uid: `${node.name
|
||||||
.replace(/[^a-z]/gi, '')
|
.replace(/[^a-z]/gi, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.substring(0, 3)}_${nodeUid()}`,
|
.substring(0, 10)}_${nodeUid()}`,
|
||||||
___uidInputs: [],
|
___uidInputs: [],
|
||||||
___uidOutputs: []
|
___uidOutputs: []
|
||||||
}
|
}
|
||||||
@@ -32,7 +41,7 @@ function generateAdditional(
|
|||||||
___uid: `${uid
|
___uid: `${uid
|
||||||
.replace(/[^a-z]/gi, '')
|
.replace(/[^a-z]/gi, '')
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.substring(0, 3)}_${nodeUid()}_${type}`,
|
.substring(0, 10)}_${nodeUid()}_${type}`,
|
||||||
___uidInputs: [],
|
___uidInputs: [],
|
||||||
___uidOutputs: []
|
___uidOutputs: []
|
||||||
}
|
}
|
||||||
@@ -79,10 +88,10 @@ function addAdditionalNodes<T extends Dict<unknown>>(
|
|||||||
inputs: string[],
|
inputs: string[],
|
||||||
outputs: string[],
|
outputs: string[],
|
||||||
nodeUid: () => string
|
nodeUid: () => string
|
||||||
): [string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>] {
|
): [string[][], NodeList<T>] {
|
||||||
const addedInputs: Dict<number> = {}
|
const addedInputs: Dict<number> = {}
|
||||||
const res: string[][] = deepcopy([[], ...rowsRaw, []])
|
const res: string[][] = deepcopy([[], ...rowsRaw, []])
|
||||||
const resNodes: Dict<GraphNodeWithIds<T | AdditionalNode>> = deepcopy(nodesRaw)
|
const resNodes: NodeList<T> = deepcopy(nodesRaw)
|
||||||
for (let i = 0; i < rowsRaw.length; i++) {
|
for (let i = 0; i < rowsRaw.length; i++) {
|
||||||
const rowInputs = uniquify(rowsRaw[i].flatMap(row => resNodes[row].inputs))
|
const rowInputs = uniquify(rowsRaw[i].flatMap(row => resNodes[row].inputs))
|
||||||
for (const rowInput of rowInputs) {
|
for (const rowInput of rowInputs) {
|
||||||
@@ -126,15 +135,12 @@ function addAdditionalNodes<T extends Dict<unknown>>(
|
|||||||
return [res, resNodes]
|
return [res, resNodes]
|
||||||
}
|
}
|
||||||
|
|
||||||
function linkNodes<T extends Dict<unknown>>(
|
function linkNodes<T extends Dict<unknown>>(rows: string[][], nodes: NodeList<T>): NodeList<T> {
|
||||||
rowsWithInOut: string[][],
|
|
||||||
nodesWithInOut: Dict<GraphNodeWithIds<T | AdditionalNode>>
|
|
||||||
): Dict<GraphNodeWithIds<T | AdditionalNode>> {
|
|
||||||
type Store = { input: Dict<string[]>[], output: Dict<string[]>[] }
|
type Store = { input: Dict<string[]>[], output: Dict<string[]>[] }
|
||||||
|
|
||||||
const store: Store = {
|
const store: Store = {
|
||||||
input: Array.from(rowsWithInOut, Object),
|
input: Array.from(rows, () => ({})),
|
||||||
output: Array.from(rowsWithInOut, Object)
|
output: Array.from(rows, () => ({}))
|
||||||
}
|
}
|
||||||
const nodesPremapping = (node: GraphNodeWithIds<T | AdditionalNode>, rowIdx: number) => {
|
const nodesPremapping = (node: GraphNodeWithIds<T | AdditionalNode>, rowIdx: number) => {
|
||||||
node.inputs.forEach(
|
node.inputs.forEach(
|
||||||
@@ -150,23 +156,30 @@ function linkNodes<T extends Dict<unknown>>(
|
|||||||
node.___uidInputs = uniquify(
|
node.___uidInputs = uniquify(
|
||||||
node.inputs.flatMap(input => store.output[rowIdx - 1][input]).filter(isNonNullable)
|
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.___uidOutputs = uniquify(
|
||||||
node.outputs.flatMap(output => store.input[rowIdx + 1][output]).filter(isNonNullable)
|
node.outputs.flatMap(output => store.input[rowIdx + 1][output]).filter(isNonNullable)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
rowsWithInOut.forEach((row, rowIdx) =>
|
rows.forEach((row, rowIdx) => {
|
||||||
row.forEach(uid => nodesPremapping(nodesWithInOut[uid], rowIdx))
|
row.forEach(uid => {
|
||||||
)
|
/*if (isAdditionalNode(nodes[uid]) && nodes[uid].___type === 'h' && (nodes[uid].inputs.length > 1 || nodes[uid].___uidInputs.length > 1))
|
||||||
rowsWithInOut.forEach((row, rowIdx) => row.forEach(uid => linkInOut(nodesWithInOut[uid], rowIdx)))
|
console.log(rowIdx, nodes[uid])*/
|
||||||
return nodesWithInOut
|
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<T extends Dict<unknown>>(
|
function crossingsOf<T extends Dict<unknown>>(
|
||||||
fixed: string[],
|
fixed: string[],
|
||||||
flex: string[],
|
flex: string[],
|
||||||
nodes: Dict<GraphNodeWithIds<T | AdditionalNode>>,
|
nodes: NodeList<T>,
|
||||||
isDown: boolean
|
isDown: boolean
|
||||||
): [number[][], number] {
|
): [number[][], number] {
|
||||||
const result = Array.from(flex, () => Array.from(flex, () => 9999999))
|
const result = Array.from(flex, () => Array.from(flex, () => 9999999))
|
||||||
@@ -199,8 +212,8 @@ function crossingsOf<T extends Dict<unknown>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function optimizeOrder<T extends Dict<unknown>>(
|
function optimizeOrder<T extends Dict<unknown>>(
|
||||||
rowsWithInOut: string[][],
|
rows: string[][],
|
||||||
nodesWithInOut: Dict<GraphNodeWithIds<T | AdditionalNode>>
|
nodes: NodeList<T>
|
||||||
): [string[][], number] {
|
): [string[][], number] {
|
||||||
function addCosts(
|
function addCosts(
|
||||||
costsUp: [number[][], number],
|
costsUp: [number[][], number],
|
||||||
@@ -213,8 +226,8 @@ function optimizeOrder<T extends Dict<unknown>>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function improveRow(top: string[] | undefined, mid: string[], bot: string[] | undefined) {
|
function improveRow(top: string[] | undefined, mid: string[], bot: string[] | undefined) {
|
||||||
const costsUp = top ? crossingsOf(top, mid, nodesWithInOut, true) : undefined
|
const costsUp = top ? crossingsOf(top, mid, nodes, true) : undefined
|
||||||
const costsDown = bot ? crossingsOf(bot, mid, nodesWithInOut, false) : undefined
|
const costsDown = bot ? crossingsOf(bot, mid, nodes, false) : undefined
|
||||||
const [costs, scoreConst] =
|
const [costs, scoreConst] =
|
||||||
costsUp && costsDown ? addCosts(costsUp, costsDown) : costsDown ?? costsUp ?? [undefined, 0]
|
costsUp && costsDown ? addCosts(costsUp, costsDown) : costsDown ?? costsUp ?? [undefined, 0]
|
||||||
let score = scoreConst
|
let score = scoreConst
|
||||||
@@ -272,21 +285,21 @@ function optimizeOrder<T extends Dict<unknown>>(
|
|||||||
const scores: number[] = []
|
const scores: number[] = []
|
||||||
while (improvement) {
|
while (improvement) {
|
||||||
improvement = false
|
improvement = false
|
||||||
rowsWithInOut.reduce((top, mid, idx) => {
|
rows.reduce((top, mid, idx) => {
|
||||||
scores[idx] = improveRow(top, mid, rowsWithInOut[idx + 1])
|
scores[idx] = improveRow(top, mid, rows[idx + 1])
|
||||||
return mid
|
return mid
|
||||||
}, undefined as string[] | undefined)
|
}, undefined as string[] | undefined)
|
||||||
rowsWithInOut.reduceRight((bot, mid, idx) => {
|
rows.reduceRight((bot, mid, idx) => {
|
||||||
scores[idx] = improveRow(rowsWithInOut[idx - 1], mid, bot)
|
scores[idx] = improveRow(rows[idx - 1], mid, bot)
|
||||||
return mid
|
return mid
|
||||||
}, undefined as string[] | undefined)
|
}, undefined as string[] | undefined)
|
||||||
}
|
}
|
||||||
return [rowsWithInOut, scores.reduce((a, b) => a + b)]
|
return [rows, scores.reduce((a, b) => a + b)]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findBest<T extends Dict<unknown>>(
|
export function findBest<T extends Dict<unknown>>(
|
||||||
rowsWithInOut: string[][],
|
rowsWithInOut: string[][],
|
||||||
nodesWithInOut: Dict<GraphNodeWithIds<T | AdditionalNode>>,
|
nodesWithInOut: NodeList<T>,
|
||||||
timeLimit: number
|
timeLimit: number
|
||||||
) {
|
) {
|
||||||
let bestScore = Infinity
|
let bestScore = Infinity
|
||||||
@@ -310,19 +323,51 @@ export function findBest<T extends Dict<unknown>>(
|
|||||||
return bestRows
|
return bestRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mergeHiddenNodes<T extends Dict<unknown>>(
|
||||||
|
rows: string[][],
|
||||||
|
nodes: NodeList<T>
|
||||||
|
): [string[][], NodeList<T>] {
|
||||||
|
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<T extends Dict<unknown>>(
|
export function graphUntangled<T extends Dict<unknown>>(
|
||||||
nodes: GraphNode<T>[],
|
nodes: GraphNode<T>[],
|
||||||
inputs: string[],
|
inputs: string[],
|
||||||
outputs: string[],
|
outputs: string[],
|
||||||
timeLimit = 0
|
options: {
|
||||||
): [string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>] {
|
timeLimit?: number
|
||||||
|
mergeHidden?: boolean
|
||||||
|
} = {}
|
||||||
|
): [string[][], NodeList<T>] {
|
||||||
const nodeSeed = seedrandom.alea('node')
|
const nodeSeed = seedrandom.alea('node')
|
||||||
const nodeUid = () => Math.abs(nodeSeed.int32()).toString(16).substring(0, 3)
|
const nodeUid = () => Math.abs(nodeSeed.int32()).toString(16).substring(0, 3)
|
||||||
//console.log('---------------')
|
|
||||||
const [rowsRaw, nodesRaw] = splitIntoRows(inputs, nodes, nodeUid)
|
const [rowsRaw, nodesRaw] = splitIntoRows(inputs, nodes, nodeUid)
|
||||||
//console.log("Step 1")
|
|
||||||
//console.table(rowsRaw)
|
|
||||||
//console.table(nodesRaw)
|
|
||||||
const [rowsWithInOut, nodesWithInOut] = addAdditionalNodes(
|
const [rowsWithInOut, nodesWithInOut] = addAdditionalNodes(
|
||||||
rowsRaw,
|
rowsRaw,
|
||||||
nodesRaw,
|
nodesRaw,
|
||||||
@@ -330,13 +375,10 @@ export function graphUntangled<T extends Dict<unknown>>(
|
|||||||
outputs,
|
outputs,
|
||||||
nodeUid
|
nodeUid
|
||||||
)
|
)
|
||||||
//console.log("Step 2")
|
|
||||||
//console.table(rowsWithInOut)
|
|
||||||
//console.table(nodesWithInOut)
|
|
||||||
const nodesLinked = linkNodes(rowsWithInOut, nodesWithInOut)
|
const nodesLinked = linkNodes(rowsWithInOut, nodesWithInOut)
|
||||||
//console.log("Step 3")
|
const [rowsMergedHidden, nodesMergedHidden] = options.mergeHidden
|
||||||
//console.table(rowsWithInOut)
|
? mergeHiddenNodes(rowsWithInOut, nodesLinked)
|
||||||
//console.table(nodesLinked)
|
: [rowsWithInOut, nodesLinked]
|
||||||
const bestRows = findBest(rowsWithInOut, nodesWithInOut, timeLimit)
|
const bestRows = findBest(rowsMergedHidden, nodesMergedHidden, options.timeLimit ?? 0)
|
||||||
return [bestRows, nodesLinked]
|
return [bestRows, nodesLinked]
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/utils.ts
17
src/utils.ts
@@ -35,6 +35,23 @@ export function shuffleInplace<T>(array: T[], rng: PRNG): T[] {
|
|||||||
return array
|
return array
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterInPlace<T>(
|
||||||
|
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 {
|
export function fixedEncodeURIComponent(str: string): string {
|
||||||
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16))
|
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user