WarCraft 3: 14. Структуры

vJass

Содержание главы:

Введение

Структуры вводят в Jass объектно ориентированную парадигму.
Я не смогу объяснить их без примера
struct Point
	real x 
	real y
endstruct

// Действия произвольного триггера
function TriggerActions takes nothing returns nothing
	local unit caster = GetTriggerUnit()
	local Point p = Point.create()
	set p.x = GetUnitX(caster)
	set p.y = GetUnitY(caster)
    call BJDebugMsg("Caster's coordinates are "+R2S(p.x)+" : "+R2S(p.y))
    call p.destroy()
	set caster  = null
endfunction
Как вы можете видеть, структуры позволяют объединять наборы данных, впоследствии используя имя структуры в качетсве типа данных, как любой другой тип данных в Jass. Обратите внимание, что для получения доступа к членам структуры используется специальный оператор "точка", подобно прочим языкам программирования.

Объявление

Прежде использования структур их необходимо объявить. Синтаксис достаточно прост и очень схож с объявлением прочих глобальных конструкций (переменные, области, библиотеки). Для объявления структуры используется ключевое слово struct, за которым следуем имя структуры. Блок кода структуры должен завершаться ключевым словом endstruct. Данный процесс достаточно наглядно показывает предыдущий пример
struct Point
	real x = 0.0
	real y = 0.0
endstruct
Объявление переменных внутри блока структуры схоже с объявлением глобальных переменных, они так же могут быть инициализированы значением по умолчанию:
<тип> <имя> [= значение по умолчанию]
Таким образом, каждый экземпляр нашей структуры Point будет иметь уже инициализированные, указанными значениями, атрибуты x и y.

Создание и уничтожение экземпляров

Структуры псевдо-динамичны. Прежде использования, вам необходимо создать экземпляр структуры, присвоить этот экземпляр переменной и по завершению использования уничтожить его.
Синтаксис создания экземпляра структуры:
	local Name p = Name.create()
где Name - имя структуры.
В ранее представленном примере структуры Point, мы создаём экземпляр и сразу присваиваем его переменной p:
	local Point p = Point.create()
Структуры имеют ограничение в количестве возможных экземпляров - вы не можете создать более 8190 экземпляров одной структуры (о причинах далее). Поэтому, необходимо обязательно уничтожать отработанный и более не используемый экземпляр структуры. Для этого предусмотрен следующий синтаксис:
	call p.destroy()
где p - ранее созданный экземпляр структуры (из примера выше).

За кулисами

Как уже неоднократно говорилось, JassHelper - препроцессор языка vJass. Он не является хаком для Warcraft 3 и потому не способен ввести какие-либо совершенно новые возможности в игровой движок и интерпретатор языка Jass. Все написанные языком vJass конструкции преобразуются в обычный Jass код. То же касается структур.
Каждая vJass-структура в конечном итоге развертывается в набор параллельных Jass-массивов. То есть, следующий код
struct Point
	real x = 0.0
	real y = 0.0
endstruct
Преобразуется в следующий Jass-код
globals
	//JASSHelper struct globals:
	constant integer si__Point=1
	integer si__Point_F=0
	integer si__Point_I=0
	integer array si__Point_V
	real array s__Point_x
	real array s__Point_y
endglobals
где массивы s__Point_x и s__Point_y и есть x и y атрибуты нашей структуры.
Более того, для каждой структуры будет сгенерирована функция-аллокатор. Функция-аллокатор вызывается в момент создания экземпляра структуры методом .create() или .allocate(). Внутри этой функци определяется незанятый индекс в массивах, в которые развертывается наша структура. Если операция прошла успешно, функция-аллокатор возвращает целочисленное значение - незанятый в массивах индекс.
Из вышесказанного вытекает следующее:
  1. Количество возможных экземпляров одной структуры ограничено максимальным размером Jass-массива. Поскольку структуры развертываются в набор параллельных массивов, очевидно, мы ограничены размерностью этих массивов, которая имеет предел в 8192 ячейки, следовательно, мы не можем создать более 8192 экземпляра одной структуры. Прим. пер: на самом деле последнее значение индекса в массивах при развертывании структур - 8190. На это нет резонных причин, просто так генерируется условие при котором выделение памяти под новый экземпляр структуры прерывается (this>8190).
  1. Переменная типа структуры хранит целочисленное значение. Опять же - структуры развертываются в параллельные массивы, а функция-аллокатор возвращает незанятый в этих массивах индекс. Соответственно, в нашем примере:
	local Point p = Point.create()
переменная p имеет целочисленный тип, а сам тип Point, это псевдоним для типа integer, используемый для сопоставления с типом структуры. То есть, вы можете использовать переменную типа структуры как в арифметических операциях, так и для их хранения, например, в хэш-таблице (SaveInteger). Отсюда вы уже можете сделать предположение о существовании следующего пункта.
  1. Значение 0 зарезервировано как указатель на нулевую структуру. Подобно тому, как вы сравниваете наследников типа handle (unit/player/group и пр.) со значением null, вы можете сравнить значение переменной типа структуры с 0. Таким образом, максимальное количество экземпляров одной структуры уменьшается на 1, поскольку индекс 0 зарезервирован в качестве указателя на нулевую структуру (подобно null для типов handle).
  1. Необходимо уничтожать более не нужный экземпляр структуры. Вновь повторяясь - количество экземпляров одной структуры ограничено размерностью Jass-массива. Следовательно, только создавая экземпляры мы в конечном итоге превысим допустимый предел. В таком случае, функция-аллокатор начнет возвращать значение 0 при попытке создать новый экземпляр. По этой причине, отработанный экземпляр структуры необходимо уничтожать, используя метод .destroy()
	call p.destroy()
в результате чего будет вызвана функция-деаллокатор (так же как и аллокатор, генерируемая парсером), которая освободит занятый этой структурой индекс.
Все вышесказанное может звучать довольно мрачно, однако следует понимать, что 8190 экземпляров, это очень большое число. Если ваша структура используется способностью, в один момент времени вряд ли будет существовать более 9 экземпляров одного типа. Даже самые большие и сложные системы не требуют единовременного существования более 2000 экземпляров. И даже если предположить, что если мы не уничтожаем ненужные экземпляры, создавая по одному раз в секунду, пройдет порядка 2.5 часов, прежде чем лимит будет превышен.
Ну и наконец, если вы все таки вышли за пределы лимита и у JassHelper активирован режим отладки (Debug Mode), вы получите об этом сообщение в игре.

