package cn.annoreg.mc.network;
import io.netty.buffer.ByteBuf;
import java.util.HashMap;
import java.util.Map;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import cn.annoreg.core.Registrant;
import cn.annoreg.mc.RegInit;
import cn.annoreg.mc.s11n.DataSerializer;
import cn.annoreg.mc.s11n.InstanceSerializer;
import cn.annoreg.mc.s11n.SerializationManager;
import cn.annoreg.mc.s11n.StorageOption;
import cpw.mods.fml.common.network.ByteBufUtils;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.network.simpleimpl.IMessage;
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
import cpw.mods.fml.relauncher.Side;
/**
* A wrapper for a returned object for network call.
* @author acaly
*
*/
public final class Future {
static SimpleNetworkWrapper wrapper;
public static void init() {
wrapper = NetworkRegistry.INSTANCE.newSimpleChannel("core_ar_futuresync");
wrapper.registerMessage(FutureSyncMessageHandler.class, FutureSyncMessage.class, 1, Side.SERVER);
wrapper.registerMessage(FutureSyncMessageHandler.class, FutureSyncMessage.class, 2, Side.CLIENT);
}
public static interface FutureCallback<T> {
/**
* Called when the future gets its value
* @param val The returned value
*/
void onReady(T val);
}
private static final ThreadLocal<Map<Integer, Future>> futureMap = new ThreadLocal<Map<Integer, Future>>() {
@Override
protected Map<Integer, Future> initialValue() {
return new HashMap();
}
};
private static ThreadLocal<Integer> nextId = new ThreadLocal<Integer>() {@Override
protected Integer initialValue() {
return 0;
}
};
private StorageOption.Option syncOption = StorageOption.Option.NULL;
private NetworkTerminal target;
private int id;
private Object returned;
private FutureCallback callback;
/**
* Create a new Future object.
* This object can be directly passed to a network call as an argument,
* or stored for later check.
* @return
*/
public static Future create() {
Future ret = new Future();
ret.target = NetworkTerminal.create();
int id = nextId.get();
ret.id = id;
nextId.set(id + 1);
futureMap.get().put(id, ret);
return ret;
}
/**
* Create a new Future object and bind a callback to it.
* @param callback
* @return
*/
public static <T> Future create(FutureCallback<T> callback) {
Future ret = create();
ret.onSync(callback);
return ret;
}
private static Future findById(int id) {
return futureMap.get().get(id);
}
private Future() {}
/**
* Get the returned value, if any.
* Return null if no value is received yet.
* @return
*/
public Object get() {
return this.returned;
}
/**
* Check if the value contained in this object is null.
* Return true if no value is returned yet, or the returned value is null.
* @return
*/
public boolean isEmpty() {
return this.returned == null;
}
/**
* Set a callback to be called once a value is returned for remote.
* @param callback
* @return
*/
public Future onSync(FutureCallback callback) {
if (this.callback != null) {
throw new RuntimeException("Can not bind multiple sync callbacks");
}
this.callback = callback;
return this;
}
/**
* Called at remote. Return an object to this Future.
* A Future can receive ONLY ONE value from any remote.
* Call this function more than once will cause an exception.
* @param obj
*/
public void setAndSync(Object obj) {
new FutureSyncMessage(this.target, this.id, obj, this.syncOption).send();
}
private static Future createWithId(NetworkTerminal target, int id, StorageOption.Option storage) {
Future ret = new Future();
ret.target = target;
ret.id = id;
ret.syncOption = storage;
return ret;
}
private void onDataReceived(Object obj) {
futureMap.get().remove(this.id);
this.returned = obj;
if (this.callback != null) {
this.callback.onReady(obj);
}
}
public static class FutureSerializer implements DataSerializer<Future>, InstanceSerializer<Future> {
@Override
public Future readInstance(NBTBase nbt) throws Exception {
NBTTagCompound comp = (NBTTagCompound) nbt;
int id = comp.getInteger("id");
NetworkTerminal target = NetworkTerminal.fromNBT(comp.getTag("target"));
return Future.createWithId(target, id, StorageOption.Option.INSTANCE);
}
@Override
public NBTBase writeInstance(Future obj) throws Exception {
NBTTagCompound comp = new NBTTagCompound();
comp.setInteger("id", obj.id);
comp.setTag("target", obj.target.toNBT());
return comp;
}
@Override
public Future readData(NBTBase nbt, Future obj) throws Exception {
NBTTagCompound comp = (NBTTagCompound) nbt;
int id = comp.getInteger("id");
NetworkTerminal target = NetworkTerminal.fromNBT(comp.getTag("target"));
return Future.createWithId(target, id, StorageOption.Option.DATA);
}
@Override
public NBTBase writeData(Future obj) throws Exception {
NBTTagCompound comp = new NBTTagCompound();
comp.setInteger("id", obj.id);
comp.setTag("target", obj.target.toNBT());
return comp;
}
}
public static class FutureSyncMessage implements IMessage {
public NetworkTerminal target; //only used in send
public int id;
public StorageOption.Option storage;
public NBTBase data;
@Override
public void fromBytes(ByteBuf buf) {
id = buf.readInt();
data = ByteBufUtils.readTag(buf).getTag("data");
storage = StorageOption.Option.values()[buf.readInt()];
}
@Override
public void toBytes(ByteBuf buf) {
buf.writeInt(id);
NBTTagCompound write = new NBTTagCompound();
write.setTag("data", data);
ByteBufUtils.writeTag(buf, write);
buf.writeInt(storage.ordinal());
}
public FutureSyncMessage() {}
public FutureSyncMessage(NetworkTerminal target, int id, Object obj, StorageOption.Option storage) {
this.target = target;
this.id = id;
this.data = SerializationManager.INSTANCE.serialize(obj, storage);
this.storage = storage;
}
public void send() {
target.send(wrapper, this);
}
}
public static class FutureSyncMessageHandler implements IMessageHandler<FutureSyncMessage, IMessage> {
@Override
public IMessage onMessage(FutureSyncMessage message, MessageContext ctx) {
Future f = Future.findById(message.id);
if (f == null) {
throw new RuntimeException("Can not find the Future object.");
}
NBTBase objNbt = message.data;
Object obj = SerializationManager.INSTANCE.deserialize(null, objNbt, message.storage);
f.onDataReceived(obj);
return null;
}
}
}