Skip to content

Commit

Permalink
The version vision
Browse files Browse the repository at this point in the history
- Implement rudimentary backend version requirements
- Backends ship with a hard coded backend version
- Mods can specify a minimum major/minor backend version
- If a backend's version is below any mod's required version it is
  considered disabled
  • Loading branch information
Jozufozu committed Oct 20, 2024
1 parent 3a949c7 commit 094acf5
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 10 deletions.
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

0 comments on commit 094acf5

Please sign in to comment.