Использование

Для использования объявленной структуры, просто укажите ее имя там, где вы указали бы имя любого другого типа - в качестве типа переменной, типа аргумента функции, возвращаемого типа и пр.
Следующий пример демонстрирует полярное смещение с применением структур (бесполезный, но наглядный):
struct Point
    real x = 0.
    real y = 0.
endstruct

function GetPolarOffset takes Point p, real angle, real dist returns Point
    local Point result = Point.create()
    set result.x = p.x + Cos(angle*bj_DEGTORAD)*dist
    set result.y = p.y + Sin(angle*bj_DEGTORAD)*dist
    return result
endfunction

function Actions takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local Point p1 = Point.create()
    local Point p2

    set p1.x = GetUnitX(u)
    set p1.y = GetUnitY(u)
    set p2 = GetPolarOffset(p1, GetUnitFacing(u), 100.)
    call SetUnitX(u, p2.x)
    call SetUnitY(u, p2.y)

	// Не забудьте уничтожить более не нужный экземпляр структуры!
    call p1.destroy()
    call p2.destroy()

    set u = null
endfunction
Здесь некоторый GetTriggerUnit будет перемещен на 100 ед. в направлении своего взгляда.

Глобальная переменная типа структуры

Вы можете объявлять глобальные переменные типа структуры. Но в результате ограничений Jass вы не можете их инициализировать по месту объявления
globals
     Point P1 = 0 			  //Ок, нулевое значение
     Point P2 = pair.create() // Ошибка!
endglobals
Инициализировать их придется где-то еще, например, внутри функции инициализации.

Публичные и приватные структуры

Внутри областей и библиотек вы можете объявить публичной или приватной как саму структуру, так и переменную типа структуры. Никаких особых правил за этим не следует, модификатор доступа проделает со структурами ту же работу, что и с прочими конструкциями внутри области/библиотеки
scope TestScope initializer Init
    public struct Point
        real x = 0.
		real y = 0.
    endstruct

    globals
        Point A
        public Point B
    endglobals

    private function Init takes nothing returns nothing
        set A = Point.create()
        set A.x = 3.
        call A.destroy()
    endfunction
endscope

function TestFunc takes nothing returns nothing
	local TestScope_Point p = TestScope_Point.create()
    set p.x = 6.
    call p.destroy()

	set TestScope_B = TestScope_Point.create()
	call TestScope_B.destroy()
endfunction

Атрибуты

Итак, вы можете объявлять атрибуты внутри структуры. Атрибуты могут быть любого типа, даже типа другой структуры. Одно ограничение - вы не можете инициализировать атрибут типа структуры во время объявления.
struct Point
	real x = 0.
	real y = 0.
endstruct

struct Rectangle
	Point leftTop = 0 				// Ок, нулевое значение.
	Point rightBot = Point.create() // Ошибка!
endstruct

Аттрибуты-массивы

Атрибутами структур могут выступать массивы. Однако, в таком случае, необходимо указывать размерность массива:
struct stack
   private integer array V[100]
   private integer N = 0

   method push takes integer i returns nothing
      set this.V[this.N] = i
      set this.N = this.N+1
   endmethod

   method pop takes nothing returns nothing
      set this.N = this.N - 1
   endmethod

   method top takes nothing returns integer
      return this.V[.N-1]
   endmethod

   method empty takes nothing returns boolean
      return (this.N == 0)
   endmethod

   method full takes nothing returns boolean
      return (this.N == this.V.size)
   endmethod
endstruct
Поскольку структуры развертываются в набор параллельных массивов, способом реализации массивов внутри структур служит резервирование определенного количества ячеек. То есть, если вы объявите структуру с атрибутом-массивом на 100 ячеек
struct stack
   private integer array V[100]
   private integer N = 0
каждый экземпляр структуры будет резервировать под эти нужды 100 последующих ячеек в массивах, в которые структура развертывается. В результате данного финта ушами, максимально возможное количество экземпляров одной структуры сильно снижается. Если вы объявите в структуре массив на 100 ячеек, вы сможете создать только ~80 экземпляров этой структуры (8190/100).
Структура может иметь любое количество атрибутов-массивов, однако ограничение на количество экземпляров этой структуры будет исходить из наибольшей размерности. То бишь, следующая структура
struct Test
	integer array a[4]
	integer array b[80]
	integer array c[17]
endstruct
может иметь не более 8190/80 = 102 экземпляров.
Данный подход объявления последовательностей уступает такой вещи как динамические массивы, но обладает более короткой и простой формой записи. Как и в случае с динамическими массивами, мы можем обратиться к атрибуту size массива, чтобы узнать его размерность. Динамические массивы мы разберем позже, пока просто имейте это в виду.

Статичные атрибуты

Поведение статичных атрибутов идентично глобальным переменным, но с привязкой к имени конкретной структуры. То есть, статичные атрибуты едины для всех экземпляров одной структуры. Обращаясь к статичному атрибуту любого экземпляра одной и той же структуры, вы обращаетесь к одной и той же переменной:
struct Number
    integer a = 0
    static integer b = 0
endstruct
    
function Init takes nothing returns nothing
    local Number n1 = Number.create()
    local Number n2 = Number.create()
    
    set n1.a = 10
    set n1.b = 15
    
    set n2.a = 4
    
    // Обращение к атрибуту, уникальному для каждого экземпляра
    call BJDebugMsg(I2S(n1.a))
    call BJDebugMsg(I2S(n2.a))
    
    // Все следующие инструкции обращаются к одной и той же переменной
    call BJDebugMsg(I2S(n1.b))
    call BJDebugMsg(I2S(n2.b))
    call BJDebugMsg(I2S(Number.b))
endfunction

Методы

