[Tutorial] Архитектура проекта
#1

Д0брого времени суток.
В данном туториале я бы хотел рассказать об одном методе как можно устраивать внутреннюю часть вашего SA-MP проекта. Как известно большая часть скриптеров хранят свои режимы всего в одном .pwn файле. На самом деле, это очень не правильное решение. Потому что, при увеличении количества кода проекта, его сложность в дальнейшей модификации значительно усложняется. Чтобы избежать значительного усложнения проекта при его модификации, вам достаточно правильно спроектировать архитектуру вашего проекта. Многие кто разбивают мод по инклудам сталкиваются с проблемой, связать все части в одну систему. И тут начинается подключение инклудов в строгом прядке, переброс функций, создание промежуточных инклудов а иногда вовсе скриптеры входят в тупик. Как же избежать таких мучений и тупиков? - спросите вы, для этого в других языках программирования существуют:
Заголовочные файлы Советую почитать тем, кто не знаком с ними, что бы в дальнейшем не было недопонимания.

Внимание! Если вы пользуетесь Pawno, то ниже предоставленный метод работать не будет. Данный метод работает на Notepad++
О том, как настроить Notepad++ вы узнаете тут: PAWN for SA-MP in Notepad++


>>Часть Первая

Для начала ознакомлю Вас с методом проектирования архитектуры.
Давайте создадим небольшой проект. Создадим папочку TestProject в любом удобном для вас месте.
В данной папочке создадим два файла, они будут главными файлами проекта, главные файлы проекта должны иметь формат "_*.*":
_main.h; -Заголовочный файл, в нём мы будем указывать явные зависимости (строгий порядок подключений хидеров *.h)
_main.pwn; - Файл кода, в нём мы будем указывать явные зависимости (строгий порядок подключений файлов кода *.pwn)

_main.h:
pawn Code:
#if !defined _MAIN_H_
#define _MAIN_H_
//==============================================================================
#include <a_samp> //Явная зависимость
//==============================================================================
#endif
_main.pwn
pawn Code:
#include <stdafx.h> //Предварительно откомпилированный заголовок
//==============================================================================
#if !defined _MAIN_PWN_
#define _MAIN_PWN_
//==============================================================================
main(){}
//==============================================================================
#endif
Создадим две папки:
_Includes; - В данную папку мы будем помещать инклуды других разработчиков но в формате *.h, например: streamer.h; mysql.h; Нужно это для того, чтобы не объявлять явные зависимости и не засорять компилятор.
Source; - В данной папке будут располагаться ресурсы нашего мода.

Напишем не большой мод, будем загружать и выгружать дома и бизнесы. Так же дома и бизнесы будут дробится на разные подтипы со своим функционалом.
Перейдём в папку Source, создаём файлы:
Callback.h:
pawn Code:
#if !defined _CALLBACK_H_
#define _CALLBACK_H_
//==============================================================================

//==============================================================================
#endif
Callback.pwn:
pawn Code:
#if !defined _CALLBACK_PWN_
#define _CALLBACK_PWN_
//==============================================================================
public OnGameModeInit()
{
    LoadHouse(); //Загрузим дома
    LoadBiznes(); //Загрузим бизнесы
    reutrn 1;
}
//==============================================================================
public OnGameModeExit()
{
    UnLoadHouse(); //Выгрузим дома
    UnLoadBiznes(); //Выгрузим бизнесы
    return 1;
}
//==============================================================================
#endif
Создадим папки: House; Biznes; В них создадим по два файла:
TestProject/Source/House/House.h; TestProject/Source/House/House.pwn;
TestProject/Source/Biznes/Biznes.h; TestProject/Source/Biznes/Biznes.pwn;

В папках две подпапки: Type_0; Type_1;
В подпапках по два файла, уже наверное догадались какие
TestProject/Source/House/Type_0/House_Type_0.h
TestProject/Source/House/Type_0/House_Type_0.pwn

