[SourcePawn] Урок 16 - Отладка (Debug)

R1KO

fuck society
Команда форума
Сообщения
8,997
Реакции
6,753
[SourcePawn] Урок 16 - Отладка (Debug)

<- К содержанию

Каждый рано или поздно сталкивается с проблемами в плагине, которые сложно сразу разглядеть или понять в чем они кроются. В таких случаях на помощь приходит отладка. Очень часто когда советую кому-то использовать отладку/дебаг, в ответ слышу "что это?"/ "как это?". Так вот ...

Отладка (Debug) - вывод промежуточных значений переменных, а так же результатов условий и вычислений и просто брейк-поинтов (контрольные точки). Это помогает понять на каком этапе (или в какой момент) появляется проблема и приближает вас к её решению т.к. чтобы решить проблему - нужно её найти.

Очень простыми словами - вывод информации в чат и/или лог чтобы понять в какой момент чему равна какая-то переменная и понять какое из условий проходит (истина) или нет (ложь).

Основные инструменты которыми удобно пользоваться при отладке:
  • PrintToChat - Используем для вывода информации в чат для одного игрока. Удобнее всего использовать когда нужно прямо во время игры видеть отладочную информацию.
    Например события происходящие с конкретным игроком (player_spawn, player_death, player_team, player_say, item_pickup) лучше выводить только ему
  • PrintToChatAll - Тоже самое что и PrintToChat но для всех.
    Например события общие для всех игроков (round_start, round_end, server_cvar, bomb_planted), которые происходят одновременно для всех игроков
  • PrintCenterText/PrintHintText/PrintCenterTextAll/PrintHintTextToAll - Удобны при выводе значений, которые часто изменяются. Особенно значений, которые интересуют не столько численно, сколько динамика их изменений.
    Например значение текущей скорости, координат, времени игры (GetGameTime()), в OnGameFrame, OnPlayerRunCmd, таймеры (повторяющиеся)
  • PrintToConsole - Вывод в консоль игроку. Например чтобы не засорять чат либо для предотвращения потери нужной информации в потоке другой.
  • PrintToServer - Вывод в консоль сервера. Используем только когда есть возможность в реальном времени смотреть в консоль сервера и уверены что сможем найти нужное сообщение.
  • LogMessage - Вывод в лог файл sourcemod`а (addons/sourcemod/logs/LГодМесяцЧисло.log (Например: L20160913.log)). Очень рекомендую использовать его. Т.к. в любой момент можно найти любые сообщения за любой период времени.
  • LogToFile - Тоже самое что и LogMessage но выводит сообщение в указанный файл.
  • LogToFileEx - Тоже самое что и LogToFile но не выводит имя плагина перед сообщением.
  • LogError - Похоже на LogMessage но выводит информацию в лог ошибок sourcemod`а (addons/sourcemod/logs/errors_ГодМесяцЧисло.log (Например: errors_20160913.log)).
  • LogToOpenFile/LogToOpenFileEx - Аналогично LogToFile/LogToFileEx но выводит в уже открытый файл по переданному дескриптору (Handle).

Советую при выводе строк окружать их какими-то символами с обеих сторон. А то в начале или конце может оказаться пробел, и его сложно будет заметить (а иногда это критично).

Так же можно создавать временные консольные команды (временные потому что удалим их после решения проблемы), которые будут выводить нужную информацию или же вызывать необходимые функции.

Для того чтобы постоянно не добавлять/удалять отладку её можно комментировать и раскомментировать.
Но лучше для этого использовать макросы, о которых будет отдельный урок.

Ну и небольшой пример.

Допустим есть у нас такой код (отрывок плагина):
PHP:
// Достоверно известно что проблема именно в ф-и OnChatMessage. Так что добавим отладку только для неё
#include <sourcemod>
#include <scp>

