Messages for games
A socket (Sockets) carries a stream of bytes. A game wants something else: short packets that arrive whole, some of which must arrive (a chat line, a move) and some of which may be dropped if a newer one is coming (where a player is now). ENet gives exactly that, over UDP, and the xTalk Suite's enetxt brings it to xTalk with handlers that begin with en. Its helpers, such as enStateName and enFormatBytes, come with it.
ENet never calls your scripts by itself. You call enPoll again and again, on a timer in an app, and each call hands back what has happened since the last: a peer connected, a packet arrived, a peer left. Here the server and the client are in one script on one computer, talking through 127.0.0.1, and a loop polls both.
Hosts and holds
enInitialize takes a hold on the library before any other call, and enDeinitialize gives it back, once for each hold. A host is your end of the line: enHostCreateServer makes one that listens, given the address to listen on, a port (0 picks any free one), how many peers may join, how many channels each has, and speed limits in bytes a second (0 for none). enHostStatus says where it listens:
put enInitialize() into tStarted
put tStarted && "(0 is good)"
put enLibraryVersion() begins with "enet "
put enHostCreateServer("127.0.0.1", 0, 8, 2, 0, 0) into tServer
put enHostStatus(tServer) into tStatus
set the itemDelimiter to ":"
put item 1 of tStatus["address"]
put item 2 of tStatus["address"] > 0
put tStatus["peerCount"] && "peers"
get enHostDestroy(tServer)
put enHostStatus(tServer) is empty
enDeinitialize
0 (0 is good) true 127.0.0.1 true 0 peers true
A closed host's handle is retired: asking about it gives empty, never another host's answer.
Connecting
A client host connects to a server. enConnect gives a peer handle at once, but the connection is real only when an event says so. Each event that enPoll returns is an array with a "name": enetConnect, enetReceive or enetDisconnect. The last parameter of enConnect is a number the server receives with the connection, such as the version of your game:
get enInitialize()
put enHostCreateServer("127.0.0.1", 0, 4, 1, 0, 0) into tServer
set the itemDelimiter to ":"
put enHostStatus(tServer) into tStatus
put item 2 of tStatus["address"] into tPort
put enHostCreateClient(1, 1, 0, 0) into tClient
put enConnect(tClient, "127.0.0.1", tPort, 1, 7) into tPeer
-- nothing happens until the hosts are polled
repeat 400 times
get enPoll(tClient)
put enPoll(tServer) into tEvents
if tEvents[1]["name"] is "enetConnect" then exit repeat
wait 5 milliseconds
end repeat
put "The server got" && tEvents[1]["name"] && "with" && tEvents[1]["data"]
put enPeerStatus(tPeer) into tStatus
put "The client is" && enStateName(tStatus["state"])
get enHostDestroy(tClient)
get enHostDestroy(tServer)
enDeinitialize
The server got enetConnect with 7 The client is connected
Text and bytes
Once connected, enSendText sends text on a channel, and the other side gets an enetReceive event whose "payload" is the bytes: textDecode turns them back into text. enSend sends any bytes, up to 60000 a packet, which is how games keep their messages small. Both return 0 when the packet is queued, and a negative number when not (-4: too big). Packets leave at the next poll, or at once with enFlush:
get enInitialize()
put enHostCreateServer("127.0.0.1", 0, 4, 1, 0, 0) into tServer
set the itemDelimiter to ":"
put enHostStatus(tServer) into tStatus
put item 2 of tStatus["address"] into tPort
put enHostCreateClient(1, 1, 0, 0) into tClient
put enConnect(tClient, "127.0.0.1", tPort, 1, 0) into tPeer
put enSend(tPeer, 0, format("%60001s", ""), 0) && "for 60001 bytes"
repeat 400 times
get enPoll(tClient)
repeat for each element tEvent in enPoll(tServer)
if tEvent["name"] is "enetConnect" then
get enSendText(tPeer, 0, "Hello, server!", 0)
get enSend(tPeer, 0, numToByte(200), 0)
else if tEvent["name"] is "enetReceive" then
put tEvent["payload"] & return after tReceived
end if
end repeat
if the number of lines of tReceived is 2 then exit repeat
wait 5 milliseconds
end repeat
put textDecode(line 1 of tReceived, "UTF-8")
put byteToNum(line 2 of tReceived)
get enHostDestroy(tClient)
get enHostDestroy(tServer)
enDeinitialize
-4 for 60001 bytes Hello, server! 200
Channels and delivery
A connection can have several channels, numbered from 0, and a late packet on one never holds up another. Each send also has a delivery flag:
0, reliable: resent until it arrives, in order. For chat and moves.1, unreliable: in order, but it may be dropped. For where a player is now: a newer one comes soon.2: it may be dropped, or arrive out of order.
get enInitialize()
put enHostCreateServer("127.0.0.1", 0, 4, 2, 0, 0) into tServer
set the itemDelimiter to ":"
put enHostStatus(tServer) into tStatus
put item 2 of tStatus["address"] into tPort
put enHostCreateClient(1, 2, 0, 0) into tClient
put enConnect(tClient, "127.0.0.1", tPort, 2, 0) into tPeer
repeat 400 times
get enPoll(tClient)
put enPoll(tServer) into tEvents
if tEvents[1]["name"] is "enetConnect" then
-- where the player is: channel 1, unreliable
get enSendText(tPeer, 1, "x=10 y=20", 1)
end if
if tEvents[1]["name"] is "enetReceive" then exit repeat
wait 5 milliseconds
end repeat
put "channel" && tEvents[1]["channel"] & ":" && textDecode(tEvents[1]["payload"], "UTF-8")
get enHostDestroy(tClient)
get enHostDestroy(tServer)
enDeinitialize
channel 1: x=10 y=20
Health, and goodbye
enPeerStatus tells a game how a connection is: its "state", the round trip in milliseconds ("rtt"), packets lost, and bytes sent and received, which enFormatBytes writes for people. enSetPeerTimeout decides when a quiet peer counts as gone. enDisconnect ends a connection politely: queued packets go first, then both sides get an enetDisconnect event, and the peer handle is retired, so a send to it is refused:
get enInitialize()
put enHostCreateServer("127.0.0.1", 0, 4, 1, 0, 0) into tServer
set the itemDelimiter to ":"
put enHostStatus(tServer) into tStatus
put item 2 of tStatus["address"] into tPort
put enHostCreateClient(1, 1, 0, 0) into tClient
put enConnect(tClient, "127.0.0.1", tPort, 1, 0) into tPeer
repeat 400 times
get enPoll(tClient)
put enPoll(tServer) into tEvents
if tEvents[1]["name"] is "enetConnect" then
put enPeerStatus(tPeer) into tStatus
put enStateName(tStatus["state"]) & "," && enFormatBytes(tStatus["bytesSent"]) && "sent" into tHealth
-- give up on a silent peer after 5 to 30 seconds
get enSetPeerTimeout(tPeer, 0, 5000, 30000)
get enDisconnect(tPeer, 0)
end if
if tEvents[1]["name"] is "enetDisconnect" then exit repeat
wait 5 milliseconds
end repeat
put matchText(tHealth, "^connected, [0-9]+ B sent$")
put "The server got" && tEvents[1]["name"]
put enSendText(tPeer, 0, "still there?", 0) < 0
get enHostDestroy(tClient)
get enHostDestroy(tServer)
enDeinitialize
true The server got enetDisconnect true
Try it yourself
Make the server answer: when it receives text, it sends it back in capitals to the peer that sent it (the event's "peer"). The client sends ping and puts what comes back.
Show a solution
get enInitialize()
put enHostCreateServer("127.0.0.1", 0, 4, 1, 0, 0) into tServer
set the itemDelimiter to ":"
put enHostStatus(tServer) into tStatus
put item 2 of tStatus["address"] into tPort
put enHostCreateClient(1, 1, 0, 0) into tClient
put enConnect(tClient, "127.0.0.1", tPort, 1, 0) into tPeer
repeat 400 times
repeat for each element tEvent in enPoll(tServer)
if tEvent["name"] is "enetReceive" then
get enSendText(tEvent["peer"], 0, toUpper(textDecode(tEvent["payload"], "UTF-8")), 0)
end if
end repeat
repeat for each element tEvent in enPoll(tClient)
if tEvent["name"] is "enetConnect" then
get enSendText(tPeer, 0, "ping", 0)
else if tEvent["name"] is "enetReceive" then
put textDecode(tEvent["payload"], "UTF-8") into tAnswer
end if
end repeat
if tAnswer is not empty then exit repeat
wait 5 milliseconds
end repeat
put tAnswer
get enHostDestroy(tClient)
get enHostDestroy(tServer)
enDeinitialize
PING
Words in this lesson
enInitializeenDeinitializeenHostCreateServerenHostCreateClientenHostStatusenHostDestroyenConnectenPollenSendTextenSendenFlushenPeerStatusenStateNameenDisconnectenSetPeerTimeoutenFormatBytesenLastErrorEach one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.