Методы подобны функциям, за одним исключением - они ассоциированы с экземпляром структуры. Вызов метода экземпляра производится посредством оператора точка. Внутри метода вы можете обратиться к текущему экземпляру (чей метод был вызван) через ключевое слово this. Пример покажет это наглядно:
struct Point
    real x = 0.
    real y = 0.

    method polarOffset takes real angle, real dist returns nothing
		// this организует доступ к экземпляру, чей метод был вызван
        set this.x = this.x + Cos(angle*bj_DEGTORAD)*dist
        set this.y = this.y + Sin(angle*bj_DEGTORAD)*dist
    endmethod
endstruct

function Test takes nothing returns nothing
	local unit u = GetTriggerUnit()
 	local Point p = Point.create()

	set p.x = GetUnitX(u)
	set p.y = GetUnitY(u)

	// Обращение к методу конкретного экземпляра
    call p.polarOffset(GetUnitFacing(u), 800.)
    call SetUnitPosition(u, p.x, p.y)
	
	call p.destroy()
	set u = null
endfunction
Как вы можете видеть, синтаксис методов схож с функциями, но для их объявления мы используем ключевое слово method вместо function.
На самом деле, ключевое слово this опционально. Чтобы обратиться к атрибутам и методам экземпляра, изнутри этого экземпляра, вы можете использовать только оператор точка или вовсе не указывать ни того, ни другого. В нашем примере, тело метода polarOffset примет следующий вид:
    method polarOffset takes real angle, real dist returns nothing
		// this опционален и может быть опущен
        set .x = .x + Cos(angle*bj_DEGTORAD)*dist
        // Оператор . так же не обязателен
        set y = y + Sin(angle*bj_DEGTORAD)*dist
    endmethod
Методы отличаются от обычных функций, которые могут быть вызваны где угодно (исключая область объявления глобальных переменных). Вы так же не можете использовать внутри методов вейты, функции синхронизации и GetTriggeringTrigger функцию (однако, вам доступны прочие событийные нативные функции) (в действительности, эта возможность зависит от ряда факторов, но пытаться совсем не рекомендуется).

Инкапсуляция

Инкапсуляция, это концепт объектно ориентированного программирования, который подразумевает организацию доступа к составляющим элементам объекта. Или если говорить проще, публичные и приватные конструкции внутри структур
struct Number
    integer a = 0
    private integer b = 0
    public integer c = 0
	readonly integer d = 0

    method randomize takes nothing returns nothing
        // Допустимо во всех случаях:
        set .a = GetRandomInt(0, 100)
		set .b = GetRandomInt(0, 100)
		set .c = GetRandomInt(0, 100)	
		set .d = GetRandomInt(0, 100)		
    endmethod
endstruct

function Test takes nothing returns nothing
    local Number num = Number.create()
	call num.randomize()
	call BJDebugMsg(I2S(num.a)) // Допустимо
    call BJDebugMsg(I2S(num.b)) // Ошибка!
    call BJDebugMsg(I2S(num.c)) // Допустимо
	call BJDebugMsg(I2S(num.d)) // Допустимо
	set num.d = 28				// Ошибка!
endfunction
доступ к приватным конструкциям структуры открыт только для инструкций внутри методов этой структуры. Ключевые слова public и private опциональны, они могут быть опущены.
В примере выше вы так же можете увидеть модификатор доступа readonly. Это особый модификатор, позволяющий получать данные того или иного атрибута, но запрещающий его изменение извне структуры, поэтому попытка присвоить этому атрибуту новое значение вызовет ошибку компиляции.
public и private модификаторы доступа так же применимы и к методам структур.

Статичные методы

Прим. пер.: в оригинальной статье так же встречается словосочетание "метод класса", но я все же откажусь от этого термина, дабы не обескуражить читателя.
Статичными могут быть не только атрибуты, но и методы. Их поведение идентично поведению функций, но в то же время они ассоциированы со своей структурой. Одно из важнейших отличий - статичные методы могут обращаться к членам структуры, в том числе приватным.
struct Number
    integer a = 0
    private integer b = 0
    public integer c = 0
	readonly integer d = 0

    method print takes nothing returns nothing
        call BJDebugMsg(I2S(.a))
        call BJDebugMsg(I2S(.b)) 
        call BJDebugMsg(I2S(.c))
        call BJDebugMsg(I2S(.d)) 
    endmethod
    
    private method randomize takes nothing returns nothing
        set .a = GetRandomInt(0, 100)
		set .b = GetRandomInt(0, 100)
		set .c = GetRandomInt(0, 100)	
		set .d = GetRandomInt(0, 100)		
    endmethod
    
    static method new takes nothing returns Number
        local Number n = Number.create()
        call n.randomize()
        return n
    endmethod
endstruct

function Test takes nothing returns nothing
    local Number num = Number.new()
    call num.print()
    call num.destroy()
endfunction
В данном примере, для создания экземпляра структуры, мы используем статичный метод new, который сами и определили. Заметьте, что при обращении к статичному методу мы используем имя самой структуры, а не ее экземпляр, что впрочем так же допустимо.
Статичные методы так же могут использоваться в качестве типа code
struct HelloWorld
    static method print takes nothing returns nothing
        call BJDebugMsg("Hello, World!")
    endmethod
endstruct

function Test takes nothing returns nothing
    call TimerStart(CreateTimer(), 1.0, false, function HelloWorld.print)
endfunction

Конструктор

В предыдущем пункте мы использовали написанный своими руками статичный метод new для создания экземпляра структуры и уже там вызывали метод create. Метод create, в свою очередь, так же является статичным и именуется конструктором, поскольку он отвечает за создание экземпляра структуры. И что немаловажно - вы можете его переопределить.
Другими словами, вы можете в теле структуры определить свой собственный статичный метод с именем create, чтобы дополнить его новыми инструкциями, которые будут выполняться в момент создания экземпляра.
На самом деле, за создание экземпляра структуры отвечает метод allocate. Это приватный метод и вы не можете вызвать его откуда-либо, кроме как изнутри структуры. Каждый раз, когда парсер натыкается на создание экземпляра путем вызова метода create, в действительности, происходит вызов метода allocate. Поэтому, если вы переопределяете конструктор create, вы должны самостоятельно вызвать метод allocate, а в результате своей работы конструктор должен вернуть готовый экземпляр
// Случайная точка в области
struct RandomPoint
    readonly real x = 0.
    readonly real y = 0.
    
    // Переопределяем конструктор
    static method create takes rect rct returns RandomPoint
        // Если мы переопределяем метод create
        // мы сами должны вызвать метод allocate
        local RandomPoint p = RandomPoint.allocate()
        set p.x = GetRandomReal(GetRectMaxX(rct), GetRectMinX(rct))
        set p.y = GetRandomReal(GetRectMaxY(rct), GetRectMinY(rct))
        // Возвращаем созданный экземпляр
        return p
    endmethod
