Jump to content

[SOLVED] Trying to make IMessageHandler more generic


coolAlias

Recommended Posts

SOLUTION: Update Forge to 1121 or later; on 1060, the IMessage system is apparently still broken, as the following generic class worked perfectly as soon as I updated! Though some may view it as pointless, I think it's pretty f-ing sweet. Now if only I could change the names of fromBytes and toBytes to include 'read' and 'write'.... :P

 

Original post:

 

Hey all,

 

So, I decided to attempt to make an abstract class implementing IMessageHandler to do some of the monotonous work for me, like so:

 

public abstract class AbstractMessage<T extends IMessage> implements IMessage, IMessageHandler <T, IMessage> {

// auto-incrementing byte discriminators should really have been built in from the start... =.=
private static byte packetId = 0;

public static void registerMessages() {
// note that: OpenGuiMessage extends AbstractMessage<OpenGuiMessage>
TestMain.dispatcher.registerMessage(OpenGuiMessage.class, OpenGuiMessage.class, packetId++, Side.SERVER);
}

/**
* Handle a message received on the client side; messages can now only be handled on ONE side
* @return a message to send back to the Server, or null if no reply is necessary
*/
public abstract IMessage handleClientMessage(EntityPlayer player, T message, MessageContext ctx);

/**
* Handle a message received on the server side; messages can now only be handled on ONE side
* @return a message to send back to the Client, or null if no reply is necessary
*/
public abstract IMessage handleServerMessage(EntityPlayer player, T message, MessageContext ctx);

@Override
public IMessage onMessage(T message, MessageContext ctx) {
if (ctx.side == Side.CLIENT) {
return handleClientMessage(Minecraft.getMinecraft().thePlayer, message, ctx);
} else {
return handleServerMessage(ctx.getServerHandler().playerEntity, message, ctx);
}
}
}

 

 

While the code seems like it should work and compiles fine, netty/FML can't seem to figure out what to do with it:

 

java.lang.IllegalStateException: cannot determine the type of the type parameter 'REQ': class cpw.mods.fml.common.network.simpleimpl.SimpleChannelHandlerWrapper
at io.netty.util.internal.TypeParameterMatcher.fail(TypeParameterMatcher.java:171) ~[TypeParameterMatcher.class:?]
at io.netty.util.internal.TypeParameterMatcher.find0(TypeParameterMatcher.java:165) ~[TypeParameterMatcher.class:?]
at io.netty.util.internal.TypeParameterMatcher.find(TypeParameterMatcher.java:93) ~[TypeParameterMatcher.class:?]
at io.netty.channel.SimpleChannelInboundHandler.<init>(SimpleChannelInboundHandler.java:60) ~[simpleChannelInboundHandler.class:?]
at io.netty.channel.SimpleChannelInboundHandler.<init>(SimpleChannelInboundHandler.java:50) ~[simpleChannelInboundHandler.class:?]
at cpw.mods.fml.common.network.simpleimpl.SimpleChannelHandlerWrapper.<init>(SimpleChannelHandlerWrapper.java:17) ~[simpleChannelHandlerWrapper.class:?]
at cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper.getHandlerWrapper(SimpleNetworkWrapper.java:85) ~[simpleNetworkWrapper.class:?]
at cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper.addServerHandlerAfter(SimpleNetworkWrapper.java:73) ~[simpleNetworkWrapper.class:?]
at cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper.registerMessage(SimpleNetworkWrapper.java:63) ~[simpleNetworkWrapper.class:?]
at testingstuff.network.AbstractMessage.registerMessages(AbstractMessage.java:37) ~[AbstractMessage.class:?]

 

 

