Wednesday, December 25, 2013

Programming By Example - Adding AngelScript to a Game Part 1

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

Using interpreted scripting languages in games instead of compiling to native code has become very common in games. Using scripting languages allows developers to make functional changes to their programs without having to compile and link the program and they allow developers to be able to separate the game logic from the game engine. These are good things, but how can a developer new to scripting successfully use a scripting language in his or her project? This article will explain how to add AngelScript to a game by taking the XACTGame example program given in the Direct X SDK.

AngelScript is a scripting language with a syntax that's very similar to C++. It is a strictly typed language with many of the types being the same as in C++. This article will explain some concepts of how to use AngelScript, but a basic knowledge of the language will be needed to understand all of the concepts. See this page in the AngelScript documentation for details. Before you begin with this tutorial, make sure you have installed the Direct X SDK. I'll be using the June 2010 update. Also, you'll need to go to www.AngelCode.com to get the latest version of the AngelScript SDK and follow the instructions on how to set up AngelScript with your compiler. Here are some details on my current setup. I'm using Angel Script 2.28 and I'm using Microsoft Visual Studio 2010 Professional.

When adding scripting to an existing program, taking an accurate survey of the code is very important. Other than the Direct X files, the XACTGame example is made up of 5 source files -- audio.cpp, audio.h, game.cpp, game.h, and main.cpp. For now, I'll ignore the audio files for now. I could script some of the capabilities, but I'd rather not make this article too long. When examining the files, you'll notice that not all of the functions have forward declarations, but we'll need to know all of the functions so we can decide what to script.

Here's the complete list:

void InitApp();
bool CALLBACK IsDeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat,
                                  D3DFORMAT BackBufferFormat, bool bWindowed, void* pUserContext );
static int __cdecl SortAspectRatios( const void* arg1, const void* arg2 );
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext );
HRESULT SplitIntoSeperateTriangles( IDirect3DDevice9* pd3dDevice, ID3DXMesh* pInMesh, CDXUTXFileMesh* pOutMesh );
HRESULT CALLBACK OnCreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
                                 void* pUserContext );
void ComputeMeshScaling( CDXUTXFileMesh& Mesh, D3DXMATRIX* pmScalingCenter, float fNewRadius );
void ComputeMeshScalingBox( CDXUTXFileMesh& Mesh, D3DXMATRIX* pmScalingCenter, D3DXVECTOR3 vNewMin,
                            D3DXVECTOR3 vNewMax );
void SetEffectTechnique();
HRESULT CALLBACK OnResetDevice( IDirect3DDevice9* pd3dDevice,
                                const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext );
void FireAmmo();
float GetDistFromWall( D3DXVECTOR3 P1, D3DXVECTOR3 P2, D3DXVECTOR3 P3, D3DXVECTOR3 N );
void DroidPickNewDirection( int A );
void DroidChooseNewTask( int A );
void HandleDroidAI( float fElapsedTime );
void CheckForAmmoToDroidCollision( int A );
void CheckForInterAmmoCollision( float fElapsedTime );
void CheckForAmmoToWallCollision( int A );
void HandleAmmoAI( float fElapsedTime );
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext );
void CreateAmmo( int nIndex, D3DXVECTOR4 Pos, D3DXVECTOR4 Vel );
void RenderAmmo( int A, D3DXMATRIXA16& mView, D3DXMATRIXA16& mProj );
void CreateDroid();
void RenderDroid( IDirect3DDevice9* pd3dDevice, int A, D3DXMATRIXA16& mView, D3DXMATRIXA16& mProj, bool bExplode );
void CALLBACK OnFrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext );
void RenderText();
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool* pbNoFurtherProcessing,
                          void* pUserContext );
void UpdateAspectRatioList( DXUTDeviceSettings* pDS );
void UpdateResolutionList( DXUTDeviceSettings* pDS );
void CALLBACK OnGUIEvent( UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext );
void ToggleMenu();
void CALLBACK KeyboardProc( UINT nChar, bool bKeyDown, bool bAltDown, void* pUserContext );
void CALLBACK OnLostDevice( void* pUserContext );
void CALLBACK OnDestroyDevice( void* pUserContext );

Of all of these functions, we now to choose suitable candidates for scripting. Again to limit the size of this article, I'll only script functions related to the game's logic. That leaves the following functions that will either be partially scripted or completely scripted.

void InitApp(); 
void FireAmmo();
void DroidPickNewDirection( int A );
void DroidChooseNewTask( int A );
void HandleDroidAI( float fElapsedTime );
void HandleAmmoAI( float fElapsedTime );

While AngelScript is a C++-style language, we can't just write the script code and be done. Our C++ code will need to be able to communicate with AngelScript, and our scripts need to be informed of the data structures and classes that we'll use. Again, an accurate examination of the base code will be needed. Let's determine the dependencies that each of the above functions have. Then we'll be able to define how our script bindings.

void InitApp();

  • RENDER_STATE - This is a structure defined in game.h that details everything that needs to be rendered. Only parts of this structure are needed by InitApp()
  • CDXUTDialog - This class is defined in DXUTgui.h and it defines a GUI dialog. The following  methods will be needed:


HRESULT AddStatic( int ID, LPCWSTR strText, int x, int y, int width, int height, bool bIsDefault=false,
      CDXUTStatic** ppCreated=NULL );
HRESULT AddButton( int ID, LPCWSTR strText, int x, int y, int width, int height, UINT nHotkey=0,
      bool bIsDefault=false, CDXUTButton** ppCreated=NULL );
HRESULT AddCheckBox( int ID, LPCWSTR strText, int x, int y, int width, int height, bool bChecked=false,
      UINT nHotkey=0, bool bIsDefault=false, CDXUTCheckBox** ppCreated=NULL );
HRESULT AddRadioButton( int ID, UINT nButtonGroup, LPCWSTR strText, int x, int y, int width,
      int height, bool bChecked=false, UINT nHotkey=0, bool bIsDefault=false,
      CDXUTRadioButton** ppCreated=NULL );
HRESULT AddComboBox( int ID, int x, int y, int width, int height, UINT nHotKey=0, bool bIsDefault=
      false, CDXUTComboBox** ppCreated=NULL );
HRESULT AddSlider( int ID, int x, int y, int width, int height, int min=0, int max=100, int value=50,
      bool bIsDefault=false, CDXUTSlider** ppCreated=NULL );

  • GAME_STATE g_GameState; - The global game state. GAME_STATE is defined in game.h

int nAmmoCount;
float fAmmoColorLerp;
D3DXCOLOR BlendFromColor;
bool bDroidMove;
bool bAutoAddDroids;
GAME_MODE gameMode;

  • D3DXCOLOR - defined in d3dx9math.h
  • GAME_MODE - enum of game modes. Defined in game.h


Many things need to be done just to get all the bindings for InitApp(). So before proceeding to the other functions, let's first build a version of XACTGame that uses AngelScript for the InitApp() function.

First, I'll start by adding two new files to the project as_scripting.h and as_scripting.cpp. The XACTGame sample application does everything in free functions so for simplicity, I'll continue to use that style.

We'll need to add some includes to the as_scripting.h file. angelscript.h is the file we need for all of the basic angelscript classes. scriptbuilder.h is in the add_on directory and it's a extra class that will help us load our scripts.
// Include the definitions of the script library and the add-ons we'll use.
// The project settings may need to be configured to let the compiler where
// to find these headers. Don't forget to add the source modules for the
// add-ons to your project as well so that they will be compiled into the 
// application.
#include 
#include 

#include "game.h"

Initially, I was going to use globals for the sake of keeping the same style as the XACTGame sample, but I've found a much better approach. To do this, I'll provide a structure called ScriptContextData, that I'll pass to the game functions and DXUT callbacks that need it. It would be easier to just use globals, but I want to show this approach because AngelScript allows multiple modules and script context to be used. A module is a compiled(compiled into VM bytecode) script and a context is an instance of the virtual machine. Complex games will probably have multiple modules and may have multiple context.

For now, I'll keep the structure simple.

enum ScriptFunctionIDs
{
 Function_InitApp = 0
};

const unsigned int max_script_functions = 1;

struct ScriptContextData
{
 asIScriptContext *ctx;
 asIScriptFunction *script_functions[max_script_functions];

 void ExecuteFunction(ScriptFunctionIDs func_id);
};

The structure keeps the context (the virtual machine) and an array of the script functions that we
can call from C++. To simplify things, I'm also adding a function that will run the function and
check for exceptions.

And for now I'll write these two functions:

