Tasteful stateful handles

- Use state machine interface in InstanceHandleImpl
- 3 states: deleted, visible, hidden
- Visible is directly implemented by AbstractInstancer
- Hidden stores the instancer supplier to recreate an instancer
This commit is contained in:
Jozufozu 2024-09-22 12:28:35 -07:00
parent 6a6d98c0a7
commit c5d9abab5f
3 changed files with 132 additions and 38 deletions

View file

@ -11,10 +11,10 @@ import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.util.AtomicBitSet; 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>, InstanceHandleImpl.State<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; private final InstanceHandleImpl.Hidden<I> hidden;
// 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();
@ -27,20 +27,45 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
protected AbstractInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) { protected AbstractInstancer(InstancerKey<I> key, Supplier<AbstractInstancer<I>> recreate) {
this.type = key.type(); this.type = key.type();
this.environment = key.environment(); this.environment = key.environment();
this.recreate = recreate; this.hidden = new InstanceHandleImpl.Hidden<>(recreate);
}
@Override
public InstanceHandleImpl.State<I> setChanged(int index) {
notifyDirty(index);
return this;
}
@Override
public InstanceHandleImpl.State<I> setDeleted(int index) {
notifyRemoval(index);
return InstanceHandleImpl.Deleted.instance();
}
@Override
public InstanceHandleImpl.State<I> setVisible(int index, I instance, boolean visible) {
if (visible) {
return this;
}
notifyRemoval(index);
return hidden;
}
@Override
public InstanceHandleImpl.Status status() {
return InstanceHandleImpl.Status.VISIBLE;
} }
@Override @Override
public I createInstance() { public I createInstance() {
synchronized (lock) { var handle = new InstanceHandleImpl<>(this);
var i = instances.size(); I instance = type.create(handle);
var handle = new InstanceHandleImpl<I>(); handle.instance = instance;
handle.instancer = this;
handle.recreate = recreate;
handle.index = i;
I instance = type.create(handle);
handle.instance = instance;
synchronized (lock) {
handle.index = instances.size();
addLocked(instance, handle); addLocked(instance, handle);
return instance; return instance;
} }
@ -62,7 +87,8 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
// Should InstanceType have an isInstance method? // Should InstanceType have an isInstance method?
var handle = (InstanceHandleImpl<I>) instanceHandle; var handle = (InstanceHandleImpl<I>) instanceHandle;
if (handle.instancer == this && handle.visible) { // Should you be allowed to steal deleted instances?
if (handle.state == this || handle.state.status() == InstanceHandleImpl.Status.DELETED) {
return; return;
} }
@ -76,15 +102,18 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
handle.setDeleted(); handle.setDeleted();
// Add the instance to this instancer. // Add the instance to this instancer.
handle.instancer = this; switch (handle.state.status()) {
handle.recreate = recreate; case VISIBLE:
handle.state = this;
if (handle.visible) {
// Only lock now that we'll be mutating our state. // Only lock now that we'll be mutating our state.
synchronized (lock) { synchronized (lock) {
handle.index = instances.size(); handle.index = instances.size();
addLocked(instance, handle); addLocked(instance, handle);
} }
break;
case HIDDEN:
handle.state = hidden;
break;
} }
} }
@ -187,8 +216,9 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
// clearing it here would cause significant visual artifacts and instance leaks. // clearing it here would cause significant visual artifacts and instance leaks.
// At the same time, we need to clear handles we own to prevent // At the same time, we need to clear handles we own to prevent
// instances from changing/deleting positions in this instancer that no longer exist. // instances from changing/deleting positions in this instancer that no longer exist.
if (handle.instancer == this) { if (handle.state == this || handle.state == hidden) {
handle.clear(); handle.clear();
handle.state = InstanceHandleImpl.Deleted.instance();
} }
} }
instances.clear(); instances.clear();

View file

@ -115,8 +115,11 @@ public abstract class DrawManager<N extends AbstractInstancer<?>> {
continue; continue;
} }
AbstractInstancer<?> abstractInstancer = impl.instancer; InstanceHandleImpl.State<?> abstractInstancer = impl.state;
// AbstractInstancer directly implement HandleState, so this check is valid.
if (!clazz.isInstance(abstractInstancer)) { if (!clazz.isInstance(abstractInstancer)) {
// This rejects instances that were created by a different engine,
// and also instances that are hidden or deleted.
continue; continue;
} }

View file

@ -8,49 +8,110 @@ 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<I extends Instance> implements InstanceHandle { public class InstanceHandleImpl<I extends Instance> implements InstanceHandle {
@UnknownNullability public State<I> state;
public AbstractInstancer<I> instancer;
@UnknownNullability @UnknownNullability
public I instance; public I instance;
@UnknownNullability
public Supplier<AbstractInstancer<I>> recreate;
public boolean visible = true;
public int index; public int index;
public InstanceHandleImpl(State<I> state) {
this.state = state;
}
@Override @Override
public void setChanged() { public void setChanged() {
instancer.notifyDirty(index); state = state.setChanged(index);
} }
@Override @Override
public void setDeleted() { public void setDeleted() {
instancer.notifyRemoval(index); state = state.setDeleted(index);
// invalidate ourselves // invalidate ourselves
clear(); clear();
} }
@Override @Override
public void setVisible(boolean visible) { public void setVisible(boolean visible) {
if (this.visible == visible) { state = state.setVisible(index, instance, visible);
return;
}
this.visible = visible;
if (visible) {
recreate.get().stealInstance(instance);
} else {
instancer.notifyRemoval(index);
clear();
}
} }
@Override @Override
public boolean isVisible() { public boolean isVisible() {
return visible; return state.status() == Status.VISIBLE;
} }
public void clear() { public void clear() {
index = -1; index = -1;
} }
public enum Status {
HIDDEN,
DELETED,
VISIBLE
}
public interface State<I extends Instance> {
State<I> setChanged(int index);
State<I> setDeleted(int index);
State<I> setVisible(int index, I instance, boolean visible);
Status status();
}
public record Hidden<I extends Instance>(Supplier<AbstractInstancer<I>> supplier) implements State<I> {
@Override
public State<I> setChanged(int index) {
return this;
}
@Override
public State<I> setDeleted(int index) {
return this;
}
@Override
public State<I> setVisible(int index, I instance, boolean visible) {
if (!visible) {
return this;
}
var instancer = supplier.get();
instancer.stealInstance(instance);
return instancer;
}
@Override
public Status status() {
return Status.HIDDEN;
}
}
public record Deleted<I extends Instance>() implements State<I> {
private static final Deleted<?> INSTANCE = new Deleted<>();
@SuppressWarnings("unchecked")
public static <I extends Instance> Deleted<I> instance() {
return (Deleted<I>) INSTANCE;
}
@Override
public State<I> setChanged(int index) {
return this;
}
@Override
public State<I> setDeleted(int index) {
return this;
}
@Override
public State<I> setVisible(int index, I instance, boolean visible) {
return this;
}
@Override
public Status status() {
return Status.DELETED;
}
}
} }