Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine craft data tag system #687

Merged
merged 15 commits into from
Aug 11, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import net.countercraft.movecraft.config.Settings;
import net.countercraft.movecraft.craft.ChunkManager;
import net.countercraft.movecraft.craft.CraftManager;
import net.countercraft.movecraft.craft.datatag.CraftDataTagRegistry;
import net.countercraft.movecraft.features.contacts.ContactsCommand;
import net.countercraft.movecraft.features.contacts.ContactsManager;
import net.countercraft.movecraft.features.contacts.ContactsSign;
Expand All @@ -38,6 +39,7 @@
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.FileOutputStream;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import net.countercraft.movecraft.async.translation.TranslationTask;
import net.countercraft.movecraft.config.Settings;
import net.countercraft.movecraft.craft.datatag.CraftDataTagContainer;
import net.countercraft.movecraft.craft.datatag.CraftDataTagKey;
import net.countercraft.movecraft.craft.datatag.CraftDataTagRegistry;
import net.countercraft.movecraft.craft.type.CraftType;
import net.countercraft.movecraft.localisation.I18nSupport;
import net.countercraft.movecraft.processing.CachedMovecraftWorld;
Expand All @@ -22,13 +24,23 @@
import net.countercraft.movecraft.util.hitboxes.SetHitBox;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import org.bukkit.*;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import org.jetbrains.annotations.NotNull;

import java.util.*;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;

Expand Down Expand Up @@ -69,7 +81,8 @@ public abstract class BaseCraft implements Craft {
private MovecraftLocation lastTranslation = new MovecraftLocation(0, 0, 0);
private Map<NamespacedKey, Set<TrackedLocation>> trackedLocations = new HashMap<>();

private final CraftDataTagContainer dataTagContainer = new CraftDataTagContainer();
@NotNull
private final CraftDataTagContainer dataTagContainer;

private final UUID uuid = UUID.randomUUID();

Expand All @@ -85,6 +98,7 @@ public BaseCraft(@NotNull CraftType type, @NotNull World world) {
disabled = false;
origPilotTime = System.currentTimeMillis();
audience = Audience.empty();
dataTagContainer = new CraftDataTagContainer();
}


Expand Down Expand Up @@ -536,8 +550,13 @@ public void setAudience(@NotNull Audience audience) {
}

@Override
public CraftDataTagContainer getDataTagContainer() {
return dataTagContainer;
public <T> void setDataTag(final @NotNull CraftDataTagKey<T> tagKey, final T data) {
dataTagContainer.set(tagKey, data);
}

@Override
public <T> T getDataTag(final @NotNull CraftDataTagKey<T> tagKey) {
return dataTagContainer.get(this, tagKey);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import net.countercraft.movecraft.craft.*;
import net.countercraft.movecraft.craft.datatag.CraftDataTagContainer;
import net.countercraft.movecraft.craft.datatag.CraftDataTagKey;
import net.countercraft.movecraft.craft.datatag.CraftDataTagRegistry;
import net.countercraft.movecraft.craft.type.CraftType;
import net.countercraft.movecraft.events.*;
import net.countercraft.movecraft.exception.EmptyHitBoxException;
Expand All @@ -27,7 +28,7 @@
import java.util.*;

public class ContactsManager extends BukkitRunnable implements Listener {
private static final CraftDataTagKey<Map<Craft, Long>> RECENT_CONTACTS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "recent-contacts"), craft -> new WeakHashMap<>());
private static final CraftDataTagKey<Map<Craft, Long>> RECENT_CONTACTS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "recent-contacts"), craft -> new WeakHashMap<>());

@Override
public void run() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.countercraft.movecraft.craft.SinkingCraft;
import net.countercraft.movecraft.craft.datatag.CraftDataTagContainer;
import net.countercraft.movecraft.craft.datatag.CraftDataTagKey;
import net.countercraft.movecraft.craft.datatag.CraftDataTagRegistry;
import net.countercraft.movecraft.craft.type.CraftType;
import net.countercraft.movecraft.craft.type.RequiredBlockEntry;
import net.countercraft.movecraft.features.status.events.CraftStatusUpdateEvent;
Expand All @@ -33,7 +34,7 @@
import java.util.function.Supplier;

