Make ME pattern audit compact
This commit is contained in:
@@ -92,8 +92,10 @@ greenhouse_inventory_debug <peripheral_name>
|
|||||||
## AE2-Pattern und Duplikate erfassen
|
## AE2-Pattern und Duplikate erfassen
|
||||||
|
|
||||||
Der rein lesende ME-Pattern-Auditor nutzt die Advanced-Peripherals-ME-Bridge und
|
Der rein lesende ME-Pattern-Auditor nutzt die Advanced-Peripherals-ME-Bridge und
|
||||||
schreibt alle vom Netzwerk angebotenen Pattern, Craftables und moegliche Duplikate
|
schreibt einen kompakten Pattern-Index, exakte Duplikate, Rezepte mit gleichem
|
||||||
in `me_pattern_debug.txt`. Er startet keine Crafts und veraendert keine Pattern.
|
Output sowie kleine Rohdaten-Samples nach `me_pattern_debug.txt`. Die kompakte
|
||||||
|
Ausgabe passt in den begrenzten Speicher eines CC-Computers. Er startet keine
|
||||||
|
Crafts und veraendert keine Pattern.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
delete me_pattern_debug.lua
|
delete me_pattern_debug.lua
|
||||||
|
|||||||
+151
-96
@@ -1,14 +1,17 @@
|
|||||||
-- ATM10 AE2 pattern audit via Advanced Peripherals ME Bridge
|
-- ATM10 compact AE2 pattern audit via Advanced Peripherals ME Bridge
|
||||||
-- Read-only: does not craft, export, import, cancel or modify patterns.
|
-- Read-only: does not craft, export, import, cancel or modify patterns.
|
||||||
|
|
||||||
|
local VERSION = "1.1.0"
|
||||||
local OUTPUT_FILE = "me_pattern_debug.txt"
|
local OUTPUT_FILE = "me_pattern_debug.txt"
|
||||||
local bridgeName
|
local SAMPLE_COUNT = 3
|
||||||
|
local SAMPLE_LIMIT = 8000
|
||||||
|
local LINE_LIMIT = 500
|
||||||
|
|
||||||
|
local bridgeName
|
||||||
local function hasType(name, wanted)
|
local function hasType(name, wanted)
|
||||||
if peripheral.hasType then return peripheral.hasType(name, wanted) end
|
if peripheral.hasType then return peripheral.hasType(name, wanted) end
|
||||||
return peripheral.getType(name) == wanted
|
return peripheral.getType(name) == wanted
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, name in ipairs(peripheral.getNames()) do
|
for _, name in ipairs(peripheral.getNames()) do
|
||||||
if hasType(name, "me_bridge") then bridgeName = name break end
|
if hasType(name, "me_bridge") then bridgeName = name break end
|
||||||
end
|
end
|
||||||
@@ -21,40 +24,29 @@ local function sortedKeys(value)
|
|||||||
return keys
|
return keys
|
||||||
end
|
end
|
||||||
|
|
||||||
local function serialize(value, depth, seen)
|
local function compact(value, depth, seen)
|
||||||
depth, seen = depth or 0, seen or {}
|
depth, seen = depth or 0, seen or {}
|
||||||
local kind = type(value)
|
local kind = type(value)
|
||||||
if kind == "string" then return string.format("%q", value) end
|
if kind == "string" then return string.format("%q", value) end
|
||||||
if kind ~= "table" then return tostring(value) end
|
if kind ~= "table" then return tostring(value) end
|
||||||
if seen[value] then return "<cycle>" end
|
if seen[value] then return "<cycle>" end
|
||||||
if depth >= 12 then return "<max-depth>" end
|
if depth >= 8 then return "<max-depth>" end
|
||||||
seen[value] = true
|
|
||||||
local parts = { "{" }
|
|
||||||
for _, key in ipairs(sortedKeys(value)) do
|
|
||||||
parts[#parts + 1] = string.rep(" ", depth + 1)
|
|
||||||
.. "[" .. serialize(key, depth + 1, seen) .. "] = "
|
|
||||||
.. serialize(value[key], depth + 1, seen) .. ","
|
|
||||||
end
|
|
||||||
parts[#parts + 1] = string.rep(" ", depth) .. "}"
|
|
||||||
seen[value] = nil
|
|
||||||
return table.concat(parts, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
local function compact(value, seen)
|
|
||||||
local kind = type(value)
|
|
||||||
if kind == "string" then return string.format("%q", value) end
|
|
||||||
if kind ~= "table" then return tostring(value) end
|
|
||||||
seen = seen or {}
|
|
||||||
if seen[value] then return "<cycle>" end
|
|
||||||
seen[value] = true
|
seen[value] = true
|
||||||
local parts = {}
|
local parts = {}
|
||||||
for _, key in ipairs(sortedKeys(value)) do
|
for _, key in ipairs(sortedKeys(value)) do
|
||||||
parts[#parts + 1] = "[" .. compact(key, seen) .. "]=" .. compact(value[key], seen)
|
parts[#parts + 1] = "[" .. compact(key, depth + 1, seen) .. "]="
|
||||||
|
.. compact(value[key], depth + 1, seen)
|
||||||
end
|
end
|
||||||
seen[value] = nil
|
seen[value] = nil
|
||||||
return "{" .. table.concat(parts, ",") .. "}"
|
return "{" .. table.concat(parts, ",") .. "}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function truncate(value, limit)
|
||||||
|
value, limit = tostring(value), limit or LINE_LIMIT
|
||||||
|
if #value <= limit then return value end
|
||||||
|
return value:sub(1, limit) .. "...<" .. tostring(#value - limit) .. " chars>"
|
||||||
|
end
|
||||||
|
|
||||||
local function callRead(method, ...)
|
local function callRead(method, ...)
|
||||||
local result = table.pack(pcall(peripheral.call, bridgeName, method, ...))
|
local result = table.pack(pcall(peripheral.call, bridgeName, method, ...))
|
||||||
if not result[1] then return false, tostring(result[2]) end
|
if not result[1] then return false, tostring(result[2]) end
|
||||||
@@ -73,98 +65,161 @@ local function listValues(value)
|
|||||||
return result
|
return result
|
||||||
end
|
end
|
||||||
|
|
||||||
local function recipeSide(pattern, names)
|
local function first(value, keys)
|
||||||
for _, name in ipairs(names) do
|
if type(value) ~= "table" then return nil end
|
||||||
if pattern[name] ~= nil then return pattern[name] end
|
for _, key in ipairs(keys) do
|
||||||
|
if value[key] ~= nil then return value[key] end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function fingerprint(pattern)
|
local function itemName(value)
|
||||||
local inputs = recipeSide(pattern, { "inputs", "input", "ingredients" })
|
if type(value) == "string" then return value end
|
||||||
local outputs = recipeSide(pattern, { "outputs", "output", "results", "result" })
|
if type(value) ~= "table" then return nil end
|
||||||
if inputs ~= nil or outputs ~= nil then
|
local name = first(value, { "name", "id", "item", "itemId", "key" })
|
||||||
return "IN=" .. compact(inputs) .. "|OUT=" .. compact(outputs)
|
if type(name) == "string" then return name end
|
||||||
end
|
if type(name) == "table" then return itemName(name) end
|
||||||
return compact(pattern)
|
local what = first(value, { "what", "stack", "resource" })
|
||||||
|
if what ~= nil and what ~= value then return itemName(what) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local lines = {}
|
local function itemToken(value)
|
||||||
local function emit(value) lines[#lines + 1] = tostring(value) end
|
local name = itemName(value)
|
||||||
|
if not name then return truncate(compact(value), 180) end
|
||||||
|
local count = type(value) == "table" and first(value, { "count", "amount", "quantity", "size" }) or nil
|
||||||
|
local token = name .. " x" .. tostring(tonumber(count) or 1)
|
||||||
|
if type(value) == "table" then
|
||||||
|
local components = first(value, { "components", "nbt", "tag" })
|
||||||
|
if components ~= nil then token = token .. " " .. truncate(compact(components), 120) end
|
||||||
|
end
|
||||||
|
return token
|
||||||
|
end
|
||||||
|
|
||||||
emit("ATM10 AE2 PATTERN DEBUG")
|
local function sideEntries(side)
|
||||||
emit("Computer ID: " .. tostring(os.getComputerID()))
|
if side == nil then return {} end
|
||||||
emit("Computer label: " .. tostring(os.getComputerLabel() or "<not set>"))
|
if type(side) ~= "table" or itemName(side) then return { itemToken(side) } end
|
||||||
emit("ME Bridge: " .. bridgeName)
|
local entries = {}
|
||||||
local okOnline, online = callRead("isConnected")
|
for _, key in ipairs(sortedKeys(side)) do
|
||||||
emit("Connected: " .. (okOnline and tostring(online) or ("ERROR: " .. tostring(online))))
|
if key ~= "n" then entries[#entries + 1] = tostring(key) .. ":" .. itemToken(side[key]) end
|
||||||
|
end
|
||||||
|
return entries
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sideText(side)
|
||||||
|
local entries = sideEntries(side)
|
||||||
|
return #entries > 0 and table.concat(entries, " + ") or "-"
|
||||||
|
end
|
||||||
|
|
||||||
|
local INPUT_KEYS = { "inputs", "input", "ingredients" }
|
||||||
|
local OUTPUT_KEYS = { "outputs", "output", "results", "result" }
|
||||||
|
local function patternInputs(pattern) return first(pattern, INPUT_KEYS) end
|
||||||
|
local function patternOutputs(pattern) return first(pattern, OUTPUT_KEYS) end
|
||||||
|
|
||||||
|
local function patternType(pattern)
|
||||||
|
local value = first(pattern, { "type", "patternType", "crafting", "processing", "isCrafting" })
|
||||||
|
return value == nil and "unknown" or tostring(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function providerText(pattern)
|
||||||
|
local value = first(pattern, {
|
||||||
|
"provider", "providerName", "patternProvider", "machine", "location", "pos", "position"
|
||||||
|
})
|
||||||
|
return value == nil and "<not exposed>" or truncate(compact(value), 180)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function exactFingerprint(pattern)
|
||||||
|
return patternType(pattern) .. "|IN=" .. table.concat(sideEntries(patternInputs(pattern)), "|")
|
||||||
|
.. "|OUT=" .. table.concat(sideEntries(patternOutputs(pattern)), "|")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function outputFingerprint(pattern)
|
||||||
|
return table.concat(sideEntries(patternOutputs(pattern)), "|")
|
||||||
|
end
|
||||||
|
|
||||||
local okPatterns, rawPatterns = callRead("getPatterns")
|
local okPatterns, rawPatterns = callRead("getPatterns")
|
||||||
if not okPatterns then okPatterns, rawPatterns = callRead("getPatterns", {}) end
|
if not okPatterns then okPatterns, rawPatterns = callRead("getPatterns", {}) end
|
||||||
if not okPatterns then error("getPatterns fehlgeschlagen: " .. tostring(rawPatterns), 0) end
|
if not okPatterns then error("getPatterns fehlgeschlagen: " .. tostring(rawPatterns), 0) end
|
||||||
|
|
||||||
local patterns = listValues(rawPatterns)
|
local patterns = listValues(rawPatterns)
|
||||||
emit("Pattern records: " .. tostring(#patterns))
|
|
||||||
local groups = {}
|
local exactGroups, outputGroups = {}, {}
|
||||||
for index, pattern in ipairs(patterns) do
|
for index, pattern in ipairs(patterns) do
|
||||||
local key = fingerprint(pattern)
|
local exact, output = exactFingerprint(pattern), outputFingerprint(pattern)
|
||||||
groups[key] = groups[key] or {}
|
exactGroups[exact] = exactGroups[exact] or {}
|
||||||
groups[key][#groups[key] + 1] = index
|
exactGroups[exact][#exactGroups[exact] + 1] = index
|
||||||
end
|
if output ~= "" then
|
||||||
local duplicates = {}
|
outputGroups[output] = outputGroups[output] or {}
|
||||||
for key, indexes in pairs(groups) do
|
outputGroups[output][#outputGroups[output] + 1] = index
|
||||||
if #indexes > 1 then duplicates[#duplicates + 1] = { key = key, indexes = indexes } end
|
|
||||||
end
|
|
||||||
table.sort(duplicates, function(a, b)
|
|
||||||
if #a.indexes == #b.indexes then return a.key < b.key end
|
|
||||||
return #a.indexes > #b.indexes
|
|
||||||
end)
|
|
||||||
|
|
||||||
emit("Duplicate candidate groups: " .. tostring(#duplicates))
|
|
||||||
emit("")
|
|
||||||
emit("DUPLICATE CANDIDATES")
|
|
||||||
emit(string.rep("=", 72))
|
|
||||||
if #duplicates == 0 then
|
|
||||||
emit("No duplicates detected from the exposed pattern schema.")
|
|
||||||
else
|
|
||||||
for groupIndex, group in ipairs(duplicates) do
|
|
||||||
emit("")
|
|
||||||
emit("GROUP " .. groupIndex .. " | copies=" .. #group.indexes
|
|
||||||
.. " | pattern indexes=" .. table.concat(group.indexes, ", "))
|
|
||||||
emit(group.key)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
emit("")
|
local function duplicatesOf(groups)
|
||||||
emit("RAW GETPATTERNS RESULT")
|
local duplicates = {}
|
||||||
emit(string.rep("=", 72))
|
for signature, indexes in pairs(groups) do
|
||||||
emit(serialize(rawPatterns))
|
if #indexes > 1 then duplicates[#duplicates + 1] = { signature = signature, indexes = indexes } end
|
||||||
local okCraftable, craftable = callRead("getCraftableItems")
|
|
||||||
emit("")
|
|
||||||
emit("RAW GETCRAFTABLEITEMS RESULT")
|
|
||||||
emit(string.rep("=", 72))
|
|
||||||
emit(okCraftable and serialize(craftable) or ("ERROR: " .. tostring(craftable)))
|
|
||||||
|
|
||||||
emit("")
|
|
||||||
emit("CONNECTED PERIPHERALS")
|
|
||||||
emit(string.rep("=", 72))
|
|
||||||
local peripheralNames = peripheral.getNames()
|
|
||||||
table.sort(peripheralNames)
|
|
||||||
for _, name in ipairs(peripheralNames) do
|
|
||||||
local types = table.pack(peripheral.getType(name))
|
|
||||||
local typeNames = {}
|
|
||||||
for index = 1, types.n do
|
|
||||||
if types[index] ~= nil then typeNames[#typeNames + 1] = tostring(types[index]) end
|
|
||||||
end
|
end
|
||||||
emit(name .. " | " .. table.concat(typeNames, ", "))
|
table.sort(duplicates, function(a, b)
|
||||||
|
if #a.indexes == #b.indexes then return a.signature < b.signature end
|
||||||
|
return #a.indexes > #b.indexes
|
||||||
|
end)
|
||||||
|
return duplicates
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local exactDuplicates = duplicatesOf(exactGroups)
|
||||||
|
local outputDuplicates = duplicatesOf(outputGroups)
|
||||||
|
if fs.exists(OUTPUT_FILE) then fs.delete(OUTPUT_FILE) end
|
||||||
local file = assert(fs.open(OUTPUT_FILE, "w"))
|
local file = assert(fs.open(OUTPUT_FILE, "w"))
|
||||||
file.write(table.concat(lines, "\n"))
|
local function write(value) file.writeLine(tostring(value)) end
|
||||||
file.write("\n")
|
|
||||||
|
write("ATM10 AE2 COMPACT PATTERN AUDIT V" .. VERSION)
|
||||||
|
write("Computer ID: " .. tostring(os.getComputerID()))
|
||||||
|
write("Computer label: " .. tostring(os.getComputerLabel() or "<not set>"))
|
||||||
|
write("ME Bridge: " .. bridgeName)
|
||||||
|
write("Pattern records: " .. tostring(#patterns))
|
||||||
|
write("Exact duplicate groups: " .. tostring(#exactDuplicates))
|
||||||
|
write("Same-output groups: " .. tostring(#outputDuplicates))
|
||||||
|
|
||||||
|
write("")
|
||||||
|
write("EXACT DUPLICATES")
|
||||||
|
write(string.rep("=", 72))
|
||||||
|
if #exactDuplicates == 0 then write("None") end
|
||||||
|
for groupIndex, group in ipairs(exactDuplicates) do
|
||||||
|
write("GROUP " .. groupIndex .. " | copies=" .. #group.indexes
|
||||||
|
.. " | indexes=" .. table.concat(group.indexes, ","))
|
||||||
|
write(truncate(group.signature))
|
||||||
|
for _, index in ipairs(group.indexes) do write(" #" .. index .. " provider=" .. providerText(patterns[index])) end
|
||||||
|
end
|
||||||
|
|
||||||
|
write("")
|
||||||
|
write("SAME OUTPUT (CHECK MANUALLY; MAY BE INTENTIONAL)")
|
||||||
|
write(string.rep("=", 72))
|
||||||
|
if #outputDuplicates == 0 then write("None") end
|
||||||
|
for groupIndex, group in ipairs(outputDuplicates) do
|
||||||
|
write("GROUP " .. groupIndex .. " | recipes=" .. #group.indexes
|
||||||
|
.. " | indexes=" .. table.concat(group.indexes, ",")
|
||||||
|
.. " | output=" .. truncate(group.signature, 220))
|
||||||
|
end
|
||||||
|
|
||||||
|
write("")
|
||||||
|
write("COMPACT PATTERN INDEX")
|
||||||
|
write(string.rep("=", 72))
|
||||||
|
for index, pattern in ipairs(patterns) do
|
||||||
|
write("#" .. index .. " | " .. patternType(pattern)
|
||||||
|
.. " | IN " .. truncate(sideText(patternInputs(pattern)), 220)
|
||||||
|
.. " | OUT " .. truncate(sideText(patternOutputs(pattern)), 220)
|
||||||
|
.. " | PROVIDER " .. providerText(pattern))
|
||||||
|
end
|
||||||
|
|
||||||
|
write("")
|
||||||
|
write("RAW SCHEMA SAMPLES")
|
||||||
|
write(string.rep("=", 72))
|
||||||
|
for index = 1, math.min(SAMPLE_COUNT, #patterns) do
|
||||||
|
write("SAMPLE #" .. index)
|
||||||
|
write(truncate(compact(patterns[index]), SAMPLE_LIMIT))
|
||||||
|
end
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
print("ME Pattern Audit abgeschlossen.")
|
print("Kompakter ME Pattern Audit abgeschlossen.")
|
||||||
print("Pattern-Datensaetze: " .. tostring(#patterns))
|
print("Pattern: " .. tostring(#patterns))
|
||||||
print("Duplikat-Gruppen: " .. tostring(#duplicates))
|
print("Exakte Duplikat-Gruppen: " .. tostring(#exactDuplicates))
|
||||||
|
print("Gleicher Output: " .. tostring(#outputDuplicates))
|
||||||
print("Erstellt: " .. OUTPUT_FILE)
|
print("Erstellt: " .. OUTPUT_FILE)
|
||||||
print("Bitte danach Codex 'fertig' schreiben.")
|
print("Bitte danach Codex 'fertig' schreiben.")
|
||||||
|
|||||||
Reference in New Issue
Block a user