Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiplayer #12

Open
rickbau5 opened this issue Mar 22, 2017 · 10 comments
Open

Multiplayer #12

rickbau5 opened this issue Mar 22, 2017 · 10 comments

Comments

@rickbau5
Copy link
Collaborator

I've been working a lot on multiplayer over the last couple of days and have some things to discuss here. I am attempting to keep it as general as possible at this point. The idea here is that the users can look at what's in place and add on top of it.

Basic Architecture

There are a lot of different methods to implement Server-Client communication, but I just went with the simple, typical Java based method which is over sockets. The server starts and listens on a socket, and for every connection it receives it starts a new thread to handle all the communication with that client.

To send data back and forth, I created a packet exchange system. This was the first thing that came to mind, and I just went for it to test it out. Each packet has a specific use case that it is responsible for serializing/deserializing to/from a byte stream. This byte stream is written/read over the socket.

An example: A client moves their player, a packet is then sent to the server with the client's player entity's id and the new location. This is encoded as a byte stream of (id, x, y) and sent over the socket to the server. The server reads the packet by reading from the stream three ints in the same order as they were written by the client.

Obviously this has some fairly significant flaws and doesn't really do much of any validation between client/server, but that's a hurdle for another time.

Moving on, whenever the server receives a packet from a client, it forwards that packet to all other clients if it's something that all clients need to be aware of.

Things To Consider

  • Serializing & Deserializing: One of the packets implemented now is for sending Entity information over the sockets. To do so, there must be some way to serialize them to a byte stream. Currently I see three ways of doing this.
    1. Add methods to the Entity class and override them where need be. This would look like:
    // in Entity.java
    public byte[] serialize(DataOutputStream dos) throws IOException {
        dos.writeInt(getEntityID());
        dos.writeInt(getY());
        dos.writeInt(getX());
        return // bytes
    }
    // in LivingEntity.java
    @Override
    public byte[] serialize(DataOutputStream dos) throws IOException {
        super(dos);
        dos.writeFloat(getHealth());
        dos.writeDouble(getSpeed());
        return //the bytes
    }
    // etc...
    public void deserialize(DataInputStream dis) ...
    1. Add an interface like NetworkSerializable with serialize and deserialize methods to anything that should be able to be sent over the network. My gripe with this method is that it would just be hierarchy hell. And could end up to requiring different types of entities for single player vs multiplayer (annoying).
    2. Use annotations like @Serialize & @Deserialize. This is my personal favorite solution. The benefit of this is that any method could be marked and be used as long as the correct return type and parameters are used. The user would be free to have any hierarchy they desire and could just slap this down anywhere. It does require them to manage more state, though. The major drawback is annotation based stuff is all going to be reflection which can get slow. This could be offset somewhat by doing class path scanning or build registries to avoid scanning during general runtime for the methods.
  • The nasty pile of threads: There are a lot of threads for the server using this method. There's the starting thread, then the game thread, then the server thread, and another thread for each client. It could be really beneficial to use some existing libraries for this stuff to ensure thread safety, something like Akka perhaps.
  • Is this going to even be useful for the general case? The more I work on it, the more it feels like I'm designing something that I find useful, but may not be useful to the average game.
  • Probably more stuff that I can't think of right now.

Conclusion

There's still a lot to be learned in this process, but I wanted to share what i have so far. I've been doing this in my fork so as to not create noise in the main repo. You can check it out here. Only 1300 lines of code! Phew.

Thoughts?

@Vrekt
Copy link
Owner

Vrekt commented Mar 23, 2017

Your work is amazing and I'd like to say thank you first of all.

"The nasty pile of threads: There are a lot of threads for the server using this method. There's the starting thread, then the game thread, then the server thread, and another thread for each client. It could be really beneficial to use some existing libraries for this stuff to ensure thread safety, something like Akka perhaps."

I think the server should all be on one thread if possible. If its not so possible Akka wouldn't be a bad solution.
Maybe create an object for each client that handles sending and receiving the client data.
Also, should the server be ran as the same tick as the client or vice-versa?

"Is this going to even be useful for the general case? The more I work on it, the more it feels like I'm designing something that I find useful, but may not be useful to the average game."

This could be very useful as the engine becomes more advanced and featured. Maybe we should have the multiplayer functionality as a different library jar? Which then users could choose if they want multiplayer in their game.

Annotations is the best method I think. Though I'm not very experienced with network/annotation related stuff but I can learn.

As a final note I think the server should NOT trust the client. Everything is handled by the server (except in cases where it can't). Its not that big of a deal right now but its possible there would be cheat developers if somebody made some multiplayer game.

@rickbau5
Copy link
Collaborator Author

Thanks! I've been enjoying working on this in my spare time!

The problem I see with having the server being single threaded is that, well it'll be single threaded. For instance, say there are 5 clients connected the loop may look like this:

Socket clientSocket = serverSocket.accept(); // i think there's a way to do non blocking accepts
clients.add(clientSocket);
for (Socket client : clients) {
    int i = inputStream.read()
    ... do more reading stuff
    for (Packet packet : outBoundPackets) 
       client.outputStream.write(packet)
}

... etc. Basically clients would be updating sequentially rather than if they were on there own thread asychronously.

I do like the annotations method, it's just going to be inherently slower in every case. You can see what has to be done each time a call is made here

I 100% agree about not trusting the client. Will make some changes to reflect that next week.

I'm on vacation for a bit so I won't be working on this.

@rickbau5
Copy link
Collaborator Author

Any further thoughts on this?

To elaborate a bit more on my main point about having all the server stuff on a single thread, is that it would be easy to get into a situation in which one client that is particularly slow, causes the other clients to see a slow down as well. So the game would play at the speed of the slowest connected client.

@Vrekt
Copy link
Owner

Vrekt commented Apr 2, 2017

Ah I see. It would also be pretty cool to have some sort of plugin system for the server. If you have ever worked with Bukkit/Spigot then you will know what I'm talking about. For each action in the server (for example a player moving or attacking an entity, etc) it fires an event in which the plugin can act upon it.

@Vrekt
Copy link
Owner

Vrekt commented May 26, 2017

Its been awhile and I haven't touched java since Apr 2. Haven't been motivated lately, this project might be dead for awhile until maybe I find the motivation to come back.

@rickbau5
Copy link
Collaborator Author

Hey no worries I've been busy with other stuff too. I'll watch for activity!

@Vrekt
Copy link
Owner

Vrekt commented Jul 27, 2017

feels very weird pushing again from IntelliJ almost forgot how to.. excuse me if i fuck something up.

@rickbau5
Copy link
Collaborator Author

Woo welcome back 😄

@Vrekt
Copy link
Owner

Vrekt commented Oct 17, 2017

Hey, would you be able to update the method Entity#getLineOfSight with your RayCast system? Since its currently outdated and I'm not that all familiar with your system yet. No rush or anything.

@rickbau5
Copy link
Collaborator Author

Yeah sure thing, going to make an issue and assign both it and this to myself as a reminder!

@rickbau5 rickbau5 self-assigned this Oct 17, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants