Hatena::ブログ(Diary)

スコトプリゴニエフスク通信 このページをアンテナに追加 RSSフィード

2012-02-02

[][][]Jasyプロジェクトを作成する

前提条件

Python 3.2とpip、それにJasy 0.4.6がインストールされているものとします。

$ pip install https://github.com/wpbasti/jasy/zipball/0.4.6

ディレクトリ構成

最初にプロジェクトのトップディレクトリを作る。ここでは設定ファイルに記載するプロジェクト名にあわせてmyprojとした。git initは任意だが、coreがないとJasyアプリケーションは実行できないので、git submoduleとして参照しておくと便利。もちろんダウンロードして自分のレポジトリ内に含めてしまったり、hg等の別のVCSの方が好みならばgit submoduleに相当する手段で参照しても可。

$ mkdir myproj
$ cd myproj
$ git init

sourceというディレクトリを作り、さらにその下にclassとassetというディレクトリを作る。class以下がJavaScriptファイルの置き場所で、assetが画像ファイルやCSSファイルの置き場所。ディレクトリ名はsource/class, source/assetそのままを推奨。これはこのディレクトリ名がJasyが期待しているディレクトリ名であるということによる。

$ mkdir -p source/{class,asset}

最初に述べたようにCoreに依存しているので、external/coreというディレクトリにセバスチャンのgithubレポジトリへの参照を張った。このexternal/coreというパスに関しては後に述べるjasyscript.pyの中の設定を変えれば変更可。

$ git submodule add git://github.com/wpbasti/core.git external/core
$ cd external/core
$ git checkout 0.3.1 # 重要!!

重要な点としてはcoreのmasterは今のところ動かないので、0.3.1というタグが打ってあるバージョンを使うこと。submoduleではなくて0.3.1のzipを拾ってきたほうがよいかも。

次に、jasyproject.jsonというJSONファイルをプロジェクトのトップディレクトリに作り、nameにプロジェクト名を記述する。ここではmyprojとした。この名称は後々JavaScriptモジュール、クラスを定義していく際に、myproj.module1.Modelのような名前空間に使うので、org.example.my.great.appみたいな長い名称を付けると、コーディングの手間になるばかりかJavaScript実行時のパフォーマンスにも影響が出ると思う。短めでドットを含まないものが良いと思う。

{
    "name": "myproj"
}

ここまでのディレクトリ構成を示す。

.
├── external
│  └── core
├── jasyproject.json
└── sou
    ├── asset
    └── class

HTML

source/index.htmlに以下のようなHTMLを追加。

<!DOCTYPE HTML>
<html>
<head>
  <title>My First Jasy App</title>
</head>
<body>
  <h1>Hello Jasy</h1>
  <div id="content"></div>
  <script type="text/javascript" src="loader.js"></script>
  <script type="text/javascript">core.io.Script.load("myproj-" + core.Env.CHECKSUM + ".js");</script>
</body>
</html>

何の変哲もないHTMLファイルだが、scriptの部分だけに関してポイントを述べると、

  • loader.jsというのがアプリ起動に最小限必要なbootstrapコード
    • loader.jsはJasyで自動生成される。
    • 後に述べるjasyscript.pyの中で変更すればloader.jsでなくても可。
  • core.io.Script.loadでアプリケーションの実体のスクリプトを読み込む。
    • この実体スクリプトはJasyで自動生成される。

JavaScript

source/class/App.jsに以下のようなJavaScriptを追加。

core.Class('myproj.App', {
    construct: function() {
        alert("Hello Jasy");
    }
});

jasyscript.py

プロジェクトのトップディレクトリにjasyscript.pyという名前で以下のようなPython3スクリプトを作る。

#!/usr/bin/env jasy

def getSession():
    session = Session()

    # 依存しているJasyプロジェクトのディレクトリを指定
    session.addProject(Project("external/core/"))
    session.addProject(Project("."))

    return session

@task
def clean():
    logging.info("Clearing cache...")
    session = getSession()
    session.clearCache()
    session.close()

