Thanks for taking a look guys.
Thanks for the backlink. That reminds me I should create one heck of a homepage... too!
No problem. And thanks!
I think that if an API can't be reasonably described on a single page, something's wrong!
The first thing I thought, right at the start of looking at how to use the API, was "why do I have to register a class to serialize it?" Would it not be better if it could auto-register unrecognised classes? Particularly those ones marked as Serializable?
Well, the quick answer is, making you register classes is easier for me. Registering means each class is given the same ID on both the server and clients. Only the ID needs to be sent across the wire.
Lesser reasons include... Registering means you have to think about what you are sending, and how it gets serialized. It provides a chance to optimize serialization by specifying things like "can never be null" or the type of items in an ArrayList. This
is a bit of an extreme case of optimization, but could save a reasonable amount of bandwidth in some situations. Here
is the (much cleaner) unoptimized class, and here
is a chart showing the differences (8.4% in this case, could be more with different data). The optimization lets it beat protobuf
in the benchmarks
Sorry I have no more links for this paragraph.
How would an auto-registration scheme work? I'm assuming what classes you want to send wouldn't be known until the first time they are sent. The first time a class is sent it'd be sent with an ID and the class name String. The receiving side would keep track of the remote mappings of ID to class and deserialization would have a lookup per object. It gets tricky if the first time a class is sent over UDP, since it isn't known if the other side will receive it.
This would be a reasonably complex orchestration for TCP and no clear solution for UDP, all just to avoid registration. What do you guys think?
I'd suggest using enums:
You're right, the enum is much clearer.
For the start method, there could be a no-arg version that did the more popular option. I wonder if daemon or non-daemon is usually what people want? If they are using the method I assume they have a game thread, so daemon is probably fine.
The Connection.id is short which means it will easily overflow, and you can (and will, eventually!) end up with concurrent connections with the same id.
Overflow is ok, only -1 and 0 are special connection IDs. Thanks for pointing it out though, I wasn't handling the case of skipping -1 and 0. To completely wrap around and have two connections with the same IDs, a client would have to connect, and then 65,535 clients would have to connect before the first one disconnects. This doesn't seem very likely!
Why does even Client have it's own Selector + Thread?
Hmm. I was assuming only a handful of Client instances would be needed at most for a single client. Usually only one is needed. Is there a use case were you would need more than a couple Client instances? I guess you could try to implement a P2P network, though KryoNet is specifically targeting client/server.
Even connecting can be non-blocking.
Originally I was doing non-blocking connects, but it got messy. I decided what I really was doing was a blocking connect anyway. After switching to blocking it made my code in that area so much simpler.
Thanks again for taking a look. The NIO stuff has all kinds of crazy corners, half of which I think I've forgotten immediately after figuring them out. The more eyes the better! I've also put a lot of effort into the API, so feedback on ease of use, discoverability, etc is highly appreciated.