Войти
ПрограммированиеСтатьиОбщее

AngelScript

Автор:

В данной статье мне хотелось бы показать почему мне понравился скриптовый язык AngelScript. Когда я выбирал язык для своего движка, я просмотрел множество кандидатур, и для подробного рассмотрения выбрал два скриптовых языка, ими были LUA и Python.

Введение.
Превью.
Особенности.
Проблемы.
  Регистрация перегруженных функций.
  Регистрация перегруженных методов.
  Получения адреса на переменную объявленную в классе.
Полезные ссылки.

Введение.

В процессе рассмотрения LUA и Python я выделил для себя, что LUA является достаточно быстрым, но с немного непривычным синтаксисом. Python же обладает очень простым синтаксисом и массой полезных библиотек, но, к сожалению, он оказался довольно медленным, и его довольно тяжело привязывать к С++. И тут на работе мне подсказали использовать AngelScript, мол, он удобный для связки, быстрее LUA и имеет С-подобный синтаксис. Как только я начал его изучать, я понял, что это тот самый скриптовый язык моей мечты.

aslogo | AngelScript

Превью.

Вот что можно прочитать про этот язык на Википедии:

AngelScript представляет собой движок, в котором приложение может регистрировать функции, свойства и типы, которые могут использоваться в скриптах. Скрипты компилируются в модули. Количество используемых модулей варьрируется в зависимости от нужд. Приложение может также использовать различные интерфейсы для каждого модуля с помощью групп конфигурации. Это особенно полезно, когда приложение работает с несколькими типами скриптов, например, GUI, AI и т.д.

Программа «Hello, world» в простейшем случае выглядит так:

void main() 
{ 
   print("Hello world\n"); 
}  

Да, синтаксис языка радует с самого начала. Язык поддерживает как методы функционального программирования, так и ООП. С самого начала он подкупает своей простотой регистрации функций, переменных, типов.

Например, регистрация глобальной переменной:

g_Engine->RegisterGlobalProperty("int SomeVal",&SomeVal);  

где SomeVal — это переменная типа int.

Регистрация глобальной функции:

g_Engine->RegisterGlobalFunction("void Print(string val)", 
                                 asFUNCTION(Print), asCALL_CDECL); 

void Print(string val) 
{ 
   cout<<val.data(); 
}

Да, для AngelScript не нужно писать функции биндинга, что является огроменным плюсом в сравнении с другими языками. Для регистрации своих типов придётся написать парочку функций. Фабрику для создания экземпляров и счётчик ссылок, для типа Тип-ссылка, и вызовы конструктора и деструктора, для объекта типа Тип-значение.

Например, у нас есть класс float3, который мы хотели бы зарегистрировать.

// класс счётчик ссылок
class RefC
{
private:
     int refC;
public: 
     RefC(){refC=1;} 
     void AddRef(){refC++;} 
     void Release() 
     { 
          if(!--refC) 
               delete this; 
     } 
};

// Класс, который мы хотим зарегистрировать 
class float3:public RefC 
{
public:
     float x;
     float y;
     float z;


     float3(){x=y=z=0;}

     void Normalize()
     {
          float Len=sqrt(x*x+y*y+z*z);
          Len=Len?Len:1;
          x/=Len;
          y/=Len;
          z/=Len;
     }
}

// Фабрика
float3* Float3FactoryE()
{
    return new float3();
}

// Функция вывода на экран
void PrintF3(float3* val)
{
    cout<<"x="<<val->x<<",y="<<val->y<<",z="<<val->z;
}

Для этого мы регистрируем объект как Тип-ссылка и указываем ему фабрику, счётчик ссылок, метод и функцию вывода данных на экран, и вот как это выглядит.

g_Engine->RegisterObjectType("float3",0,asOBJ_REF);

g_Engine->RegisterObjectMethod("float3"," void Normalize()",asMETHOD(float3, Normalize),asCALL_THISCALL);
g_Engine->RegisterObjectBehaviour("float3",asBEHAVE_FACTORY,"float3@ new_float3()",asFUNCTION(Float3FactoryE),asCALL_CDECL);
g_Engine->RegisterObjectBehaviour("float3",asBEHAVE_ADDREF,"void AddRef()",asMETHOD(float3,AddRef),asCALL_THISCALL);
g_Engine->RegisterObjectBehaviour("float3",asBEHAVE_RELEASE,"void Release()",asMETHOD(float3,Release),asCALL_THISCALL);

g_Engine->RegisterGlobalFunction("void Print(float3@ val)",asFUNCTION(PrintF3),asCALL_CDECL);

Мы на этом конечно же не остановимся, так как нам нужен доступ к значениям xyz, поэтому их мы тоже должны зарегистрировать, что мы и делаем написав.

g_Engine->RegisterObjectProperty("float3","float x",offsetof(float3,x));
g_Engine->RegisterObjectProperty("float3","float y",offsetof(float3,y));
g_Engine->RegisterObjectProperty("float3","float z",offsetof(float3,z));

Всё предельно просто и понятно. Теперь в скрипте можно написать

float3@ ObjPos;
ObjPos.x=1;
ObjPos.y=2;
ObjPos.z=3;

ObjPos.Normalize();
Print( ObjPos );

Выполнив этот скрипт, мы увидим на экране значение нормализованного вектора.

Особенности.

Меня очень порадовала возможность перегрузки операторов в AngelScript. В С++ для это существует ключевое слово operator и символ оператора. AngelScript для этого использует определённые функции. Вот полный список соответствий.

                ♦ –      opNeg
                ♦ ~      opCom
                ♦ ++    opPreInc
                ♦ --      opPreDec
                ♦ ++    opPostInc
                ♦ --      opPostDec
                ♦ ==    opEquals
                ♦ !=      opEquals
                ♦ <      opCmp
                ♦ <=    opCmp
                ♦ >      opCmp
                ♦ >=    opCmp
                ♦ =      opAssign
                ♦ +=    opAddAssign
                ♦ -=      opSubAssign
                ♦ *=    opMulAssign
                ♦ /=      opDivAssign
                ♦ &=    opAndAssign
                ♦ |=      opOrAssign
                ♦ ^=    opXorAssign
                ♦ %=    opModAssign
                ♦ <<=  opShlAssign
                ♦ >>=  opShrAssign
                ♦ >>>= opUShrAssign
                ♦ +        opAdd opAdd_r
                ♦ -        opSub opSub_r
                ♦ *        opMul opMul_r
                ♦ /        opDiv opDiv_r
                ♦ %      opMod opMod_r
                ♦ &        opAnd opAnd_r
                ♦ |        opOr opOr_r
                ♦ ^        opXor opXor_r
                ♦ <<      opShl opShl_r
                ♦ >>      opShr opShr_r
                ♦ >>>    opUShr opUShr_r
                ♦ []        opIndex

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

class float3:public RefC 
{
public:
     float x;
     float y;
     float z;

     float3(){x=y=z=0;}

     void Normalize()
     {
          float Len=sqrt(x*x+y*y+z*z);
          Len=Len?Len:1;
          x/=Len;
          y/=Len;
          z/=Len;
     }

     float3* operator+=(float3* _rval)
     {
          x+=_rval->x;
          y+=_rval->y;
          z+=_rval->z;
          this->AddRef();
          return this;
     } 
};

Осталось только зарегистрированный новый метод.

g_Engine->RegisterObjectMethod("float3", "float3@ opAddAssign(float3@ _rval)",
                               asMETHOD(float3, operator+=), asCALL_THISCALL);

И теперь можно спокойно писать так:

float3@ ObjPos;
ObjPos.x=1;
ObjPos.y=2;
ObjPos.z=3;

float3@ ObjOffset;
ObjOffset .x=3;
ObjOffset .y=1;
ObjOffset .z=5;

ObjPos+=ObjOffset ; 
Print( ObjPos );  

и мы увидим на экране x=4, y=3, z=8.

AngelScript поддерживает свойства. Выглядит это так:

class  MyObj 
{
     type get_ValueName();
     type set_ValueName(type Val);
}
MyObj a; 
type tmp=a.ValueName;// вызовется get_ValueName
a.ValueName = tmp; // вызовется set_ValueName 

Также свойства поддерживаются для оператора индекса:

class MyObj
{
     float get_opIndex(int idx) ;
     void set_opIndex(int idx, float value);
}
MyObj a;
float val=a[1];// вызовется get_opIndex 
a[2]=val;// вызовется set_opIndex 

Проблемы.

Во время изучения языка AngelScript, появилось несколько проблем, решение которых отняло у меня довольно много времени. Вот список проблем, о которых я хотел бы рассказать:
                ♦ Регистрация перегруженных функций.
                ♦ Регистрация перегруженных методов.
                ♦ Получения адреса на переменную объявленную в классе.

Регистрация перегруженных функций.

Очень часто возникает необходимость объявить перегруженную функцию для удобства чтения и понимания кода.
Например:

void Print(string val)
{
    cout<<val.data();
}

void Print(int val)
{
    cout<<val;
}

