Раздел:
Триггеры и объекты
За последний год я встречал огромное количество вопросов, связанных с передачей данных между разного рода функциями и переменными в 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!
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
0
22
14 лет назад
0
Эта статья мне кое-что прояснила, спасибо автору.
1
22
14 лет назад
1
Начал читать, не понял нафик это надо, закончил читать.
Какие функции, какие передачи, я нупЪ :О
3
6
14 лет назад
3
статья должна называться "Типы данных".
Нулевым значением типа string служит нулл (null) – нулевой указатель.
Константа пустых двойных кавычек ("") не всегда равна этому нулю, по этому не стоит её использовать.
Почему не всегда? Когда она вообще равна нулю (null)? Можно поподробней?
4
22
14 лет назад
4
mitryz, ну тут скорп немного перегнул палку, ибо за счет стрингтейбла ничего особо страшного от зануления строки присвоением ей "" не случится, но все же поясню.
Дело в том, что utf-8 строка - это просто последовательность ячеек, если это символы с кодами от 0x00 до 0x7F, то они хранятся в одном байте (сюда попадают английские буквы, цифры, знаки препинания и еще пара-тройка универсальных символов), все что сверх - хранится в 2-6 байтах (цифра 6 связана со спецификой кодирования, на самом деле все мыслимые символы умещаются в 1-4 байтах, 5 и 6 байт никем не используются, но теоретически могут). Ладно, это так, лирическое отступление для лучшего понимания, теперь перейдем к основному вопросу. Так вот строка - суть последовательность вот таких вот ячеек из 1-6 байт + еще одна ячейка для замыкающего символа равного 0x00 (это один из наиболее распространенных и общепринятых стандартов хранения строк). Т.е. строка "ABC" будет представлена как 4 байта: 0x61, 0x62, 0x63, 0x00
Суть в чем, как только ты присваиваешь некоторой строковой переменной в jass некое строковое значение, то, как уже сказал скорп, происходит переход по твоей переменной к некоторой записи таблицы строк (специальной таблицы, в которой хранятся все строки используемые в данный момент в игре) эта запись хранит текущую строку соответствующую твоей переменной (до присвоения), проверяется ссылается ли на неё еще что-нибудь, если нет, то эта запись удаляется из таблицы, далее в таблице происходит поиск присваиваемого значения, и если там эта строка не найдена, то она добавляется. И только после всей этой цепочки операций в твою переменную заносится указатель (скорее даже просто индекс в таблице) той записи таблицы, в которой хранится присвоенное значение, т.е. новая строка, соответствующая твоей переменной.
Так вот, если ты для зануления будешь присваивать своей переменной значение , то будет происходить вся эта вереница действий, в плоть до того, что в таблице строк появится (или будет найдена) строка со значением (т.е. пустая строка, вспоминаем что я говорил ранее, на самом деле пустая строка не такая уж и пустая =), она состоит из нулевого символа, т.е. символа с кодом 0x00) и вот индекс этой "пустой" строки будет присвоен твоей переменной. Мы с этого имеем небольшую потерю в производительности, да и вообще, это некрасивый подход, ибо мы хотим занулить строку, т.е. нам уже не нужно ничего хранить в ней, но мы все равно используем 1-н нулевой байт.
Т.е. в случае с варкрафтом это не критично, так как все пустые строки "" будут занимать все вместе лишь 1-н байт, так как все "пустые" строковые переменные будут ссылаться на одну и ту же запись в таблице строк.
А если же занулить строковую переменную null-ом то мы просто говорим, что она больше не ссылается ни на какую запись в таблице строк, в итоге чуть-чуть выигрываем в производительности, и получаем аж целый 1-н байт (и это только при условии, что в игре больше нет пустых строк типа , что нереально, т.е. фактически имеем только небольшой прирост производительности). Но, опять же, это только в варкрафте. Если использовать подход зануления строк путем присвоения им значения вне варкрафтовского программирования, то можно влететь не только на память но и вообще на стабильность работы программы.
Т.е. приминительно к варкрафту - зануление строки присвоением ей null, а не "", имеет на 99.99% эстетический эффект. Но он полезен с точки зрения приучивания себя к правильному стилю программирования.
Так что приучайся занулять строки через null
2
37
14 лет назад
2
тодди говорит, что были случаи, когда нулл не был равен "", тоесть "" была неопределённостью. обычно когда мы обращаемся по "", строка не возвращается из таблицы, т.к. там проверка на .empty() и вернется просто нулл. но ! иногда происходит косяк (я так понял), что эта строка "" создаётся в таблице и имеет свой хендл
и да, насчет утф-8 - размер символа там равен либо 1 байт (вся аски таблица от -128 до 127, включая 128 служебных и 128 печатных символов), либо 2 байта (все языки), но в варе небольшие косяки со вторым типом :) но в целом они всегда отображаются верно
6
22
14 лет назад
6
"вся аски таблица от -128 до 127, включая 128 служебных и 128 печатных символов"
Нет, там именно коды от 0x00 до 0x7F (т.е. 0-127), т.к. все коды овер 127 (называй их 128-255 или (-128) - (-1) без разницы), так вот все коды 127+ задают страницу кодировки последующих бит. Т.е. фактически парсер utf-8 строки берет бит и смотрит, если в нем значение 0-127, то это символ, если же в нем значение 127+ (или меньше нуля, если считать старший бит знаковым) то информация о символе хранится в этом байте + 1-5 последующих, в зависимости от значений последующих байтов (т.е. если в первом байте значение между 128-255, а в следующем 0-127, то символ задают два байта, если же и во втором байте 128-255, то смотрим третий, если и в нем 128-255, то смотрим четвертый, ну и т.д. вплоть до шести байтов подряд)
PS: Но т.к. я уже сказал, что фактически используются только четырехбайтовые символы, то большинство парсеров при взятии четвертого байта назначают символ при любом его значении, т.е. 0-255. Но по спецификации особо изощренный извращенец может потребовать себе и пятый и шестой байты.
PSPS: Нет, ну может я пургу нанес, но мне оно так объяснялось и в применении на практике меня не подводило.
0
37
14 лет назад
0
даже в утф-16 максимальный размер 2 байта, про 3,4 итп ни разу не слышал (не говоря о утф-32, там вобще отдельная история), ну насчет аски числа скорее всего так и есть
0
12
13 лет назад
0
голова трещит по швам...
по швам, про которые прочитал только что...
0
18
13 лет назад
0
Спасибо за обьяснённую работу типа string,незнаю насчёт полезности,вроде мало нового я узнал,но про СТринги в самый раз будем знать)
1
29
13 лет назад
1
ну и кому это сейчас нужно? все можно и без этой статьи разобрать ^^^ IMHO
1
17
11 лет назад
1
Msey, не скажи.. Статья очень хорошая. Написано качественно, без ошибок и заблуждений. =) В комментах поправки. Золотой запас хгм.
Кстати да, этоже Скорп.. =))) СКОРП спасибо :)!
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.