I know it's not really necessary, but I find it quite tedious (and messy-looking) having to write out "ctx.getServerHandler().playerEntity" every time I want to get the player, as well as recall which side I'm supposed to be on so that I get the correct player (yeah, it's not that hard, but I prefer it to be REALLY obvious). Making the abstract class do that work for me would just be sweet xD

 

Does anyone have any idea how (or if it's even possible) to get this working?

 

Much obliged,

coolAlias

Link to comment
Share on other sites

player = ctx.getHandler().playerEntity

From then on you only have to type 'player' BOOM magic...

Netty is all about generic data it needs a 1:1 ratio, it also needs to have non-abstract classes.

I do Forge for free, however the servers to run it arn't free, so anything is appreciated.
Consider supporting the team on Patreon

Link to comment
Share on other sites

player = ctx.getHandler().playerEntity

From then on you only have to type 'player' BOOM magic...

Netty is all about generic data it needs a 1:1 ratio, it also needs to have non-abstract classes.

I guess I need to update my Forge version then (1060 still... yeah, I know >.<); all I have is ctx.getClientHandler(), which doesn't even have a player field, and ctx.getServerHandler(); no generic version exists unless you count ctx.netHandler, but that also doesn't have a player.

 

Well, that will be one annoyance out of the way, sort of; I'd still prefer to just have the player as a parameter, but maybe that's just me. :P

 

Thanks for the reply, though.

Link to comment
Share on other sites

Its just not how netty works, you get a context that holds all the information and go from there.

There may not be a generic way of getting it my point is the 'everytime' argument is invalid as it's a basic programming setup to cache the values you need in easy to reference names.

I do Forge for free, however the servers to run it arn't free, so anything is appreciated.
Consider supporting the team on Patreon

Link to comment
Share on other sites

Its just not how netty works, you get a context that holds all the information and go from there.

There may not be a generic way of getting it my point is the 'everytime' argument is invalid as it's a basic programming setup to cache the values you need in easy to reference names.

Yeah, I understand - I just think it would be even more magical to have the EntityPlayer passed directly to the IMessageHandler#onMessage method (which is also a basic programming practice - passing variables that will likely be needed), but if for whatever reason that's not possible, it's not exactly difficult to create a local reference to a field. It's just irksome :P

 

At any rate, I was just curious if there was a way restructure it for myself using generics, both because it's an interesting exercise in Java and the result would appease my urge to economize the amount of typing I must do and the number of lines in my code. Most certainly not necessary, but an adventure in personal preference, if you will. Certainly you've restructured things to suit your personal tastes, before, no?

Link to comment
Share on other sites

Hi coolAlias

 

(welcome back, long time no see :) )

 

Just a thought - based on another thread on this forum recently, this method might cause you grief on the dedicated server because Minecraft doesn't exist there.

@Override
public IMessage onMessage(T message, MessageContext ctx) {
if (ctx.side == Side.CLIENT) {
return handleClientMessage(Minecraft.getMinecraft().thePlayer, message, ctx);
} else {
return handleServerMessage(ctx.getServerHandler().playerEntity, message, ctx);
}
}
}

Not having done much with 1.7.2 so far, I haven't yet figured out whether the best way to get around that is to put the side-specific stuff into one of the proxy classes, or to register two different handlers on the different sides (is that possible with IMessageHandler?)  Any thoughts?

 

-TGG

 

Link to comment
Share on other sites

Hi TGG,

 

Thanks for the welcome xD I was pretty busy moving and starting a new job, so didn't have much time for modding.

 

I'm pretty sure that the IMessageHandler#onMessage method is the final method called in the chain, so it will be either on the client or the server depending on which direction the packet was sent.

 

Granted, I have only just started messing around with this whole message system (since apparently the first Netty tutorial has a memory leak, though no one has bothered to explain where and why), so I could be wrong, but I assume that when MessageContext.side == CLIENT, that I am actually on the client side so Minecraft.getMinecraft() shouldn't be a problem; otherwise, how could anyone do any client-side processing of their packets? :\

 

