Здравствуйте.
Меня в начале лета торкнуло, и решил вернуться в 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 объекта. Тоже не нашёл в руководствах.

Принятый ответ

P.S. Забыл упомянуть, что все наследники интерфейса разделяют одно и то же пространство в 8192 объекта. Тоже не нашёл в руководствах.
На самом деле это следует из того, что наследники вызывают allocate родителя.
Я так понимаю, описание проблемы вот?
Самая большая проблема, которую я на текущий момент вижу, это, во-первых, возможность через индекс обращаться даже к несуществующим/уничтоженным объектам, и, во-вторых, сохранение всех ссылок на экземпляр структуры после её уничтожения (и да, я понимаю, что это не ссылки, а целочисленные индексы).
Решить первую проблему невозможно, как раз из-за того, что любой объект структуры есть целое число. И в любом месте для структуры MyStruct я могу сделать вот так:
call MyStruct(6).my_method()
Что уж говорить, есть возможность точечно менять значение приватного поля определённого объекта.
Второй проблемы в высокоуровневых языках быть не может, просто потому что объект остаётся в памяти до тех пор, пока на него ссылаются. Во время выполнения 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 сам сделает для неё триггер.
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
Макрос выше определяет структуру, которая создаёт массивы для указанного в макросе типа. Причём данный тип обязан иметь поле для хранения своего индекса.
Код под катом позволяет отлавливать и логгировать некоторую информацию об ошибках во время игры.
`
ОЖИДАНИЕ РЕКЛАМЫ...

Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
0
27
3 года назад
0
заместившей в некоторый момент интересующего триггер "Хоттабыча".
у меня например структуры юнитов строго по своим ячейкам распределены и я никогда не страдал от подобного
0
10
3 года назад
0
rsfghd, действительно, аккуратный менеджмент ресурсов легко решает все проблемы. У меня просто идея-фикс - сделать максимально безопасный для потребителя пул библиотек, которые предоставят любому желающему набор наиболее востребованных функций в более удобном виде, нежели чистый vJass.
0
32
3 года назад
0
Структуры vJassа посути массивы глобальных переменных и код, который управлет выделением ячеек, и все, поэтому твоя забота как разработчика сделать метод очистки и создания таким, чтобы небыло ни мусора ни ошибок.
0
10
3 года назад
0
quq_CCCP, разве я говорю "почините мне jass"? Я предлагаю поделится своим подходом к решению означенных проблем, если есть желание.
0
32
3 года назад
0
Paraxenate, ну так пишешь метод, который вызваешь при уничтожении, который все чистит, и все.
3
28
3 года назад
Отредактирован PT153
3
P.S. Забыл упомянуть, что все наследники интерфейса разделяют одно и то же пространство в 8192 объекта. Тоже не нашёл в руководствах.
На самом деле это следует из того, что наследники вызывают allocate родителя.
Я так понимаю, описание проблемы вот?
Самая большая проблема, которую я на текущий момент вижу, это, во-первых, возможность через индекс обращаться даже к несуществующим/уничтоженным объектам, и, во-вторых, сохранение всех ссылок на экземпляр структуры после её уничтожения (и да, я понимаю, что это не ссылки, а целочисленные индексы).
Решить первую проблему невозможно, как раз из-за того, что любой объект структуры есть целое число. И в любом месте для структуры MyStruct я могу сделать вот так:
call MyStruct(6).my_method()
Что уж говорить, есть возможность точечно менять значение приватного поля определённого объекта.
Второй проблемы в высокоуровневых языках быть не может, просто потому что объект остаётся в памяти до тех пор, пока на него ссылаются. Во время выполнения 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 сам сделает для неё триггер.
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
Макрос выше определяет структуру, которая создаёт массивы для указанного в макросе типа. Причём данный тип обязан иметь поле для хранения своего индекса.
Код под катом позволяет отлавливать и логгировать некоторую информацию об ошибках во время игры.
Принятый ответ
0
27
3 года назад
Отредактирован rsfghd
0
действительно, аккуратный менеджмент ресурсов легко решает все проблемы.
структура может хранить в себе другие структуры, поэтому, если ты к примеру сохраняешь в юнита несколько структур и там как-то подменяешь их чтобы экономить место, можно просто выделить ещё одну структуру для всех остальных, упаковать в коробку так сказать, а потом из этой коробки доставать и делать все действия, и парится не нужно
0
10
3 года назад
0
PT153, более чем исчерпывающий ответ, спасибо за уделенное время.
Показан только небольшой набор комментариев вокруг указанного. Перейти к актуальным.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.