Showing posts with label arrays. Show all posts
Showing posts with label arrays. Show all posts

Wednesday, January 8, 2014

Programming By Example - Adding AngelScript to a Game Part 2

For Part 1: Programming By Example - Adding AngelScript to a Game Part 1
For Part 2: Programming By Example - Adding AngelScript to a Game Part 2
For Part 3: Programming By Example - Adding AngelScript to a Game Part 3

Introduction

This is part 2 of the article Adding AngelScript to an Existing Game. In the first installment, This article series focuses on how to add a scripting language to a game and goes beyond just teaching a scripting language's API. Using the XACTGame example from the Microsoft Direct X SDK, I will detail the entire process. In part 1, I discussed some AngelScript basics and I showed how to add some bindings so the script can communicate with the C++ code. Then I scripted the InitApp() function. This article will build on the concepts from part 1 and in it, I'll code the remaining bindings so the script can call the proper C++ functions. In part 3, I'll write the remaining scripts. AngelScript Concepts Covered
  • Binding a C++ Interface to the script
    • Registering a Scoped Reference Type
    • Registering a POD Vale Type
    • Registering an Array as a Global Property
    • Registering functions with overloads

Adding Scripting to More Functions

As we did in part 1, we need to continue to survey the code to see what the dependencies are. Then we'll be able to figure out what bindings we'll need. Here are the remaining functions that I'll script and their dependencies. void FireAmmo();
  • D3DXVECTOR4
  • D3DXMATRIXA16
  • D3DXMatrixInverse()
  • D3DXVec4Transform()
  • g_Camera.GetViewMatrix()
  • AMMO_STATE
  • PlayAudioCue( g_audioState.iAmmoFire );
void DroidPickNewDirection( int A );
  • D3DXQUATERNION
  • D3DXQuaternionRotationYawPitchRoll
  • D3DXMatrixRotationQuaternion
  • DROID_STATE
  • AI_STATE
  • GAME_STATE - DROID_STATE DroidQ[MAX_DROID];
void DroidChooseNewTask( int A );
  • rand()
  • GAME_STATE - DROID_STATE DroidQ[MAX_DROID];
  • D3DXMatrixRotationQuaternion
  • D3DXMATRIXA16
  • Play3DAudioCue( g_audioState.iDroidScan, &g_GameState.DroidQ[A].vPosition );
void HandleDroidAI( float fElapsedTime );
  • GAME_STATE - DROID_STATE DroidQ[MAX_DROID];
  • D3DXQuaternionSlerp
  • D3DXMATRIXA16
  • D3DXMatrixRotationQuaternion
  • D3DXQUATERNION
void HandleAmmoAI( float fElapsedTime ); CheckForInterAmmoCollision() CheckForAmmoToWallCollision( A ); CheckForAmmoToDroidCollision( A );
  • GAME_STATE
  • DROID_STATE
  • AMMO_STATE
While, I'd prefer to not write any more object types, after looking at that list, it seems like I'll need to make these new types
  • D3DXVECTOR4
  • D3DXMATRIXA16
  • D3DXQUATERNION
  • DROID_STATE
  • AMMO_STATE

Registering a Scoped Reference Type for D3DXMATRIXA16

Adding the D3DXVECTOR4 and D3DXQUATERNION won't be hard considering they're almost exactly like D3DXVECTOR3. All it will take is some copying and pasting and some searching and replacing. The XACTGame sample application uses D3DXMATRIXA16 this class inherits from D3DXMATRIX and overloads the new and delete operators to keep things 16 bit aligned. Because of these special memory requirements, we can't create the object as a value type, we'll need to create it as a reference type. A reference type is an object that resides in dynamic memory; however, to use reference types, we'd have to add reference counting to the object which is not something we want to add. Thankfully AngelScript provides a special type of reference type just for this situation called a scoped reference type. To create an scoped reference type we'll need a function call like this:
r = engine->RegisterObjectType("D3DXMATRIXA16", 0, asOBJ_REF | asOBJ_SCOPED);

With reference types, we don't register constructors. Instead we register factories. A factory should create the object using dynamic memory. We also need a release function that AngelScript will use to delete the object. This is what we'll need for the D3DXMATRIXA16 type.

static D3DXMATRIXA16 *D3DXMATRIXA16_Factory()
{
  return new D3DXMATRIXA16;
}

static D3DXMATRIXA16 *D3DXMATRIXA16_FactoryCopy(const D3DXMATRIXA16 &other)
{
  return new D3DXMATRIXA16(other);
}

static D3DXMATRIXA16 *D3DXMATRIXA16_FactoryFromfloats(float _11, float _12, float _13, float _14,
                                                      float _21, float _22, float _23, float _24,
                                                      float _31, float _32, float _33, float _34,
                                                      float _41, float _42, float _43, float _44)
{
  return new D3DXMATRIXA16(_11, _12, _13, _14,
                           _21, _22, _23, _24,
                           _31, _32, _33, _34,
                           _41, _42, _43, _44);
}

static void D3DXMATRIXA16_Release(D3DXMATRIXA16 *s)
{
  if( s ) delete s;
}

I'll register the operators the same way I did or D3DXVECTOR3 and D3DXVECTOR4 with one exception. When using scoped reference types, "any function that either takes or returns the type by value in C++ must be wrapped in order to permit AngelScript to manage the life time of the values."(from the AngelScript manual) This means that will need to changed the binary operators so they return a pointer in C++, and a handle in AngelScript.

static D3DXMATRIXA16 *Matrix_opMul /* operator * */ ( const D3DXMATRIXA16 &lhs, const D3DXMATRIXA16 &rhs)
{
 // (From AngelScript Manual) any function that either takes or returns the type by value in C++ must
 // be wrapped in order to permit AngelScript to manage the life time of the values
 return new D3DXMATRIXA16(lhs * rhs);
}

Here's the function for registering the D3DXMATRIXA16 type. Redundant parts of the code have been omitted.

int RegisterD3DXMATRIXA16(asIScriptEngine *engine)
{
 int r;

 // Register the string type
 r = engine->RegisterObjectType("D3DXMATRIXA16", 0, asOBJ_REF | asOBJ_SCOPED);
 if(r < 0) return r;

 // with reference types we register factores and not constructors
 r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_FACTORY, "D3DXMATRIXA16 @f()", asFUNCTION(D3DXMATRIXA16_Factory), asCALL_CDECL);
 r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_FACTORY, "D3DXMATRIXA16 @f(const D3DXMATRIXA16 &in)", asFUNCTION(D3DXMATRIXA16_FactoryCopy), asCALL_CDECL);
 r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_FACTORY, 
  "D3DXMATRIXA16 @f(float, float, float, float, float, float, float, float, float, float, float, float, float, float, float, float)",
  asFUNCTION(D3DXMATRIXA16_FactoryFromfloats), asCALL_CDECL);
 r = engine->RegisterObjectBehaviour("D3DXMATRIXA16", asBEHAVE_RELEASE, "void f()", asFUNCTION(D3DXMATRIXA16_Release), asCALL_CDECL_OBJLAST);

 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opMulAssign( const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opMulAssign), asCALL_CDECL_OBJLAST);
 if(r < 0) return r;
 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opAddAssign( const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opAddAssign), asCALL_CDECL_OBJLAST);
 if(r < 0) return r;
 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opSubAssign( const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opSubAssign), asCALL_CDECL_OBJLAST);
 if(r < 0) return r;
 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opMulAssign( float)", asFUNCTION(Matrix_opMulAssignF), asCALL_CDECL_OBJLAST);
 if(r < 0) return r;
 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opDivAssign( float)", asFUNCTION(Matrix_opDivAssign), asCALL_CDECL_OBJLAST);
 if(r < 0) return r;

 // instead of returning a value, the operator must return a handle because this is a scoped reference type
 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 @Matrix_opMul(const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opMul), asCALL_CDECL_OBJFIRST);
 if(r < 0) return r;
 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 @Matrix_opAdd(const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opAdd), asCALL_CDECL_OBJFIRST);
 if(r < 0) return r;
 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 @Matrix_opSub(const D3DXMATRIXA16 &in)", asFUNCTION(Matrix_opSub), asCALL_CDECL_OBJFIRST);
 if(r < 0) return r;

 // some code omitted
 ...

 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "D3DXMATRIXA16 &opAssign(const D3DXMATRIXA16 &in)", asMETHODPR(D3DXMATRIXA16, operator =, (const D3DXMATRIXA16&), D3DXMATRIXA16&), asCALL_THISCALL);
 if(r < 0) return r;

 r = engine->RegisterObjectMethod("D3DXMATRIXA16", "bool opEquals(const D3DXMATRIXA16 &in) const", asFUNCTION(MatricesEqual), asCALL_CDECL_OBJFIRST);
 if(r < 0) return r;

 // register the properties
 r = engine->RegisterObjectProperty("D3DXMATRIXA16", "float _11", asOFFSET(D3DXMATRIXA16,_11));
 if(r < 0) return r;
 r = engine->RegisterObjectProperty("D3DXMATRIXA16", "float _12", asOFFSET(D3DXMATRIXA16,_12));
 if(r < 0) return r;
 r = engine->RegisterObjectProperty("D3DXMATRIXA16", "float _13", asOFFSET(D3DXMATRIXA16,_13));

 // some code omitted
 ...

 return r;
}

There's one more thing I want to mention about this type. The D3DXMATRIXA16 type is derived from D3DMATRIX which has a union so the data can be accessed through an array or through individual floats. When I add this to AngelScript, I won't add support for the array.

typedef struct _D3DMATRIX {
    union {
        struct {
            float        _11, _12, _13, _14;
            float        _21, _22, _23, _24;
            float        _31, _32, _33, _34;
            float        _41, _42, _43, _44;

        };
        float m[4][4];
    };
} D3DMATRIX;

Registering a POD Vale Type

The DROID_STATE struct is a little special in that it doesn't have any constructors, destructors, or pointers. Also, copying and assignment can be done using a bit-to-bit copy. Because of this, we can register it with the flag asOBJ_POD. When using this flag, we don't have to supply a constructor, destructor, or assignment operator.

int RegisterDROID_STATE(asIScriptEngine *scriptengine)
{
 int r;

 // register AI_STATE enum
 r = scriptengine->RegisterEnum("AI_STATE");
 if(r < 0) return r;

 r = scriptengine->RegisterEnumValue("AI_STATE", "AI_TURNING", (int)AI_TURNING);
 if(r < 0) return r;

 r = scriptengine->RegisterEnumValue("AI_STATE", "AI_MOVING", (int)AI_MOVING);
 if(r < 0) return r;

 r = scriptengine->RegisterEnumValue("AI_STATE", "AI_STOPPED", (int)AI_STOPPED);
 if(r < 0) return r;

 // Register DROID_STATE as POD Value type
 // Register a primitive type, that doesn't need any special management of the content
 r = scriptengine->RegisterObjectType("DROID_STATE", sizeof(DROID_STATE), asOBJ_VALUE | asOBJ_APP_CLASS | asOBJ_POD);

 r = scriptengine->RegisterObjectProperty("DROID_STATE", "bool bActive", asOFFSET(DROID_STATE,bActive));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXVECTOR3 vPosition", asOFFSET(DROID_STATE,vPosition));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXVECTOR3 vVelocity", asOFFSET(DROID_STATE,vVelocity));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXVECTOR3 vNudgeVelocity", asOFFSET(DROID_STATE,vNudgeVelocity));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "AI_STATE aiState", asOFFSET(DROID_STATE,aiState));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fTargetRotation", asOFFSET(DROID_STATE,fTargetRotation));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXQUATERNION qTarget", asOFFSET(DROID_STATE,qTarget));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXQUATERNION qStart", asOFFSET(DROID_STATE,qStart));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXQUATERNION qCurrent", asOFFSET(DROID_STATE,qCurrent));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fRotInterp", asOFFSET(DROID_STATE,fRotInterp));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fTaskTimer", asOFFSET(DROID_STATE,fTaskTimer));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "int nHitPoints", asOFFSET(DROID_STATE,nHitPoints));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fDeathAnimation", asOFFSET(DROID_STATE,fDeathAnimation));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "float fAlpha", asOFFSET(DROID_STATE,fAlpha));
 if(r < 0) return r;
 r = scriptengine->RegisterObjectProperty("DROID_STATE", "D3DXCOLOR Specular", asOFFSET(DROID_STATE,Specular));

 return r;
}

I'll handle the AMMO_STATE structure the same way. To see how this is coded, see "scriptdroidammostates.cpp" in the source code for this article.

Arrays - Getting The Droid State

Now there's one big thing to consider now.XACTGame sample apps's DROID_STATE and AMMO_STATE information are stored in arrays inside of the GAME_STATE struct. C/C++ static arrays cannot be registered directly with AngelScript because AngelScript can't garuntee the lifetime of the array. Applications that want to use arrays need to register an array type. The AngelScript SDK comes with an add-on that can be used to register arrays, the class CScriptArray. This is a very useful class, but because it's for generic arrays, elements can't be accessed without converting back and forth between the type and void pointers. This means I can't just plug it into my C++ code. To get around this, I've written a STL-style wrapper for the CScriptArray class that behaves much like std::vector. Now all I have to do is change the array declaration to this:

CScriptArraySTL<AMMO_STATE> AmmoQ;
CScriptArraySTL<DROID_STATE>  DroidQ;

You can download the CScriptArraySTL class, by checking out the following blog entry: Blogspot post: C++ STL-Style Array That's Compatible with AngelScript I'll also add a Reset() method to the GAME_STATE class and register it with AngelScript so the script will be able to reset the state. The Reset() method will also be important because the XACTGame sample previously just used ZeroMemory(),a macro that calls memset(), to clear the data. This will no longer work because of the CScriptArraySTL array. I've also added clear to the droid state

// Reset in GAME_STATE
void Reset()
{
 // Reset here so we don't have to use ZeroMemory which messes up the CScriptArraySTL<> arrays
 gameMode  = GAME_RUNNING;

 nAmmoCount  = 0;
 fAmmoColorLerp = 0.0f;
 BlendToColor = (DWORD)0;
 BlendFromColor = (DWORD)0;

 nDroidCount = 0;
 nMaxDroids = 0;

 bDroidCreate   = false;
 bMassDroidKill   = false;
 fDroidCreateCountdown = 0.0f;

 bDroidMove  = false;
 bAutoAddDroids = false;

 // clear the static arrays
 ZeroMemory(gamePad, sizeof(DXUT_GAMEPAD) * DXUT_MAX_CONTROLLERS);
    
 // reset Ammo array
 AmmoQ.resize(MAX_AMMO);
 for(auto it = AmmoQ.begin(); it < AmmoQ.end(); it++)
 {
  (*it).clear();
 }

 // reset the Droid array
 DroidQ.resize(MAX_DROID);
 for(auto it = DroidQ.begin(); it < DroidQ.end(); it++)
 {
  (*it).clear();
 }
}

// changes to RegisterGameStateInterface
...
result = scriptengine->RegisterGlobalFunction("void Reset()", asMETHOD(GAME_STATE, Reset), asCALL_THISCALL_ASGLOBAL, &g_GameState);
if(result < 0) return result;

// initialize the droid state and ammo state arrays
result = g_GameState.DroidQ.InitArray(scriptengine, "array");
if(result < 0) return result;

result = g_GameState.AmmoQ.InitArray(scriptengine, "array");
if(result < 0) return result;
 
// register the droid state and ammo state arrays
result = scriptengine->RegisterGlobalProperty("array DroidQ", g_GameState.DroidQ.GetRef());
if(result < 0) return result;

result = scriptengine->RegisterGlobalProperty("array AmmoQ", g_GameState.AmmoQ.GetRef());
if(result < 0) return result;
...

Registering the remaining math functions

While keeping each type in it's own source file, I've decided to clean up the DirectX math bindings by making one header that and function that will add all of the types and functions. I had to make wrappers for most of the functions because the Direct X math functions use pointers but I want to use references with my AngelScript value type objects, and the wrapped functions will just return void and not a pointer to the out parameter. Also, even though it's not a Direct X math function, I've also decided to add my rand binding here as well with one change. Instead of just using the normal rand(), I'm going to change it so it takes the min and max values as a parameter.

static int rand(int min, int max)
{
 int diff = max - min;
 return (rand() % diff) + min;
}

Now when I register the function, I'll have to do it a little differently because I just overloaded rand() so it now has two versions with different params. So AngelScript knows which version to use, we need to register it like this: result = scriptengine->RegisterGlobalFunction("int rand(int, int)", asFUNCTIONPR(rand, (int, int), int), asCALL_CDECL); The asFUNCTIONPR macro takes 3 parameters--the function name, the parameter list, and the return type. This function registers all of the needed math functions.

