buildcraft.core.network.RPCHandler.java Source code

Java tutorial

Introduction

Here is the source code for buildcraft.core.network.RPCHandler.java

Source

/**
 * Copyright (c) 2011-2014, SpaceToad and the BuildCraft Team
 * http://www.mod-buildcraft.com
 *
 * BuildCraft is distributed under the terms of the Minecraft Mod Public
 * License 1.0, or MMPL. Please check the contents of the license located in
 * http://www.mod-buildcraft.com/MMPL-1.0.txt
 */
package buildcraft.core.network;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;

import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.tileentity.TileEntity;

import buildcraft.BuildCraftCore;
import buildcraft.api.core.JavaTools;
import buildcraft.core.DefaultProps;
import buildcraft.core.network.serializers.ClassMapping;
import buildcraft.core.network.serializers.ClassSerializer;
import buildcraft.core.network.serializers.SerializationContext;
import buildcraft.transport.Pipe;

/**
 * This is a first implementation of a RPC connector, using the regular tile
 * synchronization layers as a communication protocol. As a result, these
 * RPCs must be sent and received by a tile entity.
 */
public final class RPCHandler {

    public static int MAX_PACKET_SIZE = 30 * 1024;

    private static Map<String, RPCHandler> handlers = new TreeMap<String, RPCHandler>();

    private Map<String, Integer> methodsMap = new TreeMap<String, Integer>();

    class MethodMapping {
        Method method;
        Class[] parameters;
        ClassSerializer[] mappings;
        boolean hasInfo = false;
    }

    private MethodMapping[] methods;

    private RPCHandler(Class c) {
        Method[] sortedMethods = JavaTools.getAllMethods(c).toArray(new Method[0]);

        LinkedList<MethodMapping> mappings = new LinkedList<MethodMapping>();

        Arrays.sort(sortedMethods, new Comparator<Method>() {
            @Override
            public int compare(Method o1, Method o2) {
                return o1.getName().compareTo(o2.getName());
            }
        });

        LinkedList<Method> rpcMethods = new LinkedList<Method>();

        for (Method sortedMethod : sortedMethods) {
            if (sortedMethod.getAnnotation(RPC.class) != null) {
                sortedMethod.setAccessible(true);
                methodsMap.put(sortedMethod.getName(), rpcMethods.size());
                rpcMethods.add(sortedMethod);

                MethodMapping mapping = new MethodMapping();
                mapping.method = sortedMethod;
                mapping.parameters = sortedMethod.getParameterTypes();
                mapping.mappings = new ClassSerializer[mapping.parameters.length];

                for (int j = 0; j < mapping.parameters.length; ++j) {
                    if (int.class.equals(mapping.parameters[j])) {
                        // accepted
                    } else if (char.class.equals(mapping.parameters[j])) {
                        // accepted
                    } else if (float.class.equals(mapping.parameters[j])) {
                        // accepted
                    } else if (mapping.parameters[j].equals(RPCMessageInfo.class)) {
                        mapping.hasInfo = true;
                    } else {
                        mapping.mappings[j] = ClassMapping.get(mapping.parameters[j]);
                    }
                }

                mappings.add(mapping);
            }
        }

        methods = mappings.toArray(new MethodMapping[mappings.size()]);
    }

    public static void rpcServer(TileEntity tile, String method, Object... actuals) {
        if (!handlers.containsKey(tile.getClass().getName())) {
            handlers.put(tile.getClass().getName(), new RPCHandler(tile.getClass()));
        }

        PacketRPCTile packet = handlers.get(tile.getClass().getName()).createRCPPacket(tile, method, actuals);

        if (packet != null) {
            ArrayList<PacketRPCTile> packets = packet.breakIntoSmallerPackets(30 * 1024);

            for (PacketRPCTile p : packet.breakIntoSmallerPackets(MAX_PACKET_SIZE)) {
                BuildCraftCore.instance.sendToServer(p);
            }
        }
    }

    public static void rpcPlayer(TileEntity tile, String method, EntityPlayer player, Object... actuals) {
        if (!handlers.containsKey(tile.getClass().getName())) {
            handlers.put(tile.getClass().getName(), new RPCHandler(tile.getClass()));
        }

        PacketRPCTile packet = handlers.get(tile.getClass().getName()).createRCPPacket(tile, method, actuals);

        if (packet != null) {
            for (PacketRPCTile p : packet.breakIntoSmallerPackets(MAX_PACKET_SIZE)) {
                BuildCraftCore.instance.sendToPlayer(player, p);
            }
        }
    }

    public static void rpcBroadcastDefaultPlayers(Pipe pipe, String method, Object... actuals) {
        RPCHandler.rpcBroadcastPlayers(pipe, method, DefaultProps.NETWORK_UPDATE_RANGE, actuals);
    }

    public static void rpcBroadcastPlayers(TileEntity tile, String method, Object... actuals) {
        RPCHandler.rpcBroadcastPlayersAtDistance(tile, method, DefaultProps.NETWORK_UPDATE_RANGE, actuals);
    }

