Multithreading in the network game swing: using invokeLater against locks

I am writing a simple game from top to bottom, and producing it to allow playing on the network with several players. I participated in a little reading, but this is the first time I have done this, and I would appreciate advice on choosing a reasonable design.

My GUI is written using Swing. The timer is triggered 30 times per second, and it changes my graphical interface in accordance with the data in the gameWorld object in memory (in essence, a list of ships and shells with positions, etc.). GameWorld physics updates are also performed using this timer. Thus, to implement one player, everything happens on the EDT, and it works great.

Now I have a separate thread related to incoming packages from other players. I would like to update the data in the gameWorld object based on what these packages contain. My question is: should I use invokeLater to make these changes or use locks to avoid concurrency issues?

To illustrate what I mean:

runMethodOfInputThread() { while(takingInput) { data = receiveAndInterpretIncomingPacket(); // blocks SwingUtilities.invokeLater(new Runnable() { public void run() { gameWorld.updateWithNewGameInfo(data); } }); } } 

against

 runMethodOfInputThread() { while(takingInput) { data = receiveAndInterpretIncomingPacket(); // blocks synchronize (gameWorldLock) { gameWorld.updateWithNewGameInfo(data); } } } 

The latter will also require the use of similar synchronization blocks wherever the EDT accesses gameWorld, so it seems to me that using invokeLater would be easier to implement. But do I think that both approaches will work? Are there any other important pros and cons to keep in mind?

Thanks,

Jeremiah

+6
source share
4 answers

Pros for approach 1:

  • Minimal difficulty
  • Stability

By restricting access to the "gameWorld" variable to the EDT stream, locking mechanisms are not required. Parallel programming is complex and requires the programmer to be vigilant in the entire source database when accessing objects shared between threads. It is possible for the programmer to forget to synchronize in certain cases, which leads to compromised game states or a program crash.

Pros for approach 2:

  • Scalability
  • Performance

Minimizing processing performed on the EDT stream ensures that the game interface and display are responsive to the user. Approach 1 may work for now, but later on, changes to your game will not scale to a more advanced interface if the EDT thread is busy doing non-ui processing.

+1
source

Well, first of all you do not need to choose only one method. You can use locks to make your data structure "safe" just to be sure "(since your application is already multi-threaded) and use invokeLater to actually apply changes only in EDT - in which case JIT can optimize the lock close to 0.

Further, from my point of view, invokeLater is a rather preferred way: if you can bypass multithreading, you should use this method, simply because multithreading is complex and rich in possible errors.

But applying changes through invokeLater () will put extra pressure on the EDT, so if the changes come at a high speed, you can observe a worsening GUI. Also, if gameWorld.updateWithNewGameInfo (data) is a havy method that executes the observed time to complete, it can even make you GUI freeze. In addition, invokeLater puts your task in the back of the event queue, so this will be done after all the events in the queue. In some cases, this can lead to delays in making changes, which will make your game less user friendly. This may or may not be your business, but you must keep this in mind.

As a general rule, do not use EDT for any time task. As far as I understand, parsing network packets is already in a separate thread in your application. Applying changes can (and should) be done in a separate thread if it takes a lot of time.

+3
source

Not a second. You want to work as little as possible in EDT. If you expect locks in the EDT, this is as bad as running all the other code (on the other hand of the lock) directly in the EDT, as the EDT must wait for the rest to complete.

In addition, it seems that your whole game runs on EDT. This is a bad practice. You must split your code using the model controller template. I understand that your game is small and can work in EDT, but you probably should not get used to it.

You must have your game logic running from the timer thread (java.util.concurrent.ScheduledThreadPoolExecutor), and at the end of each period you โ€œsendโ€ your data to EDT and repaint using invokeLater .

You should also have a separate thread that reads the socket, and this thread should write objects that share locks with the objects that you use in the timer game thread.

+1
source

My suggestion is as follows

  • move all downloaded data from different users (stream) to the queue
  • use another thread to read from this queue and update user interface from EDT

It should avoid your concurrency problem. How can this be achieved

 runMethodOfInputThread() { while(takingInput) { data = receiveAndInterpretIncomingPacket(); // blocks blockingQueue.add(data); } } runMethodOfUPdateUIThread() { while(updatingUI) { data = blockingQueue.take(); SwingUtilities.invokeLater(new Runnable() { public void run() { gameWorld.updateWithNewGameInfo(data); } }); } } 
0
source

Source: https://habr.com/ru/post/895239/


All Articles