endstruct

function Test takes nothing returns nothing
    local RandomPoint p = RandomPoint.create(bj_mapInitialPlayableArea)
    call BJDebugMsg(R2S(p.x)+", "+R2S(p.y))
    call p.destroy()
endfunction
Вы можете переопределить метод create любым угодным вам набором инструкций. На самом деле, этот метод может даже не создавать и не возвращать экземпляр структуры, но в таком случае вам придется самостоятельно определить какой-то статичный метод, который возьмет на себя функцию конструктора, в котором будет вызываться метод allocate.

Деструктор

Ранее мы уже неоднократно пользовались деструктором, когда избавлялись от отработанного экземпляра структуры, вызывая метод destroy. У деструктора нету столь же точного синтаксиса как и у конструктора, однако вы можете определить метод onDestroy. Метод onDestroy вызывается автоматически, во время уничтожения экземпляра структуры, то есть в момент вызова метода destroy.
Деструктор обычно используется для уничтожения более не используемых объектов и чистки переменных
struct Point
    readonly real x = 0.
    readonly real y = 0.
    
    static method create takes real x, real y returns Point
        local Point p = Point.allocate()
        set p.x = x
        set p.y = y
        return p
    endmethod
endstruct

struct Rectangle
    Point leftTop
    Point rightBot
    
    static method create takes rect rct returns Rectangle
        local Rectangle tmp = Rectangle.allocate()
        set tmp.leftTop = Point.create(GetRectMinX(rct), GetRectMaxY(rct))
        set tmp.rightBot = Point.create(GetRectMaxX(rct), GetRectMinY(rct))
        return tmp
    endmethod
    
    method onDestroy takes nothing returns nothing
        // Вместе с нашим экземпляром, уничтожаем
        // и хранимые им экземпляры других структур
        call leftTop.destroy()
        call rightBot.destroy()
        call BJDebugMsg("Rectangle has been destroyed!")
    endmethod
endstruct

function Test takes nothing returns nothing
    local Rectangle r = Rectangle.create(bj_mapInitialPlayableArea)
    call r.destroy()
endfunction

Инициализация структуры

Возможно, вам потребуется инициализировать какие-то данные структуры во время инициализации карты. Для этого предусмотрен специальный метод onInit. Статичный метод структуры с таким именем будет вызван автоматически, во время инициализации карты.
Важно отметить, что функция инициализации структуры выполняется раньше функции инициализации библиотеки. Это значит, что если вам необходимо инициализировать какие-либо данные структуры уже после инициализации библиотеки, вам придется отказаться от метода onInit и произвести инициализацию структуры внутри функции инициализации библиотеки.
Порядок инициализации структур относительно друг друга определяет расположение в коде на момент их обнаружения парсером. Свои коррективы так же вносит и порядок библиотек. То есть, если структуры располагаются внутри библиотек, то структура внутри востребованной библиотеки будет инициализирована прежде структуры внутри требующий библиотеки.
Инициализация структур так же происходит раньше инициализации областей.
struct A
    static integer array ko

    // Функция инициализации может быть как публичной так и приватной
    private static method onInit takes nothing returns nothing
        local integer i=1000
        loop
            exitwhen (i<0)
            set A.ko[i]=i*2
            set i=i-1
        endloop
   endmethod
endstruct

Перегрузка операторов

Прим. пер.: в оригинальной статье это названо "operator making", но как-то не комильфо что-ли...
JassHelper позволяет перегружать операторы <, >, ==, !=, а так же [] и []= (обращения к ячейке массива для получения и установки значения). То есть, объявлять специальный метод, который будет вызываться при использовании перегруженного оператора с экземпляром структуры. Для этого используется ключевое слово operator вместо имени метода, а за ним указывается перегружаемый оператор.
Пример вместо тысячи слов:
struct String
    string str = ""

    method operator [] takes integer i returns string
        return SubString(.str,i,i+1)
    endmethod

    method operator []= takes integer i, string ch returns nothing
        set .str=SubString(.str,0,i)+ch+SubString(.str,i+1,StringLength(.str)-i)
    endmethod
endstruct


function Test takes nothing returns nothing
    local String x = String.create()
    set x.str = "Test"
    call BJDebugMsg(x[1])
    call BJDebugMsg(x[0]+x[3])

    set x[1] = "."
    call BJDebugMsg(x.str)
endfunction
В качестве примера, мы объявили собственную структуру для работы со строками. В структуре перегружены операторы обращения к массиву - получения значения по индексту [] и установки значения []=. Таким образом, мы объявили некоторый аналог типа string, но с возможностью обращения к конкретному символу в строке, подобно массиву.
Можете обратить внимание, что во время перегрузки оператора [] передается 1 аргумент - индекс. А перегрузка оператора []= требует уже 2 аргумента - индекс и устанавливаемое значение. В то же время, оператор [] требует от метода возвращение какого-то значения.
Методы, перегружающие операторы [] и []= так же могут быть статичными. Это позволит использовать перегруженные операторы не только с экземпляром структуры, но и с именем самой структуры.
В отношении перегрузки операторов существует большое количество критики, поскольку она позволяет программисту превратить код в нечто несвязное, не имеющее никакого смысла, посему стратайтесь использовать ее ответственно.
Вы так же можете перегружать операторы < и >. Однако, предусмотрен только синтаксис для перегрузки оператора <. Перегрузка > будет выполнена автоматически на основе оператора <
struct String
    string str=""

    method operator [] takes integer i returns string
        return SubString(.str,i,i+1)
    endmethod

    method operator []= takes integer i, string ch returns nothing
        set .str=SubString(.str,0,i)+ch+SubString(.str,i+1,StringLength(.str)-i)
    endmethod
    
    method operator < takes String b returns boolean
        return StringLength(this.str) < StringLength(b.str)
    endmethod
