Раздел:
Триггеры и объекты
За последний год я встречал огромное количество вопросов, связанных с передачей данных между разного рода функциями и переменными в 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'). Подробнее об этом написано в статье от ADOLF.
Нулевое значение - просто ноль (0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=, >=), арифметические целочисленные(+, -, *, /)
и набор математических функций (подробнее в common.j).
Стоит заметить, что допускается беспараметровая операция обращения знака, например, "a = -b".
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение:
Пример на 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, то операция not в скобках вернёт true и наш if получит ответ true, после чего выполнится KickAss.
real - реальное 32-битное цисло с плавающей точкой (аналог float на Си), принимает значения от -3.4 * 10³⁸ до 3.4 * 10³⁸. Выделяется особенностью хранения – точность может быть сверхвысокой в одних и низкой в других диапазонах. Подробнее на Википедии.
Константно может быть задан в виде числа с точкой (1.2345) и целого числа (и тогда он будет преобразован к числу с точкой в процессе выполнения(см. примеры ниже)). Разрешается не ставить число до или после точки, тогда это будет считаться как ноль ( .12 эквивалентно 0.12, 12. эквивалентно 12.0 ).
Нулевое значение – ноль с точкой (0.0).
Имеет операторы присвоения(=), сравнения(==, !=, <, >, <=, >=), арифметические(+, -, *, /) и набор математических функций (подробнее в common.j).
Стоит заметить, что допускается беспараметровая операция обращения знака, например, "a = -b".
Инициализируется неопределённым значением, поэтому стоит всегда явно задавать начальное значение.
Примеры на 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) – нулевой указатель.
Константа пустых двойных кавычек ("") не всегда равна этому нулю, по этому не стоит её использовать.
В 1.29 PTR на данный момент упразднили возврат null, теперь рекомендуется использовать пустые кавычки
Хранение значений в строке работает по принципу UTF-8: все ASCII-символы занимают один байт, остальные – от двух (например, кириллица) до четырёх байт (например, китайские диалекты).
Максимальный размер строки – 1023 байта. thx to toadcop

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

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

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

