ws_webrtc_server.gd 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. extends Node
  2. enum Message {JOIN, ID, PEER_CONNECT, PEER_DISCONNECT, OFFER, ANSWER, CANDIDATE, SEAL}
  3. const TIMEOUT = 1000 # Unresponsive clients times out after 1 sec
  4. const SEAL_TIME = 10000 # A sealed room will be closed after this time
  5. const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  6. var _alfnum = ALFNUM.to_ascii_buffer()
  7. var rand: RandomNumberGenerator = RandomNumberGenerator.new()
  8. var lobbies: Dictionary = {}
  9. var tcp_server := TCPServer.new()
  10. var peers: Dictionary = {}
  11. class Peer extends RefCounted:
  12. var id = -1
  13. var lobby = ""
  14. var time = Time.get_ticks_msec()
  15. var ws = WebSocketPeer.new()
  16. func _init(peer_id, tcp):
  17. id = peer_id
  18. ws.accept_stream(tcp)
  19. func is_ws_open() -> bool:
  20. return ws.get_ready_state() == WebSocketPeer.STATE_OPEN
  21. func send(type: int, id: int, data:=""):
  22. return ws.send_text(JSON.stringify({
  23. "type": type,
  24. "id": id,
  25. "data": data,
  26. }))
  27. class Lobby extends RefCounted:
  28. var peers: = {}
  29. var host: int = -1
  30. var sealed: bool = false
  31. var time = 0
  32. var mesh := true
  33. func _init(host_id: int, use_mesh: bool):
  34. host = host_id
  35. mesh = use_mesh
  36. func join(peer: Peer) -> bool:
  37. if sealed: return false
  38. if not peer.is_ws_open(): return false
  39. peer.send(Message.ID, (1 if peer.id == host else peer.id), "true" if mesh else "")
  40. for p in peers.values():
  41. if not p.is_ws_open():
  42. continue
  43. if not mesh and p.id != host:
  44. # Only host is visible when using client-server
  45. continue
  46. p.send(Message.PEER_CONNECT, peer.id)
  47. peer.send(Message.PEER_CONNECT, (1 if p.id == host else p.id))
  48. peers[peer.id] = peer
  49. return true
  50. func leave(peer: Peer) -> bool:
  51. if not peers.has(peer.id): return false
  52. peers.erase(peer.id)
  53. var close = false
  54. if peer.id == host:
  55. # The room host disconnected, will disconnect all peers.
  56. close = true
  57. if sealed: return close
  58. # Notify other peers.
  59. for p in peers.values():
  60. if not p.is_ws_open():
  61. continue
  62. if close:
  63. # Disconnect peers.
  64. p.ws.close()
  65. else:
  66. # Notify disconnection.
  67. p.send(Message.PEER_DISCONNECT, peer.id)
  68. return close
  69. func seal(peer_id: int) -> bool:
  70. # Only host can seal the room.
  71. if host != peer_id: return false
  72. sealed = true
  73. for p in peers.values():
  74. if not p.is_ws_open():
  75. continue
  76. p.send(Message.SEAL, 0)
  77. time = Time.get_ticks_msec()
  78. peers.clear()
  79. return true
  80. func _process(delta):
  81. poll()
  82. func listen(port):
  83. if OS.get_name() == "Web":
  84. OS.alert("Cannot create WebSocket servers in Web exports due to browsers' limitations.")
  85. return
  86. stop()
  87. rand.seed = Time.get_unix_time_from_system()
  88. tcp_server.listen(port)
  89. func stop():
  90. tcp_server.stop()
  91. peers.clear()
  92. func poll():
  93. if not tcp_server.is_listening():
  94. return
  95. if tcp_server.is_connection_available():
  96. var id = randi() % (1 << 31)
  97. peers[id] = Peer.new(id, tcp_server.take_connection())
  98. # Poll peers.
  99. var to_remove := []
  100. for p in peers.values():
  101. # Peers timeout.
  102. if p.lobby == "" and Time.get_ticks_msec() - p.time > TIMEOUT:
  103. p.ws.close()
  104. p.ws.poll()
  105. while p.is_ws_open() and p.ws.get_available_packet_count():
  106. if not _parse_msg(p):
  107. print("Parse message failed from peer %d" % p.id)
  108. to_remove.push_back(p.id)
  109. p.ws.close()
  110. break
  111. var state = p.ws.get_ready_state()
  112. if state == WebSocketPeer.STATE_CLOSED:
  113. print("Peer %d disconnected from lobby: '%s'" % [p.id, p.lobby])
  114. # Remove from lobby (and lobby itself if host).
  115. if lobbies.has(p.lobby) and lobbies[p.lobby].leave(p):
  116. print("Deleted lobby %s" % p.lobby)
  117. lobbies.erase(p.lobby)
  118. # Remove from peers
  119. to_remove.push_back(p.id)
  120. # Lobby seal.
  121. for k in lobbies:
  122. if not lobbies[k].sealed:
  123. continue
  124. if lobbies[k].time + SEAL_TIME < Time.get_ticks_msec():
  125. # Close lobby.
  126. for p in lobbies[k].peers:
  127. p.ws.close()
  128. to_remove.push_back(p.id)
  129. # Remove stale peers
  130. for id in to_remove:
  131. peers.erase(id)
  132. func _join_lobby(peer: Peer, lobby: String, mesh: bool) -> bool:
  133. if lobby == "":
  134. for _i in range(0, 32):
  135. lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length()-1)])
  136. lobbies[lobby] = Lobby.new(peer.id, mesh)
  137. elif not lobbies.has(lobby):
  138. return false
  139. lobbies[lobby].join(peer)
  140. peer.lobby = lobby
  141. # Notify peer of its lobby
  142. peer.send(Message.JOIN, 0, lobby)
  143. print("Peer %d joined lobby: '%s'" % [peer.id, lobby])
  144. return true
  145. func _parse_msg(peer: Peer) -> bool:
  146. var pkt_str: String = peer.ws.get_packet().get_string_from_utf8()
  147. var parsed = JSON.parse_string(pkt_str)
  148. if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("type") or not parsed.has("id") or \
  149. typeof(parsed.get("data")) != TYPE_STRING:
  150. return false
  151. if not str(parsed.type).is_valid_int() or not str(parsed.id).is_valid_int():
  152. return false
  153. var msg := {
  154. "type": str(parsed.type).to_int(),
  155. "id": str(parsed.id).to_int(),
  156. "data": parsed.data
  157. }
  158. if msg.type == Message.JOIN:
  159. if peer.lobby: # Peer must not have joined a lobby already!
  160. return false
  161. return _join_lobby(peer, msg.data, msg.id == 0)
  162. if not lobbies.has(peer.lobby): # Lobby not found?
  163. return false
  164. var lobby = lobbies[peer.lobby]
  165. if msg.type == Message.SEAL:
  166. # Client is sealing the room
  167. return lobby.seal(peer.id)
  168. var dest_id: int = msg.id
  169. if dest_id == MultiplayerPeer.TARGET_PEER_SERVER:
  170. dest_id = lobby.host
  171. if not peers.has(dest_id): # Destination ID not connected
  172. return false
  173. if peers[dest_id].lobby != peer.lobby: # Trying to contact someone not in same lobby
  174. return false
  175. if msg.type in [Message.OFFER, Message.ANSWER, Message.CANDIDATE]:
  176. var source = MultiplayerPeer.TARGET_PEER_SERVER if peer.id == lobby.host else peer.id
  177. peers[dest_id].send(msg.type, source, msg.data)
  178. return true
  179. return false # Unknown message