feat(04-01): implement WandSelectionStore pure-data per-UUID selection store
- ConcurrentHashMap<UUID, Selection>, atomic compute() for set/get - Selection immutable holder with isComplete() - No Hytale imports; testable standalone
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package com.mythlane.gravityflip.wand;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Thread-safe per-player wand selection store. Tracks the two corner positions
|
||||
* ({@code pos1} = Primary click, {@code pos2} = Secondary click) of each
|
||||
* builder's current selection, keyed by player {@link UUID}.
|
||||
*
|
||||
* <p><b>Pure-data :</b> no Hytale runtime dependency — same philosophy as
|
||||
* {@code FallDamageGuard}. Testable with JUnit alone, reusable by future
|
||||
* {@code /gravityflip define} command (Phase 04-02+) without runtime coupling.
|
||||
*
|
||||
* <p><b>Thread-safety :</b> backed by a {@link ConcurrentHashMap} whose
|
||||
* {@code compute(...)} mutations are atomic. Safe for concurrent
|
||||
* {@link #setPos1}/{@link #setPos2} calls on the same UUID (STRIDE T-04-01-04).
|
||||
*
|
||||
* <p><b>Lifecycle :</b> in-memory only — selections are discarded on plugin
|
||||
* shutdown. Conscious design : a builder will not quit mid-{@code define}.
|
||||
*/
|
||||
public final class WandSelectionStore {
|
||||
|
||||
/** Immutable holder for a (possibly partial) selection. */
|
||||
public static final class Selection {
|
||||
/** {@code {x,y,z}} of the Primary click, or {@code null} if unset. */
|
||||
public final int[] pos1;
|
||||
/** {@code {x,y,z}} of the Secondary click, or {@code null} if unset. */
|
||||
public final int[] pos2;
|
||||
|
||||
Selection(int[] pos1, int[] pos2) {
|
||||
this.pos1 = pos1;
|
||||
this.pos2 = pos2;
|
||||
}
|
||||
|
||||
/** True iff both corners have been picked. */
|
||||
public boolean isComplete() {
|
||||
return pos1 != null && pos2 != null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Selection EMPTY = new Selection(null, null);
|
||||
|
||||
private final ConcurrentHashMap<UUID, Selection> byUuid = new ConcurrentHashMap<>();
|
||||
|
||||
/** Record the Primary-click corner for {@code uuid}. Keeps any existing pos2. */
|
||||
public void setPos1(UUID uuid, int x, int y, int z) {
|
||||
if (uuid == null) return;
|
||||
byUuid.compute(uuid, (k, prev) -> new Selection(
|
||||
new int[]{x, y, z},
|
||||
prev != null ? prev.pos2 : null));
|
||||
}
|
||||
|
||||
/** Record the Secondary-click corner for {@code uuid}. Keeps any existing pos1. */
|
||||
public void setPos2(UUID uuid, int x, int y, int z) {
|
||||
if (uuid == null) return;
|
||||
byUuid.compute(uuid, (k, prev) -> new Selection(
|
||||
prev != null ? prev.pos1 : null,
|
||||
new int[]{x, y, z}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current selection for {@code uuid}. Never returns {@code null} :
|
||||
* an unknown UUID yields a {@link Selection} with both corners {@code null}.
|
||||
*/
|
||||
public Selection get(UUID uuid) {
|
||||
if (uuid == null) return EMPTY;
|
||||
Selection s = byUuid.get(uuid);
|
||||
return s != null ? s : EMPTY;
|
||||
}
|
||||
|
||||
/** Forget the selection for {@code uuid} (e.g. after {@code /gravityflip define}). */
|
||||
public void clear(UUID uuid) {
|
||||
if (uuid == null) return;
|
||||
byUuid.remove(uuid);
|
||||
}
|
||||
|
||||
/** Diagnostic : number of players with an in-flight selection. */
|
||||
public int size() {
|
||||
return byUuid.size();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user