HEXA BLOG

その他プログラム

HEXA BLOGその他2015.4.9

天使の手稿

オッスオラマイキー

 

来月、大阪でオクトーバーフェストが開催されるという事で、「5月にオクトーバーフェストとはこれ如何に」と若干モヤモヤしつつも楽しみにしている今日この頃でございます

 

 

今日はスクリプト言語であるAngelScriptについて少し書いてみようかと思います。

とりあえず、言語の概要と、導入を少し。

 

AngelScriptはLuaやRuby、JavaScriptと同様「スクリプト言語」や「グルー言語」と呼ばれるものなんですけれども。
数多ある他のスクリプト言語と比較した際、どういった特徴があるかと言いますと、

  • 高速な動作
  • C++ライクな構文
  • 言語レベルでバインド機能が搭載されている
  • 変数の静的型付け

等が挙げられるんじゃないでしょうか。
「高速な動作」は言うまでも無く大きなメリットですし、「C++ライクな構文」は導入コストの削減に一役買ってくれます。
また、「言語レベルでバインド機能が搭載されている」のも大変有難く、言語のバージョンとバインダの対応バージョンとでヤキモキする必要が無くなります。

 

特に私が注目したいのは「変数の静的型付け」という所で、コンパイル時に型チェックが行われますので、変数型に関するエラーを実行前に検知する事が出来ます。
静的型付けとは逆に「動的型付け」は、その名の通り実行時、動的に変数の型付け含め、その他諸々の処理を行います。
この為、コンパイルを必要としませんから開発のテンポを上げることが出来る反面、開発規模が大きくなると品質を維持する為の難易度が大きくなる印象です
「型に注意してコーディングすればデメリットとならない」と言う意見もネット界隈では散見されますが、誰しもバグらせようとおもってバグらせていない訳で、「注意していれば問題ない」というのはいささか楽観的ではないか?と個人的に思います

 

さてさて前置きが長くなってしまいましたが、そろそろ導入&実装に移りたいと思います。
まずはSDKを入手します。
本家サイトの「Library download」のページから、適当なバージョンのSDKをダウンロードし、解凍して下さい。
中身は各種ドキュメントとプロジェクトファイル、そしてソースコードになっています。
今回はソースコードを直接プロジェクトに取り込んで使用したいと思いますので、以下のディレクトリに含まれるファイルのみ、使用したいと思います。
 ・sdk\angelscript\include\
 ・sdk\angelscript\source\
今回は使用していませんが、必要に応じてアドオンのソースコードもプロジェクトに追加しましょう。
たとえば、配列を使用する場合は「sdk\add_on\scriptarray」以下のファイルを追加する事で、配列を使用する事が出来るようになります。

 

今回は簡単に、
 ・C++からスクリプト内で定義されている関数を呼び出す
 ・C++で定義されている関数をバインドし、スクリプトから呼び出す
という所を目標にしていきます。
まずは、スクリプトコードです。

float testFunction(float lhs, float rhs)
{
	return sumFunction(lhs, rhs);
}

「sumFunction」という関数を呼び出しているだけの「testFunction」という関数が定義されています。
続いてプログラム側のソースコードです。

// スクリプトにバインドする関数
float sumFunction(float lhs, float rhs)
{
	printf("execute "__FUNCTION__".");
	return (lhs + rhs);
}
// sumFunction関数のジェネリック版
void sumFunctionGeneric(asIScriptGeneric *pGen)
{
	printf("execute "__FUNCTION__".");

	float* pLhs = reinterpret_cast<float*>(pGen->GetArgAddress(0));
	float* pRhs = reinterpret_cast<float*>(pGen->GetArgAddress(1));

	pGen->SetReturnFloat(sumFunction(*pLhs, *pRhs));
}

// コンパイル時のメッセージコールバック関数
void MessageCallback(const asSMessageInfo *pMsg, void *pParam)
{
	if( pMsg == NULL ) {
		printf("invalid args.");
		return;
	}
	const char *type = "ERROR";
	if( pMsg->type == asMSGTYPE_WARNING ) {
		type = "WARNINIG";
	} else if( pMsg->type == asMSGTYPE_INFORMATION ) {
		type = "INFORMATION";
	}
	printf("%s (%d, %d) : %s : %s\n", pMsg->section, pMsg->row, pMsg->col, type, pMsg->message);
	assert(pMsg->type != asMSGTYPE_ERROR);
}

// スクリプトのコンパイル
int CompileScript(asIScriptEngine *pEngine, const char* pFilePath, const char* pModuleName, const char* pSectionName)
{
	if( !pEngine || !pFilePath || !pModuleName || !pSectionName ) {
		printf("invalid args.");
		return -1;
	}

	int len(0);
	string script;
	{ // スクリプトファイルを開く
		FILE *pFile = NULL;
		errno_t err = fopen_s(&pFile, pFilePath, "rb");
		if( err != 0 || !pFile ) {
			printf("Filed to open script file [%d : %s]\n", err, pFilePath);
			return -1;
		}

		// ファイルサイズを取得
		fseek(pFile, 0, SEEK_END);
		int len = ftell(pFile);
		fseek(pFile, 0, SEEK_SET);

		// スクリプトファイルを読み込む
		script.resize(len);
		int c =	fread(&script[0], len, 1, pFile);
		fclose(pFile);
		if( c == 0 ) {
			printf("Filed to read script file [%d : %s]\n", err, pFilePath);
			return -1;
		}
	}

	// スクリプトモジュールとセクションの作成
	asIScriptModule *pModule = pEngine->GetModule(pModuleName, asGM_CREATE_IF_NOT_EXISTS);
	if( pModule == NULL ) {
		printf("Filed to create module.");
		return -1;
	}
	// スクリプトコードを読み込み
	int result(pModule->AddScriptSection(pSectionName, &script[0], len));
	if( result < 0 ) {
		printf("Filed to AddScriptSection().[%d]", result);
		return result;
	}
	// コンパイル。エラーがあればasIScriptEngine::SetMessageCallbackで設定された関数が呼ばれる
	result = pModule->Build();
	if( result < 0 ) {
		printf("Filed to Build().[%d]", result);
		return result;
	}

	return 0;
}