Аналогично для Type_1 и бизнеса.

Для примера опишем только дома. Перейдем в папку: House
House.h:
pawn Code:
#if !defined _HOUSE_H_
#define _HOUSE_H_
//==============================================================================
#define MAX_HOUSE           5
//==============================================================================
new House_Count = 0;                //Счетчик количества домов
new House_DB_ID[MAX_HOUSE];         //Каждый дом запоминает ИД в Базе Данных
new House_Type[MAX_HOUSE];          //Каждый дом запоминает тип
//==============================================================================
forward LoadHouse();
forward UnLoadHouse();
//==============================================================================
#endif
House.pwn:
pawn Code:
#if !defined _HOUSE_PWN_
#define _HOUSE_PWN_
//==============================================================================
public LoadHouse()
{
    for(new i = 0; i < MAX_HOUSE; i++) //Побежим по всем домам
    {
        House_DB_ID[House_Count] = i; //Запомним ид БД
        House_Type[House_Count] = random(2); //Запомним тип дома
        if(House_Type[House_Count] == 0) //Если тип дома равен 0
        {
            House_Create_Type_0(); //Вызвать создание 0 типа
        }
        else if(House_Type[House_Count] == 1) //Если тип дома равен 1
        {
            House_Create_Type_1(); //Вызвать создание 1 типа
        }
        House_Count++; //Количество домов увеличилось
    }
    return 1;
}

public UnLoadHouse()
{
    for(new i = 0; i < House_Count; i++) //Бежим по всем домам
    {
        if(House_Type[i] == 0) //Если тип дома равен 0
        {
            House_Destroy_Type_0(); //Вызвать разрушение 0 типа
        }
        else if(House_Type[i] == 1) //Если тип дома равен 1
        {
            House_Destroy_Type_1(); //Вызвать разрушение 1 типа
        }
    }
    return 1;
}
//==============================================================================
#endif
Как видим, мы ничего не подключаем, нам не нужно задумываться о том, что у нас не объявлены переменные в зоне видимости файла, или не подключен какой либо из хидеров, мы просто пишем код.
Удобно? Надеюсь что так и есть. Давайте продолжим, перейдём в папку:TestProject/Source/House/Type_0/

House_Type_0.h:
pawn Code:
#if !defined _HOUSE_TYPE_0_H_
#define _HOUSE_TYPE_0_H_
//==============================================================================
new House_Type_0_Count = 0;
//==============================================================================
#endif
House_Type_0.pwn:
pawn Code:
#if !defined _HOUSE_TYPE_0_PWN_
#define _HOUSE_TYPE_0_PWN_
//==============================================================================
stock House_Create_Type_0()
{
    House_Type_0_Count++;
    printf("Дом тип 0, Меня уже: %d", House_Type_0_Count);
}

stock House_Destroy_Type_0()
{
    House_Type_0_Count--;
    printf("Дом тип 0, Меня осталось: %d", House_Type_0_Count);
}
//==============================================================================
#endif
Даже функция stock находясь на уровень ниже файла House.pwn видна.

Многие скажут, что это полная ересь, что оно даже компилироваться не будет. Да и вообще так нельзя делать.
Скрывать не буду, да так и есть, обычным методом данный проект не скомпилировать. Но вы наверное заметили , вначале мы используем какой-то там #include <stdafx.h> и мы нигде его не видели.
Об этом я вам сейчас расскажу и расскажу почему данный метод не работает на Pawno. Pawno - деревянный.

>>Часть вторая

