AngelScript

Введение.
В процессе рассмотрения LUA и Python я выделил для себя, что LUA является достаточно быстрым, но с немного непривычным синтаксисом. Python же обладает очень простым синтаксисом и массой полезных библиотек, но, к сожалению, он оказался довольно медленным, и его довольно тяжело привязывать к С++. И тут на работе мне подсказали использовать AngelScript, мол, он удобный для связки, быстрее LUA и имеет С-подобный синтаксис. Как только я начал его изучать, я понял, что это тот самый скриптовый язык моей мечты.
Превью.
Вот что можно прочитать про этот язык на Википедии:
AngelScript представляет собой движок, в котором приложение может регистрировать функции, свойства и типы, которые могут использоваться в скриптах. Скрипты компилируются в модули. Количество используемых модулей варьрируется в зависимости от нужд. Приложение может также использовать различные интерфейсы для каждого модуля с помощью групп конфигурации. Это особенно полезно, когда приложение работает с несколькими типами скриптов, например, GUI, AI и т.д.
Программа «Hello, world» в простейшем случае выглядит так: 
void main() 
{ 
 print("Hello worldn"); 
} 
Да, синтаксис языка радует с самого начала. Язык поддерживает как методы функционального программирования, так и ООП. С самого начала он подкупает своей простотой регистрации функций, переменных, типов.
Например, регистрация глобальной переменной: 
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 и Шаблонов, но это только потому, что ещё не разбирался с этим. По мере изучения буду обновлять статью.
Источник: http://www.gamedev.ru

                    
                    
                    
                    
                    
                    