public class StatusManager extends BukkitRunnable implements Listener {
private static final CraftDataTagKey<Long> LAST_STATUS_CHECK = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "last-status-check"), craft -> System.currentTimeMillis());
private static final CraftDataTagKey<Long> LAST_STATUS_CHECK = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "last-status-check"), craft -> System.currentTimeMillis());

@Override
public void run() {
Expand Down
31 changes: 8 additions & 23 deletions api/src/main/java/net/countercraft/movecraft/craft/Craft.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import net.countercraft.movecraft.TrackedLocation;
import net.countercraft.movecraft.craft.datatag.CraftDataTagContainer;
import net.countercraft.movecraft.craft.datatag.CraftDataTagKey;
import net.countercraft.movecraft.craft.datatag.CraftDataTagRegistry;
import net.countercraft.movecraft.craft.type.CraftType;
import net.countercraft.movecraft.processing.MovecraftWorld;
import net.countercraft.movecraft.util.Counter;
Expand All @@ -43,11 +44,11 @@
import java.util.*;

public interface Craft {
CraftDataTagKey<List<Craft>> CONTACTS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "contacts"), craft -> new ArrayList<>(0));
CraftDataTagKey<Double> FUEL = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "fuel"), craft -> 0D);
CraftDataTagKey<Counter<Material>> MATERIALS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "materials"), craft -> new Counter<>());
CraftDataTagKey<Integer> NON_NEGLIGIBLE_BLOCKS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "non-negligible-blocks"), Craft::getOrigBlockCount);
CraftDataTagKey<Integer> NON_NEGLIGIBLE_SOLID_BLOCKS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "non-negligible-solid-blocks"), Craft::getOrigBlockCount);
CraftDataTagKey<List<Craft>> CONTACTS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "contacts"), craft -> new ArrayList<>(0));
CraftDataTagKey<Double> FUEL = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "fuel"), craft -> 0D);
CraftDataTagKey<Counter<Material>> MATERIALS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "materials"), craft -> new Counter<>());
CraftDataTagKey<Integer> NON_NEGLIGIBLE_BLOCKS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "non-negligible-blocks"), Craft::getOrigBlockCount);
CraftDataTagKey<Integer> NON_NEGLIGIBLE_SOLID_BLOCKS = CraftDataTagRegistry.INSTANCE.registerTagKey(new NamespacedKey("movecraft", "non-negligible-solid-blocks"), Craft::getOrigBlockCount);

