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>
<ScrollContainer>
<h1>Factorio Microservices</h1>
<ProducingGraph nodes={producingNodes} inputs={baseFactories} childType={NodeOverview} />
<ProducingGraph
nodes={producingNodes}
inputs={baseFactories}
childType={NodeOverview}
mergeHidden={true}
/>
</ScrollContainer>
</main>
</>

View File

@@ -16,17 +16,19 @@ interface Props<T extends Dict<unknown>> {
inputs: string[]
outputs?: string[]
childType: FC<HTMLProps<HTMLDivElement> & { node: GraphNode<T> }>
mergeHidden?: boolean
}
export const ProducingGraph = <T extends Dict<unknown>>({
nodes,
inputs,
outputs,
childType: ChildType
childType: ChildType,
mergeHidden
}: PropsWithChildren<Props<T>>) => {
const planeRef = useRef<HTMLDivElement>(null)
const [[rows, nodeMap]] = useState<[string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>]>(
graphUntangled(nodes, inputs, outputs ?? [], 3000)
graphUntangled(nodes, inputs, outputs ?? [], { timeLimit: 3000, mergeHidden })
)
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 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<T extends Dict<unknown>> = Dict<GraphNodeWithIds<T | AdditionalNode>>
function generateIds<T extends Dict<unknown>>(
node: GraphNode<T>,
nodeUid: () => string
@@ -13,7 +22,7 @@ function generateIds<T extends Dict<unknown>>(
___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<T extends Dict<unknown>>(
inputs: string[],
outputs: string[],
nodeUid: () => string
): [string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>] {
): [string[][], NodeList<T>] {
const addedInputs: Dict<number> = {}
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++) {
const rowInputs = uniquify(rowsRaw[i].flatMap(row => resNodes[row].inputs))
for (const rowInput of rowInputs) {
@@ -126,15 +135,12 @@ function addAdditionalNodes<T extends Dict<unknown>>(
return [res, resNodes]
}
function linkNodes<T extends Dict<unknown>>(
rowsWithInOut: string[][],
nodesWithInOut: Dict<GraphNodeWithIds<T | AdditionalNode>>
): Dict<GraphNodeWithIds<T | AdditionalNode>> {
function linkNodes<T extends Dict<unknown>>(rows: string[][], nodes: NodeList<T>): NodeList<T> {
type Store = { input: Dict<string[]>[], output: Dict<string[]>[] }
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<T | AdditionalNode>, rowIdx: number) => {
node.inputs.forEach(
@@ -150,23 +156,30 @@ function linkNodes<T extends Dict<unknown>>(
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<T extends Dict<unknown>>(
fixed: string[],
flex: string[],
nodes: Dict<GraphNodeWithIds<T | AdditionalNode>>,
nodes: NodeList<T>,
isDown: boolean
): [number[][], number] {
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>>(
rowsWithInOut: string[][],
nodesWithInOut: Dict<GraphNodeWithIds<T | AdditionalNode>>
rows: string[][],
nodes: NodeList<T>
): [string[][], number] {
function addCosts(
costsUp: [number[][], number],
@@ -213,8 +226,8 @@ function optimizeOrder<T extends Dict<unknown>>(
}
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<T extends Dict<unknown>>(
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<T extends Dict<unknown>>(
rowsWithInOut: string[][],
nodesWithInOut: Dict<GraphNodeWithIds<T | AdditionalNode>>,
nodesWithInOut: NodeList<T>,
timeLimit: number
) {
let bestScore = Infinity
@@ -310,19 +323,51 @@ export function findBest<T extends Dict<unknown>>(
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>>(
nodes: GraphNode<T>[],
inputs: string[],
outputs: string[],
timeLimit = 0
): [string[][], Dict<GraphNodeWithIds<T | AdditionalNode>>] {
options: {
timeLimit?: number
mergeHidden?: boolean
} = {}
): [string[][], NodeList<T>] {
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<T extends Dict<unknown>>(
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]
}

View File

@@ -35,6 +35,23 @@ export function shuffleInplace<T>(array: T[], rng: PRNG): T[] {
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 {
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16))
}