int StartScriptingSystem(asIScriptEngine *&scriptengine, CScriptBuilder &scriptbuilder, ScriptContextData &contextdata)
{
 int result;

 // Create the script engine
 scriptengine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

 // Set the message callback to receive information on errors in human readable form.
 result = scriptengine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);
 if(result < 0) return result; // an error has occurred

 result = RegisterGameInterface(scriptengine);
 if(result < 0) return result; // an error has occurred

 ...
}

void ShutdownScriptingSystem(asIScriptEngine *&scriptengine, asIScriptContext *&ctx)
{
 // Why check to see if this is NULL? This function will be called at the end of the program
 // as a way to clean it up even if an error has occurred. If an error occurs during initialization
 // one or both of these variables may be null.
 if(ctx)
 {
  ctx->Release();
  ctx = NULL; // i don't like leaving old pointers that don't point to valid data
 }

 if(scriptengine)
 {
  scriptengine->Release();
  scriptengine = NULL; // i don't like leaving old pointers that don't point to valid data
 }
}

The RegisterGameInterface() function performs all of the bindings between the script engine and the C++ code. Let's start with the easiest binding, the GAME_MODE enum.
int RegisterEnumGAME_MODE(asIScriptEngine *scriptengine)
{
 int result;

 result = scriptengine->RegisterEnum("GAME_MODE");
 if(result < 0) return result;

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

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

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

    result = scriptengine->RegisterEnumValue("GAME_MODE", "GAME_VIDEO_MENU", (int)GAME_VIDEO_MENU);

 return result;
}

Adding enumerations are simple. First use the RegisterEnum() function to register the type. Then use the RegisterEnumValue() to add each value. Sorry, there's no shorter way, but writing the code is very straight forward.

Next, I'll add the D3DXVECTOR3 type. I'll register is as a value type; however, to not clutter up the as_scripting.cpp and angelscript.h files, I'll create separate files for this type. I'll do the same with D3DXCOLOR. The files are a little long so please check the included source files for details.

Next, since the render state is a global variable in XACT, I'm going to simplify my life a little and not create bindings for the CDXUTDialog. Instead, I'm going to create a new enum XACTGAMEDIALOG, and I'll make and register some free functions with the script engine.

enum XACTGAMEDIALOG
{
 IDMainMenuDlg,
 IDVideoMenuDlg,
 IDAudioMenuDlg
};

void AddStaticToDialog(XACTGAMEDIALOG dialogID, int ID, const std::string & strText, int x, int y, int width,
 int height, bool bIsDefault);
void AddButtonToDialog(XACTGAMEDIALOG dialogID, int ID, const std::string & strText, int x, int y, int width,
 int height, UINT nHotkey, bool bIsDefault);
void AddCheckBoxToDialog(XACTGAMEDIALOG dialogID, int ID, const std::string & strText, int x, int y, int width,
 int height, bool bChecked, UINT nHotkey, bool bIsDefault);
void AddRadioButtonToDialog(XACTGAMEDIALOG dialogID, int ID, UINT nButtonGroup, const std::string & strText,
 int x, int y, int width, int height, bool bChecked, UINT nHotkey, bool bIsDefault);
void AddComboBoxToDialog(XACTGAMEDIALOG dialogID, int ID, int x, int y, int width, int height,
 UINT nHotKey, bool bIsDefault);
void AddSliderToDialog(XACTGAMEDIALOG dialogID, int ID, int x, int y, int width, int height,
 int min, int max, int value,  bool bIsDefault);

There's no reason why the interface that you supply to AngelScript has to be exactly like the C++ one. The AngelScript interface also can't handle standard C-style strings which is what the CDXUTDialog methods require for text so we'd still have to wrap them in another function either way. To make the interface a little cleaner, I'll put it in a namespace. This can be done by calling the SetDefaultNamespace()method before we register the global functions.