Re: registering of message handlers, I don't think you need to worry about partitioning those into the proxy classes; the registration process looks to me like it should work by registering during the FML initialization events without worrying about Side at all, except for as an argument to pass to the method.

 

As far as I understand, IMessageHandler should be implemented by each packet (message), not as a generic catch-all handler like some people used in 1.6 and earlier.

 

All that being said, I've only played with this message stuff for about 2 hours so far, so take all of the above with a grain of salt ;)

 

Cheers,

coolAlias

Link to comment
Share on other sites

Thanks for the welcome xD I was pretty busy moving and starting a new job, so didn't have much time for modding.

Life gets in the way, huh :)  how inconvenient... :)

 

Granted, I have only just started messing around with this whole message system (since apparently the first Netty tutorial has a memory leak, though no one has bothered to explain where and why), so I could be wrong, but I assume that when MessageContext.side == CLIENT, that I am actually on the client side so Minecraft.getMinecraft() shouldn't be a problem; otherwise, how could anyone do any client-side processing of their packets? :\

 

Re: registering of message handlers, I don't think you need to worry about partitioning those into the proxy classes; the registration process looks to me like it should work by registering during the FML initialization events without worrying about Side at all, except for as an argument to pass to the method.

coolAlias

I've into the problem before that a Server-Side class which refers to Client Side classes cause a runtime error before a single line of code gets executed.  I don't know much about Java runtime linking but it seems that the first use of the Server-side class makes the jvm look for all the other classes referred to by the Server-Side class.  Those classes don't exist so it gives a runtime error.  Doesn't make any difference that the client-side classes are unreachable when the Server-side methods are called.

 

Could be I've misinterpreted the cause of the errors I was getting, but in the end I fixed it by moving all references to client-side classes into the proxy class.  It was quite painful actually. 

 

-TGG

 

 

 

 

 

Link to comment
Share on other sites

I've into the problem before that a Server-Side class which refers to Client Side classes cause a runtime error before a single line of code gets executed.  I don't know much about Java runtime linking but it seems that the first use of the Server-side class makes the jvm look for all the other classes referred to by the Server-Side class.  Those classes don't exist so it gives a runtime error.  Doesn't make any difference that the client-side classes are unreachable when the Server-side methods are called.

 

Could be I've misinterpreted the cause of the errors I was getting, but in the end I fixed it by moving all references to client-side classes into the proxy class.  It was quite painful actually. 

 

-TGG

Sure, that can happen if it's a Server-Side class, but this isn't :P If you take another look, you'll see that the Minecraft.getMinecraft() is embedded inside of a method that only gets called when a message (packet) is being processed; this only ever happens on one side or the other (at a time - it could happen on either depending on how you register it), which is why I check the Side first. It's effectively the same as saying "if (world.isRemote) getClientStuff; else getServerStuff", which I'm sure you've seen happen in other methods, e.g. Item#onRightClick, in which you could either spawn an entity (server) or particles (client), neither of which will crash if executed on the correct side.

 

I've definitely experienced what you are talking about though - it was with things like Render, Gui, KeyBindings and those kinds of classes. Basically, any time you are going to reference something that only exists on the client, you must segregate it somehow or you will run into the issue you described.

 

The main problem is for an entire CLASS, such as a Render class that you stuck @SideOnly(Side.CLIENT) above because it's everywhere else in the code (I do that...), or a client-only field that is initialized in-line (i.e. public IIcon[] icons = new IIcon[3] <- crash), when you go to register that class, there is no segregation of sides done by default, so you must either check yourself (using something like FML's getEffectiveSide if you don't have a world object) or delegate to the Proxy classes (much cleaner and easier).

 

Same idea for something like Minecraft.getMinecraft() - as long as you can segregate the call and ensure it happens on the client side, nothing averse will come of it.

 

Anyway, my AbstractMessage class seems to be working just fine now, so I'm pretty happy with that xD I still need to test it in a multiplayer scenario, but I suspect it won't have any problems.

 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.