Вот мы и разобрались с архитектурой будущего проекта, но многое осталось не ясным, чтож, раскрываю карты господа.
#include <stdafx.h> - является результатом сборки, которая происходит перед компиляцией всего проекта и имеет следующий вид.
pawn Code:
#if !defined _stdafx_h_
#define _stdafx_h_
//==============================================================================
#include "C:\TestProject\_main.h"
#include "C:\TestProject\Source\Callback.h"
#include "C:\TestProject\Source\Biznes\Biznes.h"
#include "C:\TestProject\Source\Biznes\Type_0\Biznes_Type_0.h"
#include "C:\TestProject\Source\Biznes\Type_1\Biznes_Type_1.h"
#include "C:\TestProject\Source\House\House.h"
#include "C:\TestProject\Source\House\Type_0\House_Type_0.h"
#include "C:\TestProject\Source\House\Type_1\House_Type_1.h"
//==============================================================================
#include "C:\TestProject\_main.pwn"
#include "C:\TestProject\Source\Callback.pwn"
#include "C:\TestProject\Source\Biznes\Biznes.pwn"
#include "C:\TestProject\Source\Biznes\Type_0\Biznes_Type_0.pwn"
#include "C:\TestProject\Source\Biznes\Type_1\Biznes_Type_1.pwn"
#include "C:\TestProject\Source\House\House.pwn"
#include "C:\TestProject\Source\House\Type_0\House_Type_0.pwn"
#include "C:\TestProject\Source\House\Type_1\House_Type_1.pwn"
//==============================================================================
#endif
Как видно из алгоритма, он в начале собирает все хидеры а дальше идёт сборка файлов кода, в результате все наши массивы, дефайны и т.п. находятся всегда вверху.
#include "C:\TestProject\_main.h" - так как этот файл у нас в сборке всегда первый, то мы можем прописать в нём исключения: Явные зависимости. Пример:
pawn Code:
#if !defined _MAIN_H_
#define _MAIN_H_
//==============================================================================
#include <a_samp> //Явная зависимость
//==============================================================================
#include "C:\TestProject\Source\Biznes\Biznes.h"
#include "C:\TestProject\Source\Callback.h" // Callback.h явно зависит от Biznes.h и теперь всё в порядке, Такие зависимости могут вызывать макросы
//==============================================================================
#endif
Тоже самое происходит с #include "C:\TestProject\_main.pwn", поэтому в данном файле не рекомендую писать код.
Вы наверное подумали, что stdafx.h нужно постоянно собирать вручную и при каждом изменении архитектуры изменять его - конечно же нет, хотя если вам не лень, то можно.

Для начала вам нужно написать bat файл сборщика... шучу я его уже написал. Создаём в папке с компилятором файл: Compiler.bat, правой кнопкой по файлу -> изменить, вставляем ниже приведённый код. Вам также доступны некоторые настройки самого bat файла.
pawn Code:
@rem Нужно указать расширения файлов:
@rem header - заголовочный файл, хранит переменные и forward-ы
@rem source - файл кода, хранит код программы, stock-и и public-и
@rem stdafx - предкомпилированный заголовок, в нём собираются все header и source файлы
@rem move_to - переместит скомпилированный файл в указанную директорию.
@set header=h
@set source=pwn
@set stdafx=stdafx
@set move_to=\

@chcp 65001>nul
@rem Запоминаем и выводим время старта компиляции
@setlocal EnableDelayedExpansion
@set t0=!time!
@echo Время запуска:    !t0!
@setlocal DisableDelayedExpansion

@rem Автоматические переменные
@set name=Created_by_White_116
@set old_path_to_code=v1.0
@set path_to_code=%CD%
@set path_to_pawn=%0

@rem Вырежем путь до компилятора
@for %%i in (%path_to_pawn%) do @(set path_to_pawn=%%~dpi)

@rem Ищем корневой каталог по главному файлу компиляции
:back1
@if exist %path_to_code%\_*.%source% goto next1
@for %%i in ("%path_to_code%\..") do @(set path_to_code=%%~fi)
@if %path_to_code% == %old_path_to_code% (
    @echo Ошибка: Не найден корневой каталог для этого проекта!
    @goto next2
) else (
    @set old_path_to_code=%path_to_code%
)
@goto back1
:next1
@rem Ищем имя главного файла компиляции
@for %%i in ("%path_to_code%\_*.%source%") do @(set name=%%~ni)

