2013年1月6日日曜日

nodejs addon サンプルコード

サーバサイドのJavaScriptエンジンであるnodeで遊ぶことがあったので、addon作成時にまとめたサンプルコードを貼り付けておきます。addonはC++で記述できるので、サンプルとしてまとめたのは

  • 引数+返り値のオーソドックスな関数呼び出し
  • 新しいオブジェクト(連想配列)を返す
  • 処理の途中でコールバックを呼び出す
  • 独自のC++クラスを使う
  • 独自のC++クラスにコールバックを設定して任意のタイミングで呼ぶ
  • libuvで非同期処理させる
です。サンプル本体はgithubにおいてあります。基本的には公式ドキュメントを参考にしているだけなので、必要に応じてそちらも御覧ください。あまり深く検証しているわけではないので、特にご利用は自己責任でお願いします。


#define BUILDING_NODE_EXTENSION
#include <node.h>
#include <unistd.h>
// 趣味。特にnamespaceを切る必要はない
namespace addon {
// ----------------------------------------------------------------------------
// 引数を受け取って、普通に結果を返すだけの関数
v8::Handle<v8::Value> Add(const v8::Arguments& args) {
v8::HandleScope scope;
// 引数はv8::Argumentsにまとめられている
// 引数の個数を見る時はargs.Length ()
if (args.Length() < 2) {
// ThrowExceptionだがここで例外を投げて抜けるわけではない。
// 例外のキューに入るイメージみたい
v8::ThrowException(v8::Exception::TypeError(v8::String::New("Wrong number of arguments")));
// 値を返さない時はUndefinedを返すのがお作法らしい
return scope.Close(v8::Undefined());
}
// 数値や文字列などはC++標準ではなく、それぞれv8独自の型を使う
// 型はわからないので、IsNumber()などを使って型を確認する
if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
// 期待する型じゃなかったらException投げる
v8::ThrowException(v8::Exception::TypeError(v8::String::New("Wrong arguments")));
return scope.Close(v8::Undefined());
}
// 足し算するだけ
// 返す値もv8の型にあわせる
v8::Local<v8::Number> num = v8::Number::New(args[0]->NumberValue() +
args[1]->NumberValue());
return scope.Close(num);
}
// ----------------------------------------------------------------------------
// 新しいオブジェクト(連想配列)を生成して返す
v8::Handle<v8::Value> CreateObject(const v8::Arguments& args) {
v8::HandleScope scope;
// v8::Object::New ()で作成する。
v8::Local<v8::Object> obj = v8::Object::New();
// Setを使う。連想配列のキーはNewSymbolで生成する
obj->Set(v8::String::NewSymbol("msg"), args[0]->ToString());
return scope.Close(obj);
}
// ----------------------------------------------------------------------------
// 処理が終わったらコールバックを呼び出す関数
v8::Handle<v8::Value> RunCallback(const v8::Arguments& args) {
v8::HandleScope scope;
// 本当はちゃんとargs[0]が関数かどうかチェックする必要あり
// v8::Functionにキャストする
v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(args[0]);
// コールバックに渡す引数を作成する
const unsigned argc = 1;
v8::Local<v8::Value> argv[argc] = {
v8::Local<v8::Value>::New(v8::String::New("hello world"))
};
// コールバック呼び出し
cb->Call(v8::Context::GetCurrent()->Global(), argc, argv);
return scope.Close(v8::Undefined());
}
// ----------------------------------------------------------------------------
// 独自クラスを使う
// node::ObjectWrapを継承する
class Blue : public node::ObjectWrap {
public:
// クラス名や関数名を登録するため、初期化関数を静的に用意しておく
static void init (v8::Handle<v8::Object> target);
private:
Blue () : count_(0) {}
~Blue () {}
// v8から呼び出される関数はすべて静的関数にする
// 引数const v8::Argumentsに固定
static v8::Handle<v8::Value> New (const v8::Arguments &args);
static v8::Handle<v8::Value> Five (const v8::Arguments &args);
static v8::Handle<v8::Value> Count (const v8::Arguments &args);
int count_;
};
// 初期化関数。v8のプロセスで一度しか呼び出されない(はず)
void Blue::init (v8::Handle<v8::Object> target) {
// FunctionTemplateを作成し、自前のオブジェクト生成関数New (newではない)を設定
v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(New);
// クラス名決める
tmpl->SetClassName (v8::String::NewSymbol ("blue"));
tmpl->InstanceTemplate ()->SetInternalFieldCount (1);
// メンバ関数も名前とFunctionで登録
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("Five"),
v8::FunctionTemplate::New (Five)->GetFunction ());
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("Count"),
v8::FunctionTemplate::New (Count)->GetFunction ());
// 最後にクラス生成の関数を登録
// 多分、ここのシンボル名とSetClassNameの名前は変えられると思うが、
// 混乱すると思うのであまりオススメできない&未検証
target->Set (v8::String::NewSymbol ("blue"),
v8::Persistent<v8::Function>::New(tmpl->GetFunction ()));
}
// クラス生成関数
v8::Handle<v8::Value> Blue::New (const v8::Arguments &args) {
v8::HandleScope scope;
// ここだけは普通にnew
Blue * obj = new Blue ();
// これを呼び出す必要がある
obj->Wrap (args.This ());
return args.This ();
}
v8::Handle<v8::Value> Blue::Five (const v8::Arguments &args) {
v8::HandleScope scope;
return scope.Close (v8::Number::New (5));
}
v8::Handle<v8::Value> Blue::Count (const v8::Arguments &args){
v8::HandleScope scope;
// node::ObjectWrap::Unwrap <クラス名> (args.This ()) することで、
// インスタンスを取り出すことができる
Blue * ao = node::ObjectWrap::Unwrap <Blue> (args.This ());
// 後は好きにごにょごにょいじる
ao->count_ += 1;
return scope.Close (v8::Number::New (ao->count_));
}
// ----------------------------------------------------------------------------
// 任意のタイミング(関数の終了時などではない)でコールバックを呼び出す
class Orange : public node::ObjectWrap {
public:
static void init (v8::Handle<v8::Object> target);
private:
static v8::Handle<v8::Value> New (const v8::Arguments &args);
static v8::Handle<v8::Value> SetCallback (const v8::Arguments & args);
static v8::Handle<v8::Value> RunCallback (const v8::Arguments & args);
// v8::Persistentでメンバ変数を用意するのが重要
v8::Persistent<v8::Function> callback_;
};
void Orange::init (v8::Handle<v8::Object> target) {
v8::Local<v8::FunctionTemplate> tmpl = v8::FunctionTemplate::New(New);
tmpl->SetClassName (v8::String::NewSymbol ("orange"));
tmpl->InstanceTemplate ()->SetInternalFieldCount (1);
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("set_cb"),
v8::FunctionTemplate::New (SetCallback)->GetFunction ());
tmpl->PrototypeTemplate ()->Set (v8::String::NewSymbol ("run_cb"),
v8::FunctionTemplate::New (RunCallback)->GetFunction ());
target->Set (v8::String::NewSymbol ("orange"),
v8::Persistent<v8::Function>::New(tmpl->GetFunction ()));
}
v8::Handle<v8::Value> Orange::New (const v8::Arguments &args) {
v8::HandleScope scope;
Orange * obj = new Orange ();
obj->Wrap (args.This ());
return args.This ();
}
v8::Handle<v8::Value> Orange::SetCallback (const v8::Arguments & args) {
v8::HandleScope scope;
Orange * orange = node::ObjectWrap::Unwrap <Orange> (args.This ());
if (args.Length () != 1 || !args[0]->IsFunction ()) {
v8::ThrowException (v8::Exception::TypeError (v8::String::New ("Invalid argument")));
return scope.Close (v8::Undefined ());
}
// v8::Persistent<v8::Function>::New でコールバック関数を保持するインスタンスを
// 生成しなければならない
orange->callback_ =
v8::Persistent<v8::Function>::New (v8::Local<v8::Function>::Cast(args[0]));
return scope.Close(v8::Undefined());
}
v8::Handle<v8::Value> Orange::RunCallback (const v8::Arguments & args) {
v8::HandleScope scope;
Orange * orange = node::ObjectWrap::Unwrap <Orange> (args.This ());
v8::Local<v8::Value> argv[1];
argv[0] = v8::String::New ("scar");
// コールバック関数を呼び出す時は、もう一度v8::Localに
v8::Local<v8::Function> cb = v8::Local<v8::Function>::New(orange->callback_);
// コールバック!
cb->Call (v8::Context::GetCurrent ()->Global (), 1, argv);
return scope.Close(v8::Undefined());
}
// ----------------------------------------------------------------------------
// libuvの機能を使った非同期処理
// データをやりとりするためのクラス
class Alice {
public:
v8::Persistent<v8::Function> callback_;
long long res_;
};
// 処理をする関数
void DoTask (uv_work_t *req) {
// 適当になんかやらせる
long long a = 1, p, q;
p = q = a;
for (long long n = 0; n < 10000000000; n++) {
a = p + q;
q = p;
p = a;
}
// req->dataをキャストすると、そのまま受け渡したインスタンスが見える
Alice * k = static_cast<Alice *> (req->data);
k->res_ = a;
}
// 処理終了後の後始末関数
void FinishTask (uv_work_t *req) {
v8::HandleScope scope;
Alice * a = static_cast<Alice *> (req->data);
v8::Handle<v8::Value> argv[1];
// 処理結果を受け取る
argv[0] = v8::Integer::New (a->res_);
v8::TryCatch try_catch;
a->callback_->Call(v8::Context::GetCurrent()->Global(), 1, argv);
// お片づけ
a->callback_.Dispose();
delete a;
delete req;
if (try_catch.HasCaught())
node::FatalException(try_catch);
}
// 処理開始(この関数が呼び出される)
v8::Handle<v8::Value> RunTask (const v8::Arguments& args) {
v8::HandleScope scope;
// uv_work_tを生成
uv_work_t *req = new uv_work_t;
// 結果をやりとりするためのクラスを生成
Alice * a = new Alice ();
// コールバック登録
a->callback_ = v8::Persistent<v8::Function>::New(v8::Local<v8::Function>::Cast(args[0]));
req->data = a;
// uv_queue_work()でキューに入れる。
uv_queue_work(uv_default_loop(), req, DoTask, FinishTask);
return scope.Close (v8::Undefined());
}
// == Initializer ========================================================
void Init(v8::Handle<v8::Object> target) {
// targetに関数のシンボル(JavaScript側から呼び出すときの名前)と関数テンプレート化された
// 関数を入れ込む
target->Set (v8::String::NewSymbol("add"),
v8::FunctionTemplate::New(Add)->GetFunction());
target->Set (v8::String::NewSymbol("createObj"),
v8::FunctionTemplate::New(CreateObject)->GetFunction ());
target->Set (v8::String::NewSymbol("runCallback"),
v8::FunctionTemplate::New(RunCallback)->GetFunction());
target->Set (v8::String::NewSymbol("async_task"),
v8::FunctionTemplate::New(RunTask)->GetFunction());
Blue::init (target);
Orange::init (target);
}
// 初期化。"addon"という名前のアドオンになる
// 二番目の引数は初期化
NODE_MODULE(addon, Init);
}
view raw gistfile1.cpp hosted with ❤ by GitHub

2013年1月5日土曜日

「オンラインゲームを支える技術 --壮大なプレイ空間の舞台裏」読んだ

新年読書大会第二弾。オンラインゲームを支える技術 --壮大なプレイ空間の舞台裏を読みました。別にゲームを作りたいとかではあまりないのですが、オンラインゲームはよくやるので、興味本位で読んでみました。ちなみに、ここではソシャゲのように非同期で連携するものではなく、リアルタイムでユーザ間のインタラクションがあるものが想定されています。

IT技術中級者〜上級者向け


内容としてはオンラインゲームの歴史にはじまり、どのような企画ができるか、インフラ(ネットワーク、計算機)の見積りはどのようにすればいいか、どのようなアーキテクチャが考えられるか、パフォーマンスを維持するにはどうすればいいか、運用はどうすればいいか…、というようなことがかなり広くざっくりと書かれています。「ざっくりと」というのは決して適当に書かれているという意味ではないのですが、それぞれの話の粒度がまちまちな印象でした。新しいゲームを作るための企画段階の話から、具体的なコードはどのように書けるか、というような話まで、かなり雑多に入り交じっている感じでした。

これはどういうことなのかというと、おそらく「君はそれなりにIT技術者として経験積んでいるつもりかもしれないが、オンラインゲーム業界に行こうと思ったらここに書かれていることぐらいは押さえておいてね!」ということなのかと思いました。自分はCSの基礎は嗜む程度にやっていますが、あまりエンジニアよりの職種ではないで、ゲームならではの視点みたいなところで「なるほど」と思うところは多かったです。

先に書いた通り、この本ではリアルタイムでのオンラインゲームを重視しており、最近のソシャゲの隆盛を考えると今後どうなるかよくわかりませんが、その方面に就職・転職を考えている人は一読して、ここでのべられている能力(技術・政治・金勘定)が網羅できているか考えるとよかったりするのでは、とか思ったりします。・・・実際どうかは本職の人に聞いてみたいところですが。

「デザイニング・インターフェース ―パターンによる実践的インタラクションデザイン」読んだ



正月休みを利用して、デザイニング・インターフェース ―パターンによる実践的インタラクションデザインをざーっと読みました。
だいぶ前に買って積み本になり、引越しするときに実家に放置してきたのですが、UIを作る機会がちょこちょこ出てきたのと、実家に帰る&休みができるというイベントが重なったので、一気に走り読みしました。

基本的には事例集


プログラミングにおけるデザインパターンと同じように、UIのデザインをする際にどのような鉄板手法があるか? ということをまとめた本です。数が多いのですが、「グローバルナビゲーション(全頁に共通して定常的に使えるナビをおく)」や「動的なメニュー項目(必要に応じて項目を有効化・無効化・追加・削除)」のように様々なアプリケーションで使われている手法なので、言われてみれば「そりゃそうだね」と容易に理解できるものですが、いざ自分がそれを使おうとしたとき、各機能がどのような特性を持つのか?ということが良くまとまっていました。

おそらく、プロのUIデザイナーの方々は経験則として知っていることばかりなのだと思いますが、UIデザインが本職ではないんだけど、UIデザインをしなければいけなくなった! というような人が、どのような機能をどう組み立てていけばいいのか、この機能はどういう状況なら有効なのかを調べつつ使うのに適しています。本の中には9つのデザインに対する考え方がそれぞれ概論で示され、その後個別の100個弱のパターンが細かく解説されています。よくデザイン解説のWebページなどでは直感的に解説されていることが、(少なくとも自分の知っている範囲では)体系的に説明されているため、わかりやすく、自分がそれを使おうと思った時にも安心感があります。

UIデザインの素人だけどUIを作らねば、という人は手元にあるといいのでは


自分のように特にデザイン能力が高くないんだけどUIを作らないといけない場合、手元1冊において辞書的に使うのがよさそうな良書だと思います。UI(ユーザとインタラクション)がある部分に特化しているので、カッコイイ感じのデザインができるようになるというわけではないんですが、定石がまとまっているので、使いやすいさの向上には大いに役立ちそうです。