Skip to content

Commit

Permalink
✨ rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
TT432 committed Sep 14, 2024
1 parent 2b4c758 commit db1a194
Show file tree
Hide file tree
Showing 15 changed files with 523 additions and 5 deletions.
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,23 @@ Tuple - a heterogeneous list

TupleCodec - Codec for Tuple

ChinExtraCodecs - util class for Codec
ChinExtraCodecs - util class for Codec

### Rpc

Chin's rpc using [AspectJ](https://eclipse.dev/aspectj/).

build.gradle:

```groovy
plugins {
// ...
id "io.freefair.aspectj.post-compile-weaving" version aspectj_gradle_plugin_version
}
dependencies {
// ...
implementation("org.aspectj:aspectjrt:{aspectj_version}")
aspect implementation("io.github.tt432:chin:{chin_version}")
}
```
15 changes: 14 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ plugins {
id 'eclipse'
id 'idea'
id 'maven-publish'
id 'net.neoforged.gradle.userdev' version '7.0.145'
id 'net.neoforged.gradle.userdev' version '7.0.163'
id 'signing'
id "io.freefair.lombok" version "8.6"
id "io.freefair.aspectj.post-compile-weaving" version "8.10"
}

tasks.named('wrapper', Wrapper).configure {
Expand Down Expand Up @@ -33,6 +35,11 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(21)
//minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg')
//minecraft.accessTransformers.entry public net.minecraft.client.Minecraft textureManager # textureManager

configurations {
libraries {}
implementation.extendsFrom libraries
}

// Default run configurations.
// These can be tweaked, removed, or duplicated as needed.
runs {
Expand All @@ -51,6 +58,10 @@ runs {
systemProperty 'forge.logging.console.level', 'debug'

modSource project.sourceSets.main

dependencies {
runtime project.configurations.libraries
}
}

client {
Expand Down Expand Up @@ -114,6 +125,8 @@ dependencies {

testImplementation "org.junit.jupiter:junit-jupiter:5.10.3"
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

jarJar(libraries("org.aspectj:aspectjrt:1.9.21.1"))
}

// This block of code expands all declared replace properties in the specified resource targets.
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ mod_name=Chin
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=MIT
# The mod version. See https://semver.org/
mod_version=1.0.1
mod_version=21.0.1
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/io/github/tt432/chin/Chin.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.tt432.chin;

import io.github.tt432.chin.rpc.api.RpcSourceRegister;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.common.Mod;

/**
Expand All @@ -8,4 +10,8 @@
@Mod(Chin.MOD_ID)
public class Chin {
public static final String MOD_ID = "chin";

public Chin(IEventBus bus) {
RpcSourceRegister.CHIN_DEFERRED_REGISTER.register(bus);
}
}
47 changes: 47 additions & 0 deletions src/main/java/io/github/tt432/chin/rpc/api/Rpc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.github.tt432.chin.rpc.api;

import io.github.tt432.chin.rpc.impl.RpcAspect;
import net.neoforged.api.distmarker.Dist;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* example:
* <pre>{@code
* class MyBlockEntity extends BlockEntity {
* @Rpc(CLIENT)
* public void clickClick() {
* // do something
* }
* }
* }</pre>
*
* the method will send RpcNetworkPacket to server. equals:
*
* <pre>{@code
* class MyBlockEntity extends BlockEntity {
* public void clickClick() {
* if (isClientSide()) {
* // send packet
* } else {
* // do something
* }
* }
* }
* }</pre>
*
* @author TT432
* @see RpcAspect
* @see RpcSourceRegister
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Rpc {
/**
* @return sender side
*/
Dist value();
}
51 changes: 51 additions & 0 deletions src/main/java/io/github/tt432/chin/rpc/api/RpcSourceRegister.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.github.tt432.chin.rpc.api;

import io.github.tt432.chin.Chin;
import io.github.tt432.chin.util.ChinNetworks;
import lombok.experimental.UtilityClass;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredRegister;
import net.neoforged.neoforge.registries.NewRegistryEvent;
import net.neoforged.neoforge.registries.RegistryBuilder;

/**
* @author TT432
*/
@UtilityClass
@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD)
public class RpcSourceRegister {
public final ResourceKey<Registry<RpcSourceType<?>>> RPC_SOURCE_TYPE_KEY =
ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(Chin.MOD_ID, "rpc_source_type"));
public final Registry<RpcSourceType<?>> REGISTRY = new RegistryBuilder<>(RPC_SOURCE_TYPE_KEY).sync(true).create();

public final DeferredRegister<RpcSourceType<?>> CHIN_DEFERRED_REGISTER = DeferredRegister.create(RPC_SOURCE_TYPE_KEY, Chin.MOD_ID);

public final DeferredHolder<RpcSourceType<?>, RpcSourceType<BlockEntity>> BLOCK_ENTITY =
CHIN_DEFERRED_REGISTER.register("block_entity", () -> new RpcSourceType<>(
BlockEntity.class,
be -> be.getBlockPos().asLong(),
(ctx, id) -> {
BlockPos pos = BlockPos.of(id);
Level level = ChinNetworks.getLevel(ctx);

if (level.isLoaded(pos)) {
return level.getBlockEntity(pos);
}

return null;
}
));

@SubscribeEvent
public void onEvent(NewRegistryEvent event) {
event.register(REGISTRY);
}
}
25 changes: 25 additions & 0 deletions src/main/java/io/github/tt432/chin/rpc/api/RpcSourceType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.github.tt432.chin.rpc.api;

