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

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

 
DragonSpirit
у - уходи
offline
Опыт: 22,625
Активность:
Создание безопасного и эффективного кода(перевод)

О статье



Эта статья является переводом статьи Themerion с сайта wc3c
Это мой первый публичный перевод, поэтому просьба тапками не закидывать,и если вы нашли ошибку в переводе написать в теме,или мне в ПМ,что бы я её исправил
ссылка на саму статью
Благодарности:
Hellfim
tgonta

О чём пойдёт речь



Эта обучающая статья не рассчитана на новичка. Предполагается, что читатель уверенно владеет основами языка Jass и vJass. Кроме того, он должен знать про утечки, и как от них избавится.


Я не буду добавлять примеры к статье, т.к я не хочу просматривать wc3campaigns часами, что бы найти их. Если у тебя есть ссылки на результаты тестов или что - то подобное, пожалуйста, напишите тут

«План» статьи:


  • Краткая информация о защите
  • Группы юнитов
    - Создание / Уничтожение
    - Работа с циклами
  • Прикрепление(aka Аттач)
  • Индексирование юнитов
  • Динамические триггеры
    - Проблемы защиты
    - Действия против условий

Краткая информация о защите


  1. Использование UnitDamagePoint или UnitDamagePointLoc может вызвать десинк у mac пользователей.
  2. 8192-й слот во всех массивах будет забыт(пропущен) если игра сохранена и загружена. Это означает, что безопаснее будет использовать массив [0]..[8190]
  3. TriggerSleepAction (в GUI Wait(Ждать)) не работает, если игра остановлена при окне «Ожидание игрока» .
  4. Уничтожение таймера и последующее его обнуление иногда могут вызывать фатал, но вы же знаете это, правда?
  5. Функции returning nothing не могут быть переданы, как Фильтры/Условия, т.к это может вызвать десинк .

Группы юнитов



Создание / Уничтожение



Мы все знаем, что когда создаёшь локальную группу, то её надо уничтожить, что бы предотвратить утечки. Это так. Проблема состоит в том, что создание группы занимает некоторое время(хоть и не большое). Это имеет значение, если вы хотите использовать много групп периодически .
Кроме того, уничтожение группы юнитов в определённых ситуациях может вызвать утечки. Лучше будет, если вы будете использовать глобальную группу вместо локальной:

Код:
scope GroupEx

globals
    private group tempGroup
endglobals

function DoSomething takes nothing returns nothing
// Очищаем группу перед использованием
    call ClearGroup(tempGroup)

// Используйте tempGroup для того,что вам нужно!
// *** ПРИМЕР***
    call GroupEnumUnitsInRange(tempGroup,X,Y,500,null)
    call GroupIssuePointOrder(tempGroup,0,0,"attack")
endfunction

public function InitTrig takes nothing returns nothing
    //....
    set tempGroup=CreateGroup()
endfunction
endscope


Иногда только одной группы бывает не достаточно. Тогда ты можешь создать свой собственный стэк групп, при помощи любой структуры. Можно использовать это:
Код:
// Вместо того, что бы уничтожить группу
// просто очистите  и оставьте её для следующей структуры
struct Data
    group g

    method create takes nothing returns Data
        local Data d=Data.allocate()
        if d.g==null then
            set d.g=CreateGroup()
        endif
        return d
    endmethod

    method onDestroy takes nothing returns nothing
        call GroupClear(.g)
    endmethod
endstruct


Работа с циклами



Действие выбора всех юнитов из группы..это ForGroupBJ(), которая является «оболочкой» вокруг ForGroup(). Если у Вас уже есть группа, самый быстрый путь к циклу это действительно через ForGroup(). Используйте временные(уникальные) глобальные переменные, если надо.

Код:
globals
    private player temp
endglobals

private function Loop takes nothing returns nothing
    call SetUnitOwner(GetEnumUnit(),temp,true)
endfunction

function GroupChangeOwner takes group g, player newOwner returns nothing
    set temp=newOwner
    call ForGroup(g,function Loop)
endfunction



Если вам нужно сделать лишь одиночный цикл по группе, то не используйте ForGroup. Это очень важно для создания заклинаний. Ты можешь подключить цикл напрямую в вызове GroupEnum

Код:
globals
    private unit tempUnit
    private integer tempInt
    private group tempGroup
    private boolexpr cond
endglobals
//======================================================

private function Loop takes nothing returns boolean
    if IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(tempUnit)) then
        call UnitDamageTarget(tempUnit, GetFilterUnit(), tempInt, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
    endif
    return false
endfunction

function AOEDamage takes unit attacker, real x, real y, integer damage returns nothing
    set tempUnit = attacker
    set tempInt = damage
    call GroupEnumUnitsInRange(tempGroup,x,y,RADIUS,cond)
endfunction

//======================================================
private function Init takes nothing returns nothing
    set tempGroup=CreateGroup()
    set cond=Condition(function Loop)
endfunction


Прикрепление(aka аттач)



Используйте нестандартную функцию H2I(на новом патче нативка GetHandleId – примеч. переводчика) для трансформации чего- либо в целочисленное(integer).

Код:
function H2I takes handle h returns integer
    return h
    return 0
endfunction


Аттач используется для того, что бы прикрепить данные к объектам (есть несколько способов осуществить это). Для безопасности вам рекомендуется прикреплять только целые числа(integer) и структуры(struct). Иногда Warcraft ведёт себя довольно интересно, если вы прикрепляете(или восстанавливаете(вытаскиваете)) другие вещи, кроме целых чисел и структур. Поэтому желательно, если вы откажетесь от использования следующих вещей:
  • Handle Vars
  • CSCache

Естественно существуют более современные и устойчивые системы для аттача. Их много.Timer Utils (ссылка на наработку [1.24+]) - одна из самых популярных систем такого рода. Она позволяет прикреплять данные только к таймерам.

Цитата:
Сообщение от Vexorian(в теме про TimerUtils)
Прикрепление уникальных значений
Задание уникальных значений полезно для таймеров и триггеров. Но нам не нужны динамические триггеры, считается, что динамические триггеры - зло. Тогда можно сделать вывод, что использовать уникальные значения мы будем лишь для таймеров. Это не совсем верно, возможно, мы ещё используем их для юнитов или предметов, но в них уже есть пользовательские данные. Ещё существуют кнопки диалога, но в них скорость уже не так важна.


Разъяснение: когда он(Vexorian) говорит, что скорость не важна, он подразумевает, что вы должны найти другое решение чем нестандартное прикрепление(такое как gamecache/table). Если вам и вправду нужно быстрое прикрепление к чему – либо, кроме таймеров посмотрите HSAS, HAIL , или более старый CSData(для юнитов, используйте индексирование).

Пример прикрепления с помощью TimerUtils:
Код:
library Blah needs TimerUtils

struct EffectWrapper
    effect fx
endstruct

private function CET_2 takes nothing returns nothing
    local EffectWrapper ew=GetTimerData(GetExpiredTimer())
    call DestroySpecialEffect(ew.fx)
    call ew.destroy()
    call ReleaseTimer(GetExpiredTimer())
endfunction

public  function TimedEffect takes effect fx, real time returns nothing
    local timer t=NewTimer()
    local EffectWrapper ew = EffectWrapper.create()

    set ew.fx=fx
    call SetTimerData(t,ew)

    call TimerStart(t,time,false,function CET_2)
    set t=null
endfunction

endlibrary


Обратите внимание как TimerUtils связывает функции старого(ReleaseTimer(timer)) и нового(NewTimer()) таймера. Они работают более безопасно и быстро, нежели Создание/Уничтожение Таймера.


Индексирование юнитов



Прикрепление данных к юнитам довольно проблематично. Юнит может умереть/ разложиться/быть удалённым из игры, и мы должны удалить прикреплённые к нему данные. Однако, нет такого событие «Юнит удалён из игры»
Уже только поэтому нам нужно использовать систему индексации. Такие системы сосредотачиваются на юнитах и управляют процессом быстрого "связывания" числа с ними. Вместе PUI и DUI дадут функцию, которая для каждого юнита создаёт уникальный индекс. Затем можно использовать этот индекс для сохранения чего-либо в массив. DUI предлагает ещё и callback-функцию, когда замечает удаление юнита.

Простой подсчётчик убийств :

Код:
function CountKills_Actions takes nothing returns nothing
    local integer i = GetUnitIndex( GetTriggerUnit() )
    set killCount[i] = 1 + killCount[i]
endfunction


Динамические триггеры



Проблемы безопасности



Триггер, у которого есть свои атрибуты (событие/условие/действие), созданный скриптом, который запускается не при инициализации карты называется динамическим. Если вы не знаете о триггерах в Jass, порекомендую вам прочитать статью Vexorian'a.(Ссылка на статью)
После того, как вы сможете создавать триггеры на лету, вам нужно будет их уничтожать для того, чтобы избежать утечек памяти. Это плохо, так как, уничтожая триггер по определённым условиям (использованные или вызванные триггером wait-функции в коде) мы можем заставить два разных хендла получить один и тот же идентификатор.

Конечно, без динамических триггеров в некоторых случаях не обойтись. К примеру, узнать, получил ли юнит урон, можно лишь динамическими триггерами. Единственное решение - вообще их не уничтожать и принять как должное небольшие утечки. Лучше принять триггер с маленькой утечкой, чем вылетающую игру. Для аур и других событий, которые связаны с входом в дистанцию, используйте периодический GroupEnum.

Условия против действий



Триггерные условия работаю быстрее действий, но в условии вы не можете сможете ждать, однако выбор за вами

Код:
// Нестандартный спелл

private function Actions takes nothing returns boolean
    if GetSpellAbilityId() == 'char'  then
       call SetUnitOwner(GetSpellTargetUnit(),GetOwningPlayer(GetSpellAbilityUnit()),true)
    endif
    return false
endfunction

public function InitTrig takes nothing returns nothing
    local trigger trg=CreateTrigger()
    call TriggerRegisterAnyUnitEvent(trg,PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(trg, Condition(function Actions))
    set trg=null
endfunction


Спасибо за внимание


Отредактировано DragonSpirit, 05.11.2009 в 22:19.
Старый 04.11.2009, 23:36
tgonta
Pixelated Sora Coder
offline
Опыт: 3,160
Активность:
Из~DragonSpirit:
Это плохо, так как, уничтожая триггер по определённым условиям (использованные или вызванные триггером wait-функции в коде) мы можем заставить два разных хендла получить один и тот же идентификатор.
Услышал впервые.
Вообще, найс статья, пригодится прогрессирующим кодерам, некоторые вещи(как выше процитированная) я услышал впервые.
Старый 04.11.2009, 23:55
Hellfim
Новичок
offline
Опыт: 79,707
Активность:
Статья действительно хорошая, и будет полезна как новичкам в джасс-коддинге, так и "середнячкам". Перевод довольно хороший, в общем хорошая работа =)
Старый 05.11.2009, 00:36
KotoBog
Meow
offline
Опыт: 36,046
Активность:
Хоть я и понял далеко не всё, но выглядит аппетитно)
Молодец)
Ща ещё раз перечитаю, мб больше чем 40% дойдёт))
Старый 05.11.2009, 15:46
Enein
Silenced by ZlaYa1000
offline
Опыт: 43,453
Активность:
ForGroupe()
такой функции не существует)
Уничтожение таймера и последующая установка его переменной к нулевому указателю иногда может вызвать фатал
ололо? О_О
в последнем примере "enfunction"
Enein добавил:
ну а так впринципе статья ниочем...
Старый 05.11.2009, 16:04
DragonSpirit
у - уходи
offline
Опыт: 22,625
Активность:
Enein, спасибо,очепятки исправил
ну а так впринципе статья ниочем...
мб ты для себя ничего нового не узнал,но некоторые что то может и откроют
Старый 05.11.2009, 16:05
XOR

