Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  

Author Topic: DFHack bug (and another maybe bug)  (Read 1981 times)

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
DFHack bug (and another maybe bug)
« on: September 13, 2014, 02:37:18 pm »

First the maybe bug:
DFHacks init functions are totally broken for every 40.x version I tested (40.10 r1 and 40.8 r2):
   ALL init functions run both when the save is loaded AND when it is unloaded.
   
   A way to confim this bug is to create the onload.init file and add "workflow enable", if the bug is in effect when you load and then immediately save the region you will see something like this:
      Enabling the plugin.
      Protecting 1 jobs.
      The plugin is enabled.
      Unprotecting 1 jobs.
      Protecting 1 jobs.
      <at this point the world is loaded>
      Unprotecting 1 jobs.
      The plugin is enabled.
      Unprotecting 1 jobs.
      Protecting 1 jobs.
      Unprotecting 1 jobs.
   This sequence leads me to believe something really weird is going on, particularly as DFHack 34.11 r5e1 reports:
      Enabling the plugin.
      Protecting 1 jobs.
      The plugin is enabled.
      Unprotecting 1 jobs.
      Protecting 1 jobs.
      <at this point the world is loaded>
      Unprotecting 1 jobs.
   For the EXACT SAME SETUP, clearly anything that is supposed to run when a region is loaded is ALSO run when it is unloaded in 40.x.
   
And now the definatly bug:
Scripts use dfhack.timeout for delays that should be persistent:
   Several default scripts use dfhack.timeout to make delays, this will not persist across a save/load, so, for example, anything that uses the default "modtools/transform-unit" with a duration can and will get "stuck" if the game is saved and loaded while the unit is transformed.


Both of these bugs where discovered when working on the latest version of Rubble. The persistent timeout issue was solved by making a simple lua module:
Code: [Select]

--[[
Rubble Persistent Timeout DFHack Lua Pseudo Module

Copyright 2014 Milo Christiansen

This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use of
this software.

Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to
the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim
that you wrote the original software. If you use this software in a product, an
acknowledgment in the product documentation would be appreciated but is not
required.

2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.
]]

local _ENV = rubble.mkmodule("timeout")

data = data or {}

dfhack.onStateChange.rubble_libs_dfhack_timeout = function(code)
if code == SC_WORLD_UNLOADED then
-- save data to disk
print("rubble.timeout: Saving persistence data.")
local dat = io.open(rubble.savedir..'/raw/rubble_libs_dfhack_timeout.persist.lua', 'w')
dat:write("\n")
dat:write("rubble.timeout.data = {\n")
for k, v in pairs(data) do
if v ~= nil then
dat:write('\t["'..k..'"] = {')
dat:write(' delay = '..v.delay..',')
dat:write(' command = [[ '..v.command..' ]]')
dat:write(' },\n')
end
end
dat:write("}\n")
dat:close()

data = {}
end
if code == SC_WORLD_LOADED then
-- load data from disk
print("rubble.timeout: Loading persistence data.")
rubble.load_script(rubble.savedir.."/raw/rubble_libs_dfhack_timeout.persist.lua")
end
end

function add(id, delay, command)
data[id] = {delay = delay, command = command}
end

function del(id)
data[id] = nil
end

function tick()
for k, v in pairs(data) do
if v ~= nil then
v.delay = v.delay - 1
if v.delay <= 0 then
dfhack.run_command(v.command)
del(k)
end
end
end

dfhack.timeout(1, 'ticks', tick)
end
tick()

rubble.timeout = _ENV

And a command version:
Code: [Select]

--[[
Rubble Persistent Timeout DFHack Lua Command

Copyright 2014 Milo Christiansen

This software is provided 'as-is', without any express or implied warranty. In
no event will the authors be held liable for any damages arising from the use of
this software.

Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to
the following restrictions:

1. The origin of this software must not be misrepresented; you must not claim
that you wrote the original software. If you use this software in a product, an
acknowledgment in the product documentation would be appreciated but is not
required.

2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.

3. This notice may not be removed or altered from any source distribution.
]]

local utils = require 'utils'

validArgs = validArgs or utils.invert({
'help',
'id',
'delete',
'delay',
'command',
})

local args = utils.processArgs({...}, validArgs)

if args.help then
print([[scripts/rubble_timeout

Create and manage persistent timeouts, eg timeouts that will survive a save/load.

Arguments:
    -help
        print this help message.
    -id
The id to use for the delay (required!).
-delete
If specified removes an action instead of adding one.
    -delay
        How long to wait in ticks (defaults to 100).
-command
The command to run (as a string! defaults to `:lua print("rubble_timeout: Did we forget something?")`).

]])
return
end

if not args.id then
error "rubble_timeout: You MUST specify an id!"
end

if not args.delay or not tonumber(args.delay) then
args.delay = 100
end

if not args.command then
args.command = ':lua print("rubble_timeout: Did we forget something?")'
end

if not args.delete then
rubble.timeout.add(args.id, tonumber(args.delay), args.command)
else
rubble.timeout.del(args.id)
end


