Skip to content

Lunar Bridge

Decencies edited this page Nov 6, 2021 · 2 revisions

The Bridge

The Bridge is an inheritance based system which uses Interfaces to allow the client to work on multiple versions with minimal modification.

The following is a heavily abstracted overview of how Lunar uses a bridge system. Lunar most likely use an Annotation Processor to do most of this for them.

Let's assume we want to make a bridge method to check if Minecraft is in fullscreen.

First we will need a Bridge interface we can cast to later on, with the method we want to inherit from the Minecraft class, in this case isFullScreen().

Bridge Source Code:

public interface MinecraftBridge {
    boolean bridge$isFullScreen();
}

Then we can extend this Bridge interface within the Mixin to Minecraft.

Mixin Source Code:

import net.minecraft.client.Minecraft;

import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

@Mixin(Minecraft.class)
public abstract class MixinMinecraft implements MinecraftBridge {

    @Shadow
    public abstract boolean isFullScreen();

    @Override
    public boolean bridge$isFullScreen() {
        return isFullScreen();
    }

    // set the MinecraftBridge implementation in the BridgeManager.
    @Inject(method = "run", at = @At("HEAD"))
    public void impl$run(CallbackInfo ci) {
        BridgeManager.setMinecraftBridge(this);
    }
}

The amazing world of static

You may be wondering how we can call static minecraft methods using a non-static interface, the answer is actually quite simple. We create a class which implements the bridge, and calls the static method.

Bridge Source Code:

public interface KeyboardBridge {
    boolean bridge$areRepeatEventsEnabled();
}

Implementation Source Code:

import org.lwjgl.input.Keyboard;

public class KeyboardImplementation implements KeyboardBridge {

    @Override
    public boolean bridge$areRepeatEventsEnabled() {
        return Keyboard.areRepeatEventsEnabled();
    }
}

How it all comes together

Now you may be thinking how can you reference these bridges without calling any Minecraft code?

Lunar have created a main "BridgeManager" class which contains the instances of the bridges, which is where you can reference any bridge you need.

Bridge Class Source Code:

public class BridgeManager {
    private static IBridge INSTANCE;
    private static MinecraftBridge MINECRAFT_BRIDGE;
    private static KeyboardBridge KEYBOARD_BRIDGE;

    public static void setImplementation(IBridge bridge) {
        INSTANCE = bridge;
        INSTANCE.enable();
    }

    public static void setMinecraftBridge(MinecraftBridge bridge) {
        MINECRAFT_BRIDGE = bridge;
    }

    public static void setKeyboardBridge(KeyboardBridge bridge) {
        KEYBOARD_BRIDGE = bridge;
    }

    public static IBridge getImplementation() {
        return INSTANCE;
    }

    public static MinecraftBridge getMinecraftBridge() {
        return MINECRAFT_BRIDGE;
    }

    public static KeyboardBridge getKeyboardBridge() {
        return KEYBOARD_BRIDGE;
    }
}

Inside of IBridge, we can see utility methods, in this example, a utility method to create a ResourceLocation instance cast to ResourceLocationBridge:

public interface IBridge {

    void enable();

    ResourceLocationBridge initResourceLocation(String resourcePath);
}

And finally here's our BridgeImplementation class:

import net.minecraft.util.ResourceLocation;

public class BridgeImplementation implements IBridge {

    public void enable() {
        Bridge.setKeyboardBridge(new KeyboardImplementation());
    }

    public ResourceLocationBridge initResourceLocation(String resourcePath) {
        return (ResourceLocationBridge) new ResourceLocation(resourcePath);
    }
}

This bridge implementation class is version dependent, which requires it to be rewritten for different versions of Minecraft.

Note than in the enable() method within BridgeImplementation, static bridges are declared.

Instance dependent bridges are set in their respective Mixin classes, with one exception:

Hierarchical classes that can be called from a single bridge instance e.g. BridgeManager.getMinecraft().getPlayer() only requires the MinecraftBridge to be set.

The Reference Class

Lunar decided to create another class for commonly used references within the client to shorten their code.

A quick example would be shortening BridgeManager.getMinecraftBridge().bridge$getFontRenderer() to Ref.getFontRenderer().

Here's the source code for the Reference Class:

public class Ref {
    public static MinecraftBridge getMinecraft() {
        return BridgeManager.getMinecraftBridge();
    }

    public static FontRendererBridge getFontRenderer() {
        return getMinecraft().bridge$getFontRenderer();
    }
}

Bridges and Final Classes

In special cases, you might not be able to cast a Minecraft class to a Bridge interface within a Mixin.

Take this example:

public interface ItemStackBridge {
    String bridge$getDisplayName();
}
@Mixin(ItemStack.class)
public abstract class MixinItemStack implements ItemStackBridge {
    
    @Shadow
    public abstract String getDisplayName();
    
    public String bridge$getDisplayName() {
        return getDisplayName();
    }
}

If we wanted to cast a normal ItemStack to ItemStackBridge it will give us an error telling us that ItemStack is declared final.

package net.minecraft.item;

import ...

public final class ItemStack {
    // ^ Declared final.
    ...
}

To avoid this issue, you can use what's called an AccessTransformer which is built into ForgeGradle.

Here's how you'd remove the final flag from the ItemStack class using an AccessTransformer config:

public-f net.minecraft.item.ItemStack

The -f will remove the final flag from the class. All that's left to do is re-sync the gradle project and let ForgeGradle apply the transformation.

Another, simpler approach would be casting the instance of ItemStack to an Object, and then casting to ItemStackBridge. Thanks, LlamaLad7.

public ItemStackBridge bridge$getItem() {
    return (ItemStackBridge)((Object) getItem());
}
Clone this wiki locally