Freestyle instances

- Add Effect instancing
   - Allows mods to create visual effects without (block) entities
   - One effect object maps to many AbstractInstances
   - Effect objects are expected to exist in isolation
 - Refactor InstanceManager to be composable about how its instances are stored
   - EffectInstanceManager uses this to provide a one to many topology
   - This is in need of more iteration, and more aspects of InstanceManager should be made composable in the future
This commit is contained in:
Jozufozu 2022-07-17 16:01:30 -05:00
parent 5657e8669e
commit 1fe8297e72
22 changed files with 683 additions and 242 deletions

View file

@ -28,6 +28,7 @@ import com.jozufozu.flywheel.event.ForgeEvents;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.mixin.PausedPartialTickAccessor;
import com.jozufozu.flywheel.vanilla.VanillaInstances;
import com.jozufozu.flywheel.vanilla.effect.ExampleEffect;
import com.mojang.logging.LogUtils;
import net.minecraft.commands.synchronization.ArgumentTypes;
@ -108,6 +109,8 @@ public class Flywheel {
modEventBus.addListener(StitchedSprite::onTextureStitchPre);
modEventBus.addListener(StitchedSprite::onTextureStitchPost);
// forgeEventBus.addListener(ExampleEffect::spawn);
LayoutShaders.init();
InstanceShaders.init();
Contexts.init();

View file

@ -5,5 +5,7 @@ import java.util.List;
import com.jozufozu.flywheel.api.InstancerManager;
public interface Engine extends RenderDispatcher, InstancerManager {
void attachManagers(InstanceManager<?>... listener);
void addDebugInfo(List<String> info);
}

View file

@ -1,15 +1,9 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.Backend;
@ -20,37 +14,38 @@ import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.light.LightUpdater;
import com.mojang.math.Vector3f;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
public abstract class InstanceManager<T> {
public final InstancerManager instancerManager;
private final Set<T> queuedAdditions;
private final Set<T> queuedUpdates;
protected final Map<T, AbstractInstance> instances;
protected final Object2ObjectOpenHashMap<T, TickableInstance> tickableInstances;
protected final Object2ObjectOpenHashMap<T, DynamicInstance> dynamicInstances;
protected DistanceUpdateLimiter frame;
protected DistanceUpdateLimiter tick;
public InstanceManager(InstancerManager instancerManager) {
this.instancerManager = instancerManager;
public InstanceManager() {
this.queuedUpdates = new HashSet<>(64);
this.queuedAdditions = new HashSet<>(64);
this.instances = new HashMap<>();
this.dynamicInstances = new Object2ObjectOpenHashMap<>();
this.tickableInstances = new Object2ObjectOpenHashMap<>();
frame = createUpdateLimiter();
tick = createUpdateLimiter();
}
public abstract Storage<T> getStorage();
/**
* Is the given object currently capable of being instanced?
*
* <p>
* This won't be the case for TEs or entities that are outside of loaded chunks.
* </p>
*
* @return true if the object is currently capable of being instanced.
*/
protected abstract boolean canCreateInstance(T obj);
protected DistanceUpdateLimiter createUpdateLimiter() {
if (FlwConfig.get().limitUpdates()) {
return new BandedPrimeLimiter();
@ -65,30 +60,9 @@ public abstract class InstanceManager<T> {
* @return The object count.
*/
public int getObjectCount() {
return instances.size();
return getStorage().getObjectCount();
}
/**
* Is the given object capable of being instanced at all?
*
* @return false if on object cannot be instanced.
*/
protected abstract boolean canInstance(T obj);
/**
* Is the given object currently capable of being instanced?
*
* <p>
* This won't be the case for TEs or entities that are outside of loaded chunks.
* </p>
*
* @return true if the object is currently capable of being instanced.
*/
protected abstract boolean canCreateInstance(T obj);
@Nullable
protected abstract AbstractInstance createRaw(T obj);
/**
* Ticks the InstanceManager.
*
@ -107,14 +81,14 @@ public abstract class InstanceManager<T> {
int cY = (int) cameraY;
int cZ = (int) cameraZ;
ArrayList<TickableInstance> instances = new ArrayList<>(tickableInstances.values());
var instances = getStorage().getInstancesForTicking();
int incr = 500;
int size = instances.size();
int start = 0;
while (start < size) {
int end = Math.min(start + incr, size);
List<TickableInstance> sub = instances.subList(start, end);
var sub = instances.subList(start, end);
taskEngine.submit(() -> {
for (TickableInstance instance : sub) {
tickInstance(cX, cY, cZ, instance);
@ -154,14 +128,14 @@ public abstract class InstanceManager<T> {
int cY = (int) camera.getPosition().y;
int cZ = (int) camera.getPosition().z;
ArrayList<DynamicInstance> instances = new ArrayList<>(dynamicInstances.values());
var instances = getStorage().getInstancesForUpdate();
int incr = 500;
int size = instances.size();
int start = 0;
while (start < size) {
int end = Math.min(start + incr, size);
List<DynamicInstance> sub = instances.subList(start, end);
var sub = instances.subList(start, end);
taskEngine.submit(() -> {
for (DynamicInstance dyn : sub) {
updateInstance(dyn, lookX, lookY, lookZ, cX, cY, cZ);
@ -197,13 +171,19 @@ public abstract class InstanceManager<T> {
public void add(T obj) {
if (!Backend.isOn()) return;
if (canInstance(obj)) {
addInternal(obj);
if (canCreateInstance(obj)) {
getStorage().add(obj);
}
}
public void queueAdd(T obj) {
if (!Backend.isOn()) return;
if (!Backend.isOn()) {
return;
}
if (!canCreateInstance(obj)) {
return;
}
synchronized (queuedAdditions) {
queuedAdditions.add(obj);
@ -212,6 +192,11 @@ public abstract class InstanceManager<T> {
public void queueUpdate(T obj) {
if (!Backend.isOn()) return;
if (!canCreateInstance(obj)) {
return;
}
synchronized (queuedUpdates) {
queuedUpdates.add(obj);
}
@ -231,45 +216,21 @@ public abstract class InstanceManager<T> {
public void update(T obj) {
if (!Backend.isOn()) return;
if (canInstance(obj)) {
AbstractInstance instance = getInstance(obj);
if (instance != null) {
// resetting instances is by default used to handle block state changes.
if (instance.shouldReset()) {
// delete and re-create the instance.
// resetting an instance supersedes updating it.
removeInternal(obj, instance);
createInternal(obj);
} else {
instance.update();
}
}
if (canCreateInstance(obj)) {
getStorage().update(obj);
}
}
public void remove(T obj) {
if (!Backend.isOn()) return;
if (canInstance(obj)) {
AbstractInstance instance = getInstance(obj);
if (instance != null) removeInternal(obj, instance);
if (canCreateInstance(obj)) {
getStorage().remove(obj);
}
}
public void invalidate() {
instances.values().forEach(AbstractInstance::remove);
instances.clear();
dynamicInstances.clear();
tickableInstances.clear();
}
@Nullable
protected <I extends T> AbstractInstance getInstance(I obj) {
if (!Backend.isOn()) return null;
return instances.get(obj);
getStorage().invalidate();
}
protected void processQueuedAdditions() {
@ -285,7 +246,7 @@ public abstract class InstanceManager<T> {
}
if (!queued.isEmpty()) {
queued.forEach(this::addInternal);
queued.forEach(getStorage()::add);
}
}
@ -298,75 +259,16 @@ public abstract class InstanceManager<T> {
}
if (queued.size() > 0) {
queued.forEach(this::update);
}
}
protected void addInternal(T obj) {
if (!Backend.isOn()) return;
AbstractInstance instance = instances.get(obj);
if (instance == null && canCreateInstance(obj)) {
createInternal(obj);
}
}
protected void removeInternal(T obj, AbstractInstance instance) {
instance.remove();
instances.remove(obj);
dynamicInstances.remove(obj);
tickableInstances.remove(obj);
LightUpdater.get(instance.level)
.removeListener(instance);
}
@Nullable
protected AbstractInstance createInternal(T obj) {
AbstractInstance renderer = createRaw(obj);
if (renderer != null) {
setup(obj, renderer);
instances.put(obj, renderer);
}
return renderer;
}
private void setup(T obj, AbstractInstance renderer) {
renderer.init();
renderer.updateLight();
LightUpdater.get(renderer.level)
.addListener(renderer);
if (renderer instanceof TickableInstance r) {
tickableInstances.put(obj, r);
r.tick();
}
if (renderer instanceof DynamicInstance r) {
dynamicInstances.put(obj, r);
r.beginFrame();
queued.forEach(getStorage()::update);
}
}
public void onOriginShift() {
dynamicInstances.clear();
tickableInstances.clear();
instances.replaceAll((obj, instance) -> {
instance.remove();
AbstractInstance out = createRaw(obj);
if (out != null) {
setup(obj, out);
}
return out;
});
getStorage().recreateAll();
}
public void detachLightListeners() {
for (AbstractInstance value : instances.values()) {
public void delete() {
for (AbstractInstance value : getStorage().allInstances()) {
LightUpdater.get(value.level).removeListener(value);
}
}

View file

@ -5,13 +5,15 @@ import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.batching.BatchingEngine;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.effect.Effect;
import com.jozufozu.flywheel.backend.instancing.effect.EffectInstanceManager;
import com.jozufozu.flywheel.backend.instancing.entity.EntityInstanceManager;
import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine;
import com.jozufozu.flywheel.core.Contexts;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.core.shader.WorldProgram;
import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.util.ClientLevelExtension;
import com.jozufozu.flywheel.vanilla.effect.ExampleEffect;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
@ -29,47 +31,47 @@ import net.minecraft.world.level.block.entity.BlockEntity;
*/
public class InstanceWorld {
protected final Engine engine;
protected final InstanceManager<Entity> entityInstanceManager;
protected final InstanceManager<BlockEntity> blockEntityInstanceManager;
protected final InstanceManager<Entity> entities;
protected final InstanceManager<BlockEntity> blockEntities;
public final ParallelTaskEngine taskEngine;
private final InstanceManager<Effect> effects;
public static InstanceWorld create(LevelAccessor level) {
return switch (Backend.getBackendType()) {
case INSTANCING -> {
InstancingEngine<WorldProgram> engine = new InstancingEngine<>(Contexts.WORLD);
var entityInstanceManager = new EntityInstanceManager(engine);
var blockEntityInstanceManager = new BlockEntityInstanceManager(engine);
engine.attachManager(entityInstanceManager);
engine.attachManager(blockEntityInstanceManager);
yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager);
}
case BATCHING -> {
var engine = new BatchingEngine();
var entityInstanceManager = new EntityInstanceManager(engine);
var blockEntityInstanceManager = new BlockEntityInstanceManager(engine);
yield new InstanceWorld(engine, entityInstanceManager, blockEntityInstanceManager);
}
default -> throw new IllegalArgumentException("Unknown engine type");
var engine = switch (Backend.getBackendType()) {
case INSTANCING -> new InstancingEngine<>(Contexts.WORLD);
case BATCHING -> new BatchingEngine();
case OFF -> throw new IllegalStateException("Cannot create instance world when backend is off.");
};
var entities = new EntityInstanceManager(engine);
var blockEntities = new BlockEntityInstanceManager(engine);
var effects = new EffectInstanceManager(engine);
engine.attachManagers(entities, blockEntities, effects);
return new InstanceWorld(engine, entities, blockEntities, effects);
}
public InstanceWorld(Engine engine, InstanceManager<Entity> entityInstanceManager, InstanceManager<BlockEntity> blockEntityInstanceManager) {
public InstanceWorld(Engine engine, InstanceManager<Entity> entities, InstanceManager<BlockEntity> blockEntities,
InstanceManager<Effect> effects) {
this.engine = engine;
this.entityInstanceManager = entityInstanceManager;
this.blockEntityInstanceManager = blockEntityInstanceManager;
this.entities = entities;
this.blockEntities = blockEntities;
this.effects = effects;
this.taskEngine = Backend.getTaskEngine();
}
public InstanceManager<Entity> getEntityInstanceManager() {
return entityInstanceManager;
public InstanceManager<Entity> getEntities() {
return entities;
}
public InstanceManager<BlockEntity> getBlockEntityInstanceManager() {
return blockEntityInstanceManager;
public InstanceManager<Effect> getEffects() {
return effects;
}
public InstanceManager<BlockEntity> getBlockEntities() {
return blockEntities;
}
/**
@ -77,8 +79,8 @@ public class InstanceWorld {
*/
public void delete() {
engine.delete();
entityInstanceManager.detachLightListeners();
blockEntityInstanceManager.detachLightListeners();
entities.delete();
blockEntities.delete();
}
/**
@ -96,8 +98,9 @@ public class InstanceWorld {
taskEngine.syncPoint();
if (!shifted) {
blockEntityInstanceManager.beginFrame(taskEngine, camera);
entityInstanceManager.beginFrame(taskEngine, camera);
blockEntities.beginFrame(taskEngine, camera);
entities.beginFrame(taskEngine, camera);
effects.beginFrame(taskEngine, camera);
}
engine.beginFrame(taskEngine, camera);
@ -115,8 +118,13 @@ public class InstanceWorld {
if (renderViewEntity == null) return;
blockEntityInstanceManager.tick(taskEngine, renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
entityInstanceManager.tick(taskEngine, renderViewEntity.getX(), renderViewEntity.getY(), renderViewEntity.getZ());
double x = renderViewEntity.getX();
double y = renderViewEntity.getY();
double z = renderViewEntity.getZ();
blockEntities.tick(taskEngine, x, y, z);
entities.tick(taskEngine, x, y, z);
effects.tick(taskEngine, x, y, z);
}
/**
@ -148,7 +156,7 @@ public class InstanceWorld {
// Block entities are loaded while chunks are baked.
// Entities are loaded with the world, so when chunks are reloaded they need to be re-added.
ClientLevelExtension.getAllLoadedEntities(world)
.forEach(entityInstanceManager::add);
.forEach(entities::add);
}
}

View file

@ -3,6 +3,7 @@ package com.jozufozu.flywheel.backend.instancing;
import java.util.List;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.effect.Effect;
import com.jozufozu.flywheel.config.FlwCommands;
import com.jozufozu.flywheel.config.FlwConfig;
import com.jozufozu.flywheel.core.RenderContext;
@ -10,6 +11,7 @@ import com.jozufozu.flywheel.event.BeginFrameEvent;
import com.jozufozu.flywheel.event.ReloadRenderersEvent;
import com.jozufozu.flywheel.util.AnimationTickHolder;
import com.jozufozu.flywheel.util.WorldAttached;
import com.jozufozu.flywheel.vanilla.effect.ExampleEffect;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientLevel;
@ -30,7 +32,7 @@ public class InstancedRenderDispatcher {
public static void enqueueUpdate(BlockEntity blockEntity) {
if (Backend.isOn() && blockEntity.hasLevel() && blockEntity.getLevel() instanceof ClientLevel) {
instanceWorlds.get(blockEntity.getLevel())
.getBlockEntityInstanceManager()
.getBlockEntities()
.queueUpdate(blockEntity);
}
}
@ -42,17 +44,21 @@ public class InstancedRenderDispatcher {
public static void enqueueUpdate(Entity entity) {
if (Backend.isOn()) {
instanceWorlds.get(entity.level)
.getEntityInstanceManager()
.getEntities()
.queueUpdate(entity);
}
}
public static InstanceManager<BlockEntity> getBlockEntities(LevelAccessor world) {
return getInstanceWorld(world).getBlockEntityInstanceManager();
return getInstanceWorld(world).getBlockEntities();
}
public static InstanceManager<Entity> getEntities(LevelAccessor world) {
return getInstanceWorld(world).getEntityInstanceManager();
return getInstanceWorld(world).getEntities();
}
public static InstanceManager<Effect> getEffects(LevelAccessor world) {
return getInstanceWorld(world).getEffects();
}
/**
@ -119,7 +125,7 @@ public class InstancedRenderDispatcher {
InstanceWorld instanceWorld = instanceWorlds.get(Minecraft.getInstance().level);
debug.add("Update limiting: " + FlwCommands.boolToText(FlwConfig.get().limitUpdates()).getString());
debug.add("B: " + instanceWorld.blockEntityInstanceManager.getObjectCount() + ", E: " + instanceWorld.entityInstanceManager.getObjectCount());
debug.add("B: " + instanceWorld.blockEntities.getObjectCount() + ", E: " + instanceWorld.entities.getObjectCount());
instanceWorld.engine.addDebugInfo(debug);
} else {
debug.add("Disabled");

View file

@ -0,0 +1,148 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.light.LightUpdater;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
public abstract class One2OneStorage<T> implements Storage<T> {
private final Map<T, AbstractInstance> instances;
private final Object2ObjectOpenHashMap<T, TickableInstance> tickableInstances;
private final Object2ObjectOpenHashMap<T, DynamicInstance> dynamicInstances;
protected final InstancerManager instancerManager;
public One2OneStorage(InstancerManager instancerManager) {
this.instancerManager = instancerManager;
this.instances = new HashMap<>();
this.dynamicInstances = new Object2ObjectOpenHashMap<>();
this.tickableInstances = new Object2ObjectOpenHashMap<>();
}
@Override
public int getObjectCount() {
return instances.size();
}
@Override
public Iterable<AbstractInstance> allInstances() {
return instances.values();
}
@Override
public List<TickableInstance> getInstancesForTicking() {
return new ArrayList<>(tickableInstances.values());
}
@Override
public List<DynamicInstance> getInstancesForUpdate() {
return new ArrayList<>(dynamicInstances.values());
}
@Override
public void invalidate() {
instances.values().forEach(AbstractInstance::remove);
instances.clear();
dynamicInstances.clear();
tickableInstances.clear();
}
@Override
public void add(T obj) {
AbstractInstance instance = instances.get(obj);
if (instance == null) {
create(obj);
}
}
@Override
public void remove(T obj) {
var instance = instances.remove(obj);
if (instance == null) {
return;
}
instance.remove();
dynamicInstances.remove(obj);
tickableInstances.remove(obj);
LightUpdater.get(instance.level)
.removeListener(instance);
}
@Override
public void update(T obj) {
AbstractInstance instance = instances.get(obj);
if (instance == null) {
return;
}
// resetting instances is by default used to handle block state changes.
if (instance.shouldReset()) {
// delete and re-create the instance.
// resetting an instance supersedes updating it.
remove(obj);
create(obj);
} else {
instance.update();
}
}
@Override
public void recreateAll() {
dynamicInstances.clear();
tickableInstances.clear();
instances.replaceAll((obj, instance) -> {
instance.remove();
AbstractInstance out = createRaw(obj);
if (out != null) {
setup(obj, out);
}
return out;
});
}
private void create(T obj) {
AbstractInstance renderer = createRaw(obj);
if (renderer != null) {
setup(obj, renderer);
instances.put(obj, renderer);
}
}
@Nullable
protected abstract AbstractInstance createRaw(T obj);
private void setup(T obj, AbstractInstance renderer) {
renderer.init();
renderer.updateLight();
LightUpdater.get(renderer.level)
.addListener(renderer);
if (renderer instanceof TickableInstance r) {
tickableInstances.put(obj, r);
r.tick();
}
if (renderer instanceof DynamicInstance r) {
dynamicInstances.put(obj, r);
r.beginFrame();
}
}
}

View file

@ -0,0 +1,26 @@
package com.jozufozu.flywheel.backend.instancing;
import java.util.List;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
public interface Storage<T> {
int getObjectCount();
Iterable<AbstractInstance> allInstances();
List<TickableInstance> getInstancesForTicking();
List<DynamicInstance> getInstancesForUpdate();
void invalidate();
void add(T obj);
void remove(T obj);
void update(T obj);
void recreateAll();
}

View file

@ -8,9 +8,7 @@ import java.util.Map;
import com.jozufozu.flywheel.api.InstancedPart;
import com.jozufozu.flywheel.api.struct.StructType;
import com.jozufozu.flywheel.backend.OptifineHandler;
import com.jozufozu.flywheel.backend.instancing.BatchDrawingTracker;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.instancing.*;
import com.jozufozu.flywheel.core.RenderContext;
import com.jozufozu.flywheel.util.FlwUtil;
import com.mojang.blaze3d.platform.Lighting;
@ -112,6 +110,11 @@ public class BatchingEngine implements Engine {
submitTasks(stack, taskEngine);
}
@Override
public void attachManagers(InstanceManager<?>... listener) {
// noop
}
@Override
public void addDebugInfo(List<String> info) {
info.add("Batching");

View file

@ -5,8 +5,10 @@ import java.util.List;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.One2OneStorage;
import com.jozufozu.flywheel.backend.instancing.Storage;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
@ -17,51 +19,43 @@ import net.minecraft.world.level.block.entity.BlockEntity;
public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
private final Long2ObjectMap<BlockEntityInstance<?>> posLookup = new Long2ObjectOpenHashMap<>();
private final BlockEntityStorage storage;
public BlockEntityInstanceManager(InstancerManager instancerManager) {
super(instancerManager);
storage = new BlockEntityStorage(instancerManager);
}
@Override
public Storage<BlockEntity> getStorage() {
return storage;
}
public void getCrumblingInstances(long pos, List<BlockEntityInstance<?>> data) {
BlockEntityInstance<?> instance = posLookup.get(pos);
BlockEntityInstance<?> instance = storage.posLookup.get(pos);
if (instance != null) {
data.add(instance);
}
}
@Override
protected boolean canInstance(BlockEntity obj) {
return obj != null && InstancedRenderRegistry.canInstance(obj.getType());
}
@Override
protected AbstractInstance createRaw(BlockEntity obj) {
var instance = InstancedRenderRegistry.createInstance(instancerManager, obj);
if (instance != null) {
BlockPos blockPos = obj.getBlockPos();
posLookup.put(blockPos.asLong(), instance);
protected boolean canCreateInstance(BlockEntity blockEntity) {
if (blockEntity.isRemoved()) {
return false;
}
return instance;
}
@Override
protected void removeInternal(BlockEntity obj, AbstractInstance instance) {
super.removeInternal(obj, instance);
posLookup.remove(obj.getBlockPos().asLong());
}
@Override
protected boolean canCreateInstance(BlockEntity blockEntity) {
if (blockEntity.isRemoved()) return false;
if (!InstancedRenderRegistry.canInstance(blockEntity.getType())) {
return false;
}
Level world = blockEntity.getLevel();
if (world == null) return false;
if (world == null) {
return false;
}
if (world.isEmptyBlock(blockEntity.getBlockPos())) return false;
if (world.isEmptyBlock(blockEntity.getBlockPos())) {
return false;
}
if (Backend.isFlywheelWorld(world)) {
BlockPos pos = blockEntity.getBlockPos();
@ -73,4 +67,32 @@ public class BlockEntityInstanceManager extends InstanceManager<BlockEntity> {
return false;
}
public static class BlockEntityStorage extends One2OneStorage<BlockEntity> {
final Long2ObjectMap<BlockEntityInstance<?>> posLookup = new Long2ObjectOpenHashMap<>();
public BlockEntityStorage(InstancerManager manager) {
super(manager);
}
@Override
protected AbstractInstance createRaw(BlockEntity obj) {
var instance = InstancedRenderRegistry.createInstance(instancerManager, obj);
if (instance != null) {
BlockPos blockPos = obj.getBlockPos();
posLookup.put(blockPos.asLong(), instance);
}
return instance;
}
@Override
public void remove(BlockEntity obj) {
super.remove(obj);
posLookup.remove(obj.getBlockPos().asLong());
}
}
}

View file

@ -0,0 +1,11 @@
package com.jozufozu.flywheel.backend.instancing.effect;
import java.util.Collection;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
public interface Effect {
Collection<? extends AbstractInstance> createInstances(InstancerManager instancerManager);
}

View file

@ -0,0 +1,155 @@
package com.jozufozu.flywheel.backend.instancing.effect;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.api.instance.TickableInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.Storage;
import com.jozufozu.flywheel.light.LightUpdater;
public class EffectInstanceManager extends InstanceManager<Effect> {
private final EffectStorage<Effect> storage;
public EffectInstanceManager(InstancerManager instancerManager) {
storage = new EffectStorage<>(instancerManager);
}
@Override
public Storage<Effect> getStorage() {
return storage;
}
@Override
protected boolean canCreateInstance(Effect obj) {
return true;
}
public static class EffectStorage<T extends Effect> implements Storage<T> {
private final Multimap<T, AbstractInstance> instances;
private final Set<DynamicInstance> dynamicInstances;
private final Set<TickableInstance> tickableInstances;
private final InstancerManager manager;
public EffectStorage(InstancerManager manager) {
this.instances = HashMultimap.create();
this.dynamicInstances = new HashSet<>();
this.tickableInstances = new HashSet<>();
this.manager = manager;
}
@Override
public int getObjectCount() {
return instances.size();
}
@Override
public Iterable<AbstractInstance> allInstances() {
return instances.values();
}
@Override
public List<TickableInstance> getInstancesForTicking() {
return new ArrayList<>(tickableInstances);
}
@Override
public List<DynamicInstance> getInstancesForUpdate() {
return new ArrayList<>(dynamicInstances);
}
@Override
public void invalidate() {
instances.values().forEach(AbstractInstance::remove);
instances.clear();
tickableInstances.clear();
dynamicInstances.clear();
}
@Override
public void add(T obj) {
var instances = this.instances.get(obj);
if (instances.isEmpty()) {
create(obj);
}
}
@Override
public void remove(T obj) {
var instances = this.instances.removeAll(obj);
if (instances.isEmpty()) {
return;
}
this.tickableInstances.removeAll(instances);
this.dynamicInstances.removeAll(instances);
for (AbstractInstance instance : instances) {
LightUpdater.get(instance.level)
.removeListener(instance);
}
}
@Override
public void update(T obj) {
var instances = this.instances.get(obj);
if (instances.isEmpty()) {
return;
}
instances.forEach(AbstractInstance::update);
}
@Override
public void recreateAll() {
this.dynamicInstances.clear();
this.tickableInstances.clear();
this.instances.asMap()
.forEach((obj, instances) -> {
instances.forEach(AbstractInstance::remove);
instances.clear();
var newInstances = obj.createInstances(manager);
newInstances.forEach(this::setup);
instances.addAll(newInstances);
});
}
private void create(T obj) {
var instances = obj.createInstances(manager);
this.instances.putAll(obj, instances);
instances.forEach(this::setup);
}
private void setup(AbstractInstance renderer) {
renderer.init();
renderer.updateLight();
LightUpdater.get(renderer.level)
.addListener(renderer);
if (renderer instanceof TickableInstance r) {
tickableInstances.add(r);
r.tick();
}
if (renderer instanceof DynamicInstance r) {
dynamicInstances.add(r);
r.beginFrame();
}
}
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.backend.instancing.effect;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -1,10 +1,13 @@
package com.jozufozu.flywheel.backend.instancing.entity;
import org.jetbrains.annotations.Nullable;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.backend.Backend;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.One2OneStorage;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
@ -13,23 +16,31 @@ import net.minecraft.world.level.Level;
public class EntityInstanceManager extends InstanceManager<Entity> {
private final One2OneStorage<Entity> storage;
public EntityInstanceManager(InstancerManager instancerManager) {
super(instancerManager);
storage = new One2OneStorage<>(instancerManager) {
@Override
protected @Nullable AbstractInstance createRaw(Entity obj) {
return InstancedRenderRegistry.createInstance(this.instancerManager, obj);
}
};
}
@Override
protected boolean canInstance(Entity obj) {
return obj != null && InstancedRenderRegistry.canInstance(obj.getType());
}
@Override
protected AbstractInstance createRaw(Entity obj) {
return InstancedRenderRegistry.createInstance(instancerManager, obj);
public One2OneStorage<Entity> getStorage() {
return storage;
}
@Override
protected boolean canCreateInstance(Entity entity) {
if (!entity.isAlive()) return false;
if (!entity.isAlive()) {
return false;
}
if (!InstancedRenderRegistry.canInstance(entity.getType())) {
return false;
}
Level world = entity.level;

View file

@ -19,8 +19,8 @@ import com.jozufozu.flywheel.api.vertex.VertexType;
import com.jozufozu.flywheel.backend.gl.GlVertexArray;
import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer;
import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstanceManager;
import com.jozufozu.flywheel.backend.instancing.Engine;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.TaskEngine;
import com.jozufozu.flywheel.backend.instancing.blockentity.BlockEntityInstance;
@ -202,8 +202,9 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
return originCoordinate;
}
public void attachManager(InstanceManager<?> listener) {
instanceManagers.add(listener);
@Override
public void attachManagers(InstanceManager<?>... listener) {
instanceManagers.addAll(List.of(listener));
}
@Override
@ -350,7 +351,7 @@ public class InstancingEngine<P extends WorldProgram> implements Engine {
}
if (!(InstancedRenderDispatcher.getInstanceWorld(level)
.getBlockEntityInstanceManager() instanceof BlockEntityInstanceManager beim)) {
.getBlockEntities() instanceof BlockEntityInstanceManager beim)) {
return Int2ObjectMaps.emptyMap();
}

View file

@ -144,7 +144,7 @@ public class CrumblingRenderer {
private State() {
instancerManager = new CrumblingEngine();
instanceManager = new CrumblingInstanceManager(instancerManager);
instancerManager.attachManager(instanceManager);
instancerManager.attachManagers(instanceManager);
}
private void kill() {

View file

@ -8,7 +8,7 @@ import com.jozufozu.flywheel.core.source.parse.AbstractShaderElement;
import com.jozufozu.flywheel.core.source.span.Span;
public class ShaderField extends AbstractShaderElement {
public static final Pattern PATTERN = Pattern.compile("layout\\s+\\(location\\s+=\\s+(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)");
public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)");
public final Span location;
public final @Nullable Decoration decoration;

View file

@ -12,6 +12,7 @@ import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
@Mixin(LevelRenderer.class)
@ -24,9 +25,16 @@ public class LevelRendererInstanceUpdateMixin {
*/
@Inject(at = @At("TAIL"), method = "setBlockDirty(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/block/state/BlockState;)V")
private void checkUpdate(BlockPos pos, BlockState lastState, BlockState newState, CallbackInfo ci) {
if (Backend.isOn()) {
InstancedRenderDispatcher.getBlockEntities(level)
.update(level.getBlockEntity(pos));
if (!Backend.isOn()) {
return;
}
BlockEntity blockEntity = level.getBlockEntity(pos);
if (blockEntity == null) {
return;
}
InstancedRenderDispatcher.getBlockEntities(level)
.update(blockEntity);
}
}

View file

@ -0,0 +1,117 @@
package com.jozufozu.flywheel.vanilla.effect;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.jozufozu.flywheel.api.InstancerManager;
import com.jozufozu.flywheel.api.instance.DynamicInstance;
import com.jozufozu.flywheel.backend.instancing.AbstractInstance;
import com.jozufozu.flywheel.backend.instancing.InstancedRenderDispatcher;
import com.jozufozu.flywheel.backend.instancing.effect.Effect;
import com.jozufozu.flywheel.core.Models;
import com.jozufozu.flywheel.core.structs.StructTypes;
import com.jozufozu.flywheel.core.structs.model.TransformedPart;
import com.jozufozu.flywheel.util.box.GridAlignedBB;
import com.jozufozu.flywheel.util.box.ImmutableBox;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.phys.Vec3;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.fml.LogicalSide;
public class ExampleEffect implements Effect {
private static final int INSTANCE_COUNT = 50;
private final Level level;
private final Vec3 targetPoint;
private final BlockPos blockPos;
private final ImmutableBox volume;
private final List<Instance> effects;
public ExampleEffect(Level level, Vec3 targetPoint) {
this.level = level;
this.targetPoint = targetPoint;
this.blockPos = new BlockPos(targetPoint);
this.effects = new ArrayList<>();
this.volume = GridAlignedBB.from(this.blockPos);
}
public static void spawn(TickEvent.PlayerTickEvent event) {
if (event.side == LogicalSide.SERVER || event.phase == TickEvent.Phase.START) {
return;
}
Player player = event.player;
Level level = player.level;
if (level.random.nextFloat() > 0.01) {
return;
}
var effects = InstancedRenderDispatcher.getEffects(level);
effects.add(new ExampleEffect(level, player.position()));
}
@Override
public Collection<? extends AbstractInstance> createInstances(InstancerManager instancerManager) {
effects.clear();
for (int i = 0; i < INSTANCE_COUNT; i++) {
effects.add(new Instance(instancerManager, level));
}
return effects;
}
public class Instance extends AbstractInstance implements DynamicInstance {
TransformedPart firefly;
public Instance(InstancerManager instancerManager, Level level) {
super(instancerManager, level);
}
@Override
public void init() {
firefly = instancerManager.factory(StructTypes.TRANSFORMED)
.model(Models.block(Blocks.SHROOMLIGHT.defaultBlockState()))
.createInstance();
firefly.setBlockLight(15)
.setSkyLight(15);
}
@Override
public BlockPos getWorldPosition() {
return blockPos;
}
@Override
public void remove() {
firefly.delete();
}
@Override
public ImmutableBox getVolume() {
return volume;
}
@Override
public void beginFrame() {
var x = level.random.nextFloat() * 3 - 1.5;
var y = level.random.nextFloat() * 3 - 1.5;
var z = level.random.nextFloat() * 3 - 1.5;
firefly.loadIdentity()
.translate(instancerManager.getOriginCoordinate())
.translate(targetPoint)
.translate(x, y, z)
.scale(1 / 16f);
}
}
}

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.vanilla.effect;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -0,0 +1,6 @@
@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault
package com.jozufozu.flywheel.vanilla;
import javax.annotation.ParametersAreNonnullByDefault;
import net.minecraft.MethodsReturnNonnullByDefault;

View file

@ -2,11 +2,11 @@
#use "flywheel:util/light.glsl"
#use "flywheel:util/quaternion.glsl"
layout (location = 0) in vec2 oriented_light;
layout (location = 1) in vec4 oriented_color;
layout (location = 2) in vec3 oriented_pos;
layout (location = 3) in vec3 oriented_pivot;
layout (location = 4) in vec4 oriented_rotation;
layout(location = 0) in vec2 oriented_light;
layout(location = 1) in vec4 oriented_color;
layout(location = 2) in vec3 oriented_pos;
layout(location = 3) in vec3 oriented_pivot;
layout(location = 4) in vec4 oriented_rotation;
void flw_instanceVertex() {
flw_vertexPos = vec4(rotateVertexByQuat(flw_vertexPos.xyz - oriented_pivot, oriented_rotation) + oriented_pivot + oriented_pos, 1.0);

View file

@ -1,10 +1,10 @@
#use "flywheel:api/vertex.glsl"
#use "flywheel:util/light.glsl"
layout (location = 0) in vec2 transformed_light;
layout (location = 1) in vec4 transformed_color;
layout (location = 2) in mat4 transformed_pose;
layout (location = 6) in mat3 transformed_normal;
layout(location = 0) in vec2 transformed_light;
layout(location = 1) in vec4 transformed_color;
layout(location = 2) in mat4 transformed_pose;
layout(location = 6) in mat3 transformed_normal;
void flw_instanceVertex() {
flw_vertexPos = transformed_pose * flw_vertexPos;