From 3d2fdb7e8383e3c6de9891a2ba347839e86d6149 Mon Sep 17 00:00:00 2001 From: IThundxr Date: Fri, 18 Oct 2024 23:29:43 -0400 Subject: [PATCH] Backport changes from 1.21.1 (#265) * Backport changes from 1.21.1 * fix * Fix building * fix compile error * fix * fix build for real * address reviews * Fix sodium compat * address requested changes * mark rubidium as incompatible * add missed call * Should have worn steel toe boots - Add "stub" sourceset to each subproject - Directly pass vararg sourcesets to methods in PlatformExtension to avoid automatically shipping jars with the api stubs - We may have to include stubs in setupLoomMod, but I don't think so - A lot of this can be stripped back out if we don't need stub sources for the forge/fabric subprojects * Guarded stubs - Add Sodium 0.6 and Iris API stubs to stubs source set and remove Gradle dependencies on local Sodium jar, Iris, and Oculus - Ensure usage of APIs that may not exist at runtime is in private classes and access is always guarded - Change ShadersModHandler - Rename to ShadersModHelper - Convert methods to check for Iris' and Optifine's presence into static final fields - Move implementation to impl source set in form of IrisCompat and OptifineCompat classes - Rename CompatMods to CompatMod and add public field to access mod ID - Set BlockEntityType's Sodium predicate to null after it is removed - Update repository links - Remove local libs repository --------- Co-authored-by: Jozufozu Co-authored-by: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> --- .../gradle/platform/PlatformExtension.kt | 19 +-- .../gradle/subproject/SubprojectPlugin.kt | 3 +- .../TransitiveSourceSetsExtension.kt | 4 +- common/build.gradle.kts | 7 +- .../flywheel/backend/Backends.java | 6 +- .../flywheel/lib/internal/FlwLibLink.java | 8 ++ .../flywheel/lib/internal/FlwLibXplat.java | 5 - .../flywheel/lib/util/ShadersModHandler.java | 128 ------------------ .../flywheel/lib/util/ShadersModHelper.java | 19 +++ .../engine_room/flywheel/impl/FlwImpl.java | 2 - .../flywheel/impl/FlwImplXplat.java | 6 + .../flywheel/impl/FlwLibLinkImpl.java | 34 +++++ .../flywheel/impl/compat/CompatMod.java | 18 +++ .../flywheel/impl/compat/IrisCompat.java | 46 +++++++ .../flywheel/impl/compat/OptifineCompat.java | 73 ++++++++++ .../flywheel/impl/compat/SodiumCompat.java | 61 +++++++++ .../impl/mixin/BlockEntityTypeMixin.java | 12 +- .../flywheel/impl/mixin/EntityTypeMixin.java | 3 +- .../impl/mixin/LevelRendererMixin.java | 4 +- .../blockentity/BlockEntityRenderHandler.java | 38 ++++++ .../BlockEntityRenderPredicate.java | 16 +++ .../net/irisshaders/iris/api/v0/IrisApi.java | 114 ++++++++++++++++ fabric/build.gradle.kts | 15 +- .../flywheel/impl/FlwImplXplatImpl.java | 18 +++ .../flywheel/impl/FlwLibXplatImpl.java | 27 ---- .../impl/compat/FabricSodiumCompat.java | 48 +++++++ .../sodium/ChunkBuilderMeshingTaskMixin.java | 19 ++- .../impl/mixin/sodium/SodiumMixinPlugin.java | 12 +- fabric/src/main/resources/fabric.mod.json | 8 +- forge/build.gradle.kts | 16 ++- .../flywheel/impl/FlwImplXplatImpl.java | 17 +++ .../flywheel/impl/FlwLibXplatImpl.java | 27 ---- .../flywheel/impl/FlywheelForge.java | 3 + .../flywheel/impl/compat/EmbeddiumCompat.java | 33 +++++ .../sodium/ChunkBuilderMeshingTaskMixin.java | 22 --- .../impl/mixin/sodium/SodiumMixinPlugin.java | 48 ------- forge/src/main/resources/META-INF/mods.toml | 17 ++- .../flywheel.impl.sodium.mixins.json | 14 -- gradle.properties | 13 +- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 43583 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +- gradlew.bat | 2 + 43 files changed, 659 insertions(+), 335 deletions(-) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHandler.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHelper.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/compat/CompatMod.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/compat/IrisCompat.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/compat/OptifineCompat.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/compat/SodiumCompat.java create mode 100644 common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java create mode 100644 common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderPredicate.java create mode 100644 common/src/stubs/java/net/irisshaders/iris/api/v0/IrisApi.java create mode 100644 fabric/src/main/java/dev/engine_room/flywheel/impl/compat/FabricSodiumCompat.java create mode 100644 forge/src/main/java/dev/engine_room/flywheel/impl/compat/EmbeddiumCompat.java delete mode 100644 forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java delete mode 100644 forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java delete mode 100644 forge/src/main/resources/flywheel.impl.sodium.mixins.json diff --git a/buildSrc/src/main/kotlin/dev/engine_room/gradle/platform/PlatformExtension.kt b/buildSrc/src/main/kotlin/dev/engine_room/gradle/platform/PlatformExtension.kt index fc5df1a45..66831cedf 100644 --- a/buildSrc/src/main/kotlin/dev/engine_room/gradle/platform/PlatformExtension.kt +++ b/buildSrc/src/main/kotlin/dev/engine_room/gradle/platform/PlatformExtension.kt @@ -23,16 +23,11 @@ open class PlatformExtension(val project: Project) { var apiArtifactId: String = "flywheel-${project.name}-api-${project.property("artifact_minecraft_version")}" - private val sources = mutableSetOf() private val commonSourceSets: SourceSetContainer by lazy { commonProject.the() } - fun sources(vararg sourceSets: SourceSet) { - this.sources.addAll(sourceSets) - } - - fun setupLoomMod() { + fun setupLoomMod(vararg sourceSets: SourceSet) { project.the().mods.maybeCreate("main").apply { - sources.forEach(::sourceSet) + sourceSets.forEach(::sourceSet) } } @@ -61,13 +56,13 @@ open class PlatformExtension(val project: Project) { } } - fun compileWithCommonSourceSets() { + fun compileWithCommonSourceSets(vararg sourceSets: SourceSet) { project.tasks.apply { withType().configureEach { JarTaskSet.excludeDuplicatePackageInfos(this) } - sources.forEach { + sourceSets.forEach { val commonSourceSet = commonSourceSets.named(it.name).get() named(it.compileJavaTaskName).configure { @@ -80,10 +75,10 @@ open class PlatformExtension(val project: Project) { } } - fun setupFatJar() { + fun setupFatJar(vararg sourceSets: SourceSet) { project.tasks.apply { - val extraSourceSets = sources.filter { it.name != "main" }.toList() - val commonSources = sources.map { commonSourceSets.named(it.name).get() } + val extraSourceSets = sourceSets.filter { it.name != "main" }.toList() + val commonSources = sourceSets.map { commonSourceSets.named(it.name).get() } named("jar").configure { extraSourceSets.forEach { from(it.output) } diff --git a/buildSrc/src/main/kotlin/dev/engine_room/gradle/subproject/SubprojectPlugin.kt b/buildSrc/src/main/kotlin/dev/engine_room/gradle/subproject/SubprojectPlugin.kt index e6783a313..6c3230f0d 100644 --- a/buildSrc/src/main/kotlin/dev/engine_room/gradle/subproject/SubprojectPlugin.kt +++ b/buildSrc/src/main/kotlin/dev/engine_room/gradle/subproject/SubprojectPlugin.kt @@ -96,6 +96,7 @@ class SubprojectPlugin: Plugin { private fun setupDependencies(project: Project) { project.dependencies.apply { val minecraft_version: String by project + val parchment_minecraft_version: String by project val parchment_version: String by project val loom = project.the() @@ -103,7 +104,7 @@ class SubprojectPlugin: Plugin { add("mappings", loom.layered { officialMojangMappings() - parchment("org.parchmentmc.data:parchment-${minecraft_version}:${parchment_version}@zip") + parchment("org.parchmentmc.data:parchment-${parchment_minecraft_version}:${parchment_version}@zip") }) add("api", "com.google.code.findbugs:jsr305:3.0.2") diff --git a/buildSrc/src/main/kotlin/dev/engine_room/gradle/transitive/TransitiveSourceSetsExtension.kt b/buildSrc/src/main/kotlin/dev/engine_room/gradle/transitive/TransitiveSourceSetsExtension.kt index c259546df..05fa4dc46 100644 --- a/buildSrc/src/main/kotlin/dev/engine_room/gradle/transitive/TransitiveSourceSetsExtension.kt +++ b/buildSrc/src/main/kotlin/dev/engine_room/gradle/transitive/TransitiveSourceSetsExtension.kt @@ -25,7 +25,7 @@ open class TransitiveSourceSetsExtension(private val project: Project) { fun createCompileConfigurations() { val configs = transitives.mapValues { (sourceSet, _) -> - project.configurations.create("for${sourceSet.name.capitalize()}") { + project.configurations.create("for${sourceSet.name.replaceFirstChar { it.uppercase() }}") { isCanBeConsumed = true isCanBeResolved = false } @@ -43,7 +43,7 @@ open class TransitiveSourceSetsExtension(private val project: Project) { fun createRuntimeConfigurations() { val configs = transitives.mapValues { (sourceSet, _) -> - project.configurations.create("run${sourceSet.name.capitalize()}") { + project.configurations.create("run${sourceSet.name.replaceFirstChar { it.uppercase() }}") { isCanBeConsumed = true isCanBeResolved = false } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 23d1f23b4..5aaf88778 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -9,6 +9,7 @@ plugins { val api = sourceSets.create("api") val lib = sourceSets.create("lib") val backend = sourceSets.create("backend") +val stubs = sourceSets.create("stubs") val main = sourceSets.getByName("main") transitiveSourceSets { @@ -25,8 +26,11 @@ transitiveSourceSets { rootCompile() compile(api, lib) } + sourceSet(stubs) { + rootCompile() + } sourceSet(main) { - compile(api, lib, backend) + compile(api, lib, backend, stubs) } sourceSet(sourceSets.getByName("test")) { implementation(api, lib, backend) @@ -42,6 +46,7 @@ jarSets { outgoing("commonApiOnly", api) outgoing("commonLib", lib) outgoing("commonBackend", backend) + outgoing("commonStubs", stubs) outgoing("commonImpl", main) // For publishing. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java b/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java index 81cdf2408..c6de7eb47 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java @@ -9,7 +9,7 @@ import dev.engine_room.flywheel.backend.engine.instancing.InstancedDrawManager; import dev.engine_room.flywheel.backend.gl.GlCompat; import dev.engine_room.flywheel.lib.backend.SimpleBackend; -import dev.engine_room.flywheel.lib.util.ShadersModHandler; +import dev.engine_room.flywheel.lib.util.ShadersModHelper; public final class Backends { /** @@ -18,7 +18,7 @@ public final class Backends { public static final Backend INSTANCING = SimpleBackend.builder() .engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256)) .priority(500) - .supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse()) + .supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse()) .register(Flywheel.rl("instancing")); /** @@ -27,7 +27,7 @@ public final class Backends { public static final Backend INDIRECT = SimpleBackend.builder() .engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256)) .priority(1000) - .supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse()) + .supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse()) .register(Flywheel.rl("indirect")); private Backends() { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java index 9a112c07d..c33ae35c1 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java @@ -24,4 +24,12 @@ public interface FlwLibLink { void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha); Deque getPoseStack(PoseStack stack); + + boolean isIrisLoaded(); + + boolean isOptifineInstalled(); + + boolean isShaderPackInUse(); + + boolean isRenderingShadowPass(); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibXplat.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibXplat.java index 2809537ee..fa58991ca 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibXplat.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibXplat.java @@ -1,13 +1,11 @@ package dev.engine_room.flywheel.lib.internal; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import dev.engine_room.flywheel.api.internal.DependencyInjection; import dev.engine_room.flywheel.lib.model.baked.BakedModelBuilder; import dev.engine_room.flywheel.lib.model.baked.BlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.MultiBlockModelBuilder; -import dev.engine_room.flywheel.lib.util.ShadersModHandler; import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelManager; @@ -29,7 +27,4 @@ public interface FlwLibXplat { BlockModelBuilder createBlockModelBuilder(BlockState state); MultiBlockModelBuilder createMultiBlockModelBuilder(BlockAndTintGetter level, Iterable positions); - - @Nullable - ShadersModHandler.InternalHandler createIrisHandler(); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHandler.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHandler.java deleted file mode 100644 index ccf26ce07..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHandler.java +++ /dev/null @@ -1,128 +0,0 @@ -package dev.engine_room.flywheel.lib.util; - -import java.lang.reflect.Field; -import java.util.function.BooleanSupplier; - -import org.jetbrains.annotations.ApiStatus; - -import dev.engine_room.flywheel.lib.internal.FlwLibLink; -import dev.engine_room.flywheel.lib.internal.FlwLibXplat; - -public final class ShadersModHandler { - private static final String OPTIFINE_ROOT_PACKAGE = "net.optifine"; - - private static final boolean IS_IRIS_LOADED; - private static final boolean IS_OPTIFINE_INSTALLED; - private static final InternalHandler INTERNAL_HANDLER; - - static { - InternalHandler irisHandler = FlwLibXplat.INSTANCE - .createIrisHandler(); - IS_IRIS_LOADED = irisHandler != null; - - Package optifinePackage = Package.getPackage(OPTIFINE_ROOT_PACKAGE); - IS_OPTIFINE_INSTALLED = optifinePackage != null; - - // OptiFine and Iris/Oculus are assumed to be mutually exclusive - - if (IS_IRIS_LOADED) { - FlwLibLink.INSTANCE.getLogger().debug("Iris detected."); - INTERNAL_HANDLER = irisHandler; - } else if (IS_OPTIFINE_INSTALLED) { - FlwLibLink.INSTANCE.getLogger().debug("OptiFine detected."); - INTERNAL_HANDLER = new OptifineHandler(); - } else { - FlwLibLink.INSTANCE.getLogger().debug("No shaders mod detected."); - INTERNAL_HANDLER = new InternalHandler() {}; - } - } - - private ShadersModHandler() { - } - - public static boolean isIrisLoaded() { - return IS_IRIS_LOADED; - } - - public static boolean isOptifineInstalled() { - return IS_OPTIFINE_INSTALLED; - } - - public static boolean isShaderPackInUse() { - return INTERNAL_HANDLER.isShaderPackInUse(); - } - - public static boolean isRenderingShadowPass() { - return INTERNAL_HANDLER.isRenderingShadowPass(); - } - - @ApiStatus.Internal - public static void init() { - } - - @ApiStatus.Internal - public interface InternalHandler { - default boolean isShaderPackInUse() { - return false; - } - - default boolean isRenderingShadowPass() { - return false; - } - } - - // evil reflection - private static class OptifineHandler implements InternalHandler { - private final BooleanSupplier shadersEnabledSupplier; - private final BooleanSupplier shadowPassSupplier; - - private OptifineHandler() { - shadersEnabledSupplier = createShadersEnabledSupplier(); - shadowPassSupplier = createShadowPassSupplier(); - } - - @Override - public boolean isShaderPackInUse() { - return shadersEnabledSupplier.getAsBoolean(); - } - - @Override - public boolean isRenderingShadowPass() { - return shadowPassSupplier.getAsBoolean(); - } - - private static BooleanSupplier createShadersEnabledSupplier() { - try { - Class ofShaders = Class.forName("net.optifine.shaders.Shaders"); - Field field = ofShaders.getDeclaredField("shaderPackLoaded"); - field.setAccessible(true); - return () -> { - try { - return field.getBoolean(null); - } catch (IllegalAccessException e) { - return false; - } - }; - } catch (Exception e) { - return () -> false; - } - } - - private static BooleanSupplier createShadowPassSupplier() { - try { - Class ofShaders = Class.forName("net.optifine.shaders.Shaders"); - Field field = ofShaders.getDeclaredField("isShadowPass"); - field.setAccessible(true); - return () -> { - try { - return field.getBoolean(null); - } catch (IllegalAccessException e) { - return false; - } - }; - } catch (Exception e) { - return () -> false; - } - } - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHelper.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHelper.java new file mode 100644 index 000000000..abfc3bcbe --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ShadersModHelper.java @@ -0,0 +1,19 @@ +package dev.engine_room.flywheel.lib.util; + +import dev.engine_room.flywheel.lib.internal.FlwLibLink; + +public final class ShadersModHelper { + public static final boolean IS_IRIS_LOADED = FlwLibLink.INSTANCE.isIrisLoaded(); + public static final boolean IS_OPTIFINE_INSTALLED = FlwLibLink.INSTANCE.isOptifineInstalled(); + + private ShadersModHelper() { + } + + public static boolean isShaderPackInUse() { + return FlwLibLink.INSTANCE.isShaderPackInUse(); + } + + public static boolean isRenderingShadowPass() { + return FlwLibLink.INSTANCE.isRenderingShadowPass(); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java index 914959997..909c39647 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java @@ -6,7 +6,6 @@ import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.backend.FlwBackend; import dev.engine_room.flywheel.impl.registry.IdRegistryImpl; -import dev.engine_room.flywheel.lib.util.ShadersModHandler; import dev.engine_room.flywheel.vanilla.VanillaVisuals; public final class FlwImpl { @@ -21,7 +20,6 @@ public static void init() { BackendManagerImpl.init(); // lib - ShadersModHandler.init(); // backend FlwBackend.init(FlwConfig.INSTANCE.backendConfig()); diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplat.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplat.java index 13675de93..c8fb9fec1 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplat.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplat.java @@ -6,9 +6,15 @@ public interface FlwImplXplat { FlwImplXplat INSTANCE = DependencyInjection.load(FlwImplXplat.class, "dev.engine_room.flywheel.impl.FlwImplXplatImpl"); + boolean isModLoaded(String modId); + void dispatchReloadLevelRendererEvent(ClientLevel level); String getVersionStr(); FlwConfig getConfig(); + + boolean useSodium0_6Compat(); + + boolean useIrisCompat(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java index 47fffeeb6..1b1e19c2e 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java @@ -8,6 +8,8 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import dev.engine_room.flywheel.impl.compat.IrisCompat; +import dev.engine_room.flywheel.impl.compat.OptifineCompat; import dev.engine_room.flywheel.impl.extension.PoseStackExtension; import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor; import dev.engine_room.flywheel.impl.mixin.PoseStackAccessor; @@ -40,4 +42,36 @@ public void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer public Deque getPoseStack(PoseStack stack) { return ((PoseStackAccessor) stack).flywheel$getPoseStack(); } + + @Override + public boolean isIrisLoaded() { + return IrisCompat.ACTIVE; + } + + @Override + public boolean isOptifineInstalled() { + return OptifineCompat.IS_INSTALLED; + } + + @Override + public boolean isShaderPackInUse() { + if (IrisCompat.ACTIVE) { + return IrisCompat.isShaderPackInUse(); + } else if (OptifineCompat.IS_INSTALLED) { + return OptifineCompat.isShaderPackInUse(); + } else { + return false; + } + } + + @Override + public boolean isRenderingShadowPass() { + if (IrisCompat.ACTIVE) { + return IrisCompat.isRenderingShadowPass(); + } else if (OptifineCompat.IS_INSTALLED) { + return OptifineCompat.isRenderingShadowPass(); + } else { + return false; + } + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/compat/CompatMod.java b/common/src/main/java/dev/engine_room/flywheel/impl/compat/CompatMod.java new file mode 100644 index 000000000..725208bfa --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/compat/CompatMod.java @@ -0,0 +1,18 @@ +package dev.engine_room.flywheel.impl.compat; + +import dev.engine_room.flywheel.impl.FlwImplXplat; + +public enum CompatMod { + EMBEDDIUM("embeddium"), + IRIS("iris"), + OCULUS("oculus"), + SODIUM("sodium"); + + public final String id; + public final boolean isLoaded; + + CompatMod(String modId) { + id = modId; + isLoaded = FlwImplXplat.INSTANCE.isModLoaded(modId); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/compat/IrisCompat.java b/common/src/main/java/dev/engine_room/flywheel/impl/compat/IrisCompat.java new file mode 100644 index 000000000..97fc5f5e5 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/compat/IrisCompat.java @@ -0,0 +1,46 @@ +package dev.engine_room.flywheel.impl.compat; + +import dev.engine_room.flywheel.impl.FlwImpl; +import dev.engine_room.flywheel.impl.FlwImplXplat; +import net.irisshaders.iris.api.v0.IrisApi; + +public final class IrisCompat { + public static final boolean ACTIVE = FlwImplXplat.INSTANCE.useIrisCompat(); + + static { + if (ACTIVE) { + FlwImpl.LOGGER.debug("Detected Iris"); + } + } + + private IrisCompat() { + } + + public static boolean isShaderPackInUse() { + if (!ACTIVE) { + return false; + } + + return Internals.isShaderPackInUse(); + } + + public static boolean isRenderingShadowPass() { + if (!ACTIVE) { + return false; + } + + return Internals.isRenderingShadowPass(); + } + + private static final class Internals { + static boolean isShaderPackInUse() { + return IrisApi.getInstance() + .isShaderPackInUse(); + } + + static boolean isRenderingShadowPass() { + return IrisApi.getInstance() + .isRenderingShadowPass(); + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/compat/OptifineCompat.java b/common/src/main/java/dev/engine_room/flywheel/impl/compat/OptifineCompat.java new file mode 100644 index 000000000..42dfe4687 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/compat/OptifineCompat.java @@ -0,0 +1,73 @@ +package dev.engine_room.flywheel.impl.compat; + +import java.lang.reflect.Field; + +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.impl.FlwImpl; + +public final class OptifineCompat { + private static final String OPTIFINE_ROOT_PACKAGE = "net.optifine"; + + public static final boolean IS_INSTALLED; + @Nullable + private static final Field SHADER_PACK_LOADED_FIELD; + @Nullable + private static final Field IS_SHADOW_PASS_FIELD; + + static { + Field shaderPackLoadedField = null; + Field isShadowPassField = null; + + Package optifinePackage = Package.getPackage(OPTIFINE_ROOT_PACKAGE); + IS_INSTALLED = optifinePackage != null; + + if (IS_INSTALLED) { + try { + Class shadersClass = Class.forName("net.optifine.shaders.Shaders"); + shaderPackLoadedField = shadersClass.getDeclaredField("shaderPackLoaded"); + shaderPackLoadedField.setAccessible(true); + isShadowPassField = shadersClass.getDeclaredField("isShadowPass"); + isShadowPassField.setAccessible(true); + } catch (Exception e) { + FlwImpl.LOGGER.debug("Failed to access OptiFine internals", e); + } + } + + SHADER_PACK_LOADED_FIELD = shaderPackLoadedField; + IS_SHADOW_PASS_FIELD = isShadowPassField; + } + + static { + if (IS_INSTALLED) { + FlwImpl.LOGGER.debug("Detected OptiFine"); + } + } + + private OptifineCompat() { + } + + public static boolean isShaderPackInUse() { + if (SHADER_PACK_LOADED_FIELD == null) { + return false; + } + + try { + return SHADER_PACK_LOADED_FIELD.getBoolean(null); + } catch (IllegalAccessException e) { + return false; + } + } + + public static boolean isRenderingShadowPass() { + if (IS_SHADOW_PASS_FIELD == null) { + return false; + } + + try { + return IS_SHADOW_PASS_FIELD.getBoolean(null); + } catch (IllegalAccessException e) { + return false; + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/compat/SodiumCompat.java b/common/src/main/java/dev/engine_room/flywheel/impl/compat/SodiumCompat.java new file mode 100644 index 000000000..76fa1d499 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/compat/SodiumCompat.java @@ -0,0 +1,61 @@ +package dev.engine_room.flywheel.impl.compat; + +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.api.visualization.BlockEntityVisualizer; +import dev.engine_room.flywheel.impl.FlwImpl; +import dev.engine_room.flywheel.impl.FlwImplXplat; +import dev.engine_room.flywheel.lib.visualization.VisualizationHelper; +import net.caffeinemc.mods.sodium.api.blockentity.BlockEntityRenderHandler; +import net.caffeinemc.mods.sodium.api.blockentity.BlockEntityRenderPredicate; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; + +public final class SodiumCompat { + public static final boolean USE_0_6_COMPAT = FlwImplXplat.INSTANCE.useSodium0_6Compat(); + + static { + if (USE_0_6_COMPAT) { + FlwImpl.LOGGER.debug("Detected Sodium 0.6"); + } + } + + private SodiumCompat() { + } + + @Nullable + public static Object onSetBlockEntityVisualizer(BlockEntityType type, @Nullable BlockEntityVisualizer oldVisualizer, @Nullable BlockEntityVisualizer newVisualizer, @Nullable Object predicate) { + if (!USE_0_6_COMPAT) { + return null; + } + + if (oldVisualizer == null && newVisualizer != null) { + if (predicate != null) { + throw new IllegalArgumentException("Sodium predicate must be null when old visualizer is null"); + } + + return Internals.addPredicate(type); + } else if (oldVisualizer != null && newVisualizer == null) { + if (predicate == null) { + throw new IllegalArgumentException("Sodium predicate must not be null when old visualizer is not null"); + } + + Internals.removePredicate(type, predicate); + return null; + } + + return predicate; + } + + private static final class Internals { + static Object addPredicate(BlockEntityType type) { + BlockEntityRenderPredicate predicate = (getter, pos, be) -> VisualizationHelper.tryAddBlockEntity(be); + BlockEntityRenderHandler.instance().addRenderPredicate(type, predicate); + return predicate; + } + + static void removePredicate(BlockEntityType type, Object predicate) { + BlockEntityRenderHandler.instance().removeRenderPredicate(type, (BlockEntityRenderPredicate) predicate); + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/BlockEntityTypeMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/BlockEntityTypeMixin.java index 93c660d8d..a3e178fac 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/BlockEntityTypeMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/BlockEntityTypeMixin.java @@ -5,6 +5,7 @@ import org.spongepowered.asm.mixin.Unique; import dev.engine_room.flywheel.api.visualization.BlockEntityVisualizer; +import dev.engine_room.flywheel.impl.compat.SodiumCompat; import dev.engine_room.flywheel.impl.extension.BlockEntityTypeExtension; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -12,8 +13,13 @@ @Mixin(BlockEntityType.class) abstract class BlockEntityTypeMixin implements BlockEntityTypeExtension { @Unique + @Nullable private BlockEntityVisualizer flywheel$visualizer; + @Unique + @Nullable + private Object flywheel$sodiumPredicate; + @Override @Nullable public BlockEntityVisualizer flywheel$getVisualizer() { @@ -22,6 +28,10 @@ abstract class BlockEntityTypeMixin implements BlockEntit @Override public void flywheel$setVisualizer(@Nullable BlockEntityVisualizer visualizer) { - this.flywheel$visualizer = visualizer; + if (SodiumCompat.USE_0_6_COMPAT) { + flywheel$sodiumPredicate = SodiumCompat.onSetBlockEntityVisualizer((BlockEntityType) (Object) this, flywheel$visualizer, visualizer, flywheel$sodiumPredicate); + } + + flywheel$visualizer = visualizer; } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/EntityTypeMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/EntityTypeMixin.java index e8b605a32..91676872f 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/EntityTypeMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/EntityTypeMixin.java @@ -12,6 +12,7 @@ @Mixin(EntityType.class) abstract class EntityTypeMixin implements EntityTypeExtension { @Unique + @Nullable private EntityVisualizer flywheel$visualizer; @Override @@ -22,6 +23,6 @@ abstract class EntityTypeMixin implements EntityTypeExtension< @Override public void flywheel$setVisualizer(@Nullable EntityVisualizer visualizer) { - this.flywheel$visualizer = visualizer; + flywheel$visualizer = visualizer; } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java index 6fa747581..c7714b228 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/LevelRendererMixin.java @@ -125,8 +125,8 @@ abstract class LevelRendererMixin { } @Inject(method = "renderEntity", at = @At("HEAD"), cancellable = true) - private void flywheel$decideNotToRenderEntity(Entity pEntity, double pCamX, double pCamY, double pCamZ, float pPartialTick, PoseStack pPoseStack, MultiBufferSource pBufferSource, CallbackInfo ci) { - if (VisualizationManager.supportsVisualization(pEntity.level()) && VisualizationHelper.skipVanillaRender(pEntity)) { + private void flywheel$decideNotToRenderEntity(Entity entity, double camX, double camY, double camZ, float partialTick, PoseStack poseStack, MultiBufferSource bufferSource, CallbackInfo ci) { + if (VisualizationManager.supportsVisualization(entity.level()) && VisualizationHelper.skipVanillaRender(entity)) { ci.cancel(); } } diff --git a/common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java b/common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java new file mode 100644 index 000000000..5c4a588d7 --- /dev/null +++ b/common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java @@ -0,0 +1,38 @@ +// https://github.com/CaffeineMC/sodium-fabric/blob/e7643f4544f61180ed2f0ff4952d7daa2c1feaf4/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java +// PolyForm Shield License 1.0.0 + +package net.caffeinemc.mods.sodium.api.blockentity; + +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +@ApiStatus.AvailableSince("0.6.0") +public interface BlockEntityRenderHandler { + BlockEntityRenderHandler INSTANCE = null; + + static BlockEntityRenderHandler instance() { + return INSTANCE; + } + + /** + * Adds a predicate to determine if a block entity should be rendered. + * + *

Upon chunk bake, block entities of the given type will have {@code shouldRender} evaluated. + *
If all predicates returns {@code true} (and the block entity has a renderer), the block entity will be + * added to the chunk for future rendering.

+ * @param type The block entity type to associate the given predicate with. + * @param shouldRender The predicate for the block entity to evaluate. + */ + void addRenderPredicate(BlockEntityType type, BlockEntityRenderPredicate shouldRender); + + /** + * Removes a predicate added by {@code addRenderPredicate}. It must be the same object that was added. + * + * @param type The block entity type to associate the given predicate with. + * @param shouldRender The predicate to remove. + * @return If the predicate existed and was removed. + */ + boolean removeRenderPredicate(BlockEntityType type, BlockEntityRenderPredicate shouldRender); +} diff --git a/common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderPredicate.java b/common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderPredicate.java new file mode 100644 index 000000000..8734005f2 --- /dev/null +++ b/common/src/stubs/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderPredicate.java @@ -0,0 +1,16 @@ +// https://github.com/CaffeineMC/sodium-fabric/blob/e7643f4544f61180ed2f0ff4952d7daa2c1feaf4/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderPredicate.java +// PolyForm Shield License 1.0.0 + +package net.caffeinemc.mods.sodium.api.blockentity; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.entity.BlockEntity; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Experimental +@ApiStatus.AvailableSince("0.6.0") +@FunctionalInterface +public interface BlockEntityRenderPredicate { + boolean shouldRender(BlockGetter blockGetter, BlockPos blockPos, T entity); +} diff --git a/common/src/stubs/java/net/irisshaders/iris/api/v0/IrisApi.java b/common/src/stubs/java/net/irisshaders/iris/api/v0/IrisApi.java new file mode 100644 index 000000000..571667e1c --- /dev/null +++ b/common/src/stubs/java/net/irisshaders/iris/api/v0/IrisApi.java @@ -0,0 +1,114 @@ +// https://github.com/IrisShaders/Iris/blob/20be7fc1ff8a48048cb4eb787e1299782bb1caa4/src/main/java/net/irisshaders/iris/api/v0/IrisApi.java +// GNU Lesser General Public License v3.0 + +package net.irisshaders.iris.api.v0; + +/** + * The entry point to the Iris API, major version 0. This is currently the latest + * version of the API. + * + * To access the API, use {@link #getInstance()}. + */ +public interface IrisApi { + /** + * @since API v0.0 + */ + static IrisApi getInstance() { + return null; + } + + /** + * Gets the minor revision of this API. This is incremented when + * new methods are added without breaking API. Mods can check this + * if they wish to check whether given API calls are available on + * the currently installed Iris version. + * + * @return The current minor revision. Currently, revision 2. + */ + int getMinorApiRevision(); + + /** + * Checks whether a shader pack is currently in use and being used + * for rendering. If there is no shader pack enabled or a shader + * pack failed to compile and is therefore not in use, this will + * return false. + * + *

