XGM Forum
Сайт - Статьи - Проекты - Ресурсы - Блоги

Форуме в режиме ТОЛЬКО ЧТЕНИЕ. Вы можете задать вопросы в Q/A на сайте, либо создать свой проект или ресурс.
Вернуться   XGM Forum > Warcraft> Академия: форум для вопросов> Желтая пресса: обучающие статьи
Ник
Пароль
Войти через VK в один клик
Сайт использует только имя.

 
Dragoon

offline
Опыт: 544
Активность:
Ограничение на количество действий в функциях JASS
P.S. В тексте будут использоваться функции из статьи http://xgm.guru/forum/showthread.php?t=4094&page=1
Часть 1
В JASS присутствует крайне неприятная особенность - у функций есть ограничение на количество действий. Легко понять это поможет следующий пример.
Код:
loop
        exitwhen i > 1000
        set j = 0
        loop
            exitwhen j > 500
            call BitwiseXOR( 999999, 999999 )       
            set j = j + 1
        endloop                
        set i = i + 1
    endloop

Дело в том, что функция BitwiseXOR вызывает еще одну функцию - BitwiseOperation с определенными параметрами. BitwiseOperation так же вызывает несколько пользовательских функций. Получается цепочка некоторой длины. И в какой-то момент времени внутренний счетчик действия варкрафта оказывается превышен и вся цепочка завершается. Причем, завершается не только BitwiseOperation или BitwiseXOR , но и сам триггер, в котором была вызвана эта функция. Примеров для подобных случаев можно привести множество, так как достаточно сложные заклятья или функции могут множество раз вызывать другу друга, использовать циклы и т.п.
Есть подозрение, что в варкрафте существует определенная "система ценностей" операндов. Т.е., к примеру, set ценится намного ниже call и следовательно его можно вставлять в фунции намного чаще.
Теперь рассмотрим как можно решить эту проблему.
1) Уменьшить "ценность" функции, т.е. убрать лишние действия, в общем выполнить оптимизацию.
2) Иногда помогает данный кусок кода
Код:
call TriggerSleepAction(0.00)

Логически это бессмысленно, т.е. мы 'усыпляем' нашу функцию на 0 секунд. Однако как это ни бессмысленно, это помогает... Вероятно когда мы вызываем эту функцию, варкрафт сбрасывает счетчик или уменьшает его.
Предложенные выше методы действенны, однако далеко не всегда помогают. В моем случае они были бессильны, поэтому пришлось искать дополнительные возможности решения. И решение было найдено. Варкрафт - многопоточное приложение, т.е. в нем может несколько действий выполняться одновременно (почти одновременно). К примеру у нас есть триггер, реагирующий на событие - применение юнитом определенной способности. Если два и более юнитов применят одновременно способность, то варкрафт запустит сразу несколько копий этого триггера ( именно эта особенность заставляет использовать локальные переменные ). Причем, для каждого нового триггера создается свой собственный счетчик. В этом и заключается вся суть решения. Мы создаем функцию-оболочку, назначение которой - лишь запустить какое-либо ресурсоемкое действие, которое нам было необходимо выполнять в цикле. В моем случае это
Код:
function BitwiseXOR_Shell takes nothing returns nothing
   call BitwiseXOR( 999999, 999999 )
endfunction

Далее мы динамически создаем триггер и добавляем ему в качестве действия только что созданную функцию. Теперь в коде вместо прямого вызова функции BitwiseXOR мы будет использовать вызов триггера, который и инициирует выполнение этой функции.
Код:
loop
        exitwhen i > 1000
        set j = 0
        loop
            exitwhen j > 500
            //ttrigger - динамически созданный триггер
            call TriggerExecute(ttrigger)       
            set j = j + 1
        endloop                
        set i = i + 1
    endloop

Подобную конструкцию уже можно использовать и она даже будет работать, но в какой-то момент времени вы заметите баги. Дело в том, что, как я уже писал, каждый вызываемый триггер запускается в отдельном потоке, и варкрафт вполне может успеть запустить в цикле несколько копий триггера ttrigger. А вот это уже совсем нехорошо. Будем избавляться... Я предлагаю использовать глобальную переменную-флаг, которую мы будем устанавливать в False перед запуском функции TriggerExecute, а затем в коде функции-оболочки будем заносить в переменную True, что означает - код функции завершился. К цикле же мы будем ждать, пока переменная не станет равной True и только тогда можно запускать еще раз динамически созданный триггер.
Код:
loop
        exitwhen i > 1000
        call TriggerSleepAction(0)            
        set j = 0
        loop
            exitwhen j > 500
            //сбрасываем флаг            
            set udg_Flag = false   
            //запускаем триггер         
            call TriggerExecute(ttrigger)
            //ждем, пока флаг не станет равен True
            loop
                exitwhen (udg_Flag == true)
            endloop  
            //заканчиваем цикл          
            set j = j + 1
        endloop                
        set i = i + 1

Код функции-оболочки в данном случае меняется на
Код:
function BitwiseXOR_Shell takes nothing returns nothing
   call BitwiseXOR( 999999, 999999 )
   set udg_Flag = true
endfunction

Приведенный пример уже будет работать, причем выполнять подобным способом действия в цикле можно огромное количество раз, не боясь, что код аварийно завершится.
Рассмотрим недостатки системы
1) Так как функция оболочка не может получать параметров, то функция вызывается с предустановленными значениями.
Методы решения:
а) Использование глобальных переменных-контейнеров для передачи параметров
б) Использование Return Bug и SCV (как это можно использовать - далее)
2) Появление в функции побочного кода, соответственно он становится менее воспринимаем другими людьми. Методов решения естественно нет, так как без этого самого кода ваши функции просто не будут работать.

Часть 2
Я написал несколько функций, которые упрощают работу с динамическими триггерами и передачей функциям-оболочкам параметров. В них я использовал Retrun Bug и SVC. Кратко о том, что это вообще такое.
1) Return Bug - позволяет получить адрес любого объекта
2) SVC - Super Custom Value . Идея заключается в том, чтобы использовать уникальный адрес объекта в качестве общего ключа при обращении к кэшу. Позволяет сделать любой объект хранилищем переменных.
Подробнее читайте в статьях Sergey'а, DimonT'а и NETRAT'а.

Перед использованием функций необходимо создать глобальную переменную типа gamecache(Буфер игры) и триггер следующей структуры:
Код:
События : Map Initialization
Условия :
Действия : 
Буфер игры - Create a game cache from cscache.w3v
Set cscache = (Last Created game cache)

Теперь собственно к самому коду. Первые функции всего лишь позволяют нам получать адреса объектов и функций.
Код:
//адрес триггера
function GetTriggerAddr takes trigger ttrigger returns integer
return ttrigger
return 0
endfunction

//получить триггер по адресу
function GetTriggerByAddr takes integer ttrigger_addr returns trigger
return ttrigger_addr
return null
endfunction

//адрес функции
function GetFunctionAddr takes code tcode returns integer
return tcode
return 0
endfunction

//получить функцию по адресу
function GetFunctionByAddr takes code tcode_addr returns trigger
return tcode_addr
return null
endfunction

//получить ассициированный с функцией триггер 
//P.S. пока самое слабое место системы
function GetFunctionOwner takes code tcode returns trigger
return GetTriggerByAddr( S2I( GetStoredStringBJ("_TriggerAddr", I2S(GetFunctionAddr(tcode)), udg_cscache)))    
endfunction

Следующие функции отвечают за создание-уничтожение динамических триггеров.
Код:
//создать триггер и добавить в него действие
function CreateDynamicTrigger takes code tcode returns trigger
local trigger ttrigger=null
set ttrigger = CreateTrigger()  
call TriggerAddAction( ttrigger, tcode)  
call StoreStringBJ(I2S(GetTriggerAddr(ttrigger)), "_TriggerAddr", I2S(GetFunctionAddr(tcode)),  udg_cscache)
return ttrigger
endfunction

//удалить триггер и связанные с ним переменные
function FlushDynamicTrigger takes trigger ttrigger returns nothing
call FlushStoredMissionBJ(I2S(GetTriggerAddr(ttrigger)), udg_cscache)
call DestroyTrigger(ttrigger)
endfunction

Теперь несколько функций, позволяющий передавать параметры.
Код:
//сохранить в кэше триггера переменную с именем name и значением value
function SetTriggerExecutionParameters takes trigger ttrigger, string name, string value returns nothing
call StoreStringBJ(value, name, I2S(GetTriggerAddr(ttrigger)),  udg_cscache)    
endfunction

//вытащить из кэша триггера переменную с именем name
function GetTriggerExecutionParameters takes trigger ttrigger, string name returns string
return GetStoredStringBJ(name, I2S(GetTriggerAddr(ttrigger)), udg_cscache)    
endfunction

//Две специальных функции для установления/сброса флага выполнения