endstruct


function Test takes nothing returns nothing
    local String a = String.create()
    local String b = String.create()

    set a.str = "Test..."
    set b.str = ".Test"

    if (a < b) then
        call BJDebugMsg("a is less than b")
    endif
    if (a > b) then
        call BJDebugMsg("a is greater than b")
    endif
endfunction
Пример выше демонстрирует сравнение длины строк двух экземпляров типа String. Оператор < должен возвращать значение типа boolean и принимать аргумент того же типа, что и структура, в которой перегружен.
Аналогичным образом вы можете перегрузить оператор ==. Так же как и в случае с предыдущим оператором, перегрузка оператора != генерируется автоматически. Фактически, результат работы оператора сравнения будет просто инвертирован кодом
    not (вашМетодПерегрузки)

Имитация атрибутов

Мы так же можем определить методы, имитирующие обращение к атрибутам структуры. Это позволяет увеличить уровень абстракции.
Прим. пер.: в программировании подобное обычно называют свойством. Например, для C#, это аксессор, для Pascal - проперти.
struct X
    integer a = 2
    integer b = 2

    method operator x takes nothing returns integer
        return this.a*this.b
    endmethod

    method operator x= takes integer v returns nothing
        set this.a = v/this.b
    endmethod
endstruct

function Test takes nothing returns nothing
    local X obj = X.create()
    set obj.x = obj.x + 4

    call BJDebugMsg(I2S( obj.x)) // Выведет 8
    set obj.b = 4

    call BJDebugMsg(I2S( obj.x)) // Выведет 16
endfunction
Таким образом вы можете реализовать атрибуты доступные только для чтения
struct X
    private integer va=2

    method operator a takes nothing returns integer
        return this.a
    endmethod
endstruct

function test takes nothing returns nothing
    local X obj= X.create()

    call BJDebugMsg(I2S( obj.a) ) // Допстимо

    set obj.a=2 // Ошибка!
endfunction
Но что еще важнее, вы можете пользоваться данной возможностью, для создания атрибутов, с особыми функциями при присвоении
struct movableEffect
    private unit dummy
    private string rfx
    private effect uniteffect

    //...
    // Множество прочих методов, конструктор, деструктор и т.д.
    //...

    method operator x takes nothing returns real
        return GetUnitX(this.dummy)
    endmethod

    method operator y takes nothing returns real
        return GetUnitY(this.dummy)
    endmethod

    method operator x= takes real value returns nothing
        call SetUnitX(this.dummy, value)
    endmethod

    method operator y= takes real value returns nothing
        call SetUnitY(this.dummy, value)
    endmethod


    method operator effectpath takes nothing returns string
        return this.rfx
    endmethod

    method operator effectpath= takes string path returns nothing
        set this.rfx=path
        call DestroyEffect( this.uniteffect)
        set this.uniteffect = AddSpecialEffectTarget(this.dummy, path, "origin")
    endmethod
endstruct

function moveRandom takes movableEffect me returns nothing
    set me.x= me.x + GetRandomReal(-50,50)
    set me.y= me.y + GetRandomReal(-50,50)
endfunction

function toFire takes movableEffect me returns nothing
    set me.effectpath ="war3mapimporte\\cutefireeffect.mdl"
endfunction
Начиная с версии 0.9.Z.1, этот синтаксис доступен статичным членам структуры.

Расширение структур

Существует возможность определения стукруты, которая основана на другой, ранее определенной структуре. Новая структура будет обладать методами и атрибутами базовой стукруты, но не сможет обратиться к private членам базовой структуры. Более того, вы можете использовать новую структуру там, где требуется базовая структура (переменные, аргументы функции и пр.).
Следует рассматривать данную возможность как способ добавления логики и свойств уже существующему типу.
Для объявления расширяющего типа, используется ключевое слово extends, сразу после его имени, за которым следует имя расширяемого типа.
struct A
   integer x
   integer y

   method setxy takes integer cx, integer cy returns nothing
       set this.x=cx
       set this.y=cy
   endmethod
endstruct

struct B extends A
   integer z
   method setxyz takes integer cx, integer cy, integer cz returns nothing
       call this.setxy(cx,cy) // мы можем обращаться к атрибутам базовой структуры A
       set this.z=cz
   endmethod
endstruct
В таком случае, расширяющая структура именуется потомком, а расширяемая - родителем. То есть, в примере выше, тип B - потомок типа A, или же тип A - родитель типа B.
В случае переопределения конструктора потомка, вызов метода allocate, в действительности, приведет к вызову конструктора родителя. Из этого следует, что во время вызова метода allocate, в конструкторе потомка, вы должны передавать аргументы требующиеся конструктору родителя
struct A
    integer x
   
    static method create takes integer k returns A
        local A s=A.allocate()
        set A.x=k
        return s
    endmethod
endstruct

struct B extends A
    static method create takes nothing returns B
        return s= B.allocate(445)  // Заметьте, B.allocate требует те же аргументы что и A.create
    endmethod
endstruct

struct C extends B // Да, мы можем расширять другую расширяющую структуру
    static method create takes nothing returns C
        local C s=C.allocate()  // C, это потомок B который в своем конструкторе не принимаает аргументов,
                                // соответственно вызов C.allocate ничего не принимает
        set s.x=s.x*s.x // Опять же, структура имеет доступ к родительским атрибутам
        return s
    endmethod
endstruct
Одно НО - вы не сможете расширить структуру, чей метод create объявлен приватным.
В случае вызова деструктора, так же будет вызван деструктор всех родительских типов. То есть, в примере выше, если мы вызовем метод C.destory(), будут вызваны все деструкторы - C.onDestroy(), B.onDestroy(), A.onDestroy() (именно в таком порядке).

Переопределение методов

У вас есть возможность переопределять методы родительского типа в потомках. То есть, при попытке вызова метода родителя, будет вызван метод потомка. Методы, которые могут быть переопределены, должны отмечаться ключевым словом stub. Пример должен быть нагляднее:
struct Parent
    stub method xx takes nothing returns nothing
        call BJDebugMsg("Parent")
    endmethod

    method doSomething takes nothing returns nothing
        call this.xx()
        call this.xx()
    endmethod
