Jump to content

Some network code


Josh
 Share

Recommended Posts

Take a look at this. Instead of a host/peer design, this is a more intuitive server/client design. If you are hosting a game, you create a server. If you want to play in the game yourself, create a client. Here, I created two clients. You could create a client for each bot and run them through the network, or just one client for yourself. You'd probably want to always run the game through the network, even if it is single-player, so that you have a uniform design for single and multi player modes.

 

The messages are turned into a form with an ID and a packet, which is a stream of any length. There are built-in message IDs. Some are handled entirely silently. For example, when the server receives a chat message, it just broadcasts the data to all clients, without the user having to do anything. Synced entities would behave this way as well.

 

The user's interaction with the system comes in the form of a callback. The callback can be used to do stuff when messages are received. In the example below, the client callback is used to print the ping and to print any chat messages that are sent.

 

SuperStrict

Private

Function enet_host_port:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[1]
Local port:Int=(Short Ptr peer)[4]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return port
EndFunction

Function enet_host_ip:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[1]
Local port:Int=(Short Ptr peer)[4]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return ip
EndFunction

Function enet_peer_port:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[3]
Local port:Int=(Short Ptr peer)[8]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return port
EndFunction

Function enet_peer_ip:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[3]
Local port:Int=(Short Ptr peer)[8]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return ip
EndFunction

Type TNetworkNode

Field port:Int
Field ip:Int
Field enethost:Byte Ptr
Field enetpeer:Byte Ptr

Method Delete()
	If enethost
		enet_host_destroy(enethost)
		enethost=Null
	EndIf	
EndMethod

Method Update(callback(client:TClient,id:Int,packet:TPacket)=Null)
	Local ip:Int,port:Int,client:TClient,ev:ENetEvent=New ENetEvent,id:Byte,packet:TPacket

	If Not Self.enethost RuntimeError "Can't update a remote server."
	Repeat
		If enet_host_service(Self.enethost,ev,0)

			Select ev.event

			Case ENET_EVENT_TYPE_CONNECT
				id=NETWORK_CONNECT

			Case ENET_EVENT_TYPE_CONNECT
				id=NETWORK_DISCONNECT

			Case ENET_EVENT_TYPE_RECEIVE
				Local size:Int=enet_packet_size(ev.packet)
				Local data:Byte[size]
				MemCopy(Varptr id,enet_packet_data(ev.packet),1)
				If size>1
					packet=New TPacket
					packet._bank.resize(size-1)
					MemCopy(packet._bank.buf(),enet_packet_data(ev.packet)+1,size-1)
				EndIf

			Default
				Continue

			EndSelect

			EvaluateEvent(id,packet,ev.peer)

		Else
			Exit
		EndIf
	Forever
EndMethod

Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr) Abstract

EndType

Public


Const NETWORK_CONNECT:Int=1
Const NETWORK_DISCONNECT:Int=2
Const NETWORK_PINGREQUEST:Int=3
Const NETWORK_PINGRESPONSE:Int=4
Const NETWORK_JOINREQUEST:Int=5
Const NETWORK_JOINREPLY:Int=6
Const NETWORK_CHAT:Int=7


Type TServer Extends TNetworkNode

Const maxplayers:Int=64

Field clients:TList=New TList
Field callback(server:TServer,id:Int,packet:TPacket)

Method Delete()
	If enetpeer
		enetpeer=Null
	EndIf
	If enethost
		enet_host_destroy(enethost)
		enethost=Null
	EndIf	
EndMethod

Function Create:TServer(ip:Int=0,port:Int=7777)
	Local server:TServer=New TServer,addr:Byte Ptr

	If ip
		server.ip=ip
	Else
		server.ip=ENET_HOST_ANY
	EndIf
	server.port=port
	If server.port<>0 Or server.ip<>ENET_HOST_ANY
		addr=enet_address_create(server.ip,server.port)
	EndIf
	server.enethost=enet_host_create(addr,maxplayers,0,0)
	If addr
		enet_address_destroy(addr)
	EndIf
	If Not server.enethost
		Return Null		
	EndIf
	Return server
EndFunction

Method FindClient:TClient(ip:Int,port:Int)
	Local client:TClient
	For client=EachIn clients
		If client.ip=ip And client.port=port Return client
	Next
