From 16f4df22806cdbc71a21f34d9f58943d6eb0b627 Mon Sep 17 00:00:00 2001 From: James Hall Date: Mon, 3 Mar 2025 22:27:13 +0000 Subject: [PATCH 1/4] chore: bring over/create synched manager --- .../java/dev/amble/lib/api/KitEvents.java | 15 + .../dev/amble/lib/api/sync/Disposable.java | 13 + .../java/dev/amble/lib/api/sync/Exclude.java | 16 + .../dev/amble/lib/api/sync/Initializable.java | 50 ++++ .../dev/amble/lib/api/sync/RootComponent.java | 73 +++++ .../api/sync/handler/ComponentManager.java | 189 ++++++++++++ .../api/sync/handler/ComponentRegistry.java | 97 ++++++ .../lib/api/sync/handler/SyncComponent.java | 147 ++++++++++ .../api/sync/handler/TickingComponent.java | 14 + .../amble/lib/api/sync/manager/RootMap.java | 13 + .../lib/api/sync/manager/SyncManager.java | 178 ++++++++++++ .../manager/client/ClientComponentData.java | 24 ++ .../manager/client/ClientRootComponent.java | 22 ++ .../manager/client/ClientSyncManager.java | 162 +++++++++++ .../manager/server/ComponentFileManager.java | 117 ++++++++ .../manager/server/ServerComponentData.java | 55 ++++ .../manager/server/ServerRootComponent.java | 6 + .../manager/server/ServerSyncManager.java | 275 ++++++++++++++++++ .../lib/data/CachedDirectedGlobalPos.java | 112 +++++++ .../dev/amble/lib/data/enummap/EnumMap.java | 136 +++++++++ .../dev/amble/lib/data/enummap/EnumSet.java | 30 ++ .../dev/amble/lib/data/enummap/Ordered.java | 5 + .../lib/data/gson/BlockPosSerializer.java | 33 +++ .../lib/data/gson/GlobalPosSerializer.java | 39 +++ .../lib/data/gson/IdentifierSerializer.java | 24 ++ .../lib/data/gson/ItemStackSerializer.java | 21 ++ .../amble/lib/data/gson/NbtSerializer.java | 29 ++ .../lib/data/gson/RegistryKeySerializer.java | 30 ++ .../amble/lib/events/ServerCrashEvent.java | 21 ++ .../dev/amble/lib/events/WorldSaveEvent.java | 20 ++ .../amble/lib/mixin/MinecraftServerMixin.java | 21 ++ .../dev/amble/lib/mixin/ServerWorldMixin.java | 19 ++ .../ThreadedAnvilChunkStorageMixin.java | 23 ++ src/main/resources/amblekit.mixins.json | 5 +- 34 files changed, 2033 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/amble/lib/api/sync/Disposable.java create mode 100644 src/main/java/dev/amble/lib/api/sync/Exclude.java create mode 100644 src/main/java/dev/amble/lib/api/sync/Initializable.java create mode 100644 src/main/java/dev/amble/lib/api/sync/RootComponent.java create mode 100644 src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java create mode 100644 src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java create mode 100644 src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java create mode 100644 src/main/java/dev/amble/lib/api/sync/handler/TickingComponent.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/RootMap.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/client/ClientComponentData.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/client/ClientRootComponent.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/client/ClientSyncManager.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/server/ComponentFileManager.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/server/ServerComponentData.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/server/ServerRootComponent.java create mode 100644 src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java create mode 100644 src/main/java/dev/amble/lib/data/CachedDirectedGlobalPos.java create mode 100644 src/main/java/dev/amble/lib/data/enummap/EnumMap.java create mode 100644 src/main/java/dev/amble/lib/data/enummap/EnumSet.java create mode 100644 src/main/java/dev/amble/lib/data/enummap/Ordered.java create mode 100644 src/main/java/dev/amble/lib/data/gson/BlockPosSerializer.java create mode 100644 src/main/java/dev/amble/lib/data/gson/GlobalPosSerializer.java create mode 100644 src/main/java/dev/amble/lib/data/gson/IdentifierSerializer.java create mode 100644 src/main/java/dev/amble/lib/data/gson/ItemStackSerializer.java create mode 100644 src/main/java/dev/amble/lib/data/gson/NbtSerializer.java create mode 100644 src/main/java/dev/amble/lib/data/gson/RegistryKeySerializer.java create mode 100644 src/main/java/dev/amble/lib/events/ServerCrashEvent.java create mode 100644 src/main/java/dev/amble/lib/events/WorldSaveEvent.java create mode 100644 src/main/java/dev/amble/lib/mixin/MinecraftServerMixin.java create mode 100644 src/main/java/dev/amble/lib/mixin/ServerWorldMixin.java create mode 100644 src/main/java/dev/amble/lib/mixin/networking/ThreadedAnvilChunkStorageMixin.java diff --git a/src/main/java/dev/amble/lib/api/KitEvents.java b/src/main/java/dev/amble/lib/api/KitEvents.java index 93ebe9f..dc6051f 100644 --- a/src/main/java/dev/amble/lib/api/KitEvents.java +++ b/src/main/java/dev/amble/lib/api/KitEvents.java @@ -3,6 +3,9 @@ import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.world.chunk.WorldChunk; + public class KitEvents { public static final Event PRE_DATAPACK_LOAD = EventFactory.createArrayBacked(PreDatapackLoad.class, callbacks -> () -> { for (PreDatapackLoad callback : callbacks) { @@ -10,6 +13,13 @@ public class KitEvents { } }); + public static final Event SYNC_ROOT = EventFactory.createArrayBacked(SyncRoot.class, + callbacks -> (player, chunk) -> { + for (SyncRoot callback : callbacks) { + callback.sync(player, chunk); + } + }); + /** * Called when just before datapacks are loaded */ @@ -17,4 +27,9 @@ public class KitEvents { public interface PreDatapackLoad { void load(); } + + @FunctionalInterface + public interface SyncRoot { + void sync(ServerPlayerEntity player, WorldChunk chunk); + } } diff --git a/src/main/java/dev/amble/lib/api/sync/Disposable.java b/src/main/java/dev/amble/lib/api/sync/Disposable.java new file mode 100644 index 0000000..d84d5ba --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/Disposable.java @@ -0,0 +1,13 @@ +package dev.amble.lib.api.sync; + +public interface Disposable { + + default void age() { + } + + void dispose(); + + default boolean isAged() { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/api/sync/Exclude.java b/src/main/java/dev/amble/lib/api/sync/Exclude.java new file mode 100644 index 0000000..e9c60b9 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/Exclude.java @@ -0,0 +1,16 @@ +package dev.amble.lib.api.sync; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Exclude { + Strategy strategy() default Strategy.ALL; + + enum Strategy { + ALL, NETWORK, FILE + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/api/sync/Initializable.java b/src/main/java/dev/amble/lib/api/sync/Initializable.java new file mode 100644 index 0000000..79fbe8a --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/Initializable.java @@ -0,0 +1,50 @@ +package dev.amble.lib.api.sync; + +public abstract class Initializable { + + public static void init(Initializable component, T context) { + component.init(context); + } + + protected void init(T context) { + this.onEarlyInit(context); + + if (context.deserialized()) { + this.onLoaded(); + } else { + this.onCreate(); + } + + this.onInit(context); + } + + /** + * Called first in the initialization sequence. + */ + protected void onEarlyInit(T ctx) { + } + + /** + * Called after a {@link #onLoaded()} or {@link #onCreate()} is called. + */ + protected void onInit(T ctx) { + } + + /** + * Called when the component is created. Server-side only. + */ + public void onCreate() { + } + + /** + * Called when the component is loaded from a file or received on the client. + */ + public void onLoaded() { + } + + public interface Context { + boolean created(); + + boolean deserialized(); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/api/sync/RootComponent.java b/src/main/java/dev/amble/lib/api/sync/RootComponent.java new file mode 100644 index 0000000..1ad2a39 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/RootComponent.java @@ -0,0 +1,73 @@ +package dev.amble.lib.api.sync; + +import java.util.UUID; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.server.MinecraftServer; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.api.sync.handler.ComponentManager; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.api.sync.handler.TickingComponent; +import dev.amble.lib.api.sync.manager.SyncManager; + +public abstract class RootComponent extends Initializable implements Disposable, TickingComponent { + private UUID uuid; + protected ComponentManager manager; + + protected RootComponent(UUID uuid) { + this.uuid = uuid; + this.manager = new ComponentManager(getSyncManager().getManagerId(), getSyncManager().getRegistry()); + } + + @Override + protected void onInit(SyncComponent.InitContext ctx) { + super.onInit(ctx); + + SyncComponent.init(manager, this, ctx); + + SyncComponent.postInit(manager, ctx); + } + + public UUID getUuid() { + return uuid; + } + + @Override + public String toString() { + return uuid.toString(); + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } + + public T handler(SyncComponent.IdLike type) { + if (this.manager == null) { + AmbleKit.LOGGER.error("Asked for a handler too early on {}", this); + return null; + } + + return this.manager.get(type); + } + + public ComponentManager getHandlers() { + return manager; + } + public abstract SyncManager getSyncManager(); + + @Override + public void tick(MinecraftServer server) { + this.getHandlers().tick(server); + } + + @Environment(EnvType.CLIENT) + @Override + public void tick(MinecraftClient client) { + this.getHandlers().tick(client) ; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java b/src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java new file mode 100644 index 0000000..f633d42 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java @@ -0,0 +1,189 @@ +package dev.amble.lib.api.sync.handler; + +import java.lang.reflect.Array; +import java.util.Map; +import java.util.function.Consumer; + +import com.google.gson.*; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import org.jetbrains.annotations.ApiStatus; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.server.MinecraftServer; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.data.enummap.EnumMap; + +public class ComponentManager extends SyncComponent implements TickingComponent { + @Exclude + private final EnumMap handlers; + protected final ComponentRegistry registry; + + public ComponentManager(SyncComponent.IdLike id, ComponentRegistry registry) { + super(id); + + this.registry = registry; + handlers = new EnumMap<>(registry::lookup, + size -> (SyncComponent[]) Array.newInstance(SyncComponent.class, size)); + } + + @Override + public void onCreate() { + this.registry.fill(this::createHandler); + } + + @Override + protected void onInit(InitContext ctx) { + this.forEach(component -> SyncComponent.init(component, this.parent, ctx)); + } + + + @Override + public void postInit(InitContext ctx) { + this.forEach(component -> component.postInit(ctx)); + } + + private void forEach(Consumer consumer) { + for (SyncComponent component : this.handlers.getValues()) { + if (component == null) + continue; + + consumer.accept(component); + } + } + + private void createHandler(SyncComponent component) { + this.handlers.put(component.getId(), component); + } + + /** + * Called on the END of a servers tick + * + * @param server + * the current server + */ + public void tick(MinecraftServer server) { + this.forEach(component -> { + if (!(component instanceof TickingComponent tickable)) + return; + + try { + tickable.tick(server); + } catch (Exception e) { + AmbleKit.LOGGER.error("Ticking failed for {} | {}", component.getId().name(), component.parent().getUuid().toString(), e); + } + }); + } + + @Environment(EnvType.CLIENT) + @Override + public void tick(MinecraftClient client) { + this.forEach(component -> { + if (!(component instanceof TickingComponent tickable)) + return; + + try { + tickable.tick(client); + } catch (Exception e) { + AmbleKit.LOGGER.error("Ticking failed for {} | {}", component.getId().name(), component.parent().getUuid().toString(), e); + } + }); + } + + /** + * @deprecated Use {@link RootComponent#handler(IdLike)} + */ + @Deprecated + @ApiStatus.Internal + @SuppressWarnings("unchecked") + public C get(IdLike id) { + return (C) this.handlers.get(id); + } + + @Override + public void dispose() { + super.dispose(); + + this.forEach(SyncComponent::dispose); + this.handlers.clear(); + } + + @ApiStatus.Internal + public void set(SyncComponent t) { + this.handlers.put(t.getId(), t); + } + + public Object serializer() { + return new Serializer(this.registry, this.getId()); + } + public static Object serializer(ComponentRegistry r, IdLike id) { + return new Serializer(r, id); + } + + static class Serializer implements JsonSerializer, JsonDeserializer { + private final IdLike id; + private final ComponentRegistry registry; + + public Serializer(ComponentRegistry registry, IdLike id) { + this.id = id; + this.registry = registry; + } + + @Override + public ComponentManager deserialize(JsonElement json, java.lang.reflect.Type type, + JsonDeserializationContext context) throws JsonParseException { + ComponentManager manager = new ComponentManager(id, registry); + Map map = json.getAsJsonObject().asMap(); + + ComponentRegistry registry = manager.registry; + + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + JsonElement element = entry.getValue(); + + IdLike id = registry.get(key); + + if (id == null) { + AmbleKit.LOGGER.error("Can't find a component id with name '{}'!", key); + continue; + } + + manager.set(context.deserialize(element, id.clazz())); + } + + for (int i = 0; i < manager.handlers.size(); i++) { + if (manager.handlers.get(i) != null) + continue; + + IdLike id = registry.get(i); + AmbleKit.LOGGER.debug("Appending new component {}", id); + + manager.set(id.create()); + } + + return manager; + } + + @Override + public JsonElement serialize(ComponentManager manager, java.lang.reflect.Type type, + JsonSerializationContext context) { + JsonObject result = new JsonObject(); + + manager.forEach(component -> { + IdLike idLike = component.getId(); + + if (idLike == null) { + AmbleKit.LOGGER.error("Id was null for {}", component.getClass()); + return; + } + + result.add(idLike.name(), context.serialize(component)); + }); + + return result; + } + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java b/src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java new file mode 100644 index 0000000..6aa06ea --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java @@ -0,0 +1,97 @@ +package dev.amble.lib.api.sync.handler; + +import java.lang.reflect.Array; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import com.google.gson.*; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.register.Registry; + +public abstract class ComponentRegistry implements Registry { + private final Map REGISTRY = new HashMap<>(); + private SyncComponent.IdLike[] LOOKUP; + + private boolean frozen = false; + + public void register(SyncComponent.IdLike id) { + if (!id.creatable()) + return; + + id.index(REGISTRY.size()); + REGISTRY.put(id.name(), id); + + if (frozen) + AmbleKit.LOGGER.error("Tried to init a component id after the registry got frozen: {}", id); + } + + public void register(SyncComponent.IdLike[] idLikes) { + for (SyncComponent.IdLike idLike : idLikes) { + register(idLike); + } + } + + @Override + public void onCommonInit() { + register(ids()); + + LOOKUP = (SyncComponent.IdLike[]) Array.newInstance(SyncComponent.IdLike.class, REGISTRY.size()); + REGISTRY.forEach((name, idLike) -> LOOKUP[idLike.index()] = idLike); + + this.frozen = true; + } + + protected abstract SyncComponent.IdLike[] ids(); + + public void fill(Consumer consumer) { + for (SyncComponent.IdLike id : LOOKUP) { + consumer.accept(id.create()); + } + } + + public SyncComponent.IdLike get(String name) { + return switch (name) { + default -> REGISTRY.get(name); + }; + } + + public String get(SyncComponent component) { + return component.getId().name(); + } + + public SyncComponent.IdLike get(int index) { + return LOOKUP[index]; + } + + public Collection getValues() { + return REGISTRY.values(); + } + public SyncComponent.IdLike[] lookup() { + return LOOKUP; + } + + public Object idSerializer() { + return new Serializer(this); + } + + private record Serializer(ComponentRegistry registry) + implements + JsonSerializer, + JsonDeserializer { + + @Override + public SyncComponent.IdLike deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return registry.get(json.getAsString()); + } + + @Override + public JsonElement serialize(SyncComponent.IdLike src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(src.name()); + } + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java b/src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java new file mode 100644 index 0000000..38cdd8a --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java @@ -0,0 +1,147 @@ +package dev.amble.lib.api.sync.handler; + +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import dev.amble.lib.api.sync.Disposable; +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.Initializable; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.data.CachedDirectedGlobalPos; +import dev.amble.lib.data.enummap.Ordered; + +/** + * Base class for all syncing components. + * + * @implNote There should be NO logic run in the constructor. If you need to + * have such logic, implement it in an appropriate init method! + */ +public abstract class SyncComponent extends Initializable implements Disposable { + @Exclude + protected RootComponent parent; + @Exclude(strategy = Exclude.Strategy.NETWORK) private final IdLike id; + + /** + * Do NOT under any circumstances run logic in this constructor. Default field + * values should be inlined. All logic should be done in an appropriate init + * method. + * + * @implNote The {@link SyncComponent#parent()} will always be null at the + * time this constructor gets called. + */ + public SyncComponent(IdLike id) { + this.id = id; + } + + public void postInit(InitContext ctx) { + } + + public static void init(SyncComponent component, RootComponent parent, InitContext context) { + component.setParent(parent); + component.init(context); + } + public static

void postInit(SyncComponent component, InitContext context) { + component.postInit(context); + } + + public RootComponent parent() { + return this.parent; + } + public void setParent(RootComponent parent) { + this.parent = parent; + } + + @Override + public void dispose() { + this.parent = null; + } + + public IdLike getId() { + return this.id; + } + + public interface IdLike extends Ordered { + Class clazz(); + + default void set(RootComponent parent, SyncComponent component) { + parent.getHandlers().set(component); + } + + default SyncComponent get(RootComponent parent) { + return parent.handler(this); + } + + SyncComponent create(); + + boolean creatable(); + + String name(); + + int index(); + + void index(int i); + } + + public static class AbstractId implements IdLike { + + private final String name; + private final Supplier creator; + private final Class clazz; + + private int index; + + public AbstractId(String name, Supplier creator, Class clazz) { + this.name = name; + this.creator = creator; + this.clazz = clazz; + } + + @Override + public Class clazz() { + return this.clazz; + } + + @Override + public SyncComponent create() { + return this.creator.get(); + } + + @Override + public boolean creatable() { + return true; + } + + @Override + public String name() { + return this.name; + } + + @Override + public int index() { + return this.index; + } + + @Override + public void index(int i) { + this.index = i; + } + } + + public record InitContext(@Nullable CachedDirectedGlobalPos pos, + boolean deserialized) implements Initializable.Context { + + public static InitContext createdAt(CachedDirectedGlobalPos pos) { + return new InitContext(pos, false); + } + + public static InitContext deserialize() { + return new InitContext(null, true); + } + + @Override + public boolean created() { + return !deserialized; + } + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/handler/TickingComponent.java b/src/main/java/dev/amble/lib/api/sync/handler/TickingComponent.java new file mode 100644 index 0000000..c25beb2 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/handler/TickingComponent.java @@ -0,0 +1,14 @@ +package dev.amble.lib.api.sync.handler; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.server.MinecraftServer; + +public interface TickingComponent { + default void tick(MinecraftServer server) { } + + @Environment(EnvType.CLIENT) + default void tick(MinecraftClient client) { } +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/RootMap.java b/src/main/java/dev/amble/lib/api/sync/manager/RootMap.java new file mode 100644 index 0000000..ac08bd0 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/RootMap.java @@ -0,0 +1,13 @@ +package dev.amble.lib.api.sync.manager; + +import java.util.HashMap; +import java.util.UUID; + +import dev.amble.lib.api.sync.RootComponent; + +public class RootMap extends HashMap { + + public T put(T t) { + return this.put(t.getUuid(), t); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java new file mode 100644 index 0000000..0c491fe --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java @@ -0,0 +1,178 @@ +package dev.amble.lib.api.sync.manager; + +import java.util.Collection; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.GlobalPos; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.handler.ComponentManager; +import dev.amble.lib.api.sync.handler.ComponentRegistry; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.data.DirectedBlockPos; +import dev.amble.lib.data.DirectedGlobalPos; +import dev.amble.lib.data.gson.*; + +public abstract class SyncManager

{ + protected final RootMap

lookup = new RootMap<>(); + + protected final Gson networkGson; + protected final Gson fileGson; + + protected SyncManager() { + this.networkGson = this.getNetworkGson(this.createGsonBuilder(Exclude.Strategy.NETWORK)).create(); + this.fileGson = this.getFileGson(this.createGsonBuilder(Exclude.Strategy.FILE)).create(); + } + + protected GsonBuilder createGsonBuilder(Exclude.Strategy strategy) { + return new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes field) { + Exclude exclude = field.getAnnotation(Exclude.class); + + if (exclude == null) + return false; + + Exclude.Strategy excluded = exclude.strategy(); + return excluded == Exclude.Strategy.ALL || excluded == strategy; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return false; + } + }) + .registerTypeAdapter(DirectedGlobalPos.class, DirectedGlobalPos.serializer()) + .registerTypeAdapter(DirectedBlockPos.class, DirectedBlockPos.serializer()) + .registerTypeAdapter(NbtCompound.class, new NbtSerializer()) + .registerTypeAdapter(ItemStack.class, new ItemStackSerializer()) + .registerTypeAdapter(Identifier.class, new IdentifierSerializer()) + .registerTypeAdapter(GlobalPos.class, new GlobalPosSerializer()) + .registerTypeAdapter(BlockPos.class, new BlockPosSerializer()) + .registerTypeAdapter(RegistryKey.class, new RegistryKeySerializer()) + .registerTypeAdapter(ComponentManager.class, ComponentManager.serializer(this.getRegistry(), this.getManagerId())) + .registerTypeAdapter(SyncComponent.IdLike.class, getRegistry().idSerializer()); + } + + protected GsonBuilder getNetworkGson(GsonBuilder builder) { + return builder; + } + + protected GsonBuilder getFileGson(GsonBuilder builder) { +// if (!AmbleKit.CONFIG.SERVER.MINIFY_JSON) +// builder.setPrettyPrinting(); + + // /\ + builder.setPrettyPrinting(); + + return builder; +// return builder.registerTypeAdapter(Value.class, Value.serializer()) +// .registerTypeAdapter(BoolValue.class, BoolValue.serializer()) +// .registerTypeAdapter(IntValue.class, IntValue.serializer()) +// .registerTypeAdapter(RangedIntValue.class, RangedIntValue.serializer()) +// .registerTypeAdapter(DoubleValue.class, DoubleValue.serializer()); + } + + public void get(C c, UUID uuid, Consumer

consumer) { + if (uuid == null) + return; // ugh + + P result = this.lookup.get(uuid); + + if (result == null) { + this.load(c, uuid, consumer); + return; + } + + consumer.accept(result); + } + + /** + * By all means a bad practice. Use {@link #get(Object, UUID, Consumer)} + * instead. Ensures to return a {@link RootComponent} instance as fast as possible. + *

+ * By using this method you accept the risk of the object not being on the + * client. + * + * @deprecated Have you read the comment? + */ + @Nullable @Deprecated + public abstract P demand(C c, UUID uuid); + + public abstract void load(C c, UUID uuid, @Nullable Consumer

consumer); + + public void reset() { + this.lookup.clear(); + } + + public Collection ids() { + return this.lookup.keySet(); + } + + public void forEach(Consumer

consumer) { + this.lookup.forEach((uuid, t) -> consumer.accept(t)); + } + + public P find(Predicate

predicate) { + for (P t : this.lookup.values()) { + if (predicate.test(t)) + return t; + } + + return null; + } + + + public Gson getNetworkGson() { + return this.networkGson; + } + + public Gson getFileGson() { + return fileGson; + } + + @FunctionalInterface + public interface ContextManager { + R run(C c, SyncManager manager); + } + + public Identifier askPacket() { + return createPacket("ask"); + } + public Identifier sendPacket() { + return createPacket("send"); + } + public Identifier sendBulkPacket() { + return createPacket("send_bulk"); + } + public Identifier sendComponentPacket() { + return createPacket("send_component"); + } + public Identifier removePacket() { + return createPacket("remove"); + } + + public Identifier createPacket(String id) { + return AmbleKit.id(this.modId() + "/" + name() + "/" + id); + } + + public abstract ComponentRegistry getRegistry(); + public abstract SyncComponent.IdLike getManagerId(); + public abstract String modId(); + public abstract String name(); +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/client/ClientComponentData.java b/src/main/java/dev/amble/lib/api/sync/manager/client/ClientComponentData.java new file mode 100644 index 0000000..f18520d --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/client/ClientComponentData.java @@ -0,0 +1,24 @@ +package dev.amble.lib.api.sync.manager.client; + +import dev.amble.lib.api.sync.Disposable; +import dev.amble.lib.api.sync.Exclude; + + +public class ClientComponentData implements Disposable { + @Exclude + private boolean aged = false; + + public void age() { + this.aged = true; + } + + @Override + public void dispose() { + + } + + @Override + public boolean isAged() { + return aged; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/client/ClientRootComponent.java b/src/main/java/dev/amble/lib/api/sync/manager/client/ClientRootComponent.java new file mode 100644 index 0000000..43b6dfb --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/client/ClientRootComponent.java @@ -0,0 +1,22 @@ +package dev.amble.lib.api.sync.manager.client; + +import dev.amble.lib.api.sync.Disposable; + +public interface ClientRootComponent extends Disposable { + ClientComponentData data(); + + @Override + default void age() { + data().age(); + } + + @Override + default void dispose() { + data().dispose(); + } + + @Override + default boolean isAged() { + return data().isAged(); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/client/ClientSyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/client/ClientSyncManager.java new file mode 100644 index 0000000..a444191 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/client/ClientSyncManager.java @@ -0,0 +1,162 @@ +package dev.amble.lib.api.sync.manager.client; + +import java.util.UUID; +import java.util.function.Consumer; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientLoginConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.PacketByteBuf; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.api.sync.manager.SyncManager; + +public abstract class ClientSyncManager extends SyncManager { + private final Multimap> subscribers = ArrayListMultimap.create(); + + protected ClientSyncManager() { + super(); + + ClientPlayNetworking.registerGlobalReceiver(sendPacket(), (client, handler, buf, responseSender) -> this.syncTardis(buf)); + + ClientPlayNetworking.registerGlobalReceiver(sendBulkPacket(), + (client, handler, buf, responseSender) -> this.syncBulk(buf)); + + ClientPlayNetworking.registerGlobalReceiver(removePacket(), (client, handler, buf, responseSender) -> this.remove(buf)); + + ClientPlayNetworking.registerGlobalReceiver(sendComponentPacket(), (client, handler, buf, responseSender) -> this.syncDelta(buf)); + + ClientTickEvents.END_CLIENT_TICK.register(client -> { + if (client.player == null || client.world == null) + return; + + for (T tardis : this.lookup.values()) { + tardis.tick(client); + } + }); + + ServerLifecycleEvents.SERVER_STOPPING.register(server -> this.reset()); + ClientLoginConnectionEvents.DISCONNECT.register((client, reason) -> this.reset()); + ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> this.reset()); + } + + private void remove(PacketByteBuf buf) { + this.lookup.remove(buf.readUuid()); + } + + @Override + public void load(MinecraftClient client, UUID uuid, @Nullable Consumer consumer) { + if (client.player == null) + return; + + if (uuid == null) + return; + + PacketByteBuf data = PacketByteBufs.create(); + data.writeUuid(uuid); + + if (consumer != null) + this.subscribers.put(uuid, consumer); + + // MinecraftClient.getInstance().executeTask(() -> ClientPlayNetworking.send(ASK, data)); + } + + @Override + @Deprecated + public @Nullable T demand(MinecraftClient client, UUID uuid) { + T result = this.lookup.get(uuid); + + if (result == null) + this.load(client, uuid, null); + + return result; + } + + @Deprecated + public @Nullable T demand(UUID uuid) { + return this.demand(MinecraftClient.getInstance(), uuid); + } + + public void get(UUID uuid, Consumer consumer) { + this.get(MinecraftClient.getInstance(), uuid, consumer); + } + + @Override + public void reset() { + this.subscribers.clear(); + + this.forEach(T::dispose); + super.reset(); + } + + private void syncDelta(PacketByteBuf buf) { + UUID id = buf.readUuid(); + int count = buf.readShort(); + + T tardis = this.demand(id); + + if (tardis == null) + return; // wait 'till the server sends a full update + + for (int i = 0; i < count; i++) { + this.syncComponent(tardis, buf); + } + } + + private void syncTardis(UUID uuid, String json) { + try { + T tardis = this.networkGson.fromJson(json, getRootComponentType()); + RootComponent.init(tardis, SyncComponent.InitContext.deserialize()); + + // tardis.travel(); // get a random element. if its null it will complain + + synchronized (this) { + T old = this.lookup.put(tardis); + + if (old != null) + old.age(); + + for (Consumer consumer : this.subscribers.removeAll(uuid)) { + consumer.accept(tardis); + } + } + } catch (Throwable t) { + AmbleKit.LOGGER.error("Received malformed JSON file {}", json); + AmbleKit.LOGGER.error("Failed to deserialize {}/{} data: ", modId(), name(), t); + } + } + + private void syncTardis(PacketByteBuf buf) { + this.syncTardis(buf.readUuid(), buf.readString()); + } + + private void syncBulk(PacketByteBuf buf) { + int count = buf.readInt(); + + for (int i = 0; i < count; i++) { + this.syncTardis(buf); + } + } + + private void syncComponent(T tardis, PacketByteBuf buf) { + String rawId = buf.readString(); + + SyncComponent.IdLike id = this.getRegistry().get(rawId); + SyncComponent component = this.networkGson.fromJson(buf.readString(), id.clazz()); + + id.set(tardis, component); + SyncComponent.init(component, tardis, SyncComponent.InitContext.deserialize()); + } + + protected abstract Class getRootComponentType(); +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/server/ComponentFileManager.java b/src/main/java/dev/amble/lib/api/sync/manager/server/ComponentFileManager.java new file mode 100644 index 0000000..982aa1c --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/server/ComponentFileManager.java @@ -0,0 +1,117 @@ +package dev.amble.lib.api.sync.manager.server; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.WorldSavePath; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.manager.SyncManager; + +public class ComponentFileManager { + + protected boolean locked = false; + protected final String modid; + protected final String name; + protected final Class clazz; + + public ComponentFileManager(String modid, String name, Class clazz) { + this.modid = modid; + this.name = name; + this.clazz = clazz; + } + + public void delete(MinecraftServer server, UUID uuid) { + try { + Files.deleteIfExists(this.getSavePath(server, uuid, "json")); + } catch (IOException e) { + AmbleKit.LOGGER.error("Failed to delete Root Component {} {}", name, uuid, e); + } + } + + private Path getRootSavePath(Path root) { + return root.resolve("." + modid + "/" + name); + } + + public Path getRootSavePath(MinecraftServer server) { + return this.getRootSavePath(server.getSavePath(WorldSavePath.ROOT)); + } + + private Path getSavePath(MinecraftServer server, UUID uuid, String suffix) throws IOException { + Path result = this.getRootSavePath(server).resolve(uuid.toString() + "." + suffix); + Files.createDirectories(result.getParent()); + + return result; + } + + public R load(MinecraftServer server, SyncManager manager, UUID uuid, ComponentLoader function, + Consumer consumer) { + if (this.locked) + return null; + + long start = System.currentTimeMillis(); + + try { + Path file = this.getSavePath(server, uuid, "json"); + String raw = Files.readString(file); + + JsonObject object = JsonParser.parseString(raw).getAsJsonObject(); + + R root = function.apply(manager.getFileGson(), object); + consumer.accept(root); + + AmbleKit.LOGGER.info("Deserialized {} {} in {}ms", name, root, System.currentTimeMillis() - start); + return root; + } catch (IOException e) { + AmbleKit.LOGGER.warn("Failed to load {} {}!", name, uuid); + AmbleKit.LOGGER.warn(e.getMessage()); + } + + return null; + } + + public void save(MinecraftServer server, SyncManager manager, R root) { + try { + Path savePath = this.getSavePath(server, root.getUuid(), "json"); + Files.writeString(savePath, manager.getFileGson().toJson(root, clazz)); + } catch (IOException e) { + AmbleKit.LOGGER.warn("Couldn't save Root Component {}", root.getUuid(), e); + } + } + + public void setLocked(boolean locked) { + this.locked = locked; + } + + public boolean isLocked() { + return locked; + } + + public List getList(MinecraftServer server) { + try { + return Files.list(this.getRootSavePath(server)).map(path -> { + String name = path.getFileName().toString(); + return UUID.fromString(name.substring(0, name.indexOf('.'))); + }).toList(); + } catch (IOException e) { + AmbleKit.LOGGER.error("Failed to list {} files", name,e); + } + + return List.of(); + } + + @FunctionalInterface + public interface ComponentLoader { + R apply(Gson gson, JsonObject object); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerComponentData.java b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerComponentData.java new file mode 100644 index 0000000..824bee9 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerComponentData.java @@ -0,0 +1,55 @@ +package dev.amble.lib.api.sync.manager.server; + +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.handler.SyncComponent; + +public class ServerComponentData { + @Exclude + private boolean removed; + + @Exclude + private final Set delta = new HashSet<>(32); + + public void setRemoved(boolean removed) { + this.removed = removed; + } + + public boolean isRemoved() { + return removed; + } + + public void markDirty(SyncComponent component) { + if (component == null) + return; + + if (!(component.parent() instanceof ServerRootComponent sParent)) + return; + if (sParent.data() != this) + return; + + this.delta.add(component); + } + + public void consumeDelta(Consumer consumer) { + if (this.delta.isEmpty()) + return; + + for (SyncComponent component : this.delta) { + consumer.accept(component); + } + + this.delta.clear(); + } + + public boolean hasDelta() { + return !this.delta.isEmpty(); + } + + public int getDeltaSize() { + return this.delta.size(); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerRootComponent.java b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerRootComponent.java new file mode 100644 index 0000000..0775da7 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerRootComponent.java @@ -0,0 +1,6 @@ +package dev.amble.lib.api.sync.manager.server; + +public interface ServerRootComponent { + ServerComponentData data(); + +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java new file mode 100644 index 0000000..c5436a6 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java @@ -0,0 +1,275 @@ +package dev.amble.lib.api.sync.manager.server; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.PacketByteBuf; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.network.ServerPlayerEntity; + +import dev.amble.lib.api.KitEvents; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.api.sync.manager.SyncManager; +import dev.amble.lib.events.ServerCrashEvent; +import dev.amble.lib.events.WorldSaveEvent; + +public abstract class ServerSyncManager extends SyncManager { + protected final ComponentFileManager fileManager; + private final Set delta = new HashSet<>(); + + public ServerSyncManager() { + this.fileManager = new ComponentFileManager<>(this.modId(), this.name(), this.getRootComponentType()); + + ServerLifecycleEvents.SERVER_STARTING.register(server -> this.fileManager.setLocked(false)); + ServerLifecycleEvents.SERVER_STOPPING.register(this::saveAndReset); + + ServerCrashEvent.EVENT.register(((server, report) -> this.reset())); // just panic and reset + WorldSaveEvent.EVENT.register(world -> this.save(world.getServer(), false)); + + KitEvents.SYNC_ROOT.register((player, chunk) -> { + if (this.fileManager.isLocked()) return; + + if (this.lookup.size() >= 8) { + this.sendBulk(player, new HashSet<>(this.lookup.values())); + return; + } + + this.sendAll(player, new HashSet<>(this.lookup.values())); + }); + + /* + if (DEMENTIA) { + TardisEvents.UNLOAD_TARDIS.register(WorldWithTardis.forDesync((player, tardisSet) -> { + for (ServerTardis tardis : tardisSet) { + if (isInvalid(tardis)) + continue; + + this.sendTardisRemoval(player, tardis); + } + })); + }*/ + + + ServerTickEvents.END_SERVER_TICK.register(server -> { + for (T root : this.lookup.values()) { + if (root.data().isRemoved()) + continue; + + root.tick(server); + } + }); + + ServerTickEvents.START_SERVER_TICK.register(server -> { + if (this.fileManager.isLocked()) + return; + + for (T tardis : new HashSet<>(this.delta)) { + if (isInvalid(tardis)) + continue; + + if (!tardis.data().hasDelta()) + continue; + + PacketByteBuf buf = this.prepareSendDelta(tardis); + tardis.data().consumeDelta(component -> this.writeComponent(component, buf)); + + this.getSubscribedPlayers(tardis).forEach( + watching -> this.sendComponents(watching, buf) + ); + } + + this.delta.clear(); + }); + } + + @Override + public @Nullable T demand(MinecraftServer server, UUID uuid) { + if (uuid == null) + return null; // ugh - ong bro + + T result = this.lookup.get(uuid); + + if (result == null) + result = this.load(server, uuid); + + return result; + } + + @Override + public void load(MinecraftServer server, UUID uuid, @Nullable Consumer consumer) { + if (consumer != null) + consumer.accept(this.load(server, uuid)); + } + + private T load(MinecraftServer server, UUID uuid) { + return this.fileManager.load(server, this, uuid, this::read, this.lookup::put); + } + + public void loadAll(MinecraftServer server, @Nullable Consumer consumer) { + for (UUID id : this.fileManager.getList(server)) { + this.get(server, id, consumer); + } + } + + public void remove(MinecraftServer server, T tardis) { + tardis.data().setRemoved(true); + + tardis.dispose(); + this.sendRemoval(server, tardis); + + this.lookup.remove(tardis.getUuid()); + this.fileManager.delete(server, tardis.getUuid()); + } + + protected void sendRemoval(MinecraftServer server, T tardis) { + if (tardis == null) + return; + + PacketByteBuf data = PacketByteBufs.create(); + data.writeUuid(tardis.getUuid()); + + for (ServerPlayerEntity player : server.getPlayerManager().getPlayerList()) { + this.sendRemoval(player, data); + } + } + + protected void sendRemoval(ServerPlayerEntity player, T tardis) { + PacketByteBuf data = PacketByteBufs.create(); + data.writeUuid(tardis.getUuid()); + + this.sendRemoval(player, data); + } + + protected void sendRemoval(ServerPlayerEntity player, PacketByteBuf data) { + ServerPlayNetworking.send(player, removePacket(), data); + } + + private void save(MinecraftServer server, boolean clean) { + if (clean) + this.fileManager.setLocked(true); + + for (T tardis : this.lookup.values()) { + if (clean) { + if (tardis == null) + continue; + + tardis.dispose(); + } + + this.fileManager.save(server, this, tardis); + } + + if (!clean) + return; + } + + private void saveAndReset(MinecraftServer server) { + this.save(server, true); + this.reset(); + } + + + /** + * @return An initialized {@link RootComponent} without attachments. + */ + protected T read(Gson gson, JsonObject json) { + T tardis = gson.fromJson(json, getRootComponentType()); + RootComponent.init(tardis, SyncComponent.InitContext.deserialize()); + + return tardis; + } + + private void send(ServerPlayerEntity player, PacketByteBuf data) { + ServerPlayNetworking.send(player, sendPacket(), data); + } + + private void sendComponents(ServerPlayerEntity player, PacketByteBuf data) { + ServerPlayNetworking.send(player, sendComponentPacket(), data); + } + + private void writeSend(T tardis, PacketByteBuf buf) { + buf.writeUuid(tardis.getUuid()); + buf.writeString(this.networkGson.toJson(tardis, this.getRootComponentType())); + } + + private void writeComponent(SyncComponent component, PacketByteBuf buf) { + String rawId = this.getRegistry().get(component); + + buf.writeString(rawId); + buf.writeString(this.networkGson.toJson(component)); + } + + private PacketByteBuf prepareSend(T tardis) { + PacketByteBuf data = PacketByteBufs.create(); + this.writeSend(tardis, data); + + return data; + } + + private PacketByteBuf prepareSendDelta(T tardis) { + PacketByteBuf data = PacketByteBufs.create(); + + data.writeUuid(tardis.getUuid()); + data.writeShort(tardis.data().getDeltaSize()); + + return data; + } + + protected void sendBulk(ServerPlayerEntity player, Set set) { + PacketByteBuf data = PacketByteBufs.create(); + data.writeInt(set.size()); + + for (T tardis : set) { + if (isInvalid(tardis)) + continue; + + this.writeSend(tardis, data); + } + + ServerPlayNetworking.send(player, sendBulkPacket(), data); + } + + protected void sendAll(ServerPlayerEntity player, Set set) { + for (T tardis : set) { + if (isInvalid(tardis)) + continue; + + // TardisEvents.SEND_TARDIS.invoker().send(tardis, player); + this.send(player, this.prepareSend(tardis)); + } + } + + protected void sendAll(Set set) { + for (T tardis : set) { + if (isInvalid(tardis)) + continue; + + PacketByteBuf buf = this.prepareSend(tardis); + + this.getSubscribedPlayers(tardis).forEach( + watching -> { + this.send(watching, buf); + } + ); + } + } + + public abstract Set getSubscribedPlayers(T root); + + protected boolean isInvalid(T tardis) { + return tardis == null || tardis.data().isRemoved(); + } + + protected abstract Class getRootComponentType(); +} diff --git a/src/main/java/dev/amble/lib/data/CachedDirectedGlobalPos.java b/src/main/java/dev/amble/lib/data/CachedDirectedGlobalPos.java new file mode 100644 index 0000000..9615343 --- /dev/null +++ b/src/main/java/dev/amble/lib/data/CachedDirectedGlobalPos.java @@ -0,0 +1,112 @@ +package dev.amble.lib.data; + +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; + +import dev.amble.lib.api.sync.Exclude; + +public class CachedDirectedGlobalPos extends DirectedGlobalPos { + + @Exclude + private ServerWorld world; + + private CachedDirectedGlobalPos(RegistryKey key, BlockPos pos, byte rotation) { + super(key, pos, rotation); + } + + private CachedDirectedGlobalPos(ServerWorld world, BlockPos pos, byte rotation) { + this(world.getRegistryKey(), pos, rotation); + this.world = world; + } + + public static CachedDirectedGlobalPos create(ServerWorld world, BlockPos pos, byte rotation) { + return new CachedDirectedGlobalPos(world, pos, rotation); + } + + public static CachedDirectedGlobalPos create(RegistryKey world, BlockPos pos, byte rotation) { + return new CachedDirectedGlobalPos(world, pos, rotation); + } + + private static CachedDirectedGlobalPos createSame(ServerWorld world, RegistryKey dimension, BlockPos pos, byte rotation) { + if (world == null) + return new CachedDirectedGlobalPos(dimension, pos, rotation); + + return CachedDirectedGlobalPos.create(world, pos, rotation); + } + + private static CachedDirectedGlobalPos createNew(ServerWorld lastWorld, RegistryKey newWorldKey, BlockPos pos, + byte rotation) { + if (lastWorld == null) + return new CachedDirectedGlobalPos(newWorldKey, pos, rotation); + + ServerWorld newWorld = lastWorld; + + if (lastWorld.getRegistryKey() != newWorldKey) + newWorld = lastWorld.getServer().getWorld(newWorldKey); + + return CachedDirectedGlobalPos.create(newWorld, pos, rotation); + } + + public void init(MinecraftServer server) { + if (this.world == null) + this.world = server.getWorld(this.getDimension()); + } + + public ServerWorld getWorld() { // TODO - this is often null + return world; + } + + @Override + public CachedDirectedGlobalPos offset(int x, int y, int z) { + return pos(this.getPos().add(x, y, z)); + } + + @Override + public CachedDirectedGlobalPos world(RegistryKey dimension) { + return CachedDirectedGlobalPos.createNew(this.world, dimension, this.getPos(), this.getRotation()); + } + + @Override + public CachedDirectedGlobalPos pos(BlockPos pos) { + return CachedDirectedGlobalPos.createSame(this.world, this.getDimension(), pos, this.getRotation()); + } + + @Override + public CachedDirectedGlobalPos pos(int x, int y, int z) { + return pos(new BlockPos(x, y, z)); + } + + @Override + public CachedDirectedGlobalPos rotation(byte rotation) { + return CachedDirectedGlobalPos.createSame(this.world, this.getDimension(), this.getPos(), rotation); + } + + public CachedDirectedGlobalPos world(ServerWorld world) { + return CachedDirectedGlobalPos.create(world, this.getPos(), this.getRotation()); + } + + public static CachedDirectedGlobalPos fromNbt(NbtCompound compound) { + BlockPos pos = NbtHelper.toBlockPos(compound); + RegistryKey dimension = RegistryKey.of(RegistryKeys.WORLD, + new Identifier(compound.getString("dimension"))); + + byte rotation = compound.getByte("rotation"); + return createNew(null, dimension, pos, rotation); + } + + public static CachedDirectedGlobalPos read(PacketByteBuf buf) { + RegistryKey registryKey = buf.readRegistryKey(RegistryKeys.WORLD); + BlockPos blockPos = buf.readBlockPos(); + byte rotation = buf.readByte(); + + return CachedDirectedGlobalPos.createNew(null, registryKey, blockPos, rotation); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/data/enummap/EnumMap.java b/src/main/java/dev/amble/lib/data/enummap/EnumMap.java new file mode 100644 index 0000000..9cebc90 --- /dev/null +++ b/src/main/java/dev/amble/lib/data/enummap/EnumMap.java @@ -0,0 +1,136 @@ +package dev.amble.lib.data.enummap; + +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.jetbrains.annotations.NotNull; + +/** + * Custom and lightweight map implementation for enums. I know + * {@link java.util.EnumMap} exists, but it's different. + */ +public class EnumMap { + + private final V[] values; + + public EnumMap(Supplier values, Function supplier) { + this.values = supplier.apply(values.get().length); + } + + public void map(Function func) { + for (int i = 0; i < values.length; i++) { + values[i] = func.apply(values[i]); + } + } + + /** + * @implNote Will return ALL values, including nulls. + * @return All values associated with each variant of an enum, null if no value + * is present. + */ + public V[] getValues() { + return this.values; + } + + public V put(K k, V v) { + V prev = values[k.index()]; + values[k.index()] = v; + + return prev; + } + + public V remove(K k) { + V prev = values[k.index()]; + values[k.index()] = null; + + return prev; + } + + public V get(K k) { + return this.get(k.index()); + } + + public V get(int index) { + return values[index]; + } + + public boolean containsKey(K k) { + if ((this.size() - 1) < k.index()) return false; + + return this.values[k.index()] != null; + } + + public void clear() { + Arrays.fill(this.values, null); + } + + public int size() { + return this.values.length; + } + + public static class Compliant extends EnumMap implements Map { + + private final K[] keys; + + public Compliant(Supplier values, Function supplier) { + super(values, supplier); + this.keys = values.get(); + } + + @Override + public V remove(Object k) { + return this.remove((K) k); + } + + @Override + public V get(Object key) { + return this.get((K) key); + } + + @Override + public void putAll(@NotNull Map m) { + m.forEach(this::put); + } + + @Override + public boolean containsKey(Object key) { + return this.containsKey((K) key); + } + + @NotNull @Override + public Set> entrySet() { + V[] values = this.getValues(); + Set> set = new HashSet<>(values.length); + + for (int i = 0; i < this.size(); i++) { + V value = values[i]; + + if (value != null) + set.add(Map.entry(this.keys[i], value)); + } + + return set; + } + + @NotNull @Override + public Set keySet() { + return Set.of(this.keys); + } + + @NotNull @Override + public Collection values() { + return List.of(this.getValues()); + } + + @Override + public boolean isEmpty() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsValue(Object value) throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/java/dev/amble/lib/data/enummap/EnumSet.java b/src/main/java/dev/amble/lib/data/enummap/EnumSet.java new file mode 100644 index 0000000..dd628c6 --- /dev/null +++ b/src/main/java/dev/amble/lib/data/enummap/EnumSet.java @@ -0,0 +1,30 @@ +package dev.amble.lib.data.enummap; + +import java.util.function.Supplier; + +public class EnumSet { + + private final boolean[] values; + + public EnumSet(Supplier values) { + this.values = new boolean[values.get().length]; + } + + public boolean contains(K k) { + return values[k.index()]; + } + + public void add(K k) { + values[k.index()] = true; + } + + public void addAll(K[] ks) { + for (K k : ks) { + this.add(k); + } + } + + public void remove(K k) { + values[k.index()] = false; + } +} diff --git a/src/main/java/dev/amble/lib/data/enummap/Ordered.java b/src/main/java/dev/amble/lib/data/enummap/Ordered.java new file mode 100644 index 0000000..23d64a8 --- /dev/null +++ b/src/main/java/dev/amble/lib/data/enummap/Ordered.java @@ -0,0 +1,5 @@ +package dev.amble.lib.data.enummap; + +public interface Ordered { + int index(); +} diff --git a/src/main/java/dev/amble/lib/data/gson/BlockPosSerializer.java b/src/main/java/dev/amble/lib/data/gson/BlockPosSerializer.java new file mode 100644 index 0000000..09f41c2 --- /dev/null +++ b/src/main/java/dev/amble/lib/data/gson/BlockPosSerializer.java @@ -0,0 +1,33 @@ +package dev.amble.lib.data.gson; + +import java.lang.reflect.Type; + +import com.google.gson.*; + +import net.minecraft.util.math.BlockPos; + +public class BlockPosSerializer implements JsonDeserializer, JsonSerializer { + + @Override + public BlockPos deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + + int x = obj.get("x").getAsInt(); + int y = obj.get("y").getAsInt(); + int z = obj.get("z").getAsInt(); + + return new BlockPos(x, y, z); + } + + @Override + public JsonElement serialize(BlockPos src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + + result.addProperty("x", src.getX()); + result.addProperty("y", src.getY()); + result.addProperty("z", src.getZ()); + + return result; + } +} diff --git a/src/main/java/dev/amble/lib/data/gson/GlobalPosSerializer.java b/src/main/java/dev/amble/lib/data/gson/GlobalPosSerializer.java new file mode 100644 index 0000000..0d8cd0c --- /dev/null +++ b/src/main/java/dev/amble/lib/data/gson/GlobalPosSerializer.java @@ -0,0 +1,39 @@ +package dev.amble.lib.data.gson; + +import java.lang.reflect.Type; + +import com.google.gson.*; + +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.GlobalPos; +import net.minecraft.world.World; + +public class GlobalPosSerializer implements JsonDeserializer, JsonSerializer { + + @Override + public GlobalPos deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + + RegistryKey dimension = context.deserialize(obj.get("dimension"), RegistryKey.class); + + int x = obj.get("x").getAsInt(); + int y = obj.get("y").getAsInt(); + int z = obj.get("z").getAsInt(); + + return GlobalPos.create(dimension, new BlockPos(x, y, z)); + } + + @Override + public JsonElement serialize(GlobalPos src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + + result.add("dimension", context.serialize(src.getDimension().getValue())); + result.addProperty("x", src.getPos().getX()); + result.addProperty("y", src.getPos().getY()); + result.addProperty("z", src.getPos().getZ()); + + return result; + } +} diff --git a/src/main/java/dev/amble/lib/data/gson/IdentifierSerializer.java b/src/main/java/dev/amble/lib/data/gson/IdentifierSerializer.java new file mode 100644 index 0000000..d518d77 --- /dev/null +++ b/src/main/java/dev/amble/lib/data/gson/IdentifierSerializer.java @@ -0,0 +1,24 @@ +package dev.amble.lib.data.gson; + +import java.lang.reflect.Type; + +import com.google.gson.*; + +import net.minecraft.util.Identifier; + +/** + * A more compact identifier serializer. + */ +public class IdentifierSerializer implements JsonSerializer, JsonDeserializer { + + @Override + public Identifier deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return Identifier.tryParse(json.getAsString()); + } + + @Override + public JsonElement serialize(Identifier src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toString()); + } +} diff --git a/src/main/java/dev/amble/lib/data/gson/ItemStackSerializer.java b/src/main/java/dev/amble/lib/data/gson/ItemStackSerializer.java new file mode 100644 index 0000000..377e701 --- /dev/null +++ b/src/main/java/dev/amble/lib/data/gson/ItemStackSerializer.java @@ -0,0 +1,21 @@ +package dev.amble.lib.data.gson; + +import java.lang.reflect.Type; + +import com.google.gson.*; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtCompound; + +public class ItemStackSerializer implements JsonSerializer, JsonDeserializer { + @Override + public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + return ItemStack.fromNbt(context.deserialize(json, NbtCompound.class)); + } + + @Override + public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(src.writeNbt(new NbtCompound())); + } +} diff --git a/src/main/java/dev/amble/lib/data/gson/NbtSerializer.java b/src/main/java/dev/amble/lib/data/gson/NbtSerializer.java new file mode 100644 index 0000000..0af445b --- /dev/null +++ b/src/main/java/dev/amble/lib/data/gson/NbtSerializer.java @@ -0,0 +1,29 @@ +package dev.amble.lib.data.gson; + +import java.lang.reflect.Type; + +import com.google.gson.*; +import com.mojang.brigadier.exceptions.CommandSyntaxException; + +import dev.amble.lib.AmbleKit; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.StringNbtReader; +import net.minecraft.nbt.visitor.StringNbtWriter; + +public class NbtSerializer implements JsonSerializer, JsonDeserializer { + @Override + public NbtCompound deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + try { + return StringNbtReader.parse(json.getAsString()); + } catch (CommandSyntaxException e) { + AmbleKit.LOGGER.error("Invalid NBT string: {}", json.getAsString()); + return new NbtCompound(); + } + } + + @Override + public JsonElement serialize(NbtCompound src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(new StringNbtWriter().apply(src)); + } +} diff --git a/src/main/java/dev/amble/lib/data/gson/RegistryKeySerializer.java b/src/main/java/dev/amble/lib/data/gson/RegistryKeySerializer.java new file mode 100644 index 0000000..ca2179b --- /dev/null +++ b/src/main/java/dev/amble/lib/data/gson/RegistryKeySerializer.java @@ -0,0 +1,30 @@ +package dev.amble.lib.data.gson; + +import java.lang.reflect.Type; + +import com.google.gson.*; + +import net.minecraft.registry.RegistryKey; +import net.minecraft.util.Identifier; + +public class RegistryKeySerializer implements JsonSerializer>, JsonDeserializer> { + + @Override + public RegistryKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject object = json.getAsJsonObject(); + Identifier registry = context.deserialize(object.get("registry"), Identifier.class); + Identifier value = context.deserialize(object.get("value"), Identifier.class); + + return RegistryKey.of(RegistryKey.ofRegistry(registry), value); + } + + @Override + public JsonElement serialize(RegistryKey src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject object = new JsonObject(); + object.add("registry", context.serialize(src.getRegistry())); + object.add("value", context.serialize(src.getValue())); + + return object; + } +} diff --git a/src/main/java/dev/amble/lib/events/ServerCrashEvent.java b/src/main/java/dev/amble/lib/events/ServerCrashEvent.java new file mode 100644 index 0000000..eccc8fe --- /dev/null +++ b/src/main/java/dev/amble/lib/events/ServerCrashEvent.java @@ -0,0 +1,21 @@ +package dev.amble.lib.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.crash.CrashReport; + +public class ServerCrashEvent { + public static final Event EVENT = EventFactory.createArrayBacked(Crash.class, + callbacks -> (server, report) -> { + for (Crash callback : callbacks) { + callback.onServerCrash(server, report); + } + }); + + @FunctionalInterface + public interface Crash { + void onServerCrash(MinecraftServer server, CrashReport report); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/events/WorldSaveEvent.java b/src/main/java/dev/amble/lib/events/WorldSaveEvent.java new file mode 100644 index 0000000..2866b7d --- /dev/null +++ b/src/main/java/dev/amble/lib/events/WorldSaveEvent.java @@ -0,0 +1,20 @@ +package dev.amble.lib.events; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +import net.minecraft.server.world.ServerWorld; + +public class WorldSaveEvent { + + public static final Event EVENT = EventFactory.createArrayBacked(Save.class, callbacks -> world -> { + for (Save callback : callbacks) { + callback.onWorldSave(world); + } + }); + + @FunctionalInterface + public interface Save { + void onWorldSave(ServerWorld world); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/mixin/MinecraftServerMixin.java b/src/main/java/dev/amble/lib/mixin/MinecraftServerMixin.java new file mode 100644 index 0000000..278133d --- /dev/null +++ b/src/main/java/dev/amble/lib/mixin/MinecraftServerMixin.java @@ -0,0 +1,21 @@ +package dev.amble.lib.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.crash.CrashReport; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.events.ServerCrashEvent; + +@Mixin(MinecraftServer.class) +public abstract class MinecraftServerMixin { + @Inject(method = "setCrashReport", at = @At("TAIL")) + private void ait$setCrashReport(CrashReport report, CallbackInfo info) { + AmbleKit.LOGGER.error("Crash Detected - nice one m8"); + ServerCrashEvent.EVENT.invoker().onServerCrash((MinecraftServer) (Object) this, report); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/mixin/ServerWorldMixin.java b/src/main/java/dev/amble/lib/mixin/ServerWorldMixin.java new file mode 100644 index 0000000..49c3936 --- /dev/null +++ b/src/main/java/dev/amble/lib/mixin/ServerWorldMixin.java @@ -0,0 +1,19 @@ +package dev.amble.lib.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.server.world.ServerWorld; + +import dev.amble.lib.events.WorldSaveEvent; + +@Mixin(ServerWorld.class) +public class ServerWorldMixin { + + @Inject(method = "saveLevel", at = @At("HEAD")) + private void saveLevel(CallbackInfo ci) { + WorldSaveEvent.EVENT.invoker().onWorldSave((ServerWorld) (Object) this); + } +} \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/mixin/networking/ThreadedAnvilChunkStorageMixin.java b/src/main/java/dev/amble/lib/mixin/networking/ThreadedAnvilChunkStorageMixin.java new file mode 100644 index 0000000..4be25db --- /dev/null +++ b/src/main/java/dev/amble/lib/mixin/networking/ThreadedAnvilChunkStorageMixin.java @@ -0,0 +1,23 @@ +package dev.amble.lib.mixin.networking; + +import org.apache.commons.lang3.mutable.MutableObject; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.network.packet.s2c.play.ChunkDataS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.server.world.ThreadedAnvilChunkStorage; +import net.minecraft.world.chunk.WorldChunk; + +import dev.amble.lib.api.KitEvents; + +@Mixin(value = ThreadedAnvilChunkStorage.class, priority = 1001) +public abstract class ThreadedAnvilChunkStorageMixin { + + @Inject(method = "sendChunkDataPackets", at = @At("TAIL")) + public void sendChunkDataPackets(ServerPlayerEntity player, MutableObject cachedDataPacket, WorldChunk chunk, CallbackInfo ci) { + KitEvents.SYNC_ROOT.invoker().sync(player, chunk); + } +} diff --git a/src/main/resources/amblekit.mixins.json b/src/main/resources/amblekit.mixins.json index 13c268f..94d8969 100644 --- a/src/main/resources/amblekit.mixins.json +++ b/src/main/resources/amblekit.mixins.json @@ -6,8 +6,11 @@ "mixins": [ "AbstractBlockAccessor", "ItemMixin", + "MinecraftServerMixin", "SaveLoadingMixin", - "ServerPlayerInteractionManagerMixin" + "ServerPlayerInteractionManagerMixin", + "ServerWorldMixin", + "networking.ThreadedAnvilChunkStorageMixin" ], "client": [ "client.ClientPlayerInteractionManagerMixin" From 8adcbf612eceb53c15ab067a1ac925b93943b95f Mon Sep 17 00:00:00 2001 From: James Hall Date: Mon, 3 Mar 2025 22:45:12 +0000 Subject: [PATCH 2/4] chore: bring over/create properties v2 chore: setup KitTestMod --- src/main/java/dev/amble/lib/AmbleKit.java | 4 + .../dev/amble/lib/api/sync/RootComponent.java | 5 + .../api/sync/handler/KeyedSyncComponent.java | 45 ++++ .../manager/server/ServerSyncManager.java | 21 ++ .../lib/api/sync/properties/Property.java | 149 +++++++++++++ .../lib/api/sync/properties/PropertyMap.java | 22 ++ .../amble/lib/api/sync/properties/Value.java | 205 ++++++++++++++++++ .../sync/properties/bool/BoolProperty.java | 44 ++++ .../api/sync/properties/bool/BoolValue.java | 27 +++ .../sync/properties/dbl/DoubleProperty.java | 44 ++++ .../api/sync/properties/dbl/DoubleValue.java | 19 ++ .../sync/properties/flt/FloatProperty.java | 44 ++++ .../api/sync/properties/flt/FloatValue.java | 19 ++ .../sync/properties/integer/IntProperty.java | 42 ++++ .../api/sync/properties/integer/IntValue.java | 19 ++ .../integer/ranged/RangedIntProperty.java | 64 ++++++ .../integer/ranged/RangedIntValue.java | 27 +++ .../java/dev/amble/lib/test/KitTestMod.java | 20 ++ src/main/resources/fabric.mod.json | 3 +- 19 files changed, 822 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/Property.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/Value.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/bool/BoolValue.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleValue.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/flt/FloatValue.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/integer/IntValue.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java create mode 100644 src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntValue.java create mode 100644 src/main/java/dev/amble/lib/test/KitTestMod.java diff --git a/src/main/java/dev/amble/lib/AmbleKit.java b/src/main/java/dev/amble/lib/AmbleKit.java index 4a8cba3..d88c6ed 100644 --- a/src/main/java/dev/amble/lib/AmbleKit.java +++ b/src/main/java/dev/amble/lib/AmbleKit.java @@ -14,6 +14,7 @@ public class AmbleKit implements ModInitializer { public static final String MOD_ID = "amblekit"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + private static final boolean TESTS_ENABLED = true; @Override public void onInitialize() { @@ -27,4 +28,7 @@ public void onInitialize() { public static Identifier id(String path) { return new Identifier(MOD_ID, path); } + public static boolean isTestingEnabled() { + return FabricLoader.getInstance().isDevelopmentEnvironment() && TESTS_ENABLED; + } } \ No newline at end of file diff --git a/src/main/java/dev/amble/lib/api/sync/RootComponent.java b/src/main/java/dev/amble/lib/api/sync/RootComponent.java index 1ad2a39..b91438d 100644 --- a/src/main/java/dev/amble/lib/api/sync/RootComponent.java +++ b/src/main/java/dev/amble/lib/api/sync/RootComponent.java @@ -60,6 +60,11 @@ public ComponentManager getHandlers() { } public abstract SyncManager getSyncManager(); + @Override + public void dispose() { + this.getHandlers().dispose(); + } + @Override public void tick(MinecraftServer server) { this.getHandlers().tick(server); diff --git a/src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java b/src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java new file mode 100644 index 0000000..02e9d4c --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java @@ -0,0 +1,45 @@ +package dev.amble.lib.api.sync.handler; + +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.properties.PropertyMap; +import dev.amble.lib.api.sync.properties.Value; + +public abstract class KeyedSyncComponent extends SyncComponent { + @Exclude(strategy = Exclude.Strategy.FILE) + private PropertyMap data = new PropertyMap(); + + /** + * Do NOT under any circumstances run logic in this constructor. Default field + * values should be inlined. All logic should be done in an appropriate init + * method. + * + * @implNote The {@link SyncComponent#parent()} will always be null at the + * time this constructor gets called. + */ + public KeyedSyncComponent(IdLike id) { + super(id); + } + + @Override + protected void init(InitContext context) { + if (this.data == null) + this.data = new PropertyMap(); + + super.init(context); + } + + public void register(Value property) { + this.data.put(property.getProperty().getName(), property); + } + + @Override + public void dispose() { + super.dispose(); + + this.data.dispose(); + } + + public PropertyMap getPropertyData() { + return data; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java index c5436a6..0649700 100644 --- a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java +++ b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java @@ -7,6 +7,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; +import dev.amble.lib.api.sync.properties.Value; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; @@ -264,6 +265,26 @@ protected void sendAll(Set set) { ); } } + public void markComponentDirty(SyncComponent component) { + if (this.fileManager.isLocked()) + return; + + if (!(component.parent() instanceof RootComponent)) + return; + + @SuppressWarnings("unchecked") + T tardis = (T) component.parent(); + + if (isInvalid(tardis)) + return; + + tardis.data().markDirty(component); + this.delta.add(tardis); + } + + public void markPropertyDirty(T tardis, Value value) { + this.markComponentDirty(value.getHolder()); + } public abstract Set getSubscribedPlayers(T root); diff --git a/src/main/java/dev/amble/lib/api/sync/properties/Property.java b/src/main/java/dev/amble/lib/api/sync/properties/Property.java new file mode 100644 index 0000000..86182b7 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/Property.java @@ -0,0 +1,149 @@ +package dev.amble.lib.api.sync.properties; + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.data.CachedDirectedGlobalPos; +import dev.amble.lib.data.DirectedGlobalPos; +import net.minecraft.item.ItemStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.RegistryKeys; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import org.joml.Vector2i; + +import java.util.HashSet; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class Property { + + private final Type type; + private final String name; + + protected final Function def; + + public Property(Type type, String name, Function def) { + this.type = type; + this.name = name; + this.def = def; + } + + public Property(Type type, String name, T def) { + this(type, name, o -> def); + } + + public Value create(KeyedSyncComponent holder) { + T t = this.def == null ? null : this.def.apply(holder); + Value result = this.create(t); + + result.of(holder, this); + return result; + } + + protected Value create(T t) { + return new Value<>(t); + } + + public String getName() { + return name; + } + + public Type getType() { + return type; + } + + public Property copy(String name) { + return new Property<>(this.type, name, this.def); + } + + public Property copy(String name, T def) { + return new Property<>(this.type, name, def); + } + + public static > Property forEnum(String name, Class clazz, T def) { + return new Property<>(Type.forEnum(clazz), name, def); + } + + public static class Type { + + public static final Type DIRECTED_GLOBAL_POS = new Type<>(DirectedGlobalPos.class, + (buf, pos) -> pos.write(buf), DirectedGlobalPos::read); + + public static final Type BLOCK_POS = new Type<>(BlockPos.class, PacketByteBuf::writeBlockPos, + PacketByteBuf::readBlockPos); + public static final Type CDIRECTED_GLOBAL_POS = new Type<>( + CachedDirectedGlobalPos.class, (buf, pos) -> pos.write(buf), CachedDirectedGlobalPos::read); + public static final Type IDENTIFIER = new Type<>(Identifier.class, PacketByteBuf::writeIdentifier, + PacketByteBuf::readIdentifier); + + public static final Type> WORLD_KEY = new Type<>(RegistryKey.class, + PacketByteBuf::writeRegistryKey, buf -> buf.readRegistryKey(RegistryKeys.WORLD)); + public static final Type DIRECTION = Type.forEnum(Direction.class); + + public static final Type VEC2I = new Type<>(Vector2i.class, (buf, vector2i) -> { + buf.writeInt(vector2i.x); + buf.writeInt(vector2i.y); + }, buf -> new Vector2i(buf.readInt(), buf.readInt())); + + public static final Type STR = new Type<>(String.class, PacketByteBuf::writeString, + PacketByteBuf::readString); + + public static final Type UUID = new Type<>(UUID.class, PacketByteBuf::writeUuid, PacketByteBuf::readUuid); + + public static final Type DOUBLE = new Type<>(Double.class, PacketByteBuf::writeDouble, + PacketByteBuf::readDouble); + + public static final Type> STR_SET = new Type<>(HashSet.class, (buf, strings) -> { + buf.writeVarInt(strings.size()); + + for (String str : strings) + buf.writeString(str); + }, buf -> { + HashSet result = new HashSet<>(); + int size = buf.readVarInt(); + + for (int i = 0; i < size; i++) { + result.add(buf.readString()); + } + + return result; + }); + + public static final Type ITEM_STACK = new Type<>(ItemStack.class, PacketByteBuf::writeItemStack, + PacketByteBuf::readItemStack); + + private final Class clazz; + private final BiConsumer encoder; + private final Function decoder; + + public Type(Class clazz, BiConsumer encoder, Function decoder) { + this.clazz = clazz; + this.encoder = encoder; + this.decoder = decoder; + } + + public void encode(PacketByteBuf buf, T value) { + this.encoder.accept(buf, value); + } + + public T decode(PacketByteBuf buf) { + return this.decoder.apply(buf); + } + + public Class getClazz() { + return clazz; + } + + public static > Type forEnum(Class clazz) { + return new Type<>(clazz, PacketByteBuf::writeEnumConstant, buf -> buf.readEnumConstant(clazz)); + } + } + + public interface Mode { + byte UPDATE = 0; + byte NULL = 1; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java b/src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java new file mode 100644 index 0000000..4922862 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java @@ -0,0 +1,22 @@ +package dev.amble.lib.api.sync.properties; + +import dev.amble.lib.api.sync.Disposable; + +import java.util.HashMap; + +public class PropertyMap extends HashMap> implements Disposable { + + @SuppressWarnings("unchecked") + public Value getExact(String key) { + return (Value) this.get(key); + } + + @Override + public void dispose() { + for (Value value : this.values()) { + value.dispose(); + } + + this.clear(); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/Value.java b/src/main/java/dev/amble/lib/api/sync/properties/Value.java new file mode 100644 index 0000000..a27de2d --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/Value.java @@ -0,0 +1,205 @@ +package dev.amble.lib.api.sync.properties; + +import com.google.gson.*; +import dev.amble.lib.api.sync.Disposable; +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerRootComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.minecraft.network.PacketByteBuf; +import org.apache.commons.lang3.NotImplementedException; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +public class Value implements Disposable { + + /** + * Due to a circular-dependency between a component and a property, it should be + * excluded. + */ + @Exclude + private SyncComponent holder; + + @Exclude + protected Property property; + + @Exclude + private List> listeners; + + private T value; + private ServerSyncManager manager; + + protected Value(T value) { + this.value = value; + this.listeners = new ArrayList<>(); + } + + public void of(KeyedSyncComponent holder, Property property) { + this.holder = holder; + this.property = property; + + holder.register(this); + } + + public void addListener(Consumer listener) { + if (this.listeners == null) + this.listeners = new ArrayList<>(); + + this.listeners.add(listener); + } + + public Property getProperty() { + return property; + } + + public SyncComponent getHolder() { + return holder; + } + + public T get() { + return value; + } + + public void set(Value value) { + this.set(value.get(), true); + } + + public void set(T value) { + this.set(value, true); + } + + public void set(T value, boolean sync) { + if (this.value == value) + return; + + this.value = value; + + if (this.listeners != null) { + for (Consumer listener : this.listeners) + listener.accept(value); + } + + if (sync) + this.sync(); + } + + public Value with(ServerSyncManager manager) { + this.manager = manager; + return this; + } + + protected void sync() { + /* + if (this.holder == null || !(this.holder.tardis() instanceof ServerTardis tardis)) { + this.syncToServer(); + return; + }*/ + + // ServerTardisManager.getInstance().markPropertyDirty(tardis, this); + if (this.manager == null) { + throw new NotImplementedException("Manager is not set! Make sure to call with() method before calling sync()"); + } + + if (this.holder == null || !(this.holder.parent() instanceof ServerRootComponent)) { + return; + } + + this.manager.markPropertyDirty(this.holder.parent(), this); + } + /* + @Environment(EnvType.CLIENT) + protected void syncToServer() { // todo - flags + ClientPlayNetworking.send(new SyncPropertyC2SPacket(this.holder.tardis().getUuid(), this)); + } + */ + + public void flatMap(Function func) { + this.set(func.apply(this.value), true); + } + + public void flatMap(Function func, boolean sync) { + this.set(func.apply(this.value), sync); + } + + public void ifPresent(Consumer consumer) { + this.ifPresent(consumer, true); + } + + public void ifPresent(Consumer consumer, boolean sync) { + if (this.value == null) + return; + + consumer.accept(this.value); + + if (sync) + this.sync(); + } + + public void read(PacketByteBuf buf, byte mode) { + if (this.property == null) + throw new IllegalStateException( + "Couldn't get the parent property value! Maybe you forgot to initialize the value field on load?"); + + T value = mode == Property.Mode.UPDATE ? this.property.getType().decode(buf) : null; + + this.set(value, false); + } + + public void write(PacketByteBuf buf) { + this.property.getType().encode(buf, this.value); + } + + @Override + public void dispose() { + this.holder = null; + } + + public static Object serializer() { + return new Serializer<>(Value::new); + } + + protected static class Serializer> implements JsonSerializer, JsonDeserializer { + + private final Class clazz; + private final Function creator; + + public Serializer(Property.Type type, Function creator) { + this(type.getClazz(), creator); + } + + public Serializer(Class clazz, Function creator) { + this.clazz = clazz; + this.creator = creator; + } + + protected Serializer(Function creator) { + this((Class) null, creator); + } + + @Override + public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + Type type = clazz; + + if (clazz == null && typeOfT instanceof ParameterizedType parameter) + type = parameter.getActualTypeArguments()[0]; + + return this.creator.apply(context.deserialize(json, type)); + } + + @Override + public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(src.get()); + } + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java new file mode 100644 index 0000000..250f858 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java @@ -0,0 +1,44 @@ +package dev.amble.lib.api.sync.properties.bool; + + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.properties.Property; +import net.minecraft.network.PacketByteBuf; + +import java.util.function.Function; + +public class BoolProperty extends Property { + + public static final Type TYPE = new Type<>(Boolean.class, PacketByteBuf::writeBoolean, + PacketByteBuf::readBoolean); + + public BoolProperty(String name) { + this(name, false); + } + + public BoolProperty(String name, Boolean def) { + this(name, normalize(def)); + } + + public BoolProperty(String name, boolean def) { + super(TYPE, name, def); + } + + public BoolProperty(String name, Function def) { + super(TYPE, name, def.andThen(BoolProperty::normalize)); + } + + @Override + protected BoolValue create(Boolean bool) { + return new BoolValue(bool); + } + + @Override + public BoolValue create(KeyedSyncComponent holder) { + return (BoolValue) super.create(holder); + } + + public static boolean normalize(Boolean value) { + return value != null && value; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolValue.java b/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolValue.java new file mode 100644 index 0000000..2913d2c --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolValue.java @@ -0,0 +1,27 @@ +package dev.amble.lib.api.sync.properties.bool; + +import dev.amble.lib.api.sync.properties.Value; + +public class BoolValue extends Value { + + protected BoolValue(Boolean value) { + super(value); + } + + @Override + public void set(Boolean value, boolean sync) { + super.set(BoolProperty.normalize(value), sync); + } + + public void toggle() { + this.flatMap(b -> !b); + } + + public void toggle(boolean sync) { + this.flatMap(b -> !b, sync); + } + + public static Object serializer() { + return new Serializer<>(BoolProperty.TYPE, BoolValue::new); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java new file mode 100644 index 0000000..ed3a965 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java @@ -0,0 +1,44 @@ +package dev.amble.lib.api.sync.properties.dbl; + + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.properties.Property; +import net.minecraft.network.PacketByteBuf; + +import java.util.function.Function; + +public class DoubleProperty extends Property { + + public static final Type TYPE = new Type<>(Double.class, PacketByteBuf::writeDouble, + PacketByteBuf::readDouble); + + public DoubleProperty(String name) { + this(name, 0); + } + + public DoubleProperty(String name, Double def) { + this(name, normalize(def)); + } + + public DoubleProperty(String name, double def) { + super(TYPE, name, def); + } + + public DoubleProperty(String name, Function def) { + super(TYPE, name, def.andThen(DoubleProperty::normalize)); + } + + @Override + public DoubleValue create(KeyedSyncComponent holder) { + return (DoubleValue) super.create(holder); + } + + @Override + protected DoubleValue create(Double integer) { + return new DoubleValue(integer); + } + + public static double normalize(Double value) { + return value == null ? 0 : value; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleValue.java b/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleValue.java new file mode 100644 index 0000000..a4bbeaa --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleValue.java @@ -0,0 +1,19 @@ +package dev.amble.lib.api.sync.properties.dbl; + +import dev.amble.lib.api.sync.properties.Value; + +public class DoubleValue extends Value { + + protected DoubleValue(Double value) { + super(value); + } + + @Override + public void set(Double value, boolean sync) { + super.set(DoubleProperty.normalize(value), sync); + } + + public static Object serializer() { + return new Serializer<>(DoubleProperty.TYPE, DoubleValue::new); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java new file mode 100644 index 0000000..9aa53a9 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java @@ -0,0 +1,44 @@ +package dev.amble.lib.api.sync.properties.flt; + + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.properties.Property; +import net.minecraft.network.PacketByteBuf; + +import java.util.function.Function; + +public class FloatProperty extends Property { + + public static final Type TYPE = new Type<>(Float.class, PacketByteBuf::writeFloat, + PacketByteBuf::readFloat); + + public FloatProperty(String name) { + this(name, 0); + } + + public FloatProperty(String name, Float def) { + this(name, normalize(def)); + } + + public FloatProperty(String name, float def) { + super(TYPE, name, def); + } + + public FloatProperty(String name, Function def) { + super(TYPE, name, def.andThen(FloatProperty::normalize)); + } + + @Override + public FloatValue create(KeyedSyncComponent holder) { + return (FloatValue) super.create(holder); + } + + @Override + protected FloatValue create(Float flt) { + return new FloatValue(flt); + } + + public static float normalize(Float value) { + return value == null ? 0 : value; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatValue.java b/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatValue.java new file mode 100644 index 0000000..df03a93 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatValue.java @@ -0,0 +1,19 @@ +package dev.amble.lib.api.sync.properties.flt; + +import dev.amble.lib.api.sync.properties.Value; + +public class FloatValue extends Value { + + protected FloatValue(Float value) { + super(value); + } + + @Override + public void set(Float value, boolean sync) { + super.set(FloatProperty.normalize(value), sync); + } + + public static Object serializer() { + return new Serializer<>(FloatProperty.TYPE, FloatValue::new); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java new file mode 100644 index 0000000..ce8db06 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java @@ -0,0 +1,42 @@ +package dev.amble.lib.api.sync.properties.integer; + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.properties.Property; +import net.minecraft.network.PacketByteBuf; + +import java.util.function.Function; + +public class IntProperty extends Property { + + public static final Type TYPE = new Type<>(Integer.class, PacketByteBuf::writeInt, PacketByteBuf::readInt); + + public IntProperty(String name) { + this(name, 0); + } + + public IntProperty(String name, Integer def) { + this(name, normalize(def)); + } + + public IntProperty(String name, int def) { + super(TYPE, name, def); + } + + public IntProperty(String name, Function def) { + super(TYPE, name, def.andThen(IntProperty::normalize)); + } + + @Override + public IntValue create(KeyedSyncComponent holder) { + return (IntValue) super.create(holder); + } + + @Override + protected IntValue create(Integer integer) { + return new IntValue(integer); + } + + public static int normalize(Integer value) { + return value == null ? 0 : value; + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/integer/IntValue.java b/src/main/java/dev/amble/lib/api/sync/properties/integer/IntValue.java new file mode 100644 index 0000000..ebcbe87 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/integer/IntValue.java @@ -0,0 +1,19 @@ +package dev.amble.lib.api.sync.properties.integer; + +import dev.amble.lib.api.sync.properties.Value; + +public class IntValue extends Value { + + protected IntValue(Integer value) { + super(value); + } + + @Override + public void set(Integer value, boolean sync) { + super.set(IntProperty.normalize(value), sync); + } + + public static Object serializer() { + return new Serializer<>(IntProperty.TYPE, IntValue::new); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java new file mode 100644 index 0000000..cc98667 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java @@ -0,0 +1,64 @@ +package dev.amble.lib.api.sync.properties.integer.ranged; + + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.properties.Property; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.math.MathHelper; + +import java.util.function.Function; + +public class RangedIntProperty extends Property { + + public static final Type TYPE = new Type<>(Integer.class, PacketByteBuf::writeInt, PacketByteBuf::readInt); + + private final int min; + private final int max; + + public RangedIntProperty(String name, int max) { + this(name, 0, max, 0); + } + + public RangedIntProperty(String name, int max, Integer def) { + this(name, 0, max, normalize(0, max, def)); + } + + public RangedIntProperty(String name, int min, int max, Integer def) { + super(TYPE, name, normalize(min, max, def)); + + this.min = min; + this.max = max; + } + + public RangedIntProperty(String name, int max, int def) { + super(TYPE, name, def); + + this.min = 0; + this.max = max; + } + + public RangedIntProperty(String name, int min, int max, Function def) { + super(TYPE, name, def.andThen(i -> RangedIntProperty.normalize(min, max, i))); + + this.min = min; + this.max = max; + } + + @Override + public RangedIntValue create(KeyedSyncComponent holder) { + return (RangedIntValue) super.create(holder); + } + + @Override + protected RangedIntValue create(Integer integer) { + return new RangedIntValue(integer); + } + + public static int normalize(int min, int max, Integer value) { + return MathHelper.clamp(value == null ? 0 : value, min, max); + } + + public static int normalize(RangedIntProperty property, Integer value) { + return normalize(property.min, property.max, value); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntValue.java b/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntValue.java new file mode 100644 index 0000000..55808d8 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntValue.java @@ -0,0 +1,27 @@ +package dev.amble.lib.api.sync.properties.integer.ranged; + +import dev.amble.lib.api.sync.properties.Value; + +public class RangedIntValue extends Value { + + protected RangedIntValue(int value) { + super(value); + } + + public static RangedIntValue of(int value) { + return new RangedIntValue(value); + } + + @Override + public void set(Integer value, boolean sync) { + super.set(RangedIntProperty.normalize(this.asRanged(), value), sync); + } + + private RangedIntProperty asRanged() { + return (RangedIntProperty) this.property; + } + + public static Object serializer() { + return new Serializer<>(RangedIntProperty.TYPE, RangedIntValue::new); + } +} diff --git a/src/main/java/dev/amble/lib/test/KitTestMod.java b/src/main/java/dev/amble/lib/test/KitTestMod.java new file mode 100644 index 0000000..9134ce2 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/KitTestMod.java @@ -0,0 +1,20 @@ +package dev.amble.lib.test; + +import dev.amble.lib.AmbleKit; +import net.fabricmc.api.ModInitializer; +import org.jetbrains.annotations.TestOnly; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@TestOnly +public class KitTestMod implements ModInitializer { + public static final String MOD_ID = "amblekit-test"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + @Override + public void onInitialize() { + if (!(AmbleKit.isTestingEnabled())) return; + + LOGGER.info("AmbleKit Tests Enabled!"); + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 0b68ed9..e96bfc3 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -17,7 +17,8 @@ "environment": "*", "entrypoints": { "main": [ - "dev.amble.lib.AmbleKit" + "dev.amble.lib.AmbleKit", + "dev.amble.lib.test.KitTestMod" ], "client": [ "dev.amble.lib.client.AmbleKitClient" From 4140165cee1d9275a518a155d2c4f7da8bcfae1a Mon Sep 17 00:00:00 2001 From: James Hall Date: Mon, 3 Mar 2025 23:16:00 +0000 Subject: [PATCH 3/4] chore: sync testing setup api(properties): require ServerSyncManager fmt --- .../dev/amble/lib/api/sync/RootComponent.java | 2 +- .../api/sync/handler/ComponentManager.java | 4 + .../api/sync/handler/KeyedSyncComponent.java | 74 +++++++++---------- .../lib/api/sync/manager/SyncManager.java | 4 +- .../manager/server/ServerSyncManager.java | 32 ++++---- .../lib/api/sync/properties/Property.java | 37 ++++++---- .../lib/api/sync/properties/PropertyMap.java | 4 +- .../amble/lib/api/sync/properties/Value.java | 24 +++--- .../sync/properties/bool/BoolProperty.java | 24 +++--- .../sync/properties/dbl/DoubleProperty.java | 24 +++--- .../sync/properties/flt/FloatProperty.java | 24 +++--- .../sync/properties/integer/IntProperty.java | 24 +++--- .../integer/ranged/RangedIntProperty.java | 28 +++---- .../amble/lib/data/gson/NbtSerializer.java | 3 +- .../java/dev/amble/lib/test/KitTestMod.java | 30 ++++++-- .../lib/test/client/KitTestModClient.java | 22 ++++++ .../dev/amble/lib/test/sync/ExampleRoot.java | 21 ++++++ .../test/sync/client/ExampleClientRoot.java | 30 ++++++++ .../sync/client/ExampleClientSyncManager.java | 44 +++++++++++ .../handler/ExampleComponentRegistry.java | 64 ++++++++++++++++ .../sync/handler/FirstExampleComponent.java | 29 ++++++++ .../sync/handler/SecondExampleComponent.java | 30 ++++++++ .../test/sync/server/ExampleServerRoot.java | 30 ++++++++ .../sync/server/ExampleServerSyncManager.java | 56 ++++++++++++++ src/main/resources/fabric.mod.json | 3 +- 25 files changed, 514 insertions(+), 153 deletions(-) create mode 100644 src/main/java/dev/amble/lib/test/client/KitTestModClient.java create mode 100644 src/main/java/dev/amble/lib/test/sync/ExampleRoot.java create mode 100644 src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java create mode 100644 src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java create mode 100644 src/main/java/dev/amble/lib/test/sync/handler/ExampleComponentRegistry.java create mode 100644 src/main/java/dev/amble/lib/test/sync/handler/FirstExampleComponent.java create mode 100644 src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java create mode 100644 src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java create mode 100644 src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java diff --git a/src/main/java/dev/amble/lib/api/sync/RootComponent.java b/src/main/java/dev/amble/lib/api/sync/RootComponent.java index b91438d..f05b889 100644 --- a/src/main/java/dev/amble/lib/api/sync/RootComponent.java +++ b/src/main/java/dev/amble/lib/api/sync/RootComponent.java @@ -20,7 +20,7 @@ public abstract class RootComponent extends Initializable(registry::lookup, size -> (SyncComponent[]) Array.newInstance(SyncComponent.class, size)); } + public ComponentManager(SyncManager manager) { + this(manager.getHandlersId(), manager.getRegistry()); + } @Override public void onCreate() { diff --git a/src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java b/src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java index 02e9d4c..193fc85 100644 --- a/src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java +++ b/src/main/java/dev/amble/lib/api/sync/handler/KeyedSyncComponent.java @@ -5,41 +5,41 @@ import dev.amble.lib.api.sync.properties.Value; public abstract class KeyedSyncComponent extends SyncComponent { - @Exclude(strategy = Exclude.Strategy.FILE) - private PropertyMap data = new PropertyMap(); - - /** - * Do NOT under any circumstances run logic in this constructor. Default field - * values should be inlined. All logic should be done in an appropriate init - * method. - * - * @implNote The {@link SyncComponent#parent()} will always be null at the - * time this constructor gets called. - */ - public KeyedSyncComponent(IdLike id) { - super(id); - } - - @Override - protected void init(InitContext context) { - if (this.data == null) - this.data = new PropertyMap(); - - super.init(context); - } - - public void register(Value property) { - this.data.put(property.getProperty().getName(), property); - } - - @Override - public void dispose() { - super.dispose(); - - this.data.dispose(); - } - - public PropertyMap getPropertyData() { - return data; - } + @Exclude(strategy = Exclude.Strategy.FILE) + private PropertyMap data = new PropertyMap(); + + /** + * Do NOT under any circumstances run logic in this constructor. Default field + * values should be inlined. All logic should be done in an appropriate init + * method. + * + * @implNote The {@link SyncComponent#parent()} will always be null at the + * time this constructor gets called. + */ + public KeyedSyncComponent(IdLike id) { + super(id); + } + + @Override + protected void init(InitContext context) { + if (this.data == null) + this.data = new PropertyMap(); + + super.init(context); + } + + public void register(Value property) { + this.data.put(property.getProperty().getName(), property); + } + + @Override + public void dispose() { + super.dispose(); + + this.data.dispose(); + } + + public PropertyMap getPropertyData() { + return data; + } } diff --git a/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java index 0c491fe..cda145e 100644 --- a/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java +++ b/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java @@ -65,7 +65,7 @@ public boolean shouldSkipClass(Class clazz) { .registerTypeAdapter(GlobalPos.class, new GlobalPosSerializer()) .registerTypeAdapter(BlockPos.class, new BlockPosSerializer()) .registerTypeAdapter(RegistryKey.class, new RegistryKeySerializer()) - .registerTypeAdapter(ComponentManager.class, ComponentManager.serializer(this.getRegistry(), this.getManagerId())) + .registerTypeAdapter(ComponentManager.class, ComponentManager.serializer(this.getRegistry(), this.getHandlersId())) .registerTypeAdapter(SyncComponent.IdLike.class, getRegistry().idSerializer()); } @@ -172,7 +172,7 @@ public Identifier createPacket(String id) { } public abstract ComponentRegistry getRegistry(); - public abstract SyncComponent.IdLike getManagerId(); + public abstract SyncComponent.IdLike getHandlersId(); public abstract String modId(); public abstract String name(); } diff --git a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java index 0649700..9500907 100644 --- a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java +++ b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java @@ -7,7 +7,6 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; -import dev.amble.lib.api.sync.properties.Value; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; @@ -22,6 +21,7 @@ import dev.amble.lib.api.sync.RootComponent; import dev.amble.lib.api.sync.handler.SyncComponent; import dev.amble.lib.api.sync.manager.SyncManager; +import dev.amble.lib.api.sync.properties.Value; import dev.amble.lib.events.ServerCrashEvent; import dev.amble.lib.events.WorldSaveEvent; @@ -265,26 +265,26 @@ protected void sendAll(Set set) { ); } } - public void markComponentDirty(SyncComponent component) { - if (this.fileManager.isLocked()) - return; + public void markComponentDirty(SyncComponent component) { + if (this.fileManager.isLocked()) + return; - if (!(component.parent() instanceof RootComponent)) - return; + if (!(component.parent() instanceof RootComponent)) + return; - @SuppressWarnings("unchecked") - T tardis = (T) component.parent(); + @SuppressWarnings("unchecked") + T tardis = (T) component.parent(); - if (isInvalid(tardis)) - return; + if (isInvalid(tardis)) + return; - tardis.data().markDirty(component); - this.delta.add(tardis); - } + tardis.data().markDirty(component); + this.delta.add(tardis); + } - public void markPropertyDirty(T tardis, Value value) { - this.markComponentDirty(value.getHolder()); - } + public void markPropertyDirty(T tardis, Value value) { + this.markComponentDirty(value.getHolder()); + } public abstract Set getSubscribedPlayers(T root); diff --git a/src/main/java/dev/amble/lib/api/sync/properties/Property.java b/src/main/java/dev/amble/lib/api/sync/properties/Property.java index 86182b7..cf9d917 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/Property.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/Property.java @@ -1,8 +1,12 @@ package dev.amble.lib.api.sync.properties; -import dev.amble.lib.api.sync.handler.KeyedSyncComponent; -import dev.amble.lib.data.CachedDirectedGlobalPos; -import dev.amble.lib.data.DirectedGlobalPos; +import java.util.HashSet; +import java.util.UUID; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.joml.Vector2i; + import net.minecraft.item.ItemStack; import net.minecraft.network.PacketByteBuf; import net.minecraft.registry.RegistryKey; @@ -11,28 +15,29 @@ import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.world.World; -import org.joml.Vector2i; -import java.util.HashSet; -import java.util.UUID; -import java.util.function.BiConsumer; -import java.util.function.Function; +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.data.CachedDirectedGlobalPos; +import dev.amble.lib.data.DirectedGlobalPos; public class Property { private final Type type; private final String name; + private final ServerSyncManager manager; protected final Function def; - public Property(Type type, String name, Function def) { + public Property(Type type, String name, Function def, ServerSyncManager manager) { this.type = type; this.name = name; this.def = def; + this.manager = manager; } - public Property(Type type, String name, T def) { - this(type, name, o -> def); + public Property(Type type, String name, T def, ServerSyncManager manager) { + this(type, name, o -> def, manager); } public Value create(KeyedSyncComponent holder) { @@ -40,6 +45,8 @@ public Value create(KeyedSyncComponent holder) { Value result = this.create(t); result.of(holder, this); + result.with(manager); + return result; } @@ -56,15 +63,15 @@ public Type getType() { } public Property copy(String name) { - return new Property<>(this.type, name, this.def); + return new Property<>(this.type, name, this.def, this.manager); } public Property copy(String name, T def) { - return new Property<>(this.type, name, def); + return new Property<>(this.type, name, def, this.manager); } - public static > Property forEnum(String name, Class clazz, T def) { - return new Property<>(Type.forEnum(clazz), name, def); + public static > Property forEnum(String name, Class clazz, T def, ServerSyncManager manager) { + return new Property<>(Type.forEnum(clazz), name, def, manager); } public static class Type { diff --git a/src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java b/src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java index 4922862..01d5c79 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/PropertyMap.java @@ -1,9 +1,9 @@ package dev.amble.lib.api.sync.properties; -import dev.amble.lib.api.sync.Disposable; - import java.util.HashMap; +import dev.amble.lib.api.sync.Disposable; + public class PropertyMap extends HashMap> implements Disposable { @SuppressWarnings("unchecked") diff --git a/src/main/java/dev/amble/lib/api/sync/properties/Value.java b/src/main/java/dev/amble/lib/api/sync/properties/Value.java index a27de2d..dea26b2 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/Value.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/Value.java @@ -1,25 +1,23 @@ package dev.amble.lib.api.sync.properties; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + import com.google.gson.*; +import org.apache.commons.lang3.NotImplementedException; + +import net.minecraft.network.PacketByteBuf; + import dev.amble.lib.api.sync.Disposable; import dev.amble.lib.api.sync.Exclude; -import dev.amble.lib.api.sync.RootComponent; import dev.amble.lib.api.sync.handler.KeyedSyncComponent; import dev.amble.lib.api.sync.handler.SyncComponent; import dev.amble.lib.api.sync.manager.server.ServerRootComponent; import dev.amble.lib.api.sync.manager.server.ServerSyncManager; -import net.fabricmc.api.EnvType; -import net.fabricmc.api.Environment; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; -import net.minecraft.network.PacketByteBuf; -import org.apache.commons.lang3.NotImplementedException; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; public class Value implements Disposable { diff --git a/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java index 250f858..57600f0 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/bool/BoolProperty.java @@ -1,31 +1,33 @@ package dev.amble.lib.api.sync.properties.bool; -import dev.amble.lib.api.sync.handler.KeyedSyncComponent; -import dev.amble.lib.api.sync.properties.Property; +import java.util.function.Function; + import net.minecraft.network.PacketByteBuf; -import java.util.function.Function; +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.api.sync.properties.Property; public class BoolProperty extends Property { public static final Type TYPE = new Type<>(Boolean.class, PacketByteBuf::writeBoolean, PacketByteBuf::readBoolean); - public BoolProperty(String name) { - this(name, false); + public BoolProperty(String name, ServerSyncManager manager) { + this(name, false, manager); } - public BoolProperty(String name, Boolean def) { - this(name, normalize(def)); + public BoolProperty(String name, Boolean def, ServerSyncManager manager) { + this(name, normalize(def), manager); } - public BoolProperty(String name, boolean def) { - super(TYPE, name, def); + public BoolProperty(String name, boolean def, ServerSyncManager manager) { + super(TYPE, name, def, manager ); } - public BoolProperty(String name, Function def) { - super(TYPE, name, def.andThen(BoolProperty::normalize)); + public BoolProperty(String name, Function def, ServerSyncManager manager) { + super(TYPE, name, def.andThen(BoolProperty::normalize), manager); } @Override diff --git a/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java index ed3a965..8eed4a7 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/dbl/DoubleProperty.java @@ -1,31 +1,33 @@ package dev.amble.lib.api.sync.properties.dbl; -import dev.amble.lib.api.sync.handler.KeyedSyncComponent; -import dev.amble.lib.api.sync.properties.Property; +import java.util.function.Function; + import net.minecraft.network.PacketByteBuf; -import java.util.function.Function; +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.api.sync.properties.Property; public class DoubleProperty extends Property { public static final Type TYPE = new Type<>(Double.class, PacketByteBuf::writeDouble, PacketByteBuf::readDouble); - public DoubleProperty(String name) { - this(name, 0); + public DoubleProperty(String name, ServerSyncManager manager) { + this(name, 0, manager); } - public DoubleProperty(String name, Double def) { - this(name, normalize(def)); + public DoubleProperty(String name, Double def, ServerSyncManager manager) { + this(name, normalize(def), manager); } - public DoubleProperty(String name, double def) { - super(TYPE, name, def); + public DoubleProperty(String name, double def, ServerSyncManager manager) { + super(TYPE, name, def, manager); } - public DoubleProperty(String name, Function def) { - super(TYPE, name, def.andThen(DoubleProperty::normalize)); + public DoubleProperty(String name, Function def, ServerSyncManager manager) { + super(TYPE, name, def.andThen(DoubleProperty::normalize), manager); } @Override diff --git a/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java index 9aa53a9..1764738 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/flt/FloatProperty.java @@ -1,31 +1,33 @@ package dev.amble.lib.api.sync.properties.flt; -import dev.amble.lib.api.sync.handler.KeyedSyncComponent; -import dev.amble.lib.api.sync.properties.Property; +import java.util.function.Function; + import net.minecraft.network.PacketByteBuf; -import java.util.function.Function; +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.api.sync.properties.Property; public class FloatProperty extends Property { public static final Type TYPE = new Type<>(Float.class, PacketByteBuf::writeFloat, PacketByteBuf::readFloat); - public FloatProperty(String name) { - this(name, 0); + public FloatProperty(String name, ServerSyncManager manager) { + this(name, 0, manager); } - public FloatProperty(String name, Float def) { - this(name, normalize(def)); + public FloatProperty(String name, Float def, ServerSyncManager manager) { + this(name, normalize(def), manager); } - public FloatProperty(String name, float def) { - super(TYPE, name, def); + public FloatProperty(String name, float def, ServerSyncManager manager) { + super(TYPE, name, def, manager); } - public FloatProperty(String name, Function def) { - super(TYPE, name, def.andThen(FloatProperty::normalize)); + public FloatProperty(String name, Function def, ServerSyncManager manager) { + super(TYPE, name, def.andThen(FloatProperty::normalize), manager); } @Override diff --git a/src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java index ce8db06..adff784 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/integer/IntProperty.java @@ -1,29 +1,31 @@ package dev.amble.lib.api.sync.properties.integer; -import dev.amble.lib.api.sync.handler.KeyedSyncComponent; -import dev.amble.lib.api.sync.properties.Property; +import java.util.function.Function; + import net.minecraft.network.PacketByteBuf; -import java.util.function.Function; +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.api.sync.properties.Property; public class IntProperty extends Property { public static final Type TYPE = new Type<>(Integer.class, PacketByteBuf::writeInt, PacketByteBuf::readInt); - public IntProperty(String name) { - this(name, 0); + public IntProperty(String name, ServerSyncManager manager) { + this(name, 0, manager); } - public IntProperty(String name, Integer def) { - this(name, normalize(def)); + public IntProperty(String name, Integer def, ServerSyncManager manager) { + this(name, normalize(def), manager); } - public IntProperty(String name, int def) { - super(TYPE, name, def); + public IntProperty(String name, int def, ServerSyncManager manager) { + super(TYPE, name, def, manager); } - public IntProperty(String name, Function def) { - super(TYPE, name, def.andThen(IntProperty::normalize)); + public IntProperty(String name, Function def, ServerSyncManager manager) { + super(TYPE, name, def.andThen(IntProperty::normalize), manager); } @Override diff --git a/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java b/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java index cc98667..8c6c2cf 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/integer/ranged/RangedIntProperty.java @@ -1,12 +1,14 @@ package dev.amble.lib.api.sync.properties.integer.ranged; -import dev.amble.lib.api.sync.handler.KeyedSyncComponent; -import dev.amble.lib.api.sync.properties.Property; +import java.util.function.Function; + import net.minecraft.network.PacketByteBuf; import net.minecraft.util.math.MathHelper; -import java.util.function.Function; +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.api.sync.properties.Property; public class RangedIntProperty extends Property { @@ -15,30 +17,30 @@ public class RangedIntProperty extends Property { private final int min; private final int max; - public RangedIntProperty(String name, int max) { - this(name, 0, max, 0); + public RangedIntProperty(String name, int max, ServerSyncManager manager) { + this(name, 0, max, 0, manager); } - public RangedIntProperty(String name, int max, Integer def) { - this(name, 0, max, normalize(0, max, def)); + public RangedIntProperty(String name, int max, Integer def, ServerSyncManager manager) { + this(name, 0, max, normalize(0, max, def), manager); } - public RangedIntProperty(String name, int min, int max, Integer def) { - super(TYPE, name, normalize(min, max, def)); + public RangedIntProperty(String name, int min, int max, Integer def, ServerSyncManager manager) { + super(TYPE, name, normalize(min, max, def), manager); this.min = min; this.max = max; } - public RangedIntProperty(String name, int max, int def) { - super(TYPE, name, def); + public RangedIntProperty(String name, int max, int def, ServerSyncManager manager) { + super(TYPE, name, def, manager); this.min = 0; this.max = max; } - public RangedIntProperty(String name, int min, int max, Function def) { - super(TYPE, name, def.andThen(i -> RangedIntProperty.normalize(min, max, i))); + public RangedIntProperty(String name, int min, int max, Function def, ServerSyncManager manager) { + super(TYPE, name, def.andThen(i -> RangedIntProperty.normalize(min, max, i)), manager); this.min = min; this.max = max; diff --git a/src/main/java/dev/amble/lib/data/gson/NbtSerializer.java b/src/main/java/dev/amble/lib/data/gson/NbtSerializer.java index 0af445b..4d189e6 100644 --- a/src/main/java/dev/amble/lib/data/gson/NbtSerializer.java +++ b/src/main/java/dev/amble/lib/data/gson/NbtSerializer.java @@ -5,11 +5,12 @@ import com.google.gson.*; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import dev.amble.lib.AmbleKit; import net.minecraft.nbt.NbtCompound; import net.minecraft.nbt.StringNbtReader; import net.minecraft.nbt.visitor.StringNbtWriter; +import dev.amble.lib.AmbleKit; + public class NbtSerializer implements JsonSerializer, JsonDeserializer { @Override public NbtCompound deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) diff --git a/src/main/java/dev/amble/lib/test/KitTestMod.java b/src/main/java/dev/amble/lib/test/KitTestMod.java index 9134ce2..aac2e2b 100644 --- a/src/main/java/dev/amble/lib/test/KitTestMod.java +++ b/src/main/java/dev/amble/lib/test/KitTestMod.java @@ -1,20 +1,34 @@ package dev.amble.lib.test; -import dev.amble.lib.AmbleKit; import net.fabricmc.api.ModInitializer; import org.jetbrains.annotations.TestOnly; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import dev.amble.lib.AmbleKit; +import dev.amble.lib.test.sync.server.ExampleServerSyncManager; + @TestOnly public class KitTestMod implements ModInitializer { - public static final String MOD_ID = "amblekit-test"; - public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static final String MOD_ID = "amblekit-test"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + public static final boolean TEST_SYNCING = true; + + @Override + public void onInitialize() { + if (!(AmbleKit.isTestingEnabled())) return; + + LOGGER.info("AmbleKit Tests Enabled!"); + + if (TEST_SYNCING) { + initSyncing(); + } + } - @Override - public void onInitialize() { - if (!(AmbleKit.isTestingEnabled())) return; + private static void initSyncing() { + LOGGER.info("Syncing Tests Enabled!"); - LOGGER.info("AmbleKit Tests Enabled!"); - } + ExampleServerSyncManager.init(); + } } diff --git a/src/main/java/dev/amble/lib/test/client/KitTestModClient.java b/src/main/java/dev/amble/lib/test/client/KitTestModClient.java new file mode 100644 index 0000000..8297180 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/client/KitTestModClient.java @@ -0,0 +1,22 @@ +package dev.amble.lib.test.client; + +import net.fabricmc.api.ClientModInitializer; + +import dev.amble.lib.AmbleKit; +import dev.amble.lib.test.KitTestMod; +import dev.amble.lib.test.sync.client.ExampleClientSyncManager; + +public class KitTestModClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + if (!(AmbleKit.isTestingEnabled())) return; + + if (KitTestMod.TEST_SYNCING) { + initSyncing(); + } + } + + private static void initSyncing() { + ExampleClientSyncManager.init(); + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/ExampleRoot.java b/src/main/java/dev/amble/lib/test/sync/ExampleRoot.java new file mode 100644 index 0000000..4445e34 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/ExampleRoot.java @@ -0,0 +1,21 @@ +package dev.amble.lib.test.sync; + +import java.util.UUID; + +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.test.sync.handler.ExampleComponentRegistry; +import dev.amble.lib.test.sync.handler.FirstExampleComponent; +import dev.amble.lib.test.sync.handler.SecondExampleComponent; + +public abstract class ExampleRoot extends RootComponent { + protected ExampleRoot(UUID uuid) { + super(uuid); + } + + public FirstExampleComponent first() { + return this.handler(ExampleComponentRegistry.Id.FIRST); + } + public SecondExampleComponent second() { + return this.handler(ExampleComponentRegistry.Id.SECOND); + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java new file mode 100644 index 0000000..9795b82 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java @@ -0,0 +1,30 @@ +package dev.amble.lib.test.sync.client; + +import java.util.UUID; + +import net.minecraft.client.MinecraftClient; + +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.manager.SyncManager; +import dev.amble.lib.api.sync.manager.client.ClientComponentData; +import dev.amble.lib.api.sync.manager.client.ClientRootComponent; +import dev.amble.lib.test.sync.ExampleRoot; + +public class ExampleClientRoot extends ExampleRoot implements ClientRootComponent { + @Exclude + private final ClientComponentData data = new ClientComponentData(); + + protected ExampleClientRoot(UUID uuid) { + super(uuid); + } + + @Override + public SyncManager getSyncManager() { + return null; + } + + @Override + public ClientComponentData data() { + return data; + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java new file mode 100644 index 0000000..9001e64 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java @@ -0,0 +1,44 @@ +package dev.amble.lib.test.sync.client; + +import dev.amble.lib.api.sync.handler.ComponentRegistry; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.api.sync.manager.client.ClientSyncManager; +import dev.amble.lib.test.KitTestMod; +import dev.amble.lib.test.sync.handler.ExampleComponentRegistry; + +public class ExampleClientSyncManager extends ClientSyncManager { + private static ExampleClientSyncManager instance; + + public static void init() { + instance = new ExampleClientSyncManager(); + } + + public static ExampleClientSyncManager getInstance() { + return instance; + } + + @Override + protected Class getRootComponentType() { + return ExampleClientRoot.class; + } + + @Override + public ComponentRegistry getRegistry() { + return ExampleComponentRegistry.getInstance(); + } + + @Override + public SyncComponent.IdLike getHandlersId() { + return ExampleComponentRegistry.Id.HANDLERS; + } + + @Override + public String modId() { + return KitTestMod.MOD_ID; + } + + @Override + public String name() { + return "example"; + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/handler/ExampleComponentRegistry.java b/src/main/java/dev/amble/lib/test/sync/handler/ExampleComponentRegistry.java new file mode 100644 index 0000000..fab07f0 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/handler/ExampleComponentRegistry.java @@ -0,0 +1,64 @@ +package dev.amble.lib.test.sync.handler; + +import java.util.function.Supplier; + +import dev.amble.lib.api.sync.handler.ComponentManager; +import dev.amble.lib.api.sync.handler.ComponentRegistry; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.test.sync.server.ExampleServerSyncManager; + +public class ExampleComponentRegistry extends ComponentRegistry { + private static final ExampleComponentRegistry instance = new ExampleComponentRegistry(); + + public static ExampleComponentRegistry getInstance() { + return instance; + } + + @Override + protected SyncComponent.IdLike[] ids() { + return Id.values(); + } + + public enum Id implements SyncComponent.IdLike { + HANDLERS(ComponentManager.class, () -> new ComponentManager(ExampleServerSyncManager.getInstance())), + FIRST(FirstExampleComponent.class, FirstExampleComponent::new), + SECOND(SecondExampleComponent.class, SecondExampleComponent::new); + + private final Supplier creator; + + private final Class clazz; + + private Integer index = null; + + @SuppressWarnings("unchecked") + Id(Class clazz, Supplier creator) { + this.clazz = clazz; + this.creator = (Supplier) creator; + } + + @Override + public Class clazz() { + return clazz; + } + + @Override + public SyncComponent create() { + return this.creator.get(); + } + + @Override + public boolean creatable() { + return this.creator != null; + } + + @Override + public int index() { + return index; + } + + @Override + public void index(int i) { + this.index = i; + } + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/handler/FirstExampleComponent.java b/src/main/java/dev/amble/lib/test/sync/handler/FirstExampleComponent.java new file mode 100644 index 0000000..1de1f61 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/handler/FirstExampleComponent.java @@ -0,0 +1,29 @@ +package dev.amble.lib.test.sync.handler; + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.properties.bool.BoolProperty; +import dev.amble.lib.api.sync.properties.bool.BoolValue; +import dev.amble.lib.test.KitTestMod; +import dev.amble.lib.test.sync.server.ExampleServerSyncManager; + +public class FirstExampleComponent extends KeyedSyncComponent { + private static final BoolProperty IS_AWESOME_PROPERTY = new BoolProperty("is_awesome", false, ExampleServerSyncManager.getInstance()); + private final BoolValue isAwesome = IS_AWESOME_PROPERTY.create(this); + + public FirstExampleComponent() { + super(ExampleComponentRegistry.Id.FIRST); + } + + @Override + public void onLoaded() { + super.onLoaded(); + + KitTestMod.LOGGER.info("FirstExampleComponent loaded"); + + isAwesome.of(this, IS_AWESOME_PROPERTY); + } + + public BoolValue isAwesome() { + return isAwesome; + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java b/src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java new file mode 100644 index 0000000..1fdc8f1 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java @@ -0,0 +1,30 @@ +package dev.amble.lib.test.sync.handler; + +import dev.amble.lib.api.sync.handler.KeyedSyncComponent; +import dev.amble.lib.api.sync.properties.bool.BoolProperty; +import dev.amble.lib.api.sync.properties.bool.BoolValue; +import dev.amble.lib.test.KitTestMod; +import dev.amble.lib.test.sync.server.ExampleServerSyncManager; + +public class SecondExampleComponent extends KeyedSyncComponent { + private static final BoolProperty IS_EPIC_PROPERTY = new BoolProperty("is_epic", false, ExampleServerSyncManager.getInstance()); + private final BoolValue isEpic = IS_EPIC_PROPERTY.create(this); + + + public SecondExampleComponent() { + super(ExampleComponentRegistry.Id.SECOND); + } + + @Override + public void onLoaded() { + super.onLoaded(); + + KitTestMod.LOGGER.info("FirstExampleComponent loaded"); + + isEpic.of(this, IS_EPIC_PROPERTY); + } + + public BoolValue isEpic() { + return isEpic; + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java new file mode 100644 index 0000000..f7543bd --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java @@ -0,0 +1,30 @@ +package dev.amble.lib.test.sync.server; + +import java.util.UUID; + +import net.minecraft.server.MinecraftServer; + +import dev.amble.lib.api.sync.Exclude; +import dev.amble.lib.api.sync.manager.SyncManager; +import dev.amble.lib.api.sync.manager.server.ServerComponentData; +import dev.amble.lib.api.sync.manager.server.ServerRootComponent; +import dev.amble.lib.test.sync.ExampleRoot; + +public class ExampleServerRoot extends ExampleRoot implements ServerRootComponent { + @Exclude + private final ServerComponentData data = new ServerComponentData(); + + protected ExampleServerRoot(UUID uuid) { + super(uuid); + } + + @Override + public SyncManager getSyncManager() { + return ExampleServerSyncManager.getInstance(); + } + + @Override + public ServerComponentData data() { + return data; + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java new file mode 100644 index 0000000..0310bfb --- /dev/null +++ b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java @@ -0,0 +1,56 @@ +package dev.amble.lib.test.sync.server; + +import java.util.HashSet; +import java.util.Set; + +import net.fabricmc.fabric.api.networking.v1.PlayerLookup; + +import net.minecraft.server.network.ServerPlayerEntity; + +import dev.amble.lib.api.sync.handler.ComponentRegistry; +import dev.amble.lib.api.sync.handler.SyncComponent; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.test.KitTestMod; +import dev.amble.lib.test.sync.handler.ExampleComponentRegistry; +import dev.amble.lib.util.ServerLifecycleHooks; + +public class ExampleServerSyncManager extends ServerSyncManager { + private static ExampleServerSyncManager instance; + + public static void init() { + instance = new ExampleServerSyncManager(); + } + + @Override + public Set getSubscribedPlayers(ExampleServerRoot root) { + return new HashSet<>(PlayerLookup.all(ServerLifecycleHooks.get())); + } + + @Override + protected Class getRootComponentType() { + return ExampleServerRoot.class; + } + + @Override + public ComponentRegistry getRegistry() { + return ExampleComponentRegistry.getInstance(); + } + + @Override + public SyncComponent.IdLike getHandlersId() { + return ExampleComponentRegistry.Id.HANDLERS; + } + + @Override + public String modId() { + return KitTestMod.MOD_ID; + } + + @Override + public String name() { + return "example"; + } + public static ExampleServerSyncManager getInstance() { + return instance; + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e96bfc3..1aaf321 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -21,7 +21,8 @@ "dev.amble.lib.test.KitTestMod" ], "client": [ - "dev.amble.lib.client.AmbleKitClient" + "dev.amble.lib.client.AmbleKitClient", + "dev.amble.lib.test.client.KitTestModClient" ] }, "mixins": [ From 425b1e1493dae6756eb921f5da7e1b6ec7380b5e Mon Sep 17 00:00:00 2001 From: James Hall Date: Sat, 8 Mar 2025 13:35:39 +0000 Subject: [PATCH 4/4] api: Linking & References chore: more tests --- build.gradle | 2 +- .../api/sync/handler/ComponentManager.java | 2 + .../api/sync/handler/ComponentRegistry.java | 4 + .../lib/api/sync/handler/SyncComponent.java | 2 +- .../dev/amble/lib/api/sync/link/Linkable.java | 25 ++++ .../dev/amble/lib/api/sync/link/RootRef.java | 109 ++++++++++++++++ .../block/AbstractLinkableBlockEntity.java | 123 ++++++++++++++++++ .../link/entity/AbstractLinkableEntity.java | 96 ++++++++++++++ .../lib/api/sync/manager/SyncManager.java | 64 +++++++-- .../manager/server/ServerSyncManager.java | 13 ++ .../amble/lib/api/sync/properties/Value.java | 1 + .../java/dev/amble/lib/test/KitTestMod.java | 12 ++ .../amble/lib/test/core/block/TestBlocks.java | 8 ++ .../lib/test/core/block/TestLinkBlock.java | 56 ++++++++ .../block/entities/TestBlockEntities.java | 9 ++ .../block/entities/TestLinkBlockEntity.java | 64 +++++++++ .../test/sync/client/ExampleClientRoot.java | 8 +- .../sync/client/ExampleClientSyncManager.java | 13 ++ .../sync/handler/SecondExampleComponent.java | 2 +- .../test/sync/server/ExampleServerRoot.java | 4 +- .../sync/server/ExampleServerSyncManager.java | 17 +++ 21 files changed, 614 insertions(+), 20 deletions(-) create mode 100644 src/main/java/dev/amble/lib/api/sync/link/Linkable.java create mode 100644 src/main/java/dev/amble/lib/api/sync/link/RootRef.java create mode 100644 src/main/java/dev/amble/lib/api/sync/link/block/AbstractLinkableBlockEntity.java create mode 100644 src/main/java/dev/amble/lib/api/sync/link/entity/AbstractLinkableEntity.java create mode 100644 src/main/java/dev/amble/lib/test/core/block/TestBlocks.java create mode 100644 src/main/java/dev/amble/lib/test/core/block/TestLinkBlock.java create mode 100644 src/main/java/dev/amble/lib/test/core/block/entities/TestBlockEntities.java create mode 100644 src/main/java/dev/amble/lib/test/core/block/entities/TestLinkBlockEntity.java diff --git a/build.gradle b/build.gradle index 9244208..f61e065 100644 --- a/build.gradle +++ b/build.gradle @@ -142,7 +142,7 @@ publishing { } } -def gitBranch = 'git rev-parse --abbrev-ref HEAD'.execute().text.trim() +def gitBranch = 'git rev-parse --abbrev-link HEAD'.execute().text.trim() tasks.processResources { filesMatching("*.properties") { diff --git a/src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java b/src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java index d2d1ee5..350eb8b 100644 --- a/src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java +++ b/src/main/java/dev/amble/lib/api/sync/handler/ComponentManager.java @@ -54,6 +54,8 @@ private void forEach(Consumer consumer) { for (SyncComponent component : this.handlers.getValues()) { if (component == null) continue; + if (component == this || component.getId() == this.getId()) + continue; consumer.accept(component); } diff --git a/src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java b/src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java index 6aa06ea..9f57b1c 100644 --- a/src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java +++ b/src/main/java/dev/amble/lib/api/sync/handler/ComponentRegistry.java @@ -71,6 +71,10 @@ public Collection getValues() { return REGISTRY.values(); } public SyncComponent.IdLike[] lookup() { + if (LOOKUP == null) { + throw new UnsupportedOperationException("Registry not initialized! Dont forget to register the registry in your mod init."); + } + return LOOKUP; } diff --git a/src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java b/src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java index 38cdd8a..55054b3 100644 --- a/src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java +++ b/src/main/java/dev/amble/lib/api/sync/handler/SyncComponent.java @@ -131,7 +131,7 @@ public void index(int i) { public record InitContext(@Nullable CachedDirectedGlobalPos pos, boolean deserialized) implements Initializable.Context { - public static InitContext createdAt(CachedDirectedGlobalPos pos) { + public static InitContext createdAt(@Nullable CachedDirectedGlobalPos pos) { return new InitContext(pos, false); } diff --git a/src/main/java/dev/amble/lib/api/sync/link/Linkable.java b/src/main/java/dev/amble/lib/api/sync/link/Linkable.java new file mode 100644 index 0000000..dfeb4ae --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/link/Linkable.java @@ -0,0 +1,25 @@ +package dev.amble.lib.api.sync.link; + +import dev.amble.lib.api.sync.RootComponent; + +import java.util.UUID; + +public interface Linkable { + void link(R tardis); + + void link(UUID id); + + RootRef parent(); + + default boolean isLinked() { + return this.parent() != null && this.parent().isPresent(); + } + + /** + * @implNote This method is called when the RootRef instance gets created. This + * means that the ref is no longer null BUT the root instance still + * could be missing. + */ + default void onLinked() { + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/link/RootRef.java b/src/main/java/dev/amble/lib/api/sync/link/RootRef.java new file mode 100644 index 0000000..2b70854 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/link/RootRef.java @@ -0,0 +1,109 @@ +package dev.amble.lib.api.sync.link; + +import dev.amble.lib.api.sync.Disposable; +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.manager.SyncManager; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.Entity; +import net.minecraft.world.World; + +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; + +public class RootRef implements Disposable { + private final LoadFunc load; + private UUID id; + + private R cached; + + public RootRef(UUID id, LoadFunc load) { + this.id = id; + this.load = load; + } + + public RootRef(R tardis, LoadFunc load) { + if (tardis != null) + this.id = tardis.getUuid(); + + this.load = load; + this.cached = tardis; + } + + public R get() { + if (this.cached != null && !this.shouldInvalidate()) + return this.cached; + + this.cached = (R) this.load.apply(this.id); + return this.cached; + } + + private boolean shouldInvalidate() { + return this.cached.isAged(); + } + + public UUID getId() { + return id; + } + + public boolean isPresent() { + return this.get() != null; + } + + public boolean isEmpty() { + return this.get() == null; + } + + /** + * @return the result of the function, {@literal null} otherwise. + */ + public Optional apply(Function consumer) { + if (this.isPresent()) + return Optional.of(consumer.apply(this.cached)); + + return Optional.empty(); + } + + public void ifPresent(Consumer consumer) { + if (this.isPresent()) + consumer.accept(this.get()); + } + + @Override + public void dispose() { + this.cached = null; + } + + public boolean contains(R tardis) { + return this.get() == tardis; + } + + public static RootRef createAs(Entity entity, R tardis, SyncManager source) { + return new RootRef<>(tardis, real -> (RootComponent) source.with(entity, (o, manager) -> manager.demand(o, real))); + } + + public static RootRef createAs(Entity entity, UUID uuid, SyncManager source) { + return new RootRef<>(uuid, real -> (RootComponent) source.with(entity, (o, manager) -> manager.demand(o, real))); + } + + public static RootRef createAs(BlockEntity blockEntity, R tardis, SyncManager source) { + return new RootRef<>(tardis, real -> (RootComponent) source.with(blockEntity, (o, manager) -> manager.demand(o, real))); + } + + public static RootRef createAs(BlockEntity blockEntity, UUID uuid, SyncManager source) { + return new RootRef<>(uuid, real -> (RootComponent) source.with(blockEntity, (o, manager) -> manager.demand(o, real))); + } + + public static RootRef createAs(World world, R tardis, SyncManager source) { + return new RootRef<>(tardis, real -> (RootComponent) source.with(world, (o, manager) -> manager.demand(o, real))); + } + + public static RootRef createAs(World world, UUID uuid, SyncManager source) { + return new RootRef<>(uuid, real -> (RootComponent) source.with(world, (o, manager) -> manager.demand(o, real))); + } + + + public interface LoadFunc extends Function { + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/link/block/AbstractLinkableBlockEntity.java b/src/main/java/dev/amble/lib/api/sync/link/block/AbstractLinkableBlockEntity.java new file mode 100644 index 0000000..5a94f22 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/link/block/AbstractLinkableBlockEntity.java @@ -0,0 +1,123 @@ +package dev.amble.lib.api.sync.link.block; + +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.link.Linkable; +import dev.amble.lib.api.sync.link.RootRef; +import dev.amble.lib.api.sync.manager.SyncManager; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.network.listener.ClientPlayPacketListener; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; +import net.minecraft.server.world.ServerChunkManager; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; + +import java.util.UUID; + +public abstract class AbstractLinkableBlockEntity extends BlockEntity implements Linkable { + + protected RootRef ref; + + public AbstractLinkableBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + @Override + public RootRef parent() { + return ref; + } + + @Override + public void writeNbt(NbtCompound nbt) { + super.writeNbt(nbt); + + if (this.ref != null && this.ref.getId() != null) + nbt.putUuid(getNbtPath(), this.ref.getId()); + } + + @Override + public void readNbt(NbtCompound nbt) { + super.readNbt(nbt); + + NbtElement id = nbt.get(getNbtPath()); + + if (id == null) + return; + + this.ref = RootRef.createAs(this, NbtHelper.toUuid(id), this.getSyncManager()); + + if (this.world == null) + return; + + this.onLinked(); + } + + @Override + public void markRemoved() { + super.markRemoved(); + + if (this.ref == null || this.ref.isEmpty()) + return; + + if (!(this.world instanceof ServerWorld serverWorld)) + return; + + // ServerTardisManager.getInstance().unmark(serverWorld, (ServerTardis) this.ref.get(), new ChunkPos(this.pos)); + } + + @Override + public void link(R tardis) { + this.ref = RootRef.createAs(this, tardis, this.getSyncManager()); + this.handleLink(); + } + + @Override + public void link(UUID id) { + this.ref = RootRef.createAs(this, id, this.getSyncManager()); + this.handleLink(); + } + + private void mark() { + /*if (this.world instanceof ServerWorld serverWorld) + ServerTardisManager.getInstance().mark(serverWorld, (ServerTardis) this.tardis().get(), + new ChunkPos(this.pos));*/ + } + + private void handleLink() { + this.mark(); + this.onLinked(); + + this.sync(); + this.markDirty(); + } + + @Override + public Packet toUpdatePacket() { + return BlockEntityUpdateS2CPacket.create(this); + } + + @Override + public NbtCompound toInitialChunkDataNbt() { + if (this.isLinked()) + this.mark(); + + return createNbt(); + } + + protected void sync() { + if (this.world != null && this.world.getChunkManager() instanceof ServerChunkManager chunkManager) + chunkManager.markForUpdate(this.pos); + } + + public abstract SyncManager getSyncManager(); + + protected String getNbtPath() { + return this.getSyncManager().createPacket("root").getPath(); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/link/entity/AbstractLinkableEntity.java b/src/main/java/dev/amble/lib/api/sync/link/entity/AbstractLinkableEntity.java new file mode 100644 index 0000000..ee3a312 --- /dev/null +++ b/src/main/java/dev/amble/lib/api/sync/link/entity/AbstractLinkableEntity.java @@ -0,0 +1,96 @@ +package dev.amble.lib.api.sync.link.entity; + +import dev.amble.lib.api.sync.RootComponent; +import dev.amble.lib.api.sync.link.Linkable; +import dev.amble.lib.api.sync.link.RootRef; +import dev.amble.lib.api.sync.manager.SyncManager; +import net.minecraft.entity.Entity; +import net.minecraft.entity.data.DataTracker; +import net.minecraft.entity.data.TrackedData; +import net.minecraft.entity.data.TrackedDataHandlerRegistry; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtHelper; +import net.minecraft.world.World; + +import java.util.Optional; +import java.util.UUID; + +public interface AbstractLinkableEntity extends Linkable { + + World getWorld(); + + DataTracker getDataTracker(); + + TrackedData> getTracked(); + + RootRef asRef(); + + void setRef(RootRef ref); + + SyncManager getSyncManager(); + + @Override + default void link(UUID id) { + this.setRef(RootRef.createAs(this.getWorld(), id, this.getSyncManager())); + this.getDataTracker().set(this.getTracked(), Optional.ofNullable(id)); + } + + @Override + default void link(R tardis) { + this.setRef(RootRef.createAs(this.getWorld(), tardis, this.getSyncManager())); + this.getDataTracker().set(this.getTracked(), Optional.of(tardis.getUuid())); + } + + @Override + default RootRef parent() { + RootRef result = this.asRef(); + + if (result == null) { + this.link(this.getDataTracker().get(this.getTracked()).orElse(null)); + return this.parent(); + } + + return result; + } + + default void initDataTracker() { + this.getDataTracker().startTracking(this.getTracked(), Optional.empty()); + } + + default void onTrackedDataSet(TrackedData data) { + if (!this.getTracked().equals(data)) + return; + + this.link(this.getDataTracker().get(this.getTracked()).orElse(null)); + } + + default void readCustomDataFromNbt(NbtCompound nbt) { + NbtElement id = nbt.get(getNbtPath()); + + if (id == null) + return; + + this.link(NbtHelper.toUuid(id)); + + if (this.getWorld() == null) + return; + + this.onLinked(); + } + + default void writeCustomDataToNbt(NbtCompound nbt) { + RootRef ref = this.asRef(); + + if (ref != null && ref.getId() != null) + nbt.putUuid(getNbtPath(), ref.getId()); + } + + default String getNbtPath() { + return this.getSyncManager().createPacket("root").getPath(); + } + + static > TrackedData> register(Class self) { + return DataTracker.registerData(self, TrackedDataHandlerRegistry.OPTIONAL_UUID); + } +} diff --git a/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java index cda145e..2a2c5dd 100644 --- a/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java +++ b/src/main/java/dev/amble/lib/api/sync/manager/SyncManager.java @@ -4,11 +4,26 @@ import java.util.UUID; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.function.Supplier; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import dev.amble.lib.api.sync.manager.client.ClientSyncManager; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.api.sync.properties.Value; +import dev.amble.lib.api.sync.properties.bool.BoolValue; +import dev.amble.lib.api.sync.properties.dbl.DoubleValue; +import dev.amble.lib.api.sync.properties.integer.IntValue; +import dev.amble.lib.api.sync.properties.integer.ranged.RangedIntValue; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.Entity; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; import org.jetbrains.annotations.Nullable; import net.minecraft.item.ItemStack; @@ -65,7 +80,7 @@ public boolean shouldSkipClass(Class clazz) { .registerTypeAdapter(GlobalPos.class, new GlobalPosSerializer()) .registerTypeAdapter(BlockPos.class, new BlockPosSerializer()) .registerTypeAdapter(RegistryKey.class, new RegistryKeySerializer()) - .registerTypeAdapter(ComponentManager.class, ComponentManager.serializer(this.getRegistry(), this.getHandlersId())) + .registerTypeHierarchyAdapter(ComponentManager.class, ComponentManager.serializer(this.getRegistry(), this.getHandlersId())) .registerTypeAdapter(SyncComponent.IdLike.class, getRegistry().idSerializer()); } @@ -80,12 +95,11 @@ protected GsonBuilder getFileGson(GsonBuilder builder) { // /\ builder.setPrettyPrinting(); - return builder; -// return builder.registerTypeAdapter(Value.class, Value.serializer()) -// .registerTypeAdapter(BoolValue.class, BoolValue.serializer()) -// .registerTypeAdapter(IntValue.class, IntValue.serializer()) -// .registerTypeAdapter(RangedIntValue.class, RangedIntValue.serializer()) -// .registerTypeAdapter(DoubleValue.class, DoubleValue.serializer()); + return builder.registerTypeAdapter(Value.class, Value.serializer()) + .registerTypeAdapter(BoolValue.class, BoolValue.serializer()) + .registerTypeAdapter(IntValue.class, IntValue.serializer()) + .registerTypeAdapter(RangedIntValue.class, RangedIntValue.serializer()) + .registerTypeAdapter(DoubleValue.class, DoubleValue.serializer()); } public void get(C c, UUID uuid, Consumer

consumer) { @@ -146,11 +160,6 @@ public Gson getFileGson() { return fileGson; } - @FunctionalInterface - public interface ContextManager { - R run(C c, SyncManager manager); - } - public Identifier askPacket() { return createPacket("ask"); } @@ -175,4 +184,35 @@ public Identifier createPacket(String id) { public abstract SyncComponent.IdLike getHandlersId(); public abstract String modId(); public abstract String name(); + public abstract ServerSyncManager asServer(); + @Environment(EnvType.CLIENT) + public abstract ClientSyncManager asClient(); + + public R with(BlockEntity entity, ContextManager consumer) { + return this.with(entity.getWorld(), consumer); + } + + public R with(Entity entity, ContextManager consumer) { + return this.with(entity.getWorld(), consumer); + } + + public R with(World world, ContextManager consumer) { + return this.with(world.isClient(), consumer, world::getServer); + } + + public R with(boolean isClient, ContextManager consumer, Supplier server) { + SyncManager manager = (SyncManager) (isClient ? this.asClient() : this.asServer()); + + if (isClient) { + return consumer.run((M) MinecraftClient.getInstance(), manager); + } else { + return consumer.run((M) server.get(), manager); + } + } + + + @FunctionalInterface + public interface ContextManager { + R run(C c, SyncManager manager); + } } diff --git a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java index 9500907..21a54f8 100644 --- a/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java +++ b/src/main/java/dev/amble/lib/api/sync/manager/server/ServerSyncManager.java @@ -7,6 +7,7 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; +import dev.amble.lib.api.sync.Initializable; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; @@ -286,6 +287,18 @@ public void markPropertyDirty(T tardis, Value value) { this.markComponentDirty(value.getHolder()); } + public boolean add(T val) { + if (this.lookup.containsValue(val)) { + return false; + } + + this.lookup.put(val); + + Initializable.init(val, SyncComponent.InitContext.createdAt(null)); + + return true; + } + public abstract Set getSubscribedPlayers(T root); protected boolean isInvalid(T tardis) { diff --git a/src/main/java/dev/amble/lib/api/sync/properties/Value.java b/src/main/java/dev/amble/lib/api/sync/properties/Value.java index dea26b2..765ce7c 100644 --- a/src/main/java/dev/amble/lib/api/sync/properties/Value.java +++ b/src/main/java/dev/amble/lib/api/sync/properties/Value.java @@ -35,6 +35,7 @@ public class Value implements Disposable { private List> listeners; private T value; + @Exclude private ServerSyncManager manager; protected Value(T value) { diff --git a/src/main/java/dev/amble/lib/test/KitTestMod.java b/src/main/java/dev/amble/lib/test/KitTestMod.java index aac2e2b..252ea39 100644 --- a/src/main/java/dev/amble/lib/test/KitTestMod.java +++ b/src/main/java/dev/amble/lib/test/KitTestMod.java @@ -1,5 +1,11 @@ package dev.amble.lib.test; +import dev.amble.lib.container.RegistryContainer; +import dev.amble.lib.register.AmbleRegistries; +import dev.amble.lib.register.Registry; +import dev.amble.lib.test.core.block.TestBlocks; +import dev.amble.lib.test.core.block.entities.TestBlockEntities; +import dev.amble.lib.test.sync.handler.ExampleComponentRegistry; import net.fabricmc.api.ModInitializer; import org.jetbrains.annotations.TestOnly; import org.slf4j.Logger; @@ -24,11 +30,17 @@ public void onInitialize() { if (TEST_SYNCING) { initSyncing(); } + + LOGGER.info("Registry Tests Enabled!"); + RegistryContainer.register(TestBlocks.class, MOD_ID); + RegistryContainer.register(TestBlockEntities.class, MOD_ID); } private static void initSyncing() { LOGGER.info("Syncing Tests Enabled!"); + AmbleRegistries.getInstance().registerAll(ExampleComponentRegistry.getInstance()); + ExampleServerSyncManager.init(); } } diff --git a/src/main/java/dev/amble/lib/test/core/block/TestBlocks.java b/src/main/java/dev/amble/lib/test/core/block/TestBlocks.java new file mode 100644 index 0000000..6376a01 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/core/block/TestBlocks.java @@ -0,0 +1,8 @@ +package dev.amble.lib.test.core.block; + +import dev.amble.lib.container.impl.BlockContainer; +import net.minecraft.block.Block; + +public class TestBlocks extends BlockContainer { + public static final Block TEST_LINK_BLOCK = new TestLinkBlock(Block.Settings.copy(net.minecraft.block.Blocks.STONE)); +} diff --git a/src/main/java/dev/amble/lib/test/core/block/TestLinkBlock.java b/src/main/java/dev/amble/lib/test/core/block/TestLinkBlock.java new file mode 100644 index 0000000..2721615 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/core/block/TestLinkBlock.java @@ -0,0 +1,56 @@ +package dev.amble.lib.test.core.block; + +import dev.amble.lib.test.core.block.entities.TestBlockEntities; +import dev.amble.lib.test.core.block.entities.TestLinkBlockEntity; +import net.minecraft.block.Block; +import net.minecraft.block.BlockEntityProvider; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +public class TestLinkBlock extends Block implements BlockEntityProvider { + public TestLinkBlock(Settings settings) { + super(settings); + } + + @Override + public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) { + return TestBlockEntities.TEST_LINK_BLOCK_ENTITY.instantiate(pos, state); + } + + @Override + public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { + if (world.getBlockEntity(pos) instanceof TestLinkBlockEntity be && hand == Hand.MAIN_HAND) { + return be.onUse(state, world, pos, player); + } + + return super.onUse(state, world, pos, player, hand, hit); + } + @Override + public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) { + if (world.getBlockEntity(pos) instanceof TestLinkBlockEntity be) { + be.onPlaced(world, pos, state, placer, itemStack); + } + + super.onPlaced(world, pos, state, placer, itemStack); + } + @Override + public void onStateReplaced(BlockState state, World world, BlockPos pos, BlockState newState, boolean moved) { + if (!state.isOf(newState.getBlock())) { + BlockEntity be = world.getBlockEntity(pos); + if (be instanceof TestLinkBlockEntity) { + ((TestLinkBlockEntity) be).onBreak(); + } + } + + super.onStateReplaced(state, world, pos, newState, moved); + } +} diff --git a/src/main/java/dev/amble/lib/test/core/block/entities/TestBlockEntities.java b/src/main/java/dev/amble/lib/test/core/block/entities/TestBlockEntities.java new file mode 100644 index 0000000..8888af1 --- /dev/null +++ b/src/main/java/dev/amble/lib/test/core/block/entities/TestBlockEntities.java @@ -0,0 +1,9 @@ +package dev.amble.lib.test.core.block.entities; + +import dev.amble.lib.container.impl.BlockEntityContainer; +import dev.amble.lib.test.core.block.TestBlocks; +import net.minecraft.block.entity.BlockEntityType; + +public class TestBlockEntities implements BlockEntityContainer { + public static final BlockEntityType TEST_LINK_BLOCK_ENTITY = BlockEntityType.Builder.create(TestLinkBlockEntity::new, TestBlocks.TEST_LINK_BLOCK).build(null); +} diff --git a/src/main/java/dev/amble/lib/test/core/block/entities/TestLinkBlockEntity.java b/src/main/java/dev/amble/lib/test/core/block/entities/TestLinkBlockEntity.java new file mode 100644 index 0000000..d19274d --- /dev/null +++ b/src/main/java/dev/amble/lib/test/core/block/entities/TestLinkBlockEntity.java @@ -0,0 +1,64 @@ +package dev.amble.lib.test.core.block.entities; + +import dev.amble.lib.api.sync.link.block.AbstractLinkableBlockEntity; +import dev.amble.lib.api.sync.manager.SyncManager; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; +import dev.amble.lib.test.sync.ExampleRoot; +import dev.amble.lib.test.sync.server.ExampleServerRoot; +import dev.amble.lib.test.sync.server.ExampleServerSyncManager; +import dev.amble.lib.util.ServerLifecycleHooks; +import net.minecraft.block.BlockState; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.text.Text; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class TestLinkBlockEntity extends AbstractLinkableBlockEntity { + public TestLinkBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + public TestLinkBlockEntity(BlockPos pos, BlockState state) { + this(TestBlockEntities.TEST_LINK_BLOCK_ENTITY, pos, state); + } + + @Override + public ExampleServerSyncManager getSyncManager() { + return ExampleServerSyncManager.getInstance(); + } + + public void onPlaced(World world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack itemStack) { + ExampleServerRoot root = new ExampleServerRoot(UUID.randomUUID()); + ExampleServerSyncManager.getInstance().add(root); + this.link(root); + } + + public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player) { + if (!this.isLinked()) return ActionResult.FAIL; + + ExampleRoot root = this.parent().get(); + player.sendMessage(Text.literal("ID: " + root.getUuid()), false); + player.sendMessage(Text.literal("Is Client? " + world.isClient()), false); + player.sendMessage(Text.literal("Is Awesome? " + root.first().isAwesome().get()), false); + player.sendMessage(Text.literal("Is Epic? " + root.second().isEpic().get()), false); + + if (!world.isClient()) { + root.first().isAwesome().set(!root.first().isAwesome().get()); + root.second().isEpic().set(!root.second().isEpic().get()); + } + + return ActionResult.SUCCESS; + } + + public void onBreak() { + // delete + if (!this.isLinked()) return; + ExampleServerSyncManager.getInstance().remove(ServerLifecycleHooks.get(), (ExampleServerRoot) this.parent().get()); + } +} diff --git a/src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java index 9795b82..883db67 100644 --- a/src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java +++ b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientRoot.java @@ -11,10 +11,10 @@ import dev.amble.lib.test.sync.ExampleRoot; public class ExampleClientRoot extends ExampleRoot implements ClientRootComponent { - @Exclude - private final ClientComponentData data = new ClientComponentData(); + @Exclude(strategy = Exclude.Strategy.NETWORK) + private ClientComponentData data = new ClientComponentData(); - protected ExampleClientRoot(UUID uuid) { + private ExampleClientRoot(UUID uuid) { super(uuid); } @@ -25,6 +25,8 @@ public SyncManager getSyncManager() { @Override public ClientComponentData data() { + if (data == null) data = new ClientComponentData(); + return data; } } diff --git a/src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java index 9001e64..895771b 100644 --- a/src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java +++ b/src/main/java/dev/amble/lib/test/sync/client/ExampleClientSyncManager.java @@ -3,8 +3,11 @@ import dev.amble.lib.api.sync.handler.ComponentRegistry; import dev.amble.lib.api.sync.handler.SyncComponent; import dev.amble.lib.api.sync.manager.client.ClientSyncManager; +import dev.amble.lib.api.sync.manager.server.ServerSyncManager; import dev.amble.lib.test.KitTestMod; import dev.amble.lib.test.sync.handler.ExampleComponentRegistry; +import dev.amble.lib.test.sync.server.ExampleServerRoot; +import dev.amble.lib.test.sync.server.ExampleServerSyncManager; public class ExampleClientSyncManager extends ClientSyncManager { private static ExampleClientSyncManager instance; @@ -41,4 +44,14 @@ public String modId() { public String name() { return "example"; } + + @Override + public ServerSyncManager asServer() { + return ExampleServerSyncManager.getInstance(); + } + + @Override + public ClientSyncManager asClient() { + return this; + } } diff --git a/src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java b/src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java index 1fdc8f1..c4e646a 100644 --- a/src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java +++ b/src/main/java/dev/amble/lib/test/sync/handler/SecondExampleComponent.java @@ -19,7 +19,7 @@ public SecondExampleComponent() { public void onLoaded() { super.onLoaded(); - KitTestMod.LOGGER.info("FirstExampleComponent loaded"); + KitTestMod.LOGGER.info("SecondExampleComponent loaded"); isEpic.of(this, IS_EPIC_PROPERTY); } diff --git a/src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java index f7543bd..36a75cf 100644 --- a/src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java +++ b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerRoot.java @@ -11,10 +11,10 @@ import dev.amble.lib.test.sync.ExampleRoot; public class ExampleServerRoot extends ExampleRoot implements ServerRootComponent { - @Exclude + @Exclude(strategy = Exclude.Strategy.NETWORK) private final ServerComponentData data = new ServerComponentData(); - protected ExampleServerRoot(UUID uuid) { + public ExampleServerRoot(UUID uuid) { super(uuid); } diff --git a/src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java index 0310bfb..3f3bb67 100644 --- a/src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java +++ b/src/main/java/dev/amble/lib/test/sync/server/ExampleServerSyncManager.java @@ -3,6 +3,11 @@ import java.util.HashSet; import java.util.Set; +import dev.amble.lib.api.sync.manager.client.ClientSyncManager; +import dev.amble.lib.test.sync.client.ExampleClientRoot; +import dev.amble.lib.test.sync.client.ExampleClientSyncManager; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.networking.v1.PlayerLookup; import net.minecraft.server.network.ServerPlayerEntity; @@ -50,6 +55,18 @@ public String modId() { public String name() { return "example"; } + + @Override + public ServerSyncManager asServer() { + return this; + } + + @Environment(EnvType.CLIENT) + @Override + public ClientSyncManager asClient() { + return ExampleClientSyncManager.getInstance(); + } + public static ExampleServerSyncManager getInstance() { return instance; }