Skip to content

Commit

Permalink
feat: Implemented lobby system (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
freehuntx committed Jan 4, 2024
1 parent 7ecef5c commit 46873d5
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 77 deletions.
1 change: 1 addition & 0 deletions addons/matcha/Constants.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const VERSION = "0.3.0"
155 changes: 155 additions & 0 deletions addons/matcha/MatchaLobby.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
class_name MatchaLobby extends RefCounted

# Signals
signal joined_room(room: MatchaRoom)
signal left_room(room: MatchaRoom)
signal room_created(room: Dictionary)
signal room_updated(room: Dictionary)
signal room_closed(room: Dictionary)

# Members
var _lobby: MatchaRoom
var _rooms := {}
var _current_room: MatchaRoom

# Getters
var room_list:
get: return _rooms.values()
var current_room:
get: return _current_room

# Constructor
func _init(options:={}):
if not "identifier" in options: options.identifier = "com.matcha.lobby"
_lobby = MatchaRoom.create_mesh_room(options)
_lobby.peer_joined.connect(self._on_peer_joined)
_lobby.peer_left.connect(self._on_peer_left)

# Public methods
func join_room(room_id: String) -> Error:
var room = _find_room_by_id(room_id)
if room == null:
push_error("Room not found")
return Error.ERR_DOES_NOT_EXIST
if _current_room != null:
push_error("Already in a room")
return Error.ERR_ALREADY_IN_USE

_current_room = MatchaRoom.create_client_room(room_id)
joined_room.emit(_current_room)

return Error.OK

func create_room(room_meta:={}) -> Error:
if _current_room != null:
push_error("Already in a room")
return Error.ERR_ALREADY_IN_USE
if _lobby.peer_id in _rooms:
push_error("Already opened a room. Close it first!")
return Error.ERR_ALREADY_IN_USE

_current_room = MatchaRoom.create_server_room()
var room = {
"id": _current_room.id,
"meta": room_meta
}
_rooms[_lobby.peer_id] = room

_lobby.send_event("create_room", [room])
room_created.emit(room)
joined_room.emit(_current_room)

return Error.OK

func update_room(room_meta:={}) -> Error:
if not _lobby.peer_id in _rooms:
push_error("No room opened.")
return Error.ERR_DOES_NOT_EXIST

var room = _rooms[_lobby.peer_id]
room.meta = room_meta
_lobby.send_event("update_room", [room.meta])
room_updated.emit(room)

return Error.OK

func leave_room() -> Error:
if _current_room == null:
push_error("Not in a room.")
return Error.ERR_DOES_NOT_EXIST

if _lobby.peer_id in _rooms:
var room = _rooms[_lobby.peer_id]
_rooms.erase(_lobby.peer_id)
_lobby.send_event("close_room")
room_closed.emit(room)

var previous_room = _current_room
_current_room = null
left_room.emit(previous_room)

return Error.OK

# Private methods
func _find_room_by_id(room_id: String):
for room: Dictionary in _rooms.values():
if room.id == room_id: return room
return null

func _verify_room(room: Dictionary) -> bool:
if typeof(room) != TYPE_DICTIONARY: return false
if not "id" in room or not "meta" in room: return false
if typeof(room.id) != TYPE_STRING or not _verify_room_meta(room.meta): return false
return true

func _verify_room_meta(room_meta: Dictionary) -> bool:
return typeof(room_meta) == TYPE_DICTIONARY

func _remove_room(room_id: String) -> bool:
for peer_id: String in _rooms.keys():
var room: Dictionary = _rooms[peer_id]
if room.id != room_id: continue

_rooms.erase(peer_id)
if peer_id == _lobby.peer_id:
_lobby.send_event("close_room")
room_closed.emit(room)
return true

return false

# Peer callbacks
func _on_peer_joined(id: int, peer: MatchaPeer) -> void:
peer.on_event("create_room", self._on_peer_created_room.bind(peer))
peer.on_event("update_room", self._on_peer_updated_room.bind(peer))
peer.on_event("close_room", self._on_peer_closed_room.bind(peer))

if _lobby.peer_id in _rooms:
peer.send_event("create_room", [_rooms[_lobby.peer_id]])

func _on_peer_left(_rpc_id: int, peer: MatchaPeer) -> void:
if peer.id in _rooms:
var room = _rooms[peer.id]
_rooms.erase(peer.id)
room_closed.emit(room)

# Peer events
func _on_peer_created_room(room, peer: MatchaPeer) -> void:
if peer.id in _rooms: return
if not _verify_room(room): return

_rooms[peer.id] = room
room_created.emit(room)

func _on_peer_updated_room(room_meta: Dictionary, peer: MatchaPeer) -> void:
if not peer.id in _rooms: return
if not _verify_room_meta(room_meta): return

_rooms[peer.id].meta = room_meta
room_updated.emit(_rooms[peer.id])

func _on_peer_closed_room(peer: MatchaPeer) -> void:
if peer.id in _rooms:
var room = _rooms[peer.id]
_rooms.erase(peer.id)
room_closed.emit(room)
18 changes: 7 additions & 11 deletions addons/matcha/MatchaPeer.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ signal sdp_created(sdp: String)

# Members
var _announced := false
var _peer_id: String
var _id: String
var _offer_id: String
var _state := State.NEW
var _answered := false
Expand All @@ -29,8 +29,6 @@ var is_connected:
get: return _state == State.CONNECTED
var type:
get: return _type
var offer_id:
get: return _offer_id
var gathered:
get: return _state > State.GATHERING
var announced:
Expand All @@ -39,8 +37,12 @@ var answered:
get: return _answered
var local_sdp:
get: return _local_sdp
var peer_id:
get: return _peer_id
var id:
get: return _id
set(value): _id = value
var offer_id:
get: return _offer_id
set(value): _offer_id = value

# Static methods
static func create_offer_peer(offer_id := Utils.gen_id()) -> MatchaPeer:
Expand Down Expand Up @@ -92,12 +94,6 @@ func start() -> Error:
Engine.get_main_loop().process_frame.connect(self.__poll) # Start the poll loop
return Error.OK

func set_peer_id(new_peer_id: String) -> void:
_peer_id = new_peer_id

func set_offer_id(new_offer_id: String) -> void:
_offer_id = new_offer_id

func set_answer(remote_sdp: String) -> Error:
if _type != "offer":
push_error("The peer is not an offer")
Expand Down
62 changes: 32 additions & 30 deletions addons/matcha/MatchaRoom.gd
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ signal peer_left(rpc_id: int, peer: MatchaPeer) # Emitted when a peer left the r
var _state := State.NEW # Internal state
var _tracker_urls := [] # A list of tracker urls
var _tracker_clients: Array[TrackerClient] = [] # A list of tracker clients we use to share/get offers/answers
var _room_id: String # An unique identifier
var _id: String # An unique id for this room
var _peer_id := Utils.gen_id()
var _type: String
var _offer_timeout := 30
var _pool_size := 10
var _offer_timeout := 120
var _pool_size := 40
var _connected_peers = {}

# Getters
Expand All @@ -30,9 +30,11 @@ var peer_id:
get: return _peer_id
var type:
get: return _type
var room_id:
get: return _room_id
var _peers:
var id:
get: return _id
var connected_peers:
get: return _connected_peers.values()
var peers:
get: return get_peers().values().map(func(v): return v.connection)

# Static methods
Expand All @@ -54,14 +56,14 @@ func _init(options:={}):
if not "pool_size" in options: options.pool_size = _pool_size
if not "offer_timeout" in options: options.offer_timeout = _offer_timeout
if not "identifier" in options: options.identifier = "com.matcha.default"
if not "tracker_urls" in options: options.tracker_urls = ["wss://tracker.webtorrent.dev"]
if not "tracker_urls" in options: options.tracker_urls = ["wss://tracker.openwebtorrent.com", "wss://tracker.files.fm:7073/announce"]
if not "room_id" in options: options.room_id = options.identifier.sha1_text().substr(0, 20)
if not "type" in options: options.type = "mesh"
if not "autostart" in options: options.autostart = true
_tracker_urls = options.tracker_urls
_pool_size = options.pool_size
_offer_timeout = options.offer_timeout
_room_id = options.room_id
_id = options.room_id
_type = options.type

peer_connected.connect(self._on_peer_connected)
Expand Down Expand Up @@ -89,7 +91,7 @@ func start() -> Error:
push_error("Creating client failed")
return err
elif _type == "server":
_room_id = _peer_id # Our room_id should be our peer_id to identify ourself as the server
_id = _peer_id # Our room_id should be our peer_id to identify ourself as the server
var err := create_server()
if err != OK:
push_error("Creating server failed")
Expand All @@ -111,7 +113,7 @@ func start() -> Error:

func find_peers(filter:={}) -> Array[MatchaPeer]:
var result: Array[MatchaPeer] = []
for peer in _peers:
for peer in peers:
var matched := true
for key in filter:
if not key in peer or peer[key] != filter[key]:
Expand All @@ -129,9 +131,9 @@ func find_peer(filter:={}, allow_multiple_results:=false) -> MatchaPeer:

# Broadcast an event to everybody in this room or just specific peers. (List of peer_id)
func send_event(event_name: String, event_args:=[], target_peer_ids:=[]):
for peer: MatchaPeer in _peers:
for peer: MatchaPeer in peers:
if not peer.is_connected: continue
if target_peer_ids.size() > 0 and not target_peer_ids.has(peer.peer_id): continue
if target_peer_ids.size() > 0 and not target_peer_ids.has(peer.id): continue
peer.send_event(event_name, event_args)

# Private methods
Expand Down Expand Up @@ -189,19 +191,19 @@ func _handle_offers_announcment():
offer_peer.mark_as_announced()

for tracker_client in _tracker_clients: # Announce the offers via every tracker
tracker_client.announce(_room_id, announce_offers)
tracker_client.announce(_id, announce_offers)

func _send_answer_sdp(answer_sdp: String, peer: MatchaPeer, tracker_client: TrackerClient):
tracker_client.answer(_room_id, peer.peer_id, peer.offer_id, answer_sdp)
tracker_client.answer(_id, peer.id, peer.offer_id, answer_sdp)

func _on_got_offer(offer: TrackerClient.Response, tracker_client: TrackerClient) -> void:
if offer.info_hash != _room_id: return
if find_peer({ "peer_id": offer.peer_id }) != null: return # Ignore if the peer is already known
if _type == "client" and offer.peer_id != room_id: return # Ignore offers from others than host (in client mode)
if offer.info_hash != _id: return
if find_peer({ "id": offer.peer_id }) != null: return # Ignore if the peer is already known
if _type == "client" and offer.peer_id != _id: return # Ignore offers from others than host (in client mode)

var answer_peer := MatchaPeer.create_answer_peer(offer.offer_id, offer.sdp)
var answer_rpc_id := 1 if _type == "client" else generate_unique_id()
answer_peer.set_peer_id(offer.peer_id)
answer_peer.id = offer.peer_id

answer_peer.sdp_created.connect(self._send_answer_sdp.bind(answer_peer, tracker_client))
add_peer(answer_peer, answer_rpc_id)
Expand All @@ -210,30 +212,30 @@ func _on_got_offer(offer: TrackerClient.Response, tracker_client: TrackerClient)
remove_peer(answer_rpc_id)

func _on_got_answer(answer: TrackerClient.Response, tracker_client: TrackerClient) -> void:
if answer.info_hash != _room_id: return
if _type == "client" and answer.peer_id != room_id: return # As client we just accept answers from the host
if answer.info_hash != _id: return
if _type == "client" and answer.peer_id != _id: return # As client we just accept answers from the host

var offer_peer: MatchaPeer
if _type == "client":
if has_peer(1):
offer_peer = get_peer(1).connection
offer_peer.set_offer_id(answer.offer_id) # Fix the offer_id since we gave the server alot of offers to choose from
offer_peer.offer_id = answer.offer_id # Fix the offer_id since we gave the server alot of offers to choose from
else:
offer_peer = find_peer({ "offer_id": answer.offer_id })
if offer_peer == null: return # Ignore if we dont know that offer

offer_peer.set_peer_id(answer.peer_id)
offer_peer.id = answer.peer_id
offer_peer.set_answer(answer.sdp)

func _on_failure(reason: String, tracker_client: TrackerClient) -> void:
print("Tracker failure: ", reason, ", Tracker: ", tracker_client.tracker_url)

func _on_peer_connected(id: int):
var peer: MatchaPeer = get_peer(id).connection
_connected_peers[id] = peer
peer_joined.emit(id, peer)
func _on_peer_connected(rpc_id: int):
var peer: MatchaPeer = get_peer(rpc_id).connection
_connected_peers[rpc_id] = peer
peer_joined.emit(rpc_id, peer)

func _on_peer_disconnected(id: int):
var peer: MatchaPeer = _connected_peers[id]
_connected_peers.erase(id)
peer_left.emit(id, peer)
func _on_peer_disconnected(rpc_id: int):
var peer: MatchaPeer = _connected_peers[rpc_id]
_connected_peers.erase(rpc_id)
peer_left.emit(rpc_id, peer)
5 changes: 4 additions & 1 deletion addons/matcha/lib/WebSocketClient.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# TODO: DOCUMENT, DOCUMENT, DOCUMENT!

extends RefCounted
const Constants := preload("../Constants.gd")

# Signals
signal disconnected
Expand Down Expand Up @@ -32,7 +33,8 @@ func _init(url: String, options:={}) -> void:
_url = url
_options = options

_user_agent = "Matcha/0.0.0 (%s; %s; %s) Godot/%s" % [
_user_agent = "Matcha/%s (%s; %s; %s) Godot/%s" % [
Constants.VERSION,
OS.get_name(),
OS.get_version(),
Engine.get_architecture_name(),
Expand Down Expand Up @@ -88,6 +90,7 @@ func close(was_error=false) -> void:
_reconnect_try_counter += 1

if _reconnect_try_counter > _options.reconnect_tries:
push_error("Websocket retries exceeded")
return

_state = State.RECONNECTING
Expand Down
Loading

0 comments on commit 46873d5

Please sign in to comment.