webrtc_signaling/server/ws_webrtc_server.gd
pietru 2098118844 Try adding is lobby public
If lobby is public it will allow it to be recived as public lobby for joining it via some kind of automatic system...
2024-05-31 09:54:56 +02:00

241 lines
6.2 KiB
GDScript

extends Node
enum Message {JOIN, ID, PEER_CONNECT, PEER_DISCONNECT, OFFER, ANSWER, CANDIDATE, SEAL, GET_PUBLIC}
const TIMEOUT = 1000 # Unresponsive clients times out after 1 sec
const SEAL_TIME = 10000 # A sealed room will be closed after this time
const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
var _alfnum = ALFNUM.to_ascii_buffer()
var rand: RandomNumberGenerator = RandomNumberGenerator.new()
var lobbies: Dictionary = {}
var tcp_server := TCPServer.new()
var peers: Dictionary = {}
class Peer extends RefCounted:
var id = -1
var lobby = ""
var time = Time.get_ticks_msec()
var ws = WebSocketPeer.new()
func _init(peer_id, tcp):
id = peer_id
ws.accept_stream(tcp)
func is_ws_open() -> bool:
return ws.get_ready_state() == WebSocketPeer.STATE_OPEN
func send(type: int, id: int, data:=""):
return ws.send_text(JSON.stringify({
"type": type,
"id": id,
"data": data,
}))
class Lobby extends RefCounted:
var peers: = {}
var peers_order: = []
var host: int = -1
var sealed: bool = false
var time = 0
var mesh := true
var single_host := false
var public := false
func _init(host_id: int, use_mesh: bool, is_single_host: bool, is_public:bool):
host = host_id
mesh = use_mesh
single_host = is_single_host
public=is_public
func join(peer: Peer) -> bool:
if sealed: return false
if not peer.is_ws_open(): return false
peer.send(Message.ID, peer.id, "true" if mesh else "") #(1 if peer.id == host else peer.id)
for p in peers.values():
if not p.is_ws_open():
continue
if not mesh and p.id != host:
# Only host is visible when using client-server
continue
p.send(Message.PEER_CONNECT, peer.id, str(peers_order[0]))
peer.send(Message.PEER_CONNECT, p.id, str(peers_order[0]))
peers[peer.id] = peer
peers_order.append(peer.id)
return true
func leave(peer: Peer) -> bool:
if peers_order.has(peer.id):
peers_order.erase(peer.id)
if peers_order.size()>0 and !single_host:
host=peers_order[0]
if not peers.has(peer.id): return false
peers.erase(peer.id)
var close = false
if peer.id == host:
# The room host disconnected, will disconnect all peers.
close = true
if sealed: return close
# Notify other peers.
for p in peers.values():
if not p.is_ws_open():
continue
if close:
# Disconnect peers.
p.ws.close()
else:
# Notify disconnection.
p.send(Message.PEER_DISCONNECT, peer.id)
return close
func seal(peer_id: int) -> bool:
# Only host can seal the room.
if host != peer_id: return false
sealed = true
for p in peers.values():
if not p.is_ws_open():
continue
p.send(Message.SEAL, 0)
time = Time.get_ticks_msec()
peers.clear()
peers_order.clear()
return true
func _process(delta):
poll()
func listen(port):
stop()
rand.seed = Time.get_unix_time_from_system()
tcp_server.listen(port)
func stop():
tcp_server.stop()
peers.clear()
func poll():
if not tcp_server.is_listening():
return
if tcp_server.is_connection_available():
var id = randi() % (1 << 31)
peers[id] = Peer.new(id, tcp_server.take_connection())
# Poll peers.
var to_remove := []
for p in peers.values():
# Peers timeout.
if p.lobby == "" and Time.get_ticks_msec() - p.time > TIMEOUT:
p.ws.close()
p.ws.poll()
while p.is_ws_open() and p.ws.get_available_packet_count():
if not _parse_msg(p):
print("Parse message failed from peer %d" % p.id)
to_remove.push_back(p.id)
p.ws.close()
break
var state = p.ws.get_ready_state()
if state == WebSocketPeer.STATE_CLOSED:
print("Peer %d disconnected from lobby: '%s'" % [p.id, p.lobby])
# Remove from lobby (and lobby itself if host).
if lobbies.has(p.lobby) and lobbies[p.lobby].leave(p):
print("Deleted lobby %s" % p.lobby)
lobbies.erase(p.lobby)
# Remove from peers
to_remove.push_back(p.id)
# Lobby seal.
for k in lobbies:
if not lobbies[k].sealed:
continue
if lobbies[k].time + SEAL_TIME < Time.get_ticks_msec():
# Close lobby.
for p in lobbies[k].peers:
p.ws.close()
to_remove.push_back(p.id)
# Remove stale peers
for id in to_remove:
peers.erase(id)
func _join_lobby(peer: Peer, lobby: String, mesh: bool, single_host:bool, public:bool) -> bool:
if lobby == "":
for _i in range(0, 32):
lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length()-1)])
lobbies[lobby] = Lobby.new(peer.id, mesh, single_host, public)
elif not lobbies.has(lobby):
return false
lobbies[lobby].join(peer)
peer.lobby = lobby
# Notify peer of its lobby
peer.send(Message.JOIN, 0, lobby)
print("Peer %d joined lobby: '%s'" % [peer.id, lobby])
return true
func _parse_msg(peer: Peer) -> bool:
var pkt_str: String = peer.ws.get_packet().get_string_from_utf8()
var parsed = JSON.parse_string(pkt_str)
if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("type") or not parsed.has("id") or \
typeof(parsed.get("data")) != TYPE_STRING:
return false
if not str(parsed.type).is_valid_int() or not str(parsed.id).is_valid_int():
return false
var msg := {
"type": str(parsed.type).to_int(),
"id": str(parsed.id).to_int(),
"data": parsed.data
}
if msg.type == Message.JOIN:
if peer.lobby: # Peer must not have joined a lobby already!
return false
var dt = JSON.parse_string(msg.data)
return _join_lobby(peer, dt.lobby, msg.id == 0, dt.single_host, dt.public)
if not lobbies.has(peer.lobby): # Lobby not found?
return false
var lobby = lobbies[peer.lobby]
if msg.type == Message.SEAL:
# Client is sealing the room
return lobby.seal(peer.id)
if msg.type == Message.GET_PUBLIC:
var pb = []
for lb in lobbies.keys():
if lobbies[lb].public:
pb.append(lb)
peers[peer.id].send(msg.type, 0, JSON.stringify(pb))
return true
var dest_id: int = msg.id
if dest_id == MultiplayerPeer.TARGET_PEER_SERVER:
dest_id = lobby.host
if not peers.has(dest_id): # Destination ID not connected
return false
if peers[dest_id].lobby != peer.lobby: # Trying to contact someone not in same lobby
return false
if msg.type in [Message.OFFER, Message.ANSWER, Message.CANDIDATE]:
var source = peer.id#MultiplayerPeer.TARGET_PEER_SERVER if peer.id == lobby.host else peer.id
peers[dest_id].send(msg.type, source, msg.data)
return true
return false # Unknown message