int RegisterD3DXMathFunctions(asIScriptEngine *scriptengine)
{
 int result;

 // register our types first
 result = RegisterD3DXVECTOR3(scriptengine);
 if(result < 0) return result;
 result = RegisterD3DXVECTOR4(scriptengine);
 if(result < 0) return result;
 result = RegisterD3DXQUATERNION(scriptengine);
 if(result < 0) return result;
 result = RegisterD3DXMATRIXA16(scriptengine);
 if(result < 0) return result;

 // register the global functions
 result = scriptengine->RegisterGlobalFunction("void D3DXMatrixInverse(D3DXMATRIXA16 &out, float &out, const D3DXMATRIXA16 &in)", asFUNCTION(MatrixInverse), asCALL_CDECL);
 if(result < 0) return result;
 result = scriptengine->RegisterGlobalFunction("void D3DXVec4Transform(D3DXVECTOR4 &out, const D3DXVECTOR4 &in, const D3DXMATRIXA16 &in)", asFUNCTION(Vec4Transform), asCALL_CDECL);
 if(result < 0) return result;
 result = scriptengine->RegisterGlobalFunction("void D3DXQuaternionRotationYawPitchRoll(D3DXQUATERNION &out, float, float, float)", asFUNCTION(QuaternionRotationYawPitchRoll), asCALL_CDECL);
 if(result < 0) return result;
 result = scriptengine->RegisterGlobalFunction("void D3DXMatrixRotationQuaternion(D3DXMATRIXA16 &out, const D3DXQUATERNION &in)", asFUNCTION(MatrixRotationQuaternion), asCALL_CDECL);
 if(result < 0) return result;
 result = scriptengine->RegisterGlobalFunction("void D3DXQuaternionSlerp(D3DXQUATERNION &out, const D3DXQUATERNION &in, const D3DXQUATERNION &in, float t)", asFUNCTION(QuaternionSlerp), asCALL_CDECL);
 if(result < 0) return result;

 // add rand() here
 result = scriptengine->RegisterGlobalFunction("int rand(int, int)", asFUNCTIONPR(rand, (int, int), int), asCALL_CDECL);

 return result;
}

Handling Audio

I won't script any of the audio functions, but some of the other functions that I will script need access to some of the audio functionality. I'll need to give access to the following functions:

 HRESULT PlayAudioCue( XACTINDEX iCueIndex );
 HRESULT Play3DAudioCue( XACTINDEX iCueIndex, D3DXVECTOR3* pvPosition );
 void SetNumDroidsForAudio( int nDroidCount );

Adding these should be easy enough, but I'll need to wrap PlayAudioCue() and Play3DAudioCue() because they take the cue index as a parameter. I don't want to provide access to those variables so instead I'll make an enum that's a list of all of the cue indices and the wrapped functions will take the enum as a parameter.

enum AudioCues
{
    Cue_iAmmoBounce = 0,
    Cue_iAmmoFire,
    Cue_iDroidDestroyed,
    Cue_iDroidScan,
    Cue_iBackgroundMusic,
    Cue_iRoomRumble
};

XACTINDEX GetCueIndex(AudioCues cue)
{
 switch(cue)
 {
  case Cue_iAmmoBounce:
   return g_audioState.iAmmoBounce;
  case Cue_iAmmoFire:
   return g_audioState.iAmmoFire;
  ... // some code omitted
 }

 return 0;
}

void PlayAudioCueWrapper( AudioCues cue )
{
 PlayAudioCue(GetCueIndex(cue));
}

void Play3DAudioCueWrapper( AudioCues cue, const D3DXVECTOR3 &vPosition )
{
 Play3DAudioCue(GetCueIndex(cue), (D3DXVECTOR3 *)&vPosition);
}

int RegisterAudioInterface(asIScriptEngine *scriptengine)
{
 int result;

 // set the namespace
 result = scriptengine->SetDefaultNamespace("AUDIO"); 
 if(result < 0) return result;

 // first register our enum
 result = scriptengine->RegisterEnum("AudioCues");
 if(result < 0) return result;

 result = scriptengine->RegisterEnumValue("AudioCues", "Cue_iAmmoBounce", (int)Cue_iAmmoBounce);
 if(result < 0) return result;

 result = scriptengine->RegisterEnumValue("AudioCues", "Cue_iAmmoFire", (int)Cue_iAmmoFire);
 if(result < 0) return result;

 ... // some code omitted

 result = scriptengine->RegisterGlobalFunction("void PlayAudioCue(AudioCues)", asFUNCTION(PlayAudioCueWrapper), asCALL_CDECL);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalFunction("void Play3DAudioCue(AudioCues, const D3DXVECTOR3 &in)", asFUNCTION(Play3DAudioCueWrapper), asCALL_CDECL);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalFunction("void SetNumDroidsForAudio(int)", asFUNCTION(SetNumDroidsForAudio), asCALL_CDECL);
 if(result < 0) return result;

 // reset back to global namespace
 result = scriptengine->SetDefaultNamespace(""); 

 return result;
}

Conclusion

It's taken some time, but now I have finished all of the needed bindings between AngelScript and C++ that I'll need. Now that all of the bindings are complete, I need to work on adding the scripts. AngelScript is a very powerful scripting language and I hope to show some of its features in part 3 of this article. In part 3 of this article, I'll write all of the scripts, I'll also show how to restrict bindings to some scripts.

Coding style in this article

Listed in the best practices for AngelScript is to always check the return value for every function. In most of the AngelCode examples and in the manual an assert is used to check for errors. I don't use the assert, instead I've been using [tt]if(result < 0) return result;[/tt]. This can easily be replaced by [tt]assert(r >= 0);[/tt] as is used in the AngelScript documentation. Also, my goal with this project was to change the XACTGame sample as little as possible. The XACTGame sample was designed to show certain techniques such as adding graphics and audio, and it uses a simple framework.

Getting AngelScript

You can download the latest version of the AngelScript SDK from the AngelCode website. http://www.angelcode.com/ You'll find an excellent manual that explains the API in detail.

Note on Microsoft Source Code

Because Microsoft code was used in this program, I want to state some terms from the Direct X SDK EULA. The XACTGame sample was created by Microsoft and Microsoft owns the copyright. Changes made by Dominque Douglas have been clearly marked. Use of the source code provided does not change the license agreement for using Microsoft code. Microsoft code cannot be modified to work on non-Microsoft operating systems and Microsoft is not responsible for any claims related to the distribution of this program. Refer to the license agreement in the Direct X SDK for details.

Downloading This Project

The source code can be downloaded here: XACTGameAngelScript-Part2.zip Download note: Because of the size, this does not include the media files needed by the project such as the audio files and graphics files. You'll need to copy the "media" folder from the XACTGame sample in the Direct X SDK. You may need to alter the project's include and library directories to match your system. For simplicity, the AngelScript add-ons that were used in this project have been included. The project is a Visual Studio 2010 solution.

Saturday, January 4, 2014

C++ STL-Style Array That's Compatible with AngelScript

I've been working on a new class that I think will be useful to programmers working with AngelScript. AngelScript is nice because its data types are so similar to C++ that it's easy to bind it and share data. There's one thing that doesn't have an easy one-to-one AngelScript counterpart and that's static arrays. I can understand the reasoning behind this as the script has no mechanism that it can use to garuntee the life time of the array. AngelScript does have a nice add-on for arrays that's generic and can add arrays for any type, but I wanted something easier. I decided to make a template class that would wrap the CScriptArray add-on and give it an interface like std::vector including stl-style iterators.

Using the class.
The CScriptArraySTL class was designed so that the array can be created in C++ and then shared with AngelScript as a registered property, or as the return value of a registered functon. It is a template class that takes two template parameters.

template <class T, class TArrayClass = CScriptArray>
class CScriptArraySTL

The first parameter is the type, and the second parameter is the internal array class that has been registered with AngelScript. The default array class is the CScriptArray add-on that is included in the AngelScript SDK. To use this it, you must also include the CScriptArray add-on in your project and register it with AngelScript using the RegisterScriptArray() function. The internal array class is given as a template argument to allow the programmer to be able to use there own array implementation.

Declaring a variable:
// uses CScriptArray addon internally
// for this to work, std::string should be registered with AngelScript.
// This can be done using the ScriptStdString add-on.
CScriptArraySTL <std::string> string_array;

// uses a user-defined array type called CScriptArrayInt
// this type should be a specialized version of CScriptArray that only handles integers
CScriptArraySTL <int, CScriptArrayInt> int_array;


InitArray()
You can create variables using CScriptArraySTL anytime, but you can't use it until after the CScriptArraySTL object has been initialized using the InitArray() method. Here's the declaration for that function:
// Initializes the array so it can be directly accessed using AngelScript
// This must be called before the array can be used
int InitArray(asIScriptEngine *engine, char *declaration, size_type init_length = 0);

asIScriptEngine *engine
The first parameter is a pointer to the script engine. To maximize compatibility with AngelScript, this class uses creates it's internal data using the types that have been registered with AngelScript.

char *declaration
The second parameter is how you would write the type for this array in AngelScript. This allows the class to match its type with AngelScript. For example, if the class holds integers, it should be written "array<int>".

