Skip to content

Instantly share code, notes, and snippets.

@Beherith
Last active November 28, 2022 08:30
Show Gist options
  • Save Beherith/01ace18bda32a69b8089e38248f0dc78 to your computer and use it in GitHub Desktop.
Save Beherith/01ace18bda32a69b8089e38248f0dc78 to your computer and use it in GitHub Desktop.
Spits out the positions of every unit, every frame into a file, and can read it back for validation. Use /startlogging [filename] and /startvalidating [fielname]
function widget:GetInfo()
return {
name = "Desync Helper",
desc = "Spits out the positions of every unit, every frame into a file, and can read it back for validation. Use /startlogging [filename] and /startvalidating [fielname]",
author = "Beherith",
date = "2022.09.09",
license = "GNU GPL, v2 or later",
layer = -math.huge,
enabled = true,
}
end
-- IMPORTANT HEADLESS INFO: to enable this widget in headless mode, make the order of it non-zero in /luaui/config/byar.lua
-- back of napkin:
-- if there are 1000 units for 30*60*30 frames,
-- and each unitpos takes about 16 bytes, then what do we do?
-- file size will be ~ 1gb, that is fine.
-- each line of the file will be a lua table, where
-- [GAMEFRAME] = {unitID = {x= y= z= }}
-- things to check will be existence checks and
-- floored positions
-- need two commands, startlogging, and validatelog
local writelog = false
local validatelog = false
local loglines = nil
local padzeros = 7
local writelogfile
local validatelogfile
local gameFrame = 0
local frame = 0
local floor = math.floor
local format = string.format
local continuouslogging = false
local circularbuffer = {}
local circularbuffersize = 600
local writeprojectiles = false
local projectilefile
local myPlayerID = Spring.GetMyPlayerID()
local myPlayerName = Spring.GetPlayerInfo(myPlayerID,false)
local function StrPosition(unitID)
local x,y,z = Spring.GetUnitPosition(unitID)
if x then
return string.format('%f %f %f %s',x,y,z,UnitDefs[Spring.GetUnitDefID(unitID)].name)
else
return "0 0 0 nil"
end
end
local function StrProjPosition(projectileID)
local x,y,z = Spring.GetProjectilePosition(projectileID)
if x then
return string.format('%f %f %f %s',x,y,z,tostring(Spring.GetProjectileName(projectileID)))
else
return "0 0 0 nil"
end
end
local function ValidateUnit(unitID, posstr)
local ps
if Spring.ValidUnitID(unitID) then
ps = StrPosition(unitID)
if ps == posstr then
return true
end
end
local px, py, pz = Spring.GetUnitPosition(unitID)
Spring.SendCommands({"pause 1"})
Spring.MarkerAddPoint(px, py, pz, "INVALID:" .. tostring(unitID) )
Spring.Echo("Validation check failed for unitID", unitID)
Spring.Echo("Expected Position", posstr)
Spring.Echo("Ingame position:", ps)
return false
end
local function CreateDebugLine(allunits)
local segmentedtable = {}
for i,unitID in ipairs(allunits) do
local ps= StrPosition(unitID)
segmentedtable[i] = string.format("[%d] = \"%s\"", unitID, ps)
end
return table.concat(segmentedtable,', ')
end
local function CreateDebugLineProjectiles(allprojeciles)
local segmentedtable = {}
for i,projectileID in ipairs(allprojeciles) do
local ps= StrProjPosition(projectileID)
segmentedtable[i] = string.format("[%d] = \"%s\"", projectileID, ps)
end
return table.concat(segmentedtable,', ')
end
local syncerrorkey = "Sync error for "
local syncerrorlen = string.len(syncerrorkey)
local function dumpCircularBuffer()
local logf = io.open(string.format("SyncErrorLog_%d_%s.txt",gameFrame,myPlayerName),'w')
for i=gameFrame - 59, gameFrame do
logf:write(string.format("%07d return { %s }\n",i, circularbuffer[i % circularbuffersize]) )
end
logf:close()
Spring.Echo("dumpCircularBuffer: First sync error encountered, writing file")
end
function widget:AddConsoleLine(line, priority)
-- [t=00:20:24.273043][f=0033531] Sync error for [teh]Beherith in frame 33528 (got bfe67f8e, correct is 6ccb53e5)
if not continuouslogging then return end
if string.sub(line, 1, syncerrorlen) == syncerrorkey then
continuouslogging = false
dumpCircularBuffer()
end
end
function widget:Initialize()
gameFrame = Spring.GetGameFrame()
end
local startlogging = 'startlogging'
local startvalidating = 'startvalidating'
function widget:TextCommand(command)
if string.sub(command,1, string.len(startlogging)) == startlogging then
writelog = true
Spring.Echo("Starting desync logging:", command)
writelogfile = io.open(string.sub(command,string.len(startlogging) + 2),'w')
end
if string.sub(command,1, string.len(startvalidating)) == startvalidating then
validatelog = true
Spring.Echo("Starting validation", command)
validatelogfile = io.open(string.sub(command,string.len(startvalidating) + 2),'r')
--loglines = validatelogfile:lines()
frame = 0
while true do
local nextline = validatelogfile:read("*l")
if nextline then -- try to fast-forward
frame = tonumber(string.sub(nextline,1,padzeros))
if frame and frame >= gameFrame then
Spring.Echo("Fastforward", frame, string.sub(nextline,1,padzeros))
break
end
else
Spring.Echo("reached end of file",command, frame)
break
end
if frame % 1000 == 1 then
collectgarbage("collect")
collectgarbage("collect")
end
end
Spring.Echo("Fast-forwaded log to frame",frame)
end
if string.sub(command,1, string.len("dumpcircularbuffer")) == "dumpcircularbuffer" then
dumpCircularBuffer()
end
if string.sub(command,1, string.len("dumpprojectileids")) == "dumpprojectileids" then
Spring.Echo("Starting projectile logging")
projectilefile = io.open("dumpprojectileids.txt",'w')
writeprojectiles = true
end
end
local numchecks = 0
function widget:GameFrame()
gameFrame = Spring.GetGameFrame()
if writelog or continuouslogging then
local allunits = Spring.GetAllUnits()
local line = CreateDebugLine(allunits)
if continuouslogging then
circularbuffer[gameFrame % circularbuffersize] = nil
circularbuffer[gameFrame % circularbuffersize] = line
end
if writelog then
writelogfile:write(string.format("%07d return { %s }\n",gameFrame, line) )
end
end
if writeprojectiles then
-- Spring.GetProjectilesInRectangle ( number xmin, number zmin, number xmax, number zmax [, bool excludeWeaponProjectiles [, bool excludePieceProjectiles ]] )
local projectiles = Spring.GetProjectilesInRectangle( - Game.mapSizeX, -Game.mapSizeZ, 2* Game.mapSizeX, 2* Game.mapSizeZ, false, false)
--Spring.Echo(#projectiles)
local line = CreateDebugLineProjectiles(projectiles)
projectilefile:write(string.format("%07d return { %s }\n",gameFrame, line) )
end
if validatelog and validatelogfile then
local line = validatelogfile:read("*l")
if line then
local frame = tonumber(string.sub(line,1,padzeros))
if frame ~= gameFrame then
Spring.Echo("Validation log frames are off by", frame, gameFrame)
end
local tfunc = assert(loadstring(string.sub(line,padzeros+1)))
local unittable = tfunc()
for unitID, postable in pairs(unittable) do
ValidateUnit(unitID, postable)
numchecks = numchecks + 1
end
else
Spring.Echo("Reached end of line while reading validation log")
validatelog = false
end
if gameFrame % 120 == 0 then
Spring.Echo("Position checks performed = ", numchecks)
numchecks = 0
end
end
if (writelog or validatelog or continuouslogging) and gameFrame % (20*30) == 0 then
collectgarbage("collect")
collectgarbage("collect")
end
end
function widget:Shutdown()
if validatelogfile then validatelogfile:close() end
if writelogfile then writelogfile:close() end
if projectilefile then projectilefile:close() end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment