Добавлен , опубликован

JASS

Содержание:
Переменные - это выделенные ячейки в памяти под определенный тип данных.

Глобальные переменные

Переменные, доступные из любого места в коде.

globals

Блок, для объявления глобальных переменных в коде. Как и в случае с функциями из common.ai, допустимо любое количество блоков до объявления первой пользовательской функции.
globals
	integer a = 1
	real b = 2
	boolean c
endglobals
В него, при генерации war3map.j попадают переменные созданные в редакторе триггеров. Эти переменные будут иметь префикс udg_ (user defined global).
Выполняется до инициализации карты и соответственно в нём нельзя создавать никаких хэндлов.

constant

Ключевое слово, которое должно намекать разработчику что значение этой переменной не будет изменяться за всё время исполнения кода.
globals
	constant integer a = 1
	constant real b = 2
	constant boolean c = false
endglobals
Так же, при попытке сменить значение constant переменной произойдёт ошибка. Но это не точно.

Локальные переменные

Переменные, которые живут и умирают внутри функции. Должны быть объявлены до любого действия внутри функции с помощью ключевого слова local:
function main takes nothing returns nothing
	local integer a = 1
	local real b
endfunction

Утечка

Она же уточка, она же memory leak, это не освобождённая память. Возникает по двум причинам:
  • не удалённые объекты
  • локальные переменные с типом, наследуемым от handle
Первая причина проста - любой созданный объект необходимо удалить после себя. Да, прям как в сортире - создали, полюбовались, удалили.
function SomeTriggerAction takes nothing returns nothing
	local location rally = GetUnitRallyPoint(GetTriggerUnit())
	// делаем что-то с полученной точкой
	call RemoveLocation(rally) // удаляем созданную точку после использования
	set rally = null // об этом ниже
endfunction
Вторая причина не интуитивна и заключается в рукожопости создателей языка. Более подробно это описано здесь.
Чтоб их избежать достаточно запомнить простое правило: в момент выхода из функции всем локальным переменным с типом наследующим handle должно быть установлено значение null.
Напоминаю, что все типы объявленные в common.j наследуют handle, а выход из функции происходит когда исполняется endfunction или return.

Уточню для самых одарённых - это ошибка языка и относится только к локальным переменным. Ни глобальные перемененные, ни аргументы функции этой ошибке не подвержены.

set

Для установки значения переменным используется ключевое слово set. Установка значения в выражении недопустима.
globals
	integer a
endglobals

function main takes nothing returns nothing
	local integer b
	set a = 1
	set b = 2
endfunction

Инициализация

Несмотря на умное слово, это означает всего лишь первую установку значения переменной, у которой существует особое состояние - объявленая, но не инициализоранная переменная. При попытке получить значение такой переменной игра завершит исполнение текущей функции.
globals
	integer a // переменная объявлена, но не инициализирована
	integer b = 1 // переменная объявлена и ей установлено значение, тоесть она проинициализирована
endglobals

function main takes nothing returns nothing
	local integer c // переменная объявлена, но не инициализирована
	local integer d = 1 // переменная объявлена и ей установлено значение, тоесть она проинициализирована
	set c = 2 // первая установка значения переменной, тобишь инициализация
	set d = 3 // значение установлено при объявлении и это просто установка значения
endfunction

Немного лирики

Переменной с любым типом необязательно присваивать значение при объявлении. Но считается хорошим тоном всегда инициализировать переменные при объявлении. Делайте так и вас почешут за ушком.
globals
	integer i = 0
	real r = .0
	boolean b = false
	string s = "" // если вы используете null для строк, то вы больной ублюдок и вам пора лечиться
	unit u = null // для любых потомков handle используйте null, который собственно для них и создавался
endglobals

Массивы

Для объявления массива используется ключевое слово array. Инициализировать массив, как и указать его размер при объявлении нельзя. Что чертовски неудобно.
Индексация значений начинается с нуля.
globals
	integer array a
endglobals

function main takes nothing returns nothing
	local integer array b
	set a[0] = 1
	set b[0] = a[0] + 2
endfunction
Для типа code массивы не поддерживаются.

Выделение памяти

