diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 23d1f23b4..85e8f9878 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -72,6 +72,8 @@ jarSets { dependencies { modCompileOnly("net.fabricmc:fabric-loader:${property("fabric_loader_version")}") + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")!!) + testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/font/AsyncFontTexture.java b/common/src/backend/java/dev/engine_room/flywheel/backend/font/AsyncFontTexture.java deleted file mode 100644 index 4189aa1d7..000000000 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/font/AsyncFontTexture.java +++ /dev/null @@ -1,159 +0,0 @@ -package dev.engine_room.flywheel.backend.font; - -import java.nio.file.Path; -import java.util.List; - -import org.apache.commons.compress.utils.Lists; -import org.jetbrains.annotations.Nullable; - -import com.mojang.blaze3d.font.SheetGlyphInfo; -import com.mojang.blaze3d.platform.NativeImage; -import com.mojang.blaze3d.platform.TextureUtil; -import com.mojang.blaze3d.systems.RenderSystem; - -import dev.engine_room.flywheel.lib.internal.GlyphExtension; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.minecraft.client.gui.font.GlyphRenderTypes; -import net.minecraft.client.gui.font.glyphs.BakedGlyph; -import net.minecraft.client.renderer.texture.AbstractTexture; -import net.minecraft.client.renderer.texture.Dumpable; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; - -public class AsyncFontTexture extends AbstractTexture implements Dumpable { - private static final int SIZE = 256; - private final GlyphRenderTypes renderTypes; - private final ResourceLocation name; - private final boolean colored; - private final Node root; - - private final List uploads = Lists.newArrayList(); - - private boolean flushScheduled = false; - - public AsyncFontTexture(ResourceLocation name, GlyphRenderTypes renderTypes, boolean colored) { - this.name = name; - this.colored = colored; - this.root = new Node(0, 0, 256, 256); - this.renderTypes = renderTypes; - - if (RenderSystem.isOnRenderThreadOrInit()) { - this.init(); - } else { - RenderSystem.recordRenderCall(this::init); - } - } - - @Override - public void load(ResourceManager resourceManager) { - } - - @Override - public void close() { - this.releaseId(); - } - - @Nullable - public BakedGlyph add(SheetGlyphInfo glyphInfo) { - if (glyphInfo.isColored() != this.colored) { - return null; - } - Node node = this.root.insert(glyphInfo); - if (node != null) { - if (RenderSystem.isOnRenderThreadOrInit()) { - this.bind(); - glyphInfo.upload(node.x, node.y); - } else { - uploads.add(new Upload(glyphInfo, node.x, node.y)); - - if (!flushScheduled) { - RenderSystem.recordRenderCall(this::flush); - flushScheduled = true; - } - } - var out = new BakedGlyph(this.renderTypes, ((float) node.x + 0.01f) / 256.0f, ((float) node.x - 0.01f + (float) glyphInfo.getPixelWidth()) / 256.0f, ((float) node.y + 0.01f) / 256.0f, ((float) node.y - 0.01f + (float) glyphInfo.getPixelHeight()) / 256.0f, glyphInfo.getLeft(), glyphInfo.getRight(), glyphInfo.getUp(), glyphInfo.getDown()); - - ((GlyphExtension) out).flywheel$texture(name); - - return out; - } - return null; - } - - @Override - public void dumpContents(ResourceLocation resourceLocation, Path path) { - String string = resourceLocation.toDebugFileName(); - TextureUtil.writeAsPNG(path, string, this.getId(), 0, 256, 256, i -> (i & 0xFF000000) == 0 ? -16777216 : i); - } - - public void init() { - TextureUtil.prepareImage(colored ? NativeImage.InternalGlFormat.RGBA : NativeImage.InternalGlFormat.RED, this.getId(), 256, 256); - } - - public void flush() { - this.bind(); - for (Upload upload : this.uploads) { - upload.info.upload(upload.x, upload.y); - } - - uploads.clear(); - - flushScheduled = false; - } - - public record Upload(SheetGlyphInfo info, int x, int y) { - } - - @Environment(value = EnvType.CLIENT) - static class Node { - final int x; - final int y; - private final int width; - private final int height; - @Nullable - private Node left; - @Nullable - private Node right; - private boolean occupied; - - Node(int x, int y, int width, int height) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - @Nullable Node insert(SheetGlyphInfo glyphInfo) { - if (this.left != null && this.right != null) { - Node node = this.left.insert(glyphInfo); - if (node == null) { - node = this.right.insert(glyphInfo); - } - return node; - } - if (this.occupied) { - return null; - } - int i = glyphInfo.getPixelWidth(); - int j = glyphInfo.getPixelHeight(); - if (i > this.width || j > this.height) { - return null; - } - if (i == this.width && j == this.height) { - this.occupied = true; - return this; - } - int k = this.width - i; - int l = this.height - j; - if (k > l) { - this.left = new Node(this.x, this.y, i, this.height); - this.right = new Node(this.x + i + 1, this.y, this.width - i - 1, this.height); - } else { - this.left = new Node(this.x, this.y, this.width, j); - this.right = new Node(this.x, this.y + j + 1, this.width, this.height - j - 1); - } - return this.left.insert(glyphInfo); - } - } -} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/CodePointMapMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/CodePointMapMixin.java index 27159cd61..0dfcd993c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/CodePointMapMixin.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/CodePointMapMixin.java @@ -1,146 +1,59 @@ package dev.engine_room.flywheel.backend.mixin; -import java.util.Arrays; import java.util.function.IntFunction; -import org.jetbrains.annotations.Nullable; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; + import net.minecraft.client.gui.font.CodepointMap; @Mixin(CodepointMap.class) public class CodePointMapMixin { - @Shadow - @Final - private T[][] blockMap; - - @Shadow - @Final - private T[] empty; - - @Shadow - @Final - private IntFunction blockConstructor; - @Unique private final Object flywheel$lock = new Object(); - /** - * @author - * @reason - */ - @Overwrite - public void clear() { + @WrapMethod(method = "clear") + private void flywheel$wrapClearAsSynchronized(Operation original) { synchronized (flywheel$lock) { - Arrays.fill(this.blockMap, this.empty); + original.call(); } } - /** - * @author - * @reason - */ - @Nullable - @Overwrite - public T get(int index) { - int i = index >> 8; - int j = index & 0xFF; + @WrapMethod(method = "get") + private T flywheel$wrapGetAsSynchronized(int index, Operation original) { synchronized (flywheel$lock) { - return this.blockMap[i][j]; + return original.call(index); } } - /** - * @author - * @reason - */ - @Nullable - @Overwrite - public T put(int index, T value) { - int i = index >> 8; - int j = index & 0xFF; - T object; + @WrapMethod(method = "put") + private T flywheel$wrapPutAsSynchronized(int index, T value, Operation original) { synchronized (flywheel$lock) { - T[] objects = this.blockMap[i]; - if (objects == this.empty) { - objects = this.blockConstructor.apply(256); - this.blockMap[i] = objects; - objects[j] = value; - return null; - } - object = objects[j]; - objects[j] = value; + return original.call(index, value); } - return object; } - /** - * @author - * @reason - */ - @Overwrite - public T computeIfAbsent(int index, IntFunction valueIfAbsentGetter) { - int i = index >> 8; - int j = index & 0xFF; - T out; + @WrapMethod(method = "computeIfAbsent") + private T flywheel$wrapComputeIfAbsentAsSynchronized(int index, IntFunction valueIfAbsentGetter, Operation original) { synchronized (flywheel$lock) { - T[] objects = this.blockMap[i]; - T object = objects[j]; - if (object != null) { - return object; - } - if (objects == this.empty) { - objects = this.blockConstructor.apply(256); - this.blockMap[i] = objects; - } - out = valueIfAbsentGetter.apply(index); - objects[j] = out; + return original.call(index, valueIfAbsentGetter); } - return out; } - /** - * @author - * @reason - */ - @Nullable - @Overwrite - public T remove(int index) { - int i = index >> 8; - int j = index & 0xFF; - T object; + @WrapMethod(method = "remove") + private T flywheel$wrapRemoveAsSynchronized(int index, Operation original) { synchronized (flywheel$lock) { - T[] objects = this.blockMap[i]; - if (objects == this.empty) { - return null; - } - object = objects[j]; - objects[j] = null; + return original.call(index); } - return object; } - /** - * @author - * @reason - */ - @Overwrite - public void forEach(CodepointMap.Output output) { + @WrapMethod(method = "forEach") + private void flywheel$wrapForEachAsSynchronized(CodepointMap.Output output, Operation original) { synchronized (flywheel$lock) { - for (int i = 0; i < this.blockMap.length; ++i) { - T[] objects = this.blockMap[i]; - if (objects == this.empty) continue; - for (int j = 0; j < objects.length; ++j) { - T object = objects[j]; - if (object == null) continue; - int k = i << 8 | j; - output.accept(k, object); - } - } + original.call(output); } } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontSetMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontSetMixin.java index 28a7cf5f7..39b53f211 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontSetMixin.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontSetMixin.java @@ -1,121 +1,31 @@ package dev.engine_room.flywheel.backend.mixin; -import java.util.List; - import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.google.common.collect.Lists; -import com.mojang.blaze3d.font.GlyphInfo; -import com.mojang.blaze3d.font.GlyphProvider; -import com.mojang.blaze3d.font.SheetGlyphInfo; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.sugar.Local; -import dev.engine_room.flywheel.backend.font.AsyncFontTexture; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.IntList; +import dev.engine_room.flywheel.lib.internal.FontTextureExtension; import net.minecraft.client.gui.font.FontSet; -import net.minecraft.client.gui.font.GlyphRenderTypes; -import net.minecraft.client.gui.font.glyphs.BakedGlyph; -import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.gui.font.FontTexture; import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; @Mixin(FontSet.class) public abstract class FontSetMixin { + // Replace serial random with thread-local random @Shadow @Final - private TextureManager textureManager; - - @Shadow - private BakedGlyph missingGlyph; - - @Shadow - @Final - private ResourceLocation name; - - @Shadow - @Final - private Int2ObjectMap glyphsByWidth; - - @Shadow - public abstract BakedGlyph getGlyph(int character); - - @Unique - private static final RandomSource RANDOM = RandomSource.createNewThreadLocalInstance(); - - @Unique - private List flywheel$textures; - - - @Inject(method = "", at = @At("TAIL")) - public void init(TextureManager textureManager, ResourceLocation name, CallbackInfo ci) { - flywheel$textures = Lists.newArrayList(); + private static RandomSource RANDOM = RandomSource.createNewThreadLocalInstance(); + + @ModifyExpressionValue(method = "stitch", at = @At(value = "NEW", target = "net/minecraft/client/gui/font/FontTexture")) + private FontTexture flywheel$setNameAfterCreate(FontTexture original, @Local ResourceLocation name) { + // Forward the name to the FontTexture so we can forward the name to the BakedGlyphs it creates. + // We need to know that to determine which Material to use when actually setting up instances. + ((FontTextureExtension) original).flywheel$setName(name); + return original; } - - @Inject(method = "reload", at = @At("TAIL")) - public void reload(List glyphProviders, CallbackInfo ci) { - flywheel$closeTextures(); - } - - /** - * @author Jozufozu - * @reason Use thread safe random - */ - @Overwrite - public BakedGlyph getRandomGlyph(GlyphInfo glyph) { - IntList intList = this.glyphsByWidth.get(Mth.ceil(glyph.getAdvance(false))); - if (intList != null && !intList.isEmpty()) { - // Override to use thread safe random - // FIXME: can we just replace the static field instead? - return this.getGlyph(intList.getInt(RANDOM.nextInt(intList.size()))); - } - return this.missingGlyph; - } - - /** - * @author Jozufozu - * @reason Use our stitching - */ - @Overwrite - private BakedGlyph stitch(SheetGlyphInfo glyphInfo) { - for (AsyncFontTexture fontTexture : flywheel$textures) { - BakedGlyph bakedGlyph = fontTexture.add(glyphInfo); - if (bakedGlyph == null) continue; - - return bakedGlyph; - } - ResourceLocation resourceLocation = this.name.withSuffix("/" + flywheel$textures.size()); - boolean bl = glyphInfo.isColored(); - GlyphRenderTypes glyphRenderTypes = bl ? GlyphRenderTypes.createForColorTexture(resourceLocation) : GlyphRenderTypes.createForIntensityTexture(resourceLocation); - - AsyncFontTexture fontTexture2 = new AsyncFontTexture(resourceLocation, glyphRenderTypes, bl); - flywheel$textures.add(fontTexture2); - BakedGlyph bakedGlyph2 = fontTexture2.add(glyphInfo); - - this.textureManager.register(resourceLocation, fontTexture2); - - return bakedGlyph2 == null ? this.missingGlyph : bakedGlyph2; - } - - @Inject(method = "close", at = @At("TAIL")) - private void flywheel$close(CallbackInfo ci) { - flywheel$closeTextures(); - } - - @Unique - private void flywheel$closeTextures() { - for (AsyncFontTexture texture : flywheel$textures) { - texture.close(); - } - - flywheel$textures.clear(); - } - } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontTexture$NodeAccessor.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontTexture$NodeAccessor.java new file mode 100644 index 000000000..e8120e5ce --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontTexture$NodeAccessor.java @@ -0,0 +1,13 @@ +package dev.engine_room.flywheel.backend.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(targets = "net.minecraft.client.gui.font.FontTexture$Node") +public interface FontTexture$NodeAccessor { + @Accessor("x") + int flywheel$getX(); + + @Accessor("y") + int flywheel$getY(); +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontTextureMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontTextureMixin.java new file mode 100644 index 000000000..37d63df74 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/FontTextureMixin.java @@ -0,0 +1,114 @@ +package dev.engine_room.flywheel.backend.mixin; + +import java.util.ArrayList; +import java.util.List; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Coerce; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import com.mojang.blaze3d.font.SheetGlyphInfo; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.RenderSystem; + +import dev.engine_room.flywheel.backend.util.FontTextureUpload; +import dev.engine_room.flywheel.lib.internal.FontTextureExtension; +import dev.engine_room.flywheel.lib.internal.GlyphExtension; +import net.minecraft.client.gui.font.FontTexture; +import net.minecraft.client.gui.font.glyphs.BakedGlyph; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.resources.ResourceLocation; + +@Mixin(FontTexture.class) +public abstract class FontTextureMixin extends AbstractTexture implements FontTextureExtension { + @Unique + private final List flywheel$uploads = new ArrayList<>(); + @Unique + private boolean flywheel$flushScheduled = false; + + @Unique + private ResourceLocation flywheel$name; + + @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/font/FontTexture;getId()I")) + private int flywheel$skipGetId(FontTexture instance, Operation original) { + // getId lazily creates the texture id, which is good, + // but it doesn't check for the render thread, which explodes. + if (RenderSystem.isOnRenderThreadOrInit()) { + return original.call(instance); + } + // We'll call getId manually in the recorded render call below. + return 0; + } + + @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/TextureUtil;prepareImage(Lcom/mojang/blaze3d/platform/NativeImage$InternalGlFormat;III)V")) + private void flywheel$skipPrepareImage(NativeImage.InternalGlFormat arg, int i, int j, int k, Operation original) { + if (RenderSystem.isOnRenderThreadOrInit()) { + original.call(arg, i, j, k); + } else { + RenderSystem.recordRenderCall(() -> original.call(arg, getId(), j, k)); + } + } + + @WrapWithCondition(method = "add", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/font/FontTexture;bind()V")) + private boolean flywheel$onlyOnRenderThreadOrInitBindAndUpload(FontTexture instance) { + return RenderSystem.isOnRenderThreadOrInit(); + } + + @WrapWithCondition(method = "add", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/font/SheetGlyphInfo;upload(II)V")) + private boolean flywheel$onlyOnRenderThreadOrInitBindAndUpload2(SheetGlyphInfo instance, int x, int y) { + return RenderSystem.isOnRenderThreadOrInit(); + } + + @WrapOperation(method = "add", at = @At(value = "FIELD", target = "Lnet/minecraft/client/gui/font/FontTexture$Node;x:I", ordinal = 0)) + private int flywheel$shareNode(@Coerce Object instance, Operation original, @Share("node") LocalRef node) { + node.set(instance); + return original.call(instance); + } + + @Inject(method = "add", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/font/SheetGlyphInfo;upload(II)V", shift = At.Shift.AFTER)) + private void flywheel$uploadOrFlush(SheetGlyphInfo glyphInfo, CallbackInfoReturnable cir, @Share("node") LocalRef node) { + FontTexture$NodeAccessor accessor = ((FontTexture$NodeAccessor) node.get()); + + // Shove all the uploads into a list to be processed as a batch. + // Saves a lot of lambda allocations that would be spent binding the same texture over and over. + flywheel$uploads.add(new FontTextureUpload(glyphInfo, accessor.flywheel$getX(), accessor.flywheel$getY())); + + if (!flywheel$flushScheduled) { + RenderSystem.recordRenderCall(this::flywheel$flush); + flywheel$flushScheduled = true; + } + } + + @ModifyExpressionValue(method = "add", at = @At(value = "NEW", target = "net/minecraft/client/gui/font/glyphs/BakedGlyph")) + private BakedGlyph flywheel$setGlyphExtensionName(BakedGlyph original) { + ((GlyphExtension) original).flywheel$texture(flywheel$name); + return original; + } + + @Unique + public void flywheel$flush() { + this.bind(); + for (FontTextureUpload upload : flywheel$uploads) { + upload.info() + .upload(upload.x(), upload.y()); + } + + flywheel$uploads.clear(); + + flywheel$flushScheduled = false; + } + + @Override + public void flywheel$setName(ResourceLocation value) { + flywheel$name = value; + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/util/FontTextureUpload.java b/common/src/backend/java/dev/engine_room/flywheel/backend/util/FontTextureUpload.java new file mode 100644 index 000000000..c6865dff9 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/util/FontTextureUpload.java @@ -0,0 +1,10 @@ +package dev.engine_room.flywheel.backend.util; + +import com.mojang.blaze3d.font.SheetGlyphInfo; + +/** + * For use in {@link dev.engine_room.flywheel.backend.mixin.FontTextureMixin} + * to batch glyph uploads when they're created in a flywheel worker thread. + */ +public record FontTextureUpload(SheetGlyphInfo info, int x, int y) { +} diff --git a/common/src/backend/resources/flywheel.backend.mixins.json b/common/src/backend/resources/flywheel.backend.mixins.json index da4f3cfe7..8a4f51c8f 100644 --- a/common/src/backend/resources/flywheel.backend.mixins.json +++ b/common/src/backend/resources/flywheel.backend.mixins.json @@ -8,6 +8,8 @@ "AbstractClientPlayerAccessor", "CodePointMapMixin", "FontSetMixin", + "FontTexture$NodeAccessor", + "FontTextureMixin", "GlStateManagerMixin", "LevelRendererAccessor", "OptionsMixin", diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FontTextureExtension.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FontTextureExtension.java new file mode 100644 index 000000000..8b7b5cce7 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FontTextureExtension.java @@ -0,0 +1,7 @@ +package dev.engine_room.flywheel.lib.internal; + +import net.minecraft.resources.ResourceLocation; + +public interface FontTextureExtension { + void flywheel$setName(ResourceLocation value); +} diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index c9569e26c..84d3ea2f0 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -87,6 +87,9 @@ dependencies { modCompileOnly("maven.modrinth:embeddium:${property("embeddium_version")}") modCompileOnly("maven.modrinth:oculus:${property("oculus_version")}") + compileOnly(annotationProcessor("io.github.llamalad7:mixinextras-common:0.4.1")!!) + implementation(include("io.github.llamalad7:mixinextras-forge:0.4.1")!!) + "forApi"(project(path = ":common", configuration = "commonApiOnly")) "forLib"(project(path = ":common", configuration = "commonLib")) "forBackend"(project(path = ":common", configuration = "commonBackend")) diff --git a/gradle.properties b/gradle.properties index d8c4ff106..013c0181a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ parchment_version = 2023.09.03 # Minecraft build dependency versions minecraft_version = 1.20.1 forge_version = 47.2.19 -fabric_loader_version = 0.15.9 +fabric_loader_version=0.16.5 fabric_api_version = 0.92.1+1.20.1 # Build dependency mod versions