UDP networking for multiplayer game

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
mxmlnbndsmnn
Prole
Posts: 17
Joined: Mon Feb 11, 2019 4:17 pm

UDP networking for multiplayer game

Post by mxmlnbndsmnn » Tue Feb 19, 2019 9:11 am

Hello there,

with the help of the love2d wiki I started creating some more or less basic games but since this is getting boring over time ... I took another step that lead me towards creating a multiplayer game. I use the socket module and ofc my app communicates over UDP. Some forum posts gave me hints on how to plan the communication and I got the basic stuff working. But before I write too much, let's get to the facts+problem:

I'm using love2d version 11.1 on a Windows OS.
The goal is to create a(nother) 2D top-down shooter for local multiplayer (LAN only) (singleplayer is also possible, but not the main goal).
(so yes, my personal goal is to learn stuff about networking and shaders [which I might have some questions about, too ... maybe in another post ;) ])
With "basic stuff working" I mean my app can send game entity information and apply them (server is always right). I have a simple lobby that lets players join and the host can select the map to play on and start the game ...

Now to my current obstacle: while the game runs smoothly at first (I'm testing it on one PC starting the game twice) it becomes more and more laggy after a few seconds (20 or so?). Note that I am not even sending a huge mass of information over the network. It is just 2 players and their movement (actually location, velocity vector and rotation). Hostile mobs etc are deactivated for now!
Before you ask for a .love file (which does not exist yet), let me show you my concrete questions:
Using

Code: Select all

udp:settimeout(0)
should make the transmissions non-blocking, right? Meaning I do not need a thread to do the networking stuff.

In every update tick the server and client do

Code: Select all

self.server.listen()
self.server.processBufferData()
which is really just read the input and store it in a table and second process it.
However, this all happens in one call of the tick function and here I am not quite sure if this buffer is actually helpful at all.
Do I have to find another structure for my "buffer"? (I'm gonna append a shortened version of my network lua file)

As you can see in the attached file, a client can send a "ping" to the server (client keypress) which responds asap -> the clients knows the time delay.
After some seconds of "gameplay" this delay becomes >2 seconds and even more when "playing" longer...
So the actual thing I do not understand is ... why is that? I do not see the reason behind that. Is my "buffer" concept wrong? Is there a problem with the transmission I do not notice? I mean ... being on ONE PC should not lead to these delays and my PC is clearly not old so this should not be the bottleneck here.

Thanks for reading so far, I appreciate any ideas/hints etc. If the network code is not enough to understand my problem, let me know and I will build my .love file for you to check. Cheers!
Attachments
network_short.lua
(16.87 KiB) Downloaded 105 times

User avatar
keharriso
Citizen
Posts: 73
Joined: Fri Nov 16, 2012 9:34 pm

Re: UDP networking for multiplayer game

Post by keharriso » Tue Feb 19, 2019 10:07 pm

You might have more luck getting help if you find some sort of minimal code necessary to reproduce the issue. If it's not relevant, take it out. Also, something runnable would be nice.
LÖVE-Nuklear - a lightweight immediate mode GUI for LÖVE games

mxmlnbndsmnn
Prole
Posts: 17
Joined: Mon Feb 11, 2019 4:17 pm

Re: UDP networking for multiplayer game

Post by mxmlnbndsmnn » Wed Feb 20, 2019 9:01 pm

Okay, I've disabled all mobs. Heres the .love file.

In order to reproduce ...

- start the app twice on the same PC
- there is a setting to change the server address but it is hardcoded as localhost for now
- host: start LAN game
- client: join LAN game
- host: start the game
- now both players should be in the same game ... you can move around with your wasd keys
(- and shoot with left click, but this isn't implemented correclty yet, as it fires for ALL player entities... I started writing a basic part of the singleplayer stuff)
- the client can send a ping to the server by pressing "p"
- for me it took <0.5s briefly after the game started but less than a minute later it becomes way more than one second

(note that this is far from stable; the described steps should work, but going into a game, leaving it and start/join again might produce errors...)
Attachments
zombie shooter.love
(13.9 MiB) Downloaded 100 times

User avatar
keharriso
Citizen
Posts: 73
Joined: Fri Nov 16, 2012 9:34 pm

Re: UDP networking for multiplayer game

Post by keharriso » Wed Feb 20, 2019 10:49 pm

OK, I figured it out. You want to read ALL of the datagrams in the socket buffer instead of just one every tick.

Code: Select all

self.client.listen = function()
	data, err_msg = udp:receive()
	if data then
		--print("client: Received data: " .. data)
		self.buffer[#self.buffer + 1] = { msg = data, err = err_msg }
	elseif err_msg ~= "timeout" then
		print("client: Network error: " .. tostring(err_msg))
	end
end
becomes:

Code: Select all

self.client.listen = function()
	data = true
	while data do
		data, err_msg = udp:receive()
		if data then
			--print("client: Received data: " .. data)
			self.buffer[#self.buffer + 1] = { msg = data, err = err_msg }
		elseif err_msg ~= "timeout" then
			print("client: Network error: " .. tostring(err_msg))
		end
	end
end
and

Code: Select all

self.server.listen = function()
	-- listen to incomming messages
	data, msg_or_ip, port_or_nil = udp:receivefrom()
	if data then
		self.buffer[#self.buffer + 1] = { msg = data, ip = msg_or_ip, port = port_or_nil }
	elseif msg_or_ip == "timeout" then
		--print(tostring(serverTime)..": NETWORK TIMEOUT ERROR: " .. tostring(msg_or_ip))
	else
		print(tostring(serverTime)..": NETWORK ERROR: " .. tostring(msg_or_ip))
	end
end
becomes

Code: Select all

self.server.listen = function()
	data = true
	while data do
		-- listen to incomming messages
		data, msg_or_ip, port_or_nil = udp:receivefrom()
		if data then
			self.buffer[#self.buffer + 1] = { msg = data, ip = msg_or_ip, port = port_or_nil }
		elseif msg_or_ip == "timeout" then
			--print(tostring(serverTime)..": NETWORK TIMEOUT ERROR: " .. tostring(msg_or_ip))
		else
			print(tostring(serverTime)..": NETWORK ERROR: " .. tostring(msg_or_ip))
		end
	end
end
LÖVE-Nuklear - a lightweight immediate mode GUI for LÖVE games

User avatar
pgimeno
Party member
Posts: 1909
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: UDP networking for multiplayer game

Post by pgimeno » Thu Feb 21, 2019 1:06 am

Some locals there would be nice.

Also, Lua is not Python, we have repeat-until loops ;)

mxmlnbndsmnn
Prole
Posts: 17
Joined: Mon Feb 11, 2019 4:17 pm

Re: UDP networking for multiplayer game

Post by mxmlnbndsmnn » Thu Feb 21, 2019 8:55 am

Thanks for the really quick replies! Now that I see the "problem" it seems rather stupid as I could have known that ... but I guess that's just how you learn ^^

@pigmeno:
Do you mean localize the socket/udp functions inside the listen function?

Is there an advantage the repeat loop provides over the while loop or is it just a question of style?

User avatar
pgimeno
Party member
Posts: 1909
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: UDP networking for multiplayer game

Post by pgimeno » Thu Feb 21, 2019 10:51 am

I mean that data, msg_or_ip, port_or_nil and err_msg are all global unnecessarily. 'data' in particular is a pretty generic name that is easy to interfere with other variables. It's also best if the variables are destroyed at the end of the function, so that the no longer used space can be reclaimed if necessary.

As for repeat loops, it's both. They exist for this specific purpose: to not have to add extra code to make the condition true at the start. Granted, in this specific case it won't make any measurable difference because the condition is a variable check and the setup a variable assignment. But if the condition or the setup were expensive, saving them would make a difference, and sometimes necessary. So it's just good habit to use it where it's pertinent.

Code: Select all

self.client.listen = function()
    local data, err_msg
    repeat
        data, err_msg = udp:receive()
        if data then
            --print("client: Received data: " .. data)
            self.buffer[#self.buffer + 1] = { msg = data, err = err_msg }
        elseif err_msg ~= "timeout" then
            print("client: Network error: " .. tostring(err_msg))
        end
    until not data
end

grump
Party member
Posts: 587
Joined: Sat Jul 22, 2017 7:43 pm

Re: UDP networking for multiplayer game

Post by grump » Thu Feb 21, 2019 11:07 am

You can even write it like this:

Code: Select all

repeat
    local data, err_msg = udp:receive()
    ...
until not data

User avatar
pgimeno
Party member
Posts: 1909
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: UDP networking for multiplayer game

Post by pgimeno » Thu Feb 21, 2019 11:51 am

Oh, I thought the 'until' condition was out of the inner scope. Good to know, thanks!

mxmlnbndsmnn
Prole
Posts: 17
Joined: Mon Feb 11, 2019 4:17 pm

Find server/clients in a LAN

Post by mxmlnbndsmnn » Tue Apr 02, 2019 8:33 pm

I'm back at the networking topic :) as my code/game grew I also wanted to make it a little more flexible/comfortable.

Right now the server and client have to know the server address beforehand. This means the user has to input the IP address...
My goal is to have the server/client "find" each other over a LAN (simple case: two PCs with an Ethernet cable connection).
How does the server know where he is (which IP address the server will have in the local network)?
The socket documentation says: "UDP sockets are not bound to any address until the setsockname or the sendto method is called for the first time"... hum, okay. So how do we manage this?
Apparently I cannot send data somewhere into the network without specifiying an address.
I thought the server could send a "I am here" message to all addresses in the network and the client can answer (after using receivefrom to obtain the address) and than they could establish a connection... but how to do that without knowledge about the subnetmask?

Or do I miss another option, maybe even more simple or just better?

Post Reply

Who is online

Users browsing this forum: No registered users and 14 guests