forked from DFHack/dfhack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpersist-table.lua
More file actions
230 lines (211 loc) · 7.93 KB
/
Copy pathpersist-table.lua
File metadata and controls
230 lines (211 loc) · 7.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
local _ENV = mkmodule('persist-table')
--[[
persist-table.lua
author expwnent
This module is intended to help facilitate persistent table lookups.
It is a wrapper over dfhack.persistent calls.
It supports tables of arbitrary dimension and shape.
It stores information about each table and subtable's size and children.
For convenience, all stored information is itself persistent.
It would be more efficient to cache stored information in global variables and update it on save/load but this is not yet implemented.
Ask expwnent to try this if performance is bad.
Usage:
local persistTable = require 'persist-table'
persistTable.GlobalTable.doomitude = 'doom!' -- will be stored persistently
print(persistTable.GlobalTable.doomitude) --doom!
persistTable.GlobalTable.doomitude = nil --delete the persistent record
print(persistTable.GlobalTable.doomitude) --nil
persistTable.GlobalTable.mana = {} --allocate a subtable for mana
local mana = persistTable.GlobalTable.mana --setting elements in this table will be persistent too
mana['1'] = '3' --slightly faster than persistTable.GlobalTable.mana['1'] = '3'
--be aware that if you don't change the local variable mana when the game exits and reloads a different save you will run into mysterious problems so don't do that
mana['2'] = '100'
mana.maximum = '1000' --tables can be any arbitrary shape
local globalTable = persistTable.GlobalTable --this is safe too
globalTable.mana = nil --this is safe: it will deallocate all subtables cleanly
globalTable.revengeDesire = {}
--globalTable.revengeDseire.foo = {} -- error: tables must be allocated first with parentTable.subtableName = {}
--you can check if it exists with globalTable.tableName != nil
local revengeTable = globalTable.revengeDesire
revengeTable['2'] = revengeTable['2'] or {} --this is fine too
revengeTable['2']['3'] = 'totally a lot, man'
revengeTable['3'] = {}
revengeTable['3']['2'] = 'maybe a little bit'
revengeTable['2'] = nil
-------------
And so on.
Be careful not to name your tables in a way that will conflict with other scripts! The easiest way is to just put all your tables in one giant table named based on your script.
All stored values MUST be strings. Numbers can be supported later but are not yet supported. Keep in mind there is a significant overhead for each table element so don't go totally crazy storing massive amounts of information or the game will run out of RAM.
--table._children returns a list of child keys
for _,childKey in ipairs(table._children) do
local child = table[childKey]
--blah
end
--]]
local prefix = 'persist-table'
local function ensure(name)
return dfhack.persistent.save({key=name})
end
local function gensym()
local availables = dfhack.persistent.get_all(prefix .. '$available') or {}
local available = nil
local smallest = nil
for _,candidate in pairs(availables) do
--TODO: it would be great if we could iterate over these in order but we can't
local score = tonumber(candidate.value)
--print('gensym', candidate, score, available, smallest)
if (score and (not available or score < smallest)) then
smallest = score
available = candidate
end
end
if available then
local value = available.value
available:delete()
--print('gensym: allocate ' .. value)
return value
end
--none explicitly available, so smallest unused is the next available number
local smallestUnused = ensure(prefix .. '$smallest_unused')
if smallestUnused.value == '' then
smallestUnused.value = '0'
end
local result = smallestUnused.value
smallestUnused.value = tostring(1+tonumber(result))
smallestUnused:save()
--print('gensym: allocate ' .. result)
return result
end
local function releasesym(symb)
local availables = dfhack.persistent.get_all(prefix .. '$available') or {}
for _,available in pairs(availables) do
--print('releasesym: ', symb, available.value, available)
if available.value == symb then
print('error: persist-table.releasesym(' .. symb .. '): available.value = ' .. available.value)
return
end
end
dfhack.persistent.save({key=prefix .. '$available', value=symb}, true)
--print('releasesym: unallocate ' .. symb)
end
local intCount = 7
local existIndex = intCount-0
local existValue = 1
local pointerIndex = intCount-1
local pointerValue = 1
local defaultValue = -1
local function isEmpty(table)
return next(table) == nil
end
local function deletePersistent(name)
if name == '' then
return
end
local children = dfhack.persistent.get_all(prefix .. name) or {}
for _,childKey in ipairs(children) do
local childEntry = ensure(prefix .. name .. '$$' .. childKey.value)
if childEntry.ints[existIndex] == existValue and childEntry.ints[pointerIndex] == pointerValue then
deletePersistent(childEntry.value)
end
childEntry:delete()
childKey:delete()
end
releasesym(name)
end
GlobalTable = GlobalTable or {key = 'mastertable'}
GlobalTable.mt = GlobalTable.mt or {}
GlobalTable.mt.__index = GlobalTable.mt.__index or function(theTable, key)
if key == '_children' then
return rawget(theTable,key)
end
--print(rawget(theTable,'key') .. '[' .. key .. ']')
local entry = ensure(prefix .. rawget(theTable,'key') .. '$$' .. key)
if entry.ints[existIndex] == existValue and entry.ints[pointerIndex] == defaultValue then
--print('string: ' .. entry.value)
return entry.value
end
if entry.ints[pointerIndex] == pointerValue then
--pre-existing pointer
local result = {key = entry.value}
result.mt = rawget(GlobalTable,'mt')
local childArgs = dfhack.persistent.get_all(prefix .. entry.value)
result._children = {}
for _,childArg in ipairs(childArgs or {}) do
--print(result._children, childArg.value)
table.insert(result._children, childArg.value)
end
setmetatable(result,rawget(GlobalTable,'mt'))
--print('theTable: ' .. entry.value)
return result
end
entry:delete()
--print 'theTable[key] does not exist.'
return nil
end
GlobalTable.mt.__newindex = GlobalTable.mt.__newindex or function(theTable, key, value)
--print(rawget(theTable,'key') .. '[' .. key .. '] = ' .. tostring(value))
local entry = ensure(prefix .. rawget(theTable,'key') .. '$$' .. key)
local old = entry.value
local isNew = entry.ints[existIndex] == defaultValue
if entry.ints[existIndex] == existValue and entry.ints[pointerIndex] == pointerValue then
if type(value) == 'table' and rawget(value,'mt') == rawget(GlobalTable,'mt') and entry.value == rawget(value,'key') then
--if setting it to the same table it already is, then don't do anything
return
end
deletePersistent(entry.value)
end
if not value then
--print('__newindesx: delete')
--delete
for i,child in ipairs(dfhack.persistent.get_all(prefix .. rawget(theTable,'key')) or {}) do
if child.value == key then
child:delete()
end
end
entry:delete()
return
elseif type(value) == 'string' then
--print('__newindesx: string')
entry.value = value
entry.ints[pointerIndex] = defaultValue
entry.ints[existIndex] = existValue
entry:save()
if isNew then
--print('new child!')
dfhack.persistent.save({key=prefix .. rawget(theTable,'key'), value=key}, true)
end
return
elseif type(value) == 'table' then
--print('__newindesx: table')
if rawget(value,'mt') ~= rawget(GlobalTable,'mt') then
if not isEmpty(value) then
error('setting value to an invalid table')
end
--print('__newindesx: empty table')
--empty table: allocate a thing
entry.ints[pointerIndex] = pointerValue
entry.ints[existIndex] = existValue
entry.value = gensym()
entry:save()
if isNew then
--print('new child!')
dfhack.persistent.save({key=prefix .. rawget(theTable,'key'), value=key}, true)
end
return
end
--print('__newindesx: table assignment')
entry.value = rawget(value,'key')
entry.ints[pointerIndex] = pointerValue
entry.ints[existIndex] = existValue
entry:save()
if isNew then
--print('new child!')
dfhack.persistent.save({key=prefix .. rawget(theTable,'key'), value=key}, true)
end
return
else
error('type(value) = ' .. type(value))
end
end
setmetatable(GlobalTable, GlobalTable.mt)
return _ENV