int RegisterDialogInterface(asIScriptEngine *scriptengine)
{
 int result;

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

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

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

 ...

 // register the global functions
 result = scriptengine->RegisterGlobalFunction("void AddStaticToDialog(XACTGAMEDIALOG, int, const string &in, int, int, int, int, bool)",
  asFUNCTION(AddStaticToDialog), asCALL_CDECL);
 if(result < 0) return result;

 ...

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

We should register the enum just as before. Use the RegisterGlobalFunction() method to add each of the new functions. The method needs the declaration of the function, a function pointer, and the calling convention. When using reference parameters, you must use 'in' or 'out' as the parameter names in the declaration you supply to AngelScript so it will know how it can optimize how it uses the parameter.

Now, if we need anymore functionality from RENDER_STATE, we can just add more functions to the interface instead of giving the script direct access. This will be simple to do and safer as we can add checks to our wrapper functions. We'll do the same thing with the CFirstPersonCamera class.

// declare the global g_Camera variable as extern here so we can use the one defined in game.cpp
extern CFirstPersonCamera  g_Camera;

static void CameraSetViewParams( D3DXVECTOR3 &pvEyePt, D3DXVECTOR3 &pvLookatPt )
{
 g_Camera.SetViewParams(&pvEyePt, &pvLookatPt);
}

static void CameraSetEnableYAxisMovement( bool bEnableYAxisMovement )
{
 g_Camera.SetEnableYAxisMovement(bEnableYAxisMovement);
}

...

int RegisterCameraInterface(asIScriptEngine *scriptengine)
{
 int result;

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

 // register the global functions
 result = scriptengine->RegisterGlobalFunction("void SetViewParams( D3DXVECTOR3 &in, D3DXVECTOR3 &in )", asFUNCTION(CameraSetViewParams), asCALL_CDECL);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalFunction("void SetEnableYAxisMovement( bool )", asFUNCTION(CameraSetEnableYAxisMovement), asCALL_CDECL);
 if(result < 0) return result;

 ...


 // reset back to global namespace
 result = scriptengine->SetDefaultNamespace(""); 
 
 return result;
}
Now the last thing we need to make an interface for so that we can script the InitApp() function is GAME_STATE. There are a few possible ways to do this. One way would be to make a GAME_STATE object type in AngelScript and then register g_GameState as a global property. A second way would be to provide a set of accessor (get/set) functions and register them as global functions in a namespace. A third option would be to use a namespace and then register individual member variables of the GAME_STATE struct as global properties in AngelScript. Since there is only one g_GameState in the XACTGame sample, I think registering a new type would be a waste. There's also no need to make getters and setters since I'm not going to add any checking so I'll use the third option.

int RegisterGameStateInterface(asIScriptEngine *scriptengine)
{
 int result;

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

 // Register a primitive property that can be read and written to from the script.
 result = scriptengine->RegisterGlobalProperty("int nAmmoCount", &g_GameState.nAmmoCount);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalProperty("float fAmmoColorLerp", &g_GameState.fAmmoColorLerp);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalProperty("D3DXCOLOR BlendFromColor", &g_GameState.BlendFromColor);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalProperty("bool bDroidMove", &g_GameState.bDroidMove);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalProperty("bool bAutoAddDroids", &g_GameState.bAutoAddDroids);
 if(result < 0) return result;

 result = scriptengine->RegisterGlobalProperty("GAME_MODE gameMode", &g_GameState.gameMode);
 if(result < 0) return result;

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

 return result;
}
That's it for the interface for now, I can add more properties later if needed. Now we have the entire interface that will be needed to script the InitApp() function with AngelScript. Now we need to be able to load the script. The following code will do that.

int LoadScript(asIScriptEngine *scriptengine, CScriptBuilder &scriptbuilder, ScriptContextData &contextdata)
{
 int result;

 // The CScriptBuilder helper is an add-on that loads the file,
 // performs a pre-processing pass if necessary, and then tells
 // the engine to build a script module.
 CScriptBuilder builder;
 result = builder.StartNewModule(scriptengine, "BasicModule"); 
 if( result < 0 ) 
 {
  // If the code fails here it is usually because there
  // is no more memory to allocate the module
  MessageBoxA(NULL, "Unrecoverable error while starting a new module.", "AngelScript Message", MB_OK);
  return result;
 }
 result = builder.AddSectionFromFile("xactgamescript.as");
 if( result < 0 ) 
 {
  // The builder wasn't able to load the file. Maybe the file
  // has been removed, or the wrong name was given, or some
  // preprocessing commands are incorrectly written.
  MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
  return result;
 }
 result = builder.BuildModule();
 if( result < 0 ) 
 {
  // An error occurred. Instruct the script writer to fix the 
  // compilation errors that were listed in the output stream.
  MessageBoxA(NULL,"Please correct the errors in the script and try again.", "AngelScript Message", MB_OK);
  return result;
 }

 // Find the function that is to be called. 
 asIScriptModule *mod = scriptengine->GetModule("BasicModule");
 contextdata.script_functions[Function_InitApp] = mod->GetFunctionByDecl("void InitApp()");
 if( contextdata.script_functions[Function_InitApp] == 0 )
 {
  // The function couldn't be found. Instruct the script writer
  // to include the expected function in the script.
  MessageBoxA(NULL,"The script must have the function 'void InitApp()'. Please add it and try again.", "AngelScript Message", MB_OK);
  return -1;
 }

 return result;
}
This function uses the CScriptBuilder add-on to load a script. CScriptBuilder is a useful class because it not only loads the script, but it can also do some C-style preprocessor actions such as #include. After the script has been loaded, we use script builder to compile the script into AngelScript bytecode and build a module. In this function, I also get and stor the AngelScript function for our scripted InitApp() function so I'll be able to quickly call it later. Inside the ScriptContextData struct that I created earlier is an ExecuteFunction() method that will execute a function in the script.

// enumerations -------------------------------------------------------------
enum ScriptFunctionIDs
{
 Function_InitApp = 0
};

const unsigned int max_script_functions = 1;

// Structures ---------------------------------------------------------------
struct ScriptContextData
{
 asIScriptContext *ctx;
 asIScriptFunction *script_functions[max_script_functions];

 void ExecuteFunction(ScriptFunctionIDs func_id);
};

void ScriptContextData::ExecuteFunction(ScriptFunctionIDs func_id)
{
 if(ctx)
 {
  ctx->Prepare(script_functions[func_id]);
  int result = ctx->Execute();
  if( result != asEXECUTION_FINISHED )
  {
   // The execution didn't complete as expected. Determine what happened.
   if( result == asEXECUTION_EXCEPTION )
   {
    // An exception occurred, let the script writer know what happened so it can be corrected.
    MessageBoxA(NULL, ctx->GetExceptionString(), "An exception occurred.", MB_OK);
   }
  }
 }
}
So now let's script it. First let's take a look at the C++ version of the InitApp() function.

void InitApp()
{
    srand( 0 );

    g_Render.pEffect = NULL;
    g_Render.pDefaultTex = NULL;
    g_Render.UseFixedFunction = 0.0f;
    g_Render.ForceShader = 0;
    g_Render.MaximumResolution = 4096.0f;
    g_Render.DisableSpecular = 0.0f;
    g_Render.bDetectOptimalSettings = true;

    // Initialize dialogs
    g_Render.MainMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.MainMenuDlg.SetCallback( OnGUIEvent ); int iY = ( ( 300 - 30 * 6 ) / 2 );
    g_Render.MainMenuDlg.AddButton( IDC_AUDIO, L"Audio", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    g_Render.MainMenuDlg.AddButton( IDC_VIDEO, L"Video", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    g_Render.MainMenuDlg.AddButton( IDC_RESUME, L"Resume", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    g_Render.MainMenuDlg.AddButton( IDC_QUIT, L"Quit", ( 250 - 125 ) / 2, iY += 60, 125, 22, 'Q' );

    g_Render.AudioMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.AudioMenuDlg.SetCallback( OnGUIEvent ); iY = 60;
    g_Render.AudioMenuDlg.AddStatic( IDC_STATIC, L"Music Volume", ( 250 - 125 ) / 2, iY += 24, 125, 22 );
    g_Render.AudioMenuDlg.AddSlider( IDC_MUSIC_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    g_Render.AudioMenuDlg.AddStatic( IDC_STATIC, L"Sound Effects Volume", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    g_Render.AudioMenuDlg.AddSlider( IDC_SOUNDFX_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    g_Render.AudioMenuDlg.AddButton( IDC_BACK, L"Back", ( 250 - 125 ) / 2, iY += 40, 125, 22 );

    g_Render.VideoMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.VideoMenuDlg.SetCallback( OnGUIEvent ); iY = 0;
    g_Render.VideoMenuDlg.AddCheckBox( IDC_FULLSCREEN, L"Full screen", ( 250 - 200 ) / 2, iY += 30, 200, 22, true );
    g_Render.VideoMenuDlg.AddStatic( IDC_STATIC, L"Aspect:", 50, iY += 22, 50, 22 );
    g_Render.VideoMenuDlg.AddComboBox( IDC_ASPECT, 100, iY, 100, 22 );
    g_Render.VideoMenuDlg.AddStatic( IDC_STATIC, L"Resolution:", 30, iY += 22, 75, 22 );
    g_Render.VideoMenuDlg.AddComboBox( IDC_RESOLUTION, 100, iY, 125, 22 );
    g_Render.VideoMenuDlg.AddCheckBox( IDC_ANTI_ALIASING, L"Anti-Aliasing", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       false );
    g_Render.VideoMenuDlg.AddCheckBox( IDC_HIGH_MODEL_RES, L"High res models", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       true );
    g_Render.VideoMenuDlg.AddStatic( IDC_MAX_DROIDS_TEXT, L"Max Droids", ( 250 - 125 ) / 2, iY += 26, 125, 22 );
    g_Render.VideoMenuDlg.AddSlider( IDC_MAX_DROIDS, ( 250 - 150 ) / 2, iY += 22, 150, 22, 1, MAX_DROID, 10 );
    g_Render.VideoMenuDlg.AddButton( IDC_APPLY, L"Apply", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    g_Render.VideoMenuDlg.AddButton( IDC_BACK, L"Back", ( 250 - 125 ) / 2, iY += 30, 125, 22 );

    // Setup the camera
    D3DXVECTOR3 MinBound( g_MinBound.x + CAMERA_SIZE, g_MinBound.y + CAMERA_SIZE, g_MinBound.z + CAMERA_SIZE );
    D3DXVECTOR3 MaxBound( g_MaxBound.x - CAMERA_SIZE, g_MaxBound.y - CAMERA_SIZE, g_MaxBound.z - CAMERA_SIZE );
    g_Camera.SetClipToBoundary( true, &MinBound, &MaxBound );
    g_Camera.SetEnableYAxisMovement( false );
    g_Camera.SetRotateButtons( false, false, true );
    g_Camera.SetScalers( 0.001f, 4.0f );
    D3DXVECTOR3 vecEye( 0.0f, -GROUND_Y + 0.7f, 0.0f );
    D3DXVECTOR3 vecAt ( 0.0f, -GROUND_Y + 0.7f, 1.0f );
    g_Camera.SetViewParams( &vecEye, &vecAt );

    ZeroMemory( &g_GameState, sizeof( GAME_STATE ) );
    g_GameState.gameMode = GAME_RUNNING;
    g_GameState.nAmmoCount = 0;
    g_GameState.fAmmoColorLerp = 1000.0f;
    g_GameState.BlendFromColor = D3DXCOLOR( 0.6f, 0, 0, 1 );
    g_GameState.bAutoAddDroids = false;
    g_GameState.bDroidMove = true;

    // Store the rcWork of each monitor before a fullscreen D3D device is created 
    // This is used later to ensure the supported window mode 
    // resolutions will fit inside the desktop
    IDirect3D9* pD3D = DXUTGetD3D9Object();
    UINT numAdapters = pD3D->GetAdapterCount();
    for( UINT adapterOrdinal = 0; adapterOrdinal < numAdapters && adapterOrdinal < 10; adapterOrdinal++ )
    {
        MONITORINFO miAdapter;
        miAdapter.cbSize = sizeof( MONITORINFO );
        DXUTGetMonitorInfo( pD3D->GetAdapterMonitor( adapterOrdinal ), &miAdapter );
        g_Render.rcAdapterWork[adapterOrdinal] = miAdapter.rcWork;
    }

    // Make a list of supported windowed mode resolutions.  
    // The list of fullscreen mode resolutions are gathered from the D3D device directly.
    D3DDISPLAYMODE dm = {0, 0, 0, D3DFMT_UNKNOWN};
    dm.Width = 640; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 800; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1024; dm.Height = 768; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1280; dm.Height = 960; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1600; dm.Height = 1200; g_Render.aWindowedDMList.Add( dm ); // 4:3

    dm.Width = 852; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1067; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1280; dm.Height = 720; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1920; dm.Height = 1080; g_Render.aWindowedDMList.Add( dm ); // 16:9
}
Some things can be scripted, but some things would be better left done in C++. Now I'll rewrite it and only leave in the things that shouldn't be scripted.

void InitApp(ScriptContextData &context_data)
// Changed 2013-12-25 By Dominque Douglas for AngelScript Example
{
    srand( 0 );

    g_Render.pEffect = NULL;
    g_Render.pDefaultTex = NULL;
    g_Render.UseFixedFunction = 0.0f;
    g_Render.ForceShader = 0;
    g_Render.MaximumResolution = 4096.0f;
    g_Render.DisableSpecular = 0.0f;
    g_Render.bDetectOptimalSettings = true;

    // Initialize dialogs
    g_Render.MainMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.MainMenuDlg.SetCallback( OnGUIEvent );
 // we'll script adding all the GUI elements

    g_Render.AudioMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.AudioMenuDlg.SetCallback( OnGUIEvent );
 // we'll script adding all the GUI elements

    g_Render.VideoMenuDlg.Init( &g_Render.DialogResourceManager );
    g_Render.VideoMenuDlg.SetCallback( OnGUIEvent );
 // we'll script adding all the GUI elements

 // script setting up the camera

 // script setting the inital game state

    // Store the rcWork of each monitor before a fullscreen D3D device is created 
    // This is used later to ensure the supported window mode 
    // resolutions will fit inside the desktop
    IDirect3D9* pD3D = DXUTGetD3D9Object();
    UINT numAdapters = pD3D->GetAdapterCount();
    for( UINT adapterOrdinal = 0; adapterOrdinal < numAdapters && adapterOrdinal < 10; adapterOrdinal++ )
    {
        MONITORINFO miAdapter;
        miAdapter.cbSize = sizeof( MONITORINFO );
        DXUTGetMonitorInfo( pD3D->GetAdapterMonitor( adapterOrdinal ), &miAdapter );
        g_Render.rcAdapterWork[adapterOrdinal] = miAdapter.rcWork;
    }

    // Make a list of supported windowed mode resolutions.  
    // The list of fullscreen mode resolutions are gathered from the D3D device directly.
    D3DDISPLAYMODE dm = {0, 0, 0, D3DFMT_UNKNOWN};
    dm.Width = 640; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 800; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1024; dm.Height = 768; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1280; dm.Height = 960; g_Render.aWindowedDMList.Add( dm ); // 4:3
    dm.Width = 1600; dm.Height = 1200; g_Render.aWindowedDMList.Add( dm ); // 4:3

    dm.Width = 852; dm.Height = 480; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1067; dm.Height = 600; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1280; dm.Height = 720; g_Render.aWindowedDMList.Add( dm ); // 16:9
    dm.Width = 1920; dm.Height = 1080; g_Render.aWindowedDMList.Add( dm ); // 16:9

 // execute the script here
 context_data.ExecuteFunction(Function_InitApp);
}

With that done, now we can write the AngelScript script for our InitApp() function. I'll also add some constants to the script that were defined in the game.h file. A possible future enhancement would be to allow setting these constants in AngelScript and the allowing the C++ code use the values.

const float GROUND_Y = 3.0f; // -GROUND_Y is the Y coordinate of the ground.
const float CAMERA_SIZE = 0.2f; // CAMERA_SIZE is used for clipping camera movement
const uint MAX_DROID = 50;

// MinBound and MaxBound are the bounding box representing the cell mesh.
const D3DXVECTOR3           g_MinBound( -6.0f, -GROUND_Y, -6.0f );
const D3DXVECTOR3           g_MaxBound( 6.0f, GROUND_Y, 6.0f );

//--------------------------------------------------------------------------------------
// UI control IDs
//--------------------------------------------------------------------------------------
const uint IDC_STATIC              = 1;
const uint IDC_AUDIO               = 2;
const uint IDC_VIDEO               = 3;
const uint IDC_RESUME              = 4;
const uint IDC_QUIT                = 5;
const uint IDC_BACK                = 8;
const uint IDC_SOUNDFX_SCALE       = 6;
const uint IDC_MUSIC_SCALE         = 7;
const uint IDC_RESOLUTION          = 9;
const uint IDC_ANTI_ALIASING       = 10;
const uint IDC_MAX_DROIDS          = 11;
const uint IDC_HIGH_MODEL_RES      = 12;
const uint IDC_MAX_DROIDS_TEXT     = 13;
const uint IDC_APPLY               = 14;
const uint IDC_FULLSCREEN          = 15;
const uint IDC_ASPECT              = 16;

void InitApp()
{
 int iY = ( ( 300 - 30 * 6 ) / 2 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_AUDIO, "Audio", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_VIDEO, "Video", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_RESUME, "Resume", ( 250 - 125 ) / 2, iY += 30, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDMainMenuDlg, IDC_QUIT, "Quit", ( 250 - 125 ) / 2, iY += 60, 125, 22, 81/*'Q'*/ );

 iY = 60;
    dialogs::AddStaticToDialog(dialogs::IDAudioMenuDlg, IDC_STATIC, "Music Volume", ( 250 - 125 ) / 2, iY += 24, 125, 22 );
    dialogs::AddSliderToDialog(dialogs::IDAudioMenuDlg, IDC_MUSIC_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    dialogs::AddStaticToDialog(dialogs::IDAudioMenuDlg, IDC_STATIC, "Sound Effects Volume", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    dialogs::AddSliderToDialog(dialogs::IDAudioMenuDlg, IDC_SOUNDFX_SCALE, ( 250 - 100 ) / 2, iY += 24, 100, 22, 0, 100, 100 );
    dialogs::AddButtonToDialog(dialogs::IDAudioMenuDlg, IDC_BACK, "Back", ( 250 - 125 ) / 2, iY += 40, 125, 22 );

 iY = 0;
    dialogs::AddCheckBoxToDialog(dialogs::IDVideoMenuDlg, IDC_FULLSCREEN, "Full screen", ( 250 - 200 ) / 2, iY += 30, 200, 22, true );
    dialogs::AddStaticToDialog(dialogs::IDVideoMenuDlg, IDC_STATIC, "Aspect:", 50, iY += 22, 50, 22 );
    dialogs::AddComboBoxToDialog(dialogs::IDVideoMenuDlg, IDC_ASPECT, 100, iY, 100, 22 );
    dialogs::AddStaticToDialog(dialogs::IDVideoMenuDlg, IDC_STATIC, "Resolution:", 30, iY += 22, 75, 22 );
    dialogs::AddComboBoxToDialog(dialogs::IDVideoMenuDlg, IDC_RESOLUTION, 100, iY, 125, 22 );
    dialogs::AddCheckBoxToDialog(dialogs::IDVideoMenuDlg, IDC_ANTI_ALIASING, "Anti-Aliasing", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       false );
    dialogs::AddCheckBoxToDialog(dialogs::IDVideoMenuDlg, IDC_HIGH_MODEL_RES, "High res models", ( 250 - 200 ) / 2, iY += 26, 200, 22,
                                       true );
    dialogs::AddStaticToDialog(dialogs::IDVideoMenuDlg, IDC_MAX_DROIDS_TEXT, "Max Droids", ( 250 - 125 ) / 2, iY += 26, 125, 22 );
    dialogs::AddSliderToDialog(dialogs::IDVideoMenuDlg, IDC_MAX_DROIDS, ( 250 - 150 ) / 2, iY += 22, 150, 22, 1, MAX_DROID, 10 );
    dialogs::AddButtonToDialog(dialogs::IDVideoMenuDlg, IDC_APPLY, "Apply", ( 250 - 125 ) / 2, iY += 35, 125, 22 );
    dialogs::AddButtonToDialog(dialogs::IDVideoMenuDlg, IDC_BACK, "Back", ( 250 - 125 ) / 2, iY += 30, 125, 22 );

    // Setup the camera
    D3DXVECTOR3 MinBound( g_MinBound.x + CAMERA_SIZE, g_MinBound.y + CAMERA_SIZE, g_MinBound.z + CAMERA_SIZE );
    D3DXVECTOR3 MaxBound( g_MaxBound.x - CAMERA_SIZE, g_MaxBound.y - CAMERA_SIZE, g_MaxBound.z - CAMERA_SIZE );
    FirstPersonCamera::SetClipToBoundary( true, MinBound, MaxBound );
    FirstPersonCamera::SetEnableYAxisMovement( false );
    FirstPersonCamera::SetRotateButtons( false, false, true );
    FirstPersonCamera::SetScalers( 0.001f, 4.0f );
    D3DXVECTOR3 vecEye( 0.0f, -GROUND_Y + 0.7f, 0.0f );
    D3DXVECTOR3 vecAt ( 0.0f, -GROUND_Y + 0.7f, 1.0f );
    FirstPersonCamera::SetViewParams( vecEye, vecAt );

    GAME_STATE::gameMode = GAME_RUNNING;
    GAME_STATE::nAmmoCount = 0;
    GAME_STATE::fAmmoColorLerp = 1000.0f;
    GAME_STATE::BlendFromColor = D3DXCOLOR( 0.6f, 0, 0, 1 );
    GAME_STATE::bAutoAddDroids = false;
    GAME_STATE::bDroidMove = true;

}
All of this may seem like a lot of work especially getting all the bindings with the script so many wonder, "is it worth it?" That is a valid question that all should ask themselves when they are considering adding scripting support. For such a small program such as the Direct X XACTGame sample application, it's probably not neccessary, but as your projects increase in size the value of using scripting languages will become more apparent. The binding code for AngelScript is needed because AngelScript is a general purpose scripting language and it needs to know about the application to accurately communicate with the C++ code, but AngelScript has a nice interface, and after some practice, you'll see that doing the bindings is fairly straight forward.

 So that's it for now. In the next part of this article, I'll script some of the other functions. If you download the source code, you'll have to make sure the include and library directories for the AngelScript SDK in the project properties are correct.

For the source code and Visual Studio 2010 project:
XACTGameAngelScript-Part1.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. For Part 2 of this series, please click here: Programming By Example - Adding AngelScript to a Game Part 2

Thursday, December 19, 2013

STL-Style UTF-8 String Class now on GitHub

I've recently uploaded the files from the blog series "Writing a STL-Style UTF-8 String Class" to Github. To keep up with all of the latest updates, use the files on Github. The link can be found here: Squared'D Programming UTF-8 String Class - https://github.com/squaredprogramming/sdp_utf8string

Some additions:

  • I'm now using the BSD License. This means the source code can be used without restrictions as longs as you keep the copyright notice. The utf8string class is totally free.
  • When I was updating the source code of one of my projects to use the utf8string class, I noticed that I hadn't implemented the comparison operators so they have been added now.

For the latest information, be sure to check back here. I do more than just program UTF-8 related stuff. I also develop indie video games. Here's a link to my game development blog - http://www.gamedev.net/blog/1670-squaredds-journal/

Sunday, December 15, 2013

Squared'D Programming Project Videos

I've been slowly trying to build up my video library on Youtube. Most of the videos focus on my main project Auxnet: Battlegrounds, but I'm also trying to add some instructional videos as well. Before sure to check them out and tell me what you think about them.

http://youtu.be/sqPTR_v4qFA





Introduction to UTF-8
This video gives an introduction to UTF-8 and Unicode. It gives a detail description of UTF-8 and how to encode in UTF-8. This is a video presentation of the article "How about Unicode and UTF-8" which was published on www.gamedev.net.






Auxnet: Battlegrounds Intro (IGF 2014)
Auxnet is a futuristic, multi-player, Sci Fi video game. In the future, the internet has been replaced by super networks where uses can enter virtual worlds by connecting there consciousnesses to the network. They can experience many places and learn in a classroom, from there own homes. Auxnet is one of those super networks. On Auxnet, users can play games in virtual arenas.






Auxnet: Battlegrounds - Pre-alpha Gameplay Preview
This is just a short video that shows a model moving around the scene with some simple AI controlled characters. The game engine was written using Direct X by Dominque Douglas (Squared'D) and a lot of the core functionality has been completed.  Most programming work is now centered on gameplay and AI. The game is still in it's pre-alpha phase so a lot of the art is still in it's beginning stages and will undergo many upgrades before the final build. We hope this demo video shows some of our ability, but we expect much more and much better quality in the coming months.


This is the intro of a past IGF entry that we worked on. It used a previous version of the current 3D engine. The future of this game is not as yet known, but this intro deserves to be preserved because of all the effort that went into it.






Game Engine Entity Attachments
Have you ever wondered how to attach weapons to a 3D model in C++ and have it animate with your model? In this video blog, I give details about how I've implemented attachments to the entities in my game engine. Attachments are separate 3D models that are basically add-ons to the base model. They can be used for weapons, accessories, etc. This video should be detailed enough for you to add attachments to your existing 3D model animation system.






Effects Test
This is a short little video showing the effects system that I have been developing

Wednesday, December 11, 2013

Writing a STL-style UTF-8 String Class Part 5

Update: The latest code can now be found on GitHub. Check the post "STL-Style UTF-8 String Class now on GitHub" for more information.
____________________________________________________________________________

In this final part of my blog series "Writing a STL-style UTF-8 String Class", I will continue to make this string clas behave more like std::string. First I added support for using different allocators. How did I go about doing this? I took a lesson from std::basic_string and did it using templates. This means the entire utf8string class is now a template class. This is the basic setup now:
namespace sd_utf8
{

template <class Alloc = std::allocator<_uchar8bit>>
class _utf8string
{
 public:
  // some types that we need to define to make this work like an stl object
  // internally this is an std string, but outwardly, it returns __char32bit
  typedef _char32bit   value_type;
  typedef _char32bit   *pointer;
  typedef const _char32bit *const_pointer;
  typedef _char32bit   &reference;
  typedef const _char32bit &const_reference;
  typedef size_t    size_type;
  typedef ptrdiff_t   difference_type;

  ...

  // make our iterator types here
  typedef utf8string_iterator<value_type>   iterator;
  typedef utf8string_iterator<const value_type> const_iterator;
  typedef value_reverse_iterator<iterator>  reverse_iterator;
  typedef value_reverse_iterator<const_iterator> const_reverse_iterator;

 private:
  std::basic_string<_uchar8bit, std::char_traits<unsigned char>, Alloc> utfstring_data;

 public:
  ...


};

typedef _utf8string<> utf8string;

}


I've added the code "typedef _utf8string<> utf8string;" so I'll still be able to use utf8string with the default template parameters. Using templates in this way makes adding an allocator very simple. Of course, this also mean some changes were neccessary in utf8utils.h"

<// use a template method because the 16bit and 32 bit implementations are identical
// except for the type
template <typename char_type, typename Alloc>
inline void MakeUTF8StringImpl(const char_type* instring, std::basic_string<_uchar8bit data-blogger-escaped-alloc=""> &out, bool appendToOut)
{
...
}

Next I added all of the other std::string methods(functions), but not all of the overloads. I learned something working on this project. std::string has a ton of functions. Since the class uses a std::basic_string internally, I was able to use many of the STL functions to do the work. I just had to make sure the parameters were correct. replace() was a little trickier, but then I realized that replace was just an erase and then an insert.

_utf8string<Alloc>& replace (size_type pos, size_type len, const _utf8string<Alloc>& str)
{
 // make copy so exceptions won't change string
 _utf8string<Alloc> temp_copy(*this);

 // erase
 temp_copy.erase(pos, len);

 // insert
 temp_copy.insert(pos, str);

 assign(temp_copy);

 return *this;
}

Most of the function overloads that I didn't implement were ones that took iterators as parameters. Since my iterators are always constant and also since they don't have a reference to the actual utf8string object, addind those functions would have been difficult, but not impossible. I could have implemented them, but I'd have to re-scan the string each time and I thought that would be a bit wasteful.

This was a fun project for me. Here's a tip if you ever want to build something like this. There are so many functions that you need to try to reuse code as much as you can and find easy ways to do things.

What's next? Over the next few weeks, I'll continue to tinker with the code. My plan is to put this up on SourceForge and make it opensource. I'll try to get that set up within the next two weeks, but in the meantime, I need to work on my main project Auxnet: Battlegrounds. Thanks for reading this long series. I hope you were able to benefit from it.

For the complete source code

utf8string.h
utf8utils.h

-------------
For the other post in this series:

Writing a STL-Style UTF-8 String Class Part 1
Writing a STL-Style UTF-8 String Class Part 2
Writing a STL-Style UTF-8 String Class Part 3
Writing a STL-style UTF-8 String Class Part 4
Writing a STL-style UTF-8 String Class Part 5

Tuesday, December 10, 2013

Writing a STL-Style UTF-8 String Class Part 4

Update: The latest code can now be found on GitHub. Check the post "STL-Style UTF-8 String Class now on GitHub" for more information.
____________________________________________________________________________

In this fourth installment of "Writing a STL-Style UTF-8 String Class", I'll show the first version of the utf8string class and in my next post, I'll add all the other remaining member functions to make it behave like std::string.

One nice thing about UTF-8 is that it's compatible with normal 8-bit string functions. There is only one little problem. Processing UTF-8 requires using unsigned 8-bit characters, but some compilers have the char data type as a signed type. This means if we just cast the pointers, typical string functions will read our data that's over 127 as negative numbers. This is OK in most situations, but it will through of less than (<) and greater than (>) comparison functions. To get around this and still be able to utilize some of C++'s STL string capabilities, I'm going to cut out some of my code. I will remove the following lines:

pointer  buffer;
size_type reserve_size;
size_type used_length;

And replace them with this

std::basic_string<_uchar8bit> utfstring_data;

Also, the string will use _uchar8bit (unsigned char) internally, but externally, the class should now return _char32bit(unsigned int). This is because I want the class to return the decoded Unicode value, and not the UTF-8 encoding. This means our internal types will need to change.

typedef _char32bit   value_type;
typedef _char32bit   *pointer;
typedef const _char32bit *const_pointer;
typedef _char32bit   &reference;
typedef const _char32bit &const_reference;
typedef size_t    size_type;
typedef ptrdiff_t   difference_type;

This is significant in another way. This class can no longer return references to elements in the underlying array. It can only return values. If the class can only return values, then the iterator also should only return values. This means that our iterator will always be a const iterator that cannot change the string. This also means that we can no longer use the default std::reverse_iterator. That template class expects the iterator to return references and not values. If we try to use it, we'll get compiler errors. So we'll also need to write our
own reverse iterator. Creating our own reverse iterator is not overly difficult. The key to making a reverse iterator is having it use the forward iterator as a member. Here's an example:


template <class TBaseIterator>
class value_reverse_iterator : public std::iterator<std::bidirectional_iterator_tag, value_type>
{
 public:
  TBaseIterator forward_iterator;

 public:
  // copy constructor
  value_reverse_iterator(const value_reverse_iterator &other)
   :forward_iterator(other.forward_iterator)
  {
  }

  // create from forward iterator
  value_reverse_iterator(const TBaseIterator &iterator)
   :forward_iterator(iterator)
  {
   int a;
   a = 5;
  }

  value_type operator*() const
  {
   TBaseIterator temp = forward_iterator;
   return *(--temp);
  }

  // does not check to see if it goes past the end
  // iterating past the end is undefined
  value_reverse_iterator &operator++()
  {
   --forward_iterator;

   return *this;
  }

  // does not check to see if it goes past the end
  // iterating past the end is undefined
  value_reverse_iterator operator++(int)
  {
   value_reverse_iterator copy(*this);

   // increment
   --forward_iterator;

   return copy;
  }

  // does not check to see if it goes past the end
  // iterating past begin is undefined
  value_reverse_iterator &operator--()
  {
   ++forward_iterator;
   return *this;
  }

  // does not check to see if it goes past the end
  // iterating past begin is undefined
  value_reverse_iterator operator--(int)
  {
   value_reverse_iterator copy(*this);

   ++forward_iterator;

   return copy;
  }

  bool operator == ( const value_reverse_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return forward_iterator == other.forward_iterator;
  }
 
  bool operator != (const value_reverse_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return forward_iterator != other.forward_iterator;
  }

  bool operator < ( const value_reverse_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return forward_iterator > other.forward_iterator;
  }
 
  bool operator > (const value_reverse_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return forward_iterator < other.forward_iterator;
  }
  
  bool operator <= ( const value_reverse_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return forward_iterator >= other.forward_iterator;
  }
 
  bool operator >= (const value_reverse_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return forward_iterator <= other.forward_iterator;
  }

};

The next step is to use the UTF-8 utility functions that were made in the Part 3 to make this work with the UTF-8 encoded text. Also, all methods and operators that returned references before should now return values.

Many new constructors are needed because this should be able to automatically convert between std::string, std::wstring, and be able to use char and wchar_t type characters.

// default constructor
utf8string();

// build from a c string
// undefined (ie crashes) if str is NULL
utf8string(const _char8bit *str);

// build from a c string
// undefined (ie crashes) if str is NULL
utf8string(const _uchar8bit *str);

// construct from an unsigned char
utf8string(size_t n, _uchar8bit c);

// construct from a normal char
utf8string(size_t n, _char8bit c);

// construct from a normal char
utf8string(_uchar8bit c);

// construct from a normal char
utf8string(_char8bit c);

// construct from a normal char
utf8string(_char16bit c);

// construct from a normal char
utf8string(_char32bit c);

// copy constructor
utf8string(const utf8string &str);

/// \brief Constructs a UTF-8 string from an 16 bit character terminated string
utf8string (const _char16bit* instring_UCS2);

/// \brief Constructs a UTF-8 string from an 32 bit character terminated string
utf8string (const _char32bit* instring_UCS4);

/// \brief copy constructor from basic std::string
utf8string(const std::string &instring);

/// \brief copy constructor from basic std::string
utf8string(const std::wstring &instring);

I use the same pre-conditions as std::string. In other words, if std::string doesn't check the correctness of a parameter, I don't either. I've also written some extra constructors such as utf8string(char c). This is so I can do calls such as string += 'c' without having to overload the += operator with every type. If you provide constructors, the compiler will be able to construct a new utf8string and just use the += operator that takes "const utf8string &" as a parameter.

I'm also providing three versions of the copy method

// copies a sub string of this string to s and returns the number of characters copied
// if this string is shorter than len, as many characters as possible are copied
// undefined behavior if the buffer pointed to by s is not long enough
// UTF-8 version
size_type copy (_uchar8bit *s, size_type len, size_type pos = 0) const;

// copies a sub string of this string to s and returns the number of characters copied
// if this string is shorter than len, as many characters as possible are copied
// undefined behavior if the buffer pointed to by s is not long enough
// outputs to UCS-2
size_type copy (_char16bit *s, size_type len, size_type pos = 0) const;

// outputs to UCS-4
size_type copy (_char32bit *s, size_type len, size_type pos = 0) const;

For the complete source code so far, you can download these two files

utf8string.0.h
utf8utils.h

-------------
For the other post in this series:

Writing a STL-Style UTF-8 String Class Part 1
Writing a STL-Style UTF-8 String Class Part 2
Writing a STL-Style UTF-8 String Class Part 3
Writing a STL-style UTF-8 String Class Part 4


Monday, December 9, 2013

Writing a STL-Style UTF-8 String Class Part 3

Update: The latest code can now be found on GitHub. Check the post "STL-Style UTF-8 String Class now on GitHub" for more information.
____________________________________________________________________________

So far I've written a basic string class and have given it an iterator, but there's something missing. It doesn't use UTF-8. In this post, I'll introduce some UTF-8 utility functions that the string class will use.

The following information requires a knowledge of UTF-8. If you'd like information on it, you can watch
my video presentation that gives a good introduction to UTF-8 or check out the first post in this series for some links.

To add UTF-8 to this class, first I will define a new namespace to keep everything.

namespace sd_utf8
{
}

I'll also add another header with some UTF-8 utility functions. These functions will be used with the utf8string class, but application programmers will also be able to use the utility functions to add UTF-8 capabilities to their existing classes.

The functions will need this type which is a 4 character array. Encoded UTF-8 will be put here.

typedef _uchar8bit utf8_encoding[4];

Here some of the utility functions. All functions are inline because they are all defined in the header file.

The GetUTF8Encoding function will encode at 32 bit Unicode value into UTF-8. The size of the encoding will be returnd in out_size. This function can reorder the incoming data if it's in an endian that is the opposite of the current system.

/// This function generates a UTF-8 encoding from a 32 bit UCS-4 character.
/// This is being provided as a static method so it can be used with normal std::string objects
/// default_order is true when the byte order matches the system
inline void GetUTF8Encoding(_char32bit in_char, utf8_encoding &out_encoding, int &out_size, bool default_order = true)
{
 // check the order byte order and reorder if neccessary
 if(default_order == false)
 {
  in_char = ((in_char & 0x000000ff) << 24) + ((in_char & 0x0000ff00) << 8) + ((in_char & 0x00ff0000) >> 8) + ((in_char & 0xff000000) >> 24);
 }

 if(in_char < 0x80)
 {
  // 1 byte encoding
  out_encoding[0] = (char)in_char;
  out_size = 1;
 }
 else if(in_char < 0x800)
 {
  // 2 byte encoding
  out_encoding[0] = 0xC0 + ((in_char & 0x7C0) >> 6);
  out_encoding[1] = 0x80 + (in_char & 0x3F);
  out_size = 2;
 }
 else if(in_char < 0x10000)
 {
  // 3 byte encoding
  out_encoding[0] = 0xE0 + ((in_char & 0xF000) >> 12);
  out_encoding[1] = 0x80 + ((in_char & 0xFC0) >> 6);
  out_encoding[2] = 0x80 + (in_char & 0x3F);
  out_size = 3;
 }
 else
 {
  // 4 byte encoding
  out_encoding[0] = 0xF8 + ((in_char & 0x1C0000) >> 18);
  out_encoding[1] = 0x80 + ((in_char & 0x3F000) >> 12);
  out_encoding[2] = 0x80 + ((in_char & 0xFC0) >> 6);
  out_encoding[3] = 0x80 + (in_char & 0x3F);
  out_size = 4;
 }
}

inline void GetUTF8Encoding(_char16bit in_char, utf8_encoding &out_encoding, int &out_size, bool default_order = true)
{
 // check the order byte order and reorder if neccessary
 if(default_order == false)
 {
  in_char = ((in_char & 0x00ff) << 8) + ((in_char & 0xff00) >> 8);
 }

 // to reduce redundant code and possible bugs from typingg errors, use 32bit version
 GetUTF8Encoding((_char32bit)in_char, out_encoding, out_size, true);
}

The function UTF8CharToUnicode will read the next unicode character in a string and return a 32 bit unicode value.

inline _char32bit UTF8CharToUnicode(const _uchar8bit *utf8data)
{
 if(utf8data[0] < 0x80)
 {
  return (_char32bit)utf8data[0];
 }
 else if(utf8data[0] < 0xE0)
 {
  // 2 bytes
  return ((utf8data[0] & 0x1F) << 6) + (utf8data[1] & 0x3F);
 }
 else if (utf8data[0] < 0xF0)
 {
  // 3 bytes
  return ((utf8data[0] & 0xF) << 12) + ((utf8data[1] & 0x3F) << 6) + (utf8data[2] & 0x3F);
 }
 else
 {
  // 4 bytes
  return ((utf8data[0] & 0x7) << 18) + ((utf8data[1] & 0x3F) << 12) + ((utf8data[2] & 0x3F) << 6) + (utf8data[3] & 0x3F);
 }
}

Those two functions do all of the encoding and decoding work. In the UTF-8 code, I often just use simple < operations to determine the size of the encoding. Remember, in 4 byte encodings, the first byte will always be of the form 1111 0XXX. This means that for byte encodings will always be greater that 1111 0000 (F0 hex). 3-byte encodings have the form 1110 0000 (E0) so 3-byte encodings will be between E0 and F0. The same holds true for 2 and 1-byte encodings. So instead of doing fancy bit operations, a simple comparison is all that's needed.

Here's a list of all the other functions:

This function will increment a pointer to a UTF-8 string to the correct character position. It sets the pointer to point to the null-terminator if the position is off the string. Behavior is undefined is string doesn't point to a properly formated UTF-8 string.

inline void IncrementToPosition(const _uchar8bit *&string, size_t pos);


This function will  a UTF-8 encoded string and returns the actual begining in the buffer of the character at pos. Behavior is undefined is string doesn't point to a properly formated UTF-8 string or if pos is out of range

inline size_t GetBufferPosition(const _uchar8bit *string, size_t pos);


This function will get the minimum amount of memory needed to encode the string in UTF-8.

template <class T>
inline size_t GetMinimumBufferSize(const T *string);


Template function to convert a string into UTF-8 and stores the result in an std::basic_string. The type should be convertible to an int. A template function is being used because the 16-bit and 32-bit implementations are identical except for the type

template <typename char_type>
inline void MakeUTF8StringImpl(const char_type* instring, std::basic_string<_uchar8bit> &out, bool appendToOut);


Template function to convert a string into UTF-8 and stores the result in a buffer. The type should be convertible to an int. A template function is being used because the 16-bit and 32-bit implementations are identical except for the type. Out should point to a buffer large enough to hold the data.

template <typename char_type>
inline void MakeUTF8StringImpl(const char_type* instring, _uchar8bit *out);


This function uses the template function MakeUTF8StringImpl to convert a string.

inline void MakeUTF8String(const _char16bit* instring_UCS2, std::basic_string<_uchar8bit> &out, bool appendToOut = false);

inline void MakeUTF8String(const _char32bit* instring_UCS4, std::basic_string<_uchar8bit> &out, bool appendToOut = false);


These functions increment and decrement  a pointer to a UTF-8 encoded string to the next character. utf8data must point to a valid UTF-8 string.

inline void IncToNextCharacter(const _uchar8bit *&utf8data);

inline void DecToNextCharacter(const _uchar8bit *&utf8data);

Gets the length of a UTF-8 string in characters.

inline size_t GetNumCharactersInUTF8String(const _uchar8bit *utf8data)

Full source code can be found here: utf8utils.0.h

Sunday, December 8, 2013

Writing a STL-Style UTF-8 string class Part 2

Update: The latest code can now be found on GitHub. Check the post "STL-Style UTF-8 String Class now on GitHub" for more information.
____________________________________________________________________________

Over the next few days I will convert the basic string class that I made in my last post into a complete std::string-like UTF-8 string class. Before I add the real UTF-8 stuff, I want to add more stl-style related things to the class such as iterators and some standard types that I'll need.

First I'll add the following types

#ifndef WCHAR32BITS

// because signed or unsigned is not mandated for char or wchar_t in the standard,
// always use the char and wchar_t types or we may get compiler errors when using
// some standaard function

typedef char _char8bit; // for ASCII and UTF8
typedef unsigned char _uchar8bit; // for ASCII and UTF8
typedef wchar_t _char16bit; // for USC2
typedef std::uint32_t _char32bit; // for UTF32

#else

typedef char _char8bit; // for ASCII and UTF8
typedef unsigned char _uchar8bit; // for ASCII and UTF8
typedef std::uint16_t _char16bit; // for USC2
typedef wchar_t _char32bit; // for UTF32

#endif


The final UTF-8 string class should be able to process strings in different formats. These definitions will help keep those types organized without worrying about the compiler implementations. I'm also explicitly defining an unsigned 8-bit char type. This is because the sign of the char data type is not specified in the standard and can be signed or unsigned. For UTF-8 to work properly, it needs to be an unsigned 8-bit integer. The _char16bit and _char32bit types are being defined in terms of wchar_t to prevent compiler errors with std::wstring and so it'll work with the standard wide character string functions, such as wcscpy() and wcscmp().

Next these definitions are required by STL so they need to be added to our class. The types are fairly self-explanatory. For now the class will use _uchar8bit, but once I start adding the UTF-8 capabilities, I'll change the class to return _char32bit.

typedef _uchar8bit           value_type;
typedef _uchar8bit          *pointer;
typedef const _uchar8bit    *const_pointer;
typedef _uchar8bit          &reference;
typedef const _uchar8bit    &const_reference;
typedef size_t               size_type;
typedef ptrdiff_t            difference_type;

The class also needs an iterator. To build this iterator, I'll define a class using std::iterator as a base. The iterator will also use the std::bidirectional_iterator_tag.  This means I'll need to define increment(++) and decrement(--) operators. I'll also make it iterator a template class so I won't have to write the code twice for the const version.

template 
class utf8string_iterator : public std::iterator
{
 private:
  pointer buf_;

  void inc()
  {
   // increments the iterator by one
   // result in undefined behavior (crashes) if already at the end 
   ++buf_;
  }

  void dec()
  {
   // decrements the iterator by one
   // result in undefined behavior (crashes) if already at the beginning
   --buf_;
  }

 public:
  // b should be a null terminated string in UTF-8
  // if this is the end start_pos should be the index of the null terminator
  // start_pos should be the valid start of a character
  utf8string_iterator(pointer b, size_type start_pos)
  {
   buf_ = &b[start_pos];
  }

  // b should already point to the correct position in the string
  utf8string_iterator(pointer b)
   :buf_(b)
  {
  }

  reference operator*() const
  {
   return *buf_;
  }

  // does not check to see if it goes past the end
  // iterating past the end is undefined
  utf8string_iterator &operator++()
  {
   inc();

   return *this;
  }

  // does not check to see if it goes past the end
  // iterating past the end is undefined
  utf8string_iterator operator++(int)
  {
   utf8string_iterator copy(*this);

   // increment
   inc();

   return copy;
  }

  // does not check to see if it goes past the end
  // iterating past begin is undefined
  utf8string_iterator &operator--()
  {
   dec();
   return *this;
  }

  // does not check to see if it goes past the end
  // iterating past begin is undefined
  utf8string_iterator operator--(int)
  {
   utf8string_iterator copy(*this);

   dec();

   return copy;
  }

  bool operator == ( const utf8string_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return buf_ == other.buf_;
  }
 
  bool operator != (const utf8string_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return buf_ != other.buf_;
  }

  bool operator < ( const utf8string_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return buf_ < other.buf_;
  }
 
  bool operator > (const utf8string_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return buf_ > other.buf_;
  }
  
  bool operator <= ( const utf8string_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return buf_ <= other.buf_;
  }
 
  bool operator >= (const utf8string_iterator &other) const
  {
   // just compare pointers
   // the programmer is responsible for making both iterators are for the same set of data
   return buf_ >= other.buf_;
  }
};
// End ---------------------

Now these types need to be added to the class definition. For now, I will not need to write a
reverse iterator. There is a STL class that I can use to do that.

// make our iterator types here
typedef utf8string_iterator   iterator;
typedef utf8string_iterator const_iterator;
typedef std::reverse_iterator   reverse_iterator;
typedef std::reverse_iterator const_reverse_iterator;
// End ---------------------

After adding a few more functions and reorganizing the class, I now have a class that's more than 50% like std::string, but it's missing the most important thing. It needs process UTF-8. In my next, I'll write all of the necessary UTF-8 functions in my next post.

Be sure to check out my previous post Writing a STL-Style UTF-8 string class Part 1 and my video presentation that gives a good introduction to UTF-8.

For the full source code: mystring.1.h

-------------
For the other post in this series:

Writing a STL-Style UTF-8 String Class Part 1
Writing a STL-Style UTF-8 String Class Part 2
Writing a STL-Style UTF-8 String Class Part 3
Writing a STL-style UTF-8 String Class Part 4


Writing a STL-Style UTF-8 string class Part 1

Update: The latest code can now be found on GitHub. Check the post "STL-Style UTF-8 String Class now on GitHub" for more information.
____________________________________________________________________________

I've really been into UTF-8 these days. I've made a video introduction for it on Youtube and I've written an article. Now I'm writing an std::string style UTF-8 string class that I hope will be useful to me and to others.

This will be a 5 part series that I hope to complete this week. The final installment will be a full article about the class that I'll also publish on Gamedev.net

I will set out to create a utf8string class that behaves as much as possible as std::string. I will have all of the same methods as std::string and will overload the cast operator to be able to be cast to std::string and std::wstring. The class will also support C++ STL-style iterators. The iterators will be constant only though because UTF-8 is a variable-sized type. It wouldn't be possible to supply a mutable reference to any character. Because of this, the iterator will always return an unsigned 32-bit int type or wchar_t on systems that use a 32-bit wchar_t type. I'll implement it everything from scratch at first so you'll be able to see everything that's going on, but after that, I'll use more from the STL (Standard Template Library) to make it better.

To begin, let's start with the shell of the basic class. Over the next few days, I'll slowly transform this into a utf8string class that behaves as much like std::string as possible.

class mystring
{
 private:
  // this implementation will use a null-terminated string
  unsigned char * buffer;
  // this is the size of the buffer, not the string
  size_t   reserve_size;
  // keep the size of the string so we don't have to count it every time
  size_t   used_length;

  // this will resize the buffer, but it will not shrink the buffer is new_size < reserve_size
  void growbuffer(size_t new_size, bool copy_data = true)
  {
   if(new_size > reserve_size)
   {
    unsigned char *new_buffer = new unsigned char[new_size];

    if(used_length && copy_data)
    {
     // copy the buffer
     memcpy(new_buffer, buffer, used_length);
     new_buffer[used_length] = 0; // ensure null terminator
    }

    delete [] buffer;
    buffer = new_buffer;
    reserve_size = new_size;
   }
  }

  size_t recommendreservesize(size_t str_len)
  {
   return (str_len + 1) * 2;
  }

  // str_len is the number of 8-bit bytes before the null terminator
  void copystringtobuffer(const unsigned char *str, size_t str_len)
  {
   if(str_len >= reserve_size)
   {
    growbuffer(recommendreservesize(str_len), false);
   }
   memcpy(buffer, str, str_len);

   used_length = str_len;

   // set the null terminator
   buffer[used_length] = 0;
  }

 public:
  // default constructor
  mystring()
  :buffer(0L), reserve_size(0), used_length(0)
  {
   growbuffer(32, false); // set the string to an initial size
  }

  // build from a c string
  // undefined (ie crashes) if str is NULL
  mystring(const char *str)
  :buffer(0L), reserve_size(0), used_length(0)
  {
   copystringtobuffer((unsigned char *)str, strlen(str));
  }

  // build from a c string
  // undefined (ie crashes) if str is NULL
  mystring(const unsigned char *str)
  :buffer(0L), reserve_size(0), used_length(0)
  {
   copystringtobuffer(str, strlen((const char *)str));
  }

  // construct from an unsigned char
  mystring(const unsigned char c)
  :buffer(0L), reserve_size(0), used_length(0)
  {
   // set the string to an initial size
   growbuffer(32, false);
   buffer[0] = c;
   buffer[1] = 0;
   used_length = 1;
  }

  // construct from a normal char
  mystring(const char c)
  :buffer(0L), reserve_size(0), used_length(0)
  {
   // set the string to an initial size
   growbuffer(32, false);
   buffer[0] = (unsigned char)c;
   buffer[1] = 0;
   used_length = 1;
  }

  // copy constructor
  mystring(const mystring &str)
  :buffer(0L), reserve_size(0), used_length(0)
  {
   copystringtobuffer(str.buffer, str.used_length);
  }

  // destructor
  ~mystring()
  {
   delete [] buffer;
  }

  // cast to a c-string
  const unsigned char *c_str() const
  {
   return buffer;
  }

  // assignment operator
  // we can define assignment operators for all possible types such as char, const char *, etc,
  // but this is not neccessary. Because those constructors were provided, the compiler will be
  // able to build a mystring for those types and then call this overloaded operator.
  // if performance becomes an issue, the additional variations to this operator can be created
  mystring& operator= (const mystring &rvalue)
  {
   copystringtobuffer(rvalue.buffer, rvalue.used_length);

   return *this;
  }

  // move assignment operator
  // should move the data to this object and remove it from the old one
  mystring& operator= (mystring &&rvalue)
  {
   buffer   = rvalue.buffer;
   reserve_size = rvalue.reserve_size;
   used_length  = rvalue.used_length;

   // clear the values in the other string
   rvalue.buffer   = 0L;
   rvalue.reserve_size  = 0;
   rvalue.used_length  = 0;

   return *this;
  }

  // request a new buffer size
  // this will resize the buffer, but it will not shrink the buffer is new_size < reserve_size
  // not useful unless the actual size of the string is known
  void reserve(size_t new_size)
  {
   growbuffer(new_size, true);
  }

  // appends a string to the end of this one
  // we can define this operator for all possible types such as char, const char *, etc,
  // but this is not neccessary. Because those constructors were provided, the compiler will be
  // able to build a mystring for those types and then call this overloaded operator.
  // if performance becomes an issue, the additional variations to this operator can be created
  mystring& operator+= (const mystring& str)
  {
   size_t total_length  = used_length + str.used_length;
   if(total_length > reserve_size)
   {
    // resize the buffer
    reserve(recommendreservesize(total_length));
   }
   strcat((char *)buffer, (char *)str.buffer);
   used_length = total_length;

   // set the null terminator
   buffer[used_length] = 0;

   return *this;
  }

  // returns a reference to the character at the index
  // doesn't throw exception. undefined if out of range
  unsigned char &operator[](size_t pos)
  {
   return buffer[pos];
  }

  // returns a const reference to the character at the index
  // doesn't throw exception. undefined if out of range
  const unsigned char &operator[](size_t pos) const
  {
   return buffer[pos];
  }

  // returns a reference to the character at the index
  // will throw an exception if out of range
  unsigned char &at(size_t pos)
  {
   // check range
   if(pos >= used_length)
   {
    throw std::out_of_range("subscript out of range");
   }
   // use operator defined above, will help us later
   return (*this)[pos];
  }

  // returns a const reference to the character at the index
  // will throw an exception if out of range
  const unsigned char &at(size_t pos) const
  {
   // check range
   if(pos >= used_length)
   {
    throw std::out_of_range("subscript out of range");
   }
   // use operator defined above, will help us later
   return (*this)[pos];
  }

  // overload stream insertion so we can write to streams
  friend std::ostream& operator<<(std::ostream& os, const mystring& string)
  {
   os << string.c_str();

   return os;
  }

  // overload stream insertion so we can write to streams
  // we can define this operator for all possible types such as char, const char *, etc,
  // but this is not neccessary. Because those constructors were provided, the compiler will be
  // able to build a mystring for those types and then call this overloaded operator.
  // if performance becomes an issue, the additional variations to this operator can be created
  friend mystring operator + (const mystring& lhs, const mystring& rhs)
  {
   mystring out(lhs);
   out += rhs;

   return out;
  }
};



That's a very simple string class. It can do many of the things that std::string can do but not all of them.  Next I'll add some more things and give it more of an std::string feel.

For the full source code of this entry: UTF8 String Draft Header

I really want to thank Pete Goodliffe for inspiration. I've been meaning to make a UTF-8 string class for a long time now, but his article STL-style Circular Buffers By Example, made me want to go the extra mile with this.

-------------
For the other post in this series:

Writing a STL-Style UTF-8 String Class Part 1
Writing a STL-Style UTF-8 String Class Part 2
Writing a STL-Style UTF-8 String Class Part 3
Writing a STL-style UTF-8 String Class Part 4