Skip to content

Commit

Permalink
Refine craft data tag system (#687)
Browse files Browse the repository at this point in the history
* Add annotations to data tag container

* Composition over inheritance, concurrency

* Concurrent registry

* Avoid defaults on non-try/default methods

* Field ordering, access modifiers

* try method is not try

* cleanup

* Add javadoc

* Remove defaults

* Update return type

* annotations

* annotations

* Discreet data tag registry

* Fix build

* Add iterator over entries
  • Loading branch information
oh-noey authored Aug 11, 2024
1 parent 24fb754 commit 290917e
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 68 deletions.
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();
}
}

0 comments on commit 290917e

Please sign in to comment.