I dunno?
This commit is contained in:
parent
aad8b99dc1
commit
a1636ff2f5
2
game.gd
2
game.gd
|
@ -17,4 +17,4 @@ func _ready():
|
||||||
|
|
||||||
|
|
||||||
func _on_Button_pressed():
|
func _on_Button_pressed():
|
||||||
Global.leave_game()
|
Global.main_menu()
|
||||||
|
|
37
global.gd
37
global.gd
|
@ -4,35 +4,20 @@ const MultiplayerClient = preload("multiplayer_client.gd")
|
||||||
|
|
||||||
onready var client = MultiplayerClient.new()
|
onready var client = MultiplayerClient.new()
|
||||||
|
|
||||||
const MULTIPLAYER_URL = "wss://webrtc-signaller.deno.dev/"
|
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
client.connect("lobby_joined", self, "_lobby_joined")
|
|
||||||
client.connect("connected", self, "_connected")
|
|
||||||
add_child(client)
|
add_child(client)
|
||||||
|
|
||||||
|
func goto_scene(scene_resource_name):
|
||||||
|
var _result = get_tree().change_scene("res://%s.tscn" % scene_resource_name)
|
||||||
|
|
||||||
|
func main_menu():
|
||||||
|
goto_scene("main")
|
||||||
|
|
||||||
func start_singleplayer_game():
|
func start_singleplayer_game():
|
||||||
print("Starting singleplayer game...")
|
goto_scene("game")
|
||||||
get_tree().change_scene("res://game.tscn")
|
|
||||||
|
|
||||||
func join_lobby():
|
func multiplayer():
|
||||||
get_tree().change_scene("res://join_lobby.tscn")
|
goto_scene("multiplayer")
|
||||||
|
|
||||||
func create_lobby():
|
func quit():
|
||||||
join_lobby_with_code("")
|
get_tree().quit()
|
||||||
|
|
||||||
func _lobby_joined(_lobby):
|
|
||||||
print("Joined!")
|
|
||||||
goto_lobby()
|
|
||||||
|
|
||||||
func goto_lobby():
|
|
||||||
print("Going to lobby...")
|
|
||||||
get_tree().change_scene("res://lobby.tscn")
|
|
||||||
|
|
||||||
func leave_game():
|
|
||||||
client.stop()
|
|
||||||
print("Leaving game...")
|
|
||||||
get_tree().change_scene("res://main.tscn")
|
|
||||||
|
|
||||||
func join_lobby_with_code(code):
|
|
||||||
client.start(MULTIPLAYER_URL, code)
|
|
||||||
|
|
4
main.gd
4
main.gd
|
@ -5,8 +5,8 @@ func _on_Singleplayer_pressed():
|
||||||
|
|
||||||
|
|
||||||
func _on_CreateLobbyButton_pressed():
|
func _on_CreateLobbyButton_pressed():
|
||||||
Global.create_lobby()
|
Global.multiplayer()
|
||||||
|
|
||||||
|
|
||||||
func _on_JoinLobbyButton_pressed():
|
func _on_JoinLobbyButton_pressed():
|
||||||
Global.join_lobby()
|
Global.quit()
|
||||||
|
|
|
@ -25,7 +25,7 @@ __meta__ = {
|
||||||
[node name="Singleplayer" type="Button" parent="VBoxContainer"]
|
[node name="Singleplayer" type="Button" parent="VBoxContainer"]
|
||||||
margin_right = 995.0
|
margin_right = 995.0
|
||||||
margin_bottom = 20.0
|
margin_bottom = 20.0
|
||||||
text = "Start Singleplayer"
|
text = "Start Singleplayer Game"
|
||||||
__meta__ = {
|
__meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,13 @@ __meta__ = {
|
||||||
margin_top = 70.0
|
margin_top = 70.0
|
||||||
margin_right = 995.0
|
margin_right = 995.0
|
||||||
margin_bottom = 90.0
|
margin_bottom = 90.0
|
||||||
text = "Create Lobby"
|
text = "Multiplayer"
|
||||||
|
|
||||||
[node name="JoinLobbyButton" type="Button" parent="VBoxContainer"]
|
[node name="JoinLobbyButton" type="Button" parent="VBoxContainer"]
|
||||||
margin_top = 140.0
|
margin_top = 140.0
|
||||||
margin_right = 995.0
|
margin_right = 995.0
|
||||||
margin_bottom = 160.0
|
margin_bottom = 160.0
|
||||||
text = "Join Lobby"
|
text = "Quit"
|
||||||
__meta__ = {
|
__meta__ = {
|
||||||
"_edit_use_anchors_": false
|
"_edit_use_anchors_": false
|
||||||
}
|
}
|
||||||
|
|
9
multiplayer.gd
Normal file
9
multiplayer.gd
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
onready var is_loaded = false
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
Global.client.connect_to_signaller()
|
||||||
|
|
||||||
|
func _on_back_pressed():
|
||||||
|
pass
|
40
multiplayer.tscn
Normal file
40
multiplayer.tscn
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
[gd_scene load_steps=2 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://multiplayer.gd" type="Script" id=1]
|
||||||
|
|
||||||
|
[node name="Control" type="Control"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="back" type="Button" parent="."]
|
||||||
|
margin_left = 27.0
|
||||||
|
margin_top = 357.0
|
||||||
|
margin_right = 286.0
|
||||||
|
margin_bottom = 432.0
|
||||||
|
text = "Back"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="create_lobby" type="Button" parent="."]
|
||||||
|
margin_right = 259.0
|
||||||
|
margin_bottom = 75.0
|
||||||
|
text = "Create Lobby"
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="lobbies" type="Label" parent="."]
|
||||||
|
margin_left = 25.0
|
||||||
|
margin_top = 91.0
|
||||||
|
margin_right = 591.0
|
||||||
|
margin_bottom = 325.0
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[connection signal="pressed" from="back" to="." method="_on_back_pressed"]
|
|
@ -1,87 +1,90 @@
|
||||||
extends "ws_webrtc_client.gd"
|
extends Node
|
||||||
|
|
||||||
var rtc_mp: WebRTCMultiplayer = WebRTCMultiplayer.new()
|
"""
|
||||||
var sealed = false
|
This module sets up WebRTC peer connections.
|
||||||
|
"""
|
||||||
|
|
||||||
func _init():
|
var multiplayer_url = "ws://localhost:8888"
|
||||||
connect("connected", self, "connected")
|
var webrtc_ice_servers = [
|
||||||
connect("disconnected", self, "disconnected")
|
{ "urls": ["stun:stun.l.google.com:19302"] }
|
||||||
|
]
|
||||||
|
|
||||||
connect("offer_received", self, "offer_received")
|
const SignallerClient = preload("signaller_client.gd")
|
||||||
connect("answer_received", self, "answer_received")
|
|
||||||
connect("candidate_received", self, "candidate_received")
|
|
||||||
|
|
||||||
connect("lobby_joined", self, "lobby_joined")
|
onready var mp = WebRTCMultiplayer.new()
|
||||||
connect("lobby_sealed", self, "lobby_sealed")
|
onready var sc = SignallerClient.new()
|
||||||
connect("peer_connected", self, "peer_connected")
|
|
||||||
connect("peer_disconnected", self, "peer_disconnected")
|
|
||||||
|
|
||||||
func start(url, lobby = ""):
|
func _ready():
|
||||||
stop()
|
# connect("connected", self, "connected")
|
||||||
sealed = false
|
# connect("disconnected", self, "disconnected")
|
||||||
self.lobby = lobby
|
|
||||||
connect_to_url(url)
|
|
||||||
|
|
||||||
func stop():
|
# connect("offer_received", self, "offer_received")
|
||||||
rtc_mp.close()
|
# connect("answer_received", self, "answer_received")
|
||||||
|
# connect("candidate_received", self, "candidate_received")
|
||||||
|
|
||||||
|
# connect("lobby_joined", self, "lobby_joined")
|
||||||
|
# connect("lobby_sealed", self, "lobby_sealed")
|
||||||
|
# connect("peer_connected", self, "peer_connected")
|
||||||
|
# connect("peer_disconnected", self, "peer_disconnected")
|
||||||
|
add_child(sc)
|
||||||
|
|
||||||
|
func close():
|
||||||
|
mp.close()
|
||||||
|
sc.close()
|
||||||
|
|
||||||
|
func connect_to_signaller():
|
||||||
close()
|
close()
|
||||||
|
sc.connect_to_websocket_signaller(multiplayer_url)
|
||||||
|
|
||||||
func _create_peer(id):
|
func _create_peer(id):
|
||||||
var peer: WebRTCPeerConnection = WebRTCPeerConnection.new()
|
var peer: WebRTCPeerConnection = WebRTCPeerConnection.new()
|
||||||
peer.initialize({
|
peer.initialize({"iceServers": webrtc_ice_servers})
|
||||||
"iceServers": [ { "urls": ["stun:stun.l.google.com:19302"] } ]
|
|
||||||
})
|
|
||||||
peer.connect("session_description_created", self, "_offer_created", [id])
|
peer.connect("session_description_created", self, "_offer_created", [id])
|
||||||
peer.connect("ice_candidate_created", self, "_new_ice_candidate", [id])
|
peer.connect("ice_candidate_created", self, "_new_ice_candidate", [id])
|
||||||
rtc_mp.add_peer(peer, id)
|
mp.add_peer(peer, id)
|
||||||
if id > rtc_mp.get_unique_id():
|
if id > mp.get_unique_id():
|
||||||
peer.create_offer()
|
# TODO: peer.create_offer()
|
||||||
|
pass
|
||||||
return peer
|
return peer
|
||||||
|
|
||||||
func _new_ice_candidate(mid_name, index_name, sdp_name, id):
|
func _new_ice_candidate(mid_name, index_name, sdp_name, id):
|
||||||
print("New ICE Candidate: ", mid_name, index_name, sdp_name, id)
|
print("New ICE Candidate: ", mid_name, index_name, sdp_name, id)
|
||||||
send_candidate(id, mid_name, index_name, sdp_name)
|
# TODO: send_candidate(id, mid_name, index_name, sdp_name)
|
||||||
|
|
||||||
func _offer_created(type, data, id):
|
func _offer_created(type, data, id):
|
||||||
if not rtc_mp.has_peer(id):
|
if not mp.has_peer(id):
|
||||||
return
|
return
|
||||||
print("created", type)
|
print("created", type)
|
||||||
rtc_mp.get_peer(id).connection.set_local_description(type, data)
|
mp.get_peer(id).connection.set_local_description(type, data)
|
||||||
if type == "offer": send_offer(id, data)
|
if type == "offer":
|
||||||
else: send_answer(id, data)
|
# TODO: send_offer(id, data)
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# TODO: send_answer(id, data)
|
||||||
|
pass
|
||||||
|
|
||||||
func connected(id):
|
func connected(id):
|
||||||
print("Connected %d" % id)
|
print("Connected %d" % id)
|
||||||
rtc_mp.initialize(id, true)
|
mp.initialize(id, true)
|
||||||
|
|
||||||
func lobby_joined(lobby):
|
|
||||||
self.lobby = lobby
|
|
||||||
|
|
||||||
func lobby_sealed():
|
|
||||||
sealed = true
|
|
||||||
|
|
||||||
func disconnected():
|
|
||||||
print("Disconnected: %d: %s" % [code, reason])
|
|
||||||
if not sealed:
|
|
||||||
stop() # Unexpected disconnect
|
|
||||||
|
|
||||||
func peer_connected(id):
|
func peer_connected(id):
|
||||||
print("Peer connected %d" % id)
|
print("Peer connected %d" % id)
|
||||||
_create_peer(id)
|
_create_peer(id)
|
||||||
|
|
||||||
func peer_disconnected(id):
|
func peer_disconnected(id):
|
||||||
if rtc_mp.has_peer(id): rtc_mp.remove_peer(id)
|
if mp.has_peer(id):
|
||||||
|
mp.remove_peer(id)
|
||||||
|
|
||||||
func offer_received(id, offer):
|
func offer_received(id, offer):
|
||||||
print("Got offer: %d" % id)
|
print("Got offer: %d" % id)
|
||||||
if rtc_mp.has_peer(id):
|
if mp.has_peer(id):
|
||||||
rtc_mp.get_peer(id).connection.set_remote_description("offer", offer)
|
mp.get_peer(id).connection.set_remote_description("offer", offer)
|
||||||
|
|
||||||
func answer_received(id, answer):
|
func answer_received(id, answer):
|
||||||
print("Got answer: %d" % id)
|
print("Got answer: %d" % id)
|
||||||
if rtc_mp.has_peer(id):
|
if mp.has_peer(id):
|
||||||
rtc_mp.get_peer(id).connection.set_remote_description("answer", answer)
|
mp.get_peer(id).connection.set_remote_description("answer", answer)
|
||||||
|
|
||||||
func candidate_received(id, mid, index, sdp):
|
func candidate_received(id, mid, index, sdp):
|
||||||
if rtc_mp.has_peer(id):
|
if mp.has_peer(id):
|
||||||
rtc_mp.get_peer(id).connection.add_ice_candidate(mid, index, sdp)
|
mp.get_peer(id).connection.add_ice_candidate(mid, index, sdp)
|
||||||
|
|
|
@ -4,4 +4,10 @@
|
||||||
|
|
||||||
# Signalling Server
|
# Signalling Server
|
||||||
|
|
||||||
deno run -A server.ts
|
The signaling server is written in TypeScript to run on Deno. It can run in the
|
||||||
|
cloud via Deno deploy.
|
||||||
|
|
||||||
|
PORT=8888 deno run --allow-env --allow-net server.ts
|
||||||
|
|
||||||
|
- https://dash.deno.com/projects/webrtc-signaller
|
||||||
|
- https://webrtc-signaller.deno.dev
|
||||||
|
|
243
server.ts
243
server.ts
|
@ -1,69 +1,66 @@
|
||||||
import { randomId, randomSecret } from "./gen.ts";
|
const PORT = parseInt(Deno.env.get("PORT") || "80");
|
||||||
|
|
||||||
const MAX_PEERS = 4096;
|
const randomInt = (low: number, high: number) =>
|
||||||
const MAX_LOBBIES = 1024;
|
Math.floor(Math.random() * (high - low + 1) + low);
|
||||||
const PORT = 80;
|
|
||||||
|
|
||||||
const NO_LOBBY_TIMEOUT = 10000;
|
const randomSecret = () => new Array(8).map(() => randomInt(0, 10)).join("");
|
||||||
const SEAL_CLOSE_TIMEOUT = 10000;
|
|
||||||
// const PING_INTERVAL = 10000;
|
|
||||||
|
|
||||||
const STR_NO_LOBBY = "Have not joined lobby yet";
|
interface Client {
|
||||||
const STR_HOST_DISCONNECTED = "Room host has disconnected";
|
socket: WebSocket;
|
||||||
const STR_ONLY_HOST_CAN_SEAL = "Only host can seal the lobby";
|
|
||||||
const STR_SEAL_COMPLETE = "Seal complete";
|
|
||||||
const STR_TOO_MANY_LOBBIES = "Too many lobbies open, disconnecting";
|
|
||||||
const STR_ALREADY_IN_LOBBY = "Already in a lobby";
|
|
||||||
const STR_LOBBY_DOES_NOT_EXIST = "Lobby does not exist";
|
|
||||||
const STR_LOBBY_IS_SEALED = "Lobby is sealed";
|
|
||||||
const STR_INVALID_FORMAT = "Invalid message format";
|
|
||||||
const STR_NEED_LOBBY = "Invalid message when not in a lobby";
|
|
||||||
const STR_SERVER_ERROR = "Server error, lobby not found";
|
|
||||||
const STR_INVALID_DEST = "Invalid destination";
|
|
||||||
const STR_INVALID_CMD = "Invalid command";
|
|
||||||
const STR_TOO_MANY_PEERS = "Too many peers connected";
|
|
||||||
|
|
||||||
// TODO: setup regular pings?
|
|
||||||
|
|
||||||
// state
|
|
||||||
const lobbies = new Map();
|
|
||||||
let peersCount = 0;
|
|
||||||
|
|
||||||
class ProtoError extends Error {
|
|
||||||
code: number;
|
|
||||||
|
|
||||||
constructor(code: number, message: string) {
|
|
||||||
super(message);
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Peer {
|
// TODO: version comparison
|
||||||
id: number;
|
|
||||||
ws: WebSocket;
|
|
||||||
lobby: string;
|
|
||||||
timeout: number;
|
|
||||||
|
|
||||||
constructor(id: number, ws: WebSocket) {
|
interface Lobby {
|
||||||
this.id = id;
|
name: string;
|
||||||
this.ws = ws;
|
clients: Client[];
|
||||||
this.lobby = "";
|
// TODO: private vs public lobbies?
|
||||||
|
}
|
||||||
|
|
||||||
// close connection after 10 sec if client has not joined a lobby
|
const lobbies = new Set<Lobby>();
|
||||||
this.timeout = setTimeout(() => {
|
const clients = new Set<Client>();
|
||||||
if (!this.lobby) ws.close(4000, STR_NO_LOBBY);
|
|
||||||
}, NO_LOBBY_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
joinLobby(lobbyName: string) {
|
console.log("Listening on port", PORT);
|
||||||
if (lobbyName === "") {
|
for await (const conn of Deno.listen({ port: PORT })) {
|
||||||
if (lobbies.size >= MAX_LOBBIES) {
|
(async () => {
|
||||||
throw new ProtoError(4000, STR_TOO_MANY_LOBBIES);
|
for await (const { respondWith, request } of Deno.serveHttp(conn)) {
|
||||||
}
|
const { socket, response } = Deno.upgradeWebSocket(request);
|
||||||
// Peer must not already be in a lobby
|
const client: Client = { socket };
|
||||||
if (this.lobby !== "") {
|
socket.onmessage = (ev) => {
|
||||||
throw new ProtoError(4000, STR_ALREADY_IN_LOBBY);
|
console.log("Client Message Received", ev);
|
||||||
|
};
|
||||||
|
socket.onopen = (ev) => {
|
||||||
|
console.log("New Client:", ev);
|
||||||
|
clients.add(client);
|
||||||
|
socket.send("lobbies_start");
|
||||||
|
lobbies.forEach(({ name, clients }) => {
|
||||||
|
socket.send(JSON.stringify({ name, numClients: clients.length }));
|
||||||
|
});
|
||||||
|
socket.send("lobbies_end");
|
||||||
|
};
|
||||||
|
socket.onclose = (ev) => {
|
||||||
|
console.log("Client Socket Close:", ev);
|
||||||
|
clients.delete(client);
|
||||||
|
};
|
||||||
|
socket.onerror = (ev) => {
|
||||||
|
console.log("Client Socket Error:", ev);
|
||||||
|
clients.delete(client);
|
||||||
|
};
|
||||||
|
respondWith(response);
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
function peerCreatesLobby(peer: Peer, lobbyName: string) {
|
||||||
|
// TODO: ensure we can create a lobby
|
||||||
|
lobbies.add({
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
function peerJoinsLobby(peer: Peer, lobby: Lobby) {
|
||||||
lobbyName = randomSecret();
|
lobbyName = randomSecret();
|
||||||
lobbies.set(lobbyName, new Lobby(lobbyName, this.id));
|
lobbies.set(lobbyName, new Lobby(lobbyName, this.id));
|
||||||
console.log(`Peer ${this.id} created lobby ${lobbyName}`);
|
console.log(`Peer ${this.id} created lobby ${lobbyName}`);
|
||||||
|
@ -80,29 +77,7 @@ class Peer {
|
||||||
lobby.join(this);
|
lobby.join(this);
|
||||||
this.ws.send(`J: ${lobbyName}\n`);
|
this.ws.send(`J: ${lobbyName}\n`);
|
||||||
}
|
}
|
||||||
}
|
// TODO: ensure peer not already in a lobby
|
||||||
|
|
||||||
class Lobby {
|
|
||||||
name: string;
|
|
||||||
host: number;
|
|
||||||
peers: Peer[];
|
|
||||||
sealed: boolean;
|
|
||||||
closeTimer: number;
|
|
||||||
|
|
||||||
constructor(name: string, host: number) {
|
|
||||||
this.name = name;
|
|
||||||
this.host = host;
|
|
||||||
this.peers = [];
|
|
||||||
this.sealed = false;
|
|
||||||
this.closeTimer = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPeerId(peer: Peer) {
|
|
||||||
if (this.host === peer.id) return 1;
|
|
||||||
return peer.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
join(peer: Peer) {
|
|
||||||
const assigned = this.getPeerId(peer);
|
const assigned = this.getPeerId(peer);
|
||||||
peer.ws.send(`I: ${assigned}\n`);
|
peer.ws.send(`I: ${assigned}\n`);
|
||||||
this.peers.forEach((p) => {
|
this.peers.forEach((p) => {
|
||||||
|
@ -110,9 +85,11 @@ class Lobby {
|
||||||
peer.ws.send(`N: ${this.getPeerId(p)}\n`);
|
peer.ws.send(`N: ${this.getPeerId(p)}\n`);
|
||||||
});
|
});
|
||||||
this.peers.push(peer);
|
this.peers.push(peer);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
leave(peer: Peer) {
|
/*
|
||||||
|
function peerLeavesLobby(peer: Peer) {
|
||||||
const idx = this.peers.findIndex((p) => peer === p);
|
const idx = this.peers.findIndex((p) => peer === p);
|
||||||
if (idx === -1) return false;
|
if (idx === -1) return false;
|
||||||
const assigned = this.getPeerId(peer);
|
const assigned = this.getPeerId(peer);
|
||||||
|
@ -134,9 +111,11 @@ class Lobby {
|
||||||
this.closeTimer = -1;
|
this.closeTimer = -1;
|
||||||
}
|
}
|
||||||
return close;
|
return close;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
seal(peer: Peer) {
|
/*
|
||||||
|
function graduateLobby(lobby: Lobby) {
|
||||||
// only host can seal
|
// only host can seal
|
||||||
if (peer.id !== this.host) {
|
if (peer.id !== this.host) {
|
||||||
throw new ProtoError(4000, STR_ONLY_HOST_CAN_SEAL);
|
throw new ProtoError(4000, STR_ONLY_HOST_CAN_SEAL);
|
||||||
|
@ -155,39 +134,14 @@ class Lobby {
|
||||||
p.ws.close(1000, STR_SEAL_COMPLETE);
|
p.ws.close(1000, STR_SEAL_COMPLETE);
|
||||||
});
|
});
|
||||||
}, SEAL_CLOSE_TIMEOUT);
|
}, SEAL_CLOSE_TIMEOUT);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
function parseMsg(peer: Peer, msg: string) {
|
function parseMsg(peer: Peer, msg: string) {
|
||||||
const sep = msg.indexOf("\n");
|
// TODO: modify this?
|
||||||
if (sep < 0) throw new ProtoError(4000, STR_INVALID_FORMAT);
|
|
||||||
|
|
||||||
const cmd = msg.slice(0, sep);
|
|
||||||
if (cmd.length < 3) throw new ProtoError(4000, STR_INVALID_FORMAT);
|
|
||||||
|
|
||||||
const data = msg.slice(sep);
|
|
||||||
|
|
||||||
// join
|
|
||||||
if (cmd.startsWith("J: ")) {
|
|
||||||
peer.joinLobby(cmd.substr(3).trim());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!peer.lobby) throw new ProtoError(4000, STR_NEED_LOBBY);
|
|
||||||
const lobby = lobbies.get(peer.lobby);
|
|
||||||
if (!lobby) throw new ProtoError(4000, STR_SERVER_ERROR);
|
|
||||||
|
|
||||||
// seal
|
|
||||||
if (cmd.startsWith("S: ")) {
|
|
||||||
lobby.seal(peer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message relaying format:
|
|
||||||
//
|
|
||||||
// [O|A|C]: DEST_ID\n
|
|
||||||
// PAYLOAD
|
|
||||||
//
|
|
||||||
// O: Client is sending an offer.
|
// O: Client is sending an offer.
|
||||||
// A: Client is sending an answer.
|
// A: Client is sending an answer.
|
||||||
// C: Client is sending a candidate.
|
// C: Client is sending a candidate.
|
||||||
|
@ -208,63 +162,4 @@ function parseMsg(peer: Peer, msg: string) {
|
||||||
}
|
}
|
||||||
throw new ProtoError(4000, STR_INVALID_CMD);
|
throw new ProtoError(4000, STR_INVALID_CMD);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
console.log(`Server running on port ${PORT}`);
|
|
||||||
const server = Deno.listen({ port: PORT });
|
|
||||||
for await (const conn of server) {
|
|
||||||
(async () => {
|
|
||||||
const httpConn = Deno.serveHttp(conn);
|
|
||||||
for await (const requestEvent of httpConn) {
|
|
||||||
if (requestEvent) {
|
|
||||||
try {
|
|
||||||
const { socket, response } = Deno.upgradeWebSocket(
|
|
||||||
requestEvent.request,
|
|
||||||
);
|
|
||||||
const id = randomId();
|
|
||||||
const peer = new Peer(id, socket);
|
|
||||||
socket.onopen = (_ev) => {
|
|
||||||
if (peersCount >= MAX_PEERS) {
|
|
||||||
socket.close(4000, STR_TOO_MANY_PEERS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
peersCount++;
|
|
||||||
};
|
|
||||||
socket.onmessage = (ev) => {
|
|
||||||
try {
|
|
||||||
parseMsg(peer, ev.data);
|
|
||||||
} catch (e) {
|
|
||||||
const code = e.code || 4000;
|
|
||||||
console.log(`Error parsing message from ${id}:\n` + ev.data);
|
|
||||||
socket.close(code, e.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
socket.onclose = (ev) => {
|
|
||||||
peersCount--;
|
|
||||||
console.log(
|
|
||||||
`Connection with peer ${peer.id} closed ` +
|
|
||||||
`with reason: ${ev.reason}`,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
peer.lobby &&
|
|
||||||
lobbies.has(peer.lobby) &&
|
|
||||||
lobbies.get(peer.lobby).leave(peer)
|
|
||||||
) {
|
|
||||||
lobbies.delete(peer.lobby);
|
|
||||||
console.log(`Deleted lobby ${peer.lobby}`);
|
|
||||||
console.log(`Open lobbies: ${lobbies.size}`);
|
|
||||||
peer.lobby = "";
|
|
||||||
}
|
|
||||||
if (peer.timeout >= 0) {
|
|
||||||
clearTimeout(peer.timeout);
|
|
||||||
peer.timeout = -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
socket.onerror = (e) => console.error("WebSocket error:", e);
|
|
||||||
requestEvent.respondWith(response);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Error during connection:`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,52 +1,75 @@
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
export var autojoin = true
|
"""
|
||||||
export var lobby = "" # Will create a new lobby if empty.
|
This module is responsible for making a WebSocket connection to the signaller
|
||||||
|
in order to enable establishish WebRTC P2P connections. Another module is
|
||||||
|
expected to fully setup the peer connections.
|
||||||
|
"""
|
||||||
|
|
||||||
var client: WebSocketClient = WebSocketClient.new()
|
onready var ws: WebSocketClient = WebSocketClient.new()
|
||||||
var code = 1000
|
|
||||||
var reason = "Unknown"
|
|
||||||
|
|
||||||
signal lobby_joined(lobby)
|
func _ready():
|
||||||
signal connected(id)
|
var _result = ws.connect("data_received", self, "_parse_msg")
|
||||||
signal disconnected()
|
_result = ws.connect("connection_established", self, "_connected")
|
||||||
signal peer_connected(id)
|
_result = ws.connect("connection_closed", self, "_closed")
|
||||||
signal peer_disconnected(id)
|
_result = ws.connect("connection_error", self, "_closed")
|
||||||
signal offer_received(id, offer)
|
_result = ws.connect("server_close_request", self, "_close_request")
|
||||||
signal answer_received(id, answer)
|
|
||||||
signal candidate_received(id, mid, index, sdp)
|
|
||||||
signal lobby_sealed()
|
|
||||||
|
|
||||||
func _init():
|
|
||||||
client.connect("data_received", self, "_parse_msg")
|
|
||||||
client.connect("connection_established", self, "_connected")
|
|
||||||
client.connect("connection_closed", self, "_closed")
|
|
||||||
client.connect("connection_error", self, "_closed")
|
|
||||||
client.connect("server_close_request", self, "_close_request")
|
|
||||||
|
|
||||||
func connect_to_url(url):
|
|
||||||
close()
|
|
||||||
code = 1000
|
|
||||||
reason = "Unknown"
|
|
||||||
client.connect_to_url(url)
|
|
||||||
|
|
||||||
func close():
|
func close():
|
||||||
client.disconnect_from_host()
|
ws.disconnect_from_host()
|
||||||
|
|
||||||
func _closed(was_clean = false):
|
func connect_to_websocket_signaller(url: String):
|
||||||
emit_signal("disconnected")
|
print(ws)
|
||||||
|
close()
|
||||||
|
print("Attempting to connect to WebSocket signalling server at ", url)
|
||||||
|
var _result = ws.connect_to_url(url)
|
||||||
|
|
||||||
func _close_request(code, reason):
|
func _closed():
|
||||||
self.code = code
|
# emit_signal("disconnected")
|
||||||
self.reason = reason
|
pass
|
||||||
|
|
||||||
|
func _close_request(code: int, reason: String):
|
||||||
|
print("Received WebSocket close request from signalling server ", code, reason)
|
||||||
|
|
||||||
func _connected(protocol = ""):
|
func _connected(protocol = ""):
|
||||||
client.get_peer(1).set_write_mode(WebSocketPeer.WRITE_MODE_TEXT)
|
print("WebSocket signaller connected via protocol ", protocol)
|
||||||
if autojoin:
|
ws.get_peer(1).set_write_mode(WebSocketPeer.WRITE_MODE_TEXT)
|
||||||
join_lobby(lobby)
|
|
||||||
|
func _process(_delta: float):
|
||||||
|
var status: int = ws.get_connection_status()
|
||||||
|
if status == WebSocketClient.CONNECTION_CONNECTING or status == WebSocketClient.CONNECTION_CONNECTED:
|
||||||
|
ws.poll()
|
||||||
|
|
||||||
func _parse_msg():
|
func _parse_msg():
|
||||||
var pkt_str: String = client.get_peer(1).get_packet().get_string_from_utf8()
|
var pkt_str: String = ws.get_peer(1).get_packet().get_string_from_utf8()
|
||||||
|
print("Signaller sent: ", pkt_str)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
func _parse_msg():
|
||||||
|
var pkt_str: String = ws.get_peer(1).get_packet().get_string_from_utf8()
|
||||||
|
|
||||||
var req: PoolStringArray = pkt_str.split("\n", true, 1)
|
var req: PoolStringArray = pkt_str.split("\n", true, 1)
|
||||||
if req.size() != 2: # Invalid request size
|
if req.size() != 2: # Invalid request size
|
||||||
|
@ -99,10 +122,10 @@ func _parse_msg():
|
||||||
emit_signal("candidate_received", src_id, candidate[0], int(candidate[1]), candidate[2])
|
emit_signal("candidate_received", src_id, candidate[0], int(candidate[1]), candidate[2])
|
||||||
|
|
||||||
func join_lobby(joined_lobby):
|
func join_lobby(joined_lobby):
|
||||||
return client.get_peer(1).put_packet(("J: %s\n" % joined_lobby).to_utf8())
|
return ws.get_peer(1).put_packet(("J: %s\n" % joined_lobby).to_utf8())
|
||||||
|
|
||||||
func seal_lobby():
|
func seal_lobby():
|
||||||
return client.get_peer(1).put_packet("S: \n".to_utf8())
|
return ws.get_peer(1).put_packet("S: \n".to_utf8())
|
||||||
|
|
||||||
func send_candidate(id, mid, index, sdp) -> int:
|
func send_candidate(id, mid, index, sdp) -> int:
|
||||||
return _send_msg("C", id, "\n%s\n%d\n%s" % [mid, index, sdp])
|
return _send_msg("C", id, "\n%s\n%d\n%s" % [mid, index, sdp])
|
||||||
|
@ -114,9 +137,5 @@ func send_answer(id, answer) -> int:
|
||||||
return _send_msg("A", id, answer)
|
return _send_msg("A", id, answer)
|
||||||
|
|
||||||
func _send_msg(type, id, data) -> int:
|
func _send_msg(type, id, data) -> int:
|
||||||
return client.get_peer(1).put_packet(("%s: %d\n%s" % [type, id, data]).to_utf8())
|
return ws.get_peer(1).put_packet(("%s: %d\n%s" % [type, id, data]).to_utf8())
|
||||||
|
"""
|
||||||
func _process(_delta):
|
|
||||||
var status: int = client.get_connection_status()
|
|
||||||
if status == WebSocketClient.CONNECTION_CONNECTING or status == WebSocketClient.CONNECTION_CONNECTED:
|
|
||||||
client.poll()
|
|
Loading…
Reference in a new issue