Files
hytale-gravity-flip/src/main/java/com/mythlane/gravityflip/region/RegionRegistry.java
T
kayjaydee c20bf42c36 refactor(command): simplify region lookup in GravityFlip commands
- Replaced manual region search loops with a new `find` method in RegionRegistry for improved readability and efficiency.
- Updated `GravityFlipToggleSubCommand` and `GravityFlipTpSubCommand` to utilize the new method for finding regions by name.
- Enhanced the `add` method in RegionRegistry to use the `find` method for checking existing region names.
2026-04-24 18:07:34 +02:00

214 lines
8.8 KiB
Java

package com.mythlane.gravityflip.region;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.Config;
import com.mythlane.gravityflip.config.GravityFlipConfig;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReference;
/**
* In-memory index of {@link GravityFlipRegion}s with atomic publication for lock-free reads
* from the tick loop. Mutations go through CRUD methods which swap an immutable snapshot.
*/
public final class RegionRegistry {
// Lazy init: TransformComponent.getComponentType() triggers Hytale PluginBase static init,
// which fails under JUL unless the log manager system property is set — avoided in tests.
private static volatile ComponentType<EntityStore, TransformComponent> transformType;
private static ComponentType<EntityStore, TransformComponent> transform() {
ComponentType<EntityStore, TransformComponent> t = transformType;
if (t == null) {
synchronized (RegionRegistry.class) {
t = transformType;
if (t == null) {
t = TransformComponent.getComponentType();
transformType = t;
}
}
}
return t;
}
private final GravityFlipConfig config;
private final Config<GravityFlipConfig> holder; // nullable in tests
private final AtomicReference<List<GravityFlipRegion>> regionsSnapshot;
private final Object mutationLock = new Object();
// Keyed by Object (not World) because Mockito cannot mock World under JDK 25 in tests.
private final ConcurrentHashMap<Object, AtomicReference<RegionSnapshot>> snapshots = new ConcurrentHashMap<>();
public RegionRegistry(GravityFlipConfig cfg) {
this(cfg, null);
}
public RegionRegistry(GravityFlipConfig cfg, Config<GravityFlipConfig> holder) {
this.config = cfg;
this.holder = holder;
this.regionsSnapshot = new AtomicReference<>(List.copyOf(cfg.getRegions()));
}
/** Returns the current immutable region-list snapshot. */
public Collection<GravityFlipRegion> all() {
return regionsSnapshot.get();
}
/** Read-only view of currently-enabled regions. */
List<GravityFlipRegion> enabled() {
List<GravityFlipRegion> out = new ArrayList<>();
for (GravityFlipRegion r : regionsSnapshot.get()) {
if (r.isEnabled()) out.add(r);
}
return out;
}
/** Returns the region with the given name from the current snapshot, or null. */
public GravityFlipRegion find(String name) {
if (name == null) return null;
for (GravityFlipRegion x : regionsSnapshot.get()) {
if (x.getName().equals(name)) return x;
}
return null;
}
/** Adds a region; throws IllegalArgumentException if the name already exists. */
public void add(GravityFlipRegion r) {
synchronized (mutationLock) {
if (find(r.getName()) != null) {
throw new IllegalArgumentException("region name already exists: " + r.getName());
}
config.getRegions().add(r);
regionsSnapshot.set(List.copyOf(config.getRegions()));
}
}
/** Removes the region with the given name; returns true iff it existed. */
public boolean remove(String name) {
synchronized (mutationLock) {
boolean removed = config.getRegions().removeIf(x -> x.getName().equals(name));
if (removed) regionsSnapshot.set(List.copyOf(config.getRegions()));
return removed;
}
}
/** Toggles the enabled flag on the named region; returns true iff it existed. */
public boolean setEnabled(String name, boolean enabled) {
synchronized (mutationLock) {
for (GravityFlipRegion x : config.getRegions()) {
if (x.getName().equals(name)) {
x.setEnabled(enabled);
regionsSnapshot.set(List.copyOf(config.getRegions()));
return true;
}
}
return false;
}
}
/** Re-snapshots the region list after the underlying config was mutated externally. */
public void refreshFromConfig(GravityFlipConfig cfg) {
synchronized (mutationLock) {
regionsSnapshot.set(List.copyOf(cfg.getRegions()));
}
}
/** Persists via the bound {@code Config} holder (completed future if none is bound). */
public CompletableFuture<Void> save() {
return holder == null ? CompletableFuture.completedFuture(null) : holder.save();
}
/** Iterates the ECS for {@code world} and publishes a fresh per-region occupancy snapshot. */
public void refreshFor(World world) {
List<GravityFlipRegion> enabled = enabled();
Map<GravityFlipRegion, Collection<Ref<EntityStore>>> byRegion = new ConcurrentHashMap<>();
for (GravityFlipRegion r : enabled) {
byRegion.put(r, new ConcurrentLinkedQueue<>());
}
if (enabled.isEmpty()) {
publishSnapshot(world, snapshotOf(world, byRegion));
return;
}
// Store.forEachEntityParallel requires the WorldThread (assertThread), so dispatch via
// world.execute(Runnable). Publication becomes asynchronous (<= 1 tick of lag) — tolerable
// because RegionTickLoop runs @100ms, so snapshot freshness stays <= 100ms in the worst case.
world.execute(() -> {
try {
Store<EntityStore> store = world.getEntityStore().getStore();
ComponentType<EntityStore, TransformComponent> TRANSFORM = transform();
// ComponentType IS-A Query, so TRANSFORM is passed directly.
store.forEachEntityParallel(TRANSFORM, (index, chunk, cmdBuf) -> {
TransformComponent t = chunk.getComponent(index, TRANSFORM);
com.hypixel.hytale.math.vector.Vector3d pos = t.getPosition();
// Copy to locals — getPosition() returns a backing field; never mutated here.
double x = pos.x, y = pos.y, z = pos.z;
Ref<EntityStore> ref = null;
for (GravityFlipRegion r : enabled) {
Box box = r.asBox();
if (box.containsPosition(x, y, z)) {
if (ref == null) ref = chunk.getReferenceTo(index);
byRegion.get(r).add(ref);
}
}
});
} catch (Throwable th) {
// Swallow — keeps the scheduler alive across transient ECS-state errors
// (e.g., world being torn down). Publish whatever we collected so far.
}
publishSnapshot(world, snapshotOf(world, byRegion));
});
}
/** Off-thread consumer entry point; returns null if no snapshot has been published yet. */
public RegionSnapshot currentSnapshot(World world) {
if (world == null) return null;
AtomicReference<RegionSnapshot> ref = snapshots.get(world);
return ref == null ? null : ref.get();
}
void publishSnapshot(World world, RegionSnapshot snap) {
publishSnapshotByKey(world, snap);
}
void publishSnapshotByKey(Object key, RegionSnapshot snap) {
snapshots.computeIfAbsent(key, k -> new AtomicReference<>()).set(snap);
}
RegionSnapshot currentSnapshotByKey(Object key) {
AtomicReference<RegionSnapshot> ref = snapshots.get(key);
return ref == null ? null : ref.get();
}
private RegionSnapshot snapshotOf(World w,
Map<GravityFlipRegion, Collection<Ref<EntityStore>>> byRegion) {
long tick = 0L;
try { tick = w.getTick(); } catch (Throwable ignored) {}
final long tickId = Math.max(tick, 1L);
final Map<GravityFlipRegion, Collection<Ref<EntityStore>>> frozen =
Collections.unmodifiableMap(byRegion);
return new RegionSnapshot() {
@Override public Map<GravityFlipRegion, Collection<Ref<EntityStore>>> byRegion() { return frozen; }
@Override public long tickId() { return tickId; }
@Override public World world() { return w; }
};
}
}