Перейти к контенту

Язык Lua. Общие вопросы программирования


Malandrinus

Рекомендуемые сообщения

Monnoroch,

Не надо никаких сообщений в лог. Это сажает производительность

1. Для современных систем однострочные выводы в лог, как слону дробинка, тем более что ты не в update, надеюсь, ставишь подписки :)

2. Предполагается что двойные подписки будут отловленны на стадии разработки/тестирования. В релизе нет необходимости вывода в лог и уж тем более крэшить игру (в разработке, кстати тоже нет смысла крэшить)

 

П.С. malandrinus, прошу понять правильно, не критикую и не навязываю точку зрения, просто "разбор полетов" (feedback)

П.П.С. Про quit знаю, за fail спасибо, попробую

 

Изменено пользователем Andrey07071977

Поделиться этим сообщением


Ссылка на сообщение

Monnoroch,

двойные подписки - совершенно нормальное явление

На мой взгляд, при правильной организации кода, таких случаев не должно возникать вообще. Приведенный тобой пример, может произойти при неаккуратном совмещении модов с одноименными глобальными (_G) функциями подписчиками, но это опять-же проблема девелопера, на этапе совмещения

Поделиться этим сообщением


Ссылка на сообщение

malandrinus,

Логи игры при вылетах - результат работы именно таких ассертов. Без них было бы совсем кисло =)
Про ассерты писал исключительно в контексте проверки двойной подписки, т.е. если даже каким то образом это проникло в релиз, то просто return, без ассерта, без лога, и без приведения системы в поломанный стэйт.

 

Именно крэшить, в этом вся суть!
Дело в том что у меня на втором мониторе всегда открыта дополнительная консоль из luacap от alpet - очень рекомендую при отладке, сразу видно что и где происходит. Т.О. в моем случае крэшить смысла нет, и так сразу видно что произошла ошибка. В штатных же ситуациях, наверное ты прав, лучше сразу дать знать что произошла ошибка.

 

Подразумевается, что в релизе эти ассерты уже никогда-никогда не сработают =)
Ну и шуточки у вас, гражданин прокурор :D

 

Добавлено через 20 мин.:

П.С. Ладно, из'ездили уже тему про ассерты, пора завязывать :). Итог, кому мешают - не сложно изменить.

Изменено пользователем Andrey07071977

Поделиться этим сообщением


Ссылка на сообщение
Если можно, то я для себя сам решу, завязывать мне или нет. ок?

Ты не понял, я имел ввиду что Я завязываю про ассерты. Продолжать ли ТЕБЕ обсуждение, дело твое, мне какая разница?

Хотя, перечитав свой пост, на самом деле может быть воспринято двусмысленно.

 

Я в любом случае был неправ и грубить не имел права. Прошу прощения.

Malandrinus

Изменено пользователем malandrinus

Поделиться этим сообщением


Ссылка на сообщение
malandrinus, вопрос по проверке зависаний. Как я понимаю, колбэк заданный в level.add_call(...) вызывается с частотой апдэйта актера - не слишком ли часто это для проверки? Если, чисто теоретически, предположить что подписанно 100 функций, и не все сработают в течении 25 миллисекунд, то игра будет вылетать хотя не факт что колбэк именно завис. Или же есть ограничение что ВСЕ колбэки должны быть завершены в течении одного апдэйта? Соответственно, если таких ограничений нет, то не лучше ли сделать проверку допустим посекундно? Изменено пользователем Andrey07071977

Поделиться этим сообщением


Ссылка на сообщение
malandrinus, что-то совсем я запутался, ведь если
Этот вызов никак не может сработать во время работы другого вызова
, то по идее при зависшем колбэке вообще ничего не должно сработать, ведь работа функции так и не завершилась.

Поделиться этим сообщением


Ссылка на сообщение

Пока ничего не понял, одно противоречит другому :huh: Буду вечером разбираться в синхронизации колбэков

Изменено пользователем Andrey07071977

Поделиться этим сообщением


Ссылка на сообщение
malandrinus, ну да именно так я и предполагал. Допустим, ради примера, навесили на колбэк взятия предмета некую функцию которая будет выполнятся больше одного интервала апдэйта, например тот же перебор значений get_actor_float(...) из твоего x-ray extensions, от 1 до скажем 1000000. Этот вызов, во всяком случае на моем лаптопе, займет больше секунды. Так вот я не совсем понимаю почему проверка зависаний не сработает до тех пор пока не закончится данный вызов - ведь колбэки разные?

Поделиться этим сообщением


Ссылка на сообщение
malandrinus, кажется я понял в чем дело. Как бы ты сформулировал (блин, перевожу с Английского на Русский, дожились) "зависание колбэка"? В моем понимании, это происходит при ошибке в исполнении функции, когда был вход но небыло выхода. При этом происходит подвисание определенной нити, остальные при этом спокойно продолжают работу. Изменено пользователем Andrey07071977

Поделиться этим сообщением


Ссылка на сообщение

В продолжение дискуссии о колбэках… Так как не имел четкого представления о том как происходит синхронизация колбэков в сталкере, решил поэкспериментировать и разобраться. Для этого попытался смоделировать нечто подобное в чистом Lua. Вот что получилось и некоторые общие замечания по теме.

 

1. Из справочного руководства Lua:

Lua поддерживает подпрограммы, эту технологию часто называют общей многопоточностью. Подпрограмма Lua представляет собой независимый поток выполнения. Несмотря на это, в отличие от потоков в традиционных многопоточных системах, подпрограмма может приостановить свое выполнение только в результате явного вызова функции yield.
Это может ввести в заблуждение, так как на самом деле функции выполняются последовательно (синхронно) – в данный момент может выполнятся ОДНИН поток (coroutine). Эффект многопоточности достигается за счет очень быстрого переключения между coroutines а так же возможности останавливать и продолжать работу. Данные о состоянии потока при остановке будут сохранены в Lua State потока.

2. Каждый поток создается с помощью команды co = coroutine.create(func). Где func это функция которую нужно запустить в отдельном потоке.

3. Запустить исполнение потока (один из способов) можно через команду coroutine.resume(cor), где cor это поток созданный выше.

4. Потоки запущенные с помощью resume выполняются в песочнице (protected environment) на подобии pcall. !!! Все ошибки при этом не распространяются в основную нить программы а лишь приводят данный поток в нерабочее состояние (coroutine.status(cor) = “dead”). Это и есть причина того что колбэки в сталкере молча зависают а не приводят к вылету.

5. Ошибки которые произошли при выполнении функций в потоках, можно получить с помощью local status, err = coroutine.resume(cor). Где err и будет строка с ошибкой.

6. При зависании одного из потоков, все остальные продолжают спокойно работать, так как имеют свою собсвенную, независимую среду (state). Попытки вызвать зависший поток будут лишь выдавать ошибку «cannot resume dead coroutine», которую в общем то легко отловить и отреагировать, но движок сталкера, к сожалению, ее просто сьедает.

7. Каждому колбэку в игре создается свой персональный поток, который в итоге и зависнет ☺

 

Теперь собственно модель колбэков:

--/ Фунции которые будут вызваны в колбэках
function hang() --/ this function will hang a coroutine (thread)
    print("------> i am good now, but will hang on next run :(")
    local b = 2/nil     --/ error will be introduced here
    print("------> i am dead and you will never see this print :P")
end
function good() --/ this is a valid function and will NOT hang a thread
    print("------> i am still alive ...")
end

--/ Сами колбэки
function callback_hang()    
    while true do
        --/ script functions will be called here
        -- ...
        hang()    --/ triggering this function will cause a thread to hang
        -- ...
        --//
        coroutine.yield() --/ all functions have finished - return control to main thread
    end
end
function callback_good()
    while true do
        --/ script functions will be called here
        -- ...
        good()
        -- ...
        --// 
        coroutine.yield() --/ all functions have finished - return control to main thread
    end
end

--/ main processing thread – основная нить игры. Эта нить является связующим звеном и координатором всех остальных потоков (нитей/coroutines). Рано или поздно все потоки вернут контроль именно сюда.
function main_thread()
    --/ create a thread for each callback
    coroutine_hang = coroutine.create(callback_hang)
    coroutine_good = coroutine.create(callback_good)
    
    --/ Trigger callbacks first time
    print("*** first trigger of HANGING callback ***")
    local st, err = coroutine.resume(coroutine_hang) --/ trigger hanging callback
    print("-> status = "..tostring(st))
    print("-> coroutine status = "..tostring(coroutine.status(coroutine_hang)))
    print("-> error = "..tostring(err))
    print("*** first trigger of GOOD callback ***")
    st, err = coroutine.resume(coroutine_good) --/ trigger valid callback
    print("-> status = "..tostring(st))
    print("-> coroutine status = "..tostring(coroutine.status(coroutine_good)))
    print("-> error = "..tostring(err))
    
    print("\n... some main thread activity between callbacks ...\n")
    
    --/ Trigger callbacks second time
    print("*** second trigger of HANGING callback ***")
    st, err = coroutine.resume(coroutine_hang) --/ retrigger hanging callback
    print("-> return status = "..tostring(st))
    print("-> coroutine status = "..tostring(coroutine.status(coroutine_hang)))
    print("-> error = "..tostring(err))
    print("*** second trigger of GOOD callback ***")
    st, err = coroutine.resume(coroutine_good) --/ retrigger valid callback
    print("-> status = "..tostring(st))
    print("-> coroutine status = "..tostring(coroutine.status(coroutine_good)))
    print("-> error = "..tostring(err))
    
    print("\n... some main thread activity between callbacks ...\n")
    
    --/ Trigger callbacks third time ... and so on ...
    print("*** third trigger of HANGING callback ***")
    st, err = coroutine.resume(coroutine_hang) --/ retrigger hanging callback
    print("-> return status = "..tostring(st))
    print("-> coroutine status = "..tostring(coroutine.status(coroutine_hang)))
    print("-> error = "..tostring(err))
    print("*** third trigger of GOOD callback ***")
    st, err = coroutine.resume(coroutine_good) --/ retrigger valid callback
    print("-> status = "..tostring(st))
    print("-> coroutine status = "..tostring(coroutine.status(coroutine_good)))
    print("-> error = "..tostring(err))
end

main_thread()

 

OUTPUT:

*** first trigger of HANGING callback ***
------> i am good now, but will hang on next run :(
-> status = false
-> coroutine status = dead
-> error = [string "function hang() --/ this function will
hang..."]:3: attempt to perform arithmetic on a nil value
*** first trigger of GOOD callback ***
------> i am still alive ...
-> status = true
-> coroutine status = suspended
-> error = nil

... some main thread activity between callbacks ...

*** second trigger of HANGING callback ***
-> return status = false
-> coroutine status = dead
-> error = cannot resume dead coroutine
*** second trigger of GOOD callback ***
------> i am still alive ...
-> status = true
-> coroutine status = suspended
-> error = nil

... some main thread activity between callbacks ...

*** third trigger of HANGING callback ***
-> return status = false
-> coroutine status = dead
-> error = cannot resume dead coroutine
*** third trigger of GOOD callback ***
------> i am still alive ...
-> status = true
-> coroutine status = suspended
-> error = nil

 

 

 

Видно что при зависании, первый поток не вылетает а просто сообщает что его больше не возможно перезапустить. Второй продолжает спокойно работать.

 

Некоторые интересные моменты:

1. Если исполнение какой либо функции поместить в отдельно созданный поток и запусть его с помощью команды local st, err = coroutine.resume(cor), то информацию о ошибке можно считать в переменной err. Таким образом можно значительно упростить отладку колбэков в игре. В игре не тестировал пока, но в теории должно сработать. Позже напишу подробней.

2. Движок игры скрывает создание потоков. Функция coroutine.running() у меня всегда возвращает nil – что означает основную нить игры. С чем связанно не знаю, пока не разбирался.

 

Надеюсь поможет кому понять работу coroutines Lua в общем и конкретно колбэков в сталкере. Если есть замечания, вопросы, и тд. пишите.

 

Поделиться этим сообщением


Ссылка на сообщение
Artos, спасибо, удивительно как я умудрился пропустить пост Gun12 - сэкономил бы кучу времени на вступительной части :). Честно говоря не ставил целью научить или обьяснить работу с coroutines, просто не до конца понимал работу колбэков и решил разобраться. malandrinus, пробовал объяснить, но все равно у меня оставался пробел в логической цепочке, поэтому решил смоделировать работу в луа. Подумал может кому еще поможет разобраться с синхронизацией колбэков. Изменено пользователем Andrey07071977

Поделиться этим сообщением


Ссылка на сообщение

malandrinus,

Разве что кому-то зачем-то придёт в голову написать свой менеджер кооперативной многозадачности
В Lua уже есть этот самый менеджер, именно он отвечает за работу coroutines

Если бы были, то встречались бы вызовы lua_newthread, а ни одного нет
а lua_open не встречаются?

Всего лишь при вызове колбека ему выделяется отдельный стек
это и есть часть работы coroutines. Для каждого потока создается отдельный стек и стэйт, затем менеджер использует все это для создания "иллюзии" многопоточности. Говорю иллюзии так как для меня многопоточность это все таки параллельное исполнение каких либо задач с использованием нескольких ядер.

Поделиться этим сообщением


Ссылка на сообщение

malandrinus,

Ты имеешь в виду lua_newstate?

Нет, имел ввиду именно lua_open - ты когда проверял создание Луа потоков искал lua_newthread. А создать поток можно еще и с помощью lua_open. Цитата из вики

 

Each thread in C which interacts with Lua will need its own Lua state. Each of these states has its own runtime stack. When a new C thread is started, you can create its Lua state in one of two ways. One way is to call lua_open. This creates a new state which is independent of the states in other threads. In this case, you'll need to initialize the Lua state (for example, loading libraries) as if it was the state of a new program. This approach eliminates the need for mutex locks (discussed below), but will keep the threads from sharing global data.

 

The other approach is to call lua_newthread. This creates a child state which has its own stack and which has access to global data.

Изменено пользователем Andrey07071977

Поделиться этим сообщением


Ссылка на сообщение
  • Недавно просматривали   0 пользователей

    • Ни один зарегистрированный пользователь не просматривает эту страницу.
×
×
  • Создать...