Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement backend version gating #268

Open
wants to merge 1 commit into
base: 1.20/dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ public interface Backend {
*/
Engine createEngine(LevelAccessor level);

/**
* The version of flywheel this backend was written against.
*
* @return A backend version.
*/
BackendVersion version();

/**
* The priority of this backend.
* <p>The backend with the highest priority upon first launch will be chosen as the default backend.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package dev.engine_room.flywheel.api.backend;

public record BackendVersion(int major, int minor) implements Comparable<BackendVersion> {
@Override
public int compareTo(BackendVersion o) {
if (major != o.major) {
return Integer.compare(major, o.major);
}
return Integer.compare(minor, o.minor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.backend.compile.IndirectPrograms;
import dev.engine_room.flywheel.backend.compile.InstancingPrograms;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
Expand All @@ -12,11 +13,14 @@
import dev.engine_room.flywheel.lib.util.ShadersModHelper;

public final class Backends {
private static final BackendVersion VERSION = new BackendVersion(1, 0);

/**
* Use GPU instancing to render everything.
*/
public static final Backend INSTANCING = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256))
.version(VERSION)
.priority(500)
.supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("instancing"));
Expand All @@ -26,6 +30,7 @@ public final class Backends {
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256))
.version(VERSION)
.priority(1000)
.supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHelper.isShaderPackInUse())
.register(Flywheel.rl("indirect"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
import java.util.function.BooleanSupplier;
import java.util.function.Function;

import org.jetbrains.annotations.Nullable;

import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.api.backend.Engine;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.LevelAccessor;
Expand All @@ -13,11 +16,13 @@ public final class SimpleBackend implements Backend {
private final Function<LevelAccessor, Engine> engineFactory;
private final int priority;
private final BooleanSupplier isSupported;
private final BackendVersion version;

public SimpleBackend(int priority, Function<LevelAccessor, Engine> engineFactory, BooleanSupplier isSupported) {
public SimpleBackend(int priority, Function<LevelAccessor, Engine> engineFactory, BooleanSupplier isSupported, BackendVersion version) {
this.priority = priority;
this.engineFactory = engineFactory;
this.isSupported = isSupported;
this.version = version;
}

public static Builder builder() {
Expand All @@ -29,6 +34,11 @@ public Engine createEngine(LevelAccessor level) {
return engineFactory.apply(level);
}

@Override
public BackendVersion version() {
return version;
}

@Override
public int priority() {
return priority;
Expand All @@ -40,9 +50,13 @@ public boolean isSupported() {
}

public static final class Builder {
private Function<LevelAccessor, Engine> engineFactory;
private int priority = 0;
@Nullable
private Function<LevelAccessor, Engine> engineFactory;
@Nullable
private BooleanSupplier isSupported;
@Nullable
private BackendVersion version;

public Builder engineFactory(Function<LevelAccessor, Engine> engineFactory) {
this.engineFactory = engineFactory;
Expand All @@ -59,11 +73,17 @@ public Builder supported(BooleanSupplier isSupported) {
return this;
}

public Builder version(BackendVersion version) {
this.version = version;
return this;
}

public Backend register(ResourceLocation id) {
Objects.requireNonNull(engineFactory);
Objects.requireNonNull(isSupported);
Objects.requireNonNull(version);

return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(priority, engineFactory, isSupported));
return Backend.REGISTRY.registerAndGet(id, new SimpleBackend(priority, engineFactory, isSupported, version));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package dev.engine_room.flywheel.impl;

import java.util.ArrayList;
import java.util.List;

import dev.engine_room.flywheel.api.Flywheel;
import dev.engine_room.flywheel.api.backend.Backend;
import dev.engine_room.flywheel.api.backend.BackendVersion;
import dev.engine_room.flywheel.impl.visualization.VisualizationManagerImpl;
import dev.engine_room.flywheel.lib.backend.SimpleBackend;
import net.minecraft.client.multiplayer.ClientLevel;
Expand All @@ -15,6 +17,7 @@ public final class BackendManagerImpl {
throw new UnsupportedOperationException("Cannot create engine when backend is off.");
})
.supported(() -> true)
.version(new BackendVersion(0, 0))
.register(Flywheel.rl("off"));

public static final Backend DEFAULT_BACKEND = findDefaultBackend();
Expand All @@ -33,7 +36,7 @@ public static boolean isBackendOn() {
}

// Don't store this statically because backends can theoretically change their priorities at runtime.
private static ArrayList<Backend> backendsByPriority() {
private static List<Backend> backendsByPriority() {
var backends = new ArrayList<>(Backend.REGISTRY.getAll());

// Sort with keys backwards so that the highest priority is first.
Expand All @@ -53,27 +56,46 @@ private static Backend findDefaultBackend() {
}

private static void chooseBackend() {
var requirements = FlwImplXplat.INSTANCE.modRequirements();

var preferred = FlwConfig.INSTANCE.backend();
if (preferred.isSupported()) {
backend = preferred;
return;
if (meetsModRequirements(requirements, preferred)) {
backend = preferred;
return;
} else {
var minVersion = requirements.minimumBackendVersion();
FlwImpl.LOGGER.warn("Preferred backend '{}' does not meet minimum version requirement {}.{}", Backend.REGISTRY.getIdOrThrow(preferred), minVersion.major(), minVersion.minor());
}
}

backend = fallback(preferred, requirements);

FlwImpl.LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", Backend.REGISTRY.getIdOrThrow(preferred), Backend.REGISTRY.getIdOrThrow(backend));
}

private static Backend fallback(Backend preferred, ModRequirements requirements) {
var backendsByPriority = backendsByPriority();

var startIndex = backendsByPriority.indexOf(preferred) + 1;

// For safety in case we don't find anything
backend = OFF_BACKEND;
var out = OFF_BACKEND;
for (int i = startIndex; i < backendsByPriority.size(); i++) {
var candidate = backendsByPriority.get(i);
if (candidate.isSupported()) {
backend = candidate;
break;
if (meetsModRequirements(requirements, candidate)) {
out = candidate;
break;
}
}
}
return out;
}

FlwImpl.LOGGER.warn("Flywheel backend fell back from '{}' to '{}'", Backend.REGISTRY.getIdOrThrow(preferred), Backend.REGISTRY.getIdOrThrow(backend));
private static boolean meetsModRequirements(ModRequirements requirements, Backend candidate) {
return candidate.version()
.compareTo(requirements.minimumBackendVersion()) >= 0;
}

public static String getBackendString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ public interface FlwImplXplat {
boolean useSodium0_6Compat();

boolean useIrisCompat();

ModRequirements modRequirements();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.engine_room.flywheel.impl;

import java.util.List;

import dev.engine_room.flywheel.api.backend.BackendVersion;

public record ModRequirements(BackendVersion minimumBackendVersion, List<Entry> entries) {
public record Entry(String modId, BackendVersion version) {
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package dev.engine_room.flywheel.impl;

import java.util.ArrayList;
import java.util.List;

import org.jetbrains.annotations.Nullable;

import dev.engine_room.flywheel.api.backend.BackendVersion;
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.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.CustomValue;
import net.minecraft.client.multiplayer.ClientLevel;

public class FlwImplXplatImpl implements FlwImplXplat {
Expand Down Expand Up @@ -36,4 +44,54 @@ public boolean useSodium0_6Compat() {
public boolean useIrisCompat() {
return CompatMod.IRIS.isLoaded;
}

@Override
public ModRequirements modRequirements() {
List<ModRequirements.Entry> entries = new ArrayList<>();

for (ModContainer mod : FabricLoader.getInstance()
.getAllMods()) {
var metadata = mod.getMetadata();

var custom = metadata.getCustomValue("flywheel");

if (custom != null && custom.getType() == CustomValue.CvType.OBJECT) {
var object = custom.getAsObject();

var major = getCustomValueAsInt(object.get("backend_major_version"));
var minor = getCustomValueAsInt(object.get("backend_minor_version"));

// Major version is required
if (major != null) {
// Minor version defaults to 0
var version = new BackendVersion(major, minor == null ? 0 : minor);

entries.add(new ModRequirements.Entry(metadata.getId(), version));
} else {
FlwImpl.LOGGER.warn("Mod {} has invalid required backend version", metadata.getId());
}
}
}

if (!entries.isEmpty()) {
var minVersion = entries.stream()
.map(ModRequirements.Entry::version)
.min(BackendVersion::compareTo)
.get();

return new ModRequirements(minVersion, entries);
} else {
return new ModRequirements(new BackendVersion(0, 0), List.of());
}
}

@Nullable
private static Integer getCustomValueAsInt(@Nullable CustomValue major) {
if (major != null && major.getType() == CustomValue.CvType.NUMBER) {
return major.getAsNumber()
.intValue();
}

return null;
}
}
6 changes: 6 additions & 0 deletions fabric/src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@
"breaks": {
"sodium": ["<0.5.0", "~0.6.0- <0.6.0-beta.2"],
"embeddium": "*"
},
"custom" : {
"flywheel" : {
"backend_major_version" : 1,
"backend_minor_version" : 0
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package dev.engine_room.flywheel.impl;

import java.util.ArrayList;
import java.util.List;

import com.electronwill.nightconfig.core.Config;

import dev.engine_room.flywheel.api.backend.BackendVersion;
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.ModList;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.forgespi.language.IModInfo;

public class FlwImplXplatImpl implements FlwImplXplat {
@Override
Expand Down Expand Up @@ -36,4 +44,46 @@ public boolean useSodium0_6Compat() {
public boolean useIrisCompat() {
return CompatMod.IRIS.isLoaded || CompatMod.OCULUS.isLoaded;
}

@Override
public ModRequirements modRequirements() {
List<ModRequirements.Entry> entries = new ArrayList<>();

ModList.get()
.forEachModFile(file -> {
var info = file.getModFileInfo();

for (IModInfo mod : info.getMods()) {
var modProperties = mod.getModProperties()
.get("flywheel");

// There's no well-defined API for custom properties like in fabric.
// It just returns an object, but internally it's represented with nightconfig.
if (modProperties instanceof Config config) {
// Minor version defaults to 0, major is required
int major = config.getIntOrElse("backend_major_version", Integer.MIN_VALUE);
int minor = config.getIntOrElse("backend_minor_version", 0);

if (major != Integer.MIN_VALUE) {
entries.add(new ModRequirements.Entry(mod.getModId(), new BackendVersion(major, minor)));
} else {
FlwImpl.LOGGER.warn("Mod {} has invalid required backend version", mod.getModId());
}
} else {
FlwImpl.LOGGER.warn("Mod {} has invalid flywheel properties", mod.getModId());
}
}
});

if (!entries.isEmpty()) {
var minVersion = entries.stream()
.map(ModRequirements.Entry::version)
.min(BackendVersion::compareTo)
.get();

return new ModRequirements(minVersion, entries);
} else {
return new ModRequirements(new BackendVersion(0, 0), List.of());
}
}
}
4 changes: 4 additions & 0 deletions forge/src/main/resources/META-INF/mods.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ authors = "Jozufozu, PepperCode1"
displayURL = "${mod_homepage}"
displayTest = "IGNORE_ALL_VERSION"

[modproperties.${ mod_id }.flywheel]
backend_major_version = 1
backend_minor_version = 0

[[dependencies.${mod_id}]]
modId = "minecraft"
mandatory = true
Expand Down