Лучшие практики

Dragokas

Меценат
Сообщения
184
Реакции
145
9. Лучшие практики:

  • Используйте RemoveEntity(entity);SM 1.10+), которая является быстрой и безопасной версией AcceptEntityInput(entity, "Kill");SM <= 1.9) по сравнению с RemoveEdict(entity).
    - RemoveEdict в некоторых случаях может быть безопасной и хорошей для создания + удаления сущности в одной и той же функции, когда эта сущность вам едва ли потребуется в дальнейшем, например point_hurt и env_shake.

  • Перед использованием или убийством сущности убедитесь, что она действительна с помощью IsValidEntity();
    - Обязательно проверяйте на ноль if(entity != 0) или просто if(entity), потому что 0 (на выделенном сервере) - это сущность worldspawn и является действительной, таким образом вы можете мгновенно обвалить сервер.

  • Не имеет смысла в использовании if IsClientConnected(client) && IsClientInGame(client) одновременно, поскольку IsClientInGame уже включает проверку IsClientConnected.

  • Используйте единый стиль именования переменных. Хорошее начало - "Венгерская нотация"
    - например, предваряя имя переменной префиксом g_ в случае, когда она объявляется как глобальная.

  • Используйте TIMER_FLAG_NO_MAPCHANGE для закрытия хендлов таймера автоматически по завершению карты. Детальнее с примерами см. дополнение "Как остановить (удалить) глобальный таймер".

  • !!iValue - для преобразования "int" в "bool"

  • Лично я объявляю переменные типа "char" как "static char", если они большие и вызываются несколько раз в дорогостоящих циклах или в Think* функциях.
    • Это оптимизирует скорость, потому что для такой переменной не требуется повторное выделение памяти. НО: Эта память никогда не уничтожается и навсегда остаётся там.
    • Используйте это с умом, т.е. только, когда вы действительно понимаете, что делаете. Это на самом деле даёт выигрыш для дорогостоящих в плане производительности функций и серверах с высоким тиком и 50-100+ сумасшедшими плагинами, где оптимизация становится просто необходима.
    • Однако: "использование static для локальных массивов - не является хорошим советом, это приводит к множеству багов, в тоже время выделение памяти в стеке - дешёвая операция." - asherkin.

  • Если вы желаете присвоить одно и тоже значение всем элементам массива, вы можете это сделать прямо во время инициализации, а не через цикл позднее: int test[32] = {1, ...};
    - В противном случае int test[32] заполняется значениями по умолчанию, в данном случае 0.

  • TeleportEntity должен идти перед DispatchSpawn. Если сделать в обратном порядке, это может привести к крашу сервера. Такое случается с некоторыми типами моделей/сущностей, которые спавнятся в координатах 0,0,0.

  • switch вместо множества условий if else. Это вероятно ещё и быстрее. Больше информации здесь.
    - case секция использует константные значения. Она также поддерживает множественные значения, например case 1,2:

  • c[0] == 'a' вместо полного сравнения строки, к примеру, когда вы проверяете список известных моделей игроков. Найдите индекс в строке, где для каждой из них знак будет уникальным. Можно использовать совместно со switch().
    - Хороший пример от Lux можно найти здесь.

  • strcmp вместо StrEqual. Последнее - просто обёртка от первого и будет работать медленнее из-за создания дополнительного вызова.

  • FormatEx вместо Format. Используйте только, когда входящий и выходной буферы НЕ одинаковые.

  • FormatEx(blah, sizeof blah, "%d", i); - слегка быстрее, чем IntToString(i, blah, sizeof blah); но в целом, сравнения не делал, так что используйте любую.
    - Однако, обе они в 3 - 10x раз быстрее, чем Format(blah, sizeof blah, "%d", i);

  • StringMap.GetValue слегка быстрее, чем ArrayList.FindString и вероятно значительно быстрее на огромных массивах.

  • Предпочитайте SDKHook_Think и похожие функции вместо использования OnGameFrame.

  • Создавайте один таймер для всех игроков вместо отдельного таймера для каждого из них (например: плагины для регенерации здоровья).

  • delete и null являются предпочтительней CloseHandle и INVALID_HANDLE. delete также устанавливает хендл равным null, таким образом вам не нужно заботится об этом.

  • Слежение за кварами, передавая их хендл в HookConVarChange и хранение возвращаемого значения в глобальные переменные - является более оптимальным по скорости, чем постоянный вызов GetConvar*.
    - Посмотрите на любой из моих плагинов для примера.

  • Используйте %N вместо GetClientName() где это возможно, например, при использовании Format.

  • Поздняя загрузка - Больше инфы здесь.
    • Поздняя загрузка (Late loading) - это когда плагин был вероятно загружен вручную во время игры, или перезагружен.
    • Из-за этого, плагин может начать работать не так, как было предусмотрено, потому что сработают не все события.
    • Вы можете определить, что плагин загружен как поздний и соответственно настроить всё, как положено при нормальной загрузке.
    PHP:
    bool g_bLateLoad;
    public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
    {
        g_bLateLoad = late;
        return APLRes_Success;
    }
    
    public void OnPluginStart()
    {
        if( g_bLateLoad )
        {
            for( int i = 1; i <= MaxClients; i++; )
            {
                if( IsClientInGame(i) )
                {
                    // Делаем что-либо специфическое для клиентов, которые уже находятся в игре, если они подключились до того, как плагин был загружен.
                    OnClientPutInServer(i);
                }
            }
        }
    }
    
    public void OnClientPutInServer(int client)
    {
        // Действия
    }

  • Выгрузка плагинов
    • Я советую также поддерживать выгрузку. Это значит удалять сущности, которые ваш плагин создавал или восстанавливать пропатченную память и т.д.
    • Это особенно полезно при тестировании вашего плагина в процессе написания, когда вы постоянно его перезагружаете, например, через плагин AutoReload.
    • Это также предотвратит случаи, когда прикреплённые модели застревают на игроках, потому что они больше не отслеживаются, но и не удаляются.

  • Поддержка Listen серверов- Listen сервера - это когда игровой клиент одновременно является сервером (хостом).
    • Выделенные сервера ВСЕГДА советуют использовать вместо Listen.
    • Индекс 0 на выделенном сервере - это сущность "World". На Listen серверах клиентский индекс через консоль также 0, что создаёт проблемы с командами, используемыми через консоль.
    • Выполнение команд через консоль, которые ожидают получения индекса клиента, часто приводит к ошибкам.
    • Некоторые функции SourceMod не поддерживают listen сервера и ожидают, что индекс клиента будет > 0. Ещё одна причина использовать Выделенные сервера.
    • Некоторый функционал SourceMod также не работает на Listen серверах, к примеру SendConVarValue когда я её тестировал.
    - Просто намотайте на ус: Не используйте Listen сервера!
 
