In which I finally write up my terrible config opinions that I’ve been threatening to write for years.
Goals: realistic, friendly config systems. Aimed at the Minecraft mod space but probably broadly applicable.
Human-readability is always, always, always more important than machine-parsability.
Most people will edit the config file with notepad.exe
, so any format that’s particular about delimiters, has significant indentation, or requires syntax-highlighting is out.
Comments are critical to guide the user. A loud comment character like #
helps clarify which parts are comments in the absence of syntax highlighting. Any file format without comment support is out.
As the file grows, hierarchy becomes important. “Section headers” created out of comments can create hierarchy. Underrated: careful use of blank lines to visually separate files into paragraphs and chapters. Notice how this webpage puts more space around headings than it does between paragraphs? Why can’t your config file do that?
The file extension should be something in the intersection of these two circles:
Dare I say it: maybe .txt
is the best config file extension.
Users should not need to care why you can leave double-quotes off of numbers but strings need double quotes except if the string is true
or false
then it’s back to no quotes (but "maybe"
needs quotes) and if the number is a version number like 2.3
maybe it’ll work without quotes but "2.3.4"
definitely needs quotes.
As a programmer you understand it’s because numbers, strings, and booleans are different types in the language. But why make our implementation details into their problem?
Relevant links from the StrictYAML guy:
Solution: At the config format level, everything is just a string. When you are loading the config file into a well-typed value, that’s when you choose to parse things into numbers or booleans or whatever you need in the program. Parsing a string into a number is a validation problem, not a well-formedness problem, and deserves to be reported with the same thoughtfulness that all errors are reported in the system. Reporting this well is your responsibility, not something you can kick to the authors of your TOML library.
You also don’t end up with blessed types. In every config system I’ve used, you get a small set of types which are “free” to put in the config file – strings, numbers, whatnot – but even basic non-JSON types like ResourceLocation
s are “expensive” to put in a config file and feel like an afterthought. (Well, if everything’s a string, everything is an afterthought. At least now the need for custom types is more obvious and might actually be addressed!)
Also, if everything is a string anyway, you don’t need quoting. Or you can make quoting optional/irrelevant. Or you can make quoting required, but now that every option is quoted and parsed the same way, there are no rules to remember. Point is, this opens up file format design space.
The easy annotation-based config system in Forge 1.12 works like this:
This is pretty convenient if you stay on the happy path: simple configs using simple constraints only using supported Java types (which in practice are strings, numbers, and lists of the previous). You even get a nice GUI to edit the configuration file from in-game.
The problem is that this conflates a few ideas:
String
s in the Java object. You might have a String
field with the @Config
annotation, and then a separate MyType
field which you’re actually supposed to use from code, and a post-config-change hook parses the string and updates that object.String
-> MyType
conversion is infallible, or else the best thing you can do is throw an exception from your post-config-change hook and hope it reaches the user.That said, Forge also has a lower-level configuration API which you can use if the annotation API is too limiting. (Most complex mods end up growing into this API.) It is still lacking:
int
in some sort of packed AARRGGBB
format. This will get written to config files in base-10 and cause a mess.The idea I’m getting at is that these are separate problems which should be addressed individually:
That’s not to say there should be no crosstalk between these problems: it is convenient to grab config options off a class’s fields. But it’s not enough, and when you need more functionality the escape hatch should be a designed part of the system, not something bolted on afterwards.
Information about how to serialize an value to/from a config file is ultimately up to the field, not the type of the field.
This one’s simple: it’s the “using int
to store a color” problem again. Most int
s are not for colors and should be written in base 10, but int
s that are for colors should be written in base 16. If you are generating a config GUI, the number should get a textfield/spinnerbox/whatever, and the color should get a color picker. It doesn’t matter that they’re both the same runtime type.