Главная » Статьи » Прочее

Передача и хранение данных в Warcraft

04-12-2016
В статье рассмотрены вопросы, связанные с передачей данных между разного рода функциями и переменными в Jass. Про это уже много всего написано, но большая часть из написанного либо не содержит однозначных ответов, либо содержит неверную информацию.
Я решил написать статью, которая предоставит читателю исчерпывающую информацию о передаче и хранении значений и ссылок на языке Jass.
_
За последний год я встречал огромное количество вопросов, связанных с передачей данных между разного рода функциями и переменными в Jass. Про это уже много всего написано, но большая часть из написанного либо не содержит однозначных ответов, либо содержит неверную информацию.
Я решил написать статью, которая предоставит читателю исчерпывающую информацию о передаче и хранении значений и ссылок на языке Jass.
Статья так же содержит примеры на языке cJass для тех, кто уже смело им владеет.

Типы данных

В Jass существует несколько типов данных, группирую я их так:
  • Базовые: integer, boolean, real
  • Встроенный: string
  • Неопределённый: nothing
  • Ссылочный: handle
  • Производные: agent, unit, player, trigger и все остальные..

Базовые типы

Базовые типы являются аналогами типов на языке Си и обрабатываются так же.
integer - целое 32-битное число (аналог __int32 в Си), принимает значения от -2147483648 до 2147483647.
Константно может быть задан в десятичном (123456), восьмеричном (0361100), шестнадцатеричном виде (0x1E240) и в виде побайтового представления четырёх "чаров" (в Си – char, просто символ ASCII) ('Ab12').
Нулевое значение - просто ноль (0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=, >=), арифметические целочисленные(+, -, *, /) и набор математических функций (подробнее в common.j).
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение:
» Пример на cJass
Int i; // неверно
//...
If( i < 5 ) { //значение неопределено
}
int i=0; // верно
//...
if( i < 5 ) {
}
» Пример на Jass
local Integer i // неверно
//...
If i < 5 then //значение неопределено
endif
local integer i=0 // верно
//...
if i < 5 then
endif
boolean - логический тип(аналог bool в Си++), хранится как целое 32-битное число (аналог __int32 в Си), принимает два значения.
Константно может быть задан двумя ключевыми словами - true (да) и false (нет).
Нулевое значение – "нет" (false).
Имеет операторы присвоения(=), сравнения(==, !=), логические(and, or, not) и набор дополнительных функций (подробнее в common.j).
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение.
» Примеры на cJass
if( (IsUnitFriend(u) || IsUnitDead(u)) && (! IsUnitChuckNorris(u) ) {
KickAss(u);
}
Здесь происходит проверка – если одна из функций-предикатов IsUnitFriend и IsUnitDead вернёт true, мы проверяем IsUnitChuckNorris и если она вернула false, то операция ! в скобках вернёт true и наш if получит ответ true, после чего выполнится KickAss.
» Примеры на Jass
if ( (IsUnitFriend(u) or IsUnitDead(u)) and (not IsUnitChuckNorris(u) ) then
call KickAss(u)
endif
Здесь происходит проверка – если одна из функций-предикатов IsUnitFriend и IsUnitDead вернёт true, мы проверяем IsUnitChuckNorris и если она вернула false, то операция ! в скобках вернёт true и наш if получит ответ true, после чего выполнится KickAss.
real - реальное 32-битное цисло с плавающей точкой (аналог float на Си), принимает значения от -3.4 * 10^38 до 3.4 * 10^38. Выделяется особенностью хранения – точность может быть сверхвысокой в одних и низкой в других диапазонах. Подробнее на Википедии.
Константно может быть задан в виде числа с точкой (1.2345) и целого числа (и тогда он будет преобразован к числу с точкой в процессе выполнения(см. примеры ниже)). Разрешается не ставить число до или после запятой, тогда это будет считаться как ноль ( .12 эквивалентно 0.12, 12. эквивалентно 12.0 ).
Нулевое значение – ноль с точкой (0.0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=, >=), арифметические(+, -, *, /) и набор математических функций (подробнее в common.j).
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение.
» Примеры на cJass
float x;
float y = 1;
float z = 1.1;
Процесс выглядит так:
  1. В стеке создаётся переменная x с неопределённым значением (инициализация)
  2. Создаётся переменная y
  3. Число из константы 1 преобразовывается в число 1.0
  4. Переменной y присваивается значение 1.0
  5. Создаётся переменная z
  6. Переменной z присваивается значение из константы 1.1
» Примеры на Jass
local real x
local real y = 1
local real z = 1.1
Процесс выглядит так:
  1. В стеке создаётся переменная x с неопределённым значением (инициализация)
  2. Создаётся переменная y
  3. Число из константы 1 преобразовывается в число 1.0
  4. Переменной y присваивается значение 1.0
  5. Создаётся переменная z
  6. Переменной z присваивается значение из константы 1.1
Базовые типы передаются в функции и служат локальными переменными через стек. По окончанию работы функции, локальные переменные (в т.ч. параметры функции) удаляются.

Встроенный тип string

Тип string является нативным типом в warcraft 3 и представляет из себя ссылку на строку. Ссылка эта – аналог хендла (см. ниже).
Строка в Jass – это последовательность символов в кодировке UTF-8.
На самом деле всё немного сложнее. В памяти располагается так называемый string table – это хеш-таблица, которая содержит в себе все строки, когда-либо созданные в процессе игры. Таблица строк накапливает строки, но не удаляет их, даже если их больше не используют. Неиспользуемые строки удаляются лишь после загрузки сохранённой игры(или конца игры).
Когда мы генерируем какую-либо строку, её хеш-сумма сравнивается на объект дубликата. И если такой строки ещё не было в таблице строк – строка добавляется в эту таблицу, а ссылка типа string возвращается как результат операции. Если же строка уже была создана, то наша временная строка удаляется, а в качестве результата возвращается ссылка на уже существующую строку из таблицы.
Тип string обладает следующими операторами: присвоение(=), сравнение(==, !=) и конкатенация(+).
Операторы не работают с другими типами (например, integer в виде набора чаров), но для этого есть специальные функции конвертации типов в строку и обратно(подробнее в common.j). Так же существуют базовые функции подстроки (SubString), длины строки (StringLength) и другие.
Следует знать, что любые операции создают после себя новую ссылку на строку, поэтому не стоит ими злоупотреблять.
» Пример на cJass
//плохое решение
string f[];
//...
string name = "scorpy";
int i=0;
while(i<10) {
f[i] = name + I2S(i);
if(i==9) {
f[i] = f[i] + " always here";
}
i++;
}

//более оптимально
string f[];
//...
f[0] = "scorpy0";
f[1] = "scorpy1";
f[2] = "scorpy2";
//...
f[8] = "scorpy8";
f[9] = "scorpy9 always here";
В первом случае таблица строк будет содержать не только то, что нам надо, но ещё и строки "0", "1", ... и это только самый простой случай.
» Пример на Jass
//плохое решение
string array f
//...
local string name = "scorpy"
local integer i=0
loop
exitwhen (i<10)
set f[i] = name + I2S(i)
if (i==9) then
set f[i] = f[i] + " always here"
endif
set i=i+1
endloop

//более оптимально
string array f
//...
set f[0] = "scorpy0"
set f[1] = "scorpy1"
set f[2] = "scorpy2"
//...
set f[8] = "scorpy8"
set f[9] = "scorpy9 always here"
В первом случае таблица строк будет содержать не только то, что нам надо, но ещё и строки "0", "1", ... и это только самый простой случай.
Нулевым значением типа string служит нулл (null) – нулевой указатель.
Константа пустых двойных кавычек ("") не всегда равна этому нулю, по этому не стоит её использовать.
Хранение значений в строке работает по принципу UTF-8: все ASCII-символы занимают один байт, остальные – два байта. Максимальный размер строки – 1023 байта. thx to toadcop

Неопределённый тип nothing

nothing в Jass служит лишь как ключевое слово, которое даёт компилятору понять, что функция не имеет принимаемых или возвращаемых типов. Сам по себе тип nothing объявить нельзя.

Ссылочные типы handle

И так, что же такое этот вездесущий хендл? Очень просто – это число. Обыкновеннное целое число.
Называют его по-разному – идентификатор, ссылка, дескриптор, указатель, индекс, smart pointer и другие.
Хендл в Jass является указателем на объект в памяти. А конкретнее – это адрес ячейки в специальной хеш-таблице хендлов.
Точная структура этой таблицы не известна, но есть основание предполагать, что она устроена следующим образом:
При создании какого-либо объекта (пусть это будет юнит) в памяти динамически создаётся его структура со всеми нужными полями. В таблице хендлов выделяется один элемент под этот объект, который хранит в себе указатель на объект в памяти и дополнительную информацию. А наш хендл – это адрес этого самого элемента.
Когда мы удаляем юнита, удаляется эта структура в памяти (перейдя по нашему хендлу), но сам хендл (и другие хендлы этого юнита) остается неизменным.
Когда мы обнуляем хендл (присваиваем ему null), элемент удаляется из таблицы хендлов, а наш хендл (как число) принимает нулевое значение. И опять-же, другие хендлы, которые ссылались на нашего юнита, остаются неизменны.
Именно поэтому после работы с объектом его надо не только удалять, но и обнулять все хендлы, ссылающиеся на нашего юнита. Если этого не сделать, они будут "висеть" в таблице хендлов и не только засорять память, но и замедлять скорость доступа к этой таблице.
Исключением являются локальные переменные, которые являются параметрами функции. Они удаляются автоматически после выхода из функции.
Тип handle обладает только операторами присвоения(=) и сравнения (==, !=).
Нулевым значением для хендла является нулл(null).
Я думаю, не стоит доказывать, что при обращении к хендлу, значение которого равно нулю, игра вылетает с ошибкой.
То же может касаться и обращений к хендлам объектов, которые были удалены до этого обращения. В большинстве случаев будет ошибка Access Violation, но бывают случаи, когда ваш старый хендл "попадает" в какую-либо "правильную" структуру. И результат непредсказуем.

Return Bug и GetHandleId()

Но суть одна – получить доступ к хендлам так, чтобы работать с ними было так же удобно, как и с целыми числами.
Зачем это нужно и как их использовать написано в этих статьях:


Категория: Прочее | Добавил: Адмирал (04-12-2016 в 19:22:16)
Просмотров: 938 | Комментарии: 1 | Рейтинг: 0.0/0
Всего комментариев: 1
avatar
1
"но суть одна – получить доступ к хендлам так, чтобы работать с ними было так же удобно, как и с целыми числами." которых вроде бы нету в 1.26 версии , там только х.еш-таблицы.
avatar
Рейтинг@Mail.ru
Яндекс.Метрика

Copyright © 2010-2017
Вакансии :: Контакты
Мобильная версия сайта