Skip to content

Commit

Permalink
feat: immutable components general listeners
Browse files Browse the repository at this point in the history
  • Loading branch information
williambl committed Nov 18, 2024
1 parent b23fd3e commit 2a36e4f
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.ladysnake.cca.api.v3.component.immutable;

import org.ladysnake.cca.api.v3.component.load.ClientLoadAwareComponent;
import org.ladysnake.cca.api.v3.component.load.ClientUnloadAwareComponent;
import org.ladysnake.cca.api.v3.component.load.ServerLoadAwareComponent;
import org.ladysnake.cca.api.v3.component.load.ServerUnloadAwareComponent;
import org.ladysnake.cca.api.v3.component.tick.ClientTickingComponent;
import org.ladysnake.cca.api.v3.component.tick.ServerTickingComponent;

import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.Arrays;

public record ImmutableComponentCallbackType<I>(Class<I> itf,
String methodName,
MethodType exposedType,
MethodType implType) {
public ImmutableComponentCallbackType {
if (!itf.isInterface()
|| Arrays.stream(itf.getDeclaredMethods())
.filter(m -> Modifier.isAbstract(m.getModifiers()))
.count() != 1) {
throw new IllegalArgumentException("ImmutableComponentCallbackType accepts only functional interfaces");
}
}

public static <I> ImmutableComponentCallbackType<I> fromFunctionalInterface(Class<I> itf) {
var method = Arrays.stream(itf.getDeclaredMethods())
.filter(m -> Modifier.isAbstract(m.getModifiers()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("ImmutableComponentCallbackType accepts only functional interfaces"));
String name = method.getName();
var type = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
var implType = type.insertParameterTypes(0, ImmutableComponentWrapper.class);
return new ImmutableComponentCallbackType<>(itf, name, type, implType);
}

public static final ImmutableComponentCallbackType<ServerTickingComponent> SERVER_TICK = fromFunctionalInterface(ServerTickingComponent.class);
public static final ImmutableComponentCallbackType<ClientTickingComponent> CLIENT_TICK = fromFunctionalInterface(ClientTickingComponent.class);
public static final ImmutableComponentCallbackType<ServerLoadAwareComponent> SERVER_LOAD = fromFunctionalInterface(ServerLoadAwareComponent.class);
public static final ImmutableComponentCallbackType<ClientLoadAwareComponent> CLIENT_LOAD = fromFunctionalInterface(ClientLoadAwareComponent.class);
public static final ImmutableComponentCallbackType<ServerUnloadAwareComponent> SERVER_UNLOAD = fromFunctionalInterface(ServerUnloadAwareComponent.class);
public static final ImmutableComponentCallbackType<ClientUnloadAwareComponent> CLIENT_UNLOAD = fromFunctionalInterface(ClientUnloadAwareComponent.class);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,30 @@
import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Dynamic;
import com.mojang.serialization.MapCodec;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtOps;
import net.minecraft.network.RegistryByteBuf;
import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.util.Identifier;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponent;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentCallbackType;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentKey;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentWrapper;
import org.ladysnake.cca.api.v3.component.load.ClientLoadAwareComponent;
import org.ladysnake.cca.api.v3.component.load.ClientUnloadAwareComponent;
import org.ladysnake.cca.api.v3.component.load.ServerLoadAwareComponent;
import org.ladysnake.cca.api.v3.component.load.ServerUnloadAwareComponent;
import org.ladysnake.cca.api.v3.component.tick.ClientTickingComponent;
import org.ladysnake.cca.api.v3.component.tick.ServerTickingComponent;
import org.ladysnake.cca.internal.base.asm.CcaAsmHelper;

import java.lang.invoke.*;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class ImmutableInternals {
private static final MethodHandle SET;
Expand All @@ -34,33 +44,37 @@ public class ImmutableInternals {
}

//TODO id and type do not uniquely describe a component implementation - e.g. predicates in entity component registration
public static final Map<Pair<Identifier, Type>, ImmutableComponent.Modifier<?, ?>> serverTickHandlers = new HashMap<>();
public static final Map<Pair<Identifier, Type>, ImmutableComponent.Modifier<?, ?>> clientTickHandlers = new HashMap<>();
public static final Map<Pair<Identifier, Type>, ImmutableComponent.Modifier<?, ?>> loadServersideHandlers = new HashMap<>();
public static final Map<Pair<Identifier, Type>, ImmutableComponent.Modifier<?, ?>> loadClientsideHandlers = new HashMap<>();
public static final Map<Pair<Identifier, Type>, ImmutableComponent.Modifier<?, ?>> unloadServersideHandlers = new HashMap<>();
public static final Map<Pair<Identifier, Type>, ImmutableComponent.Modifier<?, ?>> unloadClientsideHandlers = new HashMap<>();
public static final Map<ImmutableComponentCallbackType<?>, Map<Pair<Identifier, Type>, ImmutableComponent.Modifier<?, ?>>> listeners = new HashMap<>();
public static final Set<ImmutableComponentCallbackType<?>> CALLBACK_TYPES = Set.of(
ImmutableComponentCallbackType.SERVER_TICK,
ImmutableComponentCallbackType.CLIENT_TICK,
ImmutableComponentCallbackType.SERVER_LOAD,
ImmutableComponentCallbackType.CLIENT_LOAD,
ImmutableComponentCallbackType.SERVER_UNLOAD,
ImmutableComponentCallbackType.CLIENT_UNLOAD
);

public static <C extends ImmutableComponent, E extends Entity> void addListener(ImmutableComponentKey<C> key, Class<E> target, ImmutableComponentCallbackType<?> type, ImmutableComponent.Modifier<C, E> modifier) {
listeners.computeIfAbsent(type, $ -> new HashMap<>()).put(Pair.of(key.getId(), target), modifier);
}

public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
String id,
Type targetClass) throws Throwable {
MethodType methodType;
if (type instanceof MethodType mt)
if (type instanceof MethodType mt) {
methodType = mt;
else {
} else {
methodType = null;
if (!MethodHandle.class.equals(type))
throw new IllegalArgumentException(type.toString());
}
MethodHandle handle = switch (methodName) {
case "serverTick" -> makeModifierHandler(lookup, id, targetClass, serverTickHandlers);
case "clientTick" -> makeModifierHandler(lookup, id, targetClass, clientTickHandlers);
case "loadServerside" -> makeModifierHandler(lookup, id, targetClass, loadServersideHandlers);
case "loadClientside" -> makeModifierHandler(lookup, id, targetClass, loadClientsideHandlers);
case "unloadServerside" -> makeModifierHandler(lookup, id, targetClass, unloadServersideHandlers);
case "unloadClientside" -> makeModifierHandler(lookup, id, targetClass, unloadClientsideHandlers);
default -> throw new IllegalArgumentException(methodName);
};
ImmutableComponentCallbackType<?> callbackType = CALLBACK_TYPES.stream()
.filter(t -> t.methodName().equals(methodName))
.filter(t -> methodType == null || t.implType().equals(methodType))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid method name/type: %s:%s".formatted(methodName, methodType == null ? "?" : methodType.descriptorString())));
MethodHandle handle = makeModifierHandler(lookup, id, targetClass, listeners.getOrDefault(callbackType, Map.of()));
return methodType != null ? new ConstantCallSite(handle) : handle;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,9 @@
*/
package org.ladysnake.cca.internal.base.asm;

import com.mojang.datafixers.util.Pair;
import com.google.common.collect.Iterables;
import org.ladysnake.cca.api.v3.component.ComponentFactory;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponent;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentFactory;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentKey;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentWrapper;
import org.ladysnake.cca.internal.base.ImmutableInternals;
import org.ladysnake.cca.api.v3.component.immutable.*;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
Expand All @@ -38,17 +34,16 @@
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Iterator;
import java.util.List;

import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;

public final class CcaImmutableBootstrap {
public static <C extends ImmutableComponent, O, W extends ImmutableComponentWrapper<C, O>> Class<W> makeWrapper(
ImmutableComponentKey<C> key,
Class<O> targetClass,
ImmutableComponent.Modifier<C,O> serverTicker,
ImmutableComponent.Modifier<C,O> clientTicker,
ImmutableComponent.Modifier<C,O> serverOnLoad,
ImmutableComponent.Modifier<C,O> clientOnLoad
Iterable<ImmutableComponentCallbackType<?>> callbackTypes
) throws IOException, NoSuchMethodException, IllegalAccessException {
ClassNode writer = new ClassNode(CcaAsmHelper.ASM_VERSION);
writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, CcaAsmHelper.STATIC_IMMUTABLE_COMPONENT_WRAPPER + "$" + CcaAsmHelper.getJavaIdentifierName(key.getId()), null, CcaAsmHelper.IMMUTABLE_COMPONENT_WRAPPER, null);
Expand Down Expand Up @@ -99,24 +94,25 @@ public static <C extends ImmutableComponent, O, W extends ImmutableComponentWrap
writeSyncPacket.visitEnd();
}

if (serverTicker != null) {
ImmutableInternals.serverTickHandlers.put(Pair.of(key.getId(), targetClass), serverTicker);
makeTicker(key, targetClass, writer, CcaAsmHelper.SERVER_TICKING_COMPONENT, "serverTick", CcaAsmHelper.SERVER_TICK_DESC, CcaAsmHelper.IMMUTABLE_WRAPPER_SERVER_TICK_DESC);
for (var callbackType : callbackTypes) {
implementCallbackItf(key, targetClass, writer, callbackType);
}
if (clientTicker != null) {
ImmutableInternals.clientTickHandlers.put(Pair.of(key.getId(), targetClass), clientTicker);
makeTicker(key, targetClass, writer, CcaAsmHelper.CLIENT_TICKING_COMPONENT, "clientTick", CcaAsmHelper.CLIENT_TICK_DESC, CcaAsmHelper.IMMUTABLE_WRAPPER_CLIENT_TICK_DESC);
}
//todo load/unload handlers

writer.visitEnd();
return (Class<W>) CcaAsmHelper.generateClass(writer, false, null);
}

private static <C extends ImmutableComponent, O> void makeTicker(ImmutableComponentKey<C> key, Class<O> targetClass, ClassNode writer, String interfaceName, String methodName, String methodDesc, String dynMethodDesc) {
private static <C extends ImmutableComponent, O> void implementCallbackItf(ImmutableComponentKey<C> key, Class<O> targetClass, ClassNode writer, ImmutableComponentCallbackType<?> callbackType) {
String interfaceName = Type.getInternalName(callbackType.itf());
writer.interfaces.add(interfaceName);
String methodName = callbackType.methodName();
String methodDesc = callbackType.exposedType().toMethodDescriptorString();
var dynMethodType = callbackType.exposedType().insertParameterTypes(0, ImmutableComponentWrapper.class);
String dynMethodDesc = dynMethodType.toMethodDescriptorString();
MethodVisitor onTick = writer.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDesc, null, null);
onTick.visitVarInsn(Opcodes.ALOAD, 0); // this
for (int i = 0; i < dynMethodType.parameterCount(); i++) {
onTick.visitVarInsn(Opcodes.ALOAD, i);
}
onTick.visitInvokeDynamicInsn(
methodName,
dynMethodDesc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@
import org.ladysnake.cca.api.v3.component.Component;
import org.ladysnake.cca.api.v3.component.ComponentFactory;
import org.ladysnake.cca.api.v3.component.ComponentKey;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponent;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentFactory;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentKey;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentWrapper;
import org.ladysnake.cca.api.v3.component.immutable.*;

import java.util.function.Predicate;

Expand Down Expand Up @@ -163,10 +160,13 @@ interface ImmutableRegistration<C extends ImmutableComponent, E extends Entity>
ImmutableRegistration<C, E> filter(Predicate<Class<? extends E>> test);
ImmutableRegistration<C, E> after(ComponentKey<?> dependency);
ImmutableRegistration<C, E> respawnStrategy(RespawnCopyStrategy<? super ImmutableComponentWrapper<C, E>> strategy);
ImmutableRegistration<C, E> listen(ImmutableComponentCallbackType<?> type, ImmutableComponent.Modifier<C, E> modifier);
ImmutableRegistration<C, E> onServerTick(ImmutableComponent.Modifier<C, E> modifier);
ImmutableRegistration<C, E> onClientTick(ImmutableComponent.Modifier<C, E> modifier);
ImmutableRegistration<C, E> onServerLoad(ImmutableComponent.Modifier<C, E> modifier);
ImmutableRegistration<C, E> onClientLoad(ImmutableComponent.Modifier<C, E> modifier);
ImmutableRegistration<C, E> onServerUnload(ImmutableComponent.Modifier<C, E> modifier);
ImmutableRegistration<C, E> onClientUnload(ImmutableComponent.Modifier<C, E> modifier);

void end(ImmutableComponentFactory<E, C> factory);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,16 @@

import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import org.jetbrains.annotations.Nullable;
import org.ladysnake.cca.api.v3.component.Component;
import org.ladysnake.cca.api.v3.component.ComponentContainer;
import org.ladysnake.cca.api.v3.component.ComponentFactory;
import org.ladysnake.cca.api.v3.component.ComponentKey;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponent;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentFactory;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentKey;
import org.ladysnake.cca.api.v3.component.immutable.ImmutableComponentWrapper;
import org.ladysnake.cca.api.v3.component.immutable.*;
import org.ladysnake.cca.api.v3.entity.EntityComponentFactoryRegistry;
import org.ladysnake.cca.api.v3.entity.EntityComponentInitializer;
import org.ladysnake.cca.api.v3.entity.RespawnableComponent;
import org.ladysnake.cca.api.v3.entity.RespawnCopyStrategy;
import org.ladysnake.cca.internal.base.ImmutableInternals;
import org.ladysnake.cca.internal.base.LazyDispatcher;
import org.ladysnake.cca.internal.base.QualifiedComponentFactory;
import org.ladysnake.cca.internal.base.asm.CcaImmutableBootstrap;
Expand Down Expand Up @@ -245,15 +242,13 @@ private final class ImmutableRegistrationImpl<C extends ImmutableComponent, E ex
private final Class<E> target;
private final ImmutableComponentKey<C> key;
private final Set<ComponentKey<?>> dependencies;
private @Nullable ImmutableComponent.Modifier<C, E> serverTicker;
private @Nullable ImmutableComponent.Modifier<C, E> clientTicker;
private @Nullable ImmutableComponent.Modifier<C, E> serverOnLoad;
private @Nullable ImmutableComponent.Modifier<C, E> clientOnLoad;
private final Map<ImmutableComponentCallbackType<?>, ImmutableComponent.Modifier<C, E>> callbacks;
private Predicate<Class<? extends E>> test;

ImmutableRegistrationImpl(Class<E> target, ImmutableComponentKey<C> key) {
this.target = target;
this.dependencies = new LinkedHashSet<>();
this.callbacks = new HashMap<>();
this.test = null;
this.key = key;
}
Expand All @@ -270,35 +265,46 @@ public ImmutableRegistration<C, E> after(ComponentKey<?> dependency) {
return this;
}


@Override
public ImmutableRegistration<C, E> respawnStrategy(RespawnCopyStrategy<? super ImmutableComponentWrapper<C, E>> strategy) {
CardinalEntityInternals.registerRespawnCopyStrat(this.key, this.target, strategy);
return this;
}

@Override
public ImmutableRegistration<C, E> onServerTick(ImmutableComponent.Modifier<C, E> modifier) {
this.serverTicker = modifier;
public ImmutableRegistration<C, E> listen(ImmutableComponentCallbackType<?> type, ImmutableComponent.Modifier<C, E> modifier) {
this.callbacks.put(type, modifier);
return this;
}

@Override
public ImmutableRegistration<C, E> onServerTick(ImmutableComponent.Modifier<C, E> modifier) {
return this.listen(ImmutableComponentCallbackType.SERVER_TICK, modifier);
}

@Override
public ImmutableRegistration<C, E> onClientTick(ImmutableComponent.Modifier<C, E> modifier) {
this.clientTicker = modifier;
return this;
return this.listen(ImmutableComponentCallbackType.CLIENT_TICK, modifier);
}

@Override
public ImmutableRegistration<C, E> onServerLoad(ImmutableComponent.Modifier<C, E> modifier) {
this.serverOnLoad = modifier;
return this;
return this.listen(ImmutableComponentCallbackType.SERVER_LOAD, modifier);
}

@Override
public ImmutableRegistration<C, E> onClientLoad(ImmutableComponent.Modifier<C, E> modifier) {
this.clientOnLoad = modifier;
return this;
return this.listen(ImmutableComponentCallbackType.CLIENT_LOAD, modifier);
}

@Override
public ImmutableRegistration<C, E> onServerUnload(ImmutableComponent.Modifier<C, E> modifier) {
return this.listen(ImmutableComponentCallbackType.SERVER_UNLOAD, modifier);
}

@Override
public ImmutableRegistration<C, E> onClientUnload(ImmutableComponent.Modifier<C, E> modifier) {
return this.listen(ImmutableComponentCallbackType.CLIENT_UNLOAD, modifier);
}

@Override
Expand All @@ -308,10 +314,7 @@ public void end(ImmutableComponentFactory<E, C> factory) {
Class<? extends ImmutableComponentWrapper<C, E>> componentClass = CcaImmutableBootstrap.makeWrapper(
this.key,
this.target,
this.serverTicker,
this.clientTicker,
this.serverOnLoad,
this.clientOnLoad
this.callbacks.keySet()
);
ComponentFactory<E, ? extends ImmutableComponentWrapper<C, E>> componentFactory = CcaImmutableBootstrap.makeFactory(this.key, this.target, componentClass, factory);
if (this.test == null) {
Expand All @@ -331,6 +334,9 @@ public void end(ImmutableComponentFactory<E, C> factory) {
)
));
}
for (var callback : callbacks.entrySet()) {
ImmutableInternals.addListener(this.key, this.target, callback.getKey(), callback.getValue());
}
} catch (IOException | NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
Expand Down

0 comments on commit 2a36e4f

Please sign in to comment.