Posted 06 October 2014 - 02:24 AM
If you have ever tried to write a script that saves a lot of things, say a bank server, you've had to repeatedly save tables whenever they are modified. I have the solution! After a day or two of tinkering around with metatables & serializing, I've created a method that makes a table save every time it is edited. I got the idea from locked tables, where people where using __newindex to detect when it was being modified, and prevent it. Here I've modified that method to additionally save the table after modification.
pastebin get h0eW3iEA
Alternatively, you can use it as a seperate file and load it as an api, in which case you can use this
Also, this method DOES prevent the use of pairs and ipairs, and the # operator is broken. I tried adding __len, but it doesn't want to work (although, that could be the emulator). Because of this, I modify the metatable in such a way that doing
As always, constructive criticism is appreciated.
Edit: Fixed bug with restoring previous tables!
pastebin get h0eW3iEA
code
local function serializeImpl( t, tTracking, sIndent )
local sType = type(t)
if sType == "table" then
t = getmetatable( t ).origin
if tTracking[t] ~= nil then
error( "Cannot serialize table with recursive entries", 0 )
end
tTracking[t] = true
if next(t) == nil then
-- Empty tables are simple
return "{}"
else
-- Other tables take more work
local sResult = "{\n"
local sSubIndent = sIndent .. " "
local tSeen = {}
for k,v in ipairs(t) do
tSeen[k] = true
sResult = sResult .. sSubIndent .. serializeImpl( v, tTracking, sSubIndent ) .. ",\n"
end
for k,v in pairs(t) do
if not tSeen[k] then
local sEntry
if type(k) == "string" and string.match( k, "^[%a_][%a%d_]*$" ) then
sEntry = k .. " = " .. serializeImpl( v, tTracking, sSubIndent ) .. ",\n"
else
sEntry = "[ " .. serializeImpl( k, tTracking, sSubIndent ) .. " ] = " .. serializeImpl( v, tTracking, sSubIndent ) .. ",\n"
end
sResult = sResult .. sSubIndent .. sEntry
end
end
sResult = sResult .. sIndent .. "}"
return sResult
end
elseif sType == "string" then
return string.format( "%q", t )
elseif sType == "number" or sType == "boolean" or sType == "nil" then
return tostring(t)
else
error( "Cannot serialize type "..sType, 0 )
end
end
local function serialize( t )
local tTracking = {}
return serializeImpl( t, tTracking, "" )
end
local function makeInternalSaving( tbl, save )
for k, v in pairs( tbl ) do
if type( v ) == "table" then
tbl[ k ] = makeInternalSaving( v )
end
end
return setmetatable( {}, {
__index = tbl,
__newindex = function( self, key, value )
if type( value ) == "table" then
tbl[ key ] = makeInternalSaving( value, save )
else
tbl[ key ] = value
end
save()
end,
__len = function() return #tbl end,
__metatable = { origin = tbl },
} )
end
function makeAutoSaving( tbl, file )
for k, v in pairs( tbl ) do
if type( v ) == "table" then
tbl[ k ] = makeInternalSaving( v )
end
end
local savable = {}
local function save()
local f = fs.open( file, "w" )
f.write( serialize( savable ) )
f.close()
end
return setmetatable( savable, {
__index = tbl,
__newindex = function( self, key, value )
if type( value ) == "table" then
tbl[ key ] = makeInternalSaving( value, save )
else
tbl[ key ] = value
end
save()
end,
__len = function( ... ) return #tbl end,
__metatable = { origin = tbl },
__call = function() return tbl end,
} )
end
How to use
Simple: Paste the code into the top of your program and write this:
makeAutoSaving( YOUR_TABLE, FILE_NAME )
Alternatively, you can use it as a seperate file and load it as an api, in which case you can use this
api.makeAutoSaving( YOUR_TABLE, FILE_NAME )
Also, this method DOES prevent the use of pairs and ipairs, and the # operator is broken. I tried adding __len, but it doesn't want to work (although, that could be the emulator). Because of this, I modify the metatable in such a way that doing
getmetatable( t ).origin
will get you the actual table, however setting variables to this table bypasses my code entirely. It is mainly there to get the length of, although you can iterate through it if necessary. Here's an example of good iteration:
for k, v in pairs( getmetatable( t ).origin ) do
if v == "some random thingy" then
t[ k ] = "r" --#modify the variable holding the modified table
end
end
As always, constructive criticism is appreciated.
Edit: Fixed bug with restoring previous tables!
Edited on 06 October 2014 - 12:12 PM