Bug fixing

This commit is contained in:
Sebastian Seedorf
2022-08-19 12:58:48 +02:00
parent 2b913c6014
commit 58c3b1a3c3
4 changed files with 111 additions and 45 deletions

View File

@@ -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>
</> </>

View File

@@ -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(() => {

View File

@@ -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]
} }

View File

@@ -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))
} }