Добавлен , опубликован

vJass

Содержание:

Одной из насущных проблем языка Jass было отсутствие какой-либо инкапсуляции и модульности. Извечная необходимость придумывать новое именование функциям и переменным, в надежде избежать конфликта глобальных имен, становилась все назойливее, с ростом кода карты. Для разрешения данной проблемы, vJass вводит в свой синтксис конструкцию, известную как область.

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

Объявление

Синтаксис объявления области выглядит следующим образом:
scope NAME
	// Здесь располагаются ваши функции и переменные
endscope
Таким образом мы объявляем блок кода - набор глобальных переменных, функций и констант, объединенных под общим именем - именем области.
scope MyScope
	globals
		integer X = 0
	endglobals
	
	function MyFunc takes nothing returns nothing
		// ...
	endfunction
endscope
Здесь мы объявили глобальную переменную "X" и функцию "MyFunc". Область, в данном случае, не произвела никаких изменений. Единственная задача области в данном примере, это визуальное выделение набора функций и переменных в общий блок кода. То есть, после обработки парсером, код выше будет преобразован в обычное и ничем не примечательное
globals
	integer X = 0
endglobals
	
function MyFunc takes nothing returns nothing
	// ...
endfunction
Никакой дополнительной работы над нашим кодом произведено не будет, как если бы мы вовсе не использовали область.
Тем не менее, области позволяют писать конструкции, ограниченные или изолированные от всего остального кода карты.

Публичные конструкции

Как уже было сказано выше, области хоть и позволяют объединить наборы функций и переменных в единый блок кода, сами по себе они ничего не делают. То есть, следующий код
scope MyScope
	globals
		integer X = 0
	endglobals
	
	function MyFunc takes nothing returns nothing
		call BJDebugMsg("Hello, from the Scope!")
	endfunction
endscope

function MyFunc takes nothing returns nothing
	call BJDebugMsg("Hello, from somewhere in the map!")
endfunction
вызовет сообщение об ошибке, поскольку мы объявили две функции с одним и тем же именем "MyFunc". Чтобы избежать конфликта имен, области позволяют объявлять внутри себя публичные конструкции, для чего используется ключевое слово public перед объявлением функции, переменной или константы:
scope MyScope
	globals
		public integer X = 0
	endglobals
	
	public function MyFunc takes nothing returns nothing
		call BJDebugMsg("Hello, from the Scope!")
	endfunction
endscope

function MyFunc takes nothing returns nothing
	call BJDebugMsg("Hello, from somewhere in the map!")
endfunction
Теперь нет никаких проблем.
Что происходит на самом деле? В действительности, парсер неявно добавляет к имени публичных членов приставку, в виде имени области и символа подчеркивания. В данном случае, конечное имя функции MyFunc области MyScope станет MyScope_MyFunc. Соответственно, если вы захотите обратиться этой функции извне ее области, вы вынуждены использовать соответствующий этой области префикс. Однако, префикс не обязателен в случае обращении к члену той или иной области внутри той же области.
Следующий пример продемонстрирует эти правила более наглядно:
scope MyScope
	globals
		public integer X = 0
	endglobals
	
	public function MyFunc takes nothing returns nothing
		call BJDebugMsg("Hello, from the Scope!")
	endfunction
		
	function AnotherInTheScope takes nothing returns nothing
		call MyFunc() // Будет вызвана функция MyFunc из текущей области. Префикс не требуется
		set X = X + 1 // Обращение к переменной внутри текущей области. Префикс не требуется
	endfunction
endscope

function MyFunc takes nothing returns nothing
	call BJDebugMsg("Hello, from somewhere in the map!")
endfunction

function AnotherFunc takes nothing returns nothing
	call MyFunc() // Обращаемся к функции MyFunc вне области, получим сообщение  "Hello, from somewhere in the map!"
	call MyScope_MyFunc() // Обращаемся к функции MyFunc в области MyScope, получим сообщение "Hello, from the Scope!"
	set X = X + 1 // Ошибка! Нет такой переменной!
	set MyScope_X = MyScope_X + 1 // Работает, внутри области MyScope действительно существует переменная X
endfunction

Приватные конструкции

Итак, теперь мы можем объявить область, чтобы определить некоторый блок кода под ее именем. Мы можем использовать ключевое слово public, чтобы избежать конфликта имен, между членами области и прочим кодом карты.
Но как быть, если мы желаем полностью ограничить доступ к тем или членам внутри области? Здесь на помощь приходят приватные конструкции, для объявления которых используется ключевое слово private. Сразу продемонстрируем это на примере:
scope MyScope
	globals
		public integer X = 0
		private real Y = 0.0
	endglobals
	
	public function MyPublicFunc takes nothing returns nothing
		call BJDebugMsg("Hello, from PUBLIC function!")
	endfunction
	
	private function MyPrivateFunction takes nothing returns nothing
		call BJDebugMsg("Hello, from PRIVATE function!")
	endfunction
	
	function AnotherInTheScope takes nothing returns nothing
		// Обращаемся к публичным членам области внутри той же области
		call MyPublicFunc () // ОК, префикс не требуется
		set X = X + 1 // ОК, префикс не требуется
		// Обращаемся к приватным членам внутри той же области
		call MyPrivateFunc() // ОК, доступ есть
		set Y = Y + 1.0 // ОК, доступ есть
	endfunction
endscope

function AnotherFunc takes nothing returns nothing
	// Обращаемся к публичным членам области
	call MyScope_MyPublicFunc() // ОК, MyPublicFunc является публичный, доступ через префикс
	set MyScope_X = MyScope_X + 1 // ОК, переменная X является публичный, доступ через префикс
	// Обращение к приватным членам области
	call MyScope_MyPrivateFunc() // Ошибка!, MyPrivateFunc является приватной, доступ извне области закрыт
	set MyScope_Y = MyScope_Y + 1.0 // Ошибка! Переменная Y является приватной, доступ извне области закрыт
endfunction
В действительности, так же как и в случае с публичными конструкциями, к именам приватных членов автоматически добавляет префикс. Однако, этот префикс остается недоступным для разработчика, таким образом пресекая всякую возможность обращения к приватным членам извне области.

SCOPE_PREFIX и SCOPE_PRIVATE

Как уже говорилось ранее, публичные и приватные конструкции внутри области, являются обычными функциями, переменными и константами, для ограничения или полного запрета доступа к которым, парсер неявно добавляет к именам специальный префикс. Публичный префикс можно получить исходя из имени области, приватной префикс остается недоступен для пользователя.
И как же нам быть в случае, если необходимо получить имя того или иного члена области в виде строки? Например, если нам необходимо породить новый поток посредством ExecuteFunc (подробнее см. в статье "Лимит операций")? Ведь для этого необходимо передать строку с конечным именем функции, которое получится уже после обработки кода карты парсером.
Очевидно, для публичных членов области необходимо использовать соответствующий префикс
scope MyScope
	public function MyPublicFunction takes nothing returns nothing
		// ...
	endfunction
	
	function AnotherFunc takes nothing returns nothing
		call ExecuteFunc("MyPublicFunc") // Вызовет падение игры, поскольку не является конечным именем функции
		call ExecuteFunc("MyScope_MyPublicFunc") // Работает корректно, учитывается генерируемый парсером префикс
	endfunction
endscope
Но что делать с именами приватных членов, чей префикс остается неизвестен? Для этой задачи были предусмотрены константы SCOPE_PREFIX и SCOPE_PRIVATE. Это обычные Jass строки, генерируемые парсером, во время обработки кода области.
  • SCOPE_PREFIX - содержит строку с префиксом, используемым в именах публичных членов текущей области
  • SCOPE_PRIVATE - содержит строку с префиксом, используемым в именах приватных членов текущей области
Теперь мы можем решить поставленную ранее задачу одинаково легко, как для публичных так и для приватных конструкций
scope MyScope
	public function MyPublicFunction takes nothing returns nothing
		// ...
	endfunction
	
	private function MyPrivateFunction takes nothing returns nothing
		// ...
	endfunction
	
	function AnotherFunc takes nothing returns nothing
		call ExecuteFunc(SCOPE_PREFIX+"MyPublicFunc") // Работает корректно
		call ExecuteFunc(SCOPE_PRIVATE+"MyPrivateFunc") // Работает корректно
	endfunction