Mods that need to enable custom workarounds for shaders + * should use this method. + * + * @return Whether shaders are being used for rendering. + * @since {@link #getMinorApiRevision() API v0.0} + */ + boolean isShaderPackInUse(); + + /** + * Checks whether the shadow pass is currently being rendered. + * + *

Generally, mods won't need to call this function for much. + * Mods should be fine with things being rendered multiple times + * each frame from different camera perspectives. Often, there's + * a better approach to fixing bugs than calling this function. + * + *

Pretty much the main legitimate use for this function that + * I've seen is in a mod like Immersive Portals, where it has + * very custom culling that doesn't work when the Iris shadow + * pass is active. + * + *

Naturally, this function can only return true if + * {@link #isShaderPackInUse()} returns true. + * + * @return Whether Iris is currently rendering the shadow pass. + * @since API v0.0 + */ + boolean isRenderingShadowPass(); + + /** + * Opens the main Iris GUI screen. It's up to Iris to decide + * what this screen is, but generally this is the shader selection + * screen. + * + * This method takes and returns Objects instead of any concrete + * Minecraft screen class to avoid referencing Minecraft classes. + * Nevertheless, the passed parent must either be null, or an + * object that is a subclass of the appropriate {@code Screen} + * class for the given Minecraft version. + * + * @param parent The parent screen, an instance of the appropriate + * {@code Screen} class. + * @return A {@code Screen} class for the main Iris GUI screen. + * @since API v0.0 + */ + Object openMainIrisScreenObj(Object parent); + + /** + * Gets the language key of the main screen. Currently, this + * is "options.iris.shaderPackSelection". + * + * @return the language key, for use with {@code TranslatableText} + * / {@code TranslatableComponent} + * @since API v0.0 + */ + String getMainScreenLanguageKey(); + +// /** +// * Gets a config object that can edit the Iris configuration. +// * @since API v0.0 +// */ +// IrisApiConfig getConfig(); + +// /** +// * Gets a text vertex sink to render into. +// * @param maxQuadCount Maximum amount of quads that will be rendered with this sink +// * @param bufferProvider An IntFunction that can provide a {@code ByteBuffer} with at minimum the bytes provided by the input parameter +// * @since API 0.1 +// */ +// IrisTextVertexSink createTextVertexSink(int maxQuadCount, IntFunction bufferProvider); + + /** + * Gets the sun path rotation used by the current shader pack. + * + * @return The sun path rotation as specified by the shader pack, or 0 if no shader pack is in use. + * @since API v0.2 + */ + float getSunPathRotation(); +} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index ff987b524..865773be5 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -10,6 +10,7 @@ plugins { val api = sourceSets.create("api") val lib = sourceSets.create("lib") val backend = sourceSets.create("backend") +val stubs = sourceSets.create("stubs") val main = sourceSets.getByName("main") transitiveSourceSets { @@ -26,7 +27,12 @@ transitiveSourceSets { rootCompile() compile(api, lib) } + sourceSet(stubs) { + rootCompile() + } sourceSet(main) { + // Don't want stubs at runtime + compile(stubs) implementation(api, lib, backend) } @@ -35,11 +41,10 @@ transitiveSourceSets { platform { commonProject = project(":common") - sources(api, lib, backend, main) - compileWithCommonSourceSets() - setupLoomMod() + compileWithCommonSourceSets(api, lib, backend, stubs, main) + setupLoomMod(api, lib, backend, main) setupLoomRuns() - setupFatJar() + setupFatJar(api, lib, backend, main) } jarSets { @@ -73,10 +78,10 @@ dependencies { modApi("net.fabricmc.fabric-api:fabric-api:${property("fabric_api_version")}") modCompileOnly("maven.modrinth:sodium:${property("sodium_version")}") - modCompileOnly("maven.modrinth:iris:${property("iris_version")}") "forApi"(project(path = ":common", configuration = "commonApiOnly")) "forLib"(project(path = ":common", configuration = "commonLib")) "forBackend"(project(path = ":common", configuration = "commonBackend")) + "forStubs"(project(path = ":common", configuration = "commonStubs")) "forMain"(project(path = ":common", configuration = "commonImpl")) } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java index 4bd60bd7d..d1c346906 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java @@ -1,9 +1,17 @@ package dev.engine_room.flywheel.impl; import dev.engine_room.flywheel.api.event.ReloadLevelRendererCallback; +import dev.engine_room.flywheel.impl.compat.CompatMod; +import dev.engine_room.flywheel.impl.compat.FabricSodiumCompat; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.multiplayer.ClientLevel; public class FlwImplXplatImpl implements FlwImplXplat { + @Override + public boolean isModLoaded(String modId) { + return FabricLoader.getInstance().isModLoaded(modId); + } + @Override public void dispatchReloadLevelRendererEvent(ClientLevel level) { ReloadLevelRendererCallback.EVENT.invoker().onReloadLevelRenderer(level); @@ -18,4 +26,14 @@ public String getVersionStr() { public FlwConfig getConfig() { return FabricFlwConfig.INSTANCE; } + + @Override + public boolean useSodium0_6Compat() { + return FabricSodiumCompat.USE_0_6_COMPAT; + } + + @Override + public boolean useIrisCompat() { + return CompatMod.IRIS.isLoaded; + } } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java index 897a7edd7..13b13505a 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java @@ -1,6 +1,5 @@ package dev.engine_room.flywheel.impl; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import dev.engine_room.flywheel.lib.internal.FlwLibXplat; @@ -10,9 +9,6 @@ import dev.engine_room.flywheel.lib.model.baked.FabricBlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.FabricMultiBlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.MultiBlockModelBuilder; -import dev.engine_room.flywheel.lib.util.ShadersModHandler; -import net.fabricmc.loader.api.FabricLoader; -import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.resources.model.BakedModel; @@ -48,27 +44,4 @@ public BlockModelBuilder createBlockModelBuilder(BlockState state) { public MultiBlockModelBuilder createMultiBlockModelBuilder(BlockAndTintGetter level, Iterable positions) { return new FabricMultiBlockModelBuilder(level, positions); } - - @Override - @Nullable - public ShadersModHandler.InternalHandler createIrisHandler() { - if (!FabricLoader.getInstance() - .isModLoaded("iris")) { - return null; - } - - return new ShadersModHandler.InternalHandler() { - @Override - public boolean isShaderPackInUse() { - return IrisApi.getInstance() - .isShaderPackInUse(); - } - - @Override - public boolean isRenderingShadowPass() { - return IrisApi.getInstance() - .isRenderingShadowPass(); - } - }; - } } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/compat/FabricSodiumCompat.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/compat/FabricSodiumCompat.java new file mode 100644 index 000000000..4a6ee1da4 --- /dev/null +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/compat/FabricSodiumCompat.java @@ -0,0 +1,48 @@ +package dev.engine_room.flywheel.impl.compat; + +import java.util.Optional; + +import dev.engine_room.flywheel.impl.FlwImpl; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.Version; +import net.fabricmc.loader.api.VersionParsingException; +import net.fabricmc.loader.api.metadata.version.VersionPredicate; + +public final class FabricSodiumCompat { + public static final boolean USE_0_5_COMPAT; + public static final boolean USE_0_6_COMPAT; + + static { + boolean use0_5Compat = false; + boolean use0_6Compat = false; + + Optional optionalModContainer = FabricLoader.getInstance().getModContainer(CompatMod.SODIUM.id); + + if (optionalModContainer.isPresent()) { + ModContainer modContainer = optionalModContainer.get(); + Version sodiumVersion = modContainer.getMetadata().getVersion(); + + try { + VersionPredicate predicate0_5 = VersionPredicate.parse("~0.5.0"); + VersionPredicate predicate0_6 = VersionPredicate.parse(">=0.6.0-beta.2"); + use0_5Compat = predicate0_5.test(sodiumVersion); + use0_6Compat = predicate0_6.test(sodiumVersion); + } catch (VersionParsingException e) { + FlwImpl.LOGGER.debug("Failed to parse Sodium version predicates", e); + } + } + + USE_0_5_COMPAT = use0_5Compat; + USE_0_6_COMPAT = use0_6Compat; + } + + static { + if (USE_0_5_COMPAT) { + FlwImpl.LOGGER.debug("Detected Sodium 0.5"); + } + } + + private FabricSodiumCompat() { + } +} diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java index 64e4b31e2..18b7772de 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java @@ -1,8 +1,11 @@ package dev.engine_room.flywheel.impl.mixin.sodium; +import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import dev.engine_room.flywheel.lib.visualization.VisualizationHelper; import me.jellysquid.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask; @@ -12,11 +15,19 @@ @Mixin(value = ChunkBuilderMeshingTask.class, remap = false) abstract class ChunkBuilderMeshingTaskMixin { - @Redirect(method = "execute", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderDispatcher;getRenderer(Lnet/minecraft/world/level/block/entity/BlockEntity;)Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderer;", remap = true)) - private BlockEntityRenderer flywheel$redirectGetRenderer(BlockEntityRenderDispatcher dispatcher, BlockEntity blockEntity) { + @WrapOperation( + method = "execute(Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildContext;Lme/jellysquid/mods/sodium/client/util/task/CancellationToken;)Lme/jellysquid/mods/sodium/client/render/chunk/compile/ChunkBuildOutput;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderDispatcher;getRenderer(Lnet/minecraft/world/level/block/entity/BlockEntity;)Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderer;", + remap = true + ) + ) + @Nullable + private BlockEntityRenderer flywheel$wrapGetRenderer(BlockEntityRenderDispatcher instance, BlockEntity blockEntity, Operation> original) { if (VisualizationHelper.tryAddBlockEntity(blockEntity)) { return null; } - return dispatcher.getRenderer(blockEntity); + return original.call(instance, blockEntity); } } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java index 0eda36012..d96386dad 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java @@ -2,31 +2,28 @@ import java.util.List; import java.util.Set; -import java.util.function.Supplier; +import org.jetbrains.annotations.Nullable; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; -import com.google.common.base.Suppliers; - -import net.fabricmc.loader.api.FabricLoader; +import dev.engine_room.flywheel.impl.compat.FabricSodiumCompat; public class SodiumMixinPlugin implements IMixinConfigPlugin { - private static final Supplier IS_SODIUM_LOADED = Suppliers.memoize(() -> FabricLoader.getInstance().isModLoaded("sodium")); - @Override public void onLoad(String mixinPackage) { } @Override + @Nullable public String getRefMapperConfig() { return null; } @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - return IS_SODIUM_LOADED.get(); + return FabricSodiumCompat.USE_0_5_COMPAT; } @Override @@ -34,6 +31,7 @@ public void acceptTargets(Set myTargets, Set otherTargets) { } @Override + @Nullable public List getMixins() { return null; } diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 7bfb5a37f..d90f772c9 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -24,14 +24,16 @@ "mixins": [ "flywheel.backend.mixins.json", "flywheel.impl.mixins.json", - "flywheel.impl.sodium.mixins.json", - "flywheel.impl.fabric.mixins.json" + "flywheel.impl.fabric.mixins.json", + "flywheel.impl.sodium.mixins.json" ], "depends": { "minecraft": "${minecraft_semver_version_range}", + "fabricloader": ">=0.15.0", "fabric-api": "${fabric_api_version_range}" }, "breaks": { - "sodium": "<0.5.0" + "sodium": ["<0.5.0", "~0.6.0- <0.6.0-beta.2"], + "embeddium": "*" } } diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts index c9569e26c..d1c6b3617 100644 --- a/forge/build.gradle.kts +++ b/forge/build.gradle.kts @@ -10,6 +10,7 @@ plugins { val api = sourceSets.create("api") val lib = sourceSets.create("lib") val backend = sourceSets.create("backend") +val stubs = sourceSets.create("stubs") val main = sourceSets.getByName("main") transitiveSourceSets { @@ -26,8 +27,11 @@ transitiveSourceSets { rootCompile() compile(api, lib) } + sourceSet(stubs) { + rootCompile() + } sourceSet(main) { - compile(api, lib, backend) + compile(api, lib, backend, stubs) } createCompileConfigurations() @@ -35,11 +39,10 @@ transitiveSourceSets { platform { commonProject = project(":common") - sources(api, lib, backend, main) - compileWithCommonSourceSets() - setupLoomMod() + compileWithCommonSourceSets(api, lib, backend, stubs, main) + setupLoomMod(api, lib, backend, main) setupLoomRuns() - setupFatJar() + setupFatJar(api, lib, backend, main) } jarSets { @@ -70,7 +73,6 @@ loom { forge { mixinConfig("flywheel.backend.mixins.json") mixinConfig("flywheel.impl.mixins.json") - mixinConfig("flywheel.impl.sodium.mixins.json") } runs { @@ -85,10 +87,10 @@ dependencies { forge("net.minecraftforge:forge:${property("minecraft_version")}-${property("forge_version")}") modCompileOnly("maven.modrinth:embeddium:${property("embeddium_version")}") - modCompileOnly("maven.modrinth:oculus:${property("oculus_version")}") "forApi"(project(path = ":common", configuration = "commonApiOnly")) "forLib"(project(path = ":common", configuration = "commonLib")) "forBackend"(project(path = ":common", configuration = "commonBackend")) + "forStubs"(project(path = ":common", configuration = "commonStubs")) "forMain"(project(path = ":common", configuration = "commonImpl")) } diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java index fa4f901e0..78e99c9e0 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwImplXplatImpl.java @@ -1,10 +1,17 @@ package dev.engine_room.flywheel.impl; import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent; +import dev.engine_room.flywheel.impl.compat.CompatMod; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.fml.loading.LoadingModList; public class FlwImplXplatImpl implements FlwImplXplat { + @Override + public boolean isModLoaded(String modId) { + return LoadingModList.get().getModFileById(modId) != null; + } + @Override public void dispatchReloadLevelRendererEvent(ClientLevel level) { MinecraftForge.EVENT_BUS.post(new ReloadLevelRendererEvent(level)); @@ -19,4 +26,14 @@ public String getVersionStr() { public FlwConfig getConfig() { return ForgeFlwConfig.INSTANCE; } + + @Override + public boolean useSodium0_6Compat() { + return CompatMod.SODIUM.isLoaded && !CompatMod.EMBEDDIUM.isLoaded; + } + + @Override + public boolean useIrisCompat() { + return CompatMod.IRIS.isLoaded || CompatMod.OCULUS.isLoaded; + } } diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java index eeed7907a..363e51382 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwLibXplatImpl.java @@ -2,7 +2,6 @@ import java.lang.reflect.Field; -import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.UnknownNullability; import dev.engine_room.flywheel.lib.internal.FlwLibXplat; @@ -12,8 +11,6 @@ import dev.engine_room.flywheel.lib.model.baked.ForgeBlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.ForgeMultiBlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.MultiBlockModelBuilder; -import dev.engine_room.flywheel.lib.util.ShadersModHandler; -import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.BlockRenderDispatcher; import net.minecraft.client.renderer.block.ModelBlockRenderer; @@ -23,7 +20,6 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.util.ObfuscationReflectionHelper; public class FlwLibXplatImpl implements FlwLibXplat { @@ -64,27 +60,4 @@ public BlockModelBuilder createBlockModelBuilder(BlockState state) { public MultiBlockModelBuilder createMultiBlockModelBuilder(BlockAndTintGetter level, Iterable positions) { return new ForgeMultiBlockModelBuilder(level, positions); } - - @Override - @Nullable - public ShadersModHandler.InternalHandler createIrisHandler() { - if (!ModList.get() - .isLoaded("oculus")) { - return null; - } - - return new ShadersModHandler.InternalHandler() { - @Override - public boolean isShaderPackInUse() { - return IrisApi.getInstance() - .isShaderPackInUse(); - } - - @Override - public boolean isRenderingShadowPass() { - return IrisApi.getInstance() - .isRenderingShadowPass(); - } - }; - } } diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java index 205db0497..cb8874dab 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java @@ -8,6 +8,7 @@ import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent; import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; +import dev.engine_room.flywheel.impl.compat.EmbeddiumCompat; import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; import dev.engine_room.flywheel.lib.util.LevelAttached; @@ -63,6 +64,8 @@ private static void clientInit(IEventBus forgeEventBus, IEventBus modEventBus) { CrashReportCallables.registerCrashCallable("Flywheel Backend", BackendManagerImpl::getBackendString); FlwImpl.init(); + + EmbeddiumCompat.init(); } private static void registerImplEventListeners(IEventBus forgeEventBus, IEventBus modEventBus) { diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/compat/EmbeddiumCompat.java b/forge/src/main/java/dev/engine_room/flywheel/impl/compat/EmbeddiumCompat.java new file mode 100644 index 000000000..cd1e5e138 --- /dev/null +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/compat/EmbeddiumCompat.java @@ -0,0 +1,33 @@ +package dev.engine_room.flywheel.impl.compat; + +import org.embeddedt.embeddium.api.ChunkDataBuiltEvent; + +import dev.engine_room.flywheel.impl.FlwImpl; +import dev.engine_room.flywheel.lib.visualization.VisualizationHelper; + +public final class EmbeddiumCompat { + public static final boolean ACTIVE = CompatMod.EMBEDDIUM.isLoaded; + + static { + if (ACTIVE) { + FlwImpl.LOGGER.debug("Detected Embeddium"); + } + } + + private EmbeddiumCompat() { + } + + public static void init() { + if (ACTIVE) { + Internals.init(); + } + } + + private static final class Internals { + static void init() { + ChunkDataBuiltEvent.BUS.addListener(event -> { + event.getDataBuilder().removeBlockEntitiesIf(VisualizationHelper::tryAddBlockEntity); + }); + } + } +} diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java deleted file mode 100644 index 64e4b31e2..000000000 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/ChunkBuilderMeshingTaskMixin.java +++ /dev/null @@ -1,22 +0,0 @@ -package dev.engine_room.flywheel.impl.mixin.sodium; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -import dev.engine_room.flywheel.lib.visualization.VisualizationHelper; -import me.jellysquid.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderDispatcher; -import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; -import net.minecraft.world.level.block.entity.BlockEntity; - -@Mixin(value = ChunkBuilderMeshingTask.class, remap = false) -abstract class ChunkBuilderMeshingTaskMixin { - @Redirect(method = "execute", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderDispatcher;getRenderer(Lnet/minecraft/world/level/block/entity/BlockEntity;)Lnet/minecraft/client/renderer/blockentity/BlockEntityRenderer;", remap = true)) - private BlockEntityRenderer flywheel$redirectGetRenderer(BlockEntityRenderDispatcher dispatcher, BlockEntity blockEntity) { - if (VisualizationHelper.tryAddBlockEntity(blockEntity)) { - return null; - } - return dispatcher.getRenderer(blockEntity); - } -} diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java b/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java deleted file mode 100644 index 1048d91fd..000000000 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/mixin/sodium/SodiumMixinPlugin.java +++ /dev/null @@ -1,48 +0,0 @@ -package dev.engine_room.flywheel.impl.mixin.sodium; - -import java.util.List; -import java.util.Set; -import java.util.function.Supplier; - -import org.objectweb.asm.tree.ClassNode; -import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; -import org.spongepowered.asm.mixin.extensibility.IMixinInfo; - -import com.google.common.base.Suppliers; - -import net.minecraftforge.fml.loading.LoadingModList; - -public class SodiumMixinPlugin implements IMixinConfigPlugin { - private static final Supplier IS_SODIUM_LOADED = Suppliers.memoize(() -> LoadingModList.get().getModFileById("rubidium") != null); - - @Override - public void onLoad(String mixinPackage) { - } - - @Override - public String getRefMapperConfig() { - return null; - } - - @Override - public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - return IS_SODIUM_LOADED.get(); - } - - @Override - public void acceptTargets(Set myTargets, Set otherTargets) { - } - - @Override - public List getMixins() { - return null; - } - - @Override - public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - } - - @Override - public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { - } -} diff --git a/forge/src/main/resources/META-INF/mods.toml b/forge/src/main/resources/META-INF/mods.toml index 2dc330582..be991ac88 100644 --- a/forge/src/main/resources/META-INF/mods.toml +++ b/forge/src/main/resources/META-INF/mods.toml @@ -26,10 +26,21 @@ mandatory = true versionRange = "${forge_version_range}" side = "CLIENT" +# Simulates a breaks/incompatible dependency [[dependencies.${mod_id}]] -# This replicates a "breaks" dependency. -# There's a mixin crash with Rubidium <0.7.0. modId = "rubidium" mandatory = false -versionRange = "[0.7.0,)" +versionRange = "[0.0-INCOMPATIBLE]" +side = "CLIENT" + +[[dependencies.${mod_id}]] +modId = "embeddium" +mandatory = false +versionRange = "[0.3.25,)" +side = "CLIENT" + +[[dependencies.${mod_id}]] +modId = "sodium" +mandatory = false +versionRange = "[0.6.0-beta.2,)" side = "CLIENT" diff --git a/forge/src/main/resources/flywheel.impl.sodium.mixins.json b/forge/src/main/resources/flywheel.impl.sodium.mixins.json deleted file mode 100644 index 7820cb594..000000000 --- a/forge/src/main/resources/flywheel.impl.sodium.mixins.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "required": true, - "minVersion": "0.8", - "package": "dev.engine_room.flywheel.impl.mixin.sodium", - "compatibilityLevel": "JAVA_17", - "refmap": "flywheel.refmap.json", - "plugin": "dev.engine_room.flywheel.impl.mixin.sodium.SodiumMixinPlugin", - "client": [ - "ChunkBuilderMeshingTaskMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/gradle.properties b/gradle.properties index d8c4ff106..57a9985e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,9 +7,9 @@ mod_name = Flywheel mod_version = 1.0.0-beta mod_description = An overhauled entity and block entity rendering API. mod_license = MIT -mod_sources = https://github.com/Jozufozu/Flywheel -mod_issues = https://github.com/Jozufozu/Flywheel/issues -mod_homepage = https://github.com/Jozufozu/Flywheel +mod_sources = https://github.com/Engine-Room/Flywheel +mod_issues = https://github.com/Engine-Room/Flywheel/issues +mod_homepage = https://github.com/Engine-Room/Flywheel # Mod dependency declarations minecraft_semver_version_range = >=1.20.1 <1.20.2 @@ -21,6 +21,7 @@ forge_version_range = [47.0.0,) java_version = 17 arch_loom_version = 1.7.412 cursegradle_version = 1.4.0 +parchment_minecraft_version = 1.20.1 parchment_version = 2023.09.03 # Minecraft build dependency versions @@ -30,10 +31,8 @@ fabric_loader_version = 0.15.9 fabric_api_version = 0.92.1+1.20.1 # Build dependency mod versions -sodium_version = mc1.20.1-0.5.8 -iris_version = 1.6.17+1.20.1 -embeddium_version = 0.3.9+mc1.20.1 -oculus_version = 1.20.1-1.6.15a +sodium_version = mc1.20.1-0.5.11 +embeddium_version = 0.3.25+mc1.20.1 # Publication info group = dev.engine_room.flywheel diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch delta 12612 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 HJ4oML2Lo<@i8BwbL^1~GkG`E7C$SEa_ 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6f7a6eb33..df97d72b8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30dbd..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ##########################################################################