import net.neoforged.neoforge.network.handling.IPayloadContext;

import javax.annotation.Nullable;

/**
* @author TT432
*/
public record RpcSourceType<T>(
Class<T> clazz,
IdGetter<T> idGetter,
ObjectGetter<T> objectGetter
) {
@FunctionalInterface
public interface IdGetter<T> {
long getId(T object);
}

@FunctionalInterface
public interface ObjectGetter<T> {
@Nullable
T getObject(IPayloadContext context, long id);
}
}
41 changes: 41 additions & 0 deletions src/main/java/io/github/tt432/chin/rpc/impl/RpcAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.github.tt432.chin.rpc.impl;

import io.github.tt432.chin.rpc.api.Rpc;
import io.github.tt432.chin.util.ChinNetworks;
import net.neoforged.neoforge.network.PacketDistributor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

/**
* @author TT432
*/
@Aspect
public class RpcAspect {
@Pointcut("@annotation(rpc) && args() && target(target)")
public void rpcPointcut(Rpc rpc, Object target) {
}

@Around("rpcPointcut(rpc, target)")
public Object processRpc(Rpc rpc, Object target, ProceedingJoinPoint joinPoint) throws Throwable {
if (rpc.value().isClient() == ChinNetworks.isClientSide()) {
RpcNetworkPacket<Object> packet = RpcNetworkPacket.of(
target,
target.getClass().getName() + "#" + joinPoint.getSignature().getName()
);

if (packet != null) {
if (rpc.value().isClient()) {
PacketDistributor.sendToServer(packet);
} else {
PacketDistributor.sendToAllPlayers(packet);
}
}

return null;
} else {
return joinPoint.proceed();
}
}
}
132 changes: 132 additions & 0 deletions src/main/java/io/github/tt432/chin/rpc/impl/RpcManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package io.github.tt432.chin.rpc.impl;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import io.github.tt432.chin.rpc.api.Rpc;
import io.github.tt432.chin.rpc.api.RpcSourceRegister;
import io.github.tt432.chin.rpc.api.RpcSourceType;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import lombok.experimental.UtilityClass;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModList;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent;
import net.neoforged.neoforgespi.language.ModFileScanData;

import javax.annotation.Nullable;
import java.lang.annotation.ElementType;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
* @author TT432
*/
@UtilityClass
public class RpcManager {
private final Object2LongMap<String> idToMethodMap = new Object2LongOpenHashMap<>();
private final Long2ObjectMap<MethodHandle> methodToIdMap = new Long2ObjectOpenHashMap<>();

public void handleConfigTask(RpcMethodIdMapPacket pkt) {
var newIdToMethodMap = new Object2LongOpenHashMap<String>();
var newMethodToIdMap = new Long2ObjectOpenHashMap<MethodHandle>();
newIdToMethodMap.putAll(pkt.map());

for (var objectEntry : newIdToMethodMap.object2LongEntrySet()) {
if (idToMethodMap.containsKey(objectEntry.getKey())) {
newMethodToIdMap.put(objectEntry.getLongValue(), methodToIdMap.get(idToMethodMap.getLong(objectEntry.getKey())));
}
}

idToMethodMap.clear();
methodToIdMap.clear();

idToMethodMap.putAll(newIdToMethodMap);
methodToIdMap.putAll(newMethodToIdMap);
}

public void acceptConfigTask(Consumer<CustomPacketPayload> sender) {
sender.accept(new RpcMethodIdMapPacket(idToMethodMap));
}

public long getMethodId(String methodName) {
return idToMethodMap.getOrDefault(methodName, -1L);
}

public void invoke(Object o, long method) {
try {
methodToIdMap.get(method).invoke(o);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}

private final Cache<Class<?>, RpcSourceType<?>> cache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();

@Nullable
@SuppressWarnings("unchecked")
public <T> RpcSourceType<T> getType(Object object) {
if (object == null) return null;

Class<?> oClass = object.getClass();
RpcSourceType<?> cacheValue = cache.getIfPresent(oClass);

if (cacheValue != null) {
return (RpcSourceType<T>) cacheValue;
} else {
RpcSourceType<?> newValue = null;

for (var entry : RpcSourceRegister.REGISTRY.entrySet()) {
if (entry.getValue().clazz().isInstance(object)) {
newValue = entry.getValue();
}
}

if (newValue != null) {
cache.put(oClass, newValue);
return (RpcSourceType<T>) newValue;
}

return null;
}
}

@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD)
public static final class EventListener {
@SubscribeEvent
public static void onEvent(FMLCommonSetupEvent event) {
final long[] id = {0};
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodType voidType = MethodType.methodType(void.class);

for (ModFileScanData allScanDatum : ModList.get().getAllScanData()) {
allScanDatum.getAnnotatedBy(Rpc.class, ElementType.METHOD).forEach(data -> {
long idIn = id[0]++;
String className = data.clazz().getClassName();
String methodName = data.memberName().split("\\(\\)")[0];
idToMethodMap.put(className + "#" + methodName, idIn);
try {
methodToIdMap.put(
idIn,
lookup.findVirtual(
Class.forName(className, false, RpcManager.class.getClassLoader()),
methodName,
voidType
)
);
} catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
});
}
}
}
}
Loading

0 comments on commit db1a194

Please sign in to comment.