И так, что же такое этот вездесущий хендл? Очень просто – это число. Обыкновеннное целое число.
Называют его по-разному – идентификатор, ссылка, дескриптор, указатель, индекс, smart pointer и другие.
Хендл в Jass является указателем на объект в памяти. А конкретнее – это адрес ячейки в специальной хеш-таблице хендлов.
Точная структура этой таблицы не известна, но есть основание предполагать, что она устроена следующим образом:
При создании какого-либо объекта (пусть это будет юнит) в памяти динамически создаётся его структура со всеми нужными полями. В таблице хендлов выделяется один элемент под этот объект, который хранит в себе указатель на объект в памяти и дополнительную информацию. А наш хендл – это адрес этого самого элемента.
Когда мы удаляем юнита, удаляется эта структура в памяти (перейдя по нашему хендлу), но сам хендл (и другие хендлы этого юнита) остается неизменным.
Когда мы обнуляем хендл (присваиваем ему null), объект удаляется из таблицы, а наш хендл (как число) принимает нулевое значение. И опять-же, другие хендлы, которые ссылались на наш объект, остаются неизменны.
Немного мыслей здесь
ScorpioT1000:
о каком объекте идет речь? destroy уничтожает данные мгновенно, далее объект становится "пустым" - почти все функции работы с ним возвращают фейлд, при этом хендл висит в памяти
потестируй
проверять существование юнита так GetUnitTypeId(u) > 0 итема GetItemTypeId(u) > 0
+ к этим проверкам добавлять and IsUnitAlive (ну это не точно так, но суть ясна), если мертвые тоже не нужны
Darklight:
ScorpioT1000, Проверил. Да, ScorpioT1000, ты прав! Если юнит не разлагается, то сразу же как умирает (поистечение времени смерти в параметрах юнита) значение в ячейки Хеш таблицы сразу же принимает значение null. ТО есть, ссылки из Хеш таблицы не учитываются. Вернее, после проведения более точного эксперимента, значение юнита в Хеш таблице сразу же обнолвется на null (не очищается, а заменяется значением null) после обработки гибели юнита (если он не разлагается; для разлагающихся - это произойдёт после разложения; или при вызове функции RemoveUnit), приэтом я специально оставил ссылку на юнита в отдельной переменной - так вот там юнит стал "безымянным", но id сохранился - значит варик сам обрабатывает ячейки Хеш таблицы с уничтожаемыми объектами и присваивает им значение null, несмотря на то, что на эти объекты могу быть ссылки из других переменных. И это даже без вызова RemoveUnit
Значит операться на ссылки в хеш таблицах как на источники ключей нельзя :( придётся отдельно сохранять числовые ключи идентификаторов этих ссылок
Ну, или использовать (там где можно) событие "Unit die" и обрабатывать умершего юнита до того как его ссылкав Хеш таблице превратится в null
Вот такая вот засада вышла!
Оригинал: xgm.ru/forum/showthread.php?t=56047
Именно поэтому, после работы с объектом, его надо не только удалять, но и обнулять все хендлы, ссылающиеся на нашего юнита. Если этого не сделать, они будут "висеть" в таблице хендлов и не только засорять память, но и замедлять скорость доступа к этой таблице.
Исключение – локальные переменные, которые являются параметрами функции. Они удаляются автоматически после выхода из функции.
Тип handle обладает только операторами присвоения(=) и сравнения (==, !=).
Нулевым значением для хендла является нулл(null).
Я думаю, не стоит доказывать, что при обращении к хендлу, значение которого равно нулю, игра вылетает с ошибкой.
То же может касаться и обращений к хендлам объектов, которые были удалены до этого обращения. В большинстве случаев будет ошибка Access Violation, но бывают случаи, когда ваш старый хендл "попадает" в какую-либо "правильную" структуру. И результат непредсказуем.

Return Bug и GetHandleId()

Про это уже очень много написано, например, здесь или здесь.
Но суть одна – получить доступ к хендлам так, чтобы работать с ними было так же удобно, как и с целыми числами.
Зачем это нужно и как их использовать написано в этих статьях:
xgm.guru/p/wc3/exjass "RETURN BUG (RB)" - уже устарело
xgm.guru/p/wc3/w3_special_programming "Стек и "Аттачи""

Производные типы от handle

Все остальные типы, которые являются производными от handle (unit, item, player, code и др. См. common.j) – точно такие же ссылки. Но логически поделены на подтипы, у каждого из которых своя область применения.
Стоит упомянуть, что некоторые вобще не содержат никаких таблиц хендлов, например, тип player. В Starcraft 2 этот тип заменили на int.
На сегодня всё, с вами был ScorpioT1000!

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
2
4
14 лет назад
2
Спс очень ждал эту статью.
2
14
14 лет назад
2
Че-то я не очень понял, что-же это за таблица хендлов такая и нафига она нужна? И что в этой таблице является ключом (Key), а что значением (Value)? Я то думал, что Handle - обычный указатель (как в СИ). И когда мы создаем Unit, динамически выделяется память под этого юнита, и нам возвращается указатель на эту память, но больше она никуда не сохраняется ни в какую таблицу хендлов. Соответственно, когда удаляем юнита, нужно сначала вызвать DeleteUnit(указатель на юнита) - это удалит структуру данных (Unit) по переданному указателю. Все функции DeleteXXX(handle h) удаляют объект производного типа после простого приведения handle>>XXX, как в СИ++: {XXX * a = (XXX *) h; delete a; }, а потом, нужно обнулить этот указатель h = null, просто чтобы случайно туда ниче не записать, ведь указатель не действителен. Или чето по-другому?
0
22
14 лет назад
0
Alex_Hell, залог безопасности в ограничении разработчику непосредственного доступа к памяти. Поэтому и нужна такая хитрая схема. Ты своим кодом получаешь ID записи в таблице, которому поставлен в соответствие адрес непосредственно в памяти, и этот адрес средствами Jass ты никак не получишь.
2
4
14 лет назад
2
Думаю можно было бы добавить в статью, для чего всё это нужно...
2
37
14 лет назад
2
Вы можете мне верить, а можете изучать это сами.
1
15
14 лет назад
1
80% материала не нужно ни одному картоделу
3
18
14 лет назад
3
vsparker,
если тебе не нужно или не интересно знать как устроен этот мир, это еще не значит что никому не интересно.
Имхо, каждый должен знать с чем работает.
Спасибо за статью, ScorpioT1001.
2 комментария удалено
2
19
14 лет назад
2
"исчерпывающую инормацию"
исправь ошибку, некрасиво
Этот комментарий удален
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.