endstruct

struct ChildA extends Parent
    method xx takes nothing returns nothing
        call BJDebugMsg("- Child A -")
    endmethod
endstruct

struct ChildB extends Parent
    method xx takes nothing returns nothing
        call BJDebugMsg("- Child B --")
    endmethod
endstruct


function test takes nothing returns nothing
    local Parent P = Parent.create()
    local Parent A = ChildA.create()
    local Parent B = ChildB.create()
    // Заметьте, все пеменные имеют тип Parent
    call P.doSomething() // Дважды выведет 'Parent'
    call A.doSomething() // Дважды выведет 'Child A' twice
    call B.doSomething() // Дважды выведет 'Child B' twice
endfunction

super

Может случиться так, что ваша структура расширяет другую структуру, в которой некоторый метод указан как stub. То есть, вы переопределили некоторый метод родительской структуры. Но что делать, если нам нужно обратиться к переопределенному методу родительской структуры? Для этого используется ключево слово super.
super работает подобно this, однако обеспечивает доступ к атрибутам и методам родительской структуры
struct Parent

    stub method xx takes nothing returns nothing
        call BJDebugMsg("Parent")
    endmethod

    method doSomething takes nothing returns nothing
        call this.xx()
        call this.xx()
    endmethod

endstruct

struct ChildA extends Parent
    method xx takes nothing returns nothing
        call BJDebugMsg("- Child A -")
        call super.xx()
    endmethod
endstruct

struct ChildB extends Parent
    method xx takes nothing returns nothing
        call BJDebugMsg("- Child B --")
    endmethod
endstruct


function test takes nothing returns nothing
    local Parent P = Parent.create()
    local Parent A = ChildA.create()
    local Parent B = ChildB.create()
    // Заметьте, все переменные имеют тип Parent
    call P.doSomething() // Дважды выведет 'Parent'
    call A.doSomething() // Дважды выведет 'Child A|nParent'
    call B.doSomething() // Дважды выведет 'Child B'
endfunction

Интерфейсы

Далее мы начинаем знакомиться с полиморфизмом. Полиморфизм, это концепт объекто-ориентированного программирования, при котором разные объекты могут иметь одни и те же действия, однако, отличные друг от друга. Например, некий объект "муравей" и объект "человек" могут совершать одно и то же действие - "бежать", однако эти действие реализованы у этих объектов совершенно по разному.
Интерфейсы, это нечто вроде набора правил, которым следует структура. Они определяют действия, которые могут совершать структуры разного типа. Интерфейс объявляет в себе методы, без тела, только имя, аргументы и возвращаемый тип. В свою очередь структуры расширяют интерфейс, подобно расширению других структур, но в то же время обязуются реализовать объявленные интерфейсом методы
interface printable
    method toString takes nothing returns string
endinterface

struct singleint extends printable
    integer v
    method toString takes nothing returns string
        return I2S(this.v)
    endmethod
endstruct

struct intpair extends printable
    integer a
    integer b

    method toString takes nothing returns string
        return "("+I2S(this.a)+","+I2S(this.b)+")"
    endmethod
endstruct

function printmany takes printable a, printable b, printable c returns nothing
    call BJDebugMsg( a.toString()+" - "+b.toString()+" - "+c.toString())
endfunction

function test takes nothing returns nothing
    local singleint x = singleint.create()
    local singleint y = singleint.create()
    local intpair z = intpair.create()

    set x.v = 56
    set y.v = 12
    set z.a = 45
    set z.b = 12

    call printmany(x, y, z) // результат "56 - 12 - (45,12)"
endfunction
Функция printmany принимает 3 аргумента типа printable. Эта функция ничего не знает об объектах, которые принимает, ей известно одно - передаваемые объекты должны расширять интерфейс printable.
Структуры следуют заявленному интерфейсом printable правилу - они реализуют метод toString. Как следствие, функция printmany может вызывать метод toString, ничего не зная об объектах, с которыми работает.
Каждый тип, расширяющий интерфейс printable, обладает собственным методом toString, отличный от других типов, а функция printmany каждый раз вызывает правильный метод, соответствующий передаваемому объекту.
Интерфейсы могут обладать любым количеством методов. Один и тот же интерфейс может быть расширен сколь угодным количеством структур и каждая из них должна выполнять реализацию методов интерфейса.
Интерфейсы не могут объявлять метод onDestroy. Переменные типа интерфейса могут вызывать деструктор destroy(), в этом случае вы можете ожидать вызов деструктора соответствующего объекта.
Интерфейсы могут объявлять атрибуты, таким образом реализуя псевдо-наледование
interface withpos
    real x
    real y
endinterface

struct rectangle extends withpos
    real a
    real b

    static method from takes real x, real y, real a, real b returns rectangle
        local rectangle r=rectangle.create()
        set r.x=x
        set r.y=y
        set r.a=a
        set r.b=b
        return r
    endmethod
endstruct
Вы можете узнать уникальный ID типа, если он расширяет интерфейс. Этот ID является целым числом и он уникален для каждой структуры, которая расширяет интерфейс
interface A
    integer x
endinterface

struct B extends A
    integer y
endstruct

struct C extends A
    integer y
    integer z
endstruct

function test takes A inst returns nothing
    if (inst.getType()==C.typeid) then
    // Мы точно знаем, что isnt - экземпляр типа C
        set C(inst).z=5 // Приведение типа (об этом позже)
    endif
    if (inst.getType()==B.typeid) then
        call BJDebugMsg("It was of type B with value "+I2S( B(inst).y  ) )
    endif
endfunction
Вкратце - метод .getType() позволяет узнать ID типа у экземпляра, в то время как .typeid - статичная константа структуры с тем же назначением. В примере выше, мы использовали их чтобы распознать типы, расширяющие интерфейс, в случае с типом C мы так же выполнили приведение типов (о чем позже).
Существует еще одна фича, связанная с уникальным ID расширяющего интерфейс типа. Вы можете передать ID в конструктор интерфейса (метод create) и ожидать возвращения соответствующего этому ID типа
interface MyInterface
    method msg takes nothing returns string
endinterface

