Skip to content

Instantly share code, notes, and snippets.

@Juuxel
Created May 27, 2022 13:07
Show Gist options
  • Save Juuxel/d5330e6bc3333fef9ead2d26b8093822 to your computer and use it in GitHub Desktop.
Save Juuxel/d5330e6bc3333fef9ead2d26b8093822 to your computer and use it in GitHub Desktop.
Architectury Plugin-less multiproject mods using Architectury Loom

Architectury Plugin-less multiproject mods using Architectury Loom

The What and the Why

With Architectury Loom 0.12.0's ModSettings feature, you can completely replace Architectury Plugin using pure Loom.

This leads to

  • faster build configuration times (no extra Gradle plugins needing to execute)
  • faster build times (no extra tasks to run)
  • a build that can be easier to port to other platforms

You also don't have to separate Forge sources into their own package anymore, as Forge sees all the classes as being from the same mod.

The features of Architectury Plugin often have simpler replacements. For example, you can replace @ExpectPlatform with standard Java features like ServiceLoader to bridge calls to common code.

However, note that you can't enjoy nice quality-of-life features from Architectury Injectables anymore and have to implement e.g. platform-specific methods yourself via services.

The How

This assumes you have an existing Architectury mod set up with one of the templates or a similar setup.

The Gradle side

Removing the Arch Plugin can be done with a simple checklist:

  • Remove all id("architectury-plugin") and apply plugin: 'architectury-plugin' variations from buildscripts.
  • Remove all architectury {} blocks from buildscripts.
  • The transformProductionX configurations can be replaced everywhere with Loom's own namedElements as we're not transforming anything anymore.

Finally, you need to add this to the root build.gradle(.kts):

Groovy DSL

subprojects {
    if (project.path != ':common') {
        loom {
            runConfigs.configureEach { ideConfigGenerated = true }
            mods {
                main { // to match the default mod generated for Forge
                    sourceSet project.sourceSets.main
                    sourceSet project(':common').sourceSets.main
                }
            }
        }
    }
}

Kotlin DSL

subprojects {
    if (path != ":common") {
        loom {
            runConfigs.configureEach { isIdeConfigGenerated = true }
            mods {
                fun Project.sourceSets() = extensions.getByName<SourceSetContainer>("sourceSets")

                register("main") { // to match the default mod generated for Forge
                    sourceSet project.sourceSets().getByName("main")
                    sourceSet project(":common").sourceSets().getByName("main")
                }
            }
        }
    }
}

Removing Architectury Injectables usages using ServiceLoader

This applies mostly to the @ExpectPlatform annotation, but also other classes in the architectury-injectables library like Platform or @PlatformOnly.

Instead of using @ExpectPlatform, let's put the methods in an interface called PlatformBridges:

common/src/main/java/my_mod/PlatformBridges.java

package my_mod;

public interface PlatformBridges {
    void sayHello();
}

Then we can implement that interface how we like on Fabric, Forge and Quilt:

fabric/src/main/java/my_mod/fabric/PlatformBridgesImpl.java

package my_mod.fabric;

public class PlatformBridgesImpl implements PlatformBridges {
    @Override
    public void sayHello() {
        System.out.println("Hello, Fabric!");
    }
}

The impl class has to be public and have a public no-arg constructor!

Our impl class is hooked up to the interface using a service file which we will put in META-INF/services of each platform subproject's resources. The file name should be the fully qualified class name of the interface, for example my_mod.PlatformBridges.

In the file, write the fully qualified name of the implementation class.

fabric/src/main/resources/META-INF/services/my_mod.PlatformBridges

my_mod.fabric.PlatformBridgesImpl

Finally, the service can be loaded using ServiceLoader by adding a bit of code to the common interface:

common/src/main/java/my_mod/PlatformBridges.java

import com.google.common.base.Suppliers;
import java.util.ServiceLoader;
import java.util.function.Supplier;

public interface PlatformBridges {
    private static final Supplier<PlatformBridges> INSTANCE = Suppliers.memoize(() -> {
        var serviceLoader = ServiceLoader.load(PlatformBridges.class);
        return serviceLoader.findFirst().orElseThrow(() -> new RuntimeException("Could not find platform implementation for My Mod!"));
    });

    public static PlatformBridges get() {
        return INSTANCE.get();
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment