/******************************************************************************* * AbyssalCraft * Copyright (c) 2012 - 2017 Shinoow. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl-3.0.txt * * Contributors: * Shinoow - implementation ******************************************************************************/ package com.shinoow.abyssalcraft.common.network; import io.netty.buffer.ByteBuf; import java.io.IOException; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.network.Packet; import net.minecraft.network.PacketBuffer; import net.minecraft.util.IThreadListener; import com.google.common.base.Throwables; import com.shinoow.abyssalcraft.AbyssalCraft; import net.minecraftforge.fml.common.network.simpleimpl.IMessage; import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; import net.minecraftforge.fml.relauncher.Side; /** * * Creating an abstract message class similar to {@link Packet} allows us to take * advantage of Minecraft's PacketBuffer class, which has many useful methods. * * Should the IMessageHandler be implemented as a GenericMessageHandler class, * every child message class will still have to have an empty implementation of * the handler, just so it can be registered: * * public static class Handler extends GenericMessageHandler<SomeMessage> {} * * This is kind of ridiculous, so instead the message will also implement the handler, * despite the fact that a handler instance shouldn't have any of the class members or * methods that a message instance does (since a handler is not a message). * * To combat that incongruity, the #onMessage method will be made final. * * As a bonus, registration of this class can be made less verbose than normal, since the * same class is used for both the IMessage and the IMessageHandler. * */ public abstract class AbstractMessage<T extends AbstractMessage<T>> implements IMessage, IMessageHandler <T, IMessage> { /** * Some PacketBuffer methods throw IOException - default handling propagates the exception. * if an IOException is expected but should not be fatal, handle it within this method. */ protected abstract void read(PacketBuffer buffer) throws IOException; /** * Some PacketBuffer methods throw IOException - default handling propagates the exception. * if an IOException is expected but should not be fatal, handle it within this method. */ protected abstract void write(PacketBuffer buffer) throws IOException; /** * Called on whichever side the message is received; * for bidirectional packets, be sure to check side */ public abstract void process(EntityPlayer player, Side side); /** * If message is sent to the wrong side, an exception will be thrown during handling * @return True if the message is allowed to be handled on the given side */ protected boolean isValidOnSide(Side side) { return true; // default allows handling on both sides, i.e. a bidirectional packet } /** * Whether this message requires the main thread to be processed (i.e. it * requires that the world, player, and other objects are in a valid state). */ protected boolean requiresMainThread() { return true; } @Override public void fromBytes(ByteBuf buffer) { try { read(new PacketBuffer(buffer)); } catch (IOException e) { throw Throwables.propagate(e); } } @Override public void toBytes(ByteBuf buffer) { try { write(new PacketBuffer(buffer)); } catch (IOException e) { throw Throwables.propagate(e); } } //=====================================================================// /* * Make the implementation final so child classes don't need to bother * with it, since the message class shouldn't have anything to do with * the handler. This is simply to avoid having to have: * * public static class Handler extends GenericMessageHandler<OpenGuiMessage> {} * * in every single message class for the sole purpose of registration. */ @Override public final IMessage onMessage(T msg, MessageContext ctx) { if (!msg.isValidOnSide(ctx.side)) throw new RuntimeException("Invalid side " + ctx.side.name() + " for " + msg.getClass().getSimpleName()); else if(msg.requiresMainThread()) checkThreadAndEnqueue(msg, ctx); else msg.process(AbyssalCraft.proxy.getPlayerEntity(ctx), ctx.side); return null; } /** * Ensures that the message is being handled on the main thread */ private static final <T extends AbstractMessage<T>> void checkThreadAndEnqueue(final AbstractMessage<T> msg, final MessageContext ctx) { IThreadListener thread = AbyssalCraft.proxy.getThreadFromContext(ctx); if (!thread.isCallingFromMinecraftThread()) thread.addScheduledTask(() -> msg.process(AbyssalCraft.proxy.getPlayerEntity(ctx), ctx.side)); } /** * Messages that can only be sent from the server to the client should use this class */ public static abstract class AbstractClientMessage<T extends AbstractMessage<T>> extends AbstractMessage<T> { @Override protected final boolean isValidOnSide(Side side) { return side.isClient(); } } /** * Messages that can only be sent from the client to the server should use this class */ public static abstract class AbstractServerMessage<T extends AbstractMessage<T>> extends AbstractMessage<T> { @Override protected final boolean isValidOnSide(Side side) { return side.isServer(); } } }