void Print(float val)
{
    cout<<val;
}

void Print(float3* val)
{
    cout<<"x="<<val->x<<",y="<<val->y<<",z="<<val->z;
}

Обычная регистрация вызовет ошибку.

g_Engine->RegisterGlobalFunction("void Print(string val)",
                                 asFUNCTION(Print), asCALL_CDECL); // Ошибка

Так как компилятор не понимает адрес какой из четырёх функций нужно передать, то на ум приходит два решения:
                ♦ Создать typedef нужной функции и осуществить приведение типов.
                ♦ Создать переменную указатель на нужную функцию и передать его.

вот как выглядит второе решение:

void (*PrintS)(string val)=&Print;
void (*PrintI)(int val)=&Print;
void (*PritnF)(float val)=&Print;
void (*PrintF3)(float3* val)=&Print;

g_Engine->RegisterGlobalFunction("void Print(string val)",asFUNCTION(PrintS),asCALL_CDECL);
g_Engine->RegisterGlobalFunction("void Print(int val)",asFUNCTION(PrintI),asCALL_CDECL);
g_Engine->RegisterGlobalFunction("void Print(float val)",asFUNCTION(PritnF),asCALL_CDECL);
g_Engine->RegisterGlobalFunction("void Print(float3@ val)",asFUNCTION(PrintF3),asCALL_CDECL);

Регистрация перегруженных методов.

С данной проблемой вы столкнётесь, если захотите зарегистрировать оператор присваивания. Давайте для примера модифицируем наш класс.

class float3:public RefC
{
public:
     float x;
     float y;
     float z;
     
     float3(){x=y=z=0;}

     void Normalize()
     {
         float Len=sqrt(x*x+y*y+z*z);
          Len=Len?Len:1;
          x/=Len;
          y/=Len
          z/=Len; 
     }

     float3* operator+=(float3* _rval)
     {
          x+=_rval->x;
          y+=_rval->y;
          z+=_rval->z;
          this->AddRef();
          return this;
     }
     
     float3* operator=(float3* _rval)
     {
          x=_rval->x;
          y=_rval->y;
          z=_rval->z;
          this->AddRef();
          return this;
     }
};

Обычная регистрация вызовет ошибку.

g_Engine->RegisterObjectMethod("float3", "float3@ opAssign(float3@ _rval)",
                               asMETHOD(float3, operator=), asCALL_THISCALL); // Ошибка

Решения, которые подходят для функций, тут не подойдут. Для решения проблемы надо внимательно взглянуть на макрос asMETHOD он выглядит так:

#define asMETHOD(c,m) asSMethodPtr::Convert((void (c::*)())(&c::m)) 

Соответственно, чтобы нам зарегистрировать метод, нужно конвертировать его. Тогда наша регистрация будет выглядеть так:

g_Engine->RegisterObjectMethod("float3", "float3@ opAssign(float3@ _rval)",
  asSMethodPtr::Convert((float3* (float3::*)(float3*))(&float3::operator=)),
  asCALL_THISCALL);

Получения адреса на переменную объявленную в классе.

Если с глобальными переменными дела обстоят предельно просто, то с переменными в пределах класса всё немного сложнее. Стандартные средства не позволяют получить ID переменной по её имени или объявлению, поэтому для этого необходимо пройтись по всем переменным в классе самому и сравнить имена. Для поиска адреса по имени переменной нам потребуется само имя и указатель на экземпляр класса скрипта.

Функция будет выглядеть так:

void* GetPropAddress(const char* Name,asIScriptObject* ScriptObject)
{
     for(int i=0;iGetPropertyCount();i++)
         if(!strcmp(ScriptObject->GetPropertyName(i),Name))
             return ScriptObject->GetAddressOfProperty(i);
     return 0;
}

Вот и всё, что мне хотелось бы рассказать, да я не осветил моменты касающиеся JIT и Шаблонов, но это только потому, что ещё не разбирался с этим. По мере изучения буду обновлять статью.

Angel Script Example

Полезные ссылки.



Сайт разработчиков http://www.angelcode.com/
SVN Репозиторий на WIP https://angelscript.svn.sourceforge.net/svnroot/angelscript/trunk
Русский мануал http://13d-labs.com/angelscript_manual/main.html
Мануал на английском  http://www.angelcode.com/angelscript/sdk/docs/manual/index.html
JIT Компилятор https://github.com/BlindMindStudios/AngelScript-JIT-Compiler

#AngelScript, #скрипты

2 ноября 2011 (Обновление: 1 мар 2012)

Комментарии [169]