Migrating to the new settings system - gnembon/fabric-carpet GitHub Wiki

This is a guide for extension developers to easily migrate from the old settings system to the new one. If you're just a player or server owner, you shouldn't need to care about this.

Do I need to do it just now?

No, you have a bit of time, don't worry. The old system is likely to stay for the rest of the 1.19 release cycle, you'll just get warnings about it in the logs. Note that the old system is going to be removed though, so don't hold upgrading for too long!

The bulk of the change

Rule descriptions and similar are now picked up via the translation system! That means that instead of defining the description and extra info in the annotation, you'll have to put them in a json file for (at least) english, and provide those translations to Carpet via your CarpetExtension. Here's how you do it:

Make your language file

You'll need a file called en_us.json inside your resources/[modid]/lang folder. That file will contain all of the translatable strings for your mod's rules and own categories.

Here's a small example of the contents it should have. If you're not adding your rules to CarpetServer.settingsManager, replace "carpet" with your manager's identifier.

{
  // Rule description
  // Follows the format "carpet.rule.<rule name>.desc"
  "carpet.rule.cleanLogs.desc": "Removes abnoxious messages from the logs",

  // Rule extra info
  // Follows the format "carpet.rule.<rule name>.extra.<index>"
  // If present, must start at index 0. It's not required
  "carpet.rule.cleanLogs.extra.0": "Doesn't display 'Maximum sound pool size 247 reached'",
  "carpet.rule.cleanLogs.extra.1": "which is normal with decent farms and contraptions",

  // Alternative rule names
  // This is to provide translated names in other languages, displayed next to the actual
  // rule name in those. It's not required.
  "carpet.rule.cleanLogs.name" : "cleanLogs",

  // Custom Categories
  // Follows the format "carpet.category.<category id>"
  // You don't have to add strings for those in RuleCategory
  "carpet.category.bugfix": "bugfix"
}

You have to do that for every rule you have. Too many rules? Don't worry, while updating Carpet Extra I made a small piece of code to generate a language file from your existing rules, with pretty comments, formatting and spacing. All you need is to have updated your dependency on Carpet, and then place the following code at some point after you've registered your rules to the SettingsManager. Change the condition in the filter to something that only matches your rules, and start the game. Note that some special cases may not be handled correctly and it'll add a comma at the end, but it should work for at least most of your rules.

Path languagePath = Path.of("en_us.json");
try (var out = new PrintStream(Files.newOutputStream(languagePath), true, StandardCharsets.UTF_8)) {
    out.print('{');
    CarpetServer.settingsManager.getRules().stream().filter(r -> r.categories().contains(CarpetExtraSettings.EXTRA))
    .sorted().forEach(pr -> {
        out.println();
        out.println("  // " + pr.name);
        if (!pr.description.isBlank()) {
            out.print("""
                  "%s": "%s",
                """.formatted(TranslationKeys.RULE_DESC_PATTERN.formatted("carpet", pr.name), pr.description.replace("\"", "\\\"")));
        }
        if (!pr.extraInfo.isEmpty()) {
            out.println();
            int i = 0;
            for (String info : pr.extraInfo) {
                out.print("""
                      "%s": "%s",
                    """.formatted(TranslationKeys.RULE_EXTRA_PREFIX_PATTERN.formatted("carpet", pr.name) + i, info));
                i++;
            }
        }
    });
    out.println('}');
} catch (IOException e) {
    throw new UncheckedIOException(e);
}

Register your language file

Once you have the file you'll need to provide its contents to the translations system. Currently you do that by returning a map in CarpetExtension::canHasTranslations(String lang). An example implementation for that method to read your newly created language file is as simple as the following (feel free to use it!):

public Map<String, String> canHasTranslations(String lang) {
    InputStream langFile = ExtensionClass.class.getClassLoader().getResourceAsStream("assets/yourModId/lang/%s.json".formatted(lang));
    if (langFile == null) {
        // we don't have that language
        return Collections.emptyMap();
    }
    String jsonData;
    try {
        jsonData = IOUtils.toString(langFile, StandardCharsets.UTF_8);
    } catch (IOException e) {
        return Collections.emptyMap();
    }
    Gson gson = new GsonBuilder().setLenient().create(); // lenient allows for comments
    return gson.fromJson(jsonData, new TypeToken<Map<String, String>>() {}.getType());
}

And that should be it! You now have a language file that's getting read and added to the Carpet translation system!

Migrate to the new classes

So a lot of classes changed, like a lot. But most of them were just changing package! All kept API that was in carpet.settings is now in carpet.api.settings. There's documentation about the replacements for the APIs that will tell you if it's a simple package change or if you have to change something else. Feel free to read it, but in this guide we'll go through what affects most extensions.

