From 7d6610fa0300f3cd88c516ed98ee658fba487587 Mon Sep 17 00:00:00 2001 From: Git-Nivrak <59925169+Git-Nivrak@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:40:16 +0300 Subject: [PATCH 1/6] a --- code/__DEFINES/chat.dm | 5 ++ code/controllers/subsystem/chat.dm | 95 +++++++++++++++------ code/datums/chat_payload.dm | 12 +++ code/modules/tgchat/to_chat.dm | 19 +---- code/modules/tgui/tgui_window.dm | 2 + colonialmarines.dme | 1 + tgui/packages/tgui-panel/chat/middleware.js | 39 ++++++++- 7 files changed, 128 insertions(+), 45 deletions(-) create mode 100644 code/datums/chat_payload.dm diff --git a/code/__DEFINES/chat.dm b/code/__DEFINES/chat.dm index 1edc2bd7b5a1..be6f6b81fabf 100644 --- a/code/__DEFINES/chat.dm +++ b/code/__DEFINES/chat.dm @@ -2,6 +2,11 @@ * Copyright (c) 2020 Aleksej Komarov * SPDX-License-Identifier: MIT */ + + /// How many chat payloads to keep in history +#define CHAT_RELIABILITY_HISTORY_SIZE 5 +/// How many resends to allow before giving up +#define CHAT_RELIABILITY_MAX_RESENDS 3 #define MESSAGE_TYPE_SYSTEM "system" #define MESSAGE_TYPE_LOCALCHAT "localchat" diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm index 6095e8b10f4d..222d1f220dc1 100644 --- a/code/controllers/subsystem/chat.dm +++ b/code/controllers/subsystem/chat.dm @@ -5,39 +5,84 @@ SUBSYSTEM_DEF(chat) name = "Chat" - flags = SS_TICKER + flags = SS_TICKER|SS_NO_INIT wait = 1 priority = SS_PRIORITY_CHAT init_order = SS_INIT_CHAT - var/list/payload_by_client = list() + /// Assosciates a ckey with a list of messages to send to them. + var/list/list/datum/chat_payload/client_to_payloads = list() -/datum/controller/subsystem/chat/Initialize() - // Just used by chat system to know that initialization is nearly finished. - // The to_chat checks could probably check the runlevel instead, but would require testing. - return SS_INIT_SUCCESS + /// Associates a ckey with an assosciative list of their last CHAT_RELIABILITY_HISTORY_SIZE messages. + var/list/list/datum/chat_payload/client_to_reliability_history = list() + + /// Assosciates a ckey with their next sequence number. + var/list/client_to_sequence_number = list() + +/datum/controller/subsystem/chat/proc/generate_payload(client/target, message_data) + var/sequence = client_to_sequence_number[target.ckey] + client_to_sequence_number[target.ckey] += 1 + + var/datum/chat_payload/payload = new + payload.sequence = sequence + payload.content = message_data + + if(!(target.ckey in client_to_reliability_history)) + client_to_reliability_history[target.ckey] = list() + var/list/client_history = client_to_reliability_history[target.ckey] + client_history["[sequence]"] = payload + + if(length(client_history) > CHAT_RELIABILITY_HISTORY_SIZE) + var/oldest = text2num(client_history[1]) + for(var/index in 2 to length(client_history)) + var/test = text2num(client_history[index]) + if(test < oldest) + oldest = test + client_history -= "[oldest]" + return payload + +/datum/controller/subsystem/chat/proc/send_payload_to_client(client/target, datum/chat_payload/payload) + target.tgui_panel.window.send_message("chat/message", payload.into_message()) /datum/controller/subsystem/chat/fire() - for(var/key in payload_by_client) - var/client/client = key - var/payload = payload_by_client[key] - payload_by_client -= key - if(client) - // Send to tgchat - client.tgui_panel?.window.send_message("chat/message", payload) - // Send to old chat - for(var/message in payload) - SEND_TEXT(client, message_to_html(message)) + for(var/ckey in client_to_payloads) + var/client/target = GLOB.directory[ckey] + if(isnull(target)) // verify client still exists + LAZYREMOVE(client_to_payloads, ckey) + continue + + for(var/datum/chat_payload/payload as anything in client_to_payloads[ckey]) + send_payload_to_client(target, payload) + LAZYREMOVE(client_to_payloads, ckey) + if(MC_TICK_CHECK) return -/datum/controller/subsystem/chat/proc/queue(target, message) - if(islist(target)) - for(var/_target in target) - var/client/client = CLIENT_FROM_VAR(_target) - if(client) - LAZYADD(payload_by_client[client], list(message)) +/datum/controller/subsystem/chat/proc/queue(queue_target, list/message_data) + var/list/targets = islist(queue_target) ? queue_target : list(queue_target) + for(var/target in targets) + var/client/client = CLIENT_FROM_VAR(target) + if(isnull(client)) + continue + LAZYADDASSOCLIST(client_to_payloads, client.ckey, generate_payload(client, message_data)) + +/datum/controller/subsystem/chat/proc/send_immediate(send_target, list/message_data) + var/list/targets = islist(send_target) ? send_target : list(send_target) + for(var/target in targets) + var/client/client = CLIENT_FROM_VAR(target) + if(isnull(client)) + continue + send_payload_to_client(client, generate_payload(client, message_data)) + +/datum/controller/subsystem/chat/proc/handle_resend(client/client, sequence) + var/list/client_history = client_to_reliability_history[client.ckey] + sequence = "[sequence]" + if(isnull(client_history) || !(sequence in client_history)) return - var/client/client = CLIENT_FROM_VAR(target) - if(client) - LAZYADD(payload_by_client[client], list(message)) + + var/datum/chat_payload/payload = client_history[sequence] + if(payload.resends > CHAT_RELIABILITY_MAX_RESENDS) + return + + payload.resends += 1 + send_payload_to_client(client, client_history[sequence]) diff --git a/code/datums/chat_payload.dm b/code/datums/chat_payload.dm new file mode 100644 index 000000000000..8f2dac281aac --- /dev/null +++ b/code/datums/chat_payload.dm @@ -0,0 +1,12 @@ +/// Stores information about a chat payload +/datum/chat_payload + /// Sequence number of this payload + var/sequence = 0 + /// Message we are sending + var/list/content + /// Resend count + var/resends = 0 + +/// Converts the chat payload into a JSON string +/datum/chat_payload/proc/into_message() + return "{\"sequence\":[sequence],\"content\":[json_encode(content)]}" diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm index d9f96912f8c8..511a58af79f6 100644 --- a/code/modules/tgchat/to_chat.dm +++ b/code/modules/tgchat/to_chat.dm @@ -35,23 +35,8 @@ if(text) message["text"] = text if(html) message["html"] = html if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting - var/message_blob = TGUI_CREATE_MESSAGE("chat/message", message) - var/message_html = message_to_html(message) - if(islist(target)) - for(var/_target in target) - var/client/client = CLIENT_FROM_VAR(_target) - if(client) - // Send to tgchat - client.tgui_panel?.window.send_raw_message(message_blob) - // Send to old chat - SEND_TEXT(client, message_html) - return - var/client/client = CLIENT_FROM_VAR(target) - if(client) - // Send to tgchat - client.tgui_panel?.window.send_raw_message(message_blob) - // Send to old chat - SEND_TEXT(client, message_html) + // send it immediately + SSchat.send_immediate(target, message) /** * Sends the message to the recipient (target). diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 987a2aca92a8..57ca66d03b0b 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -384,6 +384,8 @@ client << link(href_list["url"]) if("cacheReloaded") reinitialize() + if("chat/resend") + SSchat.handle_resend(client, payload) /* /datum/tgui_window/vv_edit_var(var_name, var_value) diff --git a/colonialmarines.dme b/colonialmarines.dme index 5193cd3571cb..18c814940fd5 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -335,6 +335,7 @@ #include "code\datums\bug_report.dm" #include "code\datums\callback.dm" #include "code\datums\changelog.dm" +#include "code\datums\chat_payload.dm" #include "code\datums\combat_personalized.dm" #include "code\datums\computerfiles.dm" #include "code\datums\custom_hud.dm" diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js index 9d43c45aef7b..46fefc9fee1b 100644 --- a/tgui/packages/tgui-panel/chat/middleware.js +++ b/tgui/packages/tgui-panel/chat/middleware.js @@ -82,6 +82,8 @@ const loadChatFromStorage = async (store) => { export const chatMiddleware = (store) => { let initialized = false; let loaded = false; + const sequences = []; + const sequences_requested = []; chatRenderer.events.on('batchProcessed', (countByType) => { // Use this flag to workaround unread messages caused by // loading them from storage. Side effect of that, is that @@ -103,9 +105,40 @@ export const chatMiddleware = (store) => { loadChatFromStorage(store); } if (type === 'chat/message') { - // Normalize the payload - const batch = Array.isArray(payload) ? payload : [payload]; - chatRenderer.processBatch(batch); + var payload_obj; + try { + payload_obj = JSON.parse(payload); + } catch { + return; + } + const sequence = payload_obj.sequence; + if (sequences.includes(sequence)) { + return; + } + + const sequence_count = sequences.length; + seq_check: if (sequence_count > 0) { + if (sequences_requested.includes(sequence)) { + sequences_requested.splice(sequences_requested.indexOf(sequence), 1); + // if we are receiving a message we requested, we can stop reliability checks + break seq_check; + } + + // cannot do reliability if we don't have any messages + const expected_sequence = sequences[sequence_count - 1] + 1; + if (sequence !== expected_sequence) { + for ( + let requesting = expected_sequence; + requesting < sequence; + requesting++ + ) { + requested_sequences.push(requesting); + Byond.sendMessage('chat/resend', requesting); + } + } + } + + chatRenderer.processBatch([payload_obj.content]); return; } if (type === loadChat.type) { From be553a0c5f10fc479420830a2bbaa604fd2673cb Mon Sep 17 00:00:00 2001 From: Git-Nivrak <59925169+Git-Nivrak@users.noreply.github.com> Date: Sat, 29 Jun 2024 10:53:25 +0300 Subject: [PATCH 2/6] linter --- code/__DEFINES/chat.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/__DEFINES/chat.dm b/code/__DEFINES/chat.dm index be6f6b81fabf..f7ea29044c1e 100644 --- a/code/__DEFINES/chat.dm +++ b/code/__DEFINES/chat.dm @@ -3,7 +3,7 @@ * SPDX-License-Identifier: MIT */ - /// How many chat payloads to keep in history +/// How many chat payloads to keep in history #define CHAT_RELIABILITY_HISTORY_SIZE 5 /// How many resends to allow before giving up #define CHAT_RELIABILITY_MAX_RESENDS 3 From 40702359371fcbc8a4dcaf48d2f775515a674733 Mon Sep 17 00:00:00 2001 From: Git-Nivrak <59925169+Git-Nivrak@users.noreply.github.com> Date: Sat, 29 Jun 2024 11:28:56 +0300 Subject: [PATCH 3/6] Linter 2: The lintering --- tgui/packages/tgui-panel/chat/middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tgui/packages/tgui-panel/chat/middleware.js b/tgui/packages/tgui-panel/chat/middleware.js index 46fefc9fee1b..7540851f39f8 100644 --- a/tgui/packages/tgui-panel/chat/middleware.js +++ b/tgui/packages/tgui-panel/chat/middleware.js @@ -105,7 +105,7 @@ export const chatMiddleware = (store) => { loadChatFromStorage(store); } if (type === 'chat/message') { - var payload_obj; + let payload_obj; try { payload_obj = JSON.parse(payload); } catch { From 06a271df25430a8ab0872be7731d9f3396b42231 Mon Sep 17 00:00:00 2001 From: Git-Nivrak <59925169+Git-Nivrak@users.noreply.github.com> Date: Sat, 29 Jun 2024 12:26:29 +0300 Subject: [PATCH 4/6] Update chat.dm --- code/controllers/subsystem/chat.dm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm index 222d1f220dc1..b88e7d603ce1 100644 --- a/code/controllers/subsystem/chat.dm +++ b/code/controllers/subsystem/chat.dm @@ -19,6 +19,13 @@ SUBSYSTEM_DEF(chat) /// Assosciates a ckey with their next sequence number. var/list/client_to_sequence_number = list() + /// Keeps track of resends to see how often chat bugs out + var/resends = 0 + +/datum/controller/subsystem/chat/stat_entry(msg) + msg = "Messages resent: [resends]" + return ..() + /datum/controller/subsystem/chat/proc/generate_payload(client/target, message_data) var/sequence = client_to_sequence_number[target.ckey] client_to_sequence_number[target.ckey] += 1 @@ -85,4 +92,5 @@ SUBSYSTEM_DEF(chat) return payload.resends += 1 + resends += 1 send_payload_to_client(client, client_history[sequence]) From 7bd93476989f60c0225fcab3ed04fdea2cf2d539 Mon Sep 17 00:00:00 2001 From: Git-Nivrak <59925169+Git-Nivrak@users.noreply.github.com> Date: Wed, 3 Jul 2024 20:04:20 +0300 Subject: [PATCH 5/6] Update chat.dm --- code/controllers/subsystem/chat.dm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm index b88e7d603ce1..a6273db75f6b 100644 --- a/code/controllers/subsystem/chat.dm +++ b/code/controllers/subsystem/chat.dm @@ -26,6 +26,10 @@ SUBSYSTEM_DEF(chat) msg = "Messages resent: [resends]" return ..() +/datum/controller/subsystem/chat/Shutdown() + log_world("SSchat shutting down, Number of messages resent: [resends]") + return ..() + /datum/controller/subsystem/chat/proc/generate_payload(client/target, message_data) var/sequence = client_to_sequence_number[target.ckey] client_to_sequence_number[target.ckey] += 1 From 9b0717ad6490c960aa7be2fc4ff4407fa699e4f6 Mon Sep 17 00:00:00 2001 From: Git-Nivrak <59925169+Git-Nivrak@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:38:55 +0300 Subject: [PATCH 6/6] Fixes oldchat --- code/controllers/subsystem/chat.dm | 1 + code/datums/chat_payload.dm | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/code/controllers/subsystem/chat.dm b/code/controllers/subsystem/chat.dm index a6273db75f6b..ddd302048a3a 100644 --- a/code/controllers/subsystem/chat.dm +++ b/code/controllers/subsystem/chat.dm @@ -54,6 +54,7 @@ SUBSYSTEM_DEF(chat) /datum/controller/subsystem/chat/proc/send_payload_to_client(client/target, datum/chat_payload/payload) target.tgui_panel.window.send_message("chat/message", payload.into_message()) + SEND_TEXT(target, payload.get_content_as_html()) /datum/controller/subsystem/chat/fire() for(var/ckey in client_to_payloads) diff --git a/code/datums/chat_payload.dm b/code/datums/chat_payload.dm index 8f2dac281aac..fd35bbc4eecf 100644 --- a/code/datums/chat_payload.dm +++ b/code/datums/chat_payload.dm @@ -10,3 +10,7 @@ /// Converts the chat payload into a JSON string /datum/chat_payload/proc/into_message() return "{\"sequence\":[sequence],\"content\":[json_encode(content)]}" + +/// Returns an HTML-encoded message from our contents. +/datum/chat_payload/proc/get_content_as_html() + return message_to_html(content)