,

Передаем аргументы в фунции callback'ов

» опубликован
» Способ реализации: vJass
» Тип: Алгоритм
» Версия игры: 1.26a - 1.27a, не тестировалось на более ранних версиях.
Собственно небольшая библиотека, требующая мемхак_второй_версии и больше ничего.
Позволяет передавать аргументы функциям, которые используются в качестве аргумента code в StartTimer(), ForGroup(),ForFroce(),Condition() и так далее.
» код

library Lambda initializer Init uses Memory

    globals
        constant integer BOOL = 0x1 // boolean
        constant integer CODE = 0x2 // code
        constant integer HAND = 0x3 // handle
        constant integer INTG = 0x4 // ineteger
        constant integer REAL = 0x5 // real
        constant integer STRI = 0x6 // string
        
        integer array PowArr
        integer bj_lastConvertedCode = 0
    endglobals

    function ShiftLeft takes integer bits, integer shift returns integer
        return bits * PowArr[shift]
    endfunction

    // a - highest, d - lowest
    function DwordFromBytes takes integer a, integer b, integer c, integer d returns integer
        local integer result = 0
    
        set result = result + ShiftLeft( a, 24 )
        set result = result + ShiftLeft( b, 16 )
        set result = result + ShiftLeft( c, 8 )
        set result = result + ShiftLeft( d, 0 )
    
        return result
    endfunction

    function WriteInstruction takes integer address, integer id, integer a1, integer a2, integer a3, integer param returns nothing
        call WMem( address + 0x00, DwordFromBytes( id, a1, a2, a3 ) )
        call WMem( address + 0x04, param )
    endfunction

    function CreateLambdaByIdOneArg takes integer func_id, integer param , integer argtype returns code
        local integer buffer = malloc( 4 * 0x08 )
        // size - 0x08
        // litheral - 0x0C
        // push - 0x13
        // call - 0x16
        // return - 0x27
        call WriteInstruction( buffer + 0 * 0x08, 0x0C, 1, argtype, 0, param ) //0x01 - boolean, 0x2 - code, 0x3 - handle, 0x4 - integer, 0x5 - real, 0x6 - string
        call WriteInstruction( buffer + 1 * 0x08, 0x13, 1, 0, 0, 0 )
        call WriteInstruction( buffer + 2 * 0x08, 0x16, 0, 0, 0, func_id )
        call WriteInstruction( buffer + 3 * 0x08, 0x27, 0, 0, 0, 0 )
	
        return I2C( buffer )
    endfunction
    
    function CreateLambdaByIdTwoArg takes integer func_id, integer param1, integer param2, integer argtype1, integer argtype2 returns code
        local integer buffer = malloc( 6 * 0x08 )
        // size - 0x08
        // litheral - 0x0C
        // push - 0x13
        // call - 0x16
        // return - 0x27
        call WriteInstruction( buffer + 0 * 0x08, 0x0C, 1, argtype1, 0, param1 ) //0x01 - boolean, 0x2 - code, 0x3 - handle, 0x4 - integer, 0x5 - real, 0x6 - string
        call WriteInstruction( buffer + 1 * 0x08, 0x13, 1, 0, 0, 0 )
        call WriteInstruction( buffer + 2 * 0x08, 0x0C, 2, argtype2, 0, param2 ) //0x01 - boolean, 0x2 - code, 0x3 - handle, 0x4 - integer, 0x5 - real, 0x6 - string
        call WriteInstruction( buffer + 3 * 0x08, 0x13, 2, 0, 0, 0 )
        call WriteInstruction( buffer + 4 * 0x08, 0x16, 0, 0, 0, func_id )
        call WriteInstruction( buffer + 5 * 0x08, 0x27, 0, 0, 0, 0 )
	
        return I2C( buffer )
    endfunction

    function SetCodeOneArg takes code func, integer argtype, integer arg returns code
        if ( func == null ) then
            return null
        endif
        set bj_lastConvertedCode = RMem( C2I( func ) - 0x04 )
        if ( bj_lastConvertedCode < 1 ) or ( argtype < 0 ) or ( argtype > 6 ) then
            return null
        endif
        return CreateLambdaByIdOneArg( bj_lastConvertedCode, arg, argtype )
    endfunction

    function SetCodeTwoArg takes code func, integer argtype1, integer arg1, integer argtype2, integer arg2 returns code
        if ( func == null ) then
            return null
        endif
        set bj_lastConvertedCode = RMem( C2I( func ) - 0x04 )
        if ( bj_lastConvertedCode < 1 ) or ( argtype1 < 0 ) or ( argtype1 > 6 ) or ( argtype2 < 0 ) or ( argtype2 > 6 ) then
            return null
        endif
        return CreateLambdaByIdTwoArg( bj_lastConvertedCode, arg1, arg2, argtype1, argtype2 )
    endfunction
    
    private function Init takes nothing returns nothing
        local integer i = 1
    
        set PowArr[0] = 1
   
        loop
            set PowArr[i] = PowArr[i - 1] * 2
       
            set i = i + 1
            exitwhen i == 32
        endloop
           
        set PowArr[32] = -2147483648
    endfunction

endlibrary
Собственно пример применения:
function TestFunc takes boolean breturns nothing
	if b then
		call BJDebugMsg("b - true")
	else
		call BJDebugMsg("b - false")
	endif
endfunction

//# +nosemanticerror
function somename takes nothing returns nothing  
...
 call TimerStart( CreateTimer( ), 0.50, true, SetCodeOneArg( function TestFunc,BOOL, 1 ) )
...
endfunction
При срабатывании таймера наша функция TestFunc получит b, равное true или false в зависимости от того чему мы установили последний аргумент функции SetCodeOneArg.
ОБРАТИТЕ ВНИМАНИЕ //# +nosemanticerror обазателен, т.к пасер не знают что каллбеки или code может иметь аргументы и выдаст нам вот такую ошибку:
Как видим: Функция Имя не должна иметь аргументов когда используется в качестве code!
Поэтому нам необходимо использовать данную инструкцию, чтобы пасер игнорировал это.
Самому же Warcraft3 совершенно пофиг на это, карта без JNGP в которой функцию с аргументам используют в качестве code спокойно сохранится и даже запустится, правда при вызове этой функции вар крашнется с фаталом.
Но так как у нас давным давно есть мемхак, нам нечего не стоит исправить эту проблему, на самом деле функция внутри виртуальной JASM машине может иметь аргументы всегда, не важно используется она как call func( args) или как ForGroup( grp, function func), но во втором случаи нечему засунуть список аргументов в функцию, тут нам помогает мемхак
Собственно описание функционала:
  1. SetCodeOneArg - берет code (т.е function имя вашей функции, которой вы хотите задать аргументы) , целое - тип переменной, целое значение.
        constant integer BOOL = 0x1 // boolean
        constant integer CODE = 0x2 // code
        constant integer HAND = 0x3 // handle
        constant integer INTG = 0x4 // ineteger
        constant integer REAL = 0x5 // real
        constant integer STRI = 0x6 // string
Вроде бы все просто - 0x1 буль, 0х2 код, 0х3 хендл, 0x4 целое...
Но как нам передавать handle или real, или string,и ? Функция берет только целочисленные значения.
А очень просто, нужно их преобразовать в целые числа с помощью функционала мемхака или встроенных jass функций (в одном случаи)
local code c = SetCodeOneArg( function TestFunc, CODE, C2I( function otherfunc ))  // если вы хотите передать аргумент типа code
При этом не забудьте что функция TestFunc (та в которую вы хотите передать аргумент), должна иметь только 1 принимаемый аргумент типа code
Вот так это выглядит на jass:
function TestFunc takes code c returns nothing
	call ForGroup( udg_TempGroup, c )
endfunction
При этом обратных преобразований из целого в код не потребуется, просто работаем с аргументом как с локалкой.
Примерно так же делаем с handle:
local code c = SetCodeOneArg( function TestFunc, HAND, GetHandleId(некий хендл))  // если вы хотите передать аргумент типа handle*
Но тут есть одна загвоздка, если в первом случаи мы строго указывали тип - буль, или код, то как же быть с хендалми, хендл наследуют 100500 типов, от agent до widgetevent, а точнее около 90. Но вспоминаем, что хендлы то особо не отличаются, все это ссылки на внутреннюю хештаблицу объектов, следовательно для всех типов наследуемых от handle, нужно использовать тип аргумента HAND (0x3).
Но не забывайте, в функции которой вы хотите передать аргумент, тип аргумента должен соотсветсвовать тому, которому вы хотите преедать, т.е если вы передаете юнита, то функция должна иметь тип аргумента unitа не hanlde или item...
Пример на jass:
function TestFunc takes unit u returns nothing // хоть мы указали тип handle в SetCodeOneArg, но все равно указываем юнита.
	call BJDebugMsg( GetUnitName(u)) 
endfunction
...
local code c = SetCodeOneArg( function TestFunc, HAND, GetHandleId(handle)) // указываем ссылку на юнита, если хотим предать юнита.
...
Точно так же для любого другого хендла, в SetCodeOneArg указываем тип HAND (0x3) и GetHandleId() от нужного нам обьекта, но в функцию которой мы хотим передать аргументы пишем takes тот тип, которому соответствует наш обьект, юнт-юниту, предмет-предмету, а не наоборот, передаем предмета а пишем юнит, наблюдаем фатальную ошибку....
Для целочисленных переменных никаких преобразований ненужно, указываем тип INTG (0x4), и праямо указываем число в качестве аргумента, тут никаких преобразований не потребуется:
function TestFunc takes integer a returns nothing
    call BJDebugMsg(I2S(a))
endfunction

call TimerStart( CreateTimer( ), 0.50, true, SetCodeOneArg( function TestFunc, INTG, 100500 ) )
С типом real все несколько сложнее, но тут нам поможет мемхак, а точнее пару функций из библиотеки Typecast:
  	//# +nosemanticerror
    function realToIndex takes real r returns integer
        loop
            return r
        endloop
        return 0
    endfunction

    function cleanInt takes integer i returns integer
        return i
    endfunction

    function mR2I takes real r returns integer
        return cleanInt( realToIndex( r ) )
    endfunction
Далле, делаем вот так:
function TestFunc takes real r returns nothing
    call BJDebugMsg(R2S(r))
endfunction

call TimerStart( CreateTimer( ), 0.50, true, SetCodeOneArg( function TestFunc, REAL, mR2I( 3,142857142857143 ) ) )
И спокойно передаем вещественные значения в аргумент.
Ну и последний тип, string - строки не такие уж простые обьекты как кажутся, это своего рода хендлы, ссылки в таблице строк, чтобы передать в аргумент строку нам потребуется функция из мемхака SH2I, которая находится в библиотеке Typecast, собственно пример кода ниже:

function TestFunc takes string msg returns nothing
    call BJDebugMsg(msg)
endfunction
...
call TimerStart( CreateTimer( ), 0.50, true, SetCodeOneArg( function TestFunc, STRI, SH2I("Ура, в попе дыра!") ) )
...
Так же в библиотеке имеется возможность передачи в функцию сразу двух аргументов, SetCodeTwoArg - code функция в которую хотим передать аргументы, тип аргумента1, аргумент1, тип аргумента 2, аргумент2.
Так же не забываем что у функции в которую мы хотим передать эти 2 аргумента, должно быть объявлено 2 аргумента соответствующего типа, ни больше ни меньше.
function TestFunc takes integer a, trigger trig returns nothing
    if a > 5 then
        call TriggerEvaluate( trig )
    endif
endfunction
...
call TimerStart( CreateTimer( ), 0.50, true, SetCodeTwoArg( function TestFunc, INTG, 10, HAND, GetHandleId( GetTriggeringTrigger( ) ) ) )
...
Всем спасибо за внимание!
Отдельное спасибо IseFog за посильную помощь.


Просмотров: 290

Diaboliko #1 - 4 недели назад 0
Невероятно удобно для использования БД + много таймеров (хотя БД + 1 таймер хуже лишь первоначальным кодингом системы).
Также, в принципе, очень юзабельно в узких местах (вроде передачи аргумента при использовании одного глобального таймера)
Еще не осознал есть ли в этом смыл, но геморно ли менять аргумент не перезапуская таймер?
quq_CCCP #2 - 4 недели назад 0
Diaboliko, все по тому же принципу что если бы ты выполнил функцию через call, таймер может быть периодическим, аргумент не теряется пока не вызвать таймер с другой функцией. Одновременно можно пихать в 1 функцию с разными значениями разным таймерам и все будет работать.
Этот код позволяет сравнительно просто передать аргумент в функцию таймера, чтобы не трахать себе мозг хештаблицами и структурами там, где это особо не надо. Так же это снижает кол-во писанины в разы.
Аргумент функции по сути локалка, так что пробуйте, мб пригодится.
Пока я писал ИИ меня осинило, как же надоедать вызывать всякие отсроченные дейсвтия, вроде приказал кастануть абилку и ждешь, докастует или нет, аттачить к таймеру каждый раз юнита.... гемор, при этом подобных задач куча, а просто как в гуях - указал юнита и запустил таймер, все...
Clamp #3 - 4 недели назад 0
По сути небольшой синтаксический сахар, передача параметров весьма легко реализуется и без него. Но за ресёрч определённо плюс!
quq_CCCP #4 - 4 недели назад 0
Clamp, совсем легко для ForGroup(), просто глобалки. А вот для триггеров и таймеров все не так уж и просто, хештаблица или структура.
Структуры не всегда годятся, плодить овер 100500 тоже не торт, хт - много лишней писанины.
Clamp #5 - 4 недели назад 0
плодить овер 100500 не торт
Ты фактически ничего не можешь "плодить" в JASS, он статичный.
quq_CCCP #6 - 4 недели назад 0
Clamp, да я не про то, нужно сначала описать структуру, которая нужна в 1 месте, 2 раза за игру... Таких мест может быть не один десяток.