public Action:OnChatMessage(&author, Handle:recipients, String:name[], String:message[])
{
    // Оговорюсь сразу. Эта ф-я вызывается во время отправки сообщения игроком. Отправлять одно сообщение в чат во время отправки другого - нельзя. Мы получим ошибку.
    // Рациональнее всего будет использовать вывод в лог. Выберем самый простой LogMessage.
    // Вот первая проверка. Чтобы узнать проходит ли она есть способа:
    //    1. Вывести сообщение после проверки
    //    2. Вывести до проверки её результат
    //    3. Вывести до проверки проверяемые в ней значения
    // Разберем каждый из них:
    //    1. будет выглядеть так
    //        if (g_aPlayers[author] != INVALID_HANDLE)
    //        {
    //            LogMessage("g_aPlayers != INVALID_HANDLE"); // Выводим что угодно, лишь бы было ясно что проверка пройдена
    //            ...
    //        }
    //  
    //    2. будет выглядеть так
    //        LogMessage("(g_aPlayers != INVALID_HANDLE) = %b", (g_aPlayers[author] != INVALID_HANDLE));
    //        if (g_aPlayers[author] != INVALID_HANDLE)
    //        {
    //            ...
    //        }
    //    3. будет выглядеть так
    //        LogMessage("g_aPlayers = %d (%x)", g_aPlayers[author], g_aPlayers[author]);
    //        if (g_aPlayers[author] != INVALID_HANDLE)
    //        {
    //            ...
    //        }
    // Ну для примера воспользуемся самым простым

    if (g_aPlayers[author] != INVALID_HANDLE)
    {
        LogMessage("g_aPlayers != INVALID_HANDLE");
      
        // Теперь выведем исходные данные
      
        LogMessage("name = '%s'", name);
        LogMessage("message = '%s'", message);

        new index = CHATCOLOR_NOSUBJECT;
        decl String:sNameBuffer[MAXLENGTH_NAME], String:sTagBuffer[32];
        Color_StripFromChatText(name, sNameBuffer, MAXLENGTH_NAME);
        LogMessage("Color_StripFromChatText = '%s'", sNameBuffer); // Выводиим полученное значение
  
        decl String:ColorCodes[3][12];
        if (GetTrieString(g_aPlayers[author], "namecolor", ColorCodes[0], sizeof(ColorCodes[])))
        {
            LogMessage("GetTrieString(namecolor) = '%s'", ColorCodes[0]);
            Format(sNameBuffer, sizeof(sNameBuffer), "%s%s", ColorCodes[0], sNameBuffer);
            LogMessage("sNameBuffer = '%s'", sNameBuffer);
        }
        else
        {
            Format(sNameBuffer, sizeof(sNameBuffer), "\x03%s", sNameBuffer);
        }
      
        if (GetTrieString(g_aPlayers[author], "tag", sTagBuffer, sizeof(sTagBuffer)))
        {
            LogMessage("GetTrieString(tag) = '%s'", sTagBuffer);
            Format(sNameBuffer, sizeof(sNameBuffer), "%s%s", sTagBuffer, sNameBuffer);
            LogMessage("sNameBuffer = '%s'", sNameBuffer);

            if (GetTrieString(g_aPlayers[author], "tagcolor", ColorCodes[1], sizeof(ColorCodes[])))
            {
                LogMessage("GetTrieString(tagcolor) = '%s'", ColorCodes[1]);
                Format(sNameBuffer, sizeof(sNameBuffer), "%s%s", ColorCodes[1], sNameBuffer);
                LogMessage("sNameBuffer = '%s'", sNameBuffer);
            }
        }

        LogMessage("sNameBuffer = '%s'", sNameBuffer);

        LogMessage("StrContains(%s, {T}) = %d", sNameBuffer, StrContains(sNameBuffer, "{T}"));

        if (StrContains(sNameBuffer, "{T}") != -1)
        {
            Color_ChatSetSubject(author);
        }
        index = Color_ParseChatText(sNameBuffer, name, MAXLENGTH_NAME);

        LogMessage("index = %d", index);
        Color_ChatClearSubject();
      
        if (GetTrieString(g_aPlayers[author], "textcolor", ColorCodes[2], sizeof(ColorCodes[])))
        {
            LogMessage("GetTrieString(textcolor) = '%s'", ColorCodes[2]);
            decl String:sMessageBuffer[MAXLENGTH_INPUT];
            Format(sMessageBuffer, sizeof(sMessageBuffer), "%s%s", ColorCodes[2], message);  
            LogMessage("sMessageBuffer = '%s'", sMessageBuffer);
            if (index == CHATCOLOR_NOSUBJECT)
            {
                index = Color_ParseChatText(sMessageBuffer, message, MAXLENGTH_INPUT);
                LogMessage("index = %d", index);
            }
            else
            {
                Color_ChatSetSubject(index)
                Color_ParseChatText(sMessageBuffer, message, MAXLENGTH_INPUT);
                Color_ChatClearSubject();
            }
        }
      
        if (index != CHATCOLOR_NOSUBJECT)
        {
            author = index;
        }

        return Plugin_Changed;
    }
    return Plugin_Continue;
}

В примере очень расширенная отладка. Чаще всего это не требуется, а достаточно нескольких отладочных сообщений. Подробность отладки зависит от проблемы и кол-ва кода.
 
Последнее редактирование:

Nekro

Терра инкогнита
Сообщения
1,557
Реакции
562
@R1KO, При использовании LogToFileEx в логи идёт дата "L 01/13/2021 - 18:34:59:" - "сам текст лога", а есть возможность отключить эту дату (желательно оставив время)?
 
Сверху