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

vJass

Содержание:

Содержание главы:

Методы функции

В vJass функции могут вести себя как объекты с двумя методами: evaluate и execute. Оба метода имеют такой же список принимаемых аргументов, как и сама функция. Метод evaluate так же возвращает значение.
Функция, как объект, имеет некоторые преимущества - метод evaluate позволяет совершать вызов функции из любого участка кода, даже если место вызова находится выше места объявления функции. Метод execute делает то же самое, но при этом порождает новый поток, однако, выполняется быстрее старого доброго ExecuteFunc.
Недостаток же следующий - функция, вызываемая методом evaluate не должна вызывать в своем теле функцию GetTriggeringTrigger (остальные событийные нативки доступны к использованию) или же нативную функцию синхронизации. Так же вызов функции методом evaluate не поддерживает вейты. Более того, вызов функции через evaluate производится медленнее обычного вызова.
Вызывая функцию посредством ExecuteFunc, вы были бы вынуждены использовать глобальные переменные для передачи аргументов. Методы же evaluate и execute позволяют передавать аргументы напрямую.
Взглянем на следующий пример
function A takes real x returns real
    if(GetRandomInt(0,1)==0) then
        return B(x*0.02)
    endif
    return x
endfunction

function B takes real x returns real
    if(GetRandomInt(0,1)==1) then
        return A(x*1000.)
    endif
    return x
endfunction
В случае Jass, мы получим синтаксическую ошибку, поскольку вызываем функцию B до ее объявления. Использование метода evaluate избавит нас от проблемы
function A takes real x returns real
    if(GetRandomInt(0,1)==0) then
        return B.evaluate(x*0.02)
    endif
    return x
endfunction

function B takes real x returns real
    if(GetRandomInt(0,1)==1) then
        return A(x*1000.)
    endif
    return x
endfunction
В качестве другого примера, предположим, что нам необходимо уничтожить некоторый эффект спустя какое-то время. По человечески, следует использовать таймер, но для демонстрации мы воспользуемся вейтом. Чтобы не прервать выполнение текущей функции, породим новый поток методом execute
function DestroyEffectAfter takes effect fx, real t returns nothing
    call TriggerSleepAction(t)
    call DestroyEffect(fx)
endfunction

function test takes nothing returns nothing
    local unit u=GetTriggerUnit()
    local effect f=AddSpecialEffectTarget("Abilities\\Spells\\Undead\\Cripple\\CrippleTarget.mdl",u,"chest")

    call DestroyEffectAfter.execute(f,3.0)

    set u=null
    set f=null
endfunction

Имя функции

Функция так же обладает специальным атрибутом name типа string, который хранит в себе полное имя этой функции, генерируемое с учетом всех префиксов. Вы можете использовать этот атрибут для вызова функции через ExecuteFunc, например в областях и библиотеках, которые изменяют действительное имя функции при указании модификатора доступа
scope test
    public function xxx takes nothing returns nothing
        call BJDebugMsg(xxx.name) // Выведет "test_xxx"
    endfunction
endscope

Интерфейс функций

Поскольку функция является объектом, мы можем объявить интерфейс функции. Синтаксис следующий:
function interface имя takes (аргументы) returns (типВозвращаемогоЗначения)
что близко к простому об]явлению функции.
В результате, вы можете объявить переменную типа интерфейса функции и присвоить ей значение, в виде указателя на конкретную функцию. Звучит непонятно, давайте взглянем на пример:
function interface Arealfunction takes real x returns real

function double takes real x returns real
    return x*2.0
endfunction

function triple takes real x returns real
    return x*2.0
endfunction

function Test1 takes real x, Arealfunction F returns real
    return F.evaluate(F.evaluate(x)*F.evaluate(x))
endfunction

function Test2 takes nothing returns nothing
    local Arealfunction fun = Arealfunction.double // Синтаксис получения указателя на функцию

    call BJDebugMsg( R2S(  Test1(1.2, fun) ))
    call BJDebugMsg( R2S(  Test1(1.2, Arealfunction.triple ) )) // Так же допустимо
endfunction
Во время компиляции, интерфейс функции получает в качестве статичного аргумента (подобно статичным аргументам структур) все функции в коде карты, чьи сигнатуры совпадают с сигнатурой этого интерфейса (то есть, список принимаемых аргументов и тип возвращаемого значения). В примере выше, функция double и triple имеют сигнатуру, идентичную сигнатуре интерфейса Arealfunction, что делает их статичными аргументами этого интерфейса. А строчкой
    local Arealfunction fun = Arealfunction.double // Синтаксис получения указателя на функцию
мы получаем в переменную указатель на конкретную функцию, соответствующую указанному интерфейсу. Впоследствии, мы можем передавать эту переменную в другую функцию и вызывать методы evaluate и execute описанные ранее.
Вы так же можете получить указатель на функцию не указывая имя интерфейса,
function double takes real x returns real
    return 2*x
endfunction

function square takes real x returns real
    return x*x
endfunction

function interface realfunc takes real x returns real

function repeater3 takes real x, realfunc F returns real
    set x=F.evaluate(x)
    set x=F.evaluate(x)
    set x=F.evaluate(x)
    return x
endfunction

function test takes nothing returns nothing
    local real x = repeater3( 2.0, double) // Заметьте, мы используем имя функции в качестве значения
    local real y = repeater3( 2.0, square)
    /// Да, интерфейс функции позволяет использовать функции в качестве разновидности переменной
endfunction
Мы так же можем производить приведение типом (о чем в соответствующем разделе), для преобразования указателя на функцию к целому числу и обратно.

Метод как объект

Метод структуры тоже можно рассматривать в качестве объекта, они так же обладают методом evaluate, execute и атрибутом name
struct mystruct
    static method mymethod takes nothing returns nothing
        call BJDebugMsg("this works")
    endmethod
endstruct

function myfunction takes nothing returns nothing
    call ExecuteFunc(mystruct.mymethod.name) // ВЫзов через ExecuteFunc и конечное имя метода
endfunction
Методы так же обладают атрибутом exists типа boolean, который имеет значение ture, если метод существует или false иначе. Данный атрибут используется для проверки, реализует ли структура тот или иной метод расширяемого интерфейса
interface MyInterface
    method myMethod1 takes nothing returns nothing
    method myMethod2 takes nothing returns nothing defaults nothing
endinterface

struct MyStruct extends MyInterface
    method myMethod1 takes nothing returns nothing
        /*
             Инструкции...
        */
    endmethod
endstruct

function Test takes nothing returns nothing
 local MyStruct tmp = MyStruct.create()
    // Выведет:
    // yes
    // no
    if (tmp.myMethod1.exists) then
        call BJDebugMsg("Yes")
    else
        call BJDebugMsg("No")
    endif
    if (tmp.myMethod2.exists) then
        call BJDebugMsg("Yes")
    else
        call BJDebugMsg("No")
    endif
    call tmp.destroy()
endfunction 