offline
Опыт: 38,159
Активность:
Неплохая статья, только перевод иногда шалит)
XiMiKs добавил:
Жаль только маленькая статья..
Старый 05.11.2009, 16:10
ShadoW DaemoN

offline
Опыт: 37,078
Активность:
Что-то мне подсказывает, что для хгм такого рода статья должна писаться отечественными, а не заграничными кодерами. Упоминание систем, сделанных ими же (то есть загранкодерами), считаю не совсем уместным, так как большинство не юзают их.
Также, если делаешь так:
Если вы не знаете о триггерах в Jass, порекомендую вам прочитать статью Vexorian'a.
то указывай ссылку на статью. А вообще лучше так НЕ делай.
Кроме того, статья не первой свежести (1 августа 2008 года), что как бы намекает нам о необходимости добавления и обновления информации по теме касательно патчей 1.24+. Практически все нестандартные системы аттача после выхода 1.24 нежно выкидываются.
Алсо, Адольфушка писал статью об оптимизации, в которой более подробно рассмотрен один из вопросов данной статьи.
В общем и в целом, я против данного варианта статьи.
Старый 05.11.2009, 16:13
DragonSpirit
у - уходи
offline
Опыт: 22,625
Активность:
ShadoW_DaemoN, про 1.24 я написал(в пункте про H2I)
Практически все нестандартные системы аттача после выхода 1.24 нежно
та же TimerUtils переведена на 1.24
сейчас добавлю ссылки на статьи
Старый 05.11.2009, 16:23
Enein
Silenced by ZlaYa1000
offline
Опыт: 43,453
Активность:
DragonSpirit, ты не ответил по поводу этого:
Уничтожение таймера и последующая установка его переменной к нулевому указателю иногда может вызвать фатал
помоему или ты что-то неправильно перевел, или автор статьи дугак -.-
Старый 05.11.2009, 16:31
DragonSpirit
у - уходи
offline
Опыт: 22,625
Активность:
Enein,
Destroying a timer and then setting its variable to null might sometimes cause a crash.
если я неправильно перевёл, укажите на ошибку - буду очень благодарен,хотя не исключён и второй вариант..
Старый 05.11.2009, 16:36
Hellfim
Новичок
offline
Опыт: 79,707
Активность:
Enein, так и есть, хотя я лично такого ни разу не встречал.
Старый 05.11.2009, 22:07
Enein
Silenced by ZlaYa1000
offline
Опыт: 43,453
Активность:
так и есть
доказательства?)
я специально переспросил у нескольких реально шарящих людей - все на 100% согласны с тем, что это бред
Функции returning nothing не могут быть переданы, как Фильтры/Условия, т.к это может вызвать десинк .
тоже бред, ибо jngp провернуть такое не даст
с этой статьи как минимум половину содержимого нужно выпилить) ибобред
Старый 05.11.2009, 22:17
adic3x

