Add autoload global script

This commit is contained in:
Daniel Flanagan 2021-11-15 15:43:33 -06:00
parent b915552a64
commit 6de5819595
Signed by: lytedev
GPG key ID: 5B2020A0F9921EF4
9 changed files with 162 additions and 136 deletions

20
game.gd Normal file
View file

@ -0,0 +1,20 @@
extends Node2D
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
func _on_Button_pressed():
Global.leave_game()

28
game.tscn Normal file
View file

@ -0,0 +1,28 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://game.gd" type="Script" id=1]
[node name="Node2D" type="Node2D"]
script = ExtResource( 1 )
[node name="Label" type="Label" parent="."]
margin_left = 93.0
margin_top = 109.0
margin_right = 253.0
margin_bottom = 123.0
text = "You are playing the game"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Button" type="Button" parent="Label"]
margin_left = 29.0
margin_top = 177.0
margin_right = 120.0
margin_bottom = 197.0
text = "Leave Game"
__meta__ = {
"_edit_use_anchors_": false
}
[connection signal="pressed" from="Label/Button" to="." method="_on_Button_pressed"]

20
gen.ts Normal file
View file

@ -0,0 +1,20 @@
const ALFNUM = "0123456789";
const SECRET_LENGTH = 8;
function randomInt(low: number, high: number) {
return Math.floor(Math.random() * (high - low + 1) + low);
}
export function randomId() {
const arr = new Int32Array(1);
crypto.getRandomValues(arr);
return Math.abs(arr[0]);
}
export function randomSecret() {
let out = "";
for (let i = 0; i < SECRET_LENGTH; i++) {
out += ALFNUM[randomInt(0, ALFNUM.length - 1)];
}
return out;
}

22
global.gd Normal file
View file

@ -0,0 +1,22 @@
extends Node
# Declare member variables here. Examples:
# var a = 2
# var b = "text"
# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
func start_singleplayer_game():
print("Starting singleplayer game...")
func leave_game():
print("Leaving game...")

14
main.gd
View file

@ -1,14 +1,4 @@
extends Control
func _ready():
if OS.get_name() == "HTML5":
$VBoxContainer/Signaling.hide()
func _on_listen_toggled(button_pressed): func _on_Singleplayer_pressed():
if button_pressed: Global.start_singleplayer_game()
$Server.listen(int($VBoxContainer/Signaling/Port.value))
else:
$Server.stop()
func _on_LinkButton_pressed():
OS.shell_open("https://github.com/godotengine/webrtc-native/releases")

View file

