Merge remote-tracking branch 'origin/1.18/dev' into 1.18/fabric/dev

Conflicts:
	.github/ISSUE_TEMPLATE/bug_report.yml
	build.gradle
	src/main/java/com/jozufozu/flywheel/Flywheel.java
	src/main/java/com/jozufozu/flywheel/config/FlwCommands.java
	src/main/java/com/jozufozu/flywheel/config/FlwConfig.java
This commit is contained in:
PepperCode1 2022-06-30 19:38:14 -07:00
commit e0aa5dd7ce
22 changed files with 460 additions and 153 deletions

View file

@ -81,9 +81,9 @@ body:
required: true
- type: input
attributes:
label: Fabric API Version
description: The version of Fabric API you were using when the bug occured
placeholder: 0.44.0+1.18
label: Loader Version
description: The version of Forge or Fabric you were using when the bug occured
placeholder: Forge 39.0.0
validations:
required: true
- type: textarea

View file

@ -1,3 +1,6 @@
0.6.2:
Update to 1.18.2
0.6.1:
Fixes
- Fix crash when loading block entities for Flywheel to render, most common when exploring the end.

View file

@ -2,7 +2,7 @@ org.gradle.jvmargs = -Xmx3G
org.gradle.daemon = false
# mod version info
mod_version = 0.6.2
mod_version = 0.6.3
mc_update_version = 1.18
minecraft_version = 1.18.2
loader_version = 0.13.3

View file

