Page 1 of 1

ConfigManager - Handy Library for Options Menus

Posted: Wed Nov 30, 2016 4:33 am
by acb5649
I'm in the middle of making my first Lua game and I needed a way to save the user's preferences for the game window. If they set my game to fullscreen mode, I think it had better reopen in fullscreen mode. It works by directly editing the conf.lua file instead of using an outside file to save values. It's my first published bit of Lua code, so I'd love any suggestions or recommendations.

https://github.com/acb5649/ConfigManager

Re: ConfigManager - Handy Library for Options Menus

Posted: Wed Nov 30, 2016 4:47 am
by zorg
acb5649 wrote:I'm in the middle of making my first Lua game and I needed a way to save the user's preferences for the game window. If they set my game to fullscreen mode, I think it had better reopen in fullscreen mode. It works by directly editing the conf.lua file instead of using an outside file to save values. It's my first published bit of Lua code, so I'd love any suggestions or recommendations.

https://github.com/acb5649/ConfigManager
First thing, while using lua io functions isn't a sin, this won't work in many cases, like when you zipped up your project into a .love file.
I'd use love's own filesystem stuff instead. (Though i must admit, i thought io.open needed a full absolute path... if it works for you with only "conf.lua", then that's a surprise for me)

Second issue would be that love.filesystem doesn't allow you to write to the application directory, so even if you tried to write something to conf.lua, it will write that file into your save directory, if you have set an identity for the project, that is.

Re: ConfigManager - Handy Library for Options Menus

Posted: Wed Nov 30, 2016 4:58 am
by acb5649
Thank you, I feel a little silly for forgetting Love projects are zipped for distribution.

I guess I'll leave it up while I work on a replacement, since I can't use this code for release either.

EDIT:
New working version is now online, but it does use extra files stored in the LOVE save directory.
It now uses 100% love.filesystem calls instead of io, and does not impact startup speed. See the included conf.lua file for usage example. Basically, if you want to let the user control a configuration, like MSAA, use:

Code: Select all

t.window.msaa = loadMSAA() or 0


The config file will check the save directory for a file with the MSAA information inside. If such file is not found, is defaults to 0 because of the "or" condition.

Again, any and all feedback is appreciated. Look how well it went last time!

Re: ConfigManager - Handy Library for Options Menus

Posted: Wed Nov 30, 2016 6:35 am
by ivan
Yes, you should always save progress and settings in the appdata/user directory,
writing files to the installation directory is not good practice and might not work depending on the user's permissions.
Having said that, the code needs work.
Saving a separate ".dat" file for each setting is not ideal,
the easiest option is to save everything in a single Lua table or use bartbes "inifile".
Also, note that:

Code: Select all

function loadWindowPosWidth()
  local file = "windowwidth.dat"
  if love.filesystem.exists(file) then
    local size = love.filesystem.getSize(file)
    local data = love.filesystem.read(file, size)
    return tonumber(data)
  else
    return nil
  end
end
Is the same as:

Code: Select all

function loadWindowPosWidth()
  local file = "windowwidth.dat"
  if love.filesystem.exists(file) then
    local size = love.filesystem.getSize(file)
    local data = love.filesystem.read(file, size)
    return tonumber(data)
  end
end
PS. Subfolders for each "version" of the configmanager is a good idea too, since APIs change over time.
Either that or try to include the savefile version somewhere in there.

Re: ConfigManager - Handy Library for Options Menus

Posted: Wed Nov 30, 2016 5:04 pm
by Positive07
And this:
ivan wrote:

Code: Select all

function loadWindowPosWidth()
  local file = "windowwidth.dat"
  if love.filesystem.exists(file) then
    local size = love.filesystem.getSize(file)
    local data = love.filesystem.read(file, size)
    return tonumber(data)
  end
end
As this, since read tries to read it all if no size is specified

Code: Select all

function loadWindowPosWidth()
  local file = "windowwidth.dat"
  if love.filesystem.exists(file) then
    local data = love.filesystem.read(file)
    return tonumber(data)
  end
end

Re: ConfigManager - Handy Library for Options Menus

Posted: Thu Dec 01, 2016 1:05 pm
by ivan
I think the entire API could be rewritten in 2 functions:

Code: Select all

-- very bad example writing each key to a separate file
function api.loadString(key)
  local file = key..".dat"
  if love.filesystem.exists(file) then
    return love.filesystem.read(file)
  end
end

-- load custom values if their type matches the default
function api.load(defaults, destination)
  -- overwrites the defaults if no destination table is specified
  destination = destination or defaults
  -- iterate default values
  for k, v in pairs(defaults) 
    local expected = type(v)
    -- convert based on expected type
    local custom = api.loadString(k)
    if expected == "boolean" then
       if custom == "true" then
         custom = true
       elseif custom == "false" then
         custom = false
       end
    elseif expected == "number" then
       custom = tonumber(custom)
    end
    -- check loaded type vs default type
    if type(custom) == expected then
      destination[k] = custom
    end
  end
  return destination
end
Basically you need a table of default values and you overwrite that:

Code: Select all

local defaults =
{
Width=800,
Height=600,
Borderless=true,
Resizable=0,
MinWidth=0,
MinHeight=0,
Fullscreen=true,
FullscreenType="string",
Vsync=true,
MSAA=0,
Display=0,
HighDPI=true,
WindowPosWidth=0
}

api.load(defaults)
PS. Haven't tested the code, but I hope the technique is clear.
I used this approach in my games and it works quite well, except that you have to do some extra validation,
in particular with paired values like width/height...