Здравствуйте.
Меня в начале лета торкнуло, и решил вернуться в W3 WE, делать карты. Взглянул на свои старые наработки пятилетней давности и ужаснулся; пошел изучать имеющуюся документацию по JASS'у.
Поднабравшись базовых знаний отправился пилить основные библиотеки карты. Спустя сколько-то вечеров и страданий над вопросами проектирования увидел некоторые неоднозначности - или, скорее, проблемы. Может быть, где-то это уже обсуждалось или упоминалось, но я не нашел.
Начнем с того, что ни при создании, ни при смещении структуры не происходит очистки полей. Пример.
struct magician
public integer id
public string name
public integer power
//...
public method toString takes nothing returns nothing
call BJDebugMsg("{ this=" + I2S(this) + "; id=" + I2S(id) + "; name=" + name + "; power=" + I2S(power) + " }")
endmethod
endstruct
//...
local magician mg = magician.create()
call mg.toString()
set mg.id = 101
set mg.name = "Хоттабыч"
set mg.power = 100500
call mg.toString()
call mg.destroy()
call mg.toString()
set mg = magician.create()
call mg.toString()
В результате исполнения отрывка кода выше лишь первое сообщение будет уникальным: в нем все поля объекта будут еще не инициализированы и пусты. В последующих же вызовах - во всех - вывод будет одинаков. Даже после уничтожения объекта и до создания нового на его месте. При этом новый объект наследует все поля предшественника.
И собственно отсутствие очистки полей объекта после его уничтожения я не считаю большой проблемой (хотя в реальности это запросто может привести к сложным для интерпретации и отслеживания ошибкам) - их можно и самому обнулить, можно и в конструкторе все поля до последнего инициализировать значениями. Самая большая проблема, которую я на текущий момент вижу, это, во-первых, возможность через индекс обращаться даже к несуществующим/уничтоженным объектам, и, во-вторых, сохранение всех ссылок на экземпляр структуры после её уничтожения (и да, я понимаю, что это не ссылки, а целочисленные индексы).
Следствием этой особенности джасса является огромнейший потенциал неведомых ошибок во время исполнения: при параллельном использовании одних и тех же экземпляров структур в разных триггерах триггеры в разное время могут использовать разные объекты, при этом продолжая считать, что обрабатывают первоначально поданный. Ключевым здесь является то, что такая подмена обрабатываемого объекта (указатель на который мог быть сохранен, например, в хэштаблице) не вызовет никаких исключений или ошибок. Исполнение просто продолжится, как будто так и надо. Впрочем, с самого начала было ясно, что в рамках джасса отладка является синонимом страданий. Мне, привычному к структурированной обработки C#, это бывает больно.
Итак, перехожу к вопросу. Разрабатывал ли кто-то подходы к комплексному решению этой проблемы?
Допустим, если бы каждая из структур индексировалась уникально - это уже исключило бы проблему. Я пока придумал лишь одно решение - исключая собственную индексацию объектов, которая приводит к потребности хранить кроме самого указателя на структуру еще и её уникальный индекс. Собственно, идея в том, чтобы реализовать в структуре массив из указателей на реализации интерфейса функции-диспозера, в которой ручками прямо из триггера-потребителя структуры на её onDestroy вызывать функцию и зачищать все ссылки.
Есть еще, конечно, вариант защищать структуры от уничтожения, но это все равно не спасет короткоживущие структуры от путаницы и вытекающих слабоотлавливаемых ошибок.
P.S. Забыл упомянуть, что все наследники интерфейса разделяют одно и то же пространство в 8192 объекта. Тоже не нашёл в руководствах.
Принятый ответ
На самом деле это следует из того, что наследники вызывают allocate родителя.P.S. Забыл упомянуть, что все наследники интерфейса разделяют одно и то же пространство в 8192 объекта. Тоже не нашёл в руководствах.
Я так понимаю, описание проблемы вот?
Решить первую проблему невозможно, как раз из-за того, что любой объект структуры есть целое число. И в любом месте для структуры MyStruct я могу сделать вот так:Самая большая проблема, которую я на текущий момент вижу, это, во-первых, возможность через индекс обращаться даже к несуществующим/уничтоженным объектам, и, во-вторых, сохранение всех ссылок на экземпляр структуры после её уничтожения (и да, я понимаю, что это не ссылки, а целочисленные индексы).
call MyStruct(6).my_method()
Что уж говорить, есть возможность точечно менять значение приватного поля определённого объекта.
Второй проблемы в высокоуровневых языках быть не может, просто потому что объект остаётся в памяти до тех пор, пока на него ссылаются. Во время выполнения JASS никак нельзя узнать, сколько переменных ссылаются на то или иное число, дабы отложить вызов метода destroy, не говоря уже о том, что нет никакой информации, какой именно destroy нужно отложить. Поэтому её тоже решить невозможно.
Второй проблемы в высокоуровневых языках быть не может, просто потому что объект остаётся в памяти до тех пор, пока на него ссылаются. Во время выполнения JASS никак нельзя узнать, сколько переменных ссылаются на то или иное число, дабы отложить вызов метода destroy, не говоря уже о том, что нет никакой информации, какой именно destroy нужно отложить. Поэтому её тоже решить невозможно.
Могу предложить вот что. Немного исправленный код из моей карты.Следствием этой особенности джасса является огромнейший потенциал неведомых ошибок во время исполнения: при параллельном использовании одних и тех же экземпляров структур в разных триггерах триггеры в разное время могут использовать разные объекты, при этом продолжая считать, что обрабатывают первоначально поданный. Ключевым здесь является то, что такая подмена обрабатываемого объекта (указатель на который мог быть сохранен, например, в хэштаблице) не вызовет никаких исключений или ошибок. Исполнение просто продолжится, как будто так и надо. Впрочем, с самого начала было ясно, что в рамках джасса отладка является синонимом страданий. Мне, привычному к структурированной обработки C#, это бывает больно.
раскрыть
function EndThread takes nothing returns nothing
call I2R(1 / 0)
endfunction
function ErrorOccurred takes string message, string errorcode returns nothing
if GameIsRunning then
set GameIsRunning = false
// Create error file
call PreloadGenClear()
call PreloadMessage("Error: " + message)
call PreloadGenEnd("WispTD\\Errors\\" + errorcode + ".txt")
// Update UI end environment
call ClearSelection()
call StopMusic(false)
call ClearMapMusic()
call StopAmbientSounds()
call MultiboardSetTitleText(Board, "An internal error occurred")
// Disable triggers and end game
call DisableTriggers()
call TriggerExecute(gg_trg_ErrorOccurred)
call FinishGameNoCheck("An internal error occurred. Error code: " + Color.Errors.hex + errorcode + "|r.", FinishGameTime_Error)
endif
call EndThread()
endfunction
Триггер gg_trg_ErrorOccurred нужен для выполнения кода для каждого игрока в случае ошибки в отдельном потоке операций. Во-первых, это значит, что код внутри Trig_ErrorOccured_Actions не будет учитываться в лимит операций того кода, что вызвал ErrorOccurred, а во-вторых, если какой-то код внутри Trig_ErrorOccured_Actions рушит поток (например, вызов EndThread), выполнение TriggerExecute завершится, и начнётся выполнение FinishGameNoCheck.
Впрочем, можно оставить только Trig_ErrorOccured_Actions и вызвать её через .execute(), vJass сам сделает для неё триггер.
Впрочем, можно оставить только Trig_ErrorOccured_Actions и вызвать её через .execute(), vJass сам сделает для неё триггер.
function Trig_ErrorOccured_Actions takes nothing returns nothing
local integer i = 0
loop
exitwhen i > Players.top
call Players[i].errorOccured()
set i = i + 1
endloop
call PlayingPlayers.reset()
endfunction
function InitTrig_ErrorOccurred takes nothing returns nothing
set gg_trg_ErrorOccurred = CreateTrigger()
call TriggerAddAction(gg_trg_ErrorOccurred, function Trig_ErrorOccured_Actions)
endfunction
Метод игрока, который вызывает триггер выше.
method errorOccured takes nothing returns nothing
call field.errorOccured()
if playing then
set playing = false
set inGame = false
call showGrid(false)
call GroupEnumUnitsInRect(ProxyGroup, field.playerarea, LightDeleteUnits)
endif
endmethod
Как правило, данный метод вызывает "лёгкое" удаление всех остальных объектов и структур, дабы не допустить повторных ошибок и пролагов.
Пример использования.
//! textmacro CustomArray takes name, type, size, index, err
struct $name$
private $type$ array objects[$size$]
readonly integer top = -1
method operator [] takes integer i returns $type$
return objects[i]
endmethod
method addToArray takes $type$ obj returns nothing
if obj.$index$ == -1 then
set top = top + 1
static if ErrorDetectionEnabled_CustomArray and $err$ then
if top == $size$ then
call ErrorOccurred("not enough cells in $name$ " + I2S(this) + ". Current amount of cells is " + I2S($size$), "$name$Cells")
endif
endif
set objects[top] = obj
set obj.$index$ = top
//call DebugMsg("$type$ " + I2S(obj) + " with index " + I2S(top) + " is added to $name$.")
else
call ErrorOccurred("an attempt of $type$ " + I2S(obj) + " to be added to $name$ " + I2S(this) + " second time", "$name$Add")
endif
endmethod
method deleteFromArray takes $type$ obj returns nothing
local integer i = obj.$index$
if i > -1 then
if top > i then
set objects[i] = objects[top]
set objects[i].$index$ = i
endif
set top = top - 1
set obj.$index$ = -1
//call DebugMsg("$type$ " + I2S(obj) + " with index " + I2S(i) + " is removed from $name$.")
else
call ErrorOccurred("an attempt of $type$ " + I2S(obj) + " to be deleted from $name$ " + I2S(this) + " second time", "$name$Delete")
endif
endmethod
method reset takes nothing returns nothing
set this.top = -1
endmethod
endstruct
//! endtextmacro
Макрос выше определяет структуру, которая создаёт массивы для указанного в макросе типа. Причём данный тип обязан иметь поле для хранения своего индекса.
Код под катом позволяет отлавливать и логгировать некоторую информацию об ошибках во время игры.
`
ОЖИДАНИЕ РЕКЛАМЫ...
Чтобы оставить комментарий, пожалуйста, войдите на сайт.
Отредактирован PT153
Второй проблемы в высокоуровневых языках быть не может, просто потому что объект остаётся в памяти до тех пор, пока на него ссылаются. Во время выполнения JASS никак нельзя узнать, сколько переменных ссылаются на то или иное число, дабы отложить вызов метода destroy, не говоря уже о том, что нет никакой информации, какой именно destroy нужно отложить. Поэтому её тоже решить невозможно.
Впрочем, можно оставить только Trig_ErrorOccured_Actions и вызвать её через .execute(), vJass сам сделает для неё триггер.
Отредактирован rsfghd