First try

- Implement instance hiding by deleting/stealing
- Work around instancer persistence by storing a recreation supplier in
  the instance handle
- Rework instancer ctors to just take an InstancerKey
- Parameterize InstanceHandle by I extends Instance so the steal method
  and the supplier can be safely assigned
This commit is contained in:
Jozufozu 2024-09-18 23:37:19 -07:00
parent e1b594ac47
commit 5a75fe972f
8 changed files with 87 additions and 40 deletions

View file

@ -7,4 +7,8 @@ public interface InstanceHandle {
void setChanged(); void setChanged();
void setDeleted(); void setDeleted();
void setVisible(boolean visible);
boolean isVisible();
} }

View file

@ -1,6 +1,7 @@
package dev.engine_room.flywheel.backend.engine; package dev.engine_room.flywheel.backend.engine;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -13,26 +14,32 @@ import dev.engine_room.flywheel.backend.util.AtomicBitSet;
public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> { public abstract class AbstractInstancer<I extends Instance> implements Instancer<I> {
public final InstanceType<I> type; public final InstanceType<I> type;
public final Environment environment; public final Environment environment;
private final Supplier<AbstractInstancer<I>> recreate;
// Lock for all instances, only needs to be used in methods that may run on the TaskExecutor. // Lock for all instances, only needs to be used in methods that may run on the TaskExecutor.
protected final Object lock = new Object(); protected final Object lock = new Object();
protected final ArrayList<I> instances = new ArrayList<>(); protected final ArrayList<I> instances = new ArrayList<>();
protected final ArrayList<InstanceHandleImpl> handles = new ArrayList<>(); protected final ArrayList<InstanceHandleImpl<I>> handles = new ArrayList<>();
protected final AtomicBitSet changed = new AtomicBitSet(); protected final AtomicBitSet changed = new AtomicBitSet();
protected final AtomicBitSet deleted = new AtomicBitSet(); protected final AtomicBitSet deleted = new AtomicBitSet();
protected AbstractInstancer(InstanceType<I> type, Environment environment) { protected AbstractInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
this.type = type; this.type = key.type();
this.environment = environment; this.environment = key.environment();
this.recreate = recreate;
} }
@Override @Override
public I createInstance() { public I createInstance() {
synchronized (lock) { synchronized (lock) {
var i = instances.size(); var i = instances.size();
var handle = new InstanceHandleImpl(this, i); var handle = new InstanceHandleImpl<I>();
handle.instancer = this;
handle.recreate = recreate;
handle.index = i;
I instance = type.create(handle); I instance = type.create(handle);
handle.instance = instance;
addLocked(instance, handle); addLocked(instance, handle);
return instance; return instance;
@ -47,12 +54,15 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
var instanceHandle = instance.handle(); var instanceHandle = instance.handle();
if (!(instanceHandle instanceof InstanceHandleImpl handle)) { if (!(instanceHandle instanceof InstanceHandleImpl<?>)) {
// UB: do nothing // UB: do nothing
return; return;
} }
if (handle.instancer == this) { // Should InstanceType have an isInstance method?
var handle = (InstanceHandleImpl<I>) instanceHandle;
if (handle.instancer == this && handle.visible) {
return; return;
} }
@ -65,19 +75,23 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
// is filtering deleted instances later, so is safe. // is filtering deleted instances later, so is safe.
handle.setDeleted(); handle.setDeleted();
// Only lock now that we'll be mutating our state. // Add the instance to this instancer.
synchronized (lock) { handle.instancer = this;
// Add the instance to this instancer. handle.recreate = recreate;
handle.instancer = this;
handle.index = instances.size(); if (handle.visible) {
addLocked(instance, handle); // Only lock now that we'll be mutating our state.
synchronized (lock) {
handle.index = instances.size();
addLocked(instance, handle);
}
} }
} }
/** /**
* Calls must be synchronized on {@link #lock}. * Calls must be synchronized on {@link #lock}.
*/ */
private void addLocked(I instance, InstanceHandleImpl handle) { private void addLocked(I instance, InstanceHandleImpl<I> handle) {
instances.add(instance); instances.add(instance);
handles.add(handle); handles.add(handle);
changed.set(handle.index); changed.set(handle.index);
@ -163,7 +177,7 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
* Clear all instances without freeing resources. * Clear all instances without freeing resources.
*/ */
public void clear() { public void clear() {
for (InstanceHandleImpl handle : handles) { for (InstanceHandleImpl<I> handle : handles) {
// Only clear instances that belong to this instancer. // Only clear instances that belong to this instancer.
// If one of these handles was stolen by another instancer, // If one of these handles was stolen by another instancer,
// clearing it here would cause significant visual artifacts and instance leaks. // clearing it here would cause significant visual artifacts and instance leaks.

View file

@ -11,7 +11,6 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import dev.engine_room.flywheel.api.backend.Engine; import dev.engine_room.flywheel.api.backend.Engine;
import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.api.visualization.VisualType; import dev.engine_room.flywheel.api.visualization.VisualType;
import dev.engine_room.flywheel.backend.FlwBackend; import dev.engine_room.flywheel.backend.FlwBackend;
@ -36,9 +35,13 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
*/ */
protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>(); protected final Queue<UninitializedInstancer<N, ?>> initializationQueue = new ConcurrentLinkedQueue<>();
public <I extends Instance> AbstractInstancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) {
return getInstancer(new InstancerKey<>(environment, type, model, visualType, bias));
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <I extends Instance> Instancer<I> getInstancer(Environment environment, InstanceType<I> type, Model model, VisualType visualType, int bias) { public <I extends Instance> AbstractInstancer<I> getInstancer(InstancerKey<I> key) {
return (Instancer<I>) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType, bias), this::createAndDeferInit); return (AbstractInstancer<I>) instancers.computeIfAbsent(key, this::createAndDeferInit);
} }
public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) { public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) {
@ -94,8 +97,8 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
return false; return false;
} }
protected static <I extends AbstractInstancer<?>> Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl>>>> doCrumblingSort(Class<I> clazz, List<Engine.CrumblingBlock> crumblingBlocks) { protected static <I extends AbstractInstancer<?>> Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl<?>>>>> doCrumblingSort(Class<I> clazz, List<Engine.CrumblingBlock> crumblingBlocks) {
Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl>>>> byType = new HashMap<>(); Map<GroupKey<?>, Int2ObjectMap<List<Pair<I, InstanceHandleImpl<?>>>>> byType = new HashMap<>();
for (Engine.CrumblingBlock block : crumblingBlocks) { for (Engine.CrumblingBlock block : crumblingBlocks) {
int progress = block.progress(); int progress = block.progress();
@ -107,7 +110,7 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
// Filter out instances that weren't created by this engine. // Filter out instances that weren't created by this engine.
// If all is well, we probably shouldn't take the `continue` // If all is well, we probably shouldn't take the `continue`
// branches but better to do checked casts. // branches but better to do checked casts.
if (!(instance.handle() instanceof InstanceHandleImpl impl)) { if (!(instance.handle() instanceof InstanceHandleImpl<?> impl)) {
continue; continue;
} }

View file

@ -1,16 +1,22 @@
package dev.engine_room.flywheel.backend.engine; package dev.engine_room.flywheel.backend.engine;
import java.util.function.Supplier;
import org.jetbrains.annotations.UnknownNullability;
import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceHandle; import dev.engine_room.flywheel.api.instance.InstanceHandle;
public class InstanceHandleImpl implements InstanceHandle { public class InstanceHandleImpl<I extends Instance> implements InstanceHandle {
public AbstractInstancer<?> instancer; @UnknownNullability
public AbstractInstancer<I> instancer;
@UnknownNullability
public I instance;
@UnknownNullability
public Supplier<AbstractInstancer<I>> recreate;
public boolean visible = true;
public int index; public int index;
public InstanceHandleImpl(AbstractInstancer<?> instancer, int index) {
this.instancer = instancer;
this.index = index;
}
@Override @Override
public void setChanged() { public void setChanged() {
instancer.notifyDirty(index); instancer.notifyDirty(index);
@ -23,6 +29,27 @@ public class InstanceHandleImpl implements InstanceHandle {
clear(); clear();
} }
@Override
public void setVisible(boolean visible) {
if (this.visible == visible) {
return;
}
this.visible = visible;
if (visible) {
recreate.get().stealInstance(instance);
} else {
instancer.notifyRemoval(index);
clear();
}
}
@Override
public boolean isVisible() {
return visible;
}
public void clear() { public void clear() {
index = -1; index = -1;
} }

View file

@ -66,7 +66,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
@Override @Override
protected <I extends Instance> IndirectInstancer<?> create(InstancerKey<I> key) { protected <I extends Instance> IndirectInstancer<?> create(InstancerKey<I> key) {
return new IndirectInstancer<>(key.type(), key.environment(), key.model()); return new IndirectInstancer<>(key, () -> getInstancer(key));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")

View file

@ -2,17 +2,16 @@ package dev.engine_room.flywheel.backend.engine.indirect;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.UnknownNullability; import org.jetbrains.annotations.UnknownNullability;
import org.joml.Vector4fc; import org.joml.Vector4fc;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.InstanceWriter; import dev.engine_room.flywheel.api.instance.InstanceWriter;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.backend.engine.AbstractInstancer; import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.util.AtomicBitSet; import dev.engine_room.flywheel.backend.util.AtomicBitSet;
import dev.engine_room.flywheel.lib.math.MoreMath; import dev.engine_room.flywheel.lib.math.MoreMath;
@ -29,12 +28,12 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
private int modelIndex = -1; private int modelIndex = -1;
private int baseInstance = -1; private int baseInstance = -1;
public IndirectInstancer(InstanceType<I> type, Environment environment, Model model) { public IndirectInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
super(type, environment); super(key, recreate);
instanceStride = MoreMath.align4(type.layout() instanceStride = MoreMath.align4(type.layout()
.byteSize()); .byteSize());
writer = this.type.writer(); writer = this.type.writer();
boundingSphere = model.boundingSphere(); boundingSphere = key.model().boundingSphere();
} }
@Override @Override

View file

@ -128,7 +128,7 @@ public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
@Override @Override
protected <I extends Instance> InstancedInstancer<I> create(InstancerKey<I> key) { protected <I extends Instance> InstancedInstancer<I> create(InstancerKey<I> key) {
return new InstancedInstancer<>(key.type(), key.environment()); return new InstancedInstancer<>(key, () -> getInstancer(key));
} }
@Override @Override

View file

@ -2,14 +2,14 @@ package dev.engine_room.flywheel.backend.engine.instancing;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.Instance;
import dev.engine_room.flywheel.api.instance.InstanceType;
import dev.engine_room.flywheel.api.instance.InstanceWriter; import dev.engine_room.flywheel.api.instance.InstanceWriter;
import dev.engine_room.flywheel.backend.engine.AbstractInstancer; import dev.engine_room.flywheel.backend.engine.AbstractInstancer;
import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.gl.TextureBuffer; import dev.engine_room.flywheel.backend.gl.TextureBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBufferUsage; import dev.engine_room.flywheel.backend.gl.buffer.GlBufferUsage;
@ -25,8 +25,8 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
private final List<InstancedDraw> draws = new ArrayList<>(); private final List<InstancedDraw> draws = new ArrayList<>();
public InstancedInstancer(InstanceType<I> type, Environment environment) { public InstancedInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
super(type, environment); super(key, recreate);
var layout = type.layout(); var layout = type.layout();
// Align to one texel in the texture buffer // Align to one texel in the texture buffer
instanceStride = MoreMath.align16(layout.byteSize()); instanceStride = MoreMath.align16(layout.byteSize());