Bug fixing
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
17
src/utils.ts
17
src/utils.ts
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user