SaveData: A library for saving data in 35 lines

Showcase your libraries, tools and other projects that help your fellow love users.
Post Reply
User avatar
BroccoliRaab
Prole
Posts: 8
Joined: Tue Jun 27, 2017 3:58 am

SaveData: A library for saving data in 35 lines

Post by BroccoliRaab »

SaveData is a small library for saving tables of any size into a file then loading them for use later.

Usage

Code: Select all

saveData.save(data, saveFile)
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

Code: Select all

saveData.load(saveFile)
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
User avatar
0x25a0
Prole
Posts: 36
Joined: Mon Mar 20, 2017 10:08 pm
Contact:

Re: SaveData: A library for saving data in 35 lines

Post 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.
User avatar
BroccoliRaab
Prole
Posts: 8
Joined: Tue Jun 27, 2017 3:58 am

Re: SaveData: A library for saving data in 35 lines

Post 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.
User avatar
ivan
Party member
Posts: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: SaveData: A library for saving data in 35 lines

Post 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.
User avatar
BroccoliRaab
Prole
Posts: 8
Joined: Tue Jun 27, 2017 3:58 am

Re: SaveData: A library for saving data in 35 lines

Post 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.
User avatar
ivan
Party member
Posts: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: SaveData: A library for saving data in 35 lines

Post 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
User avatar
BroccoliRaab
Prole
Posts: 8
Joined: Tue Jun 27, 2017 3:58 am

Re: SaveData: A library for saving data in 35 lines

Post by BroccoliRaab »

I added the assertion, and now it escapes characters properly
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Amazon [Bot], Bing [Bot] and 2 guests