    public static void rpcBroadcastPlayersAtDistance(TileEntity tile, String method, int maxDistance,
            Object... actuals) {
        if (!handlers.containsKey(tile.getClass().getName())) {
            handlers.put(tile.getClass().getName(), new RPCHandler(tile.getClass()));
        }

        PacketRPCTile packet = handlers.get(tile.getClass().getName()).createRCPPacket(tile, method, actuals);

        if (packet != null) {
            for (PacketRPCTile p : packet.breakIntoSmallerPackets(MAX_PACKET_SIZE)) {
                for (Object o : tile.getWorldObj().playerEntities) {
                    EntityPlayerMP player = (EntityPlayerMP) o;

                    if (Math.abs(player.posX - tile.xCoord) <= maxDistance
                            && Math.abs(player.posY - tile.yCoord) <= maxDistance
                            && Math.abs(player.posZ - tile.zCoord) <= maxDistance) {
                        BuildCraftCore.instance.sendToPlayer(player, p);
                    }
                }
            }
        }
    }

    public static void rpcBroadcastPlayers(Pipe pipe, String method, int maxDistance, Object... actuals) {
        if (!handlers.containsKey(pipe.getClass().getName())) {
            handlers.put(pipe.getClass().getName(), new RPCHandler(pipe.getClass()));
        }

        PacketRPCPipe packet = handlers.get(pipe.getClass().getName()).createRCPPacket(pipe, method, actuals);

        if (packet != null) {
            for (Object o : pipe.container.getWorld().playerEntities) {
                EntityPlayerMP player = (EntityPlayerMP) o;

                if (Math.abs(player.posX - pipe.container.xCoord) <= maxDistance
                        && Math.abs(player.posY - pipe.container.yCoord) <= maxDistance
                        && Math.abs(player.posZ - pipe.container.zCoord) <= maxDistance) {
                    BuildCraftCore.instance.sendToPlayer(player, packet);
                }
            }
        }
    }

    public static void receiveRPC(TileEntity tile, RPCMessageInfo info, ByteBuf data) {
        if (tile != null) {
            if (!handlers.containsKey(tile.getClass().getName())) {
                handlers.put(tile.getClass().getName(), new RPCHandler(tile.getClass()));
            }

            handlers.get(tile.getClass().getName()).internalRpcReceive(tile, info, data);
        }
    }

    public static void receiveRPC(Pipe pipe, RPCMessageInfo info, ByteBuf data) {
        if (pipe != null) {
            if (!handlers.containsKey(pipe.getClass().getName())) {
                handlers.put(pipe.getClass().getName(), new RPCHandler(pipe.getClass()));
            }

            handlers.get(pipe.getClass().getName()).internalRpcReceive(pipe, info, data);
        }
    }

    private PacketRPCPipe createRCPPacket(Pipe pipe, String method, Object... actuals) {
        ByteBuf data = Unpooled.buffer();

        try {
            TileEntity tile = pipe.container;

            // In order to save space on message, we assuming dimensions ids
            // small. Maybe worth using a varint instead
            data.writeShort(tile.getWorldObj().provider.dimensionId);
            data.writeInt(tile.xCoord);
            data.writeInt(tile.yCoord);
            data.writeInt(tile.zCoord);

            writeParameters(method, data, actuals);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        byte[] bytes = new byte[data.readableBytes()];
        data.readBytes(bytes);

        return new PacketRPCPipe(bytes);
    }

    private PacketRPCTile createRCPPacket(TileEntity tile, String method, Object... actuals) {
        ByteBuf data = Unpooled.buffer();

        try {
            writeParameters(method, data, actuals);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        byte[] bytes = new byte[data.readableBytes()];
        data.readBytes(bytes);

        return new PacketRPCTile(tile, bytes);
    }

    private void writeParameters(String method, ByteBuf data, Object... actuals)
            throws IOException, IllegalArgumentException, IllegalAccessException {
        if (!methodsMap.containsKey(method)) {
            throw new RuntimeException(method + " is not a callable method of " + getClass().getName());
        }

        int methodIndex = methodsMap.get(method);
        MethodMapping m = methods[methodIndex];
        Class[] formals = m.parameters;

        int expectedParameters = m.hasInfo ? formals.length - 1 : formals.length;

        if (expectedParameters != actuals.length) {
            // We accept formals + 1 as an argument, in order to support the
            // special last argument RPCMessageInfo

            throw new RuntimeException(getClass().getName() + "." + method + " expects " + m.parameters.length
                    + " parameters, not " + actuals.length);
        }

        data.writeShort(methodIndex);

        SerializationContext context = new SerializationContext();

        for (int i = 0; i < actuals.length; ++i) {
            if (int.class.equals(formals[i])) {
                data.writeInt((Integer) actuals[i]);
            } else if (float.class.equals(formals[i])) {
                data.writeFloat((Float) actuals[i]);
            } else if (char.class.equals(formals[i])) {
                data.writeChar((Character) actuals[i]);
            } else {
                m.mappings[i].write(data, actuals[i], context);
            }
        }
    }

    private void internalRpcReceive(Object o, RPCMessageInfo info, ByteBuf data) {
        try {
            short methodIndex = data.readShort();

            MethodMapping m = methods[methodIndex];
            Class[] formals = m.parameters;

            Object[] actuals = new Object[formals.length];

            int expectedParameters = m.hasInfo ? formals.length - 1 : formals.length;

            SerializationContext context = new SerializationContext();

            for (int i = 0; i < expectedParameters; ++i) {
                if (int.class.equals(formals[i])) {
                    actuals[i] = data.readInt();
                } else if (float.class.equals(formals[i])) {
                    actuals[i] = data.readFloat();
                } else if (char.class.equals(formals[i])) {
                    actuals[i] = data.readChar();
                } else {
                    actuals[i] = m.mappings[i].read(data, actuals[i], context);
                }
            }

            if (m.hasInfo) {
                actuals[actuals.length - 1] = info;
            }

            m.method.invoke(o, actuals);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }

}