Лекция #36. Обзор VM скриптовых языков (Lua, AngelScript). Часть 1. [Лектор - Black_Phoenix]
Автор: _ShaMan_
Лекция Black_Phoenix с обзором VM скриптовых языков Lua и AngelScript. Часть первая.
[20:01:32] <Black_Phoenix> Короче я просто програмист, занимаюсь много чем, в т.ч. виртуальными машинами. Написал VM эмулятор PII, также специальную VM "ZCPU" для одной игровой модификации (внутриигровой микропроцессор, для цифровых схем).
[20:01:54] <Black_Phoenix> На данный момент я пишу ОС которая полностью основана на виртуальной машине, но это не тема этой лекции
[20:02:19] <Black_Phoenix> В этой лекции будет рассмотрено общее сравнение AngelScript и Lua (как кто-то попросил), а также небольшой глубокий экскурс в то, как они работают (т.е. сами вирутальные машины, интерпретаторы).
[20:02:52] <Black_Phoenix> Перед началом экскурса в сами VM рекомендуеться достать исходные коды [AngelScript 2.16.3] и [Lua 5.1.4]. Ссылки (действительная на момент лекции): http://www.angelcode.com/angelscript/sdk/files/angelscript_2.16.3.zip http://www.lua.org/ftp/lua-5.1.4.tar.gz
[20:03:11] <Black_Phoenix> Я буду ссылаться на файлы (в принципе пока нужна только Луа)
[20:03:36] <Black_Phoenix> В этой части лекции будет общее описание Lua и AngelScript, а также детальное описание движка Lua. Во второй части будет описание движка AngelScript, и информация о том, как их надо биндить (и возможно некоторые малоизвестные фичи).
[20:04:10] <Black_Phoenix> Итак, сначала общее. И AngelScript и Lua являються скриптовыми языками для использования в программах и играх. Lua была создана в 1993 году, и на данный момент используеться в очень многих проектах. AngelScript был создан в 2003 году, и пока я не встречал прое
[20:04:11] <Black_Phoenix> ктов которые его используют (т.е. лично не встречал).
[20:04:40] <Black_Phoenix> Для начала синтаксис: Lua использует свой синтаксис. Синтаксис AngelScript чем-то похож на синтаксис С++, он также поддерживает классы и наследование.
[20:04:56] <Black_Phoenix> Lua написана на чистом С, AngelScript написан на С++, его код достаточно объектно-ориентированый.
[20:05:21] <Black_Phoenix> (лично мне больше нравиться как написан AS, хотя там много C++-изма)
[20:05:41] <Black_Phoenix> В обоих языках пошаговый интерпретатор. Есть возможность отлаживать построчно, компилировать и запускать байткод.
[20:05:47] <_ShaMan_> remark: список проектов, которые используют AngelScript, можно увидеть здесь: http://www.angelcode.com/angelscript/users.asp
[20:06:06] <Black_Phoenix> В обоих движках есть поддержка coroutines (псевдо-паралельного выполнения), и поддержка multithreading (мультипоточности). Вообще в AngelScript несколько лучше реализована загрузка и сохранение байткода - более очевидно
[20:06:37] <Black_Phoenix> В второй части лекции (которая будет через неделю) я постараюсь показать как это делать с Lua (без сторонних утилит :) )
[20:07:05] <Black_Phoenix> Lua - стековая виртуальная машина (хотя формально регистровая), AngelScript - регистровая (почти).
[20:07:31] <Black_Phoenix> Что важно, Lua есть почти для всех языков и платформ которые вам встретяться. С код достаточно легко компилируеться везде, и единственная привязка Lua к ОС это функции перераспределения памяти (не считая дополнительных библиотек).
[20:07:41] <Black_Phoenix> AngelScript есть на всех современных платформах (игровых консолях, разных ОС, также на iPhone). Известно что он компилируеться под MSVC++, GNUC, MinGW, DJGPP.
[20:08:06] <Black_Phoenix> Вообще насколько я знаю поддержка iPhone вам не нужна будет, на AppStore не принимаються виртуальные машины
[20:08:46] <Black_Phoenix> Lua требует биндинга каждой функции по своему, AS работает с функциями без биндинга (надо только сообщить движку о них). Lua использует свой стек для передачи параметров - и туда-же нужно их возвращать. AS вызывает функции по stdcall (точнее она сама
[20:08:47] <Black_Phoenix> подбирает способ вызова совместимый).
[20:09:21] <Black_Phoenix> Достаточно субьективно скажу, что Lua быстрее чем AS - здесь overhead при вызове функций, а также в garbage collectore меньше. Но в принципе по тестам они более-менее на одном уровне (по тому что я видел AS проигрывает Lua всего не более чем на 10%)
[20:09:51] <Black_Phoenix> Вообще при правильной архитектуре движка скорость скриптового движка не должна иметь значения
[20:10:11] <Black_Phoenix> В лекции я буду ссылаться на код вот так: [имя файла:номер строки], например [lvm.c:64]. В этой части пока только на исходники Lua
[20:10:48] <Black_Phoenix> Используеться такая терминология:
[20:11:04] <Black_Phoenix> (момент, я залью на everfall.com)
[20:11:27] <Black_Phoenix> http://www.everfall.com/paste/id.php?pt9ys4s20uk0 вот
[20:11:47] <Black_Phoenix> Это кроме основных понятий которые должны быть знакомы. Почти всё есть на википедии (я проверял)
[20:12:08] <Black_Phoenix> Итак, Lua. В ядре луа сидит Lua virtual machine (LVM). LVM - формально регистровая VM, но вам врут, она не регистровая (регистрами там и не пахнет) - она всё ещё стековая.
[20:12:27] <Black_Phoenix> Опкоды LVM могут читать и писать на стек (это задаёться смещением), а также со списка констант который существует у каждой функции.
[20:13:05] <Black_Phoenix> В принципе она регистровая - здесь напрямую нету операций PUSH/POP для изменения стека, но тем не менее стек - основная структура данных, скелет Луа
[20:13:27] <Black_Phoenix> Нашу экскурсию мы будет начинать с [lua.h:110]. В этом месте определены основные функции - lua_newstate(...) lua_close(...), создание стейта для виртуальной машины.
[20:13:50] <Black_Phoenix> Для создания LVM вы должны их вызвать (они вообще вызываються автоматом при вызове lua_open(). Фактически lua_open() это просто вызов lua_newstate(..)).
[20:14:08] <Black_Phoenix> Итак, что такое стейт виртуальной машины? Это структура данных которая полностью определяет текущее состояние VM.
[20:14:30] <Black_Phoenix> В LVM определение стейта (это struct) содержиться в файле [lstate.h:100] (вообще строго говоря в LVM существует два стейта - глобальный и per-thread стейт).
[20:15:03] <Black_Phoenix> При вызове lua_newstate(...) будет создан новый lua_State (локальный/per-thread стейт, [lstate.h:100]), а также будет создан глобальный стейт (global_State, [lstate.h:68]). Далее для каждого нового треда LVM будет создаваться новый lua_State.
[20:15:28] <Black_Phoenix> Вообще global_State должен быть только одним - иметь их больше чем один смысла не имеет.
[20:15:58] <Black_Phoenix> SIDE-NOTE: луа поддерживает мультипоточность - при этом вы должны совершить такие действия: создать глобальный стейт LVM и создать главный поток (lua_newstate() или lua_open(), тоже самое). Дальше средствами ОС вы создаёте ещё один поток, в котором вызываете
[20:15:59] <Black_Phoenix> lua_newthread(), и запускаете скрипт.
[20:16:44] <Black_Phoenix> Сама Lua - полностью threadsafe, насколько я смотрел везде в критических местах есть защита от конкуренции нескольких потоков
[20:16:56] <Black_Phoenix> В результате у вас у каждого потока будет lua_State, и собственно свой статус LVM. Теперь использование обоих потоков идёт аналогично и паралельно.
[20:17:15] <Black_Phoenix> Вам возможно надо будет создать свой враппер/биндинг, поскольку в стандартной Lua нету библиотек для мультипоточности (т.е. что-бы это можно было вызывать из скриптов).
[20:17:45] <Black_Phoenix> Все потоки могут вызывать глобальные функции друг друга, почему будет сказано потом.
[20:18:10] <Black_Phoenix> Итак, теперь кратко о том, что же задают global_State и lua_State. Если вы использовали LVM/Lua то вы заметили, что почти в каждую функцию надо передавать "lua_State *L" - указатель на per-thread стейт (далее - просто "стейт луа").
[20:18:53] <Black_Phoenix> Итак, lua_State ([lstate.h:100]) содержит в себе такие поля: http://www.everfall.com/paste/id.php?1ng1yo9ft9n6 (желательно ознакомиться)
[20:19:26] <Black_Phoenix> Это всё задаёт состояние скрипта, и упс, я забыл туда про garbage collector дописать :)
[20:20:11] <Black_Phoenix> Про global_State ([lstate.h:68]) я расскажу кратко (он в принципе менее интересен чем lua_State): http://www.everfall.com/paste/id.php?0qak518m03km
[20:20:39] <Black_Phoenix> Он менее интересен, в основном он только следит за всеми ресурсами и мусоросборщиком.
[20:21:40] <Black_Phoenix> В LVM есть несколько базовых типов - они все захардкожены прямо в виртуальную машину. На данный момент они такие [lua.h:74]: http://www.everfall.com/paste/id.php?5h2cpg36h0yq
[20:22:16] <Black_Phoenix> (различие между LUA_TLIGHTUSERDATA и LUA_TUSERDATA в том, как будут сравниваться два обьекта)
[20:23:10] <Black_Phoenix> LVM теоретически позволяет добавлять новые типы (надо только создать #define-ы, и функции для работы с типом, возможно поправить парсер), но я ещё не видел что-бы их добавляли прямо в VM
[20:23:28] <Black_Phoenix> Для типов подобных vector, color обычная таблица должна подойти
[20:24:03] <Black_Phoenix> Внутри LVM все типы называються общим именем TValue - типизированое значение. Оно задаёться структурой lua_TValue [lobject.h:73], которая содержит в себе числовой аналог этого типа (lua_Number), тип этого значения (например LUA_TTABLE), ссылку на свой обьект (GCObject), а
[20:24:03] <Black_Phoenix> также указатель на двоичные данные.
[20:24:42] <Black_Phoenix> lua_Number - это обычно 64-битный флоат, если не ошибаюсь. Но это можно перенастроить
[20:26:00] <Black_Phoenix> Стек в LVM - это просто стек из этих lua_TValue. base, top, stack, и другие в стейте lua_State - всё это указатели на элемент на стеке, которые имеют тип StkId [lobject.h:193].
[20:26:24] <Black_Phoenix> StkId - не более чем просто указатель на lua_TValue, и так-же определяеться (как *TValue)
[20:27:01] <Black_Phoenix> При создании локального стейта создаёться стек (stack_init(...) в f_luaopen(...) [lstate.c:70]) размером BASE_STACK_SIZE + EXTRA_STACK_SIZE (обычно это 40 записей), и вершина стека устанавливаеться в его начало.
[20:27:59] <Black_Phoenix> Щас я попробую показать картинку того, куда указывают эти указатели
[20:28:48] <Black_Phoenix> Так, картинка будет чуть позже
[20:29:19] <Black_Phoenix> Итак, что происходит со стеком при вызове функции? Вызов любой функции Lua (не С) выполняеться вызовом lua_call(...) [lapi.c:776].
[20:29:24] <Black_Phoenix> При вызове функции требуеться указать количество параметров которые передаються, и количество параметров которые ожидаються на выходе.
[20:29:46] <Black_Phoenix> SIDE-NOTE: вызов C-функции выполняеться вызовом lua_pcall(...) - protected call, который защищает стек Lua от "повреждения". Оба способа вызова, как будет щас показано, почти одно и то-же.
[20:31:03] <Black_Phoenix> Все аргументы функции находяться на стеке, сразу после самой функции (которая тоже запихиваеться на стек). Stack-frame формально начинаеться как-раз с самой функции (вообще реально запихиваеться только указатель на функцию)
[20:32:09] <Black_Phoenix> При возвращении из функции все результаты будут на стеке, начиная с начала stack frame функции (обычно). Агрументы и результаты находяться на стеке в порядке pusha на стек.
[20:32:47] <Black_Phoenix> Результаты могут находиться и за аргументами (точнее оно так почти всегда), реально при выходе из функции она сама указывает где на стеке результаты
[20:33:05] <Black_Phoenix> Результаты - это то что возвращаеться return-ом, и может быть несколько (return x,y,z)
[20:33:37] <Black_Phoenix> lua_call(...) проводит нужные проверки, и запускает виртуальную машину если вызываемая функция - не C функция. Это проверяеться вызовом luaD_precall(...) [ldo.c:264] - по сути это самая важная часть LVM.
[20:34:09] <Black_Phoenix> luaD_precall(...) запихивает новый CallInfo на стек вызваных функций, сохраняет текущий PC, запоминает текущее состояние стека.
[20:34:31] <Black_Phoenix> (PC - указатель инструкций, здесь он прямо указывает на двоичные данные, где находиться следущая инструкция)
[20:34:55] <Black_Phoenix> Если функция - С-функция, то проверяем что на стеке есть минимум места (равный LUA_MINSTACK значений), и вызываем функцию. Поскольку эта лекция - только общий обзор внутренностей, то какие именно функции здесь используються останеться тайной (сам
[20:34:55] <Black_Phoenix> и посмотрите по [ldo.c:307]).
[20:34:58] <Black_Phoenix> сами*
[20:35:44] <Black_Phoenix> SIDE-NOTE: в Lua 5.1.4 есть глюк, который позволяет C-функции нарушить стек вернув слишком много результатов. Для этого есть патч на оффициальном сайте.
[20:35:57] <Black_Phoenix> Если же это функция Lua, то будет подготовлен вызов функции (и возможно будет вызов hook-функции, если такова была установлена).
[20:36:23] <Black_Phoenix> Если вы внимательно слушали (читали), то заметили что я сначала сказал что стек создаётся размером в 40 записей. Но ведь можно делать огромную рекурсию - как это происходит?
[20:36:53] <Black_Phoenix> На самом деле, если посмотреть на то, как ведёт себя стек, можно заметить, что при достижении 50% заполнености стека его размер будет увеличен на 200%, пока этого нельзя будет зделать, или пока количество рекурсивных вызовов не превысит некото
[20:36:53] <Black_Phoenix> рое значение.
[20:37:43] <Black_Phoenix> Изменение размера стека производиться функцией luaD_reallocstack(..) [ldo.c:141] - она вызываеться автоматически, но вообще её можно вызывать и самому. Освобождённое место будет возвращено програме.
[20:38:18] <Black_Phoenix> LVM будет прерывать рекурсивные вызовы если их слишком много, что-бы стек не вырос до коллосальных размеров. К тому же сам стек ограничиваеться максимальным размером (если не ошибаюсь MAX_SIZET байт, обычно это 65536 байт).
[20:39:02] <Black_Phoenix> Ваши С-функции вызываемые Lua могут падать. Если вы правильно вернёте код ошибки (не ноль), то она даже восстановит стек и выдаст пользователю сообщение. (И работа скрипта будет продолжена)
[20:39:42] <Black_Phoenix> Итак, теперь о самой LVM. LVM основана на очень простом интерпретаторе (в Lua 5.1.4 используеться просто пошаговое выполнение инструкций). Каждый шаг - одна инструкция.
[20:40:28] <Black_Phoenix> Выполнение будет продолжаться пока скрипт не напорёться на ошибку, либо пока не будет вызван "return", либо пока установленый hook (если таковой есть) не закончиться с ошибкой.
[20:41:07] <Black_Phoenix> Таким образом прерывать выполнение скрипта можно только из самого скрипта, либо контролируя его выполнение хуком.
[20:42:03] <Black_Phoenix> Я точно не смог найти где (и вообще ли) ограничиваеться откуда будет браться байткод - могу точно сказать только то, что парсер всегда будет ставить RETURN как последнюю инструкцию в скрипте (и он будет прерван)
[20:42:33] <Black_Phoenix> Формат у опкодов такой (в скобках - сначала позиция в битах, потом размер в битах): [lopcodes.h:37] http://www.everfall.com/paste/id.php?ry0zq4z4v1vk
[20:43:07] <Black_Phoenix> Опкод сам определяет, нужно ли брать B или Bx. Каждый параметр - 8-бит смещения (unsigned) на стеке относительно указателя стека base; 8ой бит параметров В и С указывает брать ли значение из регистра (памяти), или из константы.
[20:43:52] <Black_Phoenix> Если значение берёться из константы, то параметр - индекс в список констант текущей функции. В Lua достаточно замудрено с тем, как значение собственно получается, поэтому обяснять здесь не буду.
[20:45:22] <Black_Phoenix> В LVM есть 38 опкодов, полный список их можно достать здесь: http://www.everfall.com/paste/id.php?4xs8bzkw0fld ([lopcode.h:154])
[20:46:02] <Black_Phoenix> В списке опкодов R(A) - это как-бы регистр №A, но реально это значение полученое по смещению A относительно указателя стека base. Как видите, хотя она называеться регистровой, она всё таки остаётся стековой VM.
[20:46:53] <Black_Phoenix> В общем дожно быть всё понятно по виртуальной машине. Почти все их опкодов понятны, возможно кроме некоторых
[20:48:24] <Black_Phoenix> Опкоды FORLOOP, FORPREP существуют для подготовки и выполнения цикла for (цикл while выполняеться через jmp). CLOSURE "создаёт" новую функцию. Это и другие опкоды в деталях будет рассмотрено позже, щас только общий экскурс
[20:48:59] <Black_Phoenix> Итак, теперь можно наконец поговорить о парсере Lua, а также о крутых штуках - например о глобальной таблице (точнее таблице глобальных переменных и функций).
[20:50:06] <Black_Phoenix> Сам парсер - рекурсивный, компилирует используя набор синтаксических правил ([lparser.c:1])
[20:50:27] <Black_Phoenix> Таблица глобальных переменных - обычная таблица Луа которая в себе хранит все глобальные функции и переменные (всё что не помечено как local). Если обявляеться функция или переменная без ключевого слова local, то она будет занесена в эту таблиц
[20:50:27] <Black_Phoenix> у.
[20:50:31] <Black_Phoenix> таблицу*
[20:50:55] <Black_Phoenix> При вызове lua_newstate() будет создан новый локальный стейт, и новый глобальный стейт - а также новая таблица глобальных переменных.
[20:51:16] <Black_Phoenix> При вызове lua_newthread() таблице глобальных переменных нового стейта будет присвоена таблица из другого стейта (который передаётся в lua_newthread()).
[20:52:00] <Black_Phoenix> Это значит что при создании нового потока можно использовать уже существующие глобальные функции. При добавлении новых глобальных функций одним из скриптов она будет доступна и всем другим потокам.
[20:52:37] <Black_Phoenix> Причём она станет доступной прямо во время выполнения другого скрипта - можно создать систему сигналов основаную на этом.
[20:52:46] <Black_Phoenix> Кстати, в таблице глобальных переменных и функций кроеться небольшой, но существенный облом.
[20:53:03] <Black_Phoenix> Он кроеться в том, что парсер глобальные переменные добавляет в таблицу, как и положено (для этого есть специальный опкод). Скажем мы компилируем код "gvar = 100; myvar = 200", при этом парсер запустит такой код:
[20:53:11] <Black_Phoenix> LOADK 00000000 00000ACF //Загрузка строки "gvar"
[20:53:17] <Black_Phoenix> SETGLOBAL 00000000 00000ACE //Засовываем в таблицу "100" с ключём "gvar"
[20:53:17] <Black_Phoenix> LOADK 00000000 00000AD1 //Загрузка строки "myvar"
[20:53:17] <Black_Phoenix> SETGLOBAL 00000000 00000AD0 //myvar = 200
[20:53:17] <Black_Phoenix> RETURN 00000000 1 //завершение работы VM
[20:53:36] <Black_Phoenix> Так вот, а тепер обратимся к этим переменным (вызовем код "print(gvar)"):
[20:53:45] <Black_Phoenix> GETGLOBAL 00000000 00000BA4 //Получить переменную за ключём "print"
[20:53:45] <Black_Phoenix> GETGLOBAL 00000001 00000BA5 //Получить переменную за ключём "gvar"
[20:53:45] <Black_Phoenix> CALL 00000000 2 0 //Вызвать функцию, расположеную на base+0 в стеке (с 2 значениями на стеке, 0 значений полученых назад)
[20:53:45] <Black_Phoenix> RETURN 00000000 1 //завершаем работу VM
[20:54:13] <Black_Phoenix> Для сравнения запустим код "local lvar = 100; print(lvar)":
[20:54:23] <Black_Phoenix> LOADK 00000000 00000BCD //Загрузка значения lvar
[20:54:23] <Black_Phoenix> GETGLOBAL 00000001 00000BCE //Загрузка "print"
[20:54:23] <Black_Phoenix> MOVE 00000002 00000000 //Просто переносим +0 в +2 (на стеке)
[20:54:23] <Black_Phoenix> CALL 00000001 2 0 //print...
[20:54:24] <Black_Phoenix> RETURN 00000000 1 //завершаем работу VM
[20:54:43] <Black_Phoenix> (LOADK это просто загрузка значения/константы)
[20:55:05] <Black_Phoenix> И здесь можно заметить облом - вызовы локальных переменных и функций обращаются к таблице значений, куда компилятор неким ему ведомомым образом позапихивал наши переменные.
[20:55:09] <Black_Phoenix> А вот вызов глобальной функции (или переменной) будет запрашивать у таблицы глобальных переменных её адрес - каждый раз при обращении.
[20:55:31] <Black_Phoenix> Желательно избегать глобальных функций, а особенно глобальных переменных - при росте их количества они будут всё более и более тормозить.
[20:56:21] <Black_Phoenix> Кстати, всегда при сравнении сравниваеться "A < B". Если написано "A > B", то парсер превратит это в сравнение "B < A"
[20:56:46] <Black_Phoenix> Все стандартные библиотеки (table, string, io) - это просто таблицы с функциями. Обращение к ним вызывает GETGLOBAL для полчучения таблицы, и GETTABLE для получения значения из таблицы.
[20:56:57] <Black_Phoenix> Т.е. все библиотеки Lua создаються как просто добавление таблицы с функциями в глобальную таблицу.
[20:57:22] <Black_Phoenix> Для этого есть функция luaI_openlib [lauxlib.c:242]. Здесь будет проверено загружена ли эта библиотека (есть ли имя этой таблицы в таблице "_LOADED"), и затем создано таблицу с функциями.
[20:58:54] <Black_Phoenix> В завершение кратко о сборщике мусора - он постоянно чистит уже не используемые ресурсы, а также ресурсы оставленые/забытые на стеке
[20:59:51] <Black_Phoenix> Каждый обьект который может быть убран сборщиком помечаеться как чёрный, серый, или белый - что значит что сборщик должен убрать обьект, должен проверить надо ли его убирать, и что он вообще не должен этот обьект трогать
[21:00:55] <Black_Phoenix> Существует также очистка таблиц - поиск "слабых" элементов, но это уже детали сборщика мусора.
[21:01:45] <Black_Phoenix> В принципе это всё, теперь можно задавать вопросы (если _ShaMan_ снимет +m). Желательно вопросы по LVM/Lua - на вопросы по AngelScript я отвечу в следущей лекции
[21:02:33] <Black_Phoenix> http://users.d2k5.com/Black%20Phoenix/files/luastack.png вот кстати картинка о которой я говорил
[21:03:07] <Black_Phoenix> top - указатель вершины стека, base - начало stack frame, stack + stack_end - просто для проверок выхода за границы
Вопросы:
[21:03:22] <gamer3d> как расшифровываеться LOADK 00000000 00000ACF ?
[21:04:22] <Black_Phoenix> Загрузка на стек со смещением +0 относительно BASE. Значение для загрузки береться из списка констант за номером 0xACF (или немного другим номером, не суть)
[21:05:06] <Black_Phoenix> Я немного модифицировал VM что-бы она выводила запускаемый код, она неправильно выводит номера констант
[21:06:28] <Black_Phoenix> Есть ещё LOADNIL (загрузка nil, фактически удаление), и LOADBOOL. Кстати LOADNIL почти сразу удаляет значение, высвобождая память
[21:08:24] <gamer3d> а отлаживать lua код как?
[21:09:31] <Black_Phoenix> Надо поставить hook, он будет вызываться перед каждой инструкцией. Дополнительный API в [ldebug.c] позволит находить номер строки, делать отладку построчно, смотреть весь стек удобным образом
[21:10:57] <DDMZ> как правильно использовать коуротины?
[21:13:11] <Black_Phoenix> Должна быть библиотека "coroutine" в которой можно создать и работать с короутинами
[21:14:08] <Black_Phoenix> Надо вызвать coroutine.create(<функция>), что вернёт значение типа TTHREAD, с которым потом можно будет работать - есть функции coroutine.status, coroutine.resume, coroutine.yield
[21:15:27] <Black_Phoenix> Вообще коротуны тоже вызывают lua_newthread - они создают новый стейт (который кстати и есть тем, что прячеться за TTHREAD), и потом VM будет переключаться автоматически между ними (если не ошибаюсь)
[21:15:59] <Black_Phoenix> [lbaselib.c:490] - здесь сама имплементация библиотеки coroutines
[21:17:14] <gamer3d> как делать save game/load game если в игре используется lua?
[21:17:45] <Black_Phoenix> gamer3d, детально об этом будет в следущей лекции, но по мне правильно выполнить такие шаги:
[21:18:12] <Black_Phoenix> 1) сохранить глобальную таблицу (возможно кроме таблиц библиотек)
[21:18:34] <Black_Phoenix> 2) если не хочеться возиться слежением за загружеными скриптами, сохранить весь байткод
[21:19:07] <Black_Phoenix> 3) сохранить lua_State и возможно global_State (интересует стек вызваных функций, и стек Lua)
[21:19:30] <Black_Phoenix> Это метод влоб - я бы предложил просто дать скриптам коллбек "OnGameSave"
[21:19:51] <Black_Phoenix> Пусть они сами вернут таблицу которую надо сохранить - ведь всё-таки можно и вернуть self :)
[21:21:36] <DDMZ> как правильно загрузить скрипт в луастейт?
[21:22:51] <Black_Phoenix> DDMZ, luaL_loadbuffer или luaL_loadfile если хочешь прямо грузить
[21:23:07] <Black_Phoenix> dofile/dostring это тоже самое, но ещё вызывают lua_call
[21:24:03] <DDMZ> чтобы грузить из памяти luaL_loadbuffer , и в один луастейт мы можем загрузить один скрипт? а если больше, то их склеить вручную и потом загрузит
25 июля 2009