@task
def build():
    session = getSession()

    # プロジェクトのビルド設定
    session.setField("es5", True)
    session.permutateField("debug")

    # Asset(画像, CSS等)の設定
    resolver = Resolver(session.getProjects())
    resolver.addClassName("myproj.App")
    assets = Asset(session, resolver.getIncludedClasses()).exportBuild()

    # 起動スクリプトをbuild/loader.jsに出力
    includedByKernel = storeKernel("build/loader.js", session, assets=assets)

    # Asset以外に必要な静的ファイルをビルドディレクトリにコピーする
    for staticFile in ["index.html"]:
        updateFile("source/%s" % staticFile, "build/%s" % staticFile)

    # 最適化オプションを指定
    optimization = Optimization("variables", "declarations", "blocks", "privates")
    formatting = Formatting()

    # 起動後に最初に実行されるスクリプトを指定
    bootCode = "new myproj.App();"

    # 設定ごとにbuild/myproj-{{ hexdiget }}.jsというファイルを出力する
    for permutation in session.getPermutations():

        # 依存しているクラスを解決
        resolver = Resolver(session.getProjects(), permutation)
        resolver.addClassName("myproj.App")
        resolver.excludeClasses(includedByKernel)

        # 必要なクラスのみ圧縮して書き出し
        classes = Sorter(resolver, permutation).getSortedClasses()
        compressedCode = storeCompressed("build/myproj-%s.js" % permutation.getChecksum(), classes,
            permutation=permutation, optimization=optimization, formatting=formatting, bootCode=bootCode)

    session.close()

作成したら実行権限を付与してビルドを実行。

$ chmod +x jasyscript.py
$ ./jasyscript.py build

もしくは、Jasyをインストールしたときに同時にインストールされるjasyコマンドで、

$ jasy jasyscript.py build

でもOK。以下のようなビルドのログは出力されたあと、buildディレクトリに生成物ができているはず。

% ./jasyscript.py build
>>> Jasy 0.4.6
>>> Initialized project core
>>> Initialized project myproj
>>> Detecting dependencies...
>>> Publishing files...
>>> Updated 0/0 files
>>> Detecting dependencies...
>>> Sorting classes...
>>> Compressing 13 classes...
>>> Detected 2 possible permutations
>>> Detecting dependencies...
>>> Sorting classes...
>>> Compressing 11 classes...
>>> Detecting dependencies...
>>> Sorting classes...
>>> Compressing 6 classes...
>>> Closing session...

build/index.htmlをブラウザで開けば、App.jsに書いたスクリプトが実行される。最終的なディレクトリ構成は以下のとおり。

.
├── build
│├── index.html
│├── loader.js
│├── myproj-b490a0744.js
│└── myproj-b4f8b078f.js
├── external
│└── core
├── jasycache
├── jasyproject.json
├── jasyscript.py
└── source
    ├── asset
    ├── class
    └── inde.h

よくある質問と回答

jasycacheって何ですか

JasyがJavaScriptのソースを解析するときに作るキャッシュファイルです。レポジトリに含める必要はありません。.gitignoreやsvn:ignoreでレポジトリからは外しましょう。

jasyscript.py buildで"Invalid indention in doc string at line 25"のようなエラーが出ます

アルファ版のJasy 0.5を使っているためです。しばらくJasy 0.4.6を使う方が無難です。

どうしても最新版を使いたい人は治るのを気長に待ちましょう。もしくはPush Requestを送りましょう。

shelveの"dbm.error: db type could not be determined"というエラーが出ます

Macのgdbmのバージョンの問題だと考えられます。Python 3.2の次のリリースで修正されると思いますが、それを待てない人は以下のエントリを参照してください。

[][][]Jasyを使ってJavaScriptアプリケーションを作成する - 最適化

Jasy/Coreの使い方をなんとなく語るシリーズの第一回。順番が間違っている気がするが、第一回はJasyのJavaScript最適化機能について。

解説の順番が間違っている気がするが、Python3.2とpipをインストールした上で、

$ pip install https://github.com/wpbasti/jasy/zipball/0.4.6
$ git clone git://github.com/csakatoku/my-jasy-sandbox.git
$ cd my-jasy/sandbox/helloworld
$ jasy jasyscript.py build

ビルドを行なっていただければ、Jasyがどんなものか何となく分かると思う。


Jasyは"variables", "declarations", "blocks", "privates"の4つの最適化オプションを提供している*1。それらがどのようなものかをざっと説明する。僕は"variables"と"privates"オプションが気に入っていて、あえて地雷を踏みに行くJasyを使うメリットの一つだと思っている。

なお以下の例にあるJasy適用後のJavaScriptコードは著者によって読みやすいようにインデントして、コメントを追加してあります。実際の出力は無駄なホワイトスペースとコメントはすべて除去されます。

privates