offline
Опыт: 108,439
Активность:
я помню был подобнй бред про то, что IsUnitType может вернуть не 1 и не 0, и поэтому надо проверять ее через сравнение с булькой. по моим личным исследованиям вар весьма православно обрабатывает подобные ситуации, считя все, что не равно 00h - true
Старый 05.11.2009, 22:24
Sunn
To feel joy, not be blue
offline
Опыт: 4,975
Активность:
Хм, немного непонятно с "Условия против действий". Не совсем уверен, что это хорошая идея... ну точнее что это всегда хорошая идея. Например, имея кондишн вида:
Код:
function Conditions takes nothing returns boolean
    local boolean b
    local integer i
    local real    r0
    local real    r1
    ...
    if GetSpellAbilityId () == abilityId then
        set b  = ...
        set i  = ...
        set r0 = ...
        set r1 = ...
        ...

который запускается каждый раз, когда для любого юнита на карте выполняется EVENT_PLAYER_UNIT_SPELL_EFFECT, будет выделятся память под набор локалок, что не есть хорошо.
Не лучше ли в таких случаях делать действия отдельно от условий?(время на аллокацию vs вызов Action)
Старый 07.11.2009, 17:35
DragonSpirit
у - уходи
offline
Опыт: 22,625
Активность:
Sunn,
однако выбор за вами
Старый 07.11.2009, 18:38
bee
vjass.optimizer
offline
Опыт: 16,615
Активность:
стать ниочем имхо а разве про таймеры он не приувеличил?
что возможен десинк... бред
ничего нового не узнал
Старый 23.11.2009, 03:43

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

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

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

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



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