From 3cf761226ee7ddc828af0eb7732140add2bd3e03 Mon Sep 17 00:00:00 2001 From: SpaceToad Date: Sat, 11 Jan 2014 17:27:53 +0100 Subject: [PATCH] Polymorph classes supported in serializer. The serializer now supports cases where the actual object is different from the object declared, sending once the class name on the stream. --- common/buildcraft/builders/TileArchitect.java | 12 +- .../builders/TileBlueprintLibrary.java | 19 +-- .../builders/blueprints/Blueprint.java | 4 +- .../blueprints/BlueprintDatabase.java | 42 ++++- .../buildcraft/core/network/ClassMapping.java | 155 +++++++++++++----- .../buildcraft/core/network/RPCHandler.java | 37 +++-- common/buildcraft/core/proxy/CoreProxy.java | 5 +- 7 files changed, 196 insertions(+), 78 deletions(-) diff --git a/common/buildcraft/builders/TileArchitect.java b/common/buildcraft/builders/TileArchitect.java index 4f154cdf..77b73162 100644 --- a/common/buildcraft/builders/TileArchitect.java +++ b/common/buildcraft/builders/TileArchitect.java @@ -13,10 +13,12 @@ import buildcraft.api.core.LaserKind; import buildcraft.builders.blueprints.Blueprint; import buildcraft.builders.blueprints.BlueprintDatabase; import buildcraft.core.Box; +import buildcraft.core.DefaultProps; import buildcraft.core.TileBuildCraft; import buildcraft.core.network.PacketUpdate; import buildcraft.core.network.NetworkData; import buildcraft.core.network.RPC; +import buildcraft.core.network.RPCHandler; import buildcraft.core.network.RPCSide; import buildcraft.core.proxy.CoreProxy; import buildcraft.core.utils.Utils; @@ -144,7 +146,7 @@ public class TileArchitect extends TileBuildCraft implements IInventory { return blueprint; } - @RPC + @RPC (RPCSide.SERVER) public void handleClientInput(char c) { if (c == 8) { if (name.length() > 0) { @@ -155,7 +157,13 @@ public class TileArchitect extends TileBuildCraft implements IInventory { name += c; } } - sendNetworkUpdate(); + + RPCHandler.rpcBroadcastPlayers(this, "setName", DefaultProps.NETWORK_UPDATE_RANGE, name); + } + + @RPC + public void setName (String name) { + this.name = name; } @Override diff --git a/common/buildcraft/builders/TileBlueprintLibrary.java b/common/buildcraft/builders/TileBlueprintLibrary.java index 0ec3567c..1d543605 100644 --- a/common/buildcraft/builders/TileBlueprintLibrary.java +++ b/common/buildcraft/builders/TileBlueprintLibrary.java @@ -1,6 +1,7 @@ package buildcraft.builders; import buildcraft.BuildCraftBuilders; +import buildcraft.builders.blueprints.BlueprintDatabase; import buildcraft.builders.blueprints.BlueprintMeta; import buildcraft.core.TileBuildCraft; import buildcraft.core.blueprints.BptBase; @@ -10,21 +11,15 @@ import buildcraft.core.network.RPC; import buildcraft.core.network.RPCHandler; import buildcraft.core.network.RPCMessageInfo; import buildcraft.core.network.RPCSide; -import buildcraft.core.network.NetworkData; import buildcraft.core.proxy.CoreProxy; -import buildcraft.core.utils.Utils; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; - -import com.google.common.io.ByteStreams; +import java.util.List; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -import paulscode.sound.Library; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; @@ -105,20 +100,20 @@ public class TileBlueprintLibrary extends TileBuildCraft implements IInventory { @SideOnly (Side.CLIENT) public void updateCurrentNames() { currentBlueprint.clear(); - RPCHandler.rpcServer(this, "fetchPage", 1); + RPCHandler.rpcServer(this, "fetchPage", 0); } @RPC (RPCSide.SERVER) public void fetchPage (int pageId, RPCMessageInfo info) { - for (int i = 0; i < BuildCraftBuilders.LIBRARY_PAGE_SIZE; ++i) { - BlueprintMeta dummyMeta = new BlueprintMeta (); - dummyMeta.name = "Blueprint #" + i; + List metas = BlueprintDatabase.getPage(pageId, 12); - RPCHandler.rpcPlayer(this, "receiveBlueprintMeta", info.sender, dummyMeta); + for (BlueprintMeta meta : metas) { + RPCHandler.rpcPlayer(this, "receiveBlueprintMeta", info.sender, meta); } } @RPC (RPCSide.CLIENT) + // TODO: It would be more efficient to get directly a list or an array here public void receiveBlueprintMeta (BlueprintMeta meta) { currentBlueprint.add(meta.name); } diff --git a/common/buildcraft/builders/blueprints/Blueprint.java b/common/buildcraft/builders/blueprints/Blueprint.java index d8057eae..9793a778 100644 --- a/common/buildcraft/builders/blueprints/Blueprint.java +++ b/common/buildcraft/builders/blueprints/Blueprint.java @@ -39,9 +39,7 @@ public class Blueprint { @NetworkData public BlueprintMeta meta; - // TODO: may need additional support from the serialization system: - // Schematic is an abstract class, the actual objects to serialize - // are sub classes + @NetworkData private final Schematic[][][] schematics; @NetworkData diff --git a/common/buildcraft/builders/blueprints/BlueprintDatabase.java b/common/buildcraft/builders/blueprints/BlueprintDatabase.java index cc011b6e..117bc1d0 100644 --- a/common/buildcraft/builders/blueprints/BlueprintDatabase.java +++ b/common/buildcraft/builders/blueprints/BlueprintDatabase.java @@ -19,7 +19,10 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; @@ -40,7 +43,7 @@ import net.minecraft.nbt.NBTTagCompound; public class BlueprintDatabase { /** * Initialize the blueprint database. - * + * * @param configDir config directory to read the blueprints from. */ public static void init(File configDir) { @@ -56,18 +59,47 @@ public class BlueprintDatabase { /** * Get a list with the metadata for all available blueprints. - * + * * @return meta data iterable */ public static Iterable getList() { return blueprintMetas.values(); } + /** + * Return a list of blueprint on a given page. + * + * FIXME: This returns blueprints in no particular order. We probably want + * to have an ordered list of blueprint instead + */ + public static List getPage (int pageId, int pageSize) { + List result = new ArrayList(); + + int start = pageId * pageSize; + int stop = (pageId + 1) * pageSize; + + int i = 0; + + for (BlueprintMeta meta : blueprintMetas.values()) { + i++; + + if (i >= stop) { + break; + } + + if (i >= start) { + result.add (meta); + } + } + + return result; + } + /** * Get a specific blueprint by id. - * + * * @note The blueprint will be loaded as needed. - * + * * @param id blueprint id * @return blueprint or null if it can't be retrieved */ @@ -86,7 +118,7 @@ public class BlueprintDatabase { /** * Add a blueprint to the database and save it to disk. - * + * * @param blueprint blueprint to add * @return id for the added blueprint */ diff --git a/common/buildcraft/core/network/ClassMapping.java b/common/buildcraft/core/network/ClassMapping.java index 0642cf4a..6b415932 100644 --- a/common/buildcraft/core/network/ClassMapping.java +++ b/common/buildcraft/core/network/ClassMapping.java @@ -9,14 +9,13 @@ package buildcraft.core.network; -import buildcraft.BuildCraftCore; - import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Type; +import java.util.ArrayList; import java.util.LinkedList; import java.util.Map; import java.util.TreeMap; @@ -58,6 +57,12 @@ import java.util.TreeMap; * */ public class ClassMapping { + + private class SerializationContext { + public ArrayList idToClass = new ArrayList (); + public Map classToId = new TreeMap (); + } + private LinkedList floatFields = new LinkedList(); private LinkedList doubleFields = new LinkedList(); private LinkedList stringFields = new LinkedList(); @@ -65,11 +70,15 @@ public class ClassMapping { private LinkedList intFields = new LinkedList(); private LinkedList booleanFields = new LinkedList(); private LinkedList enumFields = new LinkedList(); - private LinkedList objectFields = new LinkedList(); - private Field field; + class FieldObject { + public Field field; + public ClassMapping mapping; + } - private Class clas; + private LinkedList objectFields = new LinkedList(); + + private Class mappedClass; enum CptType { Byte, @@ -98,7 +107,7 @@ public class ClassMapping { @SuppressWarnings({ "rawtypes", "unchecked" }) public void analyzeClass(final Class c) { try { - clas = c; + mappedClass = c; if (c.isArray()) { Class cptClass = c.getComponentType(); @@ -149,13 +158,11 @@ public class ClassMapping { } else if (fieldClass.equals(double.class)) { doubleFields.add(f); } else { - // ADD SOME SAFETY HERE - if we're not child of - // Object + FieldObject obj = new FieldObject(); + obj.mapping = get (fieldClass); + obj.field = f; - ClassMapping mapping = new ClassMapping(fieldClass); - mapping.field = f; - - objectFields.add(mapping); + objectFields.add(obj); } } } @@ -172,17 +179,25 @@ public class ClassMapping { return updateAnnotation != null; } + public void setData(Object obj, DataOutputStream data) throws IllegalArgumentException, IllegalAccessException, IOException { - if (clas.isArray()) { - setDataArray(obj, data); + SerializationContext context = new SerializationContext(); + + setDataInt(obj, data, context); + } + + public void setDataInt(Object obj, DataOutputStream data, SerializationContext context) throws IllegalArgumentException, + IllegalAccessException, IOException { + if (mappedClass.isArray()) { + setDataArray(obj, data, context); } else { - setDataClass(obj, data); + setDataClass(obj, data, context); } } @SuppressWarnings("rawtypes") - private void setDataClass(Object obj, DataOutputStream data) throws IllegalArgumentException, + private void setDataClass(Object obj, DataOutputStream data, SerializationContext context) throws IllegalArgumentException, IllegalAccessException, IOException { for (Field f : shortFields) { @@ -220,21 +235,42 @@ public class ClassMapping { } } - for (ClassMapping c : objectFields) { - Object cpt = c.field.get(obj); + for (FieldObject f : objectFields) { + Object cpt = f.field.get(obj); + ClassMapping mapping = f.mapping; if (cpt == null) { data.writeBoolean(false); } else { + Class realClass = cpt.getClass(); + data.writeBoolean(true); - c.setData(cpt, data); + + if (realClass.equals(f.mapping.mappedClass)) { + data.writeByte(0); + } else { + if (context.classToId.containsKey(realClass.getCanonicalName())) { + int index = context.classToId.get(realClass.getCanonicalName()); + data.writeByte(index); + mapping = context.idToClass.get(index); + } else { + int index = context.classToId.size() + 1; + data.writeByte(index); + data.writeUTF(realClass.getCanonicalName()); + context.classToId.put(realClass.getCanonicalName(), context.classToId.size()); + } + + mapping = get (realClass); + } + + mapping.setDataInt(cpt, data, context); } } } - private void setDataArray(Object obj, DataOutputStream data) throws IllegalArgumentException, + private void setDataArray(Object obj, DataOutputStream data, SerializationContext context) throws IllegalArgumentException, IllegalAccessException, IOException { - Class cpt = clas.getComponentType(); + Class cpt = mappedClass.getComponentType(); switch (cptType) { case Byte: { @@ -321,7 +357,7 @@ public class ClassMapping { data.writeBoolean(false); } else { data.writeBoolean(true); - cptMapping.setData(arr [i], data); + cptMapping.setDataInt(arr [i], data, context); } } @@ -362,26 +398,35 @@ public class ClassMapping { * * WARNINGS * - * - class instantiation will be done after the field type, not - * the actual type used in serialization. Generally speaking, this system - * is not robust to polymorphism - * - only public non-final fields can be serialized + * - only public non-final fields can be serialized + * - non static nested classes are not supported + * - no reference analysis is done, e.g. an object referenced twice will + * be serialized twice + * + * @throws ClassNotFoundException */ - public Object updateFromData(Object obj, DataInputStream data) throws IllegalArgumentException, - IllegalAccessException, IOException, InstantiationException { - if (clas.isArray()) { - return updateFromDataArray(obj, data); + public Object updateFromData (Object obj, DataInputStream data) throws IllegalArgumentException, + IllegalAccessException, IOException, InstantiationException, ClassNotFoundException { + SerializationContext context = new SerializationContext(); + + return updateFromDataInt(obj, data, context); + } + + private Object updateFromDataInt (Object obj, DataInputStream data, SerializationContext context) throws IllegalArgumentException, + IllegalAccessException, IOException, InstantiationException, ClassNotFoundException { + if (mappedClass.isArray()) { + return updateFromDataArray(obj, data, context); } else { - return updateFromDataClass(obj, data); + return updateFromDataClass(obj, data, context); } } @SuppressWarnings("rawtypes") - public Object updateFromDataClass(Object obj, DataInputStream data) throws IllegalArgumentException, - IllegalAccessException, IOException, InstantiationException { + public Object updateFromDataClass(Object obj, DataInputStream data, SerializationContext context) throws IllegalArgumentException, + IllegalAccessException, IOException, InstantiationException, ClassNotFoundException { if (obj == null) { - obj = clas.newInstance(); + obj = mappedClass.newInstance(); } for (Field f : shortFields) { @@ -416,20 +461,48 @@ public class ClassMapping { } } - for (ClassMapping c : objectFields) { + // The data layout for an object is the following: + // [boolean] does the object exist (e.g. non-null) + // {false} exit + // [int] what is the object real class? + // {0} the same as the declared class + // {1-x} a different one + // [string] if the number is not yet registered, the name of the + // class + // [bytes] the actual contents + + for (FieldObject f : objectFields) { if (data.readBoolean()) { - c.field.set (obj, c.updateFromData(c.field.get(obj), data)); + ClassMapping mapping = f.mapping; + + int index = data.readByte(); + + if (index != 0) { + if (context.idToClass.size() < index) { + String className = data.readUTF(); + + Class cls = Class.forName(className); + + mapping = get (cls); + + context.idToClass.add(get (cls)); + } else { + mapping = context.idToClass.get(index); + } + } + + f.field.set (obj, mapping.updateFromDataInt(f.field.get(obj), data, context)); } else { - c.field.set(obj, null); + f.field.set(obj, null); } } return obj; } - private Object updateFromDataArray(Object obj, DataInputStream data) throws IllegalArgumentException, - IllegalAccessException, IOException, InstantiationException { - Class cpt = clas.getComponentType(); + private Object updateFromDataArray(Object obj, DataInputStream data, SerializationContext context) throws IllegalArgumentException, + IllegalAccessException, IOException, InstantiationException, ClassNotFoundException { + Class cpt = mappedClass.getComponentType(); int size = data.readInt(); @@ -568,7 +641,7 @@ public class ClassMapping { for (int i = 0; i < arr.length; ++i) { if (data.readBoolean()) { - arr [i] = cptMapping.updateFromData(arr [i], data); + arr [i] = cptMapping.updateFromDataInt(arr [i], data, context); } else { arr [i] = null; } diff --git a/common/buildcraft/core/network/RPCHandler.java b/common/buildcraft/core/network/RPCHandler.java index 50b040bb..ca9f8c5c 100755 --- a/common/buildcraft/core/network/RPCHandler.java +++ b/common/buildcraft/core/network/RPCHandler.java @@ -4,26 +4,18 @@ import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; -import java.io.ObjectOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.Comparator; -import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.TreeMap; import buildcraft.core.proxy.CoreProxy; -import buildcraft.core.proxy.CoreProxyClient; -import cpw.mods.fml.client.FMLClientHandler; -import cpw.mods.fml.common.network.Player; import net.minecraft.entity.player.EntityPlayer; -import net.minecraft.network.packet.Packet; +import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.tileentity.TileEntity; -import net.minecraftforge.client.MinecraftForgeClient; -import net.minecraftforge.common.DimensionManager; -import net.minecraftforge.common.MinecraftForge; /** * This is a first implementation of a RPC connector, using the regular tile @@ -115,6 +107,26 @@ public class RPCHandler { } } + public static void rpcBroadcastPlayers (TileEntity tile, String method, int maxDistance, Object ... actuals) { + if (!handlers.containsKey(tile.getClass().getName())) { + handlers.put (tile.getClass().getName(), new RPCHandler (tile.getClass())); + } + + PacketRPC packet = handlers.get (tile.getClass().getName()).createRCPPacket(tile, method, actuals); + + if (packet != null) { + for (Object o : tile.worldObj.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) { + CoreProxy.proxy.sendToPlayer(player, packet); + } + } + } + } + public static void receiveRPC (TileEntity tile, RPCMessageInfo info, DataInputStream data) { if (!handlers.containsKey(tile.getClass().getName())) { handlers.put (tile.getClass().getName(), new RPCHandler (tile.getClass())); @@ -210,19 +222,16 @@ public class RPCHandler { m.method.invoke(tile, actuals); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { - // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ClassNotFoundException e) { e.printStackTrace(); } diff --git a/common/buildcraft/core/proxy/CoreProxy.java b/common/buildcraft/core/proxy/CoreProxy.java index 14ea71c5..64323684 100644 --- a/common/buildcraft/core/proxy/CoreProxy.java +++ b/common/buildcraft/core/proxy/CoreProxy.java @@ -54,7 +54,10 @@ public class CoreProxy { return null; } - /* SIMULATION */ + /** + * Return true if this world is holding the actual game simulation, that is + * for example if this is the server world. + */ public boolean isSimulating(World world) { return !world.isRemote; }