// Java disallows private or protected fields in interfaces, this is a workaround
class Hidden {
Expand Down Expand Up @@ -272,25 +273,9 @@ default void setLastDZ(int dZ){}

void setAudience(Audience audience);

public default CraftDataTagContainer getDataTagContainer() {
return null;
}
<T> void setDataTag(@NotNull final CraftDataTagKey<T> tagKey, final T data);

public default <T> boolean setDataTag(CraftDataTagKey<T> tagKey, T data) {
CraftDataTagContainer container = this.getDataTagContainer();
if (container == null) {
return false;
}
container.set(tagKey, data);
return true;
}
public default <T> T getDataTag(CraftDataTagKey<T> tagKey) {
CraftDataTagContainer container = this.getDataTagContainer();
if (container == null) {
return null;
}
return container.get(this, tagKey);
}
<T> T getDataTag(@NotNull final CraftDataTagKey<T> tagKey);

public default void markTileStateWithUUID(TileState tile) {
// Add the marker
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,56 @@
package net.countercraft.movecraft.craft.datatag;

import net.countercraft.movecraft.craft.Craft;
import org.bukkit.NamespacedKey;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class CraftDataTagContainer extends HashMap<CraftDataTagKey<?>, Object> {
public class CraftDataTagContainer {
private final @NotNull ConcurrentMap<@NotNull CraftDataTagKey<?>, @Nullable Object> backing;

public static final Map<NamespacedKey, CraftDataTagKey<?>> REGISTERED_TAGS = new HashMap<>();

public static <T> CraftDataTagKey<T> tryRegisterTagKey(final NamespacedKey key, final Function<Craft, T> supplier) throws IllegalArgumentException {
if (REGISTERED_TAGS.containsKey(key)) {
throw new IllegalArgumentException("Duplicate keys are not allowed!");
} else {
CraftDataTagKey<T> result = new CraftDataTagKey<T>(key, supplier);
REGISTERED_TAGS.put(key, result);
return result;
}
public CraftDataTagContainer(){
backing = new ConcurrentHashMap<>();
}

public <T> T get(final Craft craft, CraftDataTagKey<T> tagKey) {
if (!REGISTERED_TAGS.containsKey(tagKey.key)) {
// TODO: Log error
return null;
/**
* Gets the data value associated with the provided tagKey from a craft.
*
* @param craft the craft to perform a lookup against
* @param tagKey the tagKey to use for looking up the relevant data
* @return the tag value associate with the provided tagKey on the specified craft
* @param <T> the value type of the registered data key
* @throws IllegalArgumentException when the provided tagKey is not registered
* @throws IllegalStateException when the provided tagKey does not match the underlying tag value
*/
public <T> T get(final @NotNull Craft craft, @NotNull CraftDataTagKey<T> tagKey) {
if (!CraftDataTagRegistry.INSTANCE.isRegistered(tagKey.key)) {
throw new IllegalArgumentException(String.format("The provided key %s was not registered.", tagKey));
}
T result = null;
if (!this.containsKey(tagKey)) {
result = tagKey.createNew(craft);
this.put(tagKey, result);
} else {
Object stored = this.getOrDefault(tagKey, tagKey.createNew(craft));
try {
T temp = (T) stored;
result = temp;
} catch (ClassCastException cce) {
// TODO: Log error
result = tagKey.createNew(craft);
this.put(tagKey, result);
}

Object stored = backing.computeIfAbsent(tagKey, ignored -> tagKey.createNew(craft));
try {
//noinspection unchecked
return (T) stored;
} catch (ClassCastException cce) {
throw new IllegalStateException(String.format("The provided key %s has an invalid value type.", tagKey), cce);
}
return result;
}

public <T> void set(CraftDataTagKey<T> tagKey, @NotNull T value) {
this.put(tagKey, value);
}
/**
* Set the value associated with the provided tagKey on the associated craft.
*
* @param tagKey the tagKey to use for storing the relevant data
* @param value the value to set for future lookups
* @param <T> the type of the value
*/
public <T> void set(@NotNull CraftDataTagKey<T> tagKey, @NotNull T value) {
if (!CraftDataTagRegistry.INSTANCE.isRegistered(tagKey.key)) {
throw new IllegalArgumentException(String.format("The provided key %s was not registered.", tagKey));
}

backing.put(tagKey, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package net.countercraft.movecraft.craft.datatag;

import net.countercraft.movecraft.craft.Craft;
import org.bukkit.NamespacedKey;
import org.jetbrains.annotations.NotNull;

import java.util.Enumeration;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

public class CraftDataTagRegistry {
public static final @NotNull CraftDataTagRegistry INSTANCE = new CraftDataTagRegistry();

private final @NotNull ConcurrentMap<@NotNull NamespacedKey, @NotNull CraftDataTagKey<?>> _registeredTags;

public CraftDataTagRegistry(){
_registeredTags = new ConcurrentHashMap<>();
}

/**
* Registers a data tag to be attached to craft instances. The data tag will initialize to the value supplied by the
* initializer. Once a tag is registered, it can be accessed from crafts using the returned key through various
* methods.
*
* @param key the namespace key to use for registration, which must be unique
* @param initializer a default initializer for the value type
* @return A CraftDataTagKey, which can be used to control an associated value on a Craft instance
* @param <T> the value type
* @throws IllegalArgumentException when the provided key is already registered
*/
public <T> @NotNull CraftDataTagKey<T> registerTagKey(final @NotNull NamespacedKey key, final @NotNull Function<Craft, T> initializer) throws IllegalArgumentException {
CraftDataTagKey<T> result = new CraftDataTagKey<>(key, initializer);
var previous = _registeredTags.putIfAbsent(key, result);
if(previous != null){
throw new IllegalArgumentException(String.format("Key %s is already registered.", key));
}

return result;
}

public boolean isRegistered(final @NotNull NamespacedKey key){
return _registeredTags.containsKey(key);
}

/**
* Get an iterable over all keys currently registered.
* @return An immutable iterable over the registry keys
*/
public @NotNull Iterable<@NotNull NamespacedKey> getAllKeys(){
return _registeredTags.keySet().stream().toList();
}
}