@ -6,8 +6,9 @@ import org.slf4j.Logger;
import com.jozufozu.flywheel.api.FlywheelWorld;
import com.jozufozu.flywheel.backend.gl.versioned.GlCompat;
import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.config.FlwEngine;
import com.jozufozu.flywheel.config.BackendType;
import com.jozufozu.flywheel.core.shader.ProgramSpec;
import com.mojang.logging.LogUtils;
@ -19,12 +20,30 @@ import net.minecraft.world.level.LevelAccessor;
public class Backend {
public static final Logger LOGGER = LogUtils.getLogger();
private static FlwEngine engine;
private static BackendType backendType;
private static ParallelTaskEngine taskEngine;
private static final Loader loader = new Loader();
public static FlwEngine getEngine() {
return engine;
/**
* Get the current Flywheel backend type.
*/
public static BackendType getBackendType() {
return backendType;
}
/**
* Get a thread pool for running Flywheel related work in parallel.
* @return A global Flywheel thread pool.
*/
public static ParallelTaskEngine getTaskEngine() {
if (taskEngine == null) {
taskEngine = new ParallelTaskEngine("Flywheel");
taskEngine.startWorkers();
}
return taskEngine;
}
/**
@ -32,7 +51,7 @@ public class Backend {
* (Meshlet, MDI, GL31 Draw Instanced are planned), this will name which one is in use.
*/
public static String getBackendDescriptor() {
return engine == null ? "Uninitialized" : engine.getProperName();
return backendType == null ? "Uninitialized" : backendType.getProperName();
}
@Nullable
@ -41,11 +60,11 @@ public class Backend {
}
public static void refresh() {
engine = chooseEngine();
backendType = chooseEngine();
}
public static boolean isOn() {
return engine != FlwEngine.OFF;
return backendType != BackendType.OFF;
}
public static boolean canUseInstancing(@Nullable Level world) {
@ -73,9 +92,9 @@ public class Backend {
RenderWork.enqueue(Minecraft.getInstance().levelRenderer::allChanged);
}
private static FlwEngine chooseEngine() {
FlwEngine preferredChoice = FlwConfig.get()
.getEngine();
private static BackendType chooseEngine() {
BackendType preferredChoice = FlwConfig.get()
.getBackendType();
boolean usingShaders = IrisShaderHandler.isShaderPackInUse();
boolean canUseEngine = switch (preferredChoice) {
@ -84,7 +103,7 @@ public class Backend {
case INSTANCING -> !usingShaders && GlCompat.getInstance().instancedArraysSupported();
};
return canUseEngine ? preferredChoice : FlwEngine.OFF;
return canUseEngine ? preferredChoice : BackendType.OFF;
}
public static void init() {

View file

@ -7,7 +7,6 @@ import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
import com.jozufozu.flywheel.config.FlwEngine;
import com.jozufozu.flywheel.core.Contexts;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.BeginFrameEvent;
@ -17,7 +16,6 @@ import com.jozufozu.flywheel.util.ClientLevelExtension;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -32,35 +30,37 @@ public class InstanceWorld {
protected final InstanceManager<Entity> entityInstanceManager;
protected final InstanceManager<BlockEntity> blockEntityInstanceManager;
protected final ParallelTaskEngine taskEngine;
public final ParallelTaskEngine taskEngine;
public InstanceWorld(LevelAccessor levelAccessor) {
Level world = (Level) levelAccessor;
this.taskEngine = new ParallelTaskEngine("Flywheel " + world.dimension().location());
this.taskEngine.startWorkers();
FlwEngine engine = Backend.getEngine();
switch (engine) {
public static InstanceWorld create(LevelAccessor level) {
return switch (Backend.getBackendType()) {
case INSTANCING -> {
InstancingEngine<WorldProgram> manager = InstancingEngine.builder(Contexts.WORLD)
.build();
entityInstanceManager = new EntityInstanceManager(manager);
blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
var entityInstanceManager = new EntityInstanceManager(manager);
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
manager.addListener(entityInstanceManager);
manager.addListener(blockEntityInstanceManager);
this.engine = manager;
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
}
case BATCHING -> {
this.engine = new BatchingEngine();
entityInstanceManager = new EntityInstanceManager(this.engine);
blockEntityInstanceManager = new BlockEntityInstanceManager(this.engine);
var manager = new BatchingEngine();
var entityInstanceManager = new EntityInstanceManager(manager);
var blockEntityInstanceManager = new BlockEntityInstanceManager(manager);
yield new InstanceWorld(manager, entityInstanceManager, blockEntityInstanceManager);
}
default -> throw new IllegalArgumentException("Unknown engine type");
}
};
}
public InstanceWorld(Engine engine, InstanceManager<Entity> entityInstanceManager, InstanceManager<BlockEntity> blockEntityInstanceManager) {
this.engine = engine;
this.entityInstanceManager = entityInstanceManager;
this.blockEntityInstanceManager = blockEntityInstanceManager;
this.taskEngine = Backend.getTaskEngine();
}
public InstanceManager<Entity> getEntityInstanceManager() {
@ -75,7 +75,6 @@ public class InstanceWorld {
* Free all acquired resources and invalidate this instance world.
*/
public void delete() {
taskEngine.stopWorkers();
engine.delete();
entityInstanceManager.detachLightListeners();
blockEntityInstanceManager.detachLightListeners();

View file

@ -20,7 +20,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
public class InstancedRenderDispatcher {
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>(InstanceWorld::new);
private static final WorldAttached<InstanceWorld> instanceWorlds = new WorldAttached<>(InstanceWorld::create);
/**
* Call this when you want to manually run {@link AbstractInstance#update()}.
@ -47,18 +47,20 @@ public class InstancedRenderDispatcher {
}
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) {
if (Backend.isOn()) {
return instanceWorlds.get(world)
.getBlockEntityInstanceManager();
} else {
throw new NullPointerException("Backend is off, cannot retrieve instance world.");
}
return getInstanceWorld(world).getBlockEntityInstanceManager();
}
public static InstanceManager<Entity> getEntities(LevelAccessor world) {
return getInstanceWorld(world).getEntityInstanceManager();
}
/**
* Get or create the {@link InstanceWorld} for the given world.
* @throws NullPointerException if the backend is off
*/
public static InstanceWorld getInstanceWorld(LevelAccessor world) {
if (Backend.isOn()) {
return instanceWorlds.get(world)
.getEntityInstanceManager();
return instanceWorlds.get(world);
} else {
throw new NullPointerException("Backend is off, cannot retrieve instance world.");
}

View file

@ -5,6 +5,9 @@ import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -20,9 +23,15 @@ import net.minecraft.util.Mth;
public class ParallelTaskEngine implements TaskEngine {
private static final Logger LOGGER = LoggerFactory.getLogger("BatchExecutor");
private final String name;
/**
* If set to false, the engine will shut down.
*/
private final AtomicBoolean running = new AtomicBoolean(false);
private final WaitGroup wg = new WaitGroup();
private final Deque<Runnable> syncTasks = new ConcurrentLinkedDeque<>();
private final Deque<Runnable> jobQueue = new ConcurrentLinkedDeque<>();
private final List<Thread> threads = new ArrayList<>();
@ -30,13 +39,15 @@ public class ParallelTaskEngine implements TaskEngine {
private final int threadCount;
private final String name;
public ParallelTaskEngine(String name) {
this.name = name;
threadCount = getOptimalThreadCount();
}
public WorkGroupBuilder group(String name) {
return new WorkGroupBuilder(name);
}
/**
* Spawns a number of work-stealing threads to process results in the build queue. If the builder is already
* running, this method does nothing and exits.
@ -117,6 +128,10 @@ public class ParallelTaskEngine implements TaskEngine {
this.wg.await();
} catch (InterruptedException ignored) {
}
while ((job = this.syncTasks.pollLast()) != null) {
job.run();
}
}
@Nullable
@ -157,6 +172,7 @@ public class ParallelTaskEngine implements TaskEngine {
private static int getMaxThreadCount() {
return Runtime.getRuntime().availableProcessors();
}
private class WorkerRunnable implements Runnable {
private final AtomicBoolean running = ParallelTaskEngine.this.running;
@ -176,4 +192,87 @@ public class ParallelTaskEngine implements TaskEngine {
}
}
public class WorkGroupBuilder {
final String name;
@Nullable
Runnable finalizer;
Stream<Runnable> tasks;
public WorkGroupBuilder(String name) {
this.name = name;
}
public <T> WorkGroupBuilder addTasks(Stream<T> iterable, Consumer<T> consumer) {
return addTasks(iterable.map(it -> () -> consumer.accept(it)));
}
public WorkGroupBuilder addTasks(Stream<Runnable> tasks) {
if (this.tasks == null) {
this.tasks = tasks;
} else {
this.tasks = Stream.concat(this.tasks, tasks);
}
return this;
}
public WorkGroupBuilder onComplete(Runnable runnable) {
this.finalizer = runnable;
return this;
}
public void submit() {
if (this.tasks == null) {
return;
}
WorkGroup workGroup = new WorkGroup(name, finalizer);
tasks.map(task -> new WorkGroupTask(workGroup, task)).forEach(ParallelTaskEngine.this::submit);
}
}
private static class WorkGroupTask implements Runnable {
private final WorkGroup parent;
private final Runnable wrapped;
public WorkGroupTask(WorkGroup parent, Runnable wrapped) {
this.parent = parent;
this.wrapped = wrapped;
this.parent.running.incrementAndGet();
}
@Override
public void run() {
this.wrapped.run();
this.parent.oneDown();
}
}
private class WorkGroup {
final String name;
final Runnable finalizer;
final AtomicInteger running = new AtomicInteger(0);
public WorkGroup(String name, @Nullable Runnable finalizer) {
this.name = name;
this.finalizer = finalizer;
}
public void oneDown() {
if (running.decrementAndGet() == 0) {
if (finalizer != null) {
ParallelTaskEngine.this.syncTasks.add(finalizer);
}
}
}
}
}

View file

@ -19,7 +19,6 @@ import com.jozufozu.flywheel.core.model.Model;
public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
private final ModelAllocator modelAllocator;
private final BufferLayout instanceFormat;
private final Instanced<D> instancedType;
@ -32,9 +31,8 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
protected boolean anyToUpdate;
public GPUInstancer(Instanced<D> type, Model model, ModelAllocator modelAllocator) {
public GPUInstancer(Instanced<D> type, Model model) {
super(type::create, model);
this.modelAllocator = modelAllocator;
this.instanceFormat = type.getLayout();
instancedType = type;
}
@ -63,7 +61,7 @@ public class GPUInstancer<D extends InstanceData> extends AbstractInstancer<D> {
return deleted || model == null;
}
public void init() {
public void init(ModelAllocator modelAllocator) {
if (isInitialized()) return;
initialized = true;

View file

@ -11,7 +11,6 @@ import com.jozufozu.flywheel.api.InstanceData;
import com.jozufozu.flywheel.api.Instancer;
import com.jozufozu.flywheel.api.Material;
import com.jozufozu.flywheel.api.struct.Instanced;
import com.jozufozu.flywheel.backend.model.ModelAllocator;
import com.jozufozu.flywheel.core.model.Model;
/**
@ -20,14 +19,12 @@ import com.jozufozu.flywheel.core.model.Model;
*/
public class InstancedMaterial<D extends InstanceData> implements Material<D> {
protected final ModelAllocator allocator;
protected final Map<Object, GPUInstancer<D>> models = new HashMap<>();
protected final Instanced<D> type;
protected final List<GPUInstancer<D>> uninitialized = new ArrayList<>();
public InstancedMaterial(Instanced<D> type, ModelAllocator allocator) {
public InstancedMaterial(Instanced<D> type) {
this.type = type;
this.allocator = allocator;
}
/**
@ -40,7 +37,7 @@ public class InstancedMaterial<D extends InstanceData> implements Material<D> {
@Override
public Instancer<D> model(Object key, Supplier<Model> modelSupplier) {
return models.computeIfAbsent(key, $ -> {
GPUInstancer<D> instancer = new GPUInstancer<>(type, modelSupplier.get(), allocator);
GPUInstancer<D> instancer = new GPUInstancer<>(type, modelSupplier.get());
uninitialized.add(instancer);
return instancer;
});

View file

@ -32,27 +32,21 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
protected final RenderType type;
private final Map<Instanced<? extends InstanceData>, InstancedMaterial<?>> materials = new HashMap<>();
private final ModelAllocator allocator;
private ModelAllocator allocator;
private int vertexCount;
private int instanceCount;
public InstancedMaterialGroup(InstancingEngine<P> owner, RenderType type) {
this.owner = owner;
this.type = type;
if (GlCompat.getInstance()
.onAMDWindows()) {
this.allocator = FallbackAllocator.INSTANCE;
} else {
this.allocator = new ModelPool(Formats.POS_TEX_NORMAL);
}
}
@SuppressWarnings("unchecked")
@Override
public <D extends InstanceData> InstancedMaterial<D> material(StructType<D> type) {
if (type instanceof Instanced<D> instanced) {
return (InstancedMaterial<D>) materials.computeIfAbsent(instanced, t -> new InstancedMaterial<>(t, allocator));
return (InstancedMaterial<D>) materials.computeIfAbsent(instanced, InstancedMaterial::new);
} else {
throw new ClassCastException("Cannot use type '" + type + "' with GPU instancing.");
}
@ -82,18 +76,7 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
}
protected void renderAll(Matrix4f viewProjection, double camX, double camY, double camZ, RenderLayer layer) {
// initialize all uninitialized instancers...
for (InstancedMaterial<?> material : materials.values()) {
for (GPUInstancer<?> instancer : material.uninitialized) {
instancer.init();
}
material.uninitialized.clear();
}
if (allocator instanceof ModelPool pool) {
// ...and then flush the model arena in case anything was marked for upload
pool.flush();
}
initializeInstancers();
vertexCount = 0;
instanceCount = 0;
@ -119,7 +102,24 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
}
}
public void setup(P program) {
private void initializeInstancers() {
ModelAllocator allocator = getModelAllocator();
// initialize all uninitialized instancers...
for (InstancedMaterial<?> material : materials.values()) {
for (GPUInstancer<?> instancer : material.uninitialized) {
instancer.init(allocator);
}
material.uninitialized.clear();
}
if (allocator instanceof ModelPool pool) {
// ...and then flush the model arena in case anything was marked for upload
pool.flush();
}
}
protected void setup(P program) {
}
@ -133,4 +133,20 @@ public class InstancedMaterialGroup<P extends WorldProgram> implements MaterialG
materials.clear();
}
private ModelAllocator getModelAllocator() {
if (allocator == null) {
allocator = createAllocator();
}
return this.allocator;
}
private static ModelAllocator createAllocator() {
if (GlCompat.getInstance()
.onAMDWindows()) {
return FallbackAllocator.INSTANCE;
} else {
return new ModelPool(Formats.POS_TEX_NORMAL);
}
}
}

View file

@ -0,0 +1,55 @@
package com.jozufozu.flywheel.config;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;
public enum BackendType {
OFF("Off"),
/**
* Use a thread pool to buffer instances in parallel on the CPU.
*/
BATCHING("Parallel Batching"),
/**
* Use GPU instancing to render everything.
*/
INSTANCING("GL33 Instanced Arrays"),
;
private static final Map<String, BackendType> lookup;
static {
lookup = new HashMap<>();
for (BackendType value : values()) {
lookup.put(value.name().toLowerCase(Locale.ROOT), value);
}
}
private final String properName;
BackendType(String properName) {
this.properName = properName;
}
public String getProperName() {
return properName;
}
public String getShortName() {
return name().toLowerCase(Locale.ROOT);
}
@Nullable
public static BackendType byName(String name) {
return lookup.get(name);
}
public static Collection<String> validNames() {
return lookup.keySet();
}
}

View file

@ -0,0 +1,50 @@
package com.jozufozu.flywheel.config;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.TranslatableComponent;
public enum BackendTypeArgument implements ArgumentType<BackendType> {
INSTANCE;
private static final Dynamic2CommandExceptionType INVALID = new Dynamic2CommandExceptionType((found, constants) -> {
// TODO: don't steal lang
return new TranslatableComponent("commands.forge.arguments.enum.invalid", constants, found);
});
@Override
public BackendType parse(StringReader reader) throws CommandSyntaxException {
String string = reader.readUnquotedString();
BackendType engine = BackendType.byName(string);
if (engine == null) {
throw INVALID.createWithContext(reader, string, BackendType.validNames());
}
return engine;
}
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(BackendType.validNames(), builder);
}
@Override
public Collection<String> getExamples() {
return BackendType.validNames();
}
public static BackendTypeArgument getInstance() {
return INSTANCE;
}
}

View file

@ -22,8 +22,8 @@ public final class FlwCommands {
public static void init(FlwConfig config) {
ConfigCommandBuilder commandBuilder = new ConfigCommandBuilder("flywheel");
commandBuilder.addOption(config.engine, "backend", (builder, option) -> enumOptionCommand(builder, config, option,
FlwEngine::getShortName,
commandBuilder.addOption(config.backend, (builder, option) -> enumOptionCommand(builder, config, option,
BackendType::getShortName,
(source, value) -> {
source.sendFeedback(getEngineMessage(value));
},
@ -100,7 +100,7 @@ public final class FlwCommands {
return b ? new TextComponent("enabled").withStyle(ChatFormatting.DARK_GREEN) : new TextComponent("disabled").withStyle(ChatFormatting.RED);
}
public static Component getEngineMessage(@NotNull FlwEngine type) {
public static Component getEngineMessage(@NotNull BackendType type) {
return switch (type) {
case OFF -> new TextComponent("Disabled Flywheel").withStyle(ChatFormatting.RED);
case INSTANCING -> new TextComponent("Using Instancing Engine").withStyle(ChatFormatting.GREEN);

View file

@ -34,9 +34,9 @@ public class FlwConfig {
protected final Object2ObjectLinkedOpenHashMap<String, Option<?>> optionMap = new Object2ObjectLinkedOpenHashMap<>();
protected final Map<String, Option<?>> optionMapView = Collections.unmodifiableMap(optionMap);
/** Enable or disable the entire engine */
public final EnumOption<FlwEngine> engine = addOption(new EnumOption<>("engine", FlwEngine.INSTANCING));
/** Enable or disable a debug overlay that colors pixels by their normal */
/** Select the backend to use. */
public final EnumOption<BackendType> backend = addOption(new EnumOption<>("engine", BackendType.INSTANCING));
/** Enable or disable a debug overlay that colors pixels by their normal. */
public final BooleanOption debugNormals = addOption(new BooleanOption("debugNormals", false));
/** Enable or disable instance update limiting with distance. */
public final BooleanOption limitUpdates = addOption(new BooleanOption("limitUpdates", true));
@ -54,8 +54,8 @@ public class FlwConfig {
FlwCommands.init(INSTANCE);
}
public FlwEngine getEngine() {
return engine.get();
public BackendType getBackendType() {
return backend.get();
}
public boolean debugNormals() {

View file

@ -1,48 +0,0 @@
package com.jozufozu.flywheel.config;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
public enum FlwEngine {
OFF("off", "Off"),
BATCHING("batching", "Parallel Batching"),
INSTANCING("instancing", "GL33 Instanced Arrays"),
;
private static final Map<String, FlwEngine> lookup;
static {
lookup = new HashMap<>();
for (FlwEngine value : values()) {
lookup.put(value.shortName, value);
}
}
private final String shortName;
private final String properName;
FlwEngine(String shortName, String properName) {
this.shortName = shortName;
this.properName = properName;
}
public String getShortName() {
return shortName;
}
public String getProperName() {
return properName;
}
@Nullable
public static FlwEngine byName(String name) {
return lookup.get(name);
}
public static Collection<String> validNames() {
return lookup.keySet();
}
}

View file

@ -54,7 +54,7 @@ public class CrumblingGroup<P extends CrumblingProgram> extends InstancedMateria
}
@Override
public void setup(P p) {
protected void setup(P p) {
p.setAtlasSize(width, height);
}
}

View file

@ -0,0 +1,50 @@
package com.jozufozu.flywheel.light;
import java.util.stream.Stream;
import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.world.level.LightLayer;
public class DummyLightUpdater extends LightUpdater {
public static final DummyLightUpdater INSTANCE = new DummyLightUpdater();
private DummyLightUpdater() {
super(null);
}
@Override
public void tick() {
// noop
}
@Override
public void addListener(LightListener listener) {
// noop
}
@Override
public void removeListener(LightListener listener) {
// noop
}
@Override
public void onLightUpdate(LightLayer type, long sectionPos) {
// noop
}
@Override
public void onLightPacket(int chunkX, int chunkZ) {
// noop
}
@Override
public Stream<ImmutableBox> getAllBoxes() {
return Stream.empty();
}
@Override
public boolean isEmpty() {
return true;
}
}

View file

@ -114,15 +114,8 @@ public class GPULightVolume extends LightVolume {
if (lightData == null) return;
if (box.contains(newSampleVolume)) {
if (newSampleVolume.intersects(sampleVolume)) {
GridAlignedBB newArea = newSampleVolume.intersect(sampleVolume);
sampleVolume.assign(newSampleVolume);
copyLight(world, newArea);
} else {
sampleVolume.assign(newSampleVolume);
initialize(world);
}
sampleVolume.assign(newSampleVolume);
initialize(world);
} else {
super.move(world, newSampleVolume);
}

View file

@ -0,0 +1,32 @@
package com.jozufozu.flywheel.light;
import net.minecraft.client.Minecraft;
import net.minecraft.world.level.LevelAccessor;
/**
* Marker interface for custom/fake levels to indicate that LightUpdater should bother interacting with it.<p>
*
* Implement this if your custom level has light updates at all. If so, be sure to call
* {@link com.jozufozu.flywheel.util.WorldAttached#invalidateWorld} when your level in unloaded.
*/
public interface LightUpdated extends LevelAccessor {
/**
* @return {@code true} if this level is passing light updates into LightUpdater.
*/
default boolean receivesLightUpdates() {
return true;
}
static boolean receivesLightUpdates(LevelAccessor level) {
// The client level is guaranteed to receive updates.
if (Minecraft.getInstance().level == level) {
return true;
}
// Custom/fake levels need to indicate that LightUpdater has meaning.
if (level instanceof LightUpdated c) {
return c.receivesLightUpdates();
}
return false;
}
}

View file

@ -1,28 +1,42 @@
package com.jozufozu.flywheel.light;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Stream;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.ParallelTaskEngine;
import com.jozufozu.flywheel.util.WeakHashSet;
import com.jozufozu.flywheel.util.WorldAttached;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.LightLayer;
/**
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
*
* @apiNote Custom/fake levels (that are {@code != Minecraft.getInstance.level}) need to implement
* {@link LightUpdated} for LightUpdater to work with them.
*/
public class LightUpdater {
private static final Map<BlockAndTintGetter, LightUpdater> light = new HashMap<>();
public static LightUpdater get(BlockAndTintGetter world) {
return light.computeIfAbsent(world, LightUpdater::new);
private static final WorldAttached<LightUpdater> LEVELS = new WorldAttached<>(LightUpdater::new);
private final ParallelTaskEngine taskEngine;
public static LightUpdater get(LevelAccessor level) {
if (LightUpdated.receivesLightUpdates(level)) {
// The level is valid, add it to the map.
return LEVELS.get(level);
} else {
// Fake light updater for a fake level.
return DummyLightUpdater.INSTANCE;
}
}
private final LightProvider provider;
@ -31,7 +45,8 @@ public class LightUpdater {
private final WeakContainmentMultiMap<LightListener> sections = new WeakContainmentMultiMap<>();
private final WeakContainmentMultiMap<LightListener> chunks = new WeakContainmentMultiMap<>();
public LightUpdater(BlockAndTintGetter world) {
public LightUpdater(LevelAccessor world) {
taskEngine = Backend.getTaskEngine();
provider = new BasicProvider(world);
}
@ -40,13 +55,31 @@ public class LightUpdater {
}
public void tick() {
for (MovingListener listener : movingListeners) {
if (listener.update(provider)) {
addListener(listener);
tickSerial();
//tickParallel();
}
private void tickSerial() {
for (MovingListener movingListener : movingListeners) {
if (movingListener.update(provider)) {
addListener(movingListener);
}
}
}
private void tickParallel() {
Queue<LightListener> listeners = new ConcurrentLinkedQueue<>();
taskEngine.group("LightUpdater")
.addTasks(movingListeners.stream(), listener -> {
if (listener.update(provider)) {
listeners.add(listener);
}
})
.onComplete(() -> listeners.forEach(this::addListener))
.submit();
}
/**
* Add a listener.
*

View file

@ -28,7 +28,7 @@ public class WorldAttached<T> {
}
public static void invalidateWorld(LevelAccessor world) {
Iterator<WeakReference<Map<LevelAccessor, ?>>> i = allMaps.iterator();
var i = allMaps.iterator();
while (i.hasNext()) {
Map<LevelAccessor, ?> map = i.next()
.get();

View file

@ -42,6 +42,15 @@ public interface ImmutableBox {
return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
}
default boolean sameAs(ImmutableBox other, int margin) {
return getMinX() == other.getMinX() - margin &&
getMinY() == other.getMinY() - margin &&
getMinZ() == other.getMinZ() - margin &&
getMaxX() == other.getMaxX() + margin &&
getMaxY() == other.getMaxY() + margin &&
getMaxZ() == other.getMaxZ() + margin;
}
default boolean sameAs(AABB other) {
return getMinX() == Math.floor(other.minX)
&& getMinY() == Math.floor(other.minY)