VMの設定
Wren VMを作るとき、WrenConfiguration構造体へのポインターを渡して、設定を調整します。Wrenにはグローバルな状態がないので、アプリケーションで複数のVMを実行する場合、それぞれを別々に設定できます。
構造体は、次のようになっています。
typedef struct
{
WrenReallocateFn reallocateFn;
WrenLoadModuleFn loadModuleFn;
WrenBindForeignMethodFn bindForeignMethodFn;
WrenBindForeignClassFn bindForeignClassFn;
WrenWriteFn writeFn;
WrenErrorFn errorFn;
size_t initialHeapSize;
size_t minHeapSize;
int heapGrowthPercent;
} WrenConfiguration;
ほとんどのフィールドには、有用な既定値があります。次を呼び出して初期化できますし、そうするべきです。
wrenInitConfiguration(&configuration);
これを呼び出すことで、WrenConfigurationへ新しいフィールドを追加しても、VMへ未初期化の設定を渡さずに済みます。各フィールドの働きを、大まかに分類して説明します。
束縛 #
VMは外の世界から隔離されています。これらのコールバックを使い、VMはインポートするコードや外部機能へのアクセスを要求できます。
loadModuleFn #
インポートしたモジュールを読み込むためにWrenが使うコールバックです。VM自身は、ファイルシステムとのやり取りを知りません。そのため、import文を実行するとき、ホストアプリケーションに頼って、モジュールのソースコードを探し、読み込みます。
この関数のシグネチャは、次のとおりです。
WrenLoadModuleResult loadModule(WrenVM* vm, const char* name)
モジュールをインポートするとき、Wrenは、この関数を呼び出し、モジュール名を渡します。ホストは、そのモジュールのソースコードをWrenLoadModuleResult構造体で返すべきです。
WrenLoadModuleResult myLoadModule(WrenVM* vm, const char* name) {
WrenLoadModuleResult result = {0};
result.source = getSourceForModule(name);
return result;
}
モジュールローダーは、一つのモジュール名につき一度だけ呼び出します。Wrenは内部で結果をキャッシュするので、同じモジュールの後続のインポートでは、すでに読み込んだコードを使います。
ホストアプリケーションが、指定した名前のモジュールを読み込めない場合、戻り値のsourceをNULLにします。Wrenは、それを実行時エラーとして報告します。
import文を使わない場合、設定のloadModuleFnフィールドは、既定値のNULLのままにできます。
さらに、WrenLoadModuleResultには、Wrenがsourceを使い終えたときのコールバックを追加できます。必要なら、メモリーを解放するために使えます。
static void loadModuleComplete(WrenVM* vm,
const char* module,
WrenLoadModuleResult result)
{
if(result.source) {
//for example, if we used malloc to allocate
//our source string, we use free to release it.
free((void*)result.source);
}
}
WrenLoadModuleResult myLoadModule(WrenVM* vm, const char* name) {
WrenLoadModuleResult result = {0};
result.onComplete = loadModuleComplete;
result.source = getSourceForModule(name);
return result;
}
bindForeignMethodFn #
Wrenが外部メソッドを探し、クラスへ束縛するために使うコールバックです。詳細はこのページを参照してください。アプリケーションが外部メソッドを定義しない場合、NULLのままにできます。
bindForeignClassFn #
Wrenが外部クラスを探し、その外部メソッドを取得するために使うコールバックです。詳細はこのページを参照してください。アプリケーションが外部クラスを定義しない場合、NULLのままにできます。
診断 #
これらを使って、最小限の出力を結び付け、コードが期待どおりに動いているか確認できます。
writeFn #
System.print()や関連するほかの関数を呼び出したときに、テキストを出力するためにWrenが使うコールバックです。VMと外の世界との最小限の接続であり、基本的な「printfデバッグ」を行えます。そのシグネチャは、次のとおりです。
void write(WrenVM* vm, const char* text)
Wrenには、この既定の実装はありません。printf()など、テキストを表示する手段へ結び付けるのは、利用者の責任です。NULLのままにすると、System.print()などの呼び出しは、何もせず、出力もありません。
errorFn #
Wrenは、このコールバックでコンパイル時エラーと実行時エラーを報告します。そのシグネチャは、次のとおりです。
void error(
WrenVM* vm,
WrenErrorType type,
const char* module,
int line,
const char* message)
type引数は、次のいずれかです。
typedef enum
{
// A syntax or resolution error detected at compile time.
WREN_ERROR_COMPILE,
// The error message for a runtime error.
WREN_ERROR_RUNTIME,
// One entry of a runtime error's stack trace.
WREN_ERROR_STACK_TRACE
} WrenErrorType;
コンパイルエラーが起きると、errorFnを一度呼び出します。種類WREN_ERROR_COMPILE、エラーが起きたモジュール名と行、エラーメッセージを渡します。
実行時エラーには、スタックトレースがあります。これを扱うため、Wrenは、まずerrorFnを、WREN_ERROR_RUNTIME、モジュールと行なし、実行時エラーのメッセージを使って呼び出します。その後、種類WREN_ERROR_STACK_TRACEを使い、スタックトレースの各行につき一度、errorFnを再び呼び出します。各呼び出しには、メソッドや関数を定義したモジュールと行があり、messageには、メソッドや関数の名前を渡します。
NULLのままにすると、Wrenは、どのエラーも報告しません。
メモリー管理 #
これらのフィールドは、VMによるメモリーの割り当てと管理を制御します。
reallocateFn #
独自のメモリー割り当て関数を指定できます。そのシグネチャは、次のとおりです。
void* reallocate(void* memory, size_t newSize, void* userData)
Wrenは、この一つの関数を使って、メモリーの割り当て、拡大、縮小、解放を行います。割り当てを変更したり解放したりする場合、呼び出し時のmemoryは、そのメモリーブロックへの既存のポインターです。新しいメモリーを要求する場合、memoryはNULLです。
newSizeは、要求するメモリーのバイト数です。メモリーを解放する場合、これは0です。コールバックは、適切な量のメモリーを割り当て、それを返すべきです。
独自のアロケーターを指定しなければ、VMは、reallocとfreeを使う既定のものを使います。
initialHeapSize #
最初のガベージコレクションを起動する前に、VMが割り当てるメモリーの総バイト数を定義します。小さな数値にすると、Wrenが一度に割り当てるメモリー量は減りますが、ガベージコレクションの頻度は増えます。
0に設定すると、Wrenは、既定の10MBを使います。
minHeapSize #
ガベージコレクションの後、次の回収のしきい値は、使用中として残ったバイト数に基づいて決めます。これにより、実際に必要なメモリー量に応じて、Wrenは、自動でメモリー使用量を増減できます。
ヒープが小さくなりすぎないようにするために使えます。小さすぎると、使えるサイズまで再びヒープが大きくなる過程で、何度も回収が起きる場合があります。
0の場合、既定値は1MBです。
heapGrowthPercent #
Wrenは、回収後にまだ使っているメモリー量に基づき、ガベージコレクションの頻度を調整します。この数値で、その調整を制御します。回収後に追加で使うメモリー量を、現在のヒープサイズに対する割合で決めます。
例えば、50とします。ガベージコレクション後、まだ400バイトを使っています。この場合、合計600バイトを割り当てた時点で、次の回収を起動します。(すでに使用中の400バイトも含みます。)
小さな数値にすると、無駄なメモリーは減りますが、ガベージコレクションの頻度は増えます。
0に設定すると、VMは、既定値の50を使います。