endscope
Мы смогли получить как очевидный префикс для публичной функции, так и скрытый для приватной. В результате поставили выполнить каждую функцию в своем потоке.
Главное не забывайте, константы SCOPE_PREFIX и SCOPE_PRIVATE доступны только внутри текущей области.

Функция инициализации

Несмотря на все представленные выше ништяки, особой пользы от областей не было бы, без функции инициализации. Области позволяют указывать имя функции, которая будет вызвана во время инициализации карты. Имя этой функции указывается после ключевого слова initializer которое следует сразу за именем области:
scope MyScope initializer Init
	private function Init takes nothing returns nothing
		// Здесь ваш код инициализации
		// Создание триггеров, событий, таймеров и пр.
	endfunction
endscope
Обратите внимание на пару важных аспектов:
  • функция инициализации не должна принимать параметров
  • функция инициализации может (но не обязана) иметь модификатор доступа
Таким образом, в купе со всеми ранее рассмотренными возможностями - области, это мощный инструмент, позволяющий писать изолированные системы, не оказывающие влияния или не испытывающие влияния от прочего кода карты.

Вложенные области

Области могут быть вложенными. То есть, одни области могут включать в свой состав другие области
scope C
	scope A
		// ...
	endscope
	
	scope B
		// ...
	endscope
endscope
Но здесь есть ряд особенностей, которые приходится держать в уме.
  1. Вложенные области не могут иметь функцию инициализации
  2. Вы не можете объявить глобальное имя (функцию/переменную/константу) внутри вложенной области, если в родительской области уже объявлено такое же имя с модификатором доступа public или private. За разъяснением лучше обратится к примеру.
scope A
	globals
		private integer X
		public integer Y
	endglobals
    
	scope B
		globals
			integer X // Ошибка! Повторное объявление
			integer Y // Ошибка! Повторное объявление
		endglobals
	endscope
endscope
Однако, стоит нам перенести объявление глобальных переменных родительской области ниже вложенной области, проблема исчезнет:
scope A   
	scope B
		globals
			integer X // ОК
			integer Y // ОК
		endglobals
	endscope

	globals
		private integer X
		public integer Y
	endglobals
endscope
И даже более того - вы можете объявить переменные дочерних областей с модификатором доступа, если глобальные переменные родительской области не имеют модификатора доступа
scope A
	globals
		integer X
		integer Y
	endglobals
    
	scope B
		globals
			private integer X // ОК
			public integer Y // ОК
		endglobals
	endscope
endscope
Держите это в уме, если собираетесь использовать вложенные области.
Ну и наконец, обращение к публичным членам вложенных областей происходит за счет каждого префикса в иерархии областей
scope A
	scope B
        scope C
            public function HelloWorld takes nothing returns nothing
                call BJDebugMsg("Hello, World!")
            endfunction
        endscope
    endscope
endscope

function OutsideFunc takes nothing returns nothing
        call A_B_C_HelloWorld() // Необходимо использовать каждый префикс иерархии
endfunction
Области могут иметь одинаковое имя, если родительские имена областей различаются. То есть, конечное имя вложенной области конструируется из всей иерархии областей. Для примера выше, имя вложенной области B - A_B, а имя вложенной области C - A_B_C.

keyword

Ключевое слово keyword позволяет вам объявить заменяющую директиву в областях, которая заменит действительное объявление функции/переменных и т.д. Это может быть полезно во многих случаях, но главная причина его использование - невозможность обращения к публичным/приватным конструкциям библиотеки прежде их объявления. В большинстве случаев, это лишь назойливое и раздражающее требование.
Например, у вас есть две вызывающие друг друга функции посредством метода evaluate (за доп. информацией обращайтесь к главе "Функция как объект"), но в то же время вы хотите, чтобы эти функции были приватными или публичными. Без использования keyword это было бы невозможно
scope myScope

    rivate keyword B // Нам пришлось объявить B без действительного объявления функции,
                     // без чего возникла бы ошибка компиляции

    private function A takes integer i returns integer
        if(i!=0) then
            return B.evaluate(i-1)*2 // Теперь мы можем обратиться к функции B
                                     // даже с учетом, что она была объявлена приватной
        endif
        return 0
    endfunction

    private function B takes integer i returns integer
        if(i!=0) then
            return A(i-1)*3
        endif
        return 0
    endfunction

endscope

`
ОЖИДАНИЕ РЕКЛАМЫ...