| Дата: Вторник, 18.10.2011, 00:15 | Сообщение # 1 |
Генералиссимус
Сообщений: 2438
|
Утечки или Blizzard - Говнокодеры
Что нам необходимо знать, для понимания этой статьи? 1) Знание ЛЮБОГО языка программирования на уровне ПТУ 2) Уметь пользоваться WinMPQ (Скачать) 3) Знать что такое локальные переменные.
Конечно же те, кто прочитали заголовок статьи заинтересовались словосочетанием: «Blizzard – говнокодеры». Наверняка подумали, что это понт какой-то, однако речь пойдет именно о косяках сделанных самими разработчиками WarCraft III.
Кому эта статья полезна?
Эта статья полезна тем, кто начал изучать jass, тем, кто хочет научится писать собственные функции и не менее интересна тем, кто хочет делать карту (пусть и на ГУИ + не хочет писать своих функций) без утечек.
Зачем писать свои функции для карт WarCraft III ?
Пожалуй, именно косяки самих разработчиков и являются мотивом к написанию своих собственных функций. Некоторые скажут, что делают свои функции, чтоб упростить себе кодинг и расширить возможности стандартного редактора, это тоже верно.
Синтаксис функции и пример её использования И так… разберемся на примере:
Необходимо создать магический спел, который отнимает ровно половину от жизней цели. Т.е. У героя 1000 хитов, делаем спел – у героя 500, делаем ещё раз – 250 и тд. Конечно, такое можно сделать триггером и даже без переменных, но мы рассмотрим, как это сделать с помощью своей функции.
Никаких сложных действий тут не будет, вся функция будет представлять из себя всего 1ну строку, но в конце статьи эта строка превратится в несколько.
Для начала разберемся, где писать нашу функцию. Открываем редактор карт, заходим в триггеры и кликаем по значку карты, в нижнем поле справа мы и будем писать код.
Вот, как выглядит функция: function TestFun takes unit U1 returns nothing call SetUnitLifeBJ( U1, ( GetUnitStateSwap(UNIT_STATE_LIFE, U1) / 2.00 ) ) endfunction
function – оператор, который сообщает компьютеру, что мы начинаем писать функцию. Endfunction – оператор, который сообщает компьютеру, что мы закончили функцию. TestFun – Имя функции, оно может быть каким угодно, но обязательно без пробелов и начинаться с буквы, нельзя использовать русские буквы. Takes – Оператор, который сообщает компьютеру, что далее идут те данные которые необходимы для выполнения функции. В нашем случае это Юнит, чьи хитпоинты делятся пополам. Unit – Тип переменной U1 – Название переменной, оно может быть каким угодно, но обязательно без пробелов и начинаться с буквы, нельзя использовать русские буквы. Returns - Оператор, который сообщает компьютеру, что далее идут те данные которые можно получить после выполнения функции. В нашем случае не будет никаких выходных данных. Вот если бы задачей нашей функции было бы решить уравнение, то выходной информацией был бы корень уравнения. Nothing – дословный перевод: «ничего». Т.е. оператор, который говорит, что ничего делать не надо. Далее идет тело функции, в котором изменяется кол-во жизни для выбранного юнита.
Как узнать какие функции есть и как их писать?
Самый простой, на мой взгляд, способ – написать триггер на ГУИ, а затем при помощи меню «правка» -> конвертировать в текст. Таким образом, можно создавать триггер с одним только действием и глянуть, как он пишется буквами.
Как использовать написанную функцию?
Наш триггер будет выглядеть следующим образом:
Событие: Юнит – приводит способность в действие Условие: Ability Comparison – (Ability being cast) = наша способность Действие: Кустом скрипт: call TestFun(GetSpellTargetUnit())
сall – это оператор, который сообщает, что далее идет какое либо действие (не присвоение), помните, что jass регистрозависимый и операторы call и set пишутся с маленькой буквы ВСЕГДА.
TestFun – имя нашей функции GetSpellTargetUnit() – юнит чьи хиты мы делим. В данном случае, это юнит, который стал целью нашего заклинания.
Всё?
Нет, теперь поговорим об утечках. Казалось бы чему тут и куда утекать? Может вы мне не поверите, но тут 2 утечки) Одну нашли? Давайте её исправим.
Те кто с утечками борются, но не делают своих функций сразу же догадались, что утечка происходит из-за переменной U1. Обратите внимание:
function TestFun takes unit U1 returns nothing call SetUnitLifeBJ( U1, ( GetUnitStateSwap(UNIT_STATE_LIFE, U1) / 2.00 ) ) endfunction
У нас есть переменная типа unit, она локально создается для каждого выполнения функции. Т.е. сколько раз мы используем спел, столько переменных типа юнит будет висеть у компьютера в памяти. Для того чтобы это исправить, переменную необходимо обнулять, для этого добавим: Set U1 = null И получится: function TestFun takes unit U1 returns nothing call SetUnitLifeBJ( U1, ( GetUnitStateSwap(UNIT_STATE_LIFE, U1) / 2.00 ) ) set U1=null endfunction Вот, одну утечку мы устранили. Кто сможет найти вторую, тот смело может взять пирожок с полки, да самый вкусный.
Blizzard – Говнокодеры По заголовку можно догадаться, что как раз таки во второй утечке виноваты создатели варика. Дело в том, что те операторы, на конце которых приписано «BJ» это тоже функции! Только их создавали не мы, а программисты из Blizzard. В нашей функции есть такая, это: SetUnitLifeBJ Но как же заглянуть во внутрь этих функций? Для этого вам и понадобится WinMPQ (Скачать). 1) Запускаем WinMPQ 2) Открываем файл из папки варика War3.mpq 3) Находим там файл Scripts\Blizzard.j
4) Открываем с помощью блокнота.
И так, перед вашими глазами все встроенные функции «нагишом». Теперь давайте узнаем, из чего состоит SetUnitLifeBJ. Для этого откройте поиск по файлу (ctrl+F), введите в окошко поиска: SetUnitLifeBJ.
Вы найдете это: function SetUnitLifeBJ takes unit whichUnit, real newValue returns nothing call SetUnitState(whichUnit, UNIT_STATE_LIFE, newValue) endfunction
Ну как?) Нашли вторую утечку? Как и в нашей функции, здесь создается переменная типа unit и с названием whichUnit Она не обнуляется. В итоге, когда вы изменяете какому либо юниту хитпоинты стандартным триггером, выполняется именно этот код, который засерает память ПК.
Как решить эту проблему?
Вообще есть 3 способа: 1)забить на все эти утечки и идти с миром. 2)Исправить код прямо в этом файле и переархивировать его. Вариант хорош, но ведь это исправит ситуацию лишь у вас на компе, но не у всех. 3)Написать свою такую же функцию без утечки. Этим мы и займемся. Теперь надо подумать как же так переделать функцию, что бы она у нас работала без утечек и выполняла нужную нам задачу. В данном примере всё элементарно, мы просто заменяем функцию SetUnitLifeBJ на SetUnitState. Как мы видим, SetUnitState использует всего 3 параметра: whichUnit – Юнит, для которого изменяется что-то UNIT_STATE_LIFE – параметр юнита, который изменяется, в данном случае обозначает текущее кол-во хитов. (его можно заменить на UNIT_STATE_MANA (если необходимо работать с маной юнита)) newValue – число, к которому будет приравниваться кол-во хитпоинтов.
В результате, если мы заменим SetUnitLifeBJ на SetUnitState, то получим: function TestFun takes unit U1 returns nothing call SetUnitState( U1,UNIT_STATE_LIFE, ( GetUnitStateSwap(UNIT_STATE_LIFE, U1) / 2.00 ) ) set U1=null endfunction
Вот так вот Устранили обе утечки и теперь у нас есть собственная функция, которая делит жизни юнита пополам и при этом не создает утечек памяти.
Вся проблема BJ функций в том, что парой используются через чур запутанные функции, и переменные в них никогда не обнуляются.
Для примера «запутанной функции» посмотрим на простейшую функцию (создать юнита): CreateNUnitsAtLoc Заметили, что нету BJ? Да-да Близы не все свои функции заклеймили, смотрим, что там внутри: function CreateNUnitsAtLoc takes integer count, integer unitId, player whichPlayer, location loc, real face returns group set bj_lastCreatedGroup = CreateGroup() loop set count = count - 1 exitwhen count < 0 call CreateUnitAtLocSaveLast(whichPlayer, unitId, loc, face) call GroupAddUnit(bj_lastCreatedGroup, bj_lastCreatedUnit) endloop return bj_lastCreatedGroup endfunction
Тут целый винегрет ) Игрок, точка. Код в принципе ясен и он будет полезен при создании нескольких юнитов, а если нам нужен всего один? Тогда тут лишний цикл, лишние операторы для работы с группой. Думаете всё? А хрен там: CreateUnitAtLocSaveLast это тоже функция BJ:
function CreateUnitAtLocSaveLast takes player id, integer unitid, location loc, real face returns unit if (unitid == 'ugol') then set bj_lastCreatedUnit = CreateBlightedGoldmine(id, GetLocationX(loc), GetLocationY(loc), face) else set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face) endif
return bj_lastCreatedUnit endfunction
Зрелище не для слабонервных, не правда ли? )) А знаете, как всё это исправить? Создать свою функцию! )) Даже если она будет выглядеть так:
function MyFun takes player id, integer unitid, location loc, real face returns unit set bj_lastCreatedUnit = CreateUnitAtLoc(id, unitid, loc, face) endfunction
То работать будет быстрее и наделает чуть ли не в двое меньше утечек. (но работать будет для создания одного юнита. Для адаптации к производству нескольких юнитов, можно убрать «set bj_lastCreatedUnit =» И запихнуть в цикл: call CreateUnitAtLoc(id, unitid, loc, face) Вообще «set bj_lastCreatedUnit =» необходимо писать, только если вы хотите в триггере распознать этого юнита, как последнего созданного (LastCreatedUnit()). Если вам это не надо, то функция может выглядеть так:
function MyFun takes player id, integer unitid, location loc, real face returns nothing call CreateUnitAtLoc(id, unitid, loc, face) endfunction
Если устранять утечки, то так:
function MyFun takes player id, integer unitid, location loc, real face returns nothing call CreateUnitAtLoc(id, unitid, loc, face) call RemoveLocation(loc) set loc = null set id = null endfunction
Вот и всё. Буду рад если кто-то доработает функцию в создание группы юнитов так же без утечек. Поставлю + в репу т.к. на wc3 не знаю, кто кроме меня эту статью поймет (
Как правильно устранять утечки памяти?
Для этого нужно понимать, что существуют в памяти компьютера объекты варика это юниты, регионы, декорации, спецэффекты. Для работы с этими объектами мы создаем или же код автоматически генерирует переменные, которые являются ссылками или ярлыками к тем или иным объектам. Т.е. мы создали переменную типа unit и назвали её U1. Теперь те действия, что мы адресуем к U1 будут произведены с юнитом, на который ссылается U1. Однако, если мы обнулим U1, (set U1 = null), То мы удалим сам ярлычок, но никак не объект. В большинстве случаев это и не надо, но если в переменной был юнит, которого убили или надо убить, или он больше нам не нужен, то сперва необходимо удалить юнита через команду: call RemoveUnit(U1) и только потом set U1 = null Точно так же с точками, декорациями и спецэффектами. Особенно со спецэффектами и точками, т.к. в большинстве случаев они используются лишь на 1но действие триггера.
Такие типы переменных, как целочисленная, реальная, текстовая – не обнуляются, оно в принципе и не нужно, но если число или текст огромны, то лучше к тексту присвоить пустую строку, а к числу 0. set text = ‘’ set int = 0 set real = 0.00 Эти переменные не так опасны, потому и обнулять их нет необходимости.
|
|
|
|
| Дата: Среда, 15.02.2012, 09:44 | Сообщение # 5 |
Генералиссимус
Сообщений: 5123
|
Quote (DonLaonda) Пожалуй, именно косяки самих разработчиков и являются мотивом к написанию своих собственных функций. Некоторые скажут, что делают свои функции, чтоб упростить себе кодинг и расширить возможности стандартного редактора, это тоже верно. Ага, хотите сказать можно сделать свою систему диалогов, как, например в Готике? Ну, знаете, чтобы не париться с переменными, нажал на диалоговую кнопочку, нпс сказал текст, а она - исчезла?
Телеграм @Tshkn Мой YouTube-канал Se Squared
|
|
|
|