Консоль не вешает вкладку под потоком логов сборки
Загрузка мира со сборкой модов печатает десятки тысяч строк: каждое исключение мода — стек на два десятка строк. Консоль рисовала их по одной и после каждой прокручивалась вниз, заставляя браузер пересчитывать раскладку всего списка. Десять тысяч строк блокировали вкладку на 29 секунд, и запуск сервера выглядел как зависшая панель. Строки копятся и выводятся пачкой раз в кадр: те же десять тысяч теперь занимают 14 мс. Узлы сверх предела списка не создаются вовсе, а на скрытой вкладке очередь ограничена, чтобы не расти в памяти. Заодно вывод игрового сервера больше не гоняется через словарь переводов: он английский, в словаре менеджера его нет, а перебор шаблонов регулярками на таком потоке стоит дорого. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+51
-13
@@ -436,8 +436,8 @@ function handleConsoleMeta(meta) {
|
||||
lastSeq = 0;
|
||||
if (!restarted) return;
|
||||
|
||||
$("#console-output").innerHTML = "";
|
||||
pushConsoleLine("manager", t("console.managerRestarted"));
|
||||
clearConsoleScreen();
|
||||
queueConsoleLine("manager", t("console.managerRestarted"));
|
||||
// Переоткрываем поток: этот запрашивал историю с прежнего номера, а её у
|
||||
// нового запуска нет.
|
||||
openConsoleStream();
|
||||
@@ -451,30 +451,68 @@ function closeConsoleStream() {
|
||||
function appendConsoleLine(line) {
|
||||
if (line.seq <= lastSeq) return;
|
||||
lastSeq = line.seq;
|
||||
pushConsoleLine(line.stream, line.text);
|
||||
queueConsoleLine(line.stream, line.text);
|
||||
}
|
||||
|
||||
// pushConsoleLine рисует строку на экране. Отдельно от нумерации: свои
|
||||
// сообщения панель показывает без номера из буфера менеджера.
|
||||
function pushConsoleLine(stream, text) {
|
||||
// Загрузка мира со сборкой модов печатает десятки тысяч строк за минуты:
|
||||
// каждое исключение — стек на два десятка строк. Рисовать их по одной нельзя:
|
||||
// прокрутка вниз после каждой строки заставляет браузер пересчитывать раскладку
|
||||
// всего списка, и вкладка встаёт колом на десятки секунд. Поэтому строки
|
||||
// копятся и выводятся пачкой раз в кадр.
|
||||
let consoleQueue = [];
|
||||
let consoleFrame = 0;
|
||||
|
||||
// queueConsoleLine ставит строку в очередь на отрисовку. Отдельно от нумерации:
|
||||
// свои сообщения панель показывает без номера из буфера менеджера.
|
||||
function queueConsoleLine(stream, text) {
|
||||
consoleQueue.push({ stream, text });
|
||||
// На скрытой вкладке кадры не идут, и очередь росла бы без предела: держим
|
||||
// столько, сколько всё равно поместится на экране.
|
||||
if (consoleQueue.length > MAX_CONSOLE_LINES * 2) {
|
||||
consoleQueue = consoleQueue.slice(-MAX_CONSOLE_LINES);
|
||||
}
|
||||
if (!consoleFrame) consoleFrame = requestAnimationFrame(flushConsoleQueue);
|
||||
}
|
||||
|
||||
function flushConsoleQueue() {
|
||||
consoleFrame = 0;
|
||||
const pending = consoleQueue;
|
||||
consoleQueue = [];
|
||||
if (!pending.length) return;
|
||||
|
||||
const output = $("#console-output");
|
||||
const filter = $("#console-filter").value.trim().toLowerCase();
|
||||
// Из потопа сохранится всё равно только хвост — остальные узлы незачем даже
|
||||
// создавать.
|
||||
const from = Math.max(0, pending.length - MAX_CONSOLE_LINES);
|
||||
|
||||
const el = document.createElement("div");
|
||||
el.className = `line-${stream}`;
|
||||
el.textContent = text;
|
||||
el.dataset.text = text.toLowerCase();
|
||||
if (filter && !el.dataset.text.includes(filter)) el.hidden = true;
|
||||
output.appendChild(el);
|
||||
const batch = document.createDocumentFragment();
|
||||
for (let i = from; i < pending.length; i++) {
|
||||
const { stream, text } = pending[i];
|
||||
const el = document.createElement("div");
|
||||
el.className = `line-${stream}`;
|
||||
el.textContent = text;
|
||||
el.dataset.text = text.toLowerCase();
|
||||
if (filter && !el.dataset.text.includes(filter)) el.hidden = true;
|
||||
batch.appendChild(el);
|
||||
}
|
||||
output.appendChild(batch);
|
||||
|
||||
while (output.childElementCount > MAX_CONSOLE_LINES) {
|
||||
output.removeChild(output.firstElementChild);
|
||||
}
|
||||
// Прокрутка — один раз на пачку: именно она и стоит дорого.
|
||||
if ($("#console-autoscroll").checked) {
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// clearConsoleScreen убирает и то, что уже нарисовано, и то, что ждёт кадра.
|
||||
function clearConsoleScreen() {
|
||||
consoleQueue = [];
|
||||
$("#console-output").innerHTML = "";
|
||||
}
|
||||
|
||||
$("#console-filter").addEventListener("input", (event) => {
|
||||
const filter = event.target.value.trim().toLowerCase();
|
||||
Array.from($("#console-output").children).forEach((line) => {
|
||||
@@ -484,7 +522,7 @@ $("#console-filter").addEventListener("input", (event) => {
|
||||
|
||||
$("#btn-console-clear").addEventListener("click", () => {
|
||||
// Чистим только экран: история остаётся в буфере менеджера.
|
||||
$("#console-output").innerHTML = "";
|
||||
clearConsoleScreen();
|
||||
});
|
||||
|
||||
// История отправленных команд: та же логика, что и в обычном терминале.
|
||||
|
||||
Reference in New Issue
Block a user