SA-MP Forums

Go Back   SA-MP Forums > Non-English > Languages > Русский/Russian > Релизы/Releases

Reply
 
Thread Tools Display Modes
Old 29/08/2014, 07:47 PM   #1
White_116
High-roller
 
Join Date: Sep 2010
Location: Russia/116
Posts: 1,495
Reputation: 69
Lightbulb Архитектура проекта

Д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 - параметр компиляции передаваемый компилятору.
Открываем наш проект (см. Вложения), нажимаем Скомпилировать и радуемся.

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

В данном туториале мы разобрались с заголовочными файлами, с архитектурой мода, с маленькими хитростями.
Надеюсь что в данном туториале всё описано доступно и понятно.
Файл проекта вы можете скачать во вложениях.
Attached Files
File Type: zip TestProject.zip (8.3 KB, 39 views)
__________________
White_116 is offline   Reply With Quote
Old 01/09/2014, 03:44 PM   #2
xJester
Big Clucker
 
xJester's Avatar
 
Join Date: Jul 2010
Posts: 186
Reputation: 1
Default Re: Архитектура проекта



Зачем так усложнять себе жизнь? Что это вообще? Это утопия языка "С" и логика прошлого века.
__________________
http://dn-mp.ru/
xJester is offline   Reply With Quote
Old 01/09/2014, 04:34 PM   #3
White_116
High-roller
 
Join Date: Sep 2010
Location: Russia/116
Posts: 1,495
Reputation: 69
Default Re: Архитектура проекта

Quote:
Originally Posted by xJester View Post


Зачем так усложнять себе жизнь? Что это вообще? Это утопия языка "С" и логика прошлого века.
Может и утопия, но для павн всё же я не нахожу иных реализаций, разве что всё в одном файле, где беготня по этому файлу занимает 25% времени. Предложите вариант попроще.
__________________
White_116 is offline   Reply With Quote
Old 02/09/2014, 07:05 AM   #4
xJester
Big Clucker
 
xJester's Avatar
 
Join Date: Jul 2010
Posts: 186
Reputation: 1
Default Re: Архитектура проекта

Допустим у нас есть папка "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 раза.
__________________
http://dn-mp.ru/
xJester is offline   Reply With Quote
Old 02/09/2014, 09:55 AM   #5
White_116
High-roller
 
Join Date: Sep 2010
Location: Russia/116
Posts: 1,495
Reputation: 69
Default Re: Архитектура проекта

Ну вот понеслось...
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. Может ты меж строк глазками пробежался и не понял основную суть, скачай проект, посмотри как всё устроено, ибо то что ты предлагаешь, является той самой проблемой, которую я пытаюсь решить данным методом.
__________________
White_116 is offline   Reply With Quote
Old 02/09/2014, 11:31 AM   #6
xJester
Big Clucker
 
xJester's Avatar
 
Join Date: Jul 2010
Posts: 186
Reputation: 1
Default Re: Архитектура проекта

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 
Проблема решена. Собственно это единственное верное решение указать компилятору что функция существует и может быть использована в коде. И неважно в каком она файле, главное что бы существовала.

Ваш метод хорош по своему, и в чем то конечно удобен, но через чур избыточен.
__________________
http://dn-mp.ru/
xJester is offline   Reply With Quote
Old 02/09/2014, 12:27 PM   #7
White_116
High-roller
 
Join Date: Sep 2010
Location: Russia/116
Posts: 1,495
Reputation: 69
Default Re: Архитектура проекта

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

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

Избыточность конечно только в самих header файлах, но думаю не так критично в особенности тем, кто также пишет на С и С++
__________________
White_116 is offline   Reply With Quote
Old 04/09/2014, 05:08 AM   #8
xJester
Big Clucker
 
xJester's Avatar
 
Join Date: Jul 2010
Posts: 186
Reputation: 1
Default Re: Архитектура проекта

А зачем нам использовать переменные которые непосредственно относятся к какому-либо файлу? Глобальные переменные тоже можно выносить в заголовки, а с локальными работать через отдельные функции.
__________________
http://dn-mp.ru/
xJester is offline   Reply With Quote
Old 05/09/2014, 02:40 PM   #9
White_116
High-roller
 
Join Date: Sep 2010
Location: Russia/116
Posts: 1,495
Reputation: 69
Default Re: Архитектура проекта

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

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off



All times are GMT. The time now is 04:16 PM.


Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.