diff --git a/addons/matcha/MatchaPeer.gd b/addons/matcha/MatchaPeer.gd index eccd54a..88aac4a 100644 --- a/addons/matcha/MatchaPeer.gd +++ b/addons/matcha/MatchaPeer.gd @@ -1,28 +1,40 @@ extends RefCounted +const Utils := preload("./lib/Utils.gd") # Signals signal connected signal connecting_failed signal disconnected +signal closed signal sdp_created(sdp: String) # Members +var announced := false +var peer_id: String +var offer_id: String var _peer: WebRTCPeerConnection var _gathered := false var _connecting := false var _connected := false +var _answered := false var _type: String var _local_sdp: String var _remote_sdp: String var _rtc_peer: WebRTCMultiplayerPeer var _rtc_peer_id: int +var type: + get: return _type var peer: get: return _peer var gathered: get: return _gathered +var answered: + get: return _answered var local_sdp: get: return _local_sdp +var rtc_peer_id: + get: return _rtc_peer_id # Constructor func _init(rtc_peer: WebRTCMultiplayerPeer): @@ -33,40 +45,46 @@ func _init(rtc_peer: WebRTCMultiplayerPeer): _peer.session_description_created.connect(self._on_sdp_created) _peer.ice_candidate_created.connect(self._on_icecandidate_created) _rtc_peer.add_peer(_peer, _rtc_peer_id) - _poll() + Engine.get_main_loop().process_frame.connect(self._poll) # Public methods func close(): - if _rtc_peer.has_peer(_rtc_peer_id): - _rtc_peer.remove_peer(_rtc_peer_id) + if _peer == null: return + _peer.close() + _peer = null + closed.emit() func set_answer(remote_sdp: String): assert(_type == "offer", "The peer is not an offer") - assert(_remote_sdp == "", "The offer was already answered") + assert(not _answered, "The offer was already answered") + _answered = true _remote_sdp = remote_sdp - _peer.set_remote_description("answer", remote_sdp) + if _peer != null: + _peer.set_remote_description("answer", remote_sdp) func create_offer() -> Error: assert(_type == "", "The peer is already in use") _type = "offer" + offer_id = Utils.gen_id() var err := _peer.create_offer() if err != OK: close() return err func create_answer(remote_sdp: String): + assert(_peer != null, "The peer null") assert(_type == "", "The peer is already in use") _type = "answer" _peer.set_remote_description("offer", remote_sdp) # Private methods func _poll(): - _peer.poll() + if _peer == null: return + if not _gathered: if _peer.get_gathering_state() == WebRTCPeerConnection.GATHERING_STATE_COMPLETE: _gathered = true _connecting = true - #_peer.set_local_description(_type, _local_sdp) sdp_created.emit(_local_sdp) if _connecting: var state := _peer.get_connection_state() @@ -87,8 +105,6 @@ func _poll(): close() return - Engine.get_main_loop().create_timer(0.1).timeout.connect(self._poll) - func _on_sdp_created(type: String, sdp: String): _local_sdp = sdp _peer.set_local_description(_type, sdp) diff --git a/addons/matcha/MatchaRoom.gd b/addons/matcha/MatchaRoom.gd index 6e8d4ec..398e594 100644 --- a/addons/matcha/MatchaRoom.gd +++ b/addons/matcha/MatchaRoom.gd @@ -3,7 +3,6 @@ const Utils := preload("res://addons/matcha/lib/Utils.gd") const TrackerClient := preload("./tracker/TrackerClient.gd") const MatchaPeer := preload("./MatchaPeer.gd") const POOL_SIZE := 10 -const POLL_INTERVAL := 0.1 const OFFER_TIMEOUT := 30 # Members @@ -12,8 +11,7 @@ var _info_hash: String var _peer_id := Utils.gen_id() var _rtc_peer := WebRTCMultiplayerPeer.new() var _rtc_peer_id: int -var _peers := {} -var _offers := {} +var _peers: Array[MatchaPeer] = [] # Getters var rtc_peer: @@ -36,79 +34,77 @@ func _init(options:={}) -> void: tracker_client.failure.connect(self._on_failure.bind(tracker_client)) _tracker_clients.append(tracker_client) - _poll() # Starts the poll loop + Engine.get_main_loop().process_frame.connect(self._poll) # Private methods func _poll(): _rtc_peer.poll() - _cleanup_offers() _create_offers() _handle_offers_announcment() - Engine.get_main_loop().create_timer(POLL_INTERVAL).timeout.connect(self._poll) # Run poll in x seconds again +func _remove_offer(offer_id: String) -> void: + for peer in _peers: + if peer.offer_id != offer_id: continue + peer.close() + _peers.erase(peer) -func _cleanup_offers() -> void: - var current_time := Time.get_unix_time_from_system() - for offer in _offers.values(): - if current_time - offer.created_at > OFFER_TIMEOUT: - offer.peer.close() - _offers.erase(offer.id) +func _remove_peer_id(peer_id: String) -> void: + for peer in _peers: + if peer.peer_id != peer_id: continue + peer.close() + _peers.erase(peer) func _create_offer() -> void: - var offer = { - "id": Utils.gen_id(), - "peer": MatchaPeer.new(_rtc_peer), - "announced": false, - "created_at": Time.get_unix_time_from_system() - } - if offer.peer.create_offer() != OK: - return - _offers[offer.id] = offer + var offer_peer := MatchaPeer.new(_rtc_peer) + if offer_peer.create_offer() != OK: return + offer_peer.closed.connect(self._remove_offer.bind(offer_peer.offer_id)) + _peers.append(offer_peer) + + Engine.get_main_loop().create_timer(OFFER_TIMEOUT).timeout.connect(self._remove_offer.bind(offer_peer.offer_id)) func _create_offers() -> void: - if _offers.size() > 0: return + var unanswered_offers := _peers.filter(func(p): return p.type == "offer" and not p.answered) + if unanswered_offers.size() > 0: return # There are ongoing offers. Dont refresh the pool. for i in range(POOL_SIZE): _create_offer() -func _cleanup_peer_id(peer_id: String): - if peer_id in _peers: - _peers.erase(peer_id) - func _handle_offers_announcment(): - if _offers.size() == 0: return # No announcements needed if we have no offers + var unannounced_offers := _peers.filter(func(p): return p.type == "offer" and not p.announced) + if unannounced_offers.size() == 0: return # There are no offers to announce var announce_offers := [] # The array we need for the tracker offer announcements - for offer in _offers.values(): - if not offer.peer.gathered: return # If we have ungathered offers we are not ready yet to announce. - if offer.announced: return # If we have announced offers something is wrong. We stop the announcement then. - announce_offers.append({ "offer_id": offer.id, "offer": { "type": "offer", "sdp": offer.peer.local_sdp } }) - for offer in _offers.values(): - offer.announced = true # Mark the offer as announced + for offer_peer in unannounced_offers: + if not offer_peer.gathered: return # If we have ungathered offers we are not ready yet to announce. + announce_offers.append({ "offer_id": offer_peer.offer_id, "offer": { "type": "offer", "sdp": offer_peer.local_sdp } }) + for offer_peer in unannounced_offers: + offer_peer.announced = true # Mark the offer as announced for tracker_client in _tracker_clients: # Announce the offers via every tracker tracker_client.announce(_info_hash, announce_offers) func _on_offer(offer: Dictionary, tracker_client: TrackerClient) -> void: - if offer.peer_id in _peers: return # Ignore the offer if we know the peer already + for peer in _peers: + if peer.peer_id == offer.peer_id: return var peer := MatchaPeer.new(_rtc_peer) - peer.disconnected.connect(self._cleanup_peer_id.bind(offer.peer_id)) - peer.connecting_failed.connect(self._cleanup_peer_id.bind(offer.peer_id)) + peer.peer_id = offer.peer_id + peer.offer_id = offer.offer_id peer.sdp_created.connect(self._send_answer_sdp.bind(offer.peer_id, offer.offer_id, tracker_client)) + peer.closed.connect(self._remove_offer.bind(offer.offer_id)) peer.create_answer(offer.sdp) - _peers[offer.peer_id] = peer + _peers.append(peer) func _on_answer(answer: Dictionary, tracker_client: TrackerClient) -> void: - if not answer.offer_id in _offers: return - var offer = _offers[answer.offer_id] - _offers.erase(answer.offer_id) - - if answer.peer_id in _peers: return - - offer.peer.disconnected.connect(self._cleanup_peer_id.bind(answer.peer_id)) - offer.peer.connecting_failed.connect(self._cleanup_peer_id.bind(answer.peer_id)) - _peers[answer.peer_id] = offer.peer - offer.peer.set_answer(answer.sdp) + var offer: MatchaPeer + for peer in _peers: + if peer.peer_id == answer.peer_id: return + if peer.type != "offer" or peer.offer_id != answer.offer_id or peer.answered: continue + offer = peer + break + + if offer == null: return + offer.peer_id = answer.peer_id + offer.set_answer(answer.sdp) func _send_answer_sdp(answer_sdp: String, peer_id: String, offer_id: String, tracker_client: TrackerClient): tracker_client.send_answer(_info_hash, { diff --git a/examples/lobby/lobby.gd b/examples/lobby/lobby.gd new file mode 100644 index 0000000..3eb6c96 --- /dev/null +++ b/examples/lobby/lobby.gd @@ -0,0 +1,2 @@ +extends Node2D + diff --git a/examples/lobby/lobby.tscn b/examples/lobby/lobby.tscn new file mode 100644 index 0000000..736ff0e --- /dev/null +++ b/examples/lobby/lobby.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://d0yexpwulxnyb"] + +[ext_resource type="Script" path="res://examples/lobby/lobby.gd" id="1_vobbu"] + +[node name="Lobby" type="Node2D"] +script = ExtResource("1_vobbu") diff --git a/export_presets.cfg b/export_presets.cfg index f23dc0f..b11befa 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -23,7 +23,7 @@ vram_texture_compression/for_desktop=true vram_texture_compression/for_mobile=true html/export_icon=true html/custom_html_shell="" -html/head_include="" +html/head_include="" html/canvas_resize_policy=2 html/focus_canvas_on_start=true html/experimental_virtual_keyboard=false diff --git a/root.gd b/root.gd index fdb9e3b..452caf8 100644 --- a/root.gd +++ b/root.gd @@ -3,3 +3,7 @@ extends Node2D func _on_bobble_btn_pressed(): get_parent().add_child(load("res://examples/bobble/bobble.tscn").instantiate()) get_parent().remove_child(self) + +func _on_lobby_btn_pressed(): + get_parent().add_child(load("res://examples/lobby/lobby.tscn").instantiate()) + get_parent().remove_child(self) diff --git a/root.tscn b/root.tscn index e86e62b..251efa5 100644 --- a/root.tscn +++ b/root.tscn @@ -12,4 +12,13 @@ offset_right = 612.0 offset_bottom = 239.0 text = "Start bobble" +[node name="lobby_btn" type="Button" parent="."] +visible = false +offset_left = 515.0 +offset_top = 267.0 +offset_right = 608.0 +offset_bottom = 298.0 +text = "Start lobby" + [connection signal="pressed" from="bobble_btn" to="." method="_on_bobble_btn_pressed"] +[connection signal="pressed" from="lobby_btn" to="." method="_on_lobby_btn_pressed"]