cocos2dでpollingを楽しく

ときどき、どうしてもSubversionリポジトリを比較的短時間にチェックしたい時があります。月額ン万円でホスティングSubversionだけじゃないけど)してもらってるのにhooksは一切いじれないとか、いじれたとしてもこちらがNATで囲まれてて growl の通知が届かないとか。多少のタイムラグがあってもコミットの通知が出ると楽しいのに・・・そんな時はポーリングするしかありません。

とりあえず作ってしまうポーリング

とりあえず動けばいい物は time.sleep() で待ってしまえばいいと思います。でも、せっかく作ったのに、せっかくイカしたコメント付けてコミットしたのに、通知まだー、とかいう気持ちになります。今回、このポーリングしているマシンがみんなの目の届く所にあるので、次回実行までの待ち時間を無駄に分かりやすく表示してみることにしました。

さっそく完成品

とはいってもそんなに凝ったことばかりしても仕方が無いので、シンプルな物にしておきます。

こんな感じで、人が左端に歩いて到達したら1回実行です。素材は FirstSeedMaterial ( http://www.tekepon.net/fsm/ )の物を使わせてもらっています。

# -*- coding: utf-8 -*-
#
import cocos
import pyglet

from pyglet import image

import tiled2cocos

pyglet.clock.set_fps_limit(30)

class PollingLayer(cocos.layer.Layer):
    def __init__(self):
        super(PollingLayer, self).__init__()

        af1 = image.AnimationFrame(image.load('image1.png'), 0.25)
        af2 = image.AnimationFrame(image.load('image2.png'), 0.25)
        af3 = image.AnimationFrame(image.load('image3.png'), 0.25)
        am = image.Animation([af1, af2, af3, af2])

        sprite = cocos.sprite.Sprite(am)
        self.add(sprite)
        self.polling_1(sprite)

        sprite = cocos.sprite.Sprite(am)
        self.add(sprite)
        self.polling_2(sprite)

    def polling_1(self, sprite):
        sprite.position = 320 + 16, 24
        sprite.do(cocos.actions.MoveTo((-16,24), 10) + 
                  cocos.actions.CallFuncS(self.polling_1))

    def polling_2(self, sprite):
        sprite.position = 320 + 16, 24 + 48
        sprite.do(cocos.actions.MoveTo((-16,24 + 48), 6) + 
                  cocos.actions.CallFuncS(self.polling_2))

if __name__ == "__main__":        
    cocos.director.director.init(320, 48 * 2)
    polling_layer = PollingLayer()

    map = tiled2cocos.load_map("8x4.tmx")
    map.set_view(0, 0, map.px_width, map.px_height)

    main_scene = cocos.scene.Scene(map, polling_layer)
    cocos.director.director.run(main_scene)

image1.png〜image3.pngについて

上記サイトからキャラクターのpngファイルを取得し、32x48ピクセルで適切な歩きパターンを切り出して背景色を透明にしたファイルです。正直、画像の加工は gimp の説明を見よう見まねでなぞっただけなので、出来上がったのが不思議です。背景以外の物も透明になっている可能性があるくらいです。

8x4.tmxとtiled2cocosについて

ここが本題?で、サンプルの背景にある殺風景な草原のための物です。素材を公開しているサイトではとても魅力的なイメージがあるのですが、残念ながら当方には使いこなすだけのセンスがありません。

それはともかく、「タイルセット」とされている大きいpngファイルをダウンロードします。
次に使うのは Tiled Map Editor ( http://www.mapeditor.org/ ) です。ここでは新しいマップを幅8タイル、高さ4タイルで作製します。ファイル名はここから来ていますね。サイズは適当に大きければいいと思います。

続いて、「マップ」メニューから「新しいタイルセット」を選択して、先ほどの「タイルセット」のpngファイルを指定します。サイズは32x32です。

あとはノリで何とかなると思いますが、保存する前に設定で出力形式をxmlにしておきます。

ただ、保存するときの拡張子は .tmx じゃないと都合が悪いみたいです。あと、8x4としたからには、8x4のマスを全部何かで埋めて下さい。次の tiled2cocos が動きません。

最後にその tiled2cocos ですが、 https://bitbucket.org/maikg/tiled2cocos/overview にあった物です。ステキですね。

ポーリングの間隔は?

cocos.actions.MoveTo の第二引数です。この「移動する」アクションの次のアクションが「関数を呼び出す」アクションです。呼び出し先でアクションをかけ直すようになっていますが、もしかしたら最初から Repeat とか上手く使えればいいのかもしれません。

最後に

ポーリングという割にはCPUリソースを消費するので、最終的には稼働させない方がいいかもしれません orz

たぶんフレームレートが高すぎるんだと思いますが、COMMAND+xでレートを表示すると 60fps とか出ます。30にしてるつもりなのに。そんなに要らないし、最小化したり他のウインドウで覆うと途端にCPU使用率が跳ね上がり、100fps 以上を記録した形跡が見えます。vsyncか何かかなぁ・・・。

tweepyのインストール方法によってtweepyが使うoauthが変わる

Ubuntu使い始めて、調子にのって apt-get install python-tweepy とかやってたら、そのtweepyパッケージにはoauthモジュールが含まれおらず、依存関係で解決されるpython-oauthのモジュールを使用することにようやく気がつきました。

tweepyの配布元がoauth.pyを含めて配布しているんだから、わざわざ依存関係にこだわらなくても良いじゃない?


と思うのは年をとったから?

Lionでport install emacs-app

そのうちなおると思うし
https://trac.macports.org/ticket/30268
にある通りなんですが、具体的にどうすればいいのか調べる気もなかったので行き当たりばったりに。

  • まず普通に sudo port install emacs-app してみる。
  • 失敗する。
  • 上記リンクから .diff ファイルを3つダウンロードする。
patch-Portfile.diff
patch-fix-title-bar.diff
patch-no-pie.diff

置き場はここ。

/opt/local/var/macports/sources/rsync.macports.org/release/tarballs/ports/aqua/emacs-app/files
  • patch 当てる(一つだけ)。

こんな感じか。

cd /opt/local/var/macports/sources/rsync.macports.org/release/tarballs/ports/aqua/emacs-app
sudo patch < files/patch-Portfile.diff
  • リビルドする。

どこかから拾ってきた実行例。

sudo port -n upgrade --force emacs-app

出来上がり。

文字列データをパックされたバイナリデータとして解釈するもう一つの方法

http://www.python.jp/doc/nightly/library/struct.html

このモジュールは、 Python の値と Python 上で文字列データとして表される C の構造体データとの間の変換を実現します。

との事なのですが、Cの構造体データとの変換にしては、フォーマット文字列がステキにコンパクトすぎます。実用的なCの構造体データは入れ子になっています。そんなにフラットな物じゃないし、このままではpackもunpackも結構めんどうです。実際使おうとすると途方に暮れます。

そのまま自分が使いやすいように何かで包んであげる手もありますが、Cの構造体データなのでctypesを使います。構造体の定義はおおむねこんな感じ。Cのヘッダファイルからエディタのマクロで編集できそうです。

from ctypes import *

class HEADER(Structure):
    _fields_ = [
        ('len',  c_char * 8),
        ('code', c_char * 7),
        ('type', c_char),
        ('flg',  c_char),
        ]

class DATA(Structure):
    _fields_ = [
        ('header',  HEADER),
        ('len',  c_char *  9),
        ('data', c_char *  256),
        ]

使うときはこんな感じ。

from ctypes import *

data = DATA()
buff = ' ' * sizeof(data)
memmove(addressof(data), buff, sizeof(data))

わ、なんか気持ち悪いね。間違うと簡単にセグメンテーション違反を起こします。

数値とか日本語とか扱ったらどうなるのかは今回必要がないので試してません。

ThreadingMixinなSocketServerの最大数

Python mini Hack-a-son ではプレゼンの方向性が違うような気がしたのでここで。

http://www.python.jp/doc/nightly/library/socketserver.html

にある通りのサンプルで、スレッドを使用したsocket通信のサーバは簡単にできてしまうんですが、調子に乗ってスレッドを作っていくと上限がないので破綻します。Forkするほうは何故か上限値が設定されています。なので、スレッドの方にも同じように上限値を設定します。

#!/bin/env python
# -*- coding: utf-8 -*-
#
import sys
import SocketServer
import threading

class Server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    def __init__(self, server_address, RequestHandlerClass, event_mapping=None):
        if not issubclass(RequestHandlerClass, Handler):
            raise TypeError
        SocketServer.TCPServer.__init__(self,
                                        server_address,
                                        RequestHandlerClass)
        self.max_children = threading.Semaphore(5)
        self.event_mapping = event_mapping
        self.reload = True

    def process_request(self, request, client_address):
        """Start a new thread to process the request."""
        self.max_children.acquire()
        t = threading.Thread(target = self.process_request_thread,
                             args = (request, client_address))
        if self.daemon_threads:
            t.setDaemon (1)
        t.start()

    def finish_request(self, request, client_address):
        """Finish one request by instantiating RequestHandlerClass."""
        if self.reload:
            m = reload(sys.modules[self.RequestHandlerClass.__module__])
            self.RequestHandlerClass = \
                getattr(m,
                        self.RequestHandlerClass.__name__)
        self.RequestHandlerClass(request, client_address, self)

class Handler(SocketServer.BaseRequestHandler):
    def __del__(self):
        self.server.max_children.release()

あと、こういうのってハンドラを修正するたびに再起動ってしたくないと思うので reloadするようにしてみました。毎回w

「それじゃ駄目なんだよ」というのがあったら誰か教えてください。CentOS 5.6(Python 2.4.3)で試験しています。