Изначально любой массив имеет размер ноль. При записи значения выделяется память на количество элементов, равное значению ближайшей сверху степени двойки. Так продолжается, пока размер не достигнет максимального, указанного в переменной JASS_MAX_ARRAY_SIZE из common.j.
function main takes nothing returns nothing
	local integer array a // размер равен нулю
	set a[0] = 1 // ближайшая степень к 0 это 2^0=1, размер равен 1
	set a[1] = 1 // ближайшая степень к 1 это 2^1=2, размер равен 2
	set a[2] = 1 // ближайшая степень к 2 это 2^2=4, размер равен 4
	set a[3] = 1 // ближайшая степень к 3 это 2^2=4, размер равен 4
	set a[4] = 1 // ближайшая степень к 4 это 2^3=8, размер равен 8
	set a[5] = 1 // ближайшая степень к 5 это 2^3=8, размер равен 8
	set a[6] = 1 // ближайшая степень к 6 это 2^3=8, размер равен 8
	//...
endfunction
Ниже приведена таблица степеней двойки и максимальный размер массива для разных версий игры.
Число Степень Значение Максимальный размер
2 0 1
2 1 2
2 2 4
2 3 8
2 4 16
2 5 32
2 6 64
2 7 128
2 8 256
2 9 512
2 10 1 024
2 11 2 048
2 12 4 096
2 13 8 192 Ванила
2 14 16 384
2 15 32 768 Reforged
2 16 65 536
2 17 131 072
2 18 262 144 UjAPI

Маленькая хитрость

Массив должен располагаться в памяти непрерывно, такова особенность архитектуры. Поэтому при смене размера будет найден участок памяти в который поместится новый массив и все значения будут скопированы туда. Как вы понимаете, эта операция не бесплатна и чтоб избежать лишних операций с памятью, можно прикинуть максимальное количество элементов массива и сразу выделить необходимую память:
globals
	integer array a
endglobals

function main takes nothing returns nothing
	set a[500] = 0 // Допустим мы прикинули, что количество элементов будет около 300
endfunction
Память выделяется в момент присваивания значения по индексу, так что само значение неважно:
globals
	integer array i
	string array s
	unit array u
	rect array r
endglobals

function main takes nothing returns nothing
	set i[500] = 0
	set s[500] = ""
	set u[500] = null
	set r[500] = null
endfunction

Утечка

Здесь всё практически так же как с переменными: глобальные массивы ведут себя как глобальные переменные и в первом приближении можно вообще относиться к ним как к переменным с хитрым синтаксисом.
Локальные массивы с типом не наследующим handle абсолютно безопасны и их использование дело вкуса.

О локальных массивах с типом наследующим handle лучше вообще забыть.
Локальные массивы с типом наследующим handle просто шикарно отстреливают жопы. При изменении размера будет утечка четырёх байт на каждое значение отличное от null. Поэтому таким массивам обязательно выделять память, гарантировано покрывающим все элементы. А так же при выходе их функции в таком массиве не должно быть значений отличных от null.

Коллизии имён

Как писалось выше, язык написан ногами, поэтому избегайте пересечения имён в одной области видимости. Ибо выстреливает такое всегда внезапно и довольно таки долго ищется.
globals
	integer A = 1
endglobals

function B takes integer D returns nothig
	local integer E = 2
	//...
endfunction

function C takes integer D returns nothig
	local integer E = 3
	//...
endfunction
Как видите, в глобальной области видимости разные имена: A, B и C. Аргумент D и локальная переменная E в каждой функции видна только внутри этой самой функции и не пересекается ни с чем из глобальной области. Здесь должна быть ссылка на статью, где это подробно объяснено, но я такой не нашёл.
В программировании это называется shadowing, но вспоминаем предыдущие пассажи про ноги...