// メイン関数
int main(void)
{
	// スクリプトエンジンを生成
	asIScriptEngine *pEngine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
	if( pEngine == 0 ) {
		printf("Failed to create script engine.\n");
		return 0;
	}

	// スクリプトコンパイラメッセージコールバック関数の設定
	int result(pEngine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL));
	if( result == asINVALID_ARG || result == asNOT_SUPPORTED ) {
		printf("Failed to Regist compiler message callback.\n");
		return 0;
	}

	// C++で実装した関数をスクリプトにバインド
	if( !strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") ) {
		// ネイティブ
		result = pEngine->RegisterGlobalFunction("float sumFunction(float lhs, float rhs)", asFUNCTION(sumFunction), asCALL_CDECL); assert( result >= 0 );
	} else {
		// ジェネリック
		result = pEngine->RegisterGlobalFunction("float sumFunction(float lhs, float rhs)", asFUNCTION(sumFunctionGeneric), asCALL_GENERIC); assert( result >= 0 );
	}

	// スクリプトのコンパイル
	if( CompileScript(pEngine, "test.as", "TestProj", "sumFunction") < 0 ) {
		printf("Failed to Compile script.\n");
		pEngine->Release();
		return 0;
	}

	// スクリプトコンテキストの作成。コンテキストはスクリプトの実行単位で必要
	asIScriptContext *pCtx = pEngine->CreateContext();
	if( !pCtx ) {
		printf("Failed to create the context.\n");
		pEngine->Release();
		return 0;
	}

	// 関数を探す
	asIScriptFunction *pFunc = pEngine->GetModule("TestProj")->GetFunctionByDecl("float testFunction(float lhs, float rhs)");
	if( pFunc == NULL ) {
		printf("Function not found.\n");
		pCtx->Release();
		pEngine->Release();
		return 0;
	}
	// Prepareはスクリプトの関数を呼び出す前に必ず実行する必要アリ。
	// 連続して実行する場合、これをスキップすることが出来る
	result = pCtx->Prepare(pFunc);
	if( result != 0 ) {
		printf("Failed to prepare the context.\n");
		pCtx->Release();
		pEngine->Release();
		return 0;
	}

	// 引き数をセット
	result = pCtx->SetArgFloat(0, 5.0f);
	if( result != 0 ) {
		printf("Failed to set arments.[%d]\n", result);
		pCtx->Release();
		pEngine->Release();
		return 0;
	}
	pCtx->SetArgFloat(1, 2.0f);
	if( result != 0 ) {
		printf("Failed to set arments.[%d]\n", result);
		pCtx->Release();
		pEngine->Release();
		return 0;
	}

	// Prepareで設定したスクリプト関数を実行
	result = pCtx->Execute();
	if( result != asEXECUTION_FINISHED ) {
		// スクリプトが通常終了しなかった場合。
		if( result == asEXECUTION_ABORTED ) { // 強制終了
			printf("Script aborted.");
		} else if( result == asEXECUTION_EXCEPTION ) { // 例外
			printf("Script threw Exception.\n");
			asIScriptFunction *func = pCtx->GetExceptionFunction();
			printf("Detail [%s / %s] %s[%d] %s\n"
				,func->GetDeclaration()
				,func->GetModuleName()
				,func->GetScriptSectionName()
				,pCtx->GetExceptionLineNumber()
				,pCtx->GetExceptionString());
		} else if( result == asEXECUTION_SUSPENDED ) { // 中断
			int line = pCtx->GetExceptionLineNumber();
			printf("Script suspend at L %d.\n", line);
		} else {
			printf("The script ended for some unforeseen reason (%d).\n", result);
		}
	} else {
		// 通常終了
		float returnValue = pCtx->GetReturnFloat();
		printf("Script returned %f.\n", returnValue);
	}

	// スクリプトが実行できたらコンテキストを開放する。
	pCtx->Release();
	// アプリケーション終了時はエンジンを開放。
	pEngine->Release();

	return 0;
}

ひとまず、これで先述した2つの要件は満たされています。
最低限動作させるためのコード量はやや多めかな?という印象。

しかし、AngelScriptにはクラスのバインドや便利なデバッグ機能等が実装されており、非常に多機能です。
また、先述したようにバインダが標準で搭載されていますので、それを差し引けば十分実用レベルかなと思います。

 

機会があればまたTips等の記事が書ければなーと思います。

 

それではまた!

RECRUIT

大阪・東京共にスタッフを募集しています。
特にキャリア採用のプログラマー・アーティストに興味がある方は下のボタンをクリックしてください

RECRUIT SITE