struct A extends MyInterface
    method msg takes nothing returns string
        return "oranges"
    endmethod
endstruct

struct B extends MyInterface
    string s
   
    static method create takes nothing returns B
        local B tmp = B.allocate()
        set tmp.s = "apples"
        return tmp
   endmethod

   method msg takes nothing returns string
       return this.s
   endmethod
endstruct

struct C extends MyInterface
   string s

   // Конструктор интерфейса MyInterface.create может обращаться только к
   // к обычному конструктору allocate, который не принимает аргументов.
   // Поскольку конструктор данной структуры имеет список принимаемых аргументов
   // создание ее экземпляра через конструктор интерфейса невозможно.

    static method create takes string str returns C
        local C tmp = C.allocate()
        set tmp.s = str
        return tmp
   endmethod

   method msg takes nothing returns string
       return this.s
   endmethod
endstruct

function Test takes nothing returns nothing
    local integer t = B.typeid
    local MyInterface tmp

    set tmp = MyInterface.create(A.typeid) // Не особо полезно, поскольку mystructA.create() делает то же самое
    call BJDebugMsg(tmp.msg())

    set tmp = MyInterface.create(t) // Больше пользы, поскольку экземпляр создается исходя из типа в переменной
    call BJDebugMsg(tmp.msg())

    set tmp = MyInterface.create(122345) // Используя несуществующий ID конструктор интерфейса вернет 0

    set tmp = MyInterface.create(C.typeid) // Заметьте, здесь не будет вызван конструктор структуры
                                           // C. Более того, это может вызвать ошибку, предупреждение
                                           // о которой появится в игре.
    call BJDebugMsg(tmp.msg())

endfunction
Будьте осторожны при использовании данного подхода, поскольку в случае неудачи, вместо экземпляра структуры будет возвращено значение 0.
Вы так же можете обявить конструктор в интерфесе, который не принимает аргументов и возвращает nothing, в таком случае, все расширяющие этот интерфейс структуры должны иметь конструктор, который не принимает аргументов
interface MyInterface
    static method create takes nothing
endinterface

struct A extends MyInterface
    static method create takes nothing returns A // Допустимо
        local A tmp = A.allocate()
        // Прочие инструкции...
        return 
    endmethod
endstruct

struct B extends MyInterface
    static method create takes integer i, integer n returns B // Ошибка!
        local B tmp = B.allocate()
        // Прочие инструкции...
        return B.allocate()
    endmethod
endstruct
Cтруктуры могут расширять другие структуры, которые реализуют интерфейсы. Однако, как следует из правила выше, все потомки интерфейса с конструктором без аргументов (а значит и потомки тех структур, что реализуют такой интерфейс) должны иметь конструктор без аргументов.
Итак, интерфейсы объявляют наборы методов, которые обязательно должны быть релизованы в теле расширяющей структуры. Тем не менее, вы можете явно указать методы интерфейса, которые не обязательны к реализации. Для этого используется ключевое слово defaults. Если расширяющая структура не реализует defaults метод, вы не получите сообщение об ошибке, вместо этого, такой метод будет автоматически реализован пустым методом.
Следом за ключевым словом defaults следует nothing (если метод возвращает nothing), либо данные того типа, которые метод должен вернуть. Иначе говоря, если defaults метод не реализован, он служит своего рода константой
interface whattodo
    method onStrike takes real x, real y returns boolean defaults false
    method onBegin  takes real x, real y returns nothing defaults nothing

    method onFinish takes nothing returns nothing
endinterface

struct A extends whattodo 
    // Структура обязана реализовать метод onFinish
    method onFinish takes nothing returns nothing //must be implemented
        // Инструкции...
    endmethod

    // Мы можем, но не обязаны реализовать метод onBegin
    method onBegin takes real x, real y returns nothing
        // Инструкции...
    endmethod

    // Метод onStrike не реализован. При попытке вызова метода onStrike
    // будет возвращено значение false.
endstruct

struct B extends whattodo
    ** Структура обязана реализовать метод onFinish
    method onFinish takes nothing returns nothing //must be implemented
        // Инструкции...
    endmethod

    // Метод onStrike не реализован. При попытке вызова метода onStrike
    // будет возвращено значение false.

    // Метод onBegin не реализован. При попытке вызова метода onBegin
    // ничего не произойдет и ничего не будет возвращено.
endstruct
Интерфейсы так же могут учавствовать в перегрузке операторов. Главное отличие - метод перегрузки логического оператора не должен иметь сигнатуры, то есть, метод не должен обладать списком аргументов и типом возвращаемого значения
interface ordered
    method operator < // Сигнатура не указывается
endinterface

interface indexed
    method operator [] takes integer index returns ordered
    method operator []= takes integer index, ordered v returns nothing
endinterface

Делегаты

Это был долгий путь и видели мы многое - интерфейсты, структуры расширяющие другие структуры, операторы... Что еще может быть? Ответ - делегаты.
Делегаты, это весьма странная возможность. Делегатом является экземпляр структуры, который входит в состав другой структуры, чтобы выполнить для владельца какую-то работу. И под работой подразумеваются методы. Делегаты могут оказаться полезной и интересной альтернативой расширению структур. Полноценная идея делегирования применяется в некоторых других языках программирования, язык же Jass является не очень динамичным, а vJass, в свою очередь, наследует его недостатки. К лучшему или худшему, делегирование в vJass исключительно статично - выполняется на этапе компиляции.
Делегаты, это структуры, которые являются атрибутами другой структуры. Однако, если вы попытаетесь обратиться к атрибуту/методу структуры и он не будет найден, JassHelper произведет поиск данного атрибута/метода в составе делегатов. Разумеется, поиск производится на этапе компиляции.
Для объявления члена структуры в качестве делегата, используется ключевое слово delegate
struct A
    private real x
    private real y

    public method performAction takes nothing returns nothing
        call DestroyEffect( AddSpecialEffect("path\\model.mdl", this.x, this.y) )
    endmethod

endstruct

struct B
    delegate A deleg

    static method create takes nothing returns B
        local B b = B.allocate()
        set B.deleg = A.create()
    endmethod

endstruct