__で始まるプロパティやメソッドを、以下の__fRv76ように一意でランダムなプロパティ名に書き換えてくれる。ちなみに、Jasyでは__で始まるプロパティやメソッドはプライベート扱いになり、これらのプロパティ、メソッドに外からアクセスしようとするとビルド時にエラーになる。

最適化前。

core.Class("p.helloworld.App", {
  construct: function(settings) {
    $("#content").html(this.sayHello());
  },

  members: {
    __name : 'Jasy',

    sayHello: function() {
        return "Hello, " + this.__name + "!";
    }
  }
});

Jasy適用後。

core.Class("p.helloworld.App", {
  construct: function() {
    $("#content").html(this.sayHello())
  },
  members: {
    __fRv76: "Jasy", // プロパティ名がランダムでユニークな文字列になっている
    sayHello: function() {
      return "Hello, " + this.__fRv76 + "!"
    }
  }
});

blocks

ifブロックを二項演算子を使って短縮してくれる機能。

適用前。

core.Class("p.helloworld.App", {
  construct: function(settings) {
    var __spam = "spam";
    var __egg = "egg";

    if (__spam) {
      console.log(__spam);
    } else {
      console.log(__egg);
    }
  }
});

適用後。

core.Class("p.helloworld.App", {
  construct: function() {
    var __spam = "spam";
    var __egg = "egg";
    __spam ? console.log(__spam) : console.log(__egg) // ifが消えた
  }
});

variables

ローカル変数を短縮してくれる機能。

適用前。

core.Class("p.helloworld.App", {
  construct: function(settings) {
    var __spam = "spam";
    var __egg = "egg";

    if (__spam) {
      console.log(__spam);
    } else {
       console.log(__egg);
    }
  }
});

適用後。

core.Class("p.helloworld.App", {
  construct: function() {
    var a = "spam"; // 長い変数名がaに変わっている
    var b = "egg";
    if (a) {
      console.log(a)
    } else {
      console.log(b)
    }
}});

declarations

変数宣言を短縮してくれる機能。

core.Class("p.helloworld.App", {
  construct: function(settings) {
    var __spam = "spam";
    var __egg = "egg";

    if (__spam) {
      console.log(__spam);
    } else {
      console.log(__egg);
    }
  }
});

適用後。

core.Class("p.helloworld.App", {
  construct: function() {
    var __spam = "spam", __egg = "egg"; // varがひとつ減っている
    if (__spam) {
      console.log(__spam)
    } else {
      console.log(__egg)
    }
  }
});

全部適用

全部の最適化を適用すると元の236バイトのスクリプトが170バイトになった。

core.Class("p.helloworld.App",{construct:function(){$("#content").html(this.sayHello())},members:{__fRv76:"Jasy",sayHello:function(){return"Hello, "+this.__fRv76+"!"}}});

Jasyで最適化できないこと

デッドコード削除は現状できていないし、ロードマップにあるのかも分からない。例えば、少しトリビアルな例だが、

function f() {
  if (0) {
    return "spam";
  } else {
    return "egg";
  }
}

は、if(0)が絶対成立しないので、

function f() { return "egg" }

と同等であるがJasyはこの部分を除去してくれない。

ただ、開発版でのロギングといったコードの削除ならば、多少冗長なのだが、core.Env.isSet("debug") で条件分岐を作ればリリース版では除去することができる。

function f() {
  if (core.Env.isSet("debug")) {
    // 開発版だけで行う処理
    console.log("very very verbose debug log");
  }
  // 何らかの処理
  alert("hello world");
}

適用結果。

function f() {
  alert("hello world");
}

[][][]Jasyを使ってJavaScriptアプリケーションを作成する - セバスチャン現る

JasyはZyngaの俊英Sebastian WernerによるJavaScriptビルドツール(Python3製)。coreは同氏によるJavaScript Framework。

coreが大規模なJavaScriptアプリケーションを作るためのモジュール機構を提供し、jasyはcoreを使ったアプリケーションをビルドしたり、Asset(画像やCSSといったリソースファイル)を管理したりするという役割をになっている。単独でも使えるかもしれないが、そうするとあまりメリットはない。

個人的にはPython3を使った実用に耐えうるアプリケーションなんて初めて見たいので、びっくりした。

たいへん注目に値するプロジェクトなのだが、ドキュメントが本当になく、セバスチャン*2の頭の中にしかロードマップがなくて本当に泣ける。しかしながら、このZyngaで最も面白いゲームの魅力を世の中の人達に知ってほしいと思い、これからしばらく僕の苦闘を語ろうと思う。

  1. Jasyプロジェクトを作成する
  2. 最適化