Последнее редактирование модератором:

Kruzya

Здравствуй, юность в сапогах
Меценат
Сообщения
10,712
Реакции
8,862
StringMap.GetValue слегка быстрее, чем ArrayList.FindString и вероятно значительно быстрее на огромных массивах.
Поскольку под капотом используется хеш-таблица. Вместо того, чтобы хранить оригинальную строку-"ключ", хранится хеш от неё.
Отсюда следует понимать так же, что возможны коллизии, и в каких-то сверх-специфических кейсах можно словить ситуацию, когда записал в мапу значение с одним ключом, и возможно это самое значение получить по другому ключу.

delete и null являются предпочтительней CloseHandle и INVALID_HANDLE. delete также устанавливает хендл равным null, таким образом вам не нужно заботится об этом.
ИМХО, вот это - полная вкусовщина.
Я любитель "старой школы", и не люблю неявное поведение. Для меня очень долгое время был не очевидным тот момент, что delete так же выполняет приравнивание переменной к null.

Потому по возможности предпочитаю использовать связку handle.Close() (скрытый метод у методмапы Handle, реализован на уровне компилятора и является тупо алиасом к CloseHandle()) и явное присванивание переменной значения null.
 

Grey83

Ленивая и невнимательная жопа
Сообщения
4,906
Реакции
2,884
Я любитель "старой школы", и не люблю неявное поведение. Для меня очень долгое время был не очевидным тот момент, что delete так же выполняет приравнивание переменной к null.

Потому по возможности предпочитаю использовать связку handle.Close() (скрытый метод у методмапы Handle, реализован на уровне компилятора и является тупо алиасом к CloseHandle()) и явное присванивание переменной значения null.
Я когда-то давно (после того как встретил delete) даже написал тестовый плагин, чтобы сравнить способы закрытия хэндлов.
Тогда и узнал, что delete приравнивает значение хэндла к null.
 

MAGNAT2645

Участник
Сообщения
64
Реакции
7
Потому по возможности предпочитаю использовать связку handle.Close() (скрытый метод у методмапы Handle, реализован на уровне компилятора и является тупо алиасом к CloseHandle()) и явное присванивание переменной значения null.
Тоже использую Handle.Close(), но только для локальных переменных т.к. не уверен: обнуляются только глобальные переменные или все?
Например, в MenuAction_End не использую delete просто потому, что menu после удаления, возможно, присваивается null (это надо проверять на практике), а в данном случае это лишняя операция.

В общем, использую delete только для глобальных дескрипторов.
 
Сверху