Ti.UI.Viewでパーティクルベンチマーク

Ti.UI.Viewで点を描いて動かすとどれくらいFPSがでるか計測してみた。
以下がソース。
実行すると黒い点が画面上を飛び回ります。

var window = Titanium.UI.createWindow({  
    backgroundColor:'#fff'
});

var view_num = 10;
var points = [];
for( var i = 0; i < view_num; i++){
    var point = Ti.UI.createView({
        backgroundColor : 'red',
        width: 2,
        height: 2,
        center: window.center
    });

    var sign = (parseInt(Math.random()*10)%2) ? 1 : -1;
    point.vx = sign * parseInt(Math.random()*10);
    var sign = (parseInt(Math.random()*10)%2) ? 1 : -1;
    point.vy = sign * parseInt(Math.random()*10);

    points.push( point );
    window.add( point );
}

var fps_text = Ti.UI.createTextField({
    value : '0 fps',
    left : 0,
});

window.add( fps_text );

function render(){
    for( var i = 0; i < points.length; i++){
        var point = points[i];
        var newx = point.center.x + point.vx;
        var newy = point.center.y + point.vy;

        if( newx <= 0 ){
            newx = 0;
            point.vx *= -1;
        }
        else if( window.width <= newx ){
            newx = window.width;
            point.vx *= -1;
        }

        if( newy <= 0 ){
            newy = 0;
            point.vy *= -1;
        }
        else if( window.height <= newy ){
            newy = window.height;
            point.vy *= -1;
        }

        point.center = { x : newx,
                         y : newy };
    }
}

window.open();

var last_time = new Date().getTime();
var elapsed_time = 0;
var framecount = 0;
setInterval( function(){
    render();
    var now = new Date().getTime();
    elapsed_time += (now - last_time);
    last_time = now;
    framecount++;
    if( 990 <= elapsed_time ){
        fps_text.value = '' + framecount + ' fps';
        framecount = 0;
        elapsed_time = 0;
    }
}, 33);
  • iPhone3GSだと8個描いて30fps切る感じ。
  • Ti.UI.Animationを使えばもっと速くなる気がしないでもないが、FPSの測り方が分からない。。。

ndkでビルドするときのオプションメモ

$export ANDROID_ROOT=android-ndk-r4b/build
$export PATH=android-ndk-r4b/build/prebuilt/darwin-x86/arm-eabi-4.4.0/bin
$./configure --host=arm-eabi CPP=arm-eabi-cpp CC=arm-eabi-gcc CFLAGS="-mandroid --sysroot=$ANDROID_ROOT/platforms/android-3/arch-arm" CPPFLAGS="-I$ANDROID_ROOT/platforms/android-3/arch-arm/usr/include"

参考:
http://blog.kmckk.com/archives/2918551.html
http://warpedtimes.wordpress.com/2010/02/03/building-open-source-libraries-with-android-ndk/

GPSとネットワークと実機デバッグを有効にする

Androidで開発するときはAndroidManifest.xmlにどの機能を有効にするのか書く必要があるっぽい

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example"
      android:versionCode="1"
      android:versionName="1.0">
      
     <!-- debuggable=trueを書かないと実機デバッグができない -->
    <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="true">
        <activity android:name=".MainActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
    
    <uses-sdk android:minSdkVersion="3" />
    
     <!-- GPSを使用する。 これを書かないとセキュリティ例外になる -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <!-- GPSを使用する。 これを書かないとセキュリティ例外になる -->
     <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
     <!-- HTTPで通信する。 これを書かないとセキュリティ例外になる -->
    <uses-permission android:name="android.permission.INTERNET"/>
</manifest> 

C++でnode.jsのaddonを書いてみた

addonを書く上で必要なことは基本ここに書いてあるんだけど今のバージョン(v0.23)だと若干便利マクロが増えてるみたい。

今回は渡された文字列とカウンタを表示するだけの簡単なクラスを作った。 JavaScriptmoduleで書くとこんな感じ

function Echo(){ this.i = 0 }
Echo.prototype.print = function(s){
    console.log( s + ' ' + this.i );
    this.i++;
}

exports.Echo = Echo;

同じことをC++のaddonで書くと以下のような感じ
※重要そうなところはコメントで。

echo.h:

#ifndef _ECHO_
#define _ECHO_

#include <node.h>
#include <v8.h>

using namespace v8; 
using namespace node;
// node.js が用意しているObjectWrapを継承することで自作のクラスもGC対象にする
class Echo : ObjectWrap { 
    public:
        // node.jsにメソッドを登録するためにstaticにしてる
        // インスタンスを作る関数はNewにするのが通例っぽい
        static Handle<Value> New(const Arguments& args);
        static Handle<Value> Print(const Arguments& args);
        Echo() : count(0) {}; 
        ~Echo();
    private:
        int count;
        void print(const char *s);
};

static void AddonInitialize( Handle<Object> target );
#endif

echo.cc:

#include <v8.h>
#include <node.h>
#include <iostream>
#include "echo.h"

using namespace node;
using namespace v8; 
using namespace std;

Handle<Value> Echo::New(const Arguments& args){
    HandleScope scope; // v8のお約束っぽい。 このscopeがないとどうなるのかよくわからない。。。

    Echo *p = new Echo();
    p->Wrap(args.This()); // thisとEchoのインスタンスを対応付ける

    return args.This();
}

Handle<Value> Echo::Print(const Arguments& args){
    HandleScope scope;
    if( args.Length() != 1 || !args[0]->IsString() ){
        // 引数がおかしい場合は例外を投げる
        return ThrowException(Exception::Error(String::New("Bad argument.")));
    }   

    Echo *o = ObjectWrap::Unwrap<Echo>(args.This());
    // char* を取り出すためにUtf8Valueに変換。
    // この辺はv8のクラスリファレンスを読むと色々わかるかも
    String::Utf8Value str(args[0]->ToString()); 

    o->print(*str);

    return Undefined();
}

void Echo::print(const char* s){ 
    cout << s << " " << count << endl;
    count++;
}

Echo::~Echo(){
    // デストラクタはnode.js側でGCが起きると呼ばれる
    cout << "delete" << endl;
}

void AddonInitialize( Handle<Object> target ){
    HandleScope scope;

    // node.jsでnew Echoされたときの関数を登録
    Local<FunctionTemplate> t = FunctionTemplate::New(Echo::New);
    // ObjectWrapを継承してる場合必要なお約束
    t->InstanceTemplate()->SetInternalFieldCount(1);
    // console.log等で表示するクラス名の登録。無くても大丈夫。
    t->SetClassName(String::NewSymbol("Echo"));

    // Echo.prototype.print に Echo::Printを登録
    NODE_SET_PROTOTYPE_METHOD(t, "print", Echo::Print);

    // exports.Echo に Echoクラスを登録
    target->Set(String::New("Echo"), t->GetFunction());
}

// addon名(echo)と初期化関数(AddonInitialize)を登録
NODE_MODULE(echo, AddonInitialize);

wscript:

srcdir = '.'
blddir = 'build'
VERSION = '0.0.1'

def set_options(opt):  opt.tool_options('compiler_cxx')
def configure(conf):
  conf.check_tool('compiler_cxx')
  conf.check_tool('node_addon')

def build(bld):
  obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
  obj.target = 'echo'
  obj.source = 'echo.cc'

上記3つのファイル(echo.h, echo.cc, wscript)を同じディレクトリに置いてnode-wafコマンドを実行するとコンパイルされてbuild/default以下にecho.nodeという名前でaddonが作成される。

node-waf実行例:

bash$ ls
echo.h  echo.cc  wscript

bash$ node-waf
Waf: Entering directory `/home/bongole/test_addon/build'
[1/2] cxx: echo.cc -> build/default/echo_1.o
[2/2] cxx_link: build/default/echo_1.o -> build/default/echo.node
Waf: Leaving directory `/home/bongole/test_addon/build'
'build' finished successfully (0.491s)

addonを実際に使ってみるには、同じディレクトリに以下のようなJavaScriptのファイルを作ってnodeコマンドで実行する。

test.js:

var Echo = require('./build/default/echo').Echo;
var o1 = new Echo();
o1.print("hello");
o1.print("world");

var o2 = new Echo();
o2.print("hello");
o2.print("again");

o1 = null;
o2 = null; 

// GCが発生するまで待つ
setTimeout(function(){}, 10000);

test.jsの実行例:

bash$ ls
build  echo.h  echo.cc  wscript  test.js

bash$ node ./test.js
hello 0
world 1
hello 0
again 1
delete
delete

ちゃんと別々のインスタンスになってるし、GCでデストラクタが呼ばれてるみたい。

まとめ:

  • node.jsのaddon書くにはv8の知識が結構必要
  • でも作法を覚えればそんなに難しくない!

参考URL:

今すぐwebサーバーが必要な人のためのワンライナー

カレントディレクトリにあるファイルを今すぐHTTP経由でブラウザに表示させたいと思ったら

ruby -rwebrick -e 's = WEBrick::HTTPServer.new(:Port=>8888, :DocumentRoot=>Dir.pwd);trap("INT"){s.shutdown};s.start'

を実行して http://localhost:8888/hoge.html をブラウザに打ち込め!