`
ОЖИДАНИЕ РЕКЛАМЫ...
0
9
3 месяца назад
0
Не очевидно, но evaluate и execute вызывают новый поток, со своим оп лимитом
0
37
3 месяца назад
0
Самое вкусное, в какую кашу из жасс это превращается, не показали(
Подозреваю таймеры, тк они быстрее триггеров
1
29
3 месяца назад
1
ScorpioT1000, здесь классический подход - вот вам удобное апи, а что под капотом не важно.
Можно подумать, что в документации по компилируемым языкам описано, в какую кашу превращается их ASM/байткод.
0
10
3 месяца назад
Отредактирован Vladimir TVK
0
Года два назад смотрел, как работает evaluate. Нашёл, что писал в ВК:
Если ты какую-то функцию эвалюэйтиш, в собранном проекте под неё делается отдельная функция-обёртка и триггер. А под аргументы - отдельные поля переменных. Соответственно, в месте, где эвалюэйтишь, ты заполняешь эти поля данными и активируешь триггер.
В своё время был удивлён, когда узнал о существовании такой фичи. Пару раз с удовольствием использовал. Конечно, использовать следует без фанатизма, держа в голове, что этот сахар делает "под капотом", дабы не словить баг. А как это визуально выглядит после сборки в общем файле - абсолютно не важно. Ты там ничего и не должен редактировать.
Загруженные файлы
0
37
3 месяца назад
0
Тоесть даже не таймеры
0
9
3 месяца назад
0
Самое вкусное, в какую кашу из жасс это превращается, не показали(
Ты имеешь в виду как компилятор разворачивает vjass в JASS ?
0
29
3 месяца назад
0
Koladik, да. Весь этот модный синтаксис превращается в лютый угар под капотом.
Жасс и так писался ногами и люто медленный. Зачем лишний раз усложнять себе жизнь на ровном месте?
0
10
3 месяца назад
Отредактирован Vladimir TVK
0
ScorpioT1000, эвалюейт должен дождаться выполнения функции и вернуть значение. Здесь таймер не вышло бы использовать.
Имхо самое важное в статье вот это:
/// Да, интерфейс функции позволяет использовать функции в качестве разновидности переменной
Не знал, что можно объявить интерфейс для функции. Но главное для меня то, что можно для экземпляра класса написать метод вне класса, и объект будет из него получать значение.

Правда, не нравится мне, что эта фича плодит триггеры. Возможно, надёжнее будет всё тоже самое делать через глобальные поля и boolexpr. Хоть и выглядит уродливо.
0
9
3 месяца назад
0
Жасс и так писался ногами и люто медленный. Зачем лишний раз усложнять себе жизнь на ровном месте?
Ну насколько я понимаю, тут только обращение к разным функциям, даже действий нет, неуверен, что это сильно замедляет выполнение. Да и кому какая разница как оно разворачивается, если этого не видно и с этим не надо работать.
0
29
3 месяца назад
0
Да и кому какая разница как оно разворачивается
И правда, какая разница, в насколько слоупочный код оно под капотом развернётся? Ведь главное это удобство. Даже если оно в 1фпс исполняться будет.
0
28
3 месяца назад
Отредактирован PT153
0
Не очевидно, но evaluate и execute вызывают новый поток, со своим оп лимитом
evaluate не создаёт.

И правда, какая разница, в насколько слоупочный код оно под капотом развернётся? Ведь главное это удобство. Даже если оно в 1фпс исполняться будет.
В ни насколько не слоупочный код. По результату тоже самое, что и сохранение имени функции в хеш и её последующий вызов через ExecuteFunc. Только в случае vJass юзается массив (быстрее хеша) и TriggerExecute (быстрее ExecuteFunc). Так что юзаем vJass на зло всем чистожассерам, которые вместо глобалок хеш юзают (а такие есть). 🤣

Правда, не нравится мне, что эта фича плодит триггеры. Возможно, надёжнее будет всё тоже самое делать через глобальные поля и boolexpr. Хоть и выглядит уродливо.
Будет тоже самое, только руками. В чём смысл?
0
10
3 месяца назад
0
Koladik, под капотом не важно, как выглядит код. Но знать, что там происходит, через какие костыли реализуется фича, полезно. Потому что даёт понимание, когда её использовать уместно, а когда нет. Помогает понять, какие могут вылезти баги или ограничения.
Что касается времени вызова. Конечно, там разница смешная. Но эта разница играет роль при многократном вызове и может выстрелить в каком-то узком месте программы. Проблема здесь не в инструменте, а в программисте, который его неправильно использует.

Будет тоже самое, только руками. В чём смысл?
Я изначально думал, что у триггеров есть какой-то строго ограниченный лимит. В своей реализации можно очень экономно использовать триггеры. Но в итоге провёл тест, спамлю триггеры, считаю их по айдишнику последнего - они не кончаются. Так что видимо всё отлично и можно свободно использовать.
0
37
3 месяца назад
Отредактирован ScorpioT1000
0
PT153, только не забывай, что там не прямая целочисленная индексация uint64, а поиск и заполнение дырок ещё происходит, насколько я помню. Где есть пул удалённых индексов для избежания переполнения основного массива бедолаги на 8к элементов. Так что тут надо ещё задуматься, даже с h2i, стоит ли игра свеч
0
28
3 месяца назад
0
О каком вообще замедлении идёт речь в контексте WC3, где главным источником лагов являются утечки и и кол-во активных объектов (типа юнитов). Тот же триггер по сути пустышка. Вон старый-добрый костыльный метод создания события по получению урона плодит ТОННУ событий, а единственный способ их все вайпнуть - вайпнуть сам триггер. Но и без вайпа через 2 часа игры это лагов не вызовет, просто потому что импакт минимальный.
0
37
3 месяца назад
0
Плюс я там где-то видел флаг для создания огромного массива из 10 массивов с бинарным поиском чтобы обойти эти 8к лимиты
0
28
3 месяца назад
Отредактирован PT153
0
ScorpioT1000, никакого поиска и заполнения дырок нет. Если у тебя в коде 1000 функции типа takes integer returns nothing, а в качестве переменных юзается 5, будет создан массив триггеров и 5 триггеров только для этих 5 функций.
0
37
3 месяца назад
0
Я именно про хранение структур, присмотрись внимательно на финальный код
0
28
3 месяца назад
0
"поиск" присутствует только в контексте обычных структур, но это даже не поиск, потому что vJass прекрасно знает, какой индекс свободен во время аллокации новой структуры указанного типа. Выходить за лимит в 8к не рекомендуется, иначе во время каждого обращения к атрибуту или перезаписываемому методу будет происходить резолв массива по индексу структуры.

ScorpioT1000, так а причём тут структуры, если речь про функции и их "интерфейсы"? Структуры другой разговор.
0
9
3 месяца назад
0
evaluate не создаёт.
Тестил недавно, 200к операций через evaluate несколько раз, все ок работает. Через обычный вызов функции не работает. Выше владимир тоже написал о том, что создается триггер.
Vladimir TVK:
в собранном проекте под неё делается отдельная функция-обёртка и триггер.
0
28
3 месяца назад
Отредактирован PT153
0
Функция, как объект, имеет некоторые преимущества - метод evaluate позволяет совершать вызов функции из любого участка кода, даже если место вызова находится выше места объявления функции. Метод execute делает то же самое, но при этом порождает новый поток, однако, выполняется быстрее старого доброго ExecuteFunc.
И может так и было, когда JassHelper только создавался, но я сейчас сам протестировал на 1.26, TriggerEvaluate действительно создаёт свой поток. Тогда в execute() нет смысла кроме вейтов.
Забавный факт: execute() не возвращает значение, даже если оригинальная функция это делает. Почему? Vexorian решил, что это не нужно. 🤣

Не удивлюсь, если TriggerExecute на самом деле нифига не быстрее ExecuteFunc.
Загруженные файлы
0
10
3 месяца назад
0
Опять люди теоретической информатикой занимаются вместо того чтобы карты делать 😞
Показали бы конкретику - карту без этой фичи и с ней, и что она конкретно дает разрабу, а главное конечному пользователю (игорьку).
0
37
3 месяца назад
0
PT153, без нового потока смысл был бы нулевой в этой функции. Но я обычно ExecuteFunc юзал для таких вещей
Slonick, это полезно для инициализации больших баз данных, потому что движок рвет поток после N операций. Например, я создавал интерфейс инвентаря и дерева талантов в jc и обычного потока не хватало, чтобы создать всё, приходилось перезапускать поток инициализации через ExecuteFunc
А для периодических действий типа движения всё и так на обычных таймерах крутится, там такой проблемы нет
0
28
3 месяца назад
Отредактирован PT153
0
без нового потока смысл был бы нулевой в этой функции.
Абсолютно нет, это всё ещё вызов условий триггера и всё ещё вызов функции из любого места кода (о чём и говорится в цитате из этой статьи).
Я всегда считал, что TriggerEvaluate не создаёт свой поток, поэтому эта функция и быстрее TriggerExecute. И поэтому для создания потоков я использовал .execute(). А оно вон как на самом деле: TriggerExecute медленный из-за поддержки вейтов.
Показали бы конкретику - карту без этой фичи и с ней, и что она конкретно дает разрабу
Читаем статью, получаем ответ на вопрос.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.