Skip to content

refactor(who): убрать глобальные буферы из команды "кто" (#3807) - #3809

Closed
bylins wants to merge 4 commits into
masterfrom
refactor/do-who-strings
Closed

refactor(who): убрать глобальные буферы из команды "кто" (#3807)#3809
bylins wants to merge 4 commits into
masterfrom
refactor/do-who-strings

Conversation

@bylins

@bylins bylins commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Первый файл слоя 1 из #3807 — заодно проба приёма на небольшом объёме.

Что было

Команда «кто» собирала вывод в глобальных buf, buf1 и arg (utils.h:638) — 54 обращения на 360 строк файла. Буферы общие на весь процесс, поэтому любой вызов посреди сборки строки затирает накопленное, а переполнение ловится только сторожевым байтом plant_magic, уже после факта.

Отдельно забавное место: строка игрока и так собиралась через fmt::format, но результат потом переписывался в глобальный буфер:

strcpy(buf, fmt::format("{}[{:2} {}] {:<30}{}", ...).c_str());

Что стало

  • разбор аргументов ведёт локальный помощник ChopWord — первое слово в нижнем регистре и остаток, обе части std::string;
  • строка игрока, заголовки разделов и итоговые счётчики собираются fmt::format в std::string;
  • лишняя пересылка через strcpy ушла — fmt::format пишет сразу в свою строку.

sprintf и strcat в файле не осталось ни одного. Обращений к глобальным буферам — тоже.

Вывод не менялся: те же форматы, та же ширина {:<30}, те же цветовые коды.

Проверено

Сборка чистая, 673 теста зелёные, малый мир поднимается. Проверять на живом — команда кто во всех видах: кто, кто -s, кто -n имя, кто -c профессия, кто боги, кто 20-30.

Дальше

По плану #3807 следующие кандидаты, от простого к сложному: do_set.cpp / do_set_all.cpp (61/62 обращения), corpse.cpp (52), named_stuff.cpp (85), punishments.cpp (98), identify.cpp (149), do_score.cpp (137), do_show.cpp (97), do_stat.cpp (350), dg_scripts.cpp (479).

🤖 Generated with Claude Code

https://claude.ai/code/session_01XUwDWDnYdXrJdvjDVd36QH


Правка после ревью (второй коммит)

Первая версия заменила глобальные буферы локальными char[] — и это было неверно дважды.

Неверно по существу. half_chop копирует остаток через strl_cpy(arg2, temp, kMaxStringLength), то есть рассчитывает на буфер в 32768 байт, а я передавал буферы по kMaxInputLength = 2048. Сегодня это не стреляет только потому, что и строка команды, и строка триггера ограничены теми же 2048 байтами (kMaxTrglineLength == kMaxInputLength). Любое изменение лимита — и запись уходит за границу стекового массива.

Не то по замыслу. Слой 1 из #3807 — про переход на std::string, а не про перенос тех же char[] из глобальной области в локальную. И это прямо расходилось с правилом, которое только что легло в CONTRIBUTING.md (#3808).

Теперь фиксированных буферов в файле не осталось вовсе.

Заодно починилось «кто боги»

if (!str_cmp(arg, "боги") && strlen(arg) == 4)

В KOI8-R «боги» занимало ровно 4 байта, в UTF-8 — 8. После перехода вторая половина условия перестала выполняться, и слово уходило в поиск по имени: кто боги искал игрока с именем «боги» вместо списка бессмертных.

Проверка длины тут и не нужна — str_cmp сравнивает строки целиком, а не по префиксу, так что условие сократилось до token == "боги" (слово уже приведено к нижнему регистру).

Отдельно проверить в игре стоит именно кто боги.


Третий коммит: ширина колонки в коротком списке

В кто -s имя сначала оборачивали в цвет, а потом дополняли до тридцати знаков:

tmp = цвет + имя + сброс;
fmt::format("{:<30}", tmp)

Цветовой код — семь невидимых символов (\x1B[1;31m), сброс — ещё семь, и fmt считает их наравне с буквами. В колонке оставалось 16 видимых знаков вместо тридцати:

было : видимых знаков в колонке = 16
стало: видимых знаков в колонке = 30

Теперь ширина добирается внутри цветов — fmt::format("{}{:<30}{}", цвет, имя, сброс).

Заодно снят устаревший комментарий, утверждавший, что fmt меряет ширину в байтах. Это было верно, пока русский текст лежал в KOI8-R: такая строка не является валидным UTF-8, и fmt считал байты. После флипа он считает символы — проверено замером.


Четвёртый коммит: опереться на готовое

Свой разбор строки оказался лишним — строковый аналог half_chop в проекте уже есть: utils::ExtractFirstArgument (utils_string.h:150, прямо подписан «аналог one_argument для string»).

Локальный ChopWord остался тонкой обёрткой над ним и добавляет ровно то, чего тот не делает, а half_chop делал: понижает регистр слова (сравнения в разборе на это рассчитывают) и срезает ведущие пробелы в остатке.

Первый файл слоя 1. Команда собирала весь вывод в глобальных buf, buf1 и arg --
общих на весь процесс. Любой вызов посреди сборки строки затирал накопленное, а
переполнение ловилось только сторожевым байтом, уже постфактум.

Разбор аргументов ведёт свои локальные буферы, строка игрока и итоговые счётчики
собираются через fmt::format в std::string. sprintf и strcat в файле не осталось
ни одного.

Заодно ушла лишняя пересылка: строка игрока раньше писалась в глобальный buf
через strcpy(buf, fmt::format(...).c_str()), теперь fmt::format пишет прямо в
свою строку.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUwDWDnYdXrJdvjDVd36QH
bylins and others added 2 commits August 29, 2026 17:23
Первая версия правки заменила глобальные буферы локальными char[] -- это было и
неверно, и не то, что задумано.

Неверно: half_chop копирует остаток строки через strl_cpy с оглядкой на
kMaxStringLength, то есть до 32768 байт, а буферы были по kMaxInputLength =
2048. Сегодня это не стреляет только потому, что строка команды и строка
триггера ограничены теми же 2048 байтами; любое изменение лимита -- и запись
уходит за границу стекового массива.

Не то: слой 1 из #3807 -- про переход на std::string, а не про перенос тех же
char[] из глобальной области в локальную.

Теперь разбор ведёт локальный помощник ChopWord: первое слово в нижнем регистре
и остаток, обе части -- std::string. Фиксированных буферов в файле не осталось.

Заодно чинится "кто боги": условие было

    if (!str_cmp(arg, "боги") && strlen(arg) == 4)

В KOI8-R "боги" занимало ровно 4 байта, в UTF-8 -- 8, поэтому вторая половина
условия перестала выполняться, и слово уходило в поиск по имени. Проверка длины
тут вообще лишняя: str_cmp сравнивает строки целиком, а не по префиксу.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUwDWDnYdXrJdvjDVd36QH
В коротком списке ("кто -s") имя сначала оборачивали в цвет, а потом дополняли
до тридцати знаков:

    tmp = цвет + имя + сброс
    "{:<30}" по tmp

Цветовой код -- семь невидимых символов (\x1B[1;31m), сброс -- ещё семь. fmt
считает их наравне с буквами, поэтому в колонке оставалось 16 видимых знаков
вместо тридцати. Проверено: было 16, стало 30.

Теперь ширина добирается внутри цветов: fmt::format("{}{:<30}{}", цвет, имя,
сброс). Колонка стала той ширины, что задумана.

Заодно снят устаревший комментарий про то, что fmt считает ширину в байтах: это
было верно, пока русский текст лежал в KOI8-R и не был валидным UTF-8. Сейчас
fmt считает символы.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUwDWDnYdXrJdvjDVd36QH
Свой разбор строки был лишним: строковый аналог half_chop в проекте уже есть --
utils::ExtractFirstArgument (utils_string.h:150). Локальный ChopWord остался
тонкой обёрткой над ним и добавляет только то, чего тот не делает, а half_chop
делал: понижает регистр слова (сравнения в разборе на него рассчитывают) и
срезает ведущие пробелы в остатке.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XUwDWDnYdXrJdvjDVd36QH
@bylins

bylins commented Aug 29, 2026

Copy link
Copy Markdown
Owner Author

Собрано вместе с остальными в #3813 — там все три темы одним PR, потому что ExtractFirstArgument из третьего нужен первым двум. Этот закрываю.

@bylins bylins closed this Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant