Форум Pawn.Wiki - Воплоти мечту в реальность!: Как разбить мод на инклуды - Форум Pawn.Wiki - Воплоти мечту в реальность!

Перейти к содержимому

Страница 1 из 1
  • Вы не можете создать новую тему
  • Вы не можете ответить в тему

[ Урок ]
Как разбить мод на инклуды модульность
Оценка: ***** 1 Голосов

#1
Пользователь офлайн   M I S T E V 

  • Вставить ник
  • Раскрыть информацию
Данная тема посвящается людям, которые не до конца понимают, что значит "разбивать" мод на инклуды. Многие воспринимают это, как вырезать некоторые участки кода из основного файла (gamemode.pwn) и скопировать в отдельные файлы (name.inc). Этот метод нельзя назвать хорошим, скорее, плохой. Подобная разбивка кода имеет логику "вот тут много строк, перенесу их в отдельный файл, чтобы в моде было меньше строк". Только вот при дальнейшей разработке это запутывает и заставляет держать открытыми кучу файлов и переключаться между ними, всегда помнить что за чем следует и какая область видимости у каждой переменной, функции и т.д.

Постараюсь объяснить, как можно сделать лучше и что вообще нужно понимать под "модульностью", а также, как всё это подружить с модом. Имею ввиду, что "модуль" - это полностью автономная или частично автономная система, которая имеет свои API функции для взаимодействия, помещённая в отдельный/отдельные инклуды.

Главный принцип - всё взаимодействие (обработка) происходит в моде, а рассчёты выполняются в инклудах.

Для начала, необходимо определить, какие системы будут использоваться. Далее, необходимо разделить эти системы на два типа: самостоятельная и зависимая. Самостоятельная - используется отдельно от других систем. Зависимая - используется совместно с другими системами. Подключать инклуды в моде нужно в порядке - сначала самостоятельные системы, а только потом зависимые.

Как понять, какая система к какому типу относится?

Система аккаунтов - самая важная на сервере, без которой невозможно представить игру на нём. Данная система взаимосвязана практически со всеми остальными системами. Важно понять, что мы используем функции аккаунта в других системах, а не внутри аккаунта используем функции других систем. Соответственно, это самостоятельная система (модуль). Что она в себя включает? Например:

CheckPlayerAccount(playerid) - проверить наличие аккаунта (зарегистрирован или нет)

GetPlayerAccountID(playerid) - получить ID аккаунта

GetPlayerAccountName(playerid) - получить имя аккаунта (игрока) 

Разберём подробнее функцию CheckPlayerAccount. Где её применять и что она делает? Применяем мы её в OnPlayerConnect. В ней мы отправляем SELECT запрос в базу данных, чтобы найти информацию игрока по его имени. Сразу вопрос - где брать и хранить имя? Можно брать имя в OnPlayerConnect, но что такое имя игрока? Разве это не относится к информации аккаунта? Относится. Соответственно, зачем нам использовать функцию GetPlayerName в OnPlayerConnect, когда логичнее её переместить в CheckPlayerAccount.

[gamemode.pwn]

public OnPlayerConnect(playerid)
{
    CheckPlayerAccount(playerid);

    return 1;
}

[account.inc]

new account_name[MAX_PLAYERS][MAX_PLAYER_NAME];

#define GetPlayerAccountName(%0) account_name[%0]


stock CheckPlayerAccount(playerid)
{
    GetPlayerName(playerid, account_name[playerid], MAX_PLAYER_NAME);

    static const string[] = "SELECT * FROM `accounts` WHERE `name` = '%e' LIMIT 1;";
    new query[szeof string + MAX_PLAYER_NAME];

    mysql_format(db, query, sizeof query, string, account_name[playerid]);
    mysql_tquery(db, query, "OnCheckPlayerAccount", "i", playerid);

   return 1;
}

Пару слов про массив account_name и макрос GetPlayerAccountName. Так как мы пишем отдельную независимую систему, было бы хорошо, чтобы у неё были свои API функции и переменные/массивы для хранения информации. Делать отдельную функцию, которая будет возвращать строку - не лучшая практика. Поэтому мы объявляем массив account_name через оператор new, чтобы его можно было вызывать из других инклудов. Далее мы создаём макрос, который называется "как функция", а именно GetPlayerAccountName. Зачем создавать макросы/функции, когда можно вызывать переменную/массив напрямую? В первую очередь, чтобы не дать совершить самим себе ошибки в будущем, а также увеличиваем читаемость кода и не позволяем "как-то не так" использовать переменные/массивы. Ещё одно удобство заключается в том, что все API функции будут иметь свой "корень" в названии функции, например Account, давая нам понять к какой системе относится данная функция. Важно отметить, что создавать отдельные функции/макросы для использования той или иной переменной - не правило, а лишь рекомендация. Бывает, что рациональнее использовать что-то напрямую.

Отправив запрос, мы ожидаем результат в функции OnCheckPlayerAccount. Теперь нужно определить, где эта функции должна создаваться - в моде или в инклуде? В данном случае, мы создаём её в инклуде.

Добавим после массива account_name:

[account.inc]

static enum E_ACCOUNT_INFO
{
    account_id,
}

static account_info[MAX_PLAYERS][E_ACCOUNT_INFO];

Добавим после макроса GetPlayerAccountName:

[account.inc]

stock GetPlayerAccountID(playerid)
{
    return account_info[playerid][account_id];
}

Теперь создаём функцию OnCheckPlayerAccount:

[account.inc]

forward OnCheckPlayerAccount(playerid);
public OnCheckPlayerAccount(playerid)
{
    if(cache_num_rows())
    {
        cache_get_value_name_int(0, "pID", account_info[playerid][account_id]);

        // загружаем всю остальную информацию

        return OnPlayerConnected(playerid, true);
    }
    else
        return OnPlayerConnected(playerid, false);
}

Что это за функция OnPlayerConnected(playerid, bool:status), где её использовать и что за параметр мы передаём? Данная функция указывает нам, что мы обратились в базу данных и получили ответ для данного игрока. Параметр status говорит нам о том, зарегистрирован ли игрок или нет. Если какая-либо информация найдена - загружаем и вызываем OnPlayerConnected с параметром true, иначе с параметром false. Использование данной функции должно быть в gamemode.pwn, но объявление её через forward должно быть в инклуде.

[account.inc]

forward OnPlayerConnected(playerid, bool:status);

[gamemode.pwn]

public OnPlayerConnect(playerid)
{
    CheckPlayerAccount(playerid);

    return 1;
}

public OnPlayerConnected(playerid, bool:status)
{
    if(status)
        // авторизация
    else
        // регистрация

    return 1;
}

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

Изображение
Касаемо зависимых систем, например фракции. Алгоритм действий похож, только мы допускаем, что в этой системе мы будем использовать функции из других систем, например система аккаунта, получая ID или имя игрока для дальнейших действий.

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

Также стоит поговорить о более сложных системах, где используется более одного инклуда. В таком случае, создаём отдельную папку и уже в ней располагаем необходимые инклуды. Допустим, у нас есть система инвентаря. В ней необходимо хранить основные данные игрока, данные для визуальной части, а так же сами функции взаимодействия. Создаём папку inventory, в ней создаём инклуды core и ui. В ui инклуде будет находиться всё, что необходимо для отрисовки визуальной части, а в core инклуде основные данные и функции взаимодействия с игроком.

При подключении в моде этих двух инклудов так:

#include "../modules/inventory/core.inc"
#include "../modules/inventory/ui.inc"

У нас возникнет проблема, что в инклуде core мы не можем обратиться к функциям из ui инклуда.

При подключении так:

#include "../modules/inventory/ui.inc"
#include "../modules/inventory/core.inc"

У нас возникнет проблема, что теперь в ui инклуде невозможно получить данные из core инклуда.

Решение? Можно объединить всё в один инклуд, чтобы все необходимые константы и функции были видны друг другу, но мы поступим иначе. Можно подключить ui инклуд внутри core инклуда.

[core.inc]

// создание всех необходимых макросов, констант, массивов и переменных

#include "../modules/inventory/ui.inc"

// созданием функций для взаимодействия с игроком

В моде оставляем подключение только core инклуда. Таким образом мы "скрыли" подключение одного из инклудов системы внутри другого (основного) и нет проблемы с областью видимости.

Дополню ещё, что все переменные/функции, которые используются только внутри инклуда, нужно создавать с помощью static, чтобы лишний раз их нельзя было вызывать где-то ещё, помимо этого инклуда. Также можно делать приставку к названию в виде нижнего подчёркивания "_" вначале, чтобы явно указывать, что функция не распространяется для других инклудов, так вместо OnCheckPlayerAccount можно использовать _OnCheckPlayerAccount.

В случае, если у нас многосоставная система, на подобии инвентаря, и нам необходимо создать функцию, которая должна быть видна во всех подключаемых инклудах, но не видна в моде, мы не можем создать функцию с помощью static, так как она не будет видна в подключаемых инклудах. Есть обходной путь:

[core.inc]

stock TestFunction()
{
    return 1;
}

// в конце core.inc
#define TestFunction _TestFunction

Саму функцию мы делаем глобальной, но потом "скрываем" её. Так как макрос создаётся в самом конце инклуда, он начинает действовать для мода, а не для самого инклуда. При этом макрос имеет приоритет в вызове, по сравнению с функцией. Поэтому с помощью данного макроса мы перенаправляем вызов функции на несуществующую функцию _TestFunction. Использовав функцию TestFunction в моде, мы получим ошибку, что такая функция не существует.

Ещё дополню, что есть способ использовать одинаковое название для функций в разных инклудах, которые невозможно вызывать в моде. Маловероятно, что это кому-то пригодится, но вот пример:

[core_1.inc]

stock TestFunction()
{
    return 1;
}

// в конце core_1.inc
static stock PREFIX1_Test(){}

#if defined _ALS_TestFunction
    #undef TestFunction
#else
    #define _ALS_TestFunction
#endif
#define TestFunction PREFIX1_TestFunction

[core_2.inc]

stock TestFunction()
{
    return -1;
}

// в конце core_2.inc
static stock PREFIX2_Test(){}

#if defined _ALS_TestFunction
    #undef TestFunction
#else
    #define _ALS_TestFunction
#endif
#define TestFunction PREFIX2_TestFunction

Лично от себя рекомендую писать комментарии к собственным системам, чтобы в дальнейшем чётко понимать, что можно использовать в моде от этой системы. Я делаю так, перед подключением, вставляю подсказку:

/*
	Name_... -> function
	NAME_... -> define / const
	name_... -> var
	
	# -> define
	* -> const
	@ -> callback
	i -> iterator
	e -> enum
	p -> pvar
	s -> svar
	- -> new
	> -> function

	сmd -> command
*/

Ниже подключаю инклуды подобным образом:

#include "../modules/account.inc"
/*
    # INVALID_ACCOUNT_ID (-1)
    # GetPlayerAccountName(%0) (account_name[%0])
    # GetPlayerAccountPassword(%0) (account_password[%0])

    * LEN_ACCOUNT_NAME (MAX_PLAYER_NAME)
    * SIZE_ACCOUNT_NAME (LEN_ACCOUNT_NAME+1)
    * LEN_ACCOUNT_PASSWORD (64)
    * SIZE_ACCOUNT_PASSWORD (LEN_ACCOUNT_PASSWORD+1)

    @ OnPlayerConnected(playerid, bool:status)

    i Account

    - account_name[MAX_PLAYERS][SIZE_ACCOUNT_NAME]
    - account_password[MAX_PLAYERS][SIZE_ACCOUNT_PASSWORD]

    > CheckPlayerAccount(playerid)
    > GetPlayerAccountID(playerid)
*/

Сообщение отредактировал M I S T E V: 24 ноября 2025 - 10:02

8

#2
Пользователь офлайн   Romzes 

  • ( ^_^ )
  • Вставить ник
  • Раскрыть информацию
Очень полезно :yes:
0

#3
Пользователь офлайн   Saibot 

  • Эксперт
  • Вставить ник
  • Раскрыть информацию
И не лень же, это все писать и рисовать.
0

#4
Пользователь офлайн   Mateusz 

  • Новичок
  • Вставить ник
  • Раскрыть информацию
не чота не хочу спс
но урок зачотный, броу уважуха
0

#5
Пользователь офлайн   plakapenka 

  • Местный
  • Вставить ник
  • Раскрыть информацию
только зачем модулю давать расширение .inc? а не .pwn
0

#6
Пользователь офлайн   M I S T E V 

  • Вставить ник
  • Раскрыть информацию

Просмотр сообщенияplakapenka (11 декабря 2025 - 14:52) писал:

только зачем модулю давать расширение .inc? а не .pwn

Всё такие inc -> include, подразумевается, что файл включён в другой. В принципе, расширение файла не имеет важного смысла

Сообщение отредактировал M I S T E V: 11 декабря 2025 - 17:36

1

#7
Пользователь офлайн   Retrace 

  • Местный
  • Вставить ник
  • Раскрыть информацию

Просмотр сообщенияM I S T E V (11 декабря 2025 - 17:35) писал:

Всё такие inc -> include, подразумевается, что файл включён в другой. В принципе, расширение файла не имеет важного смысла

Дополню, максимум ещё может потребоваться для корректности подсветки синтаксиса в редакторах текса/кода.
2

Поделиться темой:


Страница 1 из 1
  • Вы не можете создать новую тему
  • Вы не можете ответить в тему

1 человек читают эту тему
0 пользователей, 1 гостей, 0 скрытых пользователей


Яндекс.Метрика