EndMethod

Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr)
	Local client:TClient
	Local issilent:Int=False

	client=FindClient(enet_peer_ip(enetpeer),enet_peer_port(enetpeer))
	Select id

	Case NETWORK_CONNECT
		If client Disconnect(client,True)
		client=New TClient
		client.enetpeer=enetpeer
		client.ip=enet_peer_ip(enetpeer)
		client.port=enet_peer_port(enetpeer)
		client.link=clients.AddLast(client)

	Case NETWORK_PINGREQUEST
		Send(client,NETWORK_PINGRESPONSE,packet)
		issilent=True

	Case NETWORK_CHAT
		Broadcast(id,packet)
		issilent=True

	EndSelect

	If Not issilent
		If callback
			callback(Self,id,packet)
		EndIf
	EndIf	
EndMethod

Method Send:Int(client:TClient,id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
	Local enetpacket:Byte Ptr
	Local result:Int

	If Not client.enetpeer RuntimeError "Can't send to local client."

	Local data:Byte[]
	If packet
		If packet._bank.size()=0 packet=Null
	EndIf
	If packet
		data=New Byte[packet._bank.size()+1]
		MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
	Else
		data=New Byte[1]
	EndIf
	data[0]=id
	enetpacket=enet_packet_create(data,data.length,flags)
	result=(enet_peer_send(client.enetpeer,channel,enetpacket)=0)		
	Return result
EndMethod

Method Broadcast(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
	Local enetpacket:Byte Ptr
	Local result:Int

	Local data:Byte[]
	If packet
		If packet._bank.size()=0 packet=Null
	EndIf
	If packet
		data=New Byte[packet._bank.size()+1]
		MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
	Else
		data=New Byte[1]
	EndIf
	data[0]=id
	enetpacket=enet_packet_create(data,data.length,flags)
	enet_host_broadcast(Self.enethost,channel,enetpacket)
EndMethod

Method Disconnect(client:TClient,force:Int=False)
	If client.enetpeer
		If force
			enet_peer_reset(client.enetpeer)
		Else
			enet_peer_disconnect(client.enetpeer)
		EndIf
		client.link.remove()
	EndIf
EndMethod

EndType


Type TClient Extends TNetworkNode

Const maxplayers:Int=64

Field link:TLink
Field server:TServer
Field connected:Int
Field callback(client:TClient,id:Int,packet:TPacket)

Function Create:TClient(ip:Int=0,port:Int=7776)
	Local client:TClient=New TClient
	Local addr:Byte Ptr

	If ip
		client.ip=ip
	Else
		client.ip=ENET_HOST_ANY
	EndIf
	client.port=port
	If client.port<>0 Or client.ip<>ENET_HOST_ANY addr=enet_address_create(client.ip,client.port)
	client.enethost=enet_host_create(addr,maxplayers,0,0)
	If addr enet_address_destroy(addr)
	If Not client.enethost
		Return Null
	EndIf
	Return client
EndFunction

Method Disconnect(force:Int=False)
	If server
		If force
			enet_peer_reset(server.enetpeer)
		Else
			enet_peer_disconnect(server.enetpeer)
		EndIf
		server=Null
	EndIf
EndMethod

Method Connect:TServer(ip:Int,port:Int)
	Local addr:Byte Ptr,server:TServer

	If Not Self.enethost RuntimeError "Remote client cannot connect to server."
	If server Disconnect()		
	server=New TServer
	server.ip=ip
	server.port=port
	addr=enet_address_create(server.ip,server.port)
	server.enetpeer=enet_host_connect(Self.enethost,addr,1)
	enet_address_destroy(addr)
	If server.enetpeer=Null Return Null
	Self.server=server
	Return server
EndMethod

Method Send:Int(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
	Local enetpacket:Byte Ptr
	Local result:Int

	If Not connected Return 0

	If Not server RuntimeError "Client is not connected."
	Local data:Byte[]
	If packet
		If packet._bank.size()=0 packet=Null
	EndIf
	If packet
		data=New Byte[packet._bank.size()+1]
		MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
	Else
		data=New Byte[1]
	EndIf
	data[0]=id
	enetpacket=enet_packet_create(data,data.length,flags)
	result=(enet_peer_send(server.enetpeer,channel,enetpacket)=0)		
	Return result
EndMethod

Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr)
	Local issilent:Int=False

	Select id

	Case NETWORK_CONNECT
		Self.connected=True

	Case NETWORK_PINGRESPONSE

	Case NETWORK_CHAT

	EndSelect

	If Not issilent
		If callback
			callback(Self,id,packet)
		EndIf
	EndIf
EndMethod

Method Ping:Int()
	Local packet:TPacket=New TPacket
	packet.WriteInt(MilliSecs())
	Return Send(NETWORK_PINGREQUEST,packet)
EndMethod

Method Say:Int(text:String)
	Local packet:TPacket=New TPacket
	packet.WriteLine(text)
	Return Send(NETWORK_CHAT,packet)		
EndMethod

EndType


Type TPacket Extends TBankStream

Method New()
	_bank=New TBank
EndMethod

EndType


Function CreatePacket:TPacket()
Local packet:TPacket=New TPacket
Return packet
EndFunction


'----------------------------------------------------------------------------------------------------

Local server:TServer
Local client1:TClient
Local client2:TClient
Local ip:Int=HostIp("127.0.0.1")

server=TServer.Create(ip,7777)
client1=TClient.Create(ip,7776)
client2=TClient.Create(ip,7779)

client1.connect(server.ip,server.port)
client2.connect(server.ip,server.port)

client1.callback=ClientCallBack
client2.callback=ClientCallBack
server.callback=ServerCallBack

Repeat
server.Update()
client1.Update()
client2.Update()
Delay 1
If Rand(100)=1
	client1.Ping()
	client2.Say("Hello!")
EndIf
Forever

Function ServerCallBack(server:TServer,id:Int,packet:TPacket)
Select id
Case NETWORK_CONNECT
	Print "New client connected."
EndSelect
EndFunction

Function ClientCallBack(client:TClient,id:Int,packet:TPacket)
Select id
Case NETWORK_CONNECT
	Print "Connected to server."
Case NETWORK_CHAT
	Print "Says: "+packet.ReadLine()
Case NETWORK_PINGRESPONSE
	Print "Ping = "+(MilliSecs()-packet.ReadInt())
EndSelect
EndFunction

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Here's a more advanced version. This is actually pretty fun once you get into it, because you can design your own protocols as you see fit. This handles chat, whisper, kicking, banning, recurring names, and name changes:

SuperStrict

Private

Const ENET_PACKET_FLAG_UNSEQUENCED:Int=2

Function enet_host_port:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[1]
Local port:Int=(Short Ptr peer)[4]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return port
EndFunction

Function enet_host_ip:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[1]
Local port:Int=(Short Ptr peer)[4]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return ip
EndFunction

Function enet_peer_port:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[3]
Local port:Int=(Short Ptr peer)[8]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return port
EndFunction

Function enet_peer_ip:Int( peer:Byte Ptr )
Local ip:Int=(Int Ptr peer)[3]
Local port:Int=(Short Ptr peer)[8]
?LittleEndian
ip=(ip Shr 24) | (ip Shr 8 & $ff00) | (ip Shl 8 & $ff0000) | (ip Shl 24)
?
Return ip
EndFunction

Type TNetworkNode

Field port:Int
Field ip:Int
Field enethost:Byte Ptr
Field enetpeer:Byte Ptr

Method Delete()
	If enethost
		enet_host_destroy(enethost)
		enethost=Null
	EndIf	
EndMethod

Method Update(callback(client:TClient,id:Int,packet:TPacket)=Null)
	Local ip:Int,port:Int,client:TClient,ev:ENetEvent=New ENetEvent,id:Byte,packet:TPacket

	If Not Self.enethost RuntimeError "Can't update a remote server."
	Repeat
		If enet_host_service(Self.enethost,ev,0)

			Select ev.event

			Case ENET_EVENT_TYPE_CONNECT
				id=NETWORK_CONNECT

			Case ENET_EVENT_TYPE_DISCONNECT
				id=NETWORK_DISCONNECT

			Case ENET_EVENT_TYPE_RECEIVE
				Local size:Int=enet_packet_size(ev.packet)
				Local data:Byte[size]
				MemCopy(Varptr id,enet_packet_data(ev.packet),1)
				If size>1
					packet=New TPacket
					packet._bank.resize(size-1)
					MemCopy(packet._bank.buf(),enet_packet_data(ev.packet)+1,size-1)
				EndIf

			Default
				Continue

			EndSelect

			EvaluateEvent(id,packet,ev.peer)

		Else
			Exit
		EndIf
	Forever
EndMethod

Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr) Abstract

EndType

Public


Const NETWORK_CONNECT:Int=1
Const NETWORK_DISCONNECT:Int=2
Const NETWORK_PINGREQUEST:Int=3
Const NETWORK_PINGRESPONSE:Int=4
Const NETWORK_JOINREQUEST:Int=5
Const NETWORK_JOINRESPONSE:Int=6
Const NETWORK_CHAT:Int=7
Const NETWORK_LEAVEGAME:Int=8
Const NETWORK_CHANGENAMEREQUEST:Int=9
Const NETWORK_CHANGENAMERESPONSE:Int=10
Const NETWORK_PLAYERJOINED:Int=11

Const SEND_RELIABLE:Int=ENET_PACKET_FLAG_RELIABLE
Const SEND_UNSEQUENCED:Int=ENET_PACKET_FLAG_UNSEQUENCED

Type TServer Extends TNetworkNode

Const maxplayers:Int=64

Field clients:TList=New TList
Field callback(server:TServer,client:TClient,id:Int,packet:TPacket)
Field bannedips:Int[]
Field clientmap:TMap=New TMap

Method Delete()
	If enethost
		enet_host_destroy(enethost)
		enethost=Null
	EndIf
EndMethod

Function Create:TServer(ip:Int=0,port:Int=7777)
	Local server:TServer=New TServer,addr:Byte Ptr

	If ip
		server.ip=ip
	Else
		server.ip=ENET_HOST_ANY
	EndIf
	server.port=port
	If server.port<>0 Or server.ip<>ENET_HOST_ANY
		addr=enet_address_create(server.ip,server.port)
	EndIf
	server.enethost=enet_host_create(addr,maxplayers,0,0)
	If addr
		enet_address_destroy(addr)
	EndIf
	If Not server.enethost
		Return Null		
	EndIf
	Return server
EndFunction

Method FindClientByName:TClient(name:String)
	Return TClient(clientmap.valueforkey(name))
EndMethod

Method FindClientByPeer:TClient(peer:Byte Ptr)
	Local client:TClient
	For client=EachIn clients
		If client.enetpeer=peer Return client
	Next
EndMethod

Method FindClient:TClient(ip:Int,port:Int)
	Local client:TClient
	For client=EachIn clients
		If client.ip=ip And client.port=port Return client
	Next
EndMethod

Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr)
	Local client:TClient
	Local issilent:Int=False

	client=FindClient(enet_peer_ip(enetpeer),enet_peer_port(enetpeer))
	Select id

	Case NETWORK_JOINREQUEST
		If client Disconnect(client,1)
		client=TClient.Find(enet_peer_ip(enetpeer),enet_peer_port(enetpeer))
		client.enetpeer=enetpeer

		If IPBanned(client.ip)
			Disconnect(client,0)
			issilent=1
		Else
			client.name=packet.ReadLine()
			If Not FindClientByName(client.name)
				clientmap.insert(client.name,client)
				clients.AddLast(client)
				Local responsepacket:TPacket=New TPacket
				responsepacket.WriteByte(1)
				responsepacket.WriteByte(clients.count())
				Local peer:TClient
				For peer=EachIn clients
					responsepacket.WriteLine(peer.name)
				Next
				Send(client,NETWORK_JOINRESPONSE,responsepacket,SEND_RELIABLE)
				id=NETWORK_PLAYERJOINED
			Else
				packet=New TPacket
				packet.WriteByte(0)'no, you can't joint
				packet.WriteByte(1)'reason: name already taken
				Send(client,NETWORK_JOINRESPONSE,packet,SEND_RELIABLE)
				'Disconnect(client,0)
				issilent=1
			EndIf
		EndIf

	Case NETWORK_CHANGENAMEREQUEST
		Local name:String=packet.ReadLine()
		packet=New TPacket
		If client
			If FindClientByName(name)<>Null And client.name<>name
				packet.WriteByte(0)
				send(client,NETWORK_CHANGENAMERESPONSE,packet,SEND_RELIABLE)
				issilent=True
			Else
				packet.WriteByte(1)
				packet.WriteLine(name)
				send(client,NETWORK_CHANGENAMERESPONSE,packet,SEND_RELIABLE)
				clientmap.remove(client.name)
				client.name=name
				clientmap.insert(name,client)
			EndIf
		Else
			If FindClientByName(name)=Null
				packet.WriteByte(1)
				packet.WriteLine(name)
				send(client,NETWORK_CHANGENAMERESPONSE,packet,SEND_RELIABLE)
				issilent=True
			EndIf
		EndIf

	Case NETWORK_CONNECT
		issilent=True

	Case NETWORK_DISCONNECT
		client=FindClientByPeer(enetpeer)			
		If client
			Disconnect(client,0)
		Else
			issilent=True
		EndIf

	Case NETWORK_PINGREQUEST
		Send(client,NETWORK_PINGRESPONSE,packet)
		issilent=True

	Case NETWORK_CHAT
		Local relay:TPacket=New TPacket
		Local count:Int=packet.ReadByte()
		relay.WriteLine(packet.ReadLine())
		If count=0				
			Broadcast(NETWORK_CHAT,relay)
		Else
			For Local n:Int=1 To count
				client=FindClientByName(packet.ReadLine())
				If client
					Send(client,NETWORK_CHAT,relay)
				EndIf
			Next
		EndIf

		issilent=True

	EndSelect

	If Not issilent
		If callback
			If packet packet.seek(0)
			callback(Self,client,id,packet)
		EndIf
	EndIf	