function testsomething takes nothing returns nothing
    local B myB = B.create()

    // Поскольку performAction() не является частью структуры B, JassHelper произведет
    // поиск этого метода в делегатах. Делегат имеет в своем составе метод с именем 
    // performAction, посему будет выполнена попытка его вызова.
    call myB.performAction()

    // Фактически, это тоже самое что и:
    call myB.deleg.performAction()

endfunction
Несколько заметок:
  • Во время поиска атрибутов/методов JassHelper отдает приоритет членам структуры прежде поиска оных в делегатах. Из этого следует, что если структура и делегат имеют метод с одним именем, вызываться будет всегда метод структуры.
  • Между несколькими делегатами в одной структуре поиск производится в порядке их объявления.
  • Вы можете обявлять в качестве делегатов базовые типы (integer/string/real и пр.), это не вызовет ошибок компиляции, но и не имеет никакого смысла, поскольку данные типы не обладают атрибутами/методами.
  • Делегаты не могут служить реализацией интерфейсов.
  • Вам следует инициализировать делегаты, если хотите избежать ошибок во время выполнения кода.
  • Делегаты могут быть приватными.
  • Если вы попытаетесь использовать приватный делегат извне, вы скорее получите сообщение об отсутствие того или иного атрибута/метода в структуре, нежели сообщение о делегате.

thistype

thistype, это ключевое слово, которое ведет себя точно так же, как и имя текущей структуры внутри этой структуры.
Следующий код
struct test 
    thistype array ts
    method tester takes nothing returns thistype
        return thistype.allocate()
    endmethod
endstruct
эквивалентен
struct test 
    test array ts
    method tester takes nothing returns test
        return test.allocate()
    endmethod
endstruct
Данное ключевое слово ориентрировано на использование там, где оно дейтсвительно нужно, например, текстовые макросы или модули. Я не одобряю его использование с целью избежания возможных проблем при переименовывании структур, но людям нравится.

Модули

Модули, это блоки кода, которые вы можете поместить внутри структуры. Модули следует воспринимть как продвинутую версию текстовых макросов.
Внутри модуля можно обращаться к атрибутам/методам структуры, в которую этот модуль будет помещен. Однако, если имя этого атрибута/метода не будет найдено в структуре, будет вызвано сообщение о синтаксической ошибке. Модуль так же может обладать приватными членами, которые не будут видимы для структуры, в которую это модуль помещен, соответственно, конфликт имен между членами модуля и структуры невозможен, за исключением функций конструктора и деструктора (create и onDestroy). Модуль не может объявлять приватные операторы (что само по себе лишено смысла, поскольку они предназчаются для взаимодействия с окружением).
Начиная с версии Jasshelper 0.9.Z.1, приватный onInit метод модуля выполняется за каждую структуру, в которую этот модуль помещен. Разные onInit методы разных модулей могут сосуществовать с onInit методом структуры.
Для обявления модуля используются ключевые слова module и endmodule между которыми заключено тело модуля. Чтобы поместить модуль внутрь структуры, используется ключевое слово implement
// Модуль
module MyRepeatModule
    method repeat1000 takes nothing returns nothing
        local integer i=0
        loop
            exitwhen i==1000
            call this.sub() // Ожидается, что метод .sub 
                            // существует в структуре
            set i=i+1
        endloop
    endmethod
endmodule

// Структура
struct MyStruct
    method sub takes nothing returns nothing
        call BJDebugMsg("Hello world")
    endmethod
    implement MyRepeatModule // Помещаем модуль

endstruct

function MyTest takes MyStruct ms returns nothing
    call ms.repeat1000() // Вызовет метод ms.sub 1000 раз
endfunction
Вы можете обратиться к модулю из другого модуля, посредством ключевого слова implement.
Вы можете использовать ключевое слово optional перед именем используемого модуля. В таком случае будет предотвращено сообщение об ошибке, если запрашиваемый модуль в коде карты не найден. JassHelper просто проигнорирует команду добавления модуля.
В случае попытки поместить в тело структуры модуль, который уже был задействован в этой структуре, команда игнорируется.
Модули подчиняются правилам областей/библиотек, в которой были объявлены.
Вы так же можете использовать ключевое слово thistype, ради указания имени структуры, в которую этот модуль будет помещен.

module MyOtherModule
    method uhOh takes nothing returns nothing
    endmethod
endmodule


module MyModule
    // Следующая строка добавит метод uhOh из модуля
    implement optional MyOtherModule

    // Поскольку следующая инструкция опциональна, она будет
    // проигнорирована, если модуль не существует.
    implement optional OptionalModule

    // Следующий метод требует существования метода copy()
    // в структуре
    static method swap takes thistype A , thistype B returns nothing
        local thistype C = thistype.allocate()
        // Мы внутри структуры, а значит можем использовать allocate
        call C.copy(A)
        call A.copy(B)
        call B.copy(C)
        call C.destroy()  
    endmethod
endmodule

struct MyStruct
    integer a
    integer b
    integer c

    // Код копирования
    method copy takes MyStruct x returns nothing
        set this.a = x.a
        set this.b = x.b
        set this.c = x.c
    endmethod

    // Добавляем метод swap
    implement MyModule
    implement MyOtherModule // этот модуль уже был добавлен, поэтому строка игнорируется

endstruct

function MyTest takes MyStruct A, MyStruct B returns nothing
    call MyStruct.swap(A,B) // В конечном итоге, структура получила метод swap
endfunction

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

Стас Орлов #1 - 1 год назад 0
Т.е. я могу благодаря структурам сохранить юнита, который умирает, сохранить его положение и потом на его месте создать такого же?
Diaboliko #2 - 1 год назад 0
Стас Орлов:
Т.е. я могу благодаря структурам сохранить юнита, который умирает, сохранить его положение и потом на его месте создать такого же?
Структуры - фишка vJass. Вам не обязательно использовать именно их чтобы достичь желаемой цели. В итоге вжасс конвертируется в обычный жасс. Но, если осилите - вам и карты в руки. Как средство разработки - вжасс эффективнее чем жасс.
SсRealm #3 - 1 год назад 0
Один мой хороший знакомый заюзал эту системку и подарил герою способность самостоятельно бесконечно летать по карте,жаль моих мозгов не хватит осилить эту систему иначе я бы многое сотворил((