Well, first of all, change all your imports about settings to the new package, and that should already get rid of many deprecation warnings.

However, now you are likely to see a few errors arise. This is because, as we just saw, messages have moved from the annotation to the language file, so all of them will error. Assuming you migrated them to the language file, all you need to do is to just remove the strings from the annotations.

You'll have already noticed that your Validators have errors too. Their fix is very simple in most cases, you have to change the ParsedRule parameters to be CarpetRule, and that should fix them. You may need to adjust some of your validator code to use the new CarpetRule methods instead of direct field accessing to ParsedRule, but if you open ParsedRule you'll see all the replacements are documented.

Standard validators have also been moved. Now, instead of being mixed within the Validator class, they are in Validators. You should be able to find any standard validator you used in there.

Condition is now Rule.Condition, and the method name has changed to shouldRegister.

Changes in SettingsManager

There's also been a few renames in SettingsManager, mostly because of the need to return the new CarpetRule instead of ParsedRule in most methods. The new methods that replace the old will now refer to CarpetRule instead of just rule or ParsedRule (like getCarpetRule instead of just getRule), given ParsedRule may no longer be the only implementation. See the "Why this change?" section for more info about that.

Rule observers are now registered via register[Global]RuleObserver instead of add, given the old method references log4j classes directly.

And, if you're one of the few extensions that add your rules to your own SettingsManager instance, there's a few things you have to know: You should now extend carpet.api.settings.SettingsManager instead of the old type directly, though keep in mind that doing that will make you not be able to use the legacy paths inside it. Mostly because those are only present in the legacy class that extends it. You'll also need to add translation keys for the categories you use in the manager, given those keys are per-manager now, and Carpet only provides for the default manager (we can't know all the managers beforehand).

Why this change?

There's multiple motivations for the change. One of them being that the settings system is an API, even if it's not really mentioned. Carpet keeps it stable so extensions can use it to register their rules, and it's nice to make it clear that it's an API by having it in an api package. But a package rename is not enough to require all extensions to have to change.

Another reason is that it was very fragile and needed some refactoring. It has been in Carpet since before fabric was a thing, and has since been working non-stop. However, there were some stuff that was limiting in it, with some legacy systems that weren't necessary, and very strange contracts. While it hasn't affected most extensions, subtle changes have broken binary (or runtime) compatibility with extensions in what seemed harmless.

The last reason is that it wasn't really extensible. As an extension you just had this block that's defined in code, without any specific contract as to what will the method or field be doing, that you can't alter, and also you can only add rules from scanning fields in a class.

There's also the thing that the system wasn't really made for translations. There's a translation system in Carpet, but translations in Carpet aren't really that good. It's difficult to make a translation system when all messages are hardcoded directly in places like the annotations, and it just leads to code and data duplication, and to a bit of a mess. Work an upgrade to that system has started recently, and it's looking better, and one thing for sure was that rule info was going to be moved to the translation system. This makes it a perfect opportunity to apply the bulk of the changes and not just deprecate parts now, a few others in a while and so on.

This refactor resolves all three of them while keeping full binary compatibility with the old system (for now). The first one's solution is obvious, and the second and third are done via the introduction of the CarpetRule interface.

CarpetRule is the replacement for ParsedRule in this API, and instead of being a class you can only see the code for, it's a completely defined interface, that tells you what is a CarpetRule, and what you can do with it. There's no exposed direct fields or methods that don't add anything to what the class actually is, there's just that basic contract. Those convenience methods, such as resetToDefault have been moved to a satellite helper, RuleHelper, that can deal with the common rule operations that don't need a specific implementation in the rule to work.

That means, there's now a clear separation between contract and implementation. The implementation will always follow the contract, but you are not required to see exactly how and decipher those details to work. The current implementation, ParsedRule, for example, is going to be encapsulated out of the public world, given at some point Carpet may implement some of the rules with a different class! Or you, as an extension!

This system adds the interface, and it's not sealed. You are able to create your own implementation of CarpetRule and add it to your SettingsManager of choice, and it'll be managed as any other implementation, but you have full control over what happens under-the-hood. SettingsManager has a new method to add your custom CarpetRules, addCarpetRule. Note though, that method will check for duplicates!

If you want to try, check out CarpetRule and see the requirements you need to implement, and get possibilities that aren't available with current rules, such as interactive extra info menus, fully custom calls and conditions when changing, or a dynamic list of suggestions!

⚠️ **GitHub.com Fallback** ⚠️