Page 1 of 1
SaveData: A library for saving data in 35 lines
Posted: Fri Jul 07, 2017 10:40 pm
by BroccoliRaab
SaveData is a small library for saving tables of any size into a file then loading them for use later.
Usage
This will save the Table, data into a file named saveFile overwriting it if it exists and creating it if it does not.
This will return the success and error values of the love.filesystem.write function
This will return the table saved in the file named saveFile
Example
Code: Select all
local saveData = require("saveData")
function love.load()
t = {}
t.settings = {graphics = "good"}
t.settings.window = {x = 10, y = 20}
t.save = {}
t.save.scene = "boss"
saveData.save(t, "test")
local t2 = saveData.load("test")
print(t2.settings.graphics)
print(t2.settings.window.x, t2.settings.window.y)
print(t2.save.scene)
end
Github link:
https://github.com/BroccolliRaab/SaveData
Re: SaveData: A library for saving data in 35 lines
Posted: Sat Jul 08, 2017 10:37 pm
by 0x25a0
Looks nice and compact
I used the same approach in one of my projects, so here are two things I noticed in your solution:
- Indices in Lua tables don't need to be simple strings like "scene" or "settings". They can contain spaces and special characters. With the current code these indices produce invalid Lua code. It seems like numeric indices produce invalid Lua code, too. If you want to handle those kinds of indices correctly, you will need to put them in square brackets, and surround the string indices by quotes, like so:
Code: Select all
return {
["a weird index"] = "a value",
[42] = "another value",
simple = "yet another value",
-- all of these syntax forms can happily coexist within one table
}
- I assume that line 42 in your code removes the last comma after you processed all the items of a table. Note that a comma after the last table entry is actually fine in Lua:
Code: Select all
tableconstructor ::= `{´ [fieldlist] `}´
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
fieldsep ::= `,´ | `;´
(from the very bottom of http://www.lua.org/manual/5.1/manual.html)
Finally, it would be nice if the load function would return or throw the error if it encounters one, rather than swallowing it.
Re: SaveData: A library for saving data in 35 lines
Posted: Sun Jul 09, 2017 1:40 am
by BroccoliRaab
Thanks for the feedback. I have already made the changes to match your suggestions.
I kept line 42 as is , because it also erases the comma that would form after the very last curly bracket of the actual data table.
I later plan to have the option for the save function to acquire the metatable of the data table and store that as well to later be loaded.
Re: SaveData: A library for saving data in 35 lines
Posted: Sun Jul 09, 2017 6:33 am
by ivan
It's ok, but I do have some suggestions.
If you plan to support serialization of any type of table then you might as well use something like Robin's Ser.
Your serialization code is fine for simple things but unfortunately there are many easy ways to break the code:
- cyclic tables won't work (table1.value1 = table1)
- table references used as keys won't work, in fact tostring(table) won't work on line 37
- strings containing double quotation marks are not escaped properly (table["example "quoted" string"])
- note the line "love.filesystem.load" means that this lib is NOT pure Lua.
These are just a few of the problems off the top of my head, but there are many other subtle problems as well.
For example serializing numbers can be tricky as you want an easy way to define floating point precision.
The performance of the code doesn't look too good and could be faster by using table.concat.
Looks like loading save files allows malicious code and is not sandboxed in any way.
Unfortunately, super-robust serialization is tricky business.
Personally, I just make my save files a flat table, basically a list of key-value pairs.
It's faster, simpler and easier to use.
Re: SaveData: A library for saving data in 35 lines
Posted: Mon Jul 10, 2017 11:31 pm
by BroccoliRaab
I appreciate the criticism and suggestions. I was worried about performance and wasn't sure how to implement the usage of table.concat with non integer keys. I will also update it so as to escape the strings properly. I don't think I will update it to work with cyclic tables. And I feel as though table references as keys would be problematic, as it would, perhaps not require but be logical to, also have to serialize the key and store it. However, I never meant this to be the best way to serialize a table, or even be the best way to store data. I am working on a project and am trying to use only my own code, this is part of that, and thought I would share my solution.
Seeing as this is only a library intended save game data for love2d I believe it to be effective at doing that. And to that effect, the use of cyclic tables and tables as key references, and to not use love.filesystem.load seems unnecessary. But thank you for bringing up problems that I was unaware of and people that may choose to use this library may have potentially also been unaware of.
Re: SaveData: A library for saving data in 35 lines
Posted: Tue Jul 11, 2017 9:01 am
by ivan
BroccoliRaab wrote: ↑Mon Jul 10, 2017 11:31 pmI was worried about performance and wasn't sure how to implement the usage of table.concat
Performance shouldn't be an issue if you're just saving the game's options/player progress.
If you want to save more complicated things, then you need better serialization.
BroccoliRaab wrote: ↑Mon Jul 10, 2017 11:31 pmBut thank you for bringing up problems that I was unaware of and people that may choose to use this library may have potentially also been unaware of.
Sure, no worries.
Note that if your code is NOT supposed to support cycles or weak keys, then you need an assertion or some sort of error message.
Like I said, saving your options as a flat list of key-value pairs is much cleaner, here's an example using Lua's "io" module:
Code: Select all
function format(v)
local t = type(v)
if t == "string" then
v = string.format("%q", v)
elseif t == "number" or t == "boolean" then
v = tostring(v)
else
error("unsupported variable type:"..t)
end
return v
end
function save(t, fn)
-- assumes the directory exists, will fail otherwise
local f = io.open(fn, "w")
if f == nil then
return false
end
f:write("return {\n")
for k, v in pairs(t) do
k = format(k)
v = format(v)
local out = string.format('[%s]=%s,\n', k, v)
f:write(out)
end
f:write("}")
f:close()
return true
end
Alternative using table.concat:
Code: Select all
function format(v)
local t = type(v)
if t == "string" then
v = string.format("%q", v)
elseif t == "number" or t == "boolean" then
v = tostring(v)
else
error("unsupported variable type:"..t)
end
return v
end
function serialize(t, out)
out = out or {}
for k, v in pairs(t) do
k = format(k)
v = format(v)
local kv = string.format('[%s]=%s', k, v)
table.insert(out, kv)
end
return out
end
function save(t, fn)
local out = serialize(t)
local s = "return {\n"..table.concat(out, ",\n").."}"
-- todo: save to file
end
Re: SaveData: A library for saving data in 35 lines
Posted: Fri Jul 14, 2017 8:40 pm
by BroccoliRaab
I added the assertion, and now it escapes characters properly