Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration system #127

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*

.project
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Eclipse .project file. Don't want to check that in by accident.

Yes, Eclipse also has a markdown editor.

66 changes: 65 additions & 1 deletion docs/misc/config.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,65 @@
# Configuration

Configurations define settings and consumer preferences that can be applied to a mod instance. NeoForge uses a configuration system using [TOML][toml] files and read with [NightConfig][nightconfig].
## Overview

In its base form, a configuration is a persistent collection of named values that can be changed by the user. Neoforge provides a configuration system that takes care of the "persistent" and "can be changed by the user" parts, as well as more Minecraft-specific tasks like syncing configs from the server to the client.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence feels a tiny bit misleading since not all configs are synced from the server to the client. Since humans tend to remember the first thing they read, I would rephrase it to not make any assumptions about the underlying system, especially since you explain it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about a "some" between "syncing" and "configs"? That's the shortest thing I can think of to break that assumption.


Configuration values in this system have a name (called "key" in the code) and are part of a section, i.e. a named collection of values. They also have a data type, which may be `String`, `Integer`, `Long`, `Double`, `Enum` or `List` (of all of these but Enum). In addition, you can define a translation key that is used by the UI to show the name of the value, a comment that is added to the file the configuration is saved in, a tooltip for the UI (this one defaults to the comment), and further restrictions on the value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a section here? What does 'of all of these but Enum' mean? This feels like a simplifying line which tries to sum up every feature within a configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That belongs to List, those are the types that work in lists (i.e. are read back the same from the toml---you can stuff in anything but then you get its toString() back). But I agree, listing all the data types here is too early, this should go in a lower section.


Sections can be nested to any depth, allowing you to build a tree to your liking. However, you don't just have one root; you have 4. Those represent the four different types of config: startup, client, server and common. Each is stored in its own file. The startup config is loaded early in the startup process, the client config only is loaded on the physical client, the common config is loaded on both client and server independently, and the server config is loaded on the server when a world is loaded and is synced over the network to the client.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, are you referring to TOML maps for sections? This overview feels like its attempting to explain too much in so little.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably can thin the overview out once the detailed sections present the data in a nicer form.


For this to work, NeoForge's configuration system naturally needs to know about your configuration values. To accomplish this, you first acquire a `Builder`, then tell that Builder about all your configuration values. For each one, the Builder will give you a provider you can later use to get the current value. When you're done, the Builder will give you a `ModConfigSpec` object that you need to register with the configuration system.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What? I understand what the code is doing here, but this is way to glossy to understand what's going on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is basically the "top of the man page you come back to to copy the correct syntax that you understand because you read the whole page ages ago" part. The text form is very high-level and only points you in the right direction, either by giving you the keywords to remember what you read before, or by preparing your mind for the concepts that will be explained later. The code is pretty much copy&paste-ready.


You also need to register to use NeoForge's configuration UI so users can edit your config in the game. This is optional, and you can create your own configuration UI if you want.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glossing over again.


In the background, NeoForge uses [TOML][toml] files read with [NightConfig][nightconfig].

## Quick Examples
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would this be a section? It feels awkward and doesn't flow.


There are multiple ways of creating a configuration. The most straightforward way is to put them into static fields and statically fill them:

```java
public class Config {
private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder();

public static final ModConfigSpec.ConfigValue<Boolean> LOG_DIRT_BLOCK = BUILDER
.comment("Whether to log the dirt block on common setup")
.define("logDirtBlock", true);

public final ModConfigSpec SPEC = BUILDER.build();
Comment on lines +22 to +29
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh? Why would we recommend this method? We shouldn't be holding intermediate values of the spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct copy from the MDK...

```

The Builder doesn't depend on any load order, so you don't have to take any precautions here.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??? I mean sure, but this isn't necessary information.


The next step is to register your configuration. This should be done in your mod's constructor like so:

```java
@Mod(ExampleMod.MODID)
public class ExampleMod {
public ExampleMod(ModContainer modContainer) {
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC);
Comment on lines +39 to +40
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, there's a lot going on here that isn't explained.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what later sections with all the details are there for (i.e. those that I haven't touched yet).

}
Comment on lines +39 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 spaces, not tabs

```

If you want to use NeoForge's configuration UI, you need to register for it like this:

```java
@Mod(ExampleMod.MODID, dist = Dist.CLIENT)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey look, why should it only be on the client? Explain.

public class ExampleModClientMod {
public ExampleMod(ModContainer modContainer) {
container.registerExtensionPoint(IConfigScreenFactory.class, (mc, parent) -> new ConfigurationScreen(container, parent));
}
Comment on lines +47 to +51
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 spaces, not tabs

```

To later query your configuration values, simply call `get()` on your static values:

```java
if (Config.LOG_DIRT_BLOCK.get()) {
LOGGER.info("dirt!");
}
```

Note that the value you get is live. It can change at any time, for example, when a config file is changed and reloaded or when the user uses the configuration UI. If you need to rely on some config values to stay stable and/or in sync, you need to make a copy of the values. This can also be used to transform values that cannot be stored directly, e.g. converting `String`s first to `ResourceLocation`s and then to `Item`s. To update your copy, listen to the `ModConfigEvent`s on the mod event bus.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a good way of explaining the configuration value. The value is cached when get is first called and then invalidated on load and reload. So, you don't really need to make a copy unless you are not using the config for its intended purpose. And why are you transforming values in configs to Items? That feels odd since that would generally make sense as part of a datapack or something else.

Also what do the events do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I'm explaining that people don't need to copy the values out of the Supplier unless they have a very special need.

And that transformation example is a straight reference to the MDK again

And about datapacks...I have a meme for you:

image

Players cannot make datapacks. They can't even start a game without using a launcher that autostarts with the system. There even is a mod out there that changes a config value in Ender IO that is available in the ingame config---just because players are not even able to use the config menu. Um, sorry, I'm starting to rant slightly off-topic.


## Creating a Configuration

Expand Down Expand Up @@ -51,6 +110,11 @@ The `ConfigValue` specific methods take in two additional components:
- A validator to make sure the deserialized object is valid
- A class representing the data type of the config value

For lists, there are two additional components you can supply:

- A supplier for new elements. This is used by the UI to allow the user adding new elements.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems wrong. The supplier past in is the default list. Are you talking about the default value in a list of acceptable values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is new with neoforged/NeoForge#1199

Sorry, should have linked that PR at the top, but it was kinda late and I was tired.

- A size range to limit the size of the list, e.g. to require them to have at least one entry.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you talking about defineInRange here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR1199, too


```java
// For some ModConfigSpec.Builder builder
ConfigValue<T> value = builder.comment("Comment")
Expand Down