ws_webrtc_server.gd 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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. stop()
  84. rand.seed = Time.get_unix_time_from_system()
  85. tcp_server.listen(port)
  86. func stop():
  87. tcp_server.stop()
  88. peers.clear()
  89. func poll():
  90. if not tcp_server.is_listening():
  91. return
  92. if tcp_server.is_connection_available():
  93. var id = randi() % (1 << 31)
  94. peers[id] = Peer.new(id, tcp_server.take_connection())
  95. # Poll peers.
  96. var to_remove := []
  97. for p in peers.values():
  98. # Peers timeout.
  99. if p.lobby == "" and Time.get_ticks_msec() - p.time > TIMEOUT:
  100. p.ws.close()
  101. p.ws.poll()
  102. while p.is_ws_open() and p.ws.get_available_packet_count():
  103. if not _parse_msg(p):
  104. print("Parse message failed from peer %d" % p.id)
  105. to_remove.push_back(p.id)
  106. p.ws.close()
  107. break
  108. var state = p.ws.get_ready_state()
  109. if state == WebSocketPeer.STATE_CLOSED:
  110. print("Peer %d disconnected from lobby: '%s'" % [p.id, p.lobby])
  111. # Remove from lobby (and lobby itself if host).
  112. if lobbies.has(p.lobby) and lobbies[p.lobby].leave(p):
  113. print("Deleted lobby %s" % p.lobby)
  114. lobbies.erase(p.lobby)
  115. # Remove from peers
  116. to_remove.push_back(p.id)
  117. # Lobby seal.
  118. for k in lobbies:
  119. if not lobbies[k].sealed:
  120. continue
  121. if lobbies[k].time + SEAL_TIME < Time.get_ticks_msec():
  122. # Close lobby.
  123. for p in lobbies[k].peers:
  124. p.ws.close()
  125. to_remove.push_back(p.id)
  126. # Remove stale peers
  127. for id in to_remove:
  128. peers.erase(id)
  129. func _join_lobby(peer: Peer, lobby: String, mesh: bool) -> bool:
  130. if lobby == "":
  131. for _i in range(0, 32):
  132. lobby += char(_alfnum[rand.randi_range(0, ALFNUM.length()-1)])
  133. lobbies[lobby] = Lobby.new(peer.id, mesh)
  134. elif not lobbies.has(lobby):
  135. return false
  136. lobbies[lobby].join(peer)
  137. peer.lobby = lobby
  138. # Notify peer of its lobby
  139. peer.send(Message.JOIN, 0, lobby)
  140. print("Peer %d joined lobby: '%s'" % [peer.id, lobby])
  141. return true
  142. func _parse_msg(peer: Peer) -> bool:
  143. var pkt_str: String = peer.ws.get_packet().get_string_from_utf8()
  144. var parsed = JSON.parse_string(pkt_str)
  145. if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("type") or not parsed.has("id") or \
  146. typeof(parsed.get("data")) != TYPE_STRING:
  147. return false
  148. if not str(parsed.type).is_valid_int() or not str(parsed.id).is_valid_int():
  149. return false
  150. var msg := {
  151. "type": str(parsed.type).to_int(),
  152. "id": str(parsed.id).to_int(),
  153. "data": parsed.data
  154. }
  155. if msg.type == Message.JOIN:
  156. if peer.lobby: # Peer must not have joined a lobby already!
  157. return false
  158. return _join_lobby(peer, msg.data, msg.id == 0)
  159. if not lobbies.has(peer.lobby): # Lobby not found?
  160. return false
  161. var lobby = lobbies[peer.lobby]
  162. if msg.type == Message.SEAL:
  163. # Client is sealing the room
  164. return lobby.seal(peer.id)
  165. var dest_id: int = msg.id
  166. if dest_id == MultiplayerPeer.TARGET_PEER_SERVER:
  167. dest_id = lobby.host
  168. if not peers.has(dest_id): # Destination ID not connected
  169. return false
  170. if peers[dest_id].lobby != peer.lobby: # Trying to contact someone not in same lobby
  171. return false
  172. if msg.type in [Message.OFFER, Message.ANSWER, Message.CANDIDATE]:
  173. var source = MultiplayerPeer.TARGET_PEER_SERVER if peer.id == lobby.host else peer.id
  174. peers[dest_id].send(msg.type, source, msg.data)
  175. return true
  176. return false # Unknown message