Лекция #36. Обзор VM скриптовых языков (Lua, AngelScript). Часть 2. [Лектор - Black_Phoenix]
Автор: Вячеслав Е.
Лекция Black_Phoenix с обзором VM скриптовых языков Lua и AngelScript. Часть вторая.
[19:00] <Black_Phoenix> Это вторая часть лекции о вирутальных машинах/скриптовых языках AngelScript и Lua. В первой части лекции было рассмотрено различие между ними (вкратце), а также виртуальная машина LVM (языка Lua)
[19:00] <Black_Phoenix> Первую часть лекции можно найти по этой ссылке: http://www.gamedev.ru/community/gamedev_lecture/articles/?id=4446
[19:01] <Black_Phoenix> В второй части будет кратко рассмотрено виртуальную машину AngelScript, и также то, как можно биндить Lua и AngelScript в ваших програмах.
[19:02] <Black_Phoenix> AngelScript сравнительно новый проект - он был начат примерно в 2003ем году некиим Андреасом Джонсоном. Это достаточно интересный скриптовый движок - он есть strong-typed как уже раньше говорилось, и он достаточно близок к С
[19:03] <Black_Phoenix> В основе AngelScript сидит виртуальная машина AngelScript. Она по своей архитектуре чем-то напоминает архитектуру ARM, и даже ненмого х86.
[19:04] <Black_Phoenix> Одинм из бонусов такой архитектуры (и вообще движка в целом) есть то, что AngelScript умеет вызывать прибинденые функции С по stdcall (или ещё некоторым конвенциям) - т.е. в принципе ничего биндить как в Lua не надо.
[19:05] <Black_Phoenix> Здесь достаточно указать функцию (её сигнатуру), её входную точку (т.е. указатель на функцию), и то какие она типы принимает или отдаёт.
[19:05] <Black_Phoenix> Весь AngelScript основан на принципах ООП - структура движка достаточно модульная, и разобраться в его работе совершенно не составляет труда.
[19:06] <Black_Phoenix> Как и в прошлый раз при рассмотрении Lua начнём рассматривать AngelScript с самого начала. В начале создаёться обьект asIScriptEngine - собвственно сам скриптовый движок (точнее это интерфейс к нему).
[19:07] <Black_Phoenix> Для этого вызваеться функция asCreateScriptEngine() [as_scriptengine.cpp:161], которая проверяет настройки платформы (размеры всех базовых типов, также порядок байт), и создаёт сам движок - asCScriptEngine [as_scriptengine.h:76].
[19:07] <Black_Phoenix> Да кстати, здесь используеться такое же обозначение как в прошлой лекции - ссылки на исходные коды выглядят вот так: [имя файла:номер строки]
[19:08] <Black_Phoenix> В принципе этот "движок" и есть стейтом для виртуальной машины, также она привязывает к себе другие блоки - компилятор, интерпретатор, менеджер памяти, сборщик мусора.
[19:08] <Black_Phoenix> Перед рассмотрением самого стейта имеет смысл посмотреть на asCObjectType [as_objecttype.h:112] - базовый тип обьекта в AngelScript.
[19:09] <Black_Phoenix> Каждый обьект наследуется от этого класса - и предоставляет AngelScript информацию о своём названии, базовом типе (ссылка на acIObjectType - интерфейс), настройкам, и размере.
[19:09] <Black_Phoenix> Реализация операций над обьектом зделана через такие себе Behaviours - поведение обьекта в разных ситуациях - при добавлении, отнимании, и т.п. В AS есть такой тип как asEBehaviours - энумерация всех возможных поведений [angelscript.h:135]
[19:11] <Black_Phoenix> В принципе тут всё понятно - хотя видно разницу против типов Lua. Так вот, стейт движка хранит в себе списки всех зарегистрированых типов, синонимов к типам (typedefs), и энумераций (registeredObjTypes, registeredTypeDefs, registeredEnums).
[19:11] <Black_Phoenix> Также тут храниться таблица всех глобальных функций и переменных (registeredGlobalProps, registeredGlobalFuncs), информация о шаблонах (templateTypes), адреса всех глобальных переменных, вообще все функции (scriptFunctions) котрые были где-либо обявлены.
[19:11] <Black_Phoenix> Здесь же есть функции для получения и работы со всеми этими таблицами и информацией.
[19:12] <Black_Phoenix> Вообще в этом "движке" полно классных и интересных функций - все их рассматривать долго, но стоит уделить некоторым внимание. Тут есть много функций подобных RegisterObjectType - регистрация собственных типов, свойств, функций.
[19:13] <Black_Phoenix> Тут есть загрузка скриптовых модулей, представленная GetModule(), которая возвращает интерфейс к модулю (asIScriptModule [angelscript.h:594]), от которого позже можно получить индексы всех функций модуля, переменных, и т.п.
[19:14] <Black_Phoenix> Модули проименованы, и модуль без имени (вместо указателя на строку просто 0) ссылаеться на глобальный контекст - через него можно получать глобальные переменные и функции
[19:14] <Black_Phoenix> Кстати все функции как уже говорилось находяться в одной таблице - ScriptFunctions, и "индексом функции" возвращаемым всеми разными модулями есть именно индекс функции в этой таблице (массиве)
[19:15] <Black_Phoenix> А вот что нас интересует, это CreateContext(). Если брать аналогию с Lua, то здесь движок - global_State, а контекст - это собственно то, что задаёт состояние самой виртуальной машины, её выполнение.
[19:16] <Black_Phoenix> Контекстом выполнения являеться asIScriptContext [angelscript.h:656] - здесь есть функции для подготовки, запуска, остановки текущего выполнение, для установки аргументов передаваемых в скрипт (функцию), получения возвращённого значения, обработки исклю
[19:16] <Black_Phoenix> чений, и функции для отладки скрипта.
[19:16] <Black_Phoenix> Реальной имплементацией контекста есть asCContext [as_context.h:60].
[19:17] <Black_Phoenix> Тут есть прямой указатель на двоичные данные байткода, указатель на стек, указатель на текущую функцию, стек вызовов функций, данные о последнем исключении, ссылка на коллбеки (коллбек который вызываеться для каждой строки, и для исключений).
[19:17] <Black_Phoenix> Стек задаётся указателем на стек-фрейм функции - stackFramePointer, и указателем на сам стек - stackPointer.
[19:17] <Black_Phoenix> Байткод кстати достаточно интересный. Каждая инструкция виртуальной машины может быть от 1 до 18 байт в длину - в зависимости от передаваемых параметров.
[19:18] <Black_Phoenix> Набор опкодов чем-то напоминает ARM архитектуру, и вообще архитектуры разных процесоров в целом. Здесь есть 174 опкода, которые есть всемя возможными комбинациями операндов
[19:19] <Black_Phoenix> Кстати, операции прибавления, множения и всё такое выполняються в 64 битах - если исходное значение меньше чем 64 бита, то оно должно быть приведено к 64-битному значению
[19:20] <Black_Phoenix> Полный список опкодов есть здесь: http://www.everfall.com/paste/id.php?3kep988a627d (немного табуляция сломалась)
[19:21] <Black_Phoenix> Операндом может быть константа (передаётся в инструкции), регистр (в AS есть один регистр, называеться он "register1" или просто "v"), либо значение со stack-frame функции (тогда оно обозначается как "vn" где n это сдвиг относительно stackFramePointer).
[19:21] <Black_Phoenix> Кстати в AngelScript есть удобная и полезная функция, которая выводит в файл скомпилированый байткод - надо просто скомпилировать библиотеку в режиме отладки (должен существовать дефайн AS_DEBUG)
[19:22] <Black_Phoenix> Для примера скомпилируем вот такой код: "int a = 10; int b = 20; sin(a+b)" (если у нас уже определена функция sin(float). В результате будет сгенерирован вот такой байткод: http://www.everfall.com/paste/id.php?v8e2lo7qn0ut (и записан в файл)
[19:23] <Black_Phoenix> В первой колонке будет находиться сдвиг инструкции в байткоде (в байтах), во второй колонке размер stack frame функции (текущий), звёздочка обозначает меченые опкоды (для отладки), дальше название опкода и передаваемые параметры.
[19:23] <Black_Phoenix> Цифры между минусами (например "- 1,13 -") показывают номер строки (1) и столбца (13) той части скрипта, для которой следует байткод.
[19:23] <Black_Phoenix> Интересный опкод SUSPEND - он вызывает коллбек для каждой строки. Вообще он не являеться необходимым - но его нельзя отключить (его добавление зашито в компилятор AngelScript).
[19:24] <Black_Phoenix> Без него прервать выполнение кода будет невозможно (либо пока выполнение не выйдет из блока в котором храниться байткод)
[19:24] <Black_Phoenix> Кстати, недавно была дискуссия о новой JIT для AngelScript - пока правда только для платформы ARM. (Её можно прочитать здесь http://www.gamedev.net/community/forums/topic.asp?topic_id=537993). Пока что насколько я знаю пока что нету рабочей JIT AngelScript.
[19:25] <Black_Phoenix> В принципе в общем здесь всё - более технические детали очень легко узнать самим. Стоит заметить что здесь тоже есть сборка мусора, которая производится периодически, и просто убирает все элементы на которых больше нету ссылок.
[19:26] <Black_Phoenix> А вот теперь самая полезная часть лекции - о том как собственно биндить AngelScript и Lua. Надеюсь вы читали/смотрели последнюю лекцию, и знаете основные понятия Lua и то, как она работает.
[19:26] <Black_Phoenix> Я постараюсь приводить примеры с комментариями для этой части лекции, возможно будут задержки (при заливании кода на everfall.com). Я уделил немного больше внимания Lua, поскольку она более популярна, и более сложна в освоении
[19:27] <Black_Phoenix> Начнём с простого - просто создадим стейт Lua. Для этого надо вызвать lua_open() или lua_newstate() - это одно и тоже (но lua_open(), он же luaL_newstate() подставляет malloc, free, realloc как обычные функции работы с памятью).
[19:28] <Black_Phoenix> У нас получаеться такой код (надо не забыть закрыть Lua, и высвободить её ресурсы): http://www.everfall.com/paste/id.php?3f2uig2twqt2
[19:28] <Black_Phoenix> (этот, и следущие исходные коды будут на С, я опустил заголовки и всё такое для краткости)
[19:29] <Black_Phoenix> Стоило бы кстати ещё загрузить стандартные библиотеки. Это можно зделать вызовом luaL_openlibs(), передав туда наш стейт (вообще, во все функции Lua надо передавать стейт lua_State, это же С)
[19:30] <Black_Phoenix> Если позволите, в дальшейнем я опущу обработку ошибок от вызовов функций Lua - её можете потом добавить сами. Ну, разве что кроме только вывода ошибок в самом скрипте (об этом чуть ниже)
[19:30] <Black_Phoenix> Теперь у нас есть готовая среда для запуска скрипта. Для начала просто запустим некоторою строку (скажем "myvar = 200; print('Variable is '..myvar)"). Для этого есть замечательная функция luaL_dostring()
[19:31] <Black_Phoenix> Теперь наш код выглядит так (помните, мы загрузили функцию print вместе со стандартной библиотекой): http://www.everfall.com/paste/id.php?rc6659i1hciq
[19:31] <Black_Phoenix> Продолжим. Мы теперь можем запускать строчку - как теперь загрузить целый скрипт? Вообще, вызов luaL_dostring() это два вызова других функций - luaL_loadstring() который компилирует наш скрипт в байткод, и возвращает его на стек, и lua_pcall() для его запуска.
[19:32] <Black_Phoenix> Т.е. реально мы компилируем наш скрипт как одну функцию - что-то вроде "function script() ..сюда идёт всё что мы передали в loadstring.. end", а затем её вызываем. Если мы просто вызовем loadstring, а потом очистим стек, то байткод будет убран сборщиком мусора.
[19:32] <Black_Phoenix> Давайте пока у нас есть просто скрипт, в котором прописана логика функции TakeDamage (получить урон) нашего персонажа (мы кстати делаем мморпг сервер).
[19:33] <Black_Phoenix> Она принимает количество жизней, и возвращает новое количество жизней (после получегого урона). Т.е. это просто логика поведения сервера
[19:33] <Black_Phoenix> Наш скрипт будет таким: http://www.everfall.com/paste/id.php?yiwylnxxdfiq
[19:33] <Black_Phoenix> А вот так мы его запустим: http://www.everfall.com/paste/id.php?0wx6ujrwy0tc
[19:34] <Black_Phoenix> И вдруг он не работает! Казалось бы что не так, ведь вроде всё правильно. Нам бы не помешал вывод ошибок в консоль - а зделать это очень просто. Lua построена так, что компилятор в случае ошибки, вернёт нам её текст на стеке.
[19:35] <Black_Phoenix> Мы просто должны напечатать эту строчку если значение, возвращённое luaL_dostring не ноль. Изменим нашу програму вот так: http://www.everfall.com/paste/id.php?vx1odvpfar0t
[19:35] <Black_Phoenix> И вот мы видим ошибку: "1: 'end' expected near '<eof>'". И тут понимаем, что забыли дописать в конец строки "\n", ведь у нас там есть коментарий, который собой коментирует наш end.
[19:36] <Black_Phoenix> Добавляем "\n", у нас получаеться вот такой код: http://www.everfall.com/paste/id.php?67piuhoqn8wd и он успешно запускаеться.
[19:37] <Black_Phoenix> Пора приступать к самому движку нашей ММОРПГ, а именно - наш персонаж должен получать урон. Скрипт уже обрабатывает наше событие, осталось его только вызвать.
[19:38] <Black_Phoenix> Для этого мы хотим получить функцию TakeDamage из глобальной таблицы (а наша функция, заметьте глобальная). В Lua для этого используется вызов lua_getglobal который возвращает указатель функции на стек (делает PUSH).
[19:39] <Black_Phoenix> Дальше запихнём наши текущие жизни на стек через функцию lua_pushinteger (есть такие-же функции для всех типов Lua, кроме таблиц), и вызовем получение урона (у нас на стеке всё уже как надо, просто запускаем интерпретатор через lua_call).
[19:39] <Black_Phoenix> Не забудем правильно указать количество аргументов, и результатов которые мы ждём, и потом не забудем забрать результат со стека.
[19:39] <Black_Phoenix> Получение результата выполняеться функциями lua_tonumber, lua_tostring, и т.д. Результат будет нас ждать начиная с текущего положения на стеке.
[19:40] <Black_Phoenix> (оно правильно устанавливаеться вызваной функцией, переданые параметры будут убраны)
[19:41] <Black_Phoenix> Вот такой у нас код получился: http://www.everfall.com/paste/id.php?i2qpb2wdla4u
[19:41] <Black_Phoenix> Запускаем, видим что жизней стало 75. Повод отпраздновать! Теперь наша ММОРПГ достигла версии 0.99 BETA
[19:42] <Black_Phoenix> Это классно, но мало. Хочется что-бы скрипт мог вызывать наши функции (функции написаные на С). Зделаем функцию на С - RandomDamage, которая вернёт случайное повреждение для нашего героя.
[19:43] <Black_Phoenix> Любая С функция вызываемая Lua должна быть за таким шаблоном: "int <function name>(lua_State *L)", т.е. нам надо всегда возвращать целое, и принимать указатель на стейт. Возвращаемое значение - код ошибки (если всё хорошо, то это будет 1).
[19:43] <Black_Phoenix> Итак, мы написали нашу функцию (она у нас будет принимать два значения - нижний и верхний порог случайного повреждения). Не забываем что параметры следуют за самой функцией, а при вызове функции вершина стека указывает на первый аргумент (на стеке).
[19:44] <Black_Phoenix> Зарегистрировать функцию просто - есть удобная функция lua_register().
[19:45] <Black_Phoenix> У нас получился вот такой код (не забудем поправить скрипт для вызова нашей С функции): http://www.everfall.com/paste/id.php?imu8u3iqifrj
[19:45] <Black_Phoenix> Всё хорошо, но дедлайн нашей ММОРПГ близиться, и пора бы уже выделить героя в отдельный обьект. В Lua это делается достаточно легко - мы просто зделаем таблицу, в которой будут храниться жизни и броня героя (как-бы обьект).
[19:46] <Black_Phoenix> Кстати, размер скрипта растёт, пора бы его зделать в отдельном файле. Создадим файл "rpg.lua", и засунем туда вот такой код (инициализация игрока, и его методы): http://www.everfall.com/paste/id.php?0zat0o73e5xt
[19:47] <Black_Phoenix> Заметьте обьявление функций героя - если мы пишем так, как в скрипте, то мы добавляем функции в таблицу "HERO". Это будет "базовый класс" нашего героя. Мы также зделали функцию "utilNewHero" которая создаст нового героя (возможно с параметрами)
[19:47] <Black_Phoenix> Создавать героя мы будем из нашей програмы - а затем менять его характеристики и вызывать его функции.
[19:47] <Black_Phoenix> Итак, сначала мы вызовем utilNewHero. Потом мы инициализируем нашу таблицу (вызовем для неё функцию Initialize) - а дальше просто будем работать с нашим героем.
[19:48] <Black_Phoenix> Кстати, вызов у нас непростой - здесь мы должны передать нашего героя в функцию - так, что-бы работал SELF.
[19:49] <Black_Phoenix> Для того что-бы это работало (а это эквивалентно вызову some_hero:Initialize(..)) нам надо будет передать нашу таблицу героя как первый параметр функции
[19:50] <Black_Phoenix> Т.е. some_hero:Initialize() - тоже самое что и Initialize(some_hero,...), только компилятор Lua об этом знает (по тому, как была определена функция)
[19:50] <Black_Phoenix> Сейчас я покажу код, и детально опишу что мы делаем с нашим героем. Вот такой код у нас получился: http://www.everfall.com/paste/id.php?h0msm6bef8rq
[19:51] <Black_Phoenix> (Я буду ссылаться на строки файла, они отмечены по ссылке)
[19:52] <Black_Phoenix> Для начала вызовем создание нашего героя - utilNewHero. Вызов такой как мы раньше видели, просто получаем функцию из глобальной таблицы, и вызываем её через lua_call
[19:53] <Black_Phoenix> Хочу заметить, что адресация стека, которая используеться в API lua достаточно странная. Здесь -1 это текущий элемент на стеке, -2 это предыдущий, -3 это ещё раньше...
[19:53] <Black_Phoenix> А вот положительные значения - это сдвиг относительно указателя base. 1 это base+1, и так далее
[19:54] <Black_Phoenix> Также есть специальные индексы - индексы из глобальной таблицы, и прочие. Но они нас не интересуют
[19:55] <Black_Phoenix> Итак, строчки 29-30 это создание героя. Мы говорим Lua что хотим передать 0 параметров, и получить 1 результат (таблица героя). Наш герой будет на стеке с адресом -1
[19:56] <Black_Phoenix> Дальше мы хотим вызвать somehero:Initialize(..) - а это вызов HERO.Initialize(somehero,...). В строчке 35 мы получаем указатель на таблицу-базовый класс HERO
[19:56] <Black_Phoenix> Теперь по адресу -1 таблица HERO, по адресу -2 таблица героя
[19:56] <Black_Phoenix> В строчке 36 мы получаем поле из таблицы - HERO.Initialize.
[19:57] <Black_Phoenix> Теперь по адресу -1 находиться функция HERO.Initialize (её туда запихнул вызов lua_getfield), по адресу -2 - HERO, по адресу -3 - таблица героя
[19:57] <Black_Phoenix> нам больше таблица HERO не нужна, убираем её со стека на строчке 37
[19:58] <Black_Phoenix> Теперь таблица героя передвинулась на позицию -2. Не забывайте что нам её надо передать первым параметром (а щас на стеке идёт сначала таблица, потом функция)
[19:58] <Black_Phoenix> Поэтому вызовом lua_pushvalue (фактически скопировать значение) мы копируем ссылку на таблицу, и заносим на стек
[19:59] <Black_Phoenix> За ним мы запихиваем жизни героя и его имя (так, как надо для вызова инициализации). Наш стек выглядит так: (-1) "John Jackson" | (-2) 125 | (-3) таблица героя | (-4) HERO.Initialize | (-5) таблица героя
[19:59] <Black_Phoenix> Мы вызваем функцию, и наш герой готов к бою
[20:00] <Black_Phoenix> После вызова функции со стека будет убрано всё, до указателя функции (и он тоже)
[20:00] <Black_Phoenix> Т.е. у нас будет стек такой: (-1) таблица героя (и это всё)
[20:01] <Black_Phoenix> Мы получаем "health" из таблицы нашего героя, а потом и "name". Заметьте как адрес таблицы меняеться после добавления новых значений на стек
[20:02] <Black_Phoenix> Мы печатаем информацию, и правильно получаем "Our heroes life is 125 percent, our hero is Mr. John Jackson". Не забудьте убрать значения со стека, иначе они там и остануться! Для этого надо вызвать lua_pop, аргументом есть количество значений которые надо убрать
[20:03] <Black_Phoenix> Пора в битву - в строчках 58-60 мы снова получаем указатель, теперь на Hero.TakeDamage; также как на строчках 35-37
[20:03] <Black_Phoenix> Мы просто запихиваем таблицу героя на стек, и вызываем нашу функцию. Наш герой потерпел урон, и мы это проверим строками 67-71 (тоже самое что раньше)
[20:04] <Black_Phoenix> Нам правильно будет указано сообщение "Mr. John Jackson got hurt! :( His life is now 123 %%". А наша таблица всё ещё находиться на стеке!
[20:05] <Black_Phoenix> Пора бы её сохранить в глобальной таблице - для этого есть (моя персонально любимая функция) lua_setglobal - она сохраняет текущий элемент в вершине стека в глобальную переменну
[20:05] <Black_Phoenix> Поскольку мы убрали "health" и "name" со стека, в вершине будет наша таблица - и она сохраниться в переменную "johnhero"
[20:06] <Black_Phoenix> Вызов lua_setglobal зделает POP - и уберёт текущее значение со стека. А нам John ещё нужен, поэтому мы просто заново получим его из глобальной таблицы (строчка 79) - и он будет снова на стеке.
[20:07] <Black_Phoenix> На строках 81-82 указано как можно не убирая значение со стека добавить его в глобальную таблицу - просто банально продублировать. Теперь у нас есть 2 ссылки на Джона - johnhero и john_is_the_hero
[20:08] <Black_Phoenix> Проверим что обе таблицы - это просто две ссылки на одну и туже таблицу. Просто запустим простой скрипт - поменяем жизни в одной таблице и увидим что они меняються в другой (строки 88-90)
[20:09] <Black_Phoenix> Наш сервер почти готов. Теперь вам должно быть легче работать с Lua. Единственное что я ещё хочу добавить это то, как можно создавать новые таблицы из C кода (или менять уже существующие)
[20:10] <Black_Phoenix> Допустим мы хотим програмно создать нового героя. Для этого мы сначала вывовем "lua_newtable(L)" - создание новой таблицы, и PUSH её на стек.
[20:11] <Black_Phoenix> Для редактирования таблицы теперь есть два пути. Первый путь добавить/изменить значение которое уже лежит на стеке с заданым ключём - через lua_setfield
[20:11] <Black_Phoenix> Для этого надо что-бы в текущей позиции на стеке была таблица, и нужно указать индекс добавляемого элемента. Например:
[20:12] <Black_Phoenix> lua_pushinteger(L,123); lua_newtable(L); lua_setfield(L,-2,"my_integer"); //целое число находиться в позиции -2, таблица - в -1
[20:13] <Black_Phoenix> Есть второй, более красивый способ - использование lua_rawset(). Эта функция принимает индекс таблицы которую мы редактируем, и запихивает туда значения заданые парой ключ-значение которые находяться сейчас на стеке
[20:13] <Black_Phoenix> Создать нового героя для нашего мморпг сервера мы могли бы вот так:
[20:13] <Black_Phoenix> lua_newtable(L);
[20:14] <Black_Phoenix> lua_pushstring(L,"health"); lua_pushnumber(L,125); lua_rawset(L,-3); //-3 = идекс таблицы, щас у нас два значения в таком порядке: ключ - значение
[20:14] <Black_Phoenix> lua_pushstring(L,"name"); lua_pushstring(L,"Jack Johnson"); lua_rawset(L,-3);
[20:15] <Black_Phoenix> после этого в положении -1 (текущем положении стека) у нас будет наш новый герой (lua_rawset() делает POP два раза)
[20:15] <Black_Phoenix> По биндингу Lua это пока всё. Планировалось ещё рассмотреть сохранение состояния скриптов Lua - но это будет в отдельной лекции.
[20:16] <Black_Phoenix> Теперь кратко о биндинге AngelScript - я покажу только основы, а дальше очень легко разобраться самому - интерфейсы достаточно понятны и назначение функций очевидно.
[20:17] <Black_Phoenix> Для начала нам просто надо создать движок: asCreateScriptEngine(). После этого AngelScript уже будет готов к выполнению скриптов. Наш код будет выглядеть так: http://www.everfall.com/paste/id.php?jrf5pr0gcxix
[20:18] <Black_Phoenix> Нам скорее всего захочеться также смотреть ошибки скрипта. Это очень просто - нужно просто установить коллбек который будет вызываться при ошибке. AS передаёт структуру, которая содержит текст ошибки, и её позицию (строка, столбец).
[20:19] <Black_Phoenix> Вот пример установки такого коллбека: http://www.everfall.com/paste/id.php?jf3pvkr2qgkl
[20:20] <Black_Phoenix> И наконец биндинг С функций - тут с этим очень просто. Нужно просто зарегистрировать нашу функцию как функцию глобальную (или функцию модуля, класса...).
[20:21] <Black_Phoenix> Мы передаём сигнатуру функции: "<возвращаемый тип> <название функции>(<тип 1>, <тип 2>, ..)", указатель на точку входа, и параметры которые нужно передавать и возвращать.
[20:22] <Black_Phoenix> Есть специальный макрос asFUNCTIONPR который правильно скастует вашу функцию так, как нужно для AngelScript. Получаеться такой код: http://www.everfall.com/paste/id.php?9an52g7stcyy
[20:23] <Black_Phoenix> Если мы захотим запускать из файла, нам надо будет просто передать текст файла в ExecuteString, либо зделать свой контекст (CreateContext()), скомпилировать туда исходный код, и запустить.
[20:23] <Black_Phoenix> Это пока всё, можно задавать вопросы.
1 августа 2009
Комментарии [7]