EndMethod

Method Send:Int(client:TClient,id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
	Local enetpacket:Byte Ptr
	Local result:Int

	If Not client.enetpeer RuntimeError "Can't send to local client."

	Local data:Byte[]
	If packet
		If packet._bank.size()=0 packet=Null
	EndIf
	If packet
		data=New Byte[packet._bank.size()+1]
		MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
	Else
		data=New Byte[1]
	EndIf
	data[0]=id
	enetpacket=enet_packet_create(data,data.length,flags)
	result=(enet_peer_send(client.enetpeer,channel,enetpacket)=0)		
	Return result
EndMethod

Method Broadcast:Int(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
	Local result:Int=1
	For Local client:TClient=EachIn clients
		If Not Send(client,id,packet,flags,channel) result=0
	Next
	Return result
	Rem
	Local enetpacket:Byte Ptr
	Local result:Int

	Local data:Byte[]
	If packet
		If packet._bank.size()=0 packet=Null
	EndIf
	If packet
		data=New Byte[packet._bank.size()+1]
		MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
	Else
		data=New Byte[1]
	EndIf
	data[0]=id
	enetpacket=enet_packet_create(data,data.length,flags)
	enet_host_broadcast(Self.enethost,channel,enetpacket)
	EndRem
EndMethod

Method Disconnect(client:TClient,force:Int=False)
	If client.enetpeer
		If force
			enet_peer_reset(client.enetpeer)
		Else
			enet_peer_disconnect(client.enetpeer)
		EndIf
		clients.remove(client)
		If Not client.enethost
			client.link.remove()
		EndIf
		If clientmap.valueforkey(client.name)=client
			clientmap.remove(client.name)
		EndIf
	EndIf
EndMethod

Method BanIP(ip:Int)
	bannedips=bannedips[..bannedips.length+1]
	bannedips[bannedips.length-1]=ip
EndMethod

Method IPBanned:Int(ip:Int)
	For Local n:Int=0 To bannedips.length-1
		If ip=bannedips[n] Return True
	Next
	Return False
EndMethod

Method Kick(client:TClient)
	BanIP(client.ip)
	Disconnect(client)
EndMethod

EndType

Type TClient Extends TNetworkNode

Const maxplayers:Int=64

Global list:TList=New TList

Field name:String
Field link:TLink
Field server:TServer
Field connected:Int
Field joined:Int=0
Field callback(client:TClient,id:Int,packet:TPacket)

Function Find:TClient(ip:Int,port:Int)
	Local client:TClient
	For client=EachIn list
		If client.ip=ip And client.port=port Return client
	Next
	client=New TClient
	client.ip=ip
	client.port=port
	client.link=list.addlast(client)
	Return client
EndFunction

Function Create:TClient(ip:Int=0,port:Int=7776)
	Local client:TClient=Find(ip,port)
	Local addr:Byte Ptr

	If ip
		client.ip=ip
	Else
		client.ip=ENET_HOST_ANY
	EndIf
	client.port=port
	If client.port<>0 Or client.ip<>ENET_HOST_ANY addr=enet_address_create(client.ip,client.port)
	client.enethost=enet_host_create(addr,maxplayers,0,0)
	If addr enet_address_destroy(addr)
	If Not client.enethost
		Return Null
	EndIf
	Return client
EndFunction

Method Disconnect(force:Int=False)
	If server
		If force
			enet_peer_reset(server.enetpeer)
		Else
			enet_peer_disconnect(server.enetpeer)
		EndIf
		server=Null
	EndIf
EndMethod

Method SetName(name:String)
	If name.length>16 name=name[..16]
	If server
		Local packet:TPacket=New TPacket
		packet.WriteLine(name)
		Send(NETWORK_CHANGENAMEREQUEST,packet,SEND_RELIABLE)
	Else
		Self.name=name
	EndIf
EndMethod

Method Connect:Int(ip:Int,port:Int)
	Local addr:Byte Ptr

	If Not Self.enethost RuntimeError "Remote client cannot connect to server."
	If server Disconnect()		
	server=New TServer
	server.ip=ip
	server.port=port
	addr=enet_address_create(server.ip,server.port)
	server.enetpeer=enet_host_connect(Self.enethost,addr,1)
	enet_address_destroy(addr)
	If server.enetpeer=Null
		server=Null
		Return 0
	EndIf
	Return 1
EndMethod

Method Send:Int(id:Int,packet:TPacket=Null,flags:Int=0,channel:Int=0)
	Local enetpacket:Byte Ptr
	Local result:Int

	If Not connected Return 0

	If Not server RuntimeError "Client is not connected."
	Local data:Byte[]
	If packet
		If packet._bank.size()=0 packet=Null
	EndIf
	If packet
		data=New Byte[packet._bank.size()+1]
		MemCopy(Varptr data[1],packet._bank.buf(),packet._bank.size())
	Else
		data=New Byte[1]
	EndIf
	data[0]=id
	enetpacket=enet_packet_create(data,data.length,flags)
	result=(enet_peer_send(server.enetpeer,channel,enetpacket)=0)		
	Return result
EndMethod

Method EvaluateEvent(id:Int,packet:TPacket,enetpeer:Byte Ptr)
	Local issilent:Int=False

	Select id

	Case NETWORK_CONNECT
		Self.connected=True
		packet=New TPacket
		packet.WriteLine(name)
		Send(NETWORK_JOINREQUEST,packet)

	Case NETWORK_JOINRESPONSE
		joined=packet.ReadByte()

	Case NETWORK_CHANGENAMERESPONSE
		If packet.ReadByte()=1
			name=packet.ReadLine()
		EndIf

	Case NETWORK_PINGRESPONSE


	Case NETWORK_CHAT


	EndSelect

	If Not issilent
		If callback
			If packet packet.seek(0)
			callback(Self,id,packet)
		EndIf
	EndIf
EndMethod

Method Join()
	If Not joined
		Local packet:TPacket=New TPacket
		packet.WriteLine(name)
		Send(NETWORK_JOINREQUEST,packet)
	EndIf
EndMethod

Method Ping:Int()
	Local packet:TPacket=New TPacket
	packet.WriteInt(MilliSecs())
	Return Send(NETWORK_PINGREQUEST,packet)
EndMethod

Method Say:Int(text:String,recipients:String[]=Null)
	Local packet:TPacket=New TPacket
	If recipients
		packet.WriteByte(recipients.length)
		packet.WriteLine(text)
		For Local n:Int=0 To recipients.length-1
			packet.WriteLine(recipients[n])
		Next
	Else
		packet.WriteByte(0)
		packet.WriteLine(text)
	EndIf
	Return Send(NETWORK_CHAT,packet)		
EndMethod

EndType


Type TPacket Extends TBankStream

Method New()
	_bank=New TBank
EndMethod

EndType


Function CreatePacket:TPacket()
Local packet:TPacket=New TPacket
Return packet
EndFunction


'----------------------------------------------------------------------------------------------------

Local server:TServer
Local client1:TClient
Local client2:TClient
Local ip:Int=HostIp("127.0.0.1")

server=TServer.Create(ip,7777)
client1=TClient.Create(ip,7776)
client2=TClient.Create(ip,7779)

client1.name="Steve"
client2.name="Bob"

client1.connect(server.ip,server.port)
client2.connect(server.ip,server.port)

client1.callback=ClientCallBack
client2.callback=ClientCallBack
server.callback=ServerCallBack

Repeat
server.Update()
client1.Update()
client2.Update()
Delay 1
If Rand(100)=1
	client1.SetName("Barney")
	client1.Disconnect()

	'client2.Ping()
	client2.Say("Hello!")
EndIf
Forever

Function ServerCallBack(server:TServer,client:TClient,id:Int,packet:TPacket)
Select id
Case NETWORK_PLAYERJOINED
	Print "New player "+packet.ReadLine()+" joined."
Case NETWORK_DISCONNECT
	If client
		Print client.name+" disconnected."
	Else
		Print "Unknown disconnection"
		Print server.clients.count()+" players"
	EndIf
Case NETWORK_CONNECT
	Print "New client connected."
EndSelect
EndFunction

Function ClientCallBack(client:TClient,id:Int,packet:TPacket)
Select id
Case NETWORK_CHANGENAMERESPONSE
	If packet.ReadByte()=1
		Print "Changed name to "+packet.ReadLine()
	Else
		Print "Name change rejected."
	EndIf
Case NETWORK_JOINRESPONSE
	If packet.ReadByte()=1
		Print "Joined game"
		Local count:Int=packet.ReadByte()
		Print count+" players"
		For Local n:Int=1 To count
			Print n+". "+packet.ReadLine()
		Next
	Else
		Print "Rejected from game"
		Select packet.ReadByte()
		Case 1
			Print "Name already in use"
		Default
			Print "Unknown reason"
		EndSelect
	EndIf
Case NETWORK_DISCONNECT
	Print "Disconnected from server."
Case NETWORK_CONNECT
	Print "Connected to server."
Case NETWORK_CHAT
	Print "Says: "+packet.ReadLine()
Case NETWORK_PINGRESPONSE
	Print "Ping = "+(MilliSecs()-packet.ReadInt())
EndSelect
EndFunction

  • Upvote 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

This is probably just preference, but I also liked mapping messages to functions instead of having 1 huge callback function for all the events with 1 huge case statement. I just felt it makes the code cleaner. That's starting to look like Win32 API programming with the case statement.

Link to comment
Share on other sites

In lua terms it would be a table of functions where the key is the message id.

 

I guess it's nothing we couldn't do ourselves, but would be cool to have it part of the code.

 

So instead of:

Function ClientCallBack(client:TClient,id:Int,packet:TPacket)
       Select id
       Case NETWORK_CHANGENAMERESPONSE
               If packet.ReadByte()=1
                       Print "Changed name to "+packet.ReadLine()
               Else
                       Print "Name change rejected."
               EndIf
       Case NETWORK_JOINRESPONSE
               If packet.ReadByte()=1
                       Print "Joined game"
                       Local count:Int=packet.ReadByte()
                       Print count+" players"
                       For Local n:Int=1 To count
                               Print n+". "+packet.ReadLine()
                       Next
               Else
                       Print "Rejected from game"
                       Select packet.ReadByte()
                       Case 1
                               Print "Name already in use"
                       Default
                               Print "Unknown reason"
                       EndSelect
               EndIf
       Case NETWORK_DISCONNECT
               Print "Disconnected from server."
       Case NETWORK_CONNECT
               Print "Connected to server."
       Case NETWORK_CHAT
               Print "Says: "+packet.ReadLine()
       Case NETWORK_PINGRESPONSE
               Print "Ping = "+(MilliSecs()-packet.ReadInt())
       EndSelect
EndFunction

 

So the users would define a function and assign it to the message

    function NameChange(client, packet)
    end

    networkMessage[NETWORK_CHANGENAMERESPONSE] = NameChange

 

Then the library message pump would check the message id to see if it exists in the table and if so call the function the user defined for it.

 

networkMessage[id](client, packet)

 

 

The users could still do this even if you have 1 callback function anyway I guess. This just seems more "event" driven.

Link to comment
Share on other sites

  • 8 months later...
  • 2 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...