🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Programmable Config??? What now? The pressure to stray from "dumb data configs".

Started by
5 comments, last by SuperVGA 3 years, 8 months ago

Hello,

I have a problem I'm facing and I think many of us have faced it.
I'm wondering what the right approach is or what to do in this situation.

In the beginning there was a config file. This file was plain old dumb data and lived life as a yaml, json, toml or other.

Some human would author it, then it'd get fed into then the main program would read it.

Now, some humans want more power like if statements and loops.

How to satisfy them?

At many places I've worked I've seen this turn into a ‘ProgrammableConfLanguage’ that can be a pain to maintain with a syntax no one really likes.

So, what is a good approach?

Maybe just skipping the ‘programmable conf’ and jumping straight to a language like lua, or python, with a “baking step” that would run the “.lua” and output the resulting dict or definition into a plain old data “.toml”?

After all, the the “programmable conf” either needs an embedded vm in the binary that parses it, or it needs to be cooked beforehand. So why not just have a programming language that goes through a “compile/run” to generate the dumb data?

What do you think is a good way to address this pressure of DumbDataConf → ProgrammableConf, aka devs wanting more power.

(fun thing to remember: both emacs and vim's conf are programming languages)

Advertisement

I like the config files that just use some standard script/interpreted language.

Say nearly all the config for say Ruby on rails which are just Ruby or Factorio using Lua scripts to define all its object prototypes.

Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  # In the development environment your application's code is reloaded on
  # every request. This slows down response time but is perfect for development
  # since you don't have to restart the web server when you make code changes.
  config.cache_classes = false

  # Do not eager load code on boot.
  config.eager_load = false

  # Show full error reports.
  config.consider_all_requests_local = true

  # Enable/disable caching. By default caching is disabled.
  # Run rails dev:cache to toggle caching.
  if Rails.root.join('tmp', 'caching-dev.txt').exist?
    config.action_controller.perform_caching = true
    config.action_controller.enable_fragment_cache_logging = true

    config.cache_store = :memory_store
    config.public_file_server.headers = {
      'Cache-Control' => "public, max-age=#{2.days.to_i}"
    }
  else
    config.action_controller.perform_caching = false

    config.cache_store = :null_store
  end
  ...
data:extend(
{
    type = "logistic-container",
    name = "logistic-chest-storage",
    icon = "__base__/graphics/icons/logistic-chest-storage.png",
    icon_size = 64, icon_mipmaps = 4,
    flags = {"placeable-player", "player-creation"},
    minable = {mining_time = 0.1, result = "logistic-chest-storage"},
    max_health = 350,
    ...
})
...

local infinity_chest = util.table.deepcopy(data.raw["logistic-container"]["logistic-chest-storage"])
infinity_chest.type = "infinity-container"
infinity_chest.name = "infinity-chest"
infinity_chest.icon = "__base__/graphics/icons/infinity-chest.png"
...
data:extend({infinity_chest})

It is up to you to decide exactly how the interface between the config and code works. For example you might create bindings between native objects and the script ones, or maybe you do just take a bunch of hash/array/etc, objects and use those as if you would JSON etc.

The former might be useful if you decide to use scripts for a lot of other things, as you might want to access these objects again later at runtime in scripts (missions, AI, etc.).

Doing it in the main game executable is extremely nice for modders, so that might be a consideration. It also just makes changes in general easier as people can just change something then restart the level or such without a special tool step.

Do you need a configuration file at all, or can you hardcode the configuration in the executable (and recompile when the configuration changes)? When hardcoding, you have the full power of your chosen programming language available for configuration.

Does the configuration need to be directly human-editable at all, or can you hide this beneath a separate configuration program? The smarter the configuration program, the dumber the configuration file is allowed to be.

If you can't write a sufficiently powerful configuration program yourself, can you encourage your users to write their own custom scripts for generating configuration files?

If none of these work for you, using an off-the-shelf scripting language is pretty much the least bad option left over.

The “programmable configuration file” is probably too complex to be a configuration file: it wants to be a script, but no one officially introduced principled scripting and the configuration file is an architectural weak spot that can be taken advantage of.

Depending on the kind of complexity that afflicts your not-quite-configuration different strategies can be suitable.

  • Maybe it shouldn't be a user-editable configuration at all, but a fixed part of the application or a script within a document (such as a game level or mod). This is particularly important for dynamic information that needs to be computed by a nontrivial program rather than generically loaded: it should be handled consistently in one place, not in incomplete and unreliable embedded scripts that depend on users writing them correctly.
    For example, consider going beyond configuring a fixed screen resolution by determining what displays are attached to the computer this time: it should be handled by engine initialization, and the configuration should become more abstract but not more complex (e.g. from “1024 by 768, implicitly on the only/default screen” to “4:3 letterboxed on the biggest available screen”)
  • For a large quantity of simple configuration, the usefulness of a program instead of a “dumb” configuration files lies in reducing repetition, verbosity, and the effort of the human configurator in general: you can get the same benefit with programs that write a standard, simple configuration file. This has the advantage of isolating your program from special assumptions, complex behaviour and exotic data sources such programs might use (for example, scanning a directory or a web site for relevant files).
    In most cases, editing large amounts of data is a job for asset pipelines that shield users and modders from the tedious and error-prone details: big and overly detailed configuration files might be a sign that some important aspect is handled in a simplistic way.
  • For very complex configuration, you probably cannot allow scripting in a safe way: instead, paying attention to file formats and providing ways to override or include separate files can keep complexity in check.
    For example, the code in the previous post by SyncViews is quite bad: it invokes statements in a specific order (deepCopy, data.extend…), it repeats “logistic-chest-storage” one or two times too many (the mining result and probably the sprite file name), it probably also repeats the sprite file folder across other definitions, and is more verbose and less explicit than it should (a simple JSON or XML format could just say that “infinity_chest” inherits from “logistic-chest-storage”, instead of clumsily copying objects and using variables in a script).

Note that in a game the boundaries between mods, levels, game variants, and per-player and per-site configurations can be quite blurred.

Omae Wa Mou Shindeiru

Plotnus said:
Now, some humans want more power like if statements and loops

Shouldn't the right question be here “What does those humans want to do that they need loops and conditionals for”? Either you have a configuration file like JSON, INI and other or you have batch scripts but what are you trying to achieve and whyt is it used for?

I was at a similar point of ‘enhancing’ my configuration files for our build system that uses different profiles based on a target architecture, a configuration and a platform setting which are loosly coupled into a profile. For this purpose, I decided to have a text-templating language instead that looks some kind similar to C++ preprocessor code with full conditional and include support on top of any possible file format. We choose JSON as our base file format so our Alchemy library produces JSON out of those .alc files.

I honestly never ever needed something more complex for ‘configuring’ a program

This topic is closed to new replies.

Advertisement