`
ОЖИДАНИЕ РЕКЛАМЫ...
1
15
4 недели назад
Отредактирован IceFog
1
При записи значения выделяется память на количество элементов, равное значению ближайшей сверху степени двойки.
При увеличении массива, его размер подстраивается так, чтобы быть кратным числу X, где X = требуемый размер, округленый вниз до ближайшей степени двойки, но не больше чем 64.
выделение памяти для массивов
void __thiscall GrowableArray_DWORD_::set_capacity(GrowableArray_DWORD_ *this, size_t new_capacity)
{
    size_t items_count_to_copy; // ebp
    DWORD *old_items; // ebx
    DWORD *new_items; // eax
    size_t i; // edx
    DWORD *p_new_item; // eax

    items_count_to_copy = new_capacity;
    old_items = this->items;
    this->capacity = new_capacity;
    new_items = Storm_405_SMemReAlloc(old_items, 4 * new_capacity, DWORD::TypeName, -2, 0x10u);
    this->items = new_items;
    if ( !new_items )
    {
        this->items = Storm_401_SMemAlloc(4 * new_capacity, DWORD::TypeName, -2, 0);
        if ( old_items )
        {
            if ( new_capacity >= this->length )
                items_count_to_copy = this->length;
            for ( i = 0; i < items_count_to_copy; ++i )
            {
                p_new_item = &this->items[i];
                if ( p_new_item )
                    *p_new_item = old_items[i];
            }
            Storm_403_SMemFree(old_items, DWORD::TypeName, -2, 0);
        }
    }
}

size_t __thiscall GrowableArray_DWORD_::get_grow_step(GrowableArray *this, size_t desired_length)
{
    size_t result; // eax
    size_t i; // ecx

    result = desired_length;
    if ( desired_length >= 64 )
    {
        this->grow_step = 64;
        return 64;
    }
    else
    {
        for ( i = desired_length & (desired_length - 1); i; i &= i - 1 )
            result = i;
        if ( !result )
            return 1;
    }
    return result;
}

void __thiscall JassArray::grow_to_fit(JassArray *this, size_t required_length)
{
    DWORD old_length; // eax MAPDST
    DWORD new_length; // esi
    GrowableArray *array; // ebx
    DWORD grow_step; // edi
    DWORD new_capacity; // ecx

    if ( required_length > 8192 )
        required_length = 8192;
    old_length = this->length;
    new_length = required_length;
    if ( required_length <= old_length )
        new_length = this->length;
    array = &this->Array;
    if ( new_length > this->length && new_length > array->capacity )
    {
        grow_step = this->grow_step;
        if ( !grow_step )
            grow_step = GrowableArray_DWORD_::get_grow_step(&this->Array, new_length);
        new_capacity = new_length;
        if ( new_length % grow_step )
            new_capacity = grow_step + new_length - new_length % grow_step;
        GrowableArray_DWORD_::set_capacity(array, new_capacity);
    }
    array->length = new_length;
    if ( required_length > old_length )
        memset(&this->items[old_length], 0, 4 * (required_length - old_length));
}
Локальные массивы с типом наследующим handle просто шикарно отстреливают жопы. При изменении размера будет утечка четырёх байт на каждое значение отличное от null. Поэтому таким массивам обязательно выделять память, гарантировано покрывающим все элементы. А так же при выходе их функции в таком массиве не должно быть значений отличных от null.
Глобальные и локальные массивы работают одинаково. Когда память перевыделяется, то старые елементы копируются в новый буфер, а старый буфер освобождается.
Если перед выходом из функции обнулять все элементы, содержащие agent'ы, то ссылки на таблицу хэндлов тоже не утекут. Тут всё как и с обычными переменными.
запись в массив
void __thiscall JassArray::set_item(JassArray *this, size_t index, int value)
{
    if ( index < this->length )
        this->items[index] = value;
}

void __thiscall ScriptData::set_array_length(ScriptData *this, size_t length, BOOL keep_old_data)
{
    JassArray *value; // eax
    JassArray *old_array; // ecx
    JassArray *new_array; // eax

    if ( this->initialized_type >= TYPE_INT_ARRAY )
    {
        value = this->value;
        if ( value )
        {
            if ( !keep_old_data )
            {
                TSGrowableArray_DWORD_::clear(&value->Array);
                old_array = this->value;
                if ( old_array )
                    old_array->vtable->delete(old_array, TRUE);
                this->value = 0;
            }
        }
    }
    if ( length )
    {
        if ( !this->value )
        {
            new_array = Storm_401_SMemAlloc(sizeof(JassArray), ".\\Instance.cpp", 1362, 0);
            if ( new_array )
            {
                new_array->capacity = 0;
                new_array->items = 0;
                new_array->grow_step = 0;
                new_array->vtable = &JassArray::vtable;
                new_array->length = 0;
                this->value = new_array;
                JassArray::grow_to_fit(new_array, length);
                return;
            }
            this->value = 0;
        }
        JassArray::grow_to_fit(this->value, length);
    }
}

void __thiscall ScriptData::set_array_item_value(
        ScriptData *this,
        int value_type,
        size_t index,
        int value,
        int trust_level)
{
    VAR_TYPE array_type; // eax

    array_type = this->initialized_type;
    if ( array_type >= TYPE_INT_ARRAY && array_type - 5 == value_type || trust_level < 1 )
    {
        ScriptData::set_array_length(this, index + 1, TRUE);
        JassArray::set_item(this->value, index, value);
    }
}
Из багов с массивами, могу припомнить краш при загрузке сохранения, в скрипте которого присутствует массив, размеров больше чем 8191, который происходит из-за неправильной проверки.
фрагмент кода для загрузки массива из потока данных
// ...
    CDataStore::ReadDword(stream, &array_length);
    if ( array_length >= 8192 )
    {
        array_length = 8191;
    }
// ...
0
19
2 месяца назад
0
Инициализировать массив, как и указать его размер при объявлении нельзя. Что чертовски неудобно.
Они на старте уже инициализированы, либо 0, либо null. Проверено.
Как то я случайно сломал свою базу данных, и функция обращалась к неиспользуемой ячейке массива. Массив boolexpr. Неинициализированный, то есть не стоит никакого цикла, который null или 0 расставляет. Ничего не вылетало, не глючило, но фильтр игра воспринимала как null и забирала в группу всех юнитов подряд.
Второй момент был связан с попыткой вывести имя юнита по айди, тоже функция залезла не в ту ячейку и выдала (Default string) вместо имени. То есть, в той ячейке стоял 0. Версия игры 1.26 соответственно.
Ответы (9)
0
29
2 месяца назад
0
Они на старте уже инициализированы, либо 0, либо null. Проверено.
Каким образом проверено? Ты хочешь сказать, что при объявлении массива выделяется вся доступная память и забивается нулями?
1
20
2 месяца назад
1
Открою секрет, массив не иницилизирован пока не используется set, а 0 возвращается так как это стандартное значение. 0 в string = "" или же null.
Измерение свечками - это конечно прикольно, но не стоит распространять дезинформацию, не проведя тестов/не изучив внутренности. :(
Загруженные файлы
0
19
2 месяца назад
0
nazarpunk, Unryze, На практике в игре проверено, причем несколько раз. Какая дезинформация? Можете сами сделать массив, и обратиться к пустой ячейке и вывести результат на экран через BJDebugMsg.
Кстати, о дезинформации:
Unryze
0 в string = "" или же null.
string s = "" если вы используете null для строк, то вы больной ублюдок и вам пора лечиться
Кто дезинформирует?
1
20
2 месяца назад
1
EugeAl, ещё раз, посмотри мои скрины глазами, а не пятой точкой. То, что "null" применимо к string - не значит, что это СТОИТ использовать, джассу то пофиг, ибо строки по сути в переменной хранят индекс на текст, а не сам текст. Потому 0 = null = "", но когда это дело нужно транслировать допустим в AS, то начинаются отстрелы задниц, когда строки сравнивают с null, что недопустим в AS.
Повторяю на более простом языке, когда у тебя массив только объявлен, то размер равен нулю, так как индекс 0 >= размеру, то возвращается всегда 0, не потому что в нём есть значение, а потому что 0 - это стандартное значение. На кой болт ты споришь - непонятно.
И повторюсь в третий раз, ВЫВОД 0 НЕ ОЗНАЧАЕТ ЧТО У НАС БЫЛА ИНЦИАЛИЗАЦИЯ, то, что игра не отстреливает - как раз причина проверки индекса против размера.
Надеюсь столько повторений хватит.
0
19
2 месяца назад
0
Unryze, я не разбираюсь в с++.
Значит, массивы инициализировать не обязательно, если есть стандартное значение. Спорил я для того, чтобы установить истину ) а то один одно говорит, другой другое, это непорядок, нужно было узнать.
2
20
2 месяца назад
Отредактирован Unryze
2
globals
	integer array someInts // массив пустой, get любого индекса вернёт 0, по указанным выше причинам.
endglobals

function TestArrayStuff takes nothing returns nothing
	set someInts[1000] = 5000 // первая установка значения так же инициализирует индексы от 0 до X по мере возрастания 1/2/4/8/16/32/64 то есть после 64 индекса расти будет по 64. И того выделиться размер в 1024, то бишь от 0 до 1023.
endfunction
Поясняю, в Jass нельзя именно инициализировать массив, ибо он растёт автоматически, у него нет требования указания строго размера как допустим в AS:
array<unit> U_SelectionSelArr( 12 );
Ты путаешь вещи, и писать "проверено" - не проверив / не зная внутренности движка - и есть "дезинформация", ибо ответ твой был в корне неверный.
Ну и закончим опять же этим кодом:
//----- (6F459730) --------------------------------------------------------
int __thiscall JassVM::GetArrayValueById(uint32_t *this, unsigned int a2)
{
  int result; // eax

  if ( a2 < this[2] )
    result = *(uint32_t *)(this[3] + 4 * a2);
  else
    result = 0;
  return result;
}
Даже без знания ЯП у тебя лишь 2 варианта:
  1. a2 (индекс) меньше числа (размера) - то вернуть это число по индексу a2.
  2. a2 (индекс) больше или равен числу (размеру) - то вернуть 0.
Ну ты и опирался на 2) не понимая почему так.
0
19
2 месяца назад
Отредактирован EugeAl
0
Unryze, set someInts[1000] = 5000 первая установка значения так же инициализирует индексы от 0 до X
Это действие инициал все предыдущие ячейки тоже?
a2 (индекс) меньше числа (размера) - то вернуть это число по индексу a2.
a2 (индекс) больше или равен числу (размеру) - то вернуть 0.
Спасибо!
"проверено" - не проверив / не зная внутренности движка - и есть "дезинформация"
тогда можно 95% статей по варкрафту с ХГМ удалять, ведь практически никто из авторов не имеет доступа к движку и тем более исходному коду, ведь они не из близзард, и значит, у них "дезинформация". Хотя они тоже "проверяли", как и я.
Не желаю более возвращаться к этой теме.
1
20
2 месяца назад
1
EugeAl, да, `set someInts[1000] = 5000 первая установка значения так же инициализирует индексы от 0 до X` Как я и указал индексы от 0 до 1023 (ибо размер 1024) будут заполнены нулями, а индекс 1000 примет значение 5000.
Уйму статей потому я и опровергал в Jass Mythbusters, и потому был дан резкий ответ, чтобы последующие мифы не рождались.
0
19
2 месяца назад
0
Unryze, Хорошо, спасибо. Не придется циклы писать )
Ок)
1
22
2 месяца назад
Отредактирован makkad
1
При изменении размера будет утечка четырёх байт на каждое значение отличное от null
Т.е. локальные массивы типа Handle лучше задавать начиная с последнего индекса?
local rect array r
set r[3]=Rect(0,0,1,1)
set r[2]=Rect(0,0,1,1)
set r[1]=Rect(0,0,1,1)
set r[0]=Rect(0,0,1,1)

Для выделения памяти массивам типа handle достаточно присвоить значению с последним индексом значение null? Или нужно присвоить существующий объект?
local rect array r
set r[500]=null
Ответы (1)
0
29
2 месяца назад
Отредактирован nazarpunk
0
makkad, дополнил статью.

Память выделяется в момент присваивания значения по индексу, так что само значение неважно:
globals
	integer array i
	string array s
	unit array u
	rect array r
endglobals

function main takes nothing returns nothing
	set i[500] = 0
	set s[500] = ""
	set u[500] = null
	set r[500] = null
endfunction
1
3
2 месяца назад
1
Самые лучшие карты всегда на джаз написанные как по мне
Ответы (2)
1
19
4 недели назад
1
Ден94, а я больше рок люблю
1
27
4 недели назад
1
Ден94, я люблю когда в меня втыкают тысячу ножей вместе с иглами и прокручивают мои кишки изнутри наворачивая их на шашлычки для бездомных шакалов, мряф ~ </3
0
37
4 недели назад
0
Забыли написать, что длина имени переменной значительно влияет на производительность в классике
Ответы (2)
0
29
4 недели назад
0
ScorpioT1000, и это конечно же подтверждено тестами?
2
15
4 недели назад
Отредактирован IceFog
2
nazarpunk, я делал тесты.
0
37
4 недели назад
Отредактирован ScorpioT1000
0
Ещё я бы добавил, что обнулить нужно не только при выходе из функции, но и если ты умышленно рвёшь подпоток (например, делением на ноль)
Редкий кейс, но раз уж покрывать, так целиком)
Останавливать подпоток можно с целью какого-то жесткого assert глубоко внутри стека, чтобы избежать полного краша игры. Я это использовал для избежания некоторых ситуаций, которые не должны случаться by design, но теоретически могут. Например, если счетчик рекурсии (depth) превысил свой хард лимит - выводим ошибку и валим стек делением на ноль. Игра подвиснет, но продолжит работать.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.