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.
This commit is contained in:
SpaceToad 2014-01-11 17:27:53 +01:00
parent 08d01c0394
commit 3cf761226e
7 changed files with 196 additions and 78 deletions

View file

@ -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

View file

@ -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 <BlueprintMeta> 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);
}

View file

@ -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

View file

@ -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<BlueprintMeta> 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 <BlueprintMeta> getPage (int pageId, int pageSize) {
List <BlueprintMeta> result = new ArrayList<BlueprintMeta>();
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
*/

View file

@ -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<ClassMapping> idToClass = new ArrayList<ClassMapping> ();
public Map <String, Integer> classToId = new TreeMap<String, Integer> ();
}
private LinkedList<Field> floatFields = new LinkedList<Field>();
private LinkedList<Field> doubleFields = new LinkedList<Field>();
private LinkedList<Field> stringFields = new LinkedList<Field>();
@ -65,11 +70,15 @@ public class ClassMapping {
private LinkedList<Field> intFields = new LinkedList<Field>();
private LinkedList<Field> booleanFields = new LinkedList<Field>();
private LinkedList<Field> enumFields = new LinkedList<Field>();
private LinkedList<ClassMapping> objectFields = new LinkedList<ClassMapping>();
private Field field;
class FieldObject {
public Field field;
public ClassMapping mapping;
}
private Class<? extends Object> clas;
private LinkedList<FieldObject> objectFields = new LinkedList<FieldObject>();
private Class<? extends Object> mappedClass;
enum CptType {
Byte,
@ -98,7 +107,7 @@ public class ClassMapping {
@SuppressWarnings({ "rawtypes", "unchecked" })
public void analyzeClass(final Class<? extends Object> 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<? extends Object> cpt = clas.getComponentType();
Class<? extends Object> 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<? extends Object> cpt = clas.getComponentType();
private Object updateFromDataArray(Object obj, DataInputStream data, SerializationContext context) throws IllegalArgumentException,
IllegalAccessException, IOException, InstantiationException, ClassNotFoundException {
Class<? extends Object> 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;
}

View file

@ -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();
}

View file

@ -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;
}