//установить значение флага 
function SetTriggerFlag takes trigger ttrigger, boolean value returns nothing
call StoreBooleanBJ(value, I2S(GetTriggerAddr(ttrigger))+"_Flag", I2S(GetTriggerAddr(ttrigger)),  udg_cscache)    
endfunction

//получить значение флага
function GetTriggerFlag takes trigger ttrigger returns boolean
return GetStoredBooleanBJ(I2S(GetTriggerAddr(ttrigger))+"_Flag", I2S(GetTriggerAddr(ttrigger)),  udg_cscache)    
endfunction

И последняя функция отвечает за запуск потока и возвращение в главный код лишь по выполнении функции-оболочки
Код:
//запустить триггер с функцией-оболочкой
function RunThreadAndWait takes trigger ttrigger returns nothing
call SetTriggerFlag(ttrigger, false)
call TriggerExecute(ttrigger)
loop
    exitwhen (GetTriggerFlag(ttrigger)==true)
endloop    
endfunction

Как видно, все глобальные переменные теперь перенесены в кэш. И кстати, теперь в функции-оболочке ОБЯЗАТЕЛЬНО в конце должна стоять строка
Код:
call SetTriggerFlag(GetFunctionOwner(function BitwiseXOR_Shell), true)

Эта строка устанавливает флаг выполнения в True, что и говорит функции RunThreadAndWait о том, что можно возвращаться в главный поток.

В примере приведены пять триггеров.
1) Init - инициализирует кэш и запускает триггер-пример, показывающий битовые операции
2) Execution 2 - без использования новых функций и возможностей.
3) Execution 1 - с ними.
4) Execution 3 - показывает, что вообще без использования каких-либо доработок
триггер не работает корректно
5) Test - всего лишь пример работы битового модуля
Для просмотра результатов необходимо выключать один из них. Даже два сразу работать НЕ БУДУТ, так как используют одну и ту же переменную для создания плавающего текста. Смысла делать их параллельными не вижу, поэтому оставил все так, как есть.
Прикрепленные файлы
Тип файла: w3x BitwiseOperations.w3x (23.5 Кбайт, 22 просмотров )

Отредактировано Dragoon, 03.05.2006 в 18:44.
Старый 03.05.2006, 15:21
remal
нечто
offline
Опыт: 2,087
Активность:
потоки в варе выполняются по очереди. каждый поток выполняется либо определённое время (и завершается от Watchdog Timer'a), либо до вызова triggersleepaction/sleep. также переход "очерёдности" происходит при использовании executefunc. поэтому вводить глобальную переменную не надо.
Старый 04.05.2006, 14:47
Dragoon

offline
Опыт: 544
Активность:
Но почему тогда необходимо применять локальные переменные в способностях и т.п. ? Если бы триггеры выполнялись по очереди, то глобалки были бы панацеей. Я не прав ?

Dragoon добавил:
Кстати, от глобальной переменной я отказался в части 2. Там создается связанная с созданным триггером переменная-флаг. Да и если ее полностью удалить, то в целевой функции станут невозможны функции TriggerSleepAction и ExecuteFunction , как ты сам и писал... А так файтически нет ограничений на исполняемый код
Старый 04.05.2006, 15:05
NETRAT

offline
Опыт: 83,712
Активность:
О, нет, не по очереди, а в потоках, только фиг его знает как менеджер их выполняет в варе
Старый 05.05.2006, 01:43
FellGuard
Losyash
offline
Опыт: 39,547
Активность:
NETRAT, труе, не по очереди. Для каждого игрока свой поток АИ, плюс триггеры, когда одновременно запускаются несколько штук, плюс главный поток - если бы все это было бы по очереди, то Вар был бы на уровне тетриса - например, АИ на более чем 3 человека уже начинал бы тормозить и глючить с принятием решений и выдачей приказов.. Такого нет.
Старый 05.05.2006, 10:17
Dragoon

offline
Опыт: 544
Активность:
Вот я про то и говорю...
Старый 05.05.2006, 22:05
Toadcop

offline
Опыт: 54,313
Активность:
Dragoon ну это уже было известно ! т.е. все большие лооп конструкции надо Ексекутить :) а том он вешает их и всё :) да Близзы мастеры своего дела :) !
Старый 06.05.2006, 20:09
Dragoon

offline
Опыт: 544
Активность:
Toadcop, целью статьи было показать конкретный метод решения и его реализацию, а не рассказывать об этой неприятности... :)
Старый 10.05.2006, 13:29

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы можете скачивать файлы

BB-коды Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход



Часовой пояс GMT +3, время: 18:15.