This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Gonow32's profile picture

My network game shuts down the PC

Started by Gonow32, 28 May 2016 - 07:51 AM
Gonow32 #1
Posted 28 May 2016 - 09:51 AM
Hello,
I've created an Alpha version of a multiplayer top-down shooter I'm planning to make. However, the server code seems to crash within a few seconds of it running. It shuts down the computer when it crashes without any error messages or anything.

Code:
Spoiler

local tArgs = {...}
local tMap = {}
if #tArgs < 2 then
  print("Usage: server <channel> <name>")
  return
end
local m = peripheral.find("modem")
if not m then
  print("No modem found")
  return
end
if not tonumber(tArgs[1]) then
  print("Invalid channel")
  return
elseif tonumber(tArgs[1]) > 65535 then
  print("Channels cannot be over 65535")
  return
elseif tonumber(tArgs[1]) < 1 then
  print("Channels cannot be under 1")
  return
end
local w, h = term.getSize()
for i = 1, w do
  tMap[i] = {}
  for i2 = 1, h do
	tMap[i][i2] = colours.grey
  end
end
local channel = tonumber(tArgs[1])
if not m.isOpen(channel) then
  m.open(channel)
end
print("[Server] Connection open on port "..tostring(channel))
local tPlayers = {}
local tPlayerIDs = {}
function createPlayer(user)
  local unique = false
  repeat
	local id = math.random(1,256)
	if not tPlayerIDs[id] then
	  tPlayers[user] = {
		nHealth = 100,
		nArmour = 100,
		nKills = 0,
		nX = 2,
		nY = 2,
	  }
	  tPlayerIDs[id] = user
	  m.transmit(channel, channel, {
		sType = "connected",
		sRecipient = user,
		nPlayerID = id,
		sServer = tArgs[2],
	  })
	  unique = true
	end
  until unique == true
end
parallel.waitForAny(function()
  print("[Server] Thread 1 initialised")
  while true do
	local e = {os.pullEvent("modem_message")}
	if e[3] == 30000 then
	  if type(e[5]) == "table" then
		if e[5].sType then
		  if e[5].sType == "connect" then
			if e[5].sUsername then
			  if e[5].sServer == tArgs[2] then
				createPlayer(e[5].sUsername)
				local map = tMap
				os.queueEvent("quicksleep")
				os.pullEvent("quicksleep")
				m.transmit(channel, channel, {
				  sType = "game-update",
				  sServer = tArgs[2],
				  tMap = map,
				  tPlayerList = tPlayers,
				  tIDList = tPlayerIDs,
				})
			  end
			end
		  elseif e[5].sType == "key_press" then
			if e[5].sServer == tArgs[2] then
			  local id = tPlayerIDs[e[5].nSender]
			  if e[5].nKey == keys.up then
				tPlayers[id].nY = tPlayers[id].nY - 1
			  elseif e[5].nKey == keys.down then
				tPlayers[id].nY = tPlayers[id].nY + 1
			  elseif e[5].nKey == keys.left then
				tPlayers[id].nX = tPlayers[id].nX - 1
			  elseif e[5].nKey == keys.right then
				tPlayers[id].nX = tPlayers[id].nX + 1
			  end
			  local map = tMap
			  m.transmit(channel, channel, {
				sType = "game-update",
				sServer = tArgs[2],
				tMap = map,
				tPlayerList = tPlayers,
				tIDList = tPlayerIDs,
			  })
			end
		  end
		end
	  end
	end
  end
end, function()
  print("[Server] Thread 2 initialised")
  while true do
	local queue = {}
	for i,v in pairs(tPlayers) do
	  local id = -1
	  for i2,v2 in ipairs(tPlayerIDs) do
		if i == v2 then
		  id = i2
		  modem.transmit(channel, channel, {
			sType = "ping",
			nRecipient = id,
			sServer = tArgs[2],
		  })
		  local time = os.startTimer(0.1)
		  local event
		  repeat
			local e = {os.pullEvent()}
			if e[1] == "modem_message" then
			  if e[3] == channel then
				if type(e[5]) == "table" then
				  if e[5].sType then
					if e[5].sType == "pong" then
					  if e[5].nSender == id then
						if e[5].sServer == tArgs[2] then
						  event = e[1]
						end
					  end
					end
				  end
				end
			  end
			elseif e[1] == "timer" then
			  if e[2] == time then
				event = e[1]
			  end
			end
		  until event == "modem_message" or event == "timer"
		  if event == "timer" then
			table.insert(queue, id)
		  end
		  break
		end
	  end
	end
	for i,v in pairs(queue) do
	  tPlayers[tPlayerIDs[v]] = nil
	  tPlayerIDs[v] = nil
	end
  end
end)

Thanks for your help.
Bomb Bloke #2
Posted 28 May 2016 - 10:11 AM
When a computer/turtle starts running code, ComputerCraft starts a ten second timer. If that code doesn't yield before that timer ends then ComputerCraft will either crash the script or the whole computer (depending on the nature of the functions your script is calling). After each yield, any other systems waiting to run code may do so, then after they've all yielded, processing continues with a new time limit.

The reason why is that running your code chews up valuable server processing power, and so it shouldn't be able to monopolise it. In fact, only ONE CC device can run code at a time: While one is doing something, none of the others can do anything until it yields.

Whether or not it takes more than ten seconds for your code to execute has a little to do with the power of the Minecraft server it's running on, and a lot to do with how often you allow your code to yield. Pulling events (eg getting typed characters or checking timers) triggers a yield, and many commands (eg turtle movements, sleeping, or getting text input from the user) have to pull events to work anyway. Basically, anything that triggers a pause is pulling an event in order to do it, and in order to pull an event the code yields.

In your code, you start two functions via the parallel API, the second of which has a "while" loop which only yields if certain conditions are met. For starters, tPlayers / tPlayerIDs need to contain something - and if they don't, the code which adds stuff to them will never get a chance to run, because that second loop will never yield once it starts! Making the loop sleep a few seconds per iteration wouldn't hurt - I doubt you really need to send pings / pongs "as fast as the Minecraft server can handle it".
Edited on 28 May 2016 - 08:13 AM
Gonow32 #3
Posted 28 May 2016 - 10:30 AM
Okay, so I should delay the ping/pong thread by a few seconds on each cycle so the other thread gets a chance to run?
Edit: Done that now, it works! Thanks for the help.
Edited on 28 May 2016 - 10:22 AM
Bomb Bloke #4
Posted 28 May 2016 - 12:47 PM
Yeah sure why not.