Last active
November 28, 2022 08:30
-
-
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]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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