@rem Заполняем Precompiled Headers
@(
    @(echo.#if !defined _stdafx_h_)
    @(echo.#define _stdafx_h_)
    @(echo.//==============================================================================)
    @for /F "delims=" %%i in ('dir /s /b %path_to_code%\*.%header%') do @(echo.#include "%%i")
    @(echo.//==============================================================================)
    @for /F "delims=" %%i in ('dir /s /b %path_to_code%\*.%source%') do @(echo.#include "%%i")
    @(echo.//==============================================================================)
    @(echo.#endif)
)>%path_to_pawn%\include\%stdafx%.%header%

@rem Компилируем проект
@%path_to_pawn%pawncc.exe -;+ -(+ %path_to_code%\%name%.%source% -o%path_to_code%\%name%.amx  %1 %2 %3 %4 %5 %6 %7 %8 %9

@rem Если не нужно перемещать файл то перепрыгиваем, иначе перемещаем
@if %move_to% == \ goto next2

@rem Если существует файл переместить его
@if exist %path_to_code%\%name%.amx ^
move %path_to_code%\%name%.amx %move_to%

@rem Выводим время завершения и затраченное время на компиляцию
:next2
@setlocal EnableDelayedExpansion
@set t1=!time!
@for /F "tokens=1-8 delims=:.," %%a in ("!t0: =0!:!t1: =0!") do @(set /a "a=(((1%%e-1%%a)*60)+1%%f-1%%b)*6000+1%%g%%h-1%%c%%d, a+=(a>>31) & 8640000")
@echo Время завершения: !t1!    Затрачено времени: !a!0 мс.
@setlocal DisableDelayedExpansion

@rem Покинем, всё OK!
@exit /b 1
Данный bat файл умеет:
Quote:

-Засекать время компиляции проекта.
-При редактировании любого файла проекта и при нажатии кнопки компилирования, автоматически находить главный файл проекта который и будет компилироваться, главный файл проекта должен иметь формат _*.pwn
-Создавать предкомпилированный заголовок, создаётся автоматически в папке с инклудами, чтобы не мудрить с подключением в проекте.
-Перемещать скомпилированный файл в указанный каталог.
-Передавать параметры компилятору.

Открываем Notepad++, переходим в Плагины/NppExec/Execute... (по умолчанию кнопка F6) и вписываем:

Quote:

cd $(CURRENT_DIRECTORY)
"P:\Сompiler.bat" -O2

- где:
Quote:
cd $(CURRENT_DIRECTORY) -указывает текущий каталог компилируемого файла.
"P:\Сompiler.bat" -путь до нашего батника, который должен лежать рядом с компилятором.
-O2 - параметр компиляции передаваемый компилятору.

Открываем наш проект (см. Вложения), нажимаем Скомпилировать и радуемся.

>>Заключение

В данном туториале мы разобрались с заголовочными файлами, с архитектурой мода, с маленькими хитростями.
Надеюсь что в данном туториале всё описано доступно и понятно.
Файл проекта вы можете скачать во вложениях.
Reply
#2



Зачем так усложнять себе жизнь? Что это вообще? Это утопия языка "С" и логика прошлого века.
Reply
#3

Quote:
Originally Posted by xJester
View Post


Зачем так усложнять себе жизнь? Что это вообще? Это утопия языка "С" и логика прошлого века.
Может и утопия, но для павн всё же я не нахожу иных реализаций, разве что всё в одном файле, где беготня по этому файлу занимает 25% времени. Предложите вариант попроще.
Reply
#4

Допустим у нас есть папка "myProject" с таким содержимым:
PHP Code:
../gamemode.pwn
  
|- houses.pwn
  
|- houses
    
| - house-1.pwn
    
| - house-2.pwn 
Тогда мы можем (в gamemode.pwn):
PHP Code:
#include <a_samp>
#include "houses.pwn" 
И в конце концов (в houses.pwn):
PHP Code:
#include "/houses/house-1.pwn"
#include "/houses/house-2.pwn" 
Вывод:
1. Зачем нужна избыточность, если мы в процессе компиляции все равно увидим ошибки (дублирования функции и т.д.).
2. Зависимость можно регулировать вынесение отдельных общих функции в файл, который будем подключать сразу после <a_samp>
3. Избавимся от жесткой привязки к файловой системе (#include "C:\TestProject\Source\Biznes\Type_0\Biznes_Type_0 .h").
4. Количество подключаемых файлов сократилось в 2 раза.
Reply
#5

Ну вот понеслось...
1) Я предложил метод который исключает:
Quote:

Жесткой привязки к файловой системе (#include "C:\TestProject\Source\Biznes\Type_0\Biznes_Ty pe_0 .h").

2)
Quote:

Количество подключаемых файлов сократилось в 2 раза.

Нам ручками ничего подключать не нужно. За нас это сделает "сборщик".

3) Решает явные зависимости
Quote:

Зависимость можно регулировать вынесение отдельных общих функции в файл, который будем подключать сразу после <a_samp>

Этот файл уже существует и наврятли вообще понадобится...

4)
PHP Code:
#if !defined _HOUSE_PWN_
#define _HOUSE_PWN_
#endif 
Данную конструкцию не обязательно везде писать, а лишь при добавлении в файла в явную зависимость, ибо "сборщик" включает все файлы и файл включённый в явную зависимость продублируется.

5)Вот пример:
pawn Code:
../gamemode.pwn
  |- Some_Func.pwn
  |- houses.pwn
  |- houses
    | - house-1.pwn
    | - house-2.pwn
Some_Func.pwn имеет функцию которая будет обращаться к перемененной лежащей в house-2.pwn. Вывод: Компиляция заглохнет. Реши эту проблему!
Я тоже изначально придерживался данной тактики, которую вы предложили, в результате пришлось всё перемешать и получить кашу, я же предлагаю раздельное("компонентное") питание для компилятора.

P.S. Может ты меж строк глазками пробежался и не понял основную суть, скачай проект, посмотри как всё устроено, ибо то что ты предлагаешь, является той самой проблемой, которую я пытаюсь решить данным методом.
Reply
#6

1,2 - согласен, поторопился, не вник с суть.

3,4 -
PHP Code:
../gamemode.pwn (forward some_func();)
  |- 
Some_Func.pwn (call some_func();)
  |- 
houses.pwn 
  
|- houses 
    
| - house-1.pwn (public some_func() {return 1;} )
    | - 
house-2.pwn 
Проблема решена. Собственно это единственное верное решение указать компилятору что функция существует и может быть использована в коде. И неважно в каком она файле, главное что бы существовала.

Ваш метод хорош по своему, и в чем то конечно удобен, но через чур избыточен.
Reply
#7

Quote:
Originally Posted by xJester
View Post
Проблема решена. Собственно это единственное верное решение указать компилятору что функция существует и может быть использована в коде. И неважно в каком она файле, главное что бы существовала.

Ваш метод хорош по своему, и в чем то конечно удобен, но через чур избыточен.
Зачем создавать промежуточные функции? Ведь можно "на прямую" использовать сами переменные и в данном методе тоже не важно где они находятся.

Избыточность конечно только в самих header файлах, но думаю не так критично в особенности тем, кто также пишет на С и С++
Reply
#8

А зачем нам использовать переменные которые непосредственно относятся к какому-либо файлу? Глобальные переменные тоже можно выносить в заголовки, а с локальными работать через отдельные функции.
Reply
#9

Quote:
Originally Posted by xJester
View Post
А зачем нам использовать переменные которые непосредственно относятся к какому-либо файлу? Глобальные переменные тоже можно выносить в заголовки, а с локальными работать через отдельные функции.
Для различных проверок, данный метод уже подразумевает, что они будут глобальными и видимы во всех файлах.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)