@ -1,7 +1,6 @@
[gd_scene load_steps=3 format=2] [gd_scene load_steps=2 format=2]
[ext_resource path="res://main.gd" type="Script" id=1] [ext_resource path="res://main.gd" type="Script" id=1]
[ext_resource path="res://client_ui.tscn" type="PackedScene" id=2]
[node name="Control" type="Control"] [node name="Control" type="Control"]
anchor_left = 0.0136719 anchor_left = 0.0136719
@ -23,76 +22,29 @@ __meta__ = {
"_edit_use_anchors_": true "_edit_use_anchors_": true
} }
[node name="Signaling" type="HBoxContainer" parent="VBoxContainer"] [node name="Singleplayer" type="Button" parent="VBoxContainer"]
margin_right = 995.0 margin_right = 995.0
margin_bottom = 24.0 margin_bottom = 20.0
text = "Start Singleplayer"
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Label" type="Label" parent="VBoxContainer/Signaling"] [node name="CreateLobbyButton" type="Button" parent="VBoxContainer"]
margin_top = 5.0 margin_top = 70.0
margin_right = 104.0
margin_bottom = 19.0
text = "Signaling server:"
[node name="Port" type="SpinBox" parent="VBoxContainer/Signaling"]
margin_left = 108.0
margin_right = 182.0
margin_bottom = 24.0
min_value = 1025.0
max_value = 65535.0
value = 9080.0
[node name="ListenButton" type="Button" parent="VBoxContainer/Signaling"]
margin_left = 186.0
margin_right = 237.0
margin_bottom = 24.0
toggle_mode = true
text = "Listen"
[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer/Signaling"]
margin_left = 241.0
margin_right = 995.0 margin_right = 995.0
margin_bottom = 24.0 margin_bottom = 90.0
size_flags_horizontal = 3 text = "Create Lobby"
size_flags_vertical = 3
[node name="LinkButton" type="LinkButton" parent="VBoxContainer/Signaling/CenterContainer"] [node name="JoinLobbyButton" type="Button" parent="VBoxContainer"]
margin_left = 104.0 margin_top = 140.0
margin_top = 5.0
margin_right = 650.0
margin_bottom = 19.0
text = "Make sure to download the GDNative WebRTC Plugin and place it in the project folder"
[node name="Clients" type="GridContainer" parent="VBoxContainer"]
margin_top = 74.0
margin_right = 995.0 margin_right = 995.0
margin_bottom = 579.0 margin_bottom = 160.0
size_flags_horizontal = 3 text = "Join Lobby"
size_flags_vertical = 3 __meta__ = {
custom_constants/vseparation = 15 "_edit_use_anchors_": false
custom_constants/hseparation = 15 }
columns = 2
[node name="ClientUI" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
margin_right = 490.0
margin_bottom = 245.0
[node name="ClientUI2" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
margin_left = 505.0
margin_right = 995.0
margin_bottom = 245.0
[node name="ClientUI3" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
margin_top = 260.0
margin_right = 490.0
margin_bottom = 505.0
[node name="ClientUI4" parent="VBoxContainer/Clients" instance=ExtResource( 2 )]
margin_left = 505.0
margin_top = 260.0
margin_right = 995.0
margin_bottom = 505.0
[node name="Server" type="Node" parent="."] [node name="Server" type="Node" parent="."]
[connection signal="toggled" from="VBoxContainer/Signaling/ListenButton" to="." method="_on_listen_toggled"] [connection signal="pressed" from="VBoxContainer/Singleplayer" to="." method="_on_Singleplayer_pressed"]
[connection signal="pressed" from="VBoxContainer/Signaling/CenterContainer/LinkButton" to="." method="_on_LinkButton_pressed"]

View file

@ -39,6 +39,7 @@ func _create_peer(id):
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)
send_candidate(id, mid_name, index_name, sdp_name) send_candidate(id, mid_name, index_name, sdp_name)
func _offer_created(type, data, id): func _offer_created(type, data, id):

View file

@ -14,6 +14,10 @@ config/name="kdt"
run/main_scene="res://main.tscn" run/main_scene="res://main.tscn"
config/icon="res://icon.png" config/icon="res://icon.png"
[autoload]
Global="*res://global.gd"
[gdnative] [gdnative]
singletons=[ "res://webrtc/webrtc.tres" ] singletons=[ "res://webrtc/webrtc.tres" ]

103
server.ts
View file

@ -1,11 +1,12 @@
import { randomId, randomSecret } from "./gen.ts";
const MAX_PEERS = 4096; const MAX_PEERS = 4096;
const MAX_LOBBIES = 1024; const MAX_LOBBIES = 1024;
const PORT = 9080; const PORT = 9080;
const ALFNUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const NO_LOBBY_TIMEOUT = 1000; const NO_LOBBY_TIMEOUT = 10000;
const SEAL_CLOSE_TIMEOUT = 10000; const SEAL_CLOSE_TIMEOUT = 10000;
const PING_INTERVAL = 10000; // const PING_INTERVAL = 10000;
const STR_NO_LOBBY = "Have not joined lobby yet"; const STR_NO_LOBBY = "Have not joined lobby yet";
const STR_HOST_DISCONNECTED = "Room host has disconnected"; const STR_HOST_DISCONNECTED = "Room host has disconnected";
@ -22,23 +23,11 @@ const STR_INVALID_DEST = "Invalid destination";
const STR_INVALID_CMD = "Invalid command"; const STR_INVALID_CMD = "Invalid command";
const STR_TOO_MANY_PEERS = "Too many peers connected"; const STR_TOO_MANY_PEERS = "Too many peers connected";
function randomInt(low: number, high: number) { // TODO: setup regular pings?
return Math.floor(Math.random() * (high - low + 1) + low);
}
function randomId() { // state
const arr = new Int32Array(1); const lobbies = new Map();
crypto.getRandomValues(arr); let peersCount = 0;
return Math.abs(arr[0]);
}
function randomSecret() {
let out = "";
for (let i = 0; i < 16; i++) {
out += ALFNUM[randomInt(0, ALFNUM.length - 1)];
}
return out;
}
class ProtoError extends Error { class ProtoError extends Error {
code: number; code: number;
@ -59,11 +48,38 @@ class Peer {
this.id = id; this.id = id;
this.ws = ws; this.ws = ws;
this.lobby = ""; this.lobby = "";
// Close connection after 1 sec if client has not joined a lobby
// close connection after 10 sec if client has not joined a lobby
this.timeout = setTimeout(() => { this.timeout = setTimeout(() => {
if (!this.lobby) ws.close(4000, STR_NO_LOBBY); if (!this.lobby) ws.close(4000, STR_NO_LOBBY);
}, NO_LOBBY_TIMEOUT); }, NO_LOBBY_TIMEOUT);
} }
joinLobby(lobbyName: string) {
if (lobbyName === "") {
if (lobbies.size >= MAX_LOBBIES) {
throw new ProtoError(4000, STR_TOO_MANY_LOBBIES);
}
// Peer must not already be in a lobby
if (this.lobby !== "") {
throw new ProtoError(4000, STR_ALREADY_IN_LOBBY);
}
lobbyName = randomSecret();
lobbies.set(lobbyName, new Lobby(lobbyName, this.id));
console.log(`Peer ${this.id} created lobby ${lobbyName}`);
console.log(`Open lobbies: ${lobbies.size}`);
}
const lobby = lobbies.get(lobbyName);
if (!lobby) throw new ProtoError(4000, STR_LOBBY_DOES_NOT_EXISTS);
if (lobby.sealed) throw new ProtoError(4000, STR_LOBBY_IS_SEALED);
this.lobby = lobbyName;
console.log(
`Peer ${this.id} joining lobby ${lobbyName} ` +
`with ${lobby.peers.length} peers`,
);
lobby.join(this);
this.ws.send(`J: ${lobbyName}\n`);
}
} }
class Lobby { class Lobby {
@ -95,6 +111,7 @@ class Lobby {
}); });
this.peers.push(peer); this.peers.push(peer);
} }
leave(peer: Peer) { leave(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;
@ -102,9 +119,9 @@ class Lobby {
const close = assigned === 1; const close = assigned === 1;
this.peers.forEach((p) => { this.peers.forEach((p) => {
try { try {
// Room host disconnected, must close. // room host disconnected
if (close) p.ws.close(4000, STR_HOST_DISCONNECTED); if (close) p.ws.close(4000, STR_HOST_DISCONNECTED);
// Notify peer disconnect. // notify peers
else p.ws.send(`D: ${assigned}\n`); else p.ws.send(`D: ${assigned}\n`);
} catch (e) { } catch (e) {
console.error(`Error when leaving: ${e}`); console.error(`Error when leaving: ${e}`);
@ -112,14 +129,15 @@ class Lobby {
}); });
this.peers.splice(idx, 1); this.peers.splice(idx, 1);
if (close && this.closeTimer >= 0) { if (close && this.closeTimer >= 0) {
// We are closing already. // we are closing already.
clearTimeout(this.closeTimer); clearTimeout(this.closeTimer);
this.closeTimer = -1; this.closeTimer = -1;
} }
return close; return close;
} }
seal(peer: Peer) { seal(peer: Peer) {
// 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);
} }
@ -132,7 +150,7 @@ class Lobby {
`with ${this.peers.length} peers`, `with ${this.peers.length} peers`,
); );
this.closeTimer = setTimeout(() => { this.closeTimer = setTimeout(() => {
// Close peer connection to host (and thus the lobby) // close peer connection to host (and thus the lobby)
this.peers.forEach((p) => { this.peers.forEach((p) => {
p.ws.close(1000, STR_SEAL_COMPLETE); p.ws.close(1000, STR_SEAL_COMPLETE);
}); });
@ -140,35 +158,6 @@ class Lobby {
} }
} }
const lobbies = new Map();
let peersCount = 0;
function joinLobby(peer: Peer, lobbyName: string) {
if (lobbyName === "") {
if (lobbies.size >= MAX_LOBBIES) {
throw new ProtoError(4000, STR_TOO_MANY_LOBBIES);
}
// Peer must not already be in a lobby
if (peer.lobby !== "") {
throw new ProtoError(4000, STR_ALREADY_IN_LOBBY);
}
lobbyName = randomSecret();
lobbies.set(lobbyName, new Lobby(lobbyName, peer.id));
console.log(`Peer ${peer.id} created lobby ${lobbyName}`);
console.log(`Open lobbies: ${lobbies.size}`);
}
const lobby = lobbies.get(lobbyName);
if (!lobby) throw new ProtoError(4000, STR_LOBBY_DOES_NOT_EXISTS);
if (lobby.sealed) throw new ProtoError(4000, STR_LOBBY_IS_SEALED);
peer.lobby = lobbyName;
console.log(
`Peer ${peer.id} joining lobby ${lobbyName} ` +
`with ${lobby.peers.length} peers`,
);
lobby.join(peer);
peer.ws.send(`J: ${lobbyName}\n`);
}
function parseMsg(peer: Peer, msg: string) { function parseMsg(peer: Peer, msg: string) {
const sep = msg.indexOf("\n"); const sep = msg.indexOf("\n");
if (sep < 0) throw new ProtoError(4000, STR_INVALID_FORMAT); if (sep < 0) throw new ProtoError(4000, STR_INVALID_FORMAT);
@ -178,9 +167,9 @@ function parseMsg(peer: Peer, msg: string) {
const data = msg.slice(sep); const data = msg.slice(sep);
// Lobby joining. // join
if (cmd.startsWith("J: ")) { if (cmd.startsWith("J: ")) {
joinLobby(peer, cmd.substr(3).trim()); peer.joinLobby(cmd.substr(3).trim());
return; return;
} }
@ -188,7 +177,7 @@ function parseMsg(peer: Peer, msg: string) {
const lobby = lobbies.get(peer.lobby); const lobby = lobbies.get(peer.lobby);
if (!lobby) throw new ProtoError(4000, STR_SERVER_ERROR); if (!lobby) throw new ProtoError(4000, STR_SERVER_ERROR);
// Lobby sealing. // seal
if (cmd.startsWith("S: ")) { if (cmd.startsWith("S: ")) {
lobby.seal(peer); lobby.seal(peer);
return; return;