Класс Dictionary
Что-то я в последнее время стал делать из JASS'а C++ :)
Предисловие
Данный класс - это ассоциативный массив, он является эквивалентом классов std::map в C++ и System.Collections.Generic.Dictionary в C#. Проще говоря, это массив, в качестве индексов в котором выступают не целые числа, а любые типы, которые Вы укажете.
Использование
Основное применение - создание баз данных. Скорость обращения к таким базам относительно линейного поиска тем выше, чем больше элементов в базе. Также в качестве индексов Вы можете использовать... таймеры. Да, это еще одна замена хэшу, однако применять ее стоит лишь тогда, когда одновременно существует много объектов. При малом количестве повышения производительности не будет.
Объявление типа
Думаю, можно не говорить, что следует скопировать триггер Dictionary себе в карту. Затем Вам нужно определить функции, которые будут сравнивать индексы. Создаются они по такому шаблону:[jass]library <Имя>
public function less takes <Имя типа> left, <Имя типа> right returns boolean
<Условие, если левый операнд меньше правого>
endfunction
public function equal takes <Имя типа> left, <Имя типа> right returns boolean
<Условие, если операнды равны>
endfunction
endlibrary[/jass]Разберем на конкретном примере - пусть нам нужен массив с индексатором integer:[jass]library IntegerComparison
public function less takes integer op1, integer op2 returns boolean
return op1 < op2
endfunction
public function equal takes integer op1, integer op2 returns boolean
return op1 == op2
endfunction
endlibrary[/jass]
После этого необходимо объявить массив нужного типа. Делается это с помощью следующей инструкции препроцессора:[jass]//! runtextmacro DeclareDictionary("<Тип индексатора>","<Тип значений>","<Наследуется ли индексатор от handle>","<Наследуется ли значение от handle>","<Имя библиотеки сравнения>","<Максимальный суммарный размер всех словарей данного типа>")[/jass]Например:[jass]//! runtextmacro DeclareDictionary("integer","unit","false","true","IntegerComparison","8191")[/jass]Эта инструкция объявит тип массива unit с индексом integer (integer не наследуется от handle, unit - наследуется, поэтому "false, true"). Для сравнения ключей будут использоваться функции из библиотеки IntegerComparison, размер всех словарей - 8191. Этот размер не стоит уменьшать - память Вы этим не сэкономите.
Обратите внимание: все значения макроса пишутся в кавычках; между запятыми и кавычками не должно быть пробелов!
Все необходимые приготовления сделаны, можно начинать кодить.
Работа с классом
Нужно просто объявить переменную типа Dictionary_<Тип индексатора>_<Тип значений> и инициализировать ее значением Dictionary_<Тип индексатора>_<Тип значений>.Create(), затем можно использовать ее как обычный массив:[jass]function Example1 takes nothing returns nothing
local Dictionary_integer_unit d = Dictionary_integer_unit.Create()
set d[0] = CreateUnit(...)
set d[1] = null
set d[-1] = CreateUnit(...)//Записываем по отрицательному индексу. Да, это возможно!
set d[100500] = CreateUnit(...)//И это тоже разрешено!
call KillUnit(d[0])
call RemoveUnit(d[-1])
//После использования, если массив временный, его необходимо уничтожить:
call d.Destroy()
endfunction[/jass]Однако гораздо чаще требуются словари, которые существуют всю игру и, следовательно, не требуют удаления. Их можно делать глобальными:[jass]globals
Dictionary_integer_unit MyDict
endglobals
function InitTrig_Example2 takes nothing returns nothing
set MyDict = Dictionary_integer_unit.Create()
set MyDict[0] = CreateUnit(...)
endfunction[/jass]Следует запомнить, что глобальный словарь, к сожалению, нельзя создать сразу же в globals.[jass]globals
Dictionary_integer_unit MyDict = Dictionary_integer_unit.Create()//Недопустимо!
endglobals[/jass]
Можно полностью очистить массив, не удаляя его:[jass]call MyDict.Clear()[/jass]Также есть свойство, показывающее количество элементов в данный момент:[jass]local Dictionary_integer_unit d = Dictionary_integer_unit.Create()
set d[0] = CreateUnit(...)
set d[1] = CreateUnit(...)
set d[2] = CreateUnit(...)
set d[3] = CreateUnit(...)
call BJDebugMsg(I2S(d.Count))//Выведет "4"
call d.Destroy()[/jass]После "Count" скобки не пишутся.
При попытке получить из словаря значение, которое не было туда помещено, будет возвращено значение по умолчанию. Вы также можете просто узнать, есть ли оно там:[jass]local Dictionary_integer_unit d = Dictionary_integer_unit.Create()
set d[0] = CreateUnit(...)
call BJDebugMsg(I2S(GetHandleId(d[2])))//d[2] пуст, поэтому будет возвращен юнит null, ID которого равен 0
if not d.Exists[2] then
call BJDebugMsg("По индексу 2 ничего не записано")
endif
call d.Destroy()[/jass]Если Вы делаете большую базу данных, которая не меняется всю игру, и Вам нужно ее максимальное быстродействие, Вам может оказаться полезным метод Rebuild, перестраивающий словарь для наиболее быстрого доступа к элементам. Следует учесть, что он достаточно медленный, поэтому после каждого обновления его вызывать не стоит.[jass]//! runtextmacro DeclareDictionary("integer","string","false","false","IntegerComparison","8191")
globals
Dictionary_integer_string SpellData
endglobals
function Trig_Spells_Actions takes nothing returns boolean
local string s = SpellData[GetSpellAbilityId()]
if s != null then
call ExecuteFunc(s)
endif
return false
endfunction
function InitTrig_Spells takes nothing returns nothing
local trigger trig = CreateTrigger()
local integer i = 0
loop
call TriggerRegisterPlayerUnitEvent(trig, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
exitwhen i == 15
set i = i + 1
endloop
call TriggerAddCondition(trig, Condition(function Trig_Spells_Actions))
set trig = null
set SpellData = Dictionary_integer_string.Create()
set SpellData['A000'] = "Trig_FireStorm_Actions"
set SpellData['A001'] = "Trig_IceWall_Actions"
set SpellData['A002'] = "Trig_PoisonTouch_Actions"
set SpellData['A003'] = "Trig_HandOfGod_Actions"
set SpellData['A004'] = "Trig_Timeback_Actions"
//...
call SpellData.Rebuild()
endfunction[/jass]Еще один способ применения, который я упомянул ранее - создание MUI-спеллов.[jass]//! runtextmacro DeclareDictionary("timer","TimebackStruct","true","false","HandleComparison","8191")
library HandleComparison
public function less takes handle op1, handle op2 returns boolean
return GetHandleId(op1) < GetHandleId(op2)
endfunction
public function equal takes handle op1, handle op2 returns boolean
return op1 == op2
endfunction
endlibrary
globals
Dictionary_timer_TimebackStruct TimebackDict
endglobals
struct TimebackStruct
unit u
real x
real y
endstruct
function Trig_Timeback_Timer takes nothing returns nothing
local timer t = GetExpiredTimer()
local TimebackStruct s = TimebackDict[t]
call SetUnitX(s.u, s.x)
call SetUnitY(s.u, s.y)
set TimebackDict[t] = 0
call DestroyTimer(t)
call s.destroy()
set t = null
endfunction
function Trig_Timeback_Actions takes nothing returns nothing
local TimebackStruct s = TimebackStruct.create()
local timer t = CreateTimer()
set s.u = GetSpellTargetUnit()
set s.x = GetWidgetX(s.u)
set s.y = GetWidgetY(s.u)
set TimebackDict[t] = s
call TimerStart(t, 5., false, function Trig_Timeback_Timer)
set t = null
endfunction
function InitTrig_Timeback takes nothing returns nothing
set TimebackDict = Dictionary_timer_TimebackStruct.Create()
endfunction[/jass]Важный момент - в таких случаях необходимо ВСЕГДА обнулять данные массивов, даже если это integer или real.
Взгляд изнутри
Этот раздел посвящен тем, кто хочет разобраться, как именно (и почему ) данная система работает.
Класс Dictionary сделан на основе бинарного древа, так что доступ к элементам осуществляется с логарифмической скоростью. При перестроении дерево раскладывается в массив, который сортируется методом расчески и снова собирается в дерево. Код не влез в размеры сообщения, но его можно скачать отдельно.
Если возникнут вопросы, задавайте, не стесняйтесь.