苦闘は今も以下のレポジトリで続いています。

*1:4.6 stable, 5.0 alpha現在

*2:全然面識がないの勝手にファーストネームで呼んでいる

2011-12-11

[][]LZOとSnappyの圧縮効率・処理速度を比較する

LZOSnappyは共に、圧縮効率は他に劣るものの、処理速度が早いことを売りにした圧縮ライブラリ。

インストール

Pythonで使うならば、LZOに関しては python-lzo-static を使うの楽なのだろうか?

$ hg clone https://bitbucket.org/james_taylor/python-lzo-static
$ cd python-lzo-static
$ pythnon setup.py install

SnappyPyPIに登録されているので、もしSnappyがインストールされているならば*1

$ pip install python-snappy

で行ける。

検証

検証には青空文庫の芥川龍之介『トロッコ』のテキスト(10263byte)を使った。

検証スクリプト。

# -*- coding: utf-8 -*- 
import timeit
import zlib
import bz2
import lzo
import snappy

content = open('torokko.txt').read()
print 'original content length', len(content)
                                                                                                                        
for lib in (zlib, bz2, lzo, snappy):
    timer = timeit.Timer('lib.compress(content)', 'from __main__ import lib, content')
    result = timer.timeit(number=1000)

    compressed = lib.compress(content)
    print lib.__name__, result, len(compressed), ('%0.4f' % (len(compressed) / float(len(content))))

結果。

original content length 10263
zlib 0.546717882156 5270 0.5135
bz2 4.33483314514 4857 0.4733
lzo 0.130795001984 6950 0.6772
snappy 0.0607089996338 7133 0.6950

すなわち、

圧縮ライブラリ 処理時間 圧縮率
zlib 0.546717882156 0.5135
bz2 4.33483314514 0.4733
lzo 0.130795001984 0.6772
snappy 0.0607089996338 0.6950

のようになった。

timeitの使い方ってこれでいいんだっけ?という感じで自信がないのだが、もし上記計測結果が正しいならば、

  • 圧縮効率はbz2, zlib, lzo, snappyの順番で良いが、bz2とzlibの間の差、lzoとsnappyの間の差は微差。bz2, zlibのグループとlzo,snappyのグループの間の差は有意な差がある。
  • 圧縮速度はsnappy, lzo, zlib, bz2の順番で良く、それぞれの間に有意な差がある。

と言ってよいのではないだろうか。

次の課題

LZOとSnappyの処理速度の差は圧倒的に見えるが、LZOの存在意義って何だろうかを考える。こことかにLZO圧縮を使うMemcacheクライアントライブラリとかがあるが、こいつの存在意義って何だろうか考える。

*1:例えばdebianの場合は、sudo apt-get install libsnappy-dev

2011-03-02

[]全俺が泣いた - チラシの裏

『【GDC2011】ジンガが振り返る『FarmVille』から『CityVille』で得た教訓』という記事が面白いです。

『CityVille』は昨年8月頃に開発が始まったものの、明確なビジョンが定まらないままかなりの迷走があったそうです。一時は買収したオースティンのChallenge Gamesに開発を任せようという話もあったようです。

社内で開発が継続され9月には既に遊べるバージョンが出来ましたが、決して面白いと言えるものではなかったそうです。明確なビジョンがないため、チームメンバーから寄せられる様々な意見が盛り込まれては要素が多いだけのものになっていたそうです。そこで企画書から脱却し、プロトタイピングに集中することで収拾していき、9月末に「見る」という要素が入り、徐々に改善していったそうです。友達の街を見たり、ヘルプをしてくれる友達が見える、といったものです。

徐々に開発は進展していきます。10月、11月とゲームは良くなっていきます。当初のリリースは11月の予定でしたが、ジンガは延期する決断をします。Skaggs氏は「リリース後は全てが困難になるから」と理由を説明します。ソーシャルゲームはリリース後も改善できると言われますが、同氏は「戦いはリリース時には決まっている」とコメント。完成度を高めておく必要性を語りました。

http://www.inside-games.jp/article/2011/03/01/47635.html

強調は引用者による。

全俺が泣いた。むしろ全米が泣いてもおかしくない。映画化決定してもおかしくない(タイトルは「The Social Game」)。むしろ、ゲーム化決定してもおかしくない。