Skip to content

Commit c28fb6f

Browse files
committed
Initial resurrection implementation
1 parent 2d24ec0 commit c28fb6f

File tree

13 files changed

+433
-216
lines changed

13 files changed

+433
-216
lines changed

internal/api/http.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func (handler *V1Handler) SetupRoutes(rootPath string, register func(string, str
3131
// We support both path parameter and cookie.
3232
register("GET", path.Join(v1, "lobby", "ws"), handler.websocketUpgrade)
3333

34+
register("POST", path.Join(v1, "lobby", "resurrect"), handler.resurrectLobby)
3435
register("POST", path.Join(v1, "lobby", "{lobby_id}", "player"), handler.postPlayer)
3536
}
3637

internal/api/v1.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package api
44

55
import (
6+
"encoding/base64"
67
json "encoding/json"
78
"errors"
89
"fmt"
@@ -63,7 +64,6 @@ type LobbyEntry struct {
6364
func (handler *V1Handler) getLobbies(writer http.ResponseWriter, _ *http.Request) {
6465
// REMARK: If paging is ever implemented, we might want to maintain order
6566
// when deleting lobbies from state in the state package.
66-
6767
lobbies := state.GetPublicLobbies()
6868
lobbyEntries := make(LobbyEntries, 0, len(lobbies))
6969
for _, lobby := range lobbies {
@@ -80,7 +80,7 @@ func (handler *V1Handler) getLobbies(writer http.ResponseWriter, _ *http.Request
8080
MaxClientsPerIP: lobby.ClientsPerIPLimit,
8181
Wordpack: lobby.Wordpack,
8282
State: lobby.State,
83-
Scoring: lobby.ScoreCalculation.Identifier(),
83+
Scoring: lobby.ScoreCalculationIdentifier,
8484
})
8585
}
8686

@@ -92,6 +92,28 @@ func (handler *V1Handler) getLobbies(writer http.ResponseWriter, _ *http.Request
9292
}
9393
}
9494

95+
func (handler *V1Handler) resurrectLobby(writer http.ResponseWriter, request *http.Request) {
96+
var data game.LobbyRestoreData
97+
base64Decoder := base64.NewDecoder(base64.StdEncoding, request.Body)
98+
if err := json.NewDecoder(base64Decoder).Decode(&data); err != nil {
99+
log.Println("Error unmarshalling lobby resurrection data:", err)
100+
http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
101+
return
102+
}
103+
104+
lobby := data.Lobby
105+
// We add the lobby, while the lobby mutex is aqcuired. This prevents us
106+
// from attempting to connect to the lobby, before the internal state has
107+
// been restored correctly.
108+
lobby.Synchronized(func() {
109+
if state.ResurrectLobby(lobby) {
110+
lobby.WriteObject = WriteObject
111+
lobby.WritePreparedMessage = WritePreparedMessage
112+
lobby.ResurrectUnsynchronized(&data)
113+
}
114+
})
115+
}
116+
95117
func (handler *V1Handler) postLobby(writer http.ResponseWriter, request *http.Request) {
96118
if err := request.ParseForm(); err != nil {
97119
http.Error(writer, err.Error(), http.StatusBadRequest)

internal/api/ws.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ func (handler *V1Handler) websocketUpgrade(writer http.ResponseWriter, request *
5151

5252
lobby := state.GetLobby(lobbyId)
5353
if lobby == nil {
54-
http.Error(writer, ErrLobbyNotExistent.Error(), http.StatusNotFound)
54+
socket, err := upgrader.Upgrade(writer, request)
55+
if err != nil {
56+
http.Error(writer, err.Error(), http.StatusInternalServerError)
57+
return
58+
}
59+
socket.WriteClose(1000, []byte("lobby_gone"))
5560
return
5661
}
5762

internal/frontend/index.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
const discordInstanceId = getCookie("discord-instance-id")
22
const rootPath = `${discordInstanceId ? ".proxy/" : ""}{{.RootPath}}`
33

4+
function createLabel(cssClass, forElement, text) {
5+
const label = document.createElement("label");
6+
label.setAttribute("for", forElement);
7+
label.classList.add(cssClass);
8+
label.innerText = text;
9+
return label;
10+
}
11+
12+
function createNumberInput(id, min, max, value) {
13+
const input = document.createElement("input");
14+
input.setAttribute("type", "number");
15+
// Figure out why I did this exactly.
16+
input.setAttribute("size", "4");
17+
input.setAttribute("id", id);
18+
input.setAttribute("name", id);
19+
input.setAttribute("value", Number.toString(value));
20+
input.setAttribute("min", Number.toString(min));
21+
input.setAttribute("max", Number.toString(max));
22+
23+
const decButton = document.createElement("button");
24+
decButton.setAttribute("type", "button");
25+
decButton.classList.add("number-decrement");
26+
decButton.addEventListener("click", function() {
27+
input.stepDown();
28+
})
29+
decButton.innerText = "-";
30+
31+
const incButton = document.createElement("button");
32+
incButton.setAttribute("type", "button");
33+
incButton.classList.add("number-increment");
34+
incButton.addEventListener("click", function() {
35+
input.stepUp();
36+
})
37+
incButton.innerText = "+";
38+
39+
const div = document.createElement("div")
40+
div.append(decButton, input, incButton);
41+
return div;
42+
}
43+
444
Array
545
.from(document.getElementsByClassName("number-input"))
646
.forEach(number_input => {

internal/frontend/lobby.js

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,73 @@ let hasSocketEverConnected = false;
1010
let socket;
1111
function connectToWebsocket() {
1212
if (socketIsConnecting === true) {
13+
console.log("aborting connection attempt.");
1314
return;
1415
}
1516

1617
socketIsConnecting = true;
1718

18-
socket = new WebSocket(`${rootPath}/v1/lobby/ws`);
19+
try {
20+
socket = new WebSocket(`${rootPath}/v1/lobby/ws`);
21+
} catch (exception) {
22+
console.log("Connection error:" + exception)
23+
socketIsConnecting = false;
24+
connectToWebsocket();
25+
return;
26+
}
1927

2028
socket.onerror = error => {
2129
//Is not connected and we haven't yet said that we are done trying to
2230
//connect, this means that we could never even establish a connection.
23-
if (socket.readyState != 1 && !hasSocketEverConnected) {
31+
if (socket.readyState != 1) {
2432
socketIsConnecting = false;
25-
showTextDialog("connection-error-dialog",
26-
'{{.Translation.Get "error-connecting"}}',
27-
`{{.Translation.Get "error-connecting-text"}}`);
28-
console.log("Error establishing connection: ", error);
33+
if (!hasSocketEverConnected) {
34+
showTextDialog("connection-error-dialog",
35+
'{{.Translation.Get "error-connecting"}}',
36+
`{{.Translation.Get "error-connecting-text"}}`);
37+
console.log("Error establishing connection: ", error);
38+
} else {
39+
connectToWebsocket();
40+
}
2941
} else {
3042
console.log("Socket error: ", error)
3143
}
3244
};
3345

3446
socket.onopen = () => {
47+
closeDialog(shutdownDialogId);
3548
closeDialog(reconnectDialogId);
3649

3750
hasSocketEverConnected = true;
3851
socketIsConnecting = false;
3952

4053
socket.onclose = event => {
41-
//We want to avoid handling the error multiple times and showing the incorrect dialogs.
54+
//We w to avoid handling the error multiple times and showing the incorrect dialogs.
4255
socket.onerror = null;
4356

4457
console.log("Socket Closed Connection: ", event);
45-
console.log("Attempting to reestablish socket connection.");
46-
showReconnectDialogIfNotShown();
47-
connectToWebsocket();
58+
59+
if (restoreData && event.reason === "lobby_gone") {
60+
console.log("Resurrecting lobby ...",);
61+
fetch('/v1/lobby/resurrect', {
62+
method: 'POST',
63+
body: restoreData,
64+
}).then(() => {
65+
console.log("Attempting to reestablish socket connection after resurrection ...");
66+
socketIsConnecting = false;
67+
connectToWebsocket();
68+
});
69+
70+
return
71+
}
72+
73+
if (event.reason !== "lobby_gone" && event.reason !== "server_restart") {
74+
console.log("Attempting to reestablish socket connection.");
75+
showReconnectDialogIfNotShown();
76+
}
77+
if (event.reason === "server_restart") {
78+
connectToWebsocket();
79+
}
4880
};
4981

5082
registerMessageHandler(socket);
@@ -53,6 +85,7 @@ function connectToWebsocket() {
5385
};
5486
}
5587

88+
const shutdownDialogId = "shutdown-dialog";
5689
const reconnectDialogId = "reconnect-dialog";
5790
function showReconnectDialogIfNotShown() {
5891
const previousReconnectDialog = document.getElementById(reconnectDialogId);
@@ -833,6 +866,7 @@ let rounds = 0;
833866
let roundEndTime = 0;
834867
let gameState = "unstarted";
835868
let drawingTimeSetting = "∞";
869+
let restoreData;
836870

837871
function registerMessageHandler(targetSocket) {
838872
targetSocket.onmessage = event => {
@@ -985,10 +1019,16 @@ function registerMessageHandler(targetSocket) {
9851019
+ '{{.Translation.Get "custom-words-per-turn-setting"}}: ' + parsed.data.customWordsPerTurn + "%\n"
9861020
+ '{{.Translation.Get "players-per-ip-limit-setting"}}: ' + parsed.data.clientsPerIpLimit);
9871021
} else if (parsed.type === "shutdown") {
988-
socket.onclose = null;
989-
socket.close();
990-
showDialog("shutdown-info", "Server shutting down",
991-
document.createTextNode("Sorry, but the server is about to shut down. Please come back at a later time."));
1022+
console.log("Shutdown event received");
1023+
if (parsed.data) {
1024+
restoreData = parsed.data;
1025+
// FIXMe Text anpassen!
1026+
showDialog("shutdown-dialog", "Server shutting down",
1027+
document.createTextNode("Sorry, but the server is about to shut down. Attempting to restore lobby on restart ..."));
1028+
} else {
1029+
showDialog("shutdown-dialog", "Server shutting down",
1030+
document.createTextNode("Sorry, but the server is about to shut down. Please come back at a later time."));
1031+
}
9921032
}
9931033
}
9941034
};
@@ -1033,6 +1073,7 @@ function setRoundTimeLeft(timeLeftMs) {
10331073
}
10341074

10351075
function handleReadyEvent(ready) {
1076+
restoreData = null;
10361077
ownerID = ready.ownerId;
10371078
ownID = ready.playerId;
10381079

0 commit comments

Comments
 (0)