The module uses a custom Rubble system for loading "modules" from the raw directory, this is not easy to change as the persistence mechanic depends on it. You will either need to rewrite the system or you will need to load it with the system Rubble provides, namely put this in your init.lua:
Code: [Select]

-- Important Globals
dfhack.BASE_G.rubble = dfhack.BASE_G.rubble or {}
rubble.savedir = SAVE_PATH
function rubble.load_script(name)
env = {}
setmetatable(env, { __index = dfhack.BASE_G })

local f, perr = loadfile(name, 't', env)
if f then
return safecall(f)
end
dfhack.printerr("    Error: "..perr)
end
function rubble.mkmodule(name)
rubble[name] = rubble[name] or {}
setmetatable(rubble[name], { __index = dfhack.BASE_G })
return rubble[name]
end

-- Pseudo Modules and Scripts
local scrlist = dfhack.internal.getDir(SAVE_PATH.."/raw/dfhack/")
if scrlist then
table.sort(scrlist)
for i,name in ipairs(scrlist) do
if string.match(name,'%.mod.lua$') then
print("  Module: "..name)
rubble.load_script(SAVE_PATH.."/raw/dfhack/"..name)
end
end
for i,name in ipairs(scrlist) do
if string.match(name,'%.lua$') and not string.match(name,'%.mod.lua$') then
print("  Script: "..name)
rubble.load_script(SAVE_PATH.."/raw/dfhack/"..name)
end
end
end

And put the "module" in "raw/dfhack" with the .mod.lua extension.

The other issue didn't really cause any problems, but it is really weird.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

lethosor

  • Bay Watcher
    • View Profile
Re: DFHack bug (and another maybe bug)
« Reply #1 on: September 13, 2014, 03:30:56 pm »

Quote
DFHacks init functions are totally broken for every 40.x version I tested (40.10 r1 and 40.8 r2):
   ALL init functions run both when the save is loaded AND when it is unloaded.
I'm not experiencing this, and the code that handles onLoad.init and onUnload.init appears to be working correctly. Are you sure you haven't accidentally duplicated the contents of onLoad.init in onUnload.init?
Logged
DFHack - Dwarf Manipulator (Lua) - DF Wiki talk

There was a typo in the siegers' campfire code. When the fires went out, so did the game.

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: DFHack bug (and another maybe bug)
« Reply #2 on: September 13, 2014, 03:46:27 pm »

Very sure. I duplicated all my tests with fresh versions of DF and DFHack, nothing else installed.

And this also effects init.lua as well, actually that is where I first noticed it.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

lethosor

  • Bay Watcher
    • View Profile
Re: DFHack bug (and another maybe bug)
« Reply #3 on: September 13, 2014, 04:03:22 pm »

Try saving this as "hack/scripts/sctest.lua" and running "sctest" in the DFHack console:
Code: [Select]
dfhack.onStateChange.sctest=function(state)
  if state == SC_WORLD_LOADED then print('load')
  elseif state == SC_WORLD_UNLOADED then print('unload')
  end
end
When are "load" and "unload" displayed?
Also, what platform are you using?
Logged
DFHack - Dwarf Manipulator (Lua) - DF Wiki talk

There was a typo in the siegers' campfire code. When the fires went out, so did the game.

milo christiansen

  • Bay Watcher
  • Something generic here
    • View Profile
Re: DFHack bug (and another maybe bug)
« Reply #4 on: September 13, 2014, 04:23:42 pm »

Windows, and can you wait a week or so? It'll take about that long for me to get back to you...

I was debugging persistence for my timeout module when I discovered the problem, so I was printing check points when the persistence data was loaded or saved. Lucky you this was when those states changed, and it went something like this (omitting all the other stuff that it printed):
Code: [Select]
rubble.timeout: Loaded persistence file: <table contents>
<at this point I saved my game>
rubble.timeout: Saved persistence file.
rubble.timeout: Loaded persistence file: <table contents>
rubble.timeout: Saved persistence file.
Logged
Rubble 8 - The most powerful modding suite in existence!
After all, coke is for furnaces, not for snorting.
You're not true dwarven royalty unless you own the complete 'Signature Collection' baby-bone bedroom set from NOKEAS

breadman

  • Bay Watcher
    • View Profile
Re: DFHack bug (and another maybe bug)
« Reply #5 on: September 13, 2014, 11:46:04 pm »

Quote
DFHacks init functions are totally broken for every 40.x version I tested (40.10 r1 and 40.8 r2):
   ALL init functions run both when the save is loaded AND when it is unloaded.
I'm not experiencing this, and the code that handles onLoad.init and onUnload.init appears to be working correctly. Are you sure you haven't accidentally duplicated the contents of onLoad.init in onUnload.init?

I noticed this when debugging the log-region script, and should probably have said something.  It's not the onLoad.init script code that causes the issue, but the code that fires the SC_WORLD_UNLOADED; for some reason, SC_WORLD_LOADED is fired both on load and on unload.  Oddly, when unloading a fortress, it gets fired when the fortress_entity is invalid, but while the site and civ are still available.
Logged
Quote from: Kevin Wayne, in r.g.r.n
Is a "diety" the being pictured by one of those extremely skinny aboriginal statues?