size_t init_length
This is the initial size of the array.

GetRef()
This function returns a pointer to the internal array class and can be registered with AngelScript as a property or returned from a function that has been registered with AngelScript.

Release()
This will release a reference to the array. After this method has been called, the CScriptArraySTL class will no longer be able to access the array data; however, as arrays are reference types in AngelScript, the data may still exist inside AngelScript until the reference count is zero. This method should be called before the script engine is released.

Sample Code

// Create the script engine
asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

// Register needed add-ons
RegisterStdString(engine);
RegisterScriptArray(engine, true);

// setup our string array
CScriptArraySTL <std::string> string_array;
string_array.InitArray(engine, "array<string>");

// register our array as a global variable in the script
r = engine->RegisterGlobalProperty("array<string> string_array", string_array.GetRef()); assert( r >= 0 );

// do other things and load and run scripts
...

// Release the array
string_array.Release();

// Release the script engine
engine->Release();

Get the Source.
The source code and a example project can be found on GitHub. If you copy the "scriptarraystl" folder to the AngelScript SDK add_on folder, the project directory paths should work. Only a Visual Studio 2010 solution is being provided, but you should be able to run it using other compilers. To use this class in your project, all you need to do is include "ScriptArraySTL.h" The class has been implemented entirely in one header file.

GitHub link: https://github.com/squaredprogramming/scriptarraystl

____________________________________________________________________

Here's the complete list of the methods that I've implemented for the class.
// Constructors, Destructors and AngelScript initialization -------------------------------
CScriptArraySTL(void);
~CScriptArraySTL(void);

// Initializes the array so it can be directly accessed using AngelScript
// This must be called before the array can be used
int InitArray(asIScriptEngine *engine, char *declaration, size_type init_length = 0);

// returns a pointer to an array class that can be used in an AS script
TArrayClass *GetRef();

// Releases a reference to the array. After this is called, this class can
// no longer access the array data, but the data may still exist inside
// AngelScript until the refernce count is 0.
void Release();

// Capacity ----------------------------------------------------------------------------------

// returns the number of elements in the array
size_type size() const;
 
// resizes the array, adding unitialized data if the new size is bigger than the old size or
// deleting data if the new size is smaller
void resize(size_type n);

// returns true if the array is empty
bool empty() const;

// grows the buffer capacity
void reserve(size_type n);


// iterators ----------------------------------------------------------------------------
// returns an iterator to the begining of the array
iterator begin();

// returns an iterator to the end of the array
iterator end();

// returns a constant iterator to the begining of the array
const_iterator cbegin() const;

// returns a constant iterator to the end of the array
const_iterator cend() const;

// returns a constant iterator to the begining of the array
iterator begin() const;

// returns a constant iterator to the end of the array
iterator end() const;

// returns a reverse iterator to the begining of the array
reverse_iterator rbegin();

// returns a reverse iterator to the end of the array
reverse_iterator rend();

// returns a constant reverse iterator to the begining of the array
const_reverse_iterator crbegin() const;

// returns a constant reverse iterator to the end of the array
const_reverse_iterator crend() const;

// returns a constant reverse iterator to the begining of the array
const_reverse_iterator rbegin() const;

// returns a constant reverse iterator to the end of the array
const_reverse_iterator rend() const;

// Element Access -----------------------------------------------------------------------

// returns a reference to an element in the array. This will not throw an out-of-range exception.
// undefined behavior if out of range.
reference operator[](size_type index);

// returns a const reference to an element in the array. This will not throw an out-of-range exception.
// undefined behavior if out of range.
const_reference operator[](size_type index) const;

// returns a reference to an element in the array. This will throw an out-of-range exception.
reference at(size_type index);

// returns a constant reference to an element in the array. This will throw an out-of-range exception.
const_reference at(size_type) const;

// returns a reference to the first element
// undefined if empty
reference front();

// returns a constant reference to the first element
// undefined if empty
const_reference front() const;

// returns a reference to the last element
// undefined if empty
reference back();

// returns a constant reference to the last element
// undefined if empty
const_reference back() const;

// Modifiers ------------------------------------------------------------------------------------

// adds a value to the end of the array
void push_back (const value_type& val);

// removes the last element
void pop_back();

// assigns new data to the array using iterators.
template <class inputiterator=""&rt;
void assign (InputIterator first, InputIterator last);

// fills the array 
void assign (size_type n, const value_type& val);

// clears the contents of the array
void clear()