-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSFUtils_Tables.lua
More file actions
340 lines (286 loc) · 10.9 KB
/
Copy pathSFUtils_Tables.lua
File metadata and controls
340 lines (286 loc) · 10.9 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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
--[[
This module provides a suite of helper functions for safe and efficient table manipulation.
It focuses on preventing runtime errors caused by nil values, handling circular references
during copying, and merging configuration data with defaults.
Key Features:
Safety: Functions like safeTable and safeClearTable prevent crashes when passed nil or non-table values.
Deep Copying: deepCopy handles circular references and preserves metatables.
Configuration Merging: defaultMissing fills in missing settings without overwriting existing user data.
Debugging: dTable provides recursive string representation of complex tables.
Technical Notes
Metatable Preservation: deepCopy explicitly retrieves and sets the metatable of the original table,
ensuring custom behaviors (like __index) are maintained in the copy.
Circular Reference Safety: The seen table in deepCopy is crucial for preventing stack overflow when
copying tables that reference themselves.
Performance: getSize uses next() which is generally faster and more memory-efficient than iterating
with pairs() for large tables.
Return Types: Most functions return the modified table or a new table, allowing for method chaining
or immediate assignment.
--]]
local sfutil = LibSFUtils
assert(sfutil, "LibSFUtils_Global must be loaded before this file")
--[[
sfutil.dTable(vtable, depth, name)
Recursively converts a table into a formatted string for debugging.
Parameters:
vtable (table): The table to inspect.
depth (number, optional): Maximum recursion depth. Defaults to 0 (no recursion).
name (string, optional): A prefix name for the root table (used in output labels).
Behavior:
Iterates through keys and values.
Functions are labeled as (function).
Nested tables are recursively processed up to the depth limit.
Returns a concatenated string of key -> value pairs.
Returns: String representation of the table.
Note: If vtable is not a table, it returns the stringified value using sfutil.str.
--]]
function sfutil.dTable(vtable, depth, name)
if type(vtable) ~= "table" then
return sfutil.str(vtable)
end
depth = math.max(0, tonumber(depth) or 0)
local function appendVal(tbl, val)
tbl[#tbl+1] = val
return tbl
end
local arg = {}
local vt
if depth == nil or depth < 1 then return table.concat(arg) end
for k, v in pairs(vtable) do
vt = type(v)
if vt == "function" then
appendVal(arg, sfutil.str(name, " : ", k, " -> (function), \n"))
elseif vt == "table" then
appendVal(arg, (sfutil.dTable(v, depth - 1, name.." - ["..tostring(k).."]")))
else
appendVal(arg, sfutil.str(name, " : ", k, " -> ", v, ", \n"))
end
end
return table.concat(arg)
end
--[[ ---------------------
sfutil.defaultMissing(svtable, defaulttable)
Merges default values into a saved table without overwriting existing data.
Parameters:
svtable (table): The existing saved data (may be nil).
defaulttable (table): The template of default values.
Behavior:
If svtable is nil, returns a deep copy of defaulttable.
If svtable is not a table, returns a safe empty table.
Iterates through defaulttable:
If a key is missing in svtable, it is added.
If the default value is a table, it recursively fills missing sub-keys.
Existing values in svtable are preserved.
--]]
function sfutil.defaultMissing(svtable, defaulttable)
if svtable == nil then return sfutil.safeTable(sfutil.deepCopy(defaulttable)) end
if type(svtable) ~= 'table' or type(defaulttable) ~= 'table' then return sfutil.safeTable(svtable) end
for k in pairs(defaulttable) do
if svtable[k] == nil then
if type( defaulttable[k] )=='table' then
svtable[k] = {}
sfutil.defaultMissing( svtable[k], defaulttable[k])
else
svtable[k] = defaulttable[k]
end
end
end
return svtable
end
--[[ ---------------------
sfutil.deepCopy(orig, seen)
Creates a deep copy of a table, handling circular references and metatables.
Parameters:
orig (any): The value to copy.
seen (table, internal): Tracks already copied tables to prevent infinite loops.
Behavior:
If orig is not a table, returns it directly.
Detects circular references using the seen table and returns the existing copy.
Copies all keys and values recursively.
The resulting table also gets a reference to the metatable from the orig table.
Returns: A new independent table (or the original value if not a table).
Use Case: Creating isolated snapshots of configuration or state without affecting the original.
The seen argument is typically nii when called from
outside, and a table of already seen tables when
called internally (recursively). This is so that
an orig table that contains multiple copies of another
table will copy the first instance of that table to the result
table and after that set all other instance of that table to
reference the first full copy.
--]]
function sfutil.deepCopy(orig, seen)
seen = sfutil.safeTable(seen)
if type(orig) ~= 'table' then return orig end
if seen[orig] then
return seen[orig]
end
local tcopy = {}
seen[orig] = tcopy
local dcpy = sfutil.deepCopy -- alias
for orig_key, orig_value in pairs(orig) do
if orig_key then
tcopy[dcpy(orig_key, seen)] = dcpy(orig_value, seen)
end
end
local mt = getmetatable(orig)
if mt then
setmetatable(tcopy, mt)
end
return tcopy
end
--[[ ---------------------
sfutil.safeTable(tbl)
Ensures a value is a table.
Parameters: tbl (any).
Behavior:
If tbl is a table, returns it (reference).
If tbl is nil or not a table, returns a new empty table {}.
--]]
function sfutil.safeTable(tbl)
if type(tbl) ~= "table" then
return {}
end
return tbl
end
--[[ ---------------------
sfutil.safeClearTable(tbl)
Clears all entries in a table safely.
Parameters: tbl (any).
Behavior:
If tbl is not a table, returns a new empty table {}.
If tbl is a table, iterates through keys and sets them to nil (similar to ZO_ClearTable).
Returns the original table after clearing it, or a new empty table if the input
was not a table. The original table reference is preserved when clearing succeeds.
The difference between this and ZO_ClearTable is the initial safety check
and that we return the empty table (which might have been created if
the parameter was not a proper table). ZO_ClearTable will error if passed nil.
Returns the original table after clearing it, or a new empty table if the input
was not a table. The original table reference is preserved when clearing succeeds.
--]]
function sfutil.safeClearTable(tbl)
if type(tbl) ~= "table" then
return {}
end
-- Equivalent behavior to ZO_ClearTable for valid tables.
for k in pairs(tbl) do
tbl[k] = nil
end
return tbl
end
--[[ ---------------------
sfutil.RemainsInList(listA, listB)
Finds items in listA that are not present in listB.
Parameters:
listA (table): The primary list (values are treated as keys in the result).
listB (table): The exclusion list (values are treated as keys for lookup).
Behavior:
Iterates through listA.
If a value from listA is not a key in listB, it is added to the result.
Returns a table where keys are the remaining values and values are 1.
Returns: Table of listA (all) items that are not in listB (known).
Note: This function assumes listB is used as a set (keys matter, values don't).
--]]
function sfutil.RemainsInList(listA, listB)
local newList = {}
if listA == nil or listB == nil then return newList end
for _, v in pairs(listA) do
if listB[v] == nil then
newList[v] = 1
end
end
return newList
end
--[[ ---------------------
sfutil.getSize(tbl)
Counts the number of items in a table.
Handles non-contiguous tables. More safe version of ZOS's NonContiguousCount()
Parameters: tbl (any).
Behavior:
Returns 0 if tbl is not a table.
Uses next() to iterate, handling non-contiguous arrays and dictionary-style tables correctly.
Returns: Integer count of keys or 0 if tbl is not a table.
Advantage: More robust than #tbl (which only works for contiguous arrays) and safer than pairs() loops for counting.
--]]
function sfutil.GetSize(tbl)
if type(tbl) ~= "table" then
return 0
end
local count = 0
local k = next(tbl, nil) -- first key
while k do
count = count + 1
k = next(tbl, k) -- subsequent keys
end
--[[
for _ in pairs(tbl) do
count = count + 1
end
--]]
return count
end
--[[ ---------------------
sfutil.isEmpty(tbl)
Checks if a table is empty.
Parameters: tbl (any).
Behavior:
Returns true if the table has no keys.
Returns false if the table has keys.
Returns nil if tbl is nil or not a table.
Note: Unlike ZO_IsTableEmpty, this explicitly returns nil for non-table inputs. Also, I regard a
nil "tbl" to be not an "empty" table as it is not a table at all. ZOS seems to disagree.
--]]
function sfutil.isEmpty(tbl)
if type(tbl) ~= "table" then
return nil
end
return next(tbl) == nil
end
--[[ Gets a new merged table with all keys from table1 and table2. If the same key exists in both tables,
table1's value is used. Performs shallow copies to fill the merged table.
Does not copy metatables from either original table!
--]]
function sfutil.tableMerge(table1, table2)
-- Early exit optimization: if table2 is invalid, only copy table1
if not table2 or type(table2) ~= "table" then
if type(table1) ~= "table" then return {} end
return ZO_ShallowTableCopy(table1)
end
local merged
if type(table1) ~= "table" then
merged = {}
else
merged = ZO_ShallowTableCopy(table1)
end
for key2, value2 in pairs(table2) do
if merged[key2] == nil then
merged[key2] = value2
end
end
return merged
end
--[[ Gets a new merged array table with all values from table1 and table2.
The table2 values will be appended to table1 values with keys = #merged + key2.
Performs shallow copies to fill the merged array.
Notes:
- `arrayMerge()` expects sequential numeric arrays.
- The returned table is a shallow copy.
- Nested tables are shared between the original and merged arrays.
- The original tables are not modified.
- Values from `table2` are always appended after values from `table1`.
--]]
function sfutil.arrayMerge(table1, table2)
local merged
if type(table1) ~= "table" then
merged = {}
else
merged = ZO_ShallowTableCopy(table1)
end
if type(table2) == "table" then
local cnt = #merged
-- Use ipairs for better performance on sequential arrays
for idx, value2 in ipairs(table2) do
merged[cnt + idx] = value2
end
end
return merged
end