ど~なっつ、時を司るプログラミング言語。

Posted on

あけましておめでとうございます!!今年は友人のサークルで売り子をしていたのですが、なんと完売御礼でした!!やったねたえちゃん!(謎

さて元日も過ぎて餅にも飽きてきた頃かと思いますので、半年間掛けて未踏IT人材育成事業で開発していたもののうち、なんとか外に出せそうなスクリプト言語部分について紹介します!

ど〜なっつ、時を司るスクリプト言語

20120102

ど~なっつは

  • 処理系とその外界の状態を過去に戻し、未来に戻せ、
  • 任意の時点でのセーブデータを書き出し、後で実行を再開できる、

ちょっと変わった「時を司るプログラミング言語」です!

時を司るプログラミング言語

普通、プログラミング言語って上から下に実行されて、起こった事は元に戻りません。副作用ってやつです。覆水盆に返らず。

int main(){
    int x = 10; //このときxは10。
    printf("x is %d", x);
    x = 20; //これ以降、ずっと20で、以前10だった痕跡は一切残らない。
    printf("x is %d", x);
    return 0;
}

タイムリープでフロー制御。

でも、戻りたい時だってあるかもしれません。例えば、ユーザーのログイン処理なんかそうでしょうか。普通のプログラミング言語、例えばRubyだと、こんな感じでパスワード認証を書くでしょうか?

while true do
    puts "パスワードを入力して下さい!"
    passwd = gets
    if passwd == "正しいパスワード" then
        puts "認証完了"
        break
    end
    puts "間違ったパスワードです"
end

puts "こんにちは、正しいユーザーさん!"

すっきり書けました。

でも…。やりたい事は

  • 正しいパスワードが入力されるまで繰り返し

ではなくて、

  • 正しいパスワードが入力されたらログインしていいけど、そうじゃなかったら門前払い

なのではないでしょうか?「ど~なっつ」では、こんな感じで同じプログラムを書けます!

//このあとの命令を使って、この時点まで時を戻せるようになります。
save_time=Homura.tick();

System.print("パスワードを入力してください。");
//パスワードを読み込みます。
passwd=System.readline();

if(passwd == "abcdef"){
  //パスワードの認証に成功しました。
  System.println("こんにちは!正しいユーザーさん。");
} else {
  // 上で記録した時間まで、スクリプトエンジン全体を戻します。
  System.println("パスワードが間違っています!");
  Homura.seek(save_time);
};

「Homura」というオブジェクト1を使って、パスワードが間違っていたときはログイン処理前にまでタイムリープさせてしまいます。変数、コールスタック、全ての「環境」が文字通り「戻ります」。

これで不自然な繰り返しを使わずにユーザーログイン処理が書けてしまうのです!(ドヤァ

私は何度でも繰り返す。

gotoでも同じように書けますが、変数も全て巻き戻されるので、安全に巻き戻す事ができます。この事を利用すると、「時が戻る事を利用したループ」がつくれます。普通に起こすと無限ループになりそうですが、ほむらオブジェクトに突っ込んだ値はタイムリープ前の事を覚えているので、コレを使います。交わした約束忘れないよ

//普通の変数は、時間操作の影響を受けますが…
tabeta=0;

//Homuraは時間操作前の事を覚えていて、時間操作の影響を受けません。
Homura.counter=0;

//このあとの命令を使って、この時点まで時を戻せるようになります。
save_time=Homura.tick();

if(Homura.counter < 10){
  // ドーナッツを食べましょう。
  // この足し算の結果は、時間操作で戻ってしまいます。
  tabeta++;

  // ドーナッツを食べた回数を表示しましょう
  System.println(tabeta, "番目のドーナッツを食べた!");

  // Homuraに入った値は時間操作の影響を受けないので、
  // 時間操作を行なってもこの足し算の結果は戻りません。
  Homura.counter+=1;

  // 上で記録した時間まで、スクリプトエンジン全体を戻します。
  Homura.seek(save_time);
} else {
  //ど~なっつでは、else節を省略することはできません。
};

これを実行すると、

% donut time_op.donut
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!
 1番目のドーナッツを食べた!

このように何度ループをめぐっても1番めのドーナッツを食べ続けることができますが、Homuraは時間操作の影響を受けないので、無限ループには陥らずに済みます。

「継続」と違って スクリプト外部への副作用も制御できる

こういったループはSchemeのcall/ccやRubyのFiberような「継続」を使えば記述することができます。しかし、「継続」のアプローチでは戻せないものがあります。それは、言語処理系の環境です。

つまり、Scheme内から外界(たとえばウィンドウシステム)に、「ウインドウを表示する」と言った副作用を起こしていた場合、「ウインドウを表示する」以前の継続に戻っても、戻るのはSchemeの中でだけであって、ウインドウは表示されたままになってしまいます2

「ど〜なっつ」では言語処理系外に起こした副作用も、「副作用」に対応した「反副作用」みたいなものを登録して管理させることができます。時間操作が行われた瞬間にその「反副作用」を用いて副作用を打ち消させることができます。

でも、「反副作用」を作れない場合もありますよね?例えば、ネットワークでデータを送信するような操作は絶対取り消せませんから、「反副作用」は作れません。そのときにその時刻以前には戻れないようにする機構も作られています。コンソールへの出力も取り消せませんから、今回使ってるSystem.printlnなども本当は取り消せないメソッドとして登録すべきなのですが、副作用のないメソッドとして登録されています。HaskellのUnsafeIOみたいな感じです。

普通にスクリプト言語

関数も使えますし、関数はクロージャーですし、第一級オブジェクトです。JavaScriptのような感じで使えます。

f = func(idx, acc){
    if( idx <= 0 ){
        acc; //最後に評価された値が戻り値になる
    }else{
        return f(idx-1, acc+idx); //戻り値を明示することもできます。
    };
};
System.println(f(10, 0)); // prints 55

またプロトタイプ型オブジェクト指向言語で、__proto__にオブジェクトを指定するとJSのようにプロトタイプを巡ってくれます(この辺のドキュメントも書かなきゃ…)。全体的にはJavaScript: The Good Parts ―「良いパーツ」によるベストプラクティスを参考に設計されています。

 

まだまだ開発中で、リポジトリも未踏でのプロジェクトと同じリポジトリに入っていて大変使いづらいとは思うのですが、テンションを上げるために公開してみました。

ダウンロード

ダウンロードはど~なっつのサイトからお願いします!githubでのソースは本体プロジェクトの「ちさ」と同じ所に入ってます。

ライセンス

ど~なっつのライセンスはGPLv3となっています。

他に開発中のもの

他に開発中の本体プロジェクト「ちさ」は、このど~なっつをネイティブで使用して、時を司り、さらに任意の時点での実行状態を保存できるGUIツールキット兼電子コンテンツエンジンです。が、まだあんまり出来てないので公開はまだしません。

Thanks

ど~なっつはIPAの未踏IT人材発掘・育成事業に採択された「CPUの理解を容易にするシステムと解説サイトの構築」の一部分として開発され、支援を受けています。

 

  1. もちろんアレです []
  2. もちろんアドホックに実装した場合は除きます []

クリスマスはNoiz2saをChromeで遊ぼう!

Posted on

Native Clientは死んだんだ
いくら呼んでも帰っては来ないんだ
もうあの時間は終わって、君も人生と向き合う時なんだ


弾幕の海にたゆたう、アブストラクトシューティングNoiz2sa。

ChromeにNoize2saを移植してみた

Google ChromeにNoiz2saを以前移植して公開してあったのですが、こちらでは書きそびれてたので、クリスマスというタイミングに書くことにします。

ABA GamesのSTG “Noiz2sa”をブラウザで遊べるようにしてみました。

20121224

Chrome ウェブストア – noiz2sa

 ChromeのNative Clientという仕組みを使っていて、これを使うと比較的簡単にネイティブのゲームをブラウザ上で動かすことが出来るようになります!

GamepadのAPIがうまく動かないので残念ながらゲームパッドでは動く動かないのですが、キーボードでならガリガリ動くのでJoyToKeyでも使ってやってみてください!…だったら普通にWindows版で遊ぶ?そ、そうですよね…。

特に何もしてないけどクロスプラットフォーム

NativeClientは、何もしなくてもクロスプラットフォームに対応していて、今回の拡張もLinux/Windows/MaxOSXで動きます。ちょうべんりー。ちなみに開発自体はLinuxでやってます。

NativeClientのしんどいところ

NativeClientはセキュリティ上の要請から、色々制限があります。普通に計算をさせる分には困らないですし、グラフィックやサウンドの出力も、NaCl Portsがあるお陰で普通に使う分には困りませんが、ファイルアクセスやネットへの接続などでは独自のライブラリを使わなければなりません。

今回ゲームということでネットワークは使っていないのですが、タイトルの「noiz2sa」のロゴ画像(ビットマップ)や弾幕の定義データ(XML)、BGMや効果音のファイルなど、比較的数は少ないもののファイルアクセスがあり、これに対処する必要がありました。

ファイルはすべてゲームバイナリ内に

Native ClientにはURLLoaderというクラスがあるので、これを使えば任意のURLの内容を取得できます。これをローカルファイルの代わりに使えば良さそうです…が、今回はゲームなのでローディングを避けたいのと、大した容量でもない(8MBくらい)なので、全部バイナリ内に入れてしまうことにしました。

具体的にいうと…

struct FileEntry{
const char* filename;
const size_t size;
const char* data;
};
extern const struct FileEntry gFileImages[];
extern const size_t gNumberOfFileImages;

Noiz2saNaCl/nacl/file_data.h at master ・ ledyba/Noiz2saNaCl ・ GitHub

というような構造体をつくって、

static const char ImageData[8810309] = [具体的なデータ>];
const size_t gNumberOfFileImages=96;
const struct FileEntry gFileImages[96] = {
{
.filename="boss/forward_3way.xml",
.size=818,
.data=&ImageData[0],
},
(ファイルのエントリをたくさん)
};

Noiz2saNaCl/nacl/file_data.c at master ・ ledyba/Noiz2saNaCl ・ GitHub

として全データを格納します。

これを読み書きするためのfopen、fread、fcloseなどの関数を独自に定義して

extern size_t nacl_ftell (FILE *file);
extern int nacl_fseek (FILE *file, size_t offset, int whence);
extern FILE *nacl_fopen (const char *filename, const char *modes);
extern size_t nacl_fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
extern size_t nacl_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
extern int nacl_fclose(FILE *fp);
extern int nacl_feof(FILE *stream);
extern int nacl_ferror(FILE *stream);
extern void nacl_rewind(FILE *stream);
extern int nacl_fgetc(FILE *stream);
extern int nacl_fflush(FILE *stream);

最後にマクロで普通のfopenを乗っ取ります

#define fopen(n,m)  nacl_fopen(n,m)
#define fclose(f) nacl_fclose(f)
#define ftell(f) nacl_ftell(f)
#define fseek(f,o,w) nacl_fseek(f,o,w)
#define fread(p,s,n,f) nacl_fread(p,s,n,f)
#define fwrite(p,s,n,f) nacl_fwrite(p,s,n,f)
#define feof(f) nacl_feof(f)
#define ferror(f) nacl_ferror(f)
#define rewind(f) nacl_rewind(f)
#define getc(f) nacl_fgetc(f)
#define fgetc(f) nacl_fgetc(f)
#define clearerr(f) nacl_clearerr(f)
#define fileno(f) -1
#define fflush(f) nacl_fflush(f)

Noiz2saNaCl/nacl/storage.h at master ・ ledyba/Noiz2saNaCl

ソースコードを改変できるので別に乗っ取る必要はないのですが、NativeClientの元の記事がそういう感じで乗っ取ってたのでそうしときました。

ディレクトリ内のファイルを列挙するAPIであるreaddirなどもおんなじ感じで乗っ取っております。

この方法はGoogle先生謹製の記事を参考にしていて、ソースも一部拝借してます。Case Study: Porting MAME to Native Client – Native Client — Google Developers

さてどうしたものか

NativeClient、ファイルが絡むと一気にめんどくさくなります。既存の技術(Flashとか)を置き換えるには少し不便ですね。

WordPressに移行した&支援スクリプト書いた

Posted on

諸事情でWordPressをインストールして別箇所で使っていたのですが、これは素晴らしいですね!!!
何が素晴らしいって、もう

  • UIは使いやすいし!
  • アップデートは自動だし!
  • 固定ページとかの編集も簡単だし!
  • テンプレートを簡単に変更できるし!
  • プラグインは豊富だし!
  • かわいいテンプレート一杯あるし!
  • かわいいテンプレートがたくさんあるし!
  • かわ(ry

もう移行しない理由とかないよねーないよねー、ということでちょっと重い腰を上げて移行!することにしました。言語別にパッケージが分かれてるのは正直ダサいと思いますが、不満はそれぐらいです。

はてな記法を捨てたい。

はてな記法のプラグインを使ってて依存していたため移行をためらっていたのですが、今回この負の連鎖を断ち切るべく、プラグインの変換エンジン部分を適当に引っ張ってきて、エクスポートしたデータのはてな記法の部分を変換する(!)スクリプトを書きました。

  • github: ledyba/mt-hatena-converter

「ブログ記事のエクスポート」で引っ張ってきたexport*****.txtファイルを、このスクリプトに渡して下さい

perl format.pl export******.txt

ごめんなさい:追記部分の変換を忘れてました。使用時には注意してくださいね…。

今回Perl使ったのは初めてなのですが、空文字列をfalseとして認識する使用で随分ひっかかりました。もうやめようよそういう仕様…ね?

 

gmtime/localtimeと閏秒の面倒くさい関係

Posted on

お久しぶりです、ψ(プサイ)です。

今日は今やってるプロジェクトとは一切関係なく、gmtime/localtimeの実装が気になったので、読んで分かったことをまとめておきます。

軽くおさらいですが、gmtime/localtimeは、unixtime(1970年1月1 00:00:00 AM UTCからの経過秒数)をtm構造体に入った年・月・日・時間・分・秒に変換してくれるCの標準ライブラリ関数です。unixtimeのままでは人間が到底読めないので、表示する時などに必要な関数です。

思えば、このgmtime/localtimeの実装は結構めんどくさそうです。すごくざっくりで良いなら、

年=(int)1970+(unixtime/(24*365*3600))

みたいな感じで簡単に算出できますが、これでは全然駄目な点がたくさんあります。

  • うるう年は?
  • うるう秒は?
  • (localtimeの時)時差は?

どうやるか自体は大体想像できると思いますが、折角なので読んでみました。そんな具体的な話には興味ないという方は飛ばして下さい。

newlibのgmtimeを読んでみよう

newlibは標準Cライブラリの軽量な実装です。組み込みの分野でよく使われていて、コンパクトなのが特徴だそうです。

ためしに、このライブラリでのgmtimeを読んでみます。newlib/libc/time以下のフォルダにあります。

gmtime.c

struct tm *
_DEFUN (gmtime, (tim_p),
	_CONST time_t * tim_p)
{
  _REENT_CHECK_TM(_REENT);
  return gmtime_r (tim_p, (struct tm *)_REENT_TM(_REENT));
}

gmtimeはグローバル変数のtm構造体を返すのですが、ユーザの指定したtm構造体に値を格納するgmtime_rという関数もあるので、それに処理を投げているようです。その先は…

gmtime_r.c

struct tm *
_DEFUN (gmtime_r, (tim_p, res),
	_CONST time_t * tim_p _AND
	struct tm *res)
{
  return (_mktm_r (tim_p, res, 1));
}

localtimeと処理を共用しているようで、その共通の関数に投げてます。3つ目の引数の「1」は、gmtを使うという意味のようです。

mktime_r.c

struct tm *
_DEFUN (_mktm_r, (tim_p, res, is_gmtime),
	_CONST time_t * tim_p _AND
	struct tm *res _AND
	int is_gmtime)
{
  long days, rem;
  time_t lcltime;
  int y;
  int yleap;
  _CONST int *ip;
   __tzinfo_type *tz = __gettzinfo ();
  /* base decision about std/dst time on current time */
  lcltime = *tim_p;

この関数は結構長いので、ちょっとずつ読んで行きましょう。まずはローカル変数の定義です。昔のCの規格でも動くように、ローカル変数は必ず関数(もっというとスコープ)の一番最初で宣言されているみたいです。std/dstという文字列が見えますね…サマータイムと通常時間の事です。私は最初、すっかり存在を忘れてました。。。もしオレオレ実装を作っていたらすっかり忘れていた所です。

  days = ( (long)lcltime) / SECSPERDAY;
  rem = ( (long)lcltime) % SECSPERDAY;
  while (rem < 0) 
    {
      rem += SECSPERDAY;
      --days;
    }
  while (rem >= SECSPERDAY)
    {
      rem -= SECSPERDAY;
      ++days;
    }

1970年の元旦からの日数と、残りの秒数を求めているみたいです。

if文の分岐先が中々不思議ですね。あまりを求めるときのあまりは、法(割る数)の符号に合わせて決めるはずだったと思うので、マイナスになることは無いし、法を超える事も無いはずだと思うのですが、もしかするとCの仕様ではそう決まってないのかも…?? よくわかりません(

  /* compute hour, min, and sec */  
  res->tm_hour = (int) (rem / SECSPERHOUR);
  rem %= SECSPERHOUR;
  res->tm_min = (int) (rem / SECSPERMIN);
  res->tm_sec = (int) (rem % SECSPERMIN);
  /* compute day of week */
  if ( (res->tm_wday = ( (EPOCH_WDAY + days) % DAYSPERWEEK)) < 0)
    res->tm_wday += DAYSPERWEEK;

時間・分・秒・曜日は特に何も考えなくても機械的に決定できるので、問題ないです。

 /* compute year & day of year */
  y = EPOCH_YEAR;
  if (days >= 0)
    {
      for (;;)
	{
	  yleap = isleap(y);
	  if (days < year_lengths[yleap])
	    break;
	  y++;
	  days -= year_lengths[yleap];
	}
    }
  else
    {
      do
	{
	  --y;
	  yleap = isleap(y);
	  days += year_lengths[yleap];
	} while (days < 0);
    }
  res->tm_year = y - YEAR_BASE;
  res->tm_yday = days;

少し長いですが、1,970年の元旦からの日数から、年を求めています。isleap関数はうるう年の時は1を、そうでないときは0を返す関数で、year_lengthsは{365,366}の配列です。このふたつを組み合わせるとその年の日数をうるう年を考慮した上で知ることができます。

基本的には1年ずつ年数を調べて、最初に計算した1970年からの経過時間であるdaysから引いていって、1年に満たなくなるまでループを回す、っていう、素朴な実装です。

最初に分岐がありますが、相変わらず、days<0になるのはいまいちどういうケースなのか想像できないです。。。

そして最後の-YEAR_BASEですが、tm構造体のtm_yearの値は1900年からの年数を表す事になってるので、これで引いています。

  ip = mon_lengths[yleap];
  for (res->tm_mon = 0; days >= ip[res->tm_mon]; ++res->tm_mon)
    days -= ip[res->tm_mon];
  res->tm_mday = days + 1;

ここまでで、daysは1年のうちでの日数を表しているので、年でやったのと同じように何月なのかを計算しています。mon_lengthsは…

static _CONST int mon_lengths[2][MONSPERYEAR] = {
  {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
  {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
} ;

こんな感じの配列で、うるう年を考慮した月の日数の一覧です。

最後の+1ですが、tm_mdayは1から始まるきまりになってるので、その調整です。

そして最後の最後にgmtimeとlocaltimeでの分岐処理が入ります。

  if (!is_gmtime)
//省略
  else
    res->tm_isdst = 0;

gmtの時はtm_isdts=0に設定してるだけで簡単です。gmtにはサマータイムとかは無く、またunixtimeはGMTの1970年の元旦からの秒数なので時差もありません。

localtimeの時の処理はかなり骨がおれます。if文の中身を読んで行きましょう。

      if (_daylight)
	{
	  if (y == tz->__tzyear || __tzcalc_limits (y))
	    res->tm_isdst = (tz->__tznorth 
			     ? (*tim_p >= tz->__tzrule[0].change 
				&& *tim_p < tz->__tzrule[1].change)
			     : (*tim_p >= tz->__tzrule[0].change 
				|| *tim_p < tz->__tzrule[1].change));
	  else
	    res->tm_isdst = -1;
	}
      else
	res->tm_isdst = 0;

_daylightはグローバル変数で、サマータイムを使う可能性があるときにセットされます。

res->tm_isdstの判定なのですが、すいません、よくわかりません。__tznorthがfalseの時、条件が反転するみたいです。謎い…。サマータイム、嫌すぎる

→コメント覧参照ですが、これはどうやら南半球でのサマータイム(10月〜翌年3月ぐらい)の判定と、北半球でのそれとでの差のようです。これも最初まったく考えが至りませんでした。…北半球から出たことないです…。

      offset = (res->tm_isdst == 1 
		  ? tz->__tzrule[1].offset 
		  : tz->__tzrule[0].offset);
      hours = (int) (offset / SECSPERHOUR);
      offset = offset % SECSPERHOUR;

      mins = (int) (offset / SECSPERMIN);
      secs = (int) (offset % SECSPERMIN);
      res->tm_sec -= secs;
      res->tm_min -= mins;
      res->tm_hour -= hours;

サマータイムを考慮しつつ、時差を取得して、その分を足したり引いたりしている処理です。この時、機械的に足すので、時間が24を超えたり、マイナスになるかもしれません。ので、次で調整します。

      if (res->tm_sec >= SECSPERMIN)
	{
	  res->tm_min += 1;
	  res->tm_sec -= SECSPERMIN;
	}
      else if (res->tm_sec < 0)
	{
	  res->tm_min -= 1;
	  res->tm_sec += SECSPERMIN;
	}
      if (res->tm_min >= MINSPERHOUR)
	{
	  res->tm_hour += 1;
	  res->tm_min -= MINSPERHOUR;
	}
      else if (res->tm_min < 0)
	{
	  res->tm_hour -= 1;
	  res->tm_min += MINSPERHOUR;
	}
      if (res->tm_hour >= HOURSPERDAY)
	{
	  ++res->tm_yday;
	  ++res->tm_wday;
	  if (res->tm_wday > 6)
	    res->tm_wday = 0;
	  ++res->tm_mday;
	  res->tm_hour -= HOURSPERDAY;
	  if (res->tm_mday > ip[res->tm_mon])
	    {
	      res->tm_mday -= ip[res->tm_mon];
	      res->tm_mon += 1;
	      if (res->tm_mon == 12)
		{
		  res->tm_mon = 0;
		  res->tm_year += 1;
		  res->tm_yday = 0;
		}
	    }
	}
       else if (res->tm_hour < 0)
	{
	  res->tm_yday -= 1;
	  res->tm_wday -= 1;
	  if (res->tm_wday < 0)
	    res->tm_wday = 6;
	  res->tm_mday -= 1;
	  res->tm_hour += 24;
	  if (res->tm_mday == 0)
	    {
	      res->tm_mon -= 1;
	      if (res->tm_mon < 0)
		{
		  res->tm_mon = 11;
		  res->tm_year -= 1;
		  res->tm_yday = 364 + isleap(res->tm_year + 1900);
		}
	      res->tm_mday = ip[res->tm_mon];
	    }
	}

かなり長いですが、これはかなり単純で、例えば分が60を超えていたら調整のためにその上の時間を足して、…という処理を延々年まで行なっていってるだけです。

以上です。いかがだったでしょうか。一つ一つの処理はそこまで複雑ではないのですが、やはり長いですし、細かい例外もちゃんとフォローしてありましたね。ライブラリは偉大です。

閏秒は????????

さて、ここまでで気づきませんでしたか。

閏秒という概念が登場しなかった、という事実を。

そう、実はposix標準ではgmtime/localtimeは閏秒を考慮しないらしいですえっそれ普通に困るのでは

が、しかし、glibcのソースを眺めてみると、めんどくさくてあまり読んでないのですが、次の内部関数を見ると、どうやらうるう秒を考慮していそうです。

time/tzfile.c

void
__tzfile_compute (time_t timer, int use_localtime,
		  long int *leap_correct, int *leap_hit,
		  struct tm *tp)
  /* Apply its correction.  */
  *leap_correct = leaps[i].change;
  if (timer == leaps[i].transition && /* Exactly at the transition time.  */
      ( (i == 0 && leaps[i].change > 0) ||
       leaps[i].change > leaps[i - 1].change))
    {
      *leap_hit = 1;
      while (i > 0
	     && leaps[i].transition == leaps[i - 1].transition + 1
	     && leaps[i].change == leaps[i - 1].change + 1)
	{
	  ++*leap_hit;
	  --i;
	}
    }
}

むー、なんか複雑になってきました。

ということは:同じ時刻、違うunixtime?

閏秒を考慮する場合としない場合で、当然時刻の計算が違ってくるはずです。glibはうるう秒を考慮に入れてるっぽいので、適当に決めたunixtime=1354320000で、差をチェックしてみましょう。

ソースはこちらです。

#include 
#include 
int main()
{
	struct tm* result;
	time_t constTime = 1354320000L;
	result = gmtime(&constTime);
	printf("%04d/%02d/%02d %02d:%02d:%02d\n",
			result->tm_year+1900,
			result->tm_mon+1, //1月=0
			result->tm_mday,
			result->tm_hour,
			result->tm_min,
			result->tm_sec);
	return 0;
}

まずはnewlibでやってみます。なんかprintfがうまく動かないので、gdbでステップ実行しました(情弱)

結果がこんな感じ

# gdb ./gmtime_test
(略)
(gdb) print *result //resultの中身を表示するよ
$2 = {tm_sec = 0, tm_min = 0, tm_hour = 0, tm_mday = 1, tm_mon = 11, 
  tm_year = 112, tm_wday = 6, tm_yday = 335, tm_isdst = 0}
# -> 2012/12/01 00:00:00

で、Fedora 17のデフォルトのglibc(2.15)とリンクした方でテストすると、

# ./gmtime_test
2012/12/01 00:00:00

あれれ???一緒ですね…

さらに泥沼が待っているとはこの時まだ私は知らないのであった…

タイムゾーンの設定が、2つある

glibcのところで、うるう秒を考慮にいれているようなソースがありましたね。うるう秒というのはうるう年とは違って、ランダムに挿入されます。ので、どこかに「うるう秒の一覧」を記録しておかないと、正しく計算することができません。

「うるう秒一覧」が何処にあるかというと、fedora 17の場合、/usr/share/zoneinfoというフォルダの中にあり…

Africa	    Chile    GB		Indian	     Mideast   posixrules  US
America     CST6CDT  GB-Eire	Iran	     MST       PRC	   UTC
Antarctica  Cuba     GMT	iso3166.tab  MST7MDT   PST8PDT	   WET
Arctic	    EET      GMT0	Israel	     Navajo    right	   W-SU
Asia	    Egypt    GMT-0	Jamaica      NZ        ROC	   zone.tab
Atlantic    Eire     GMT+0	Japan	     NZ-CHAT   ROK	   Zulu
Australia   EST      Greenwich	Kwajalein    Pacific   Singapore
Brazil	    EST5EDT  Hongkong	Libya	     Poland    Turkey
Canada	    Etc      HST	MET	     Portugal  UCT
CET	    Europe   Iceland	Mexico	     posix     Universal

こんな感じでたくさん入ってます。この中には時差その他のデータが入っていて、うるう秒のデータも含めることができるそうです。詳しくは:tz database – Wikipedia

これを日本について見てみると…

% zdump Japan -v
Japan  -9223372036854775808 = NULL
Japan  -9223372036854689408 = NULL
Japan  Sat Dec 31 14:59:59 1887 UTC = Sun Jan  1 00:18:58 1888 LMT isdst=0 gmtoff=33539
Japan  Sat Dec 31 15:00:00 1887 UTC = Sun Jan  1 00:00:00 1888 JST isdst=0 gmtoff=32400
(略)
Japan  Fri Sep  7 16:00:00 1951 UTC = Sat Sep  8 01:00:00 1951 JST isdst=0 gmtoff=32400
Japan  9223372036854689407 = NULL
Japan  9223372036854775807 = NULL

となってて、2012年の7月にあった最新のうるう秒などのデータが入ってるようには見えません。

実はright/Japanというファイルもあって、こちらを見てみると…

% zdump right/Japan -v
right/Japan  -9223372036854775808 = NULL
right/Japan  -9223372036854689408 = NULL
right/Japan  Sat Dec 31 14:59:59 1887 UTC = Sun Jan  1 00:18:58 1888 LMT isdst=0 gmtoff=33539
(略)
right/Japan  Fri Sep  7 16:00:00 1951 UTC = Sat Sep  8 01:00:00 1951 JST isdst=0 gmtoff=32400
(略)
right/Japan  Sat Jun 30 23:59:60 2012 UTC = Sun Jul  1 08:59:60 2012 JST isdst=0 gmtoff=32400

2012年7月の表記があるので、ちゃんとうるう秒が反映されているようだ、とわかります。

タイムゾーンを正しい方に設定してみる

やっと正しい方が分かった所で、この正しい方にタイムゾーンを設定してみましょう。TZ環境変数に正しい方のファイルへのパスを指定すると、どのタイムゾーンのファイルを使うか設定することができます。

glibcの方から先にしてみましょう。

% TZ=/usr/share/zoneinfo/right/Japan ./gmtime_test  
2012/11/30 23:59:35

ふぅ。。。。さっきは12/01でしたが、うるう秒を考慮した結果25秒ほど過去に戻って11月になってしまいました。

一応、うるう秒を考慮にいれていないはずであるnewlibも試して見ました。

% TZ=/usr/share/zoneinfo/right/Japan gdb ./gmtime_test_newlib
(gdb) print *result
$1 = {tm_sec = 0, tm_min = 0, tm_hour = 0, tm_mday = 1, tm_mon = 11, 
  tm_year = 112, tm_wday = 6, tm_yday = 335, tm_isdst = 0}
# -> 2012/12/01 00:00:00

おおお、やっと差が出ましたね!つ、疲れた…。まとめましょう。

ちなみに毎度毎度TZ環境変数にいれなくても、システム全体で正しい方を使うようにもできます

まとめ

  • gmtime/localtime、簡単そうな処理ですがサマータイムとかうるう秒を考えたらやっぱりライブラリ使いましょう
  • gmtime/localtimeはposix標準ではうるう秒を考慮しません
  • glibcでは考慮しますが、デフォルトでは無効化されていて、タイムゾーンファイルの指定を変更することで「正しい」時間を表示するように出来ます。
  • する場合としない場合で、2012年現在25秒も違います。

なんていうか、…大変ですね(しみじみ)。一番の問題はやっぱりposix標準で「うるう秒を考慮しない」としてる点だと思います。今更変えられないのが非常に…悔しいですね…。

差分アップデートを実装したさきゅばす2.0b3を公開しました

Posted on

リリースノート

 コメントの表示時間に関するバグの修正と、前回言っていた差分による自動アップデータを搭載したバージョンを公開します。

 自動アップデートを搭載したので、今後のアップデート作業は大分楽になると思います。とはいえ、最初のバイナリのサイズがまた増えてしまったのですが…。

変更履歴

  • コメントの表示時間がue/shitaと通常コメントで逆になっていた問題を修正しました。
  • 自動アップデータを搭載しました(bazaarを利用しています)
  • FFmpegのバイナリを最新のものに更新しました
  • libxml2とx264を更新しました。

ダウンロード

 いつも通りSourceforgeからどうぞ

 なお、zipと7zは内容は同じです。7zのほうがファイルサイズが小さいので7zが解凍できる人はどうぞ。

自動アップデータの使い方

 Wikiに書いておきました

20120824_01.png

 Bazaarの軽量チェックアウトを使っています。従来のSubversionより容量を使わなかったので急遽乗り換えました。

Chromeではメソッドをオブジェクトに直接入れてはいけない!?

Posted on

 短く言うと:アセンブラっぽく書く オブジェクト指向つかったら負け

 前の記事で書いたJavaScriptのファミコンエミュレータ「CycloaJS」を実装する際に使った最適化の手法について紹介します。

関数呼び出し、避けましょう

 まずはこんな関数から〜。

"use strict";
var TIMES = 100000000;
function Klass(){
	this.val = 0;
	this.add = function(val){
		this.val += val;
	};
}
function doBench() {
	var memory = new Uint8Array(1024*1024);
	log("calling func: "+cycloa.probe.measure(function(){
		var obj = new Klass();
		var i = TIMES;
		while(--i){
			obj.add(i&0xfff);
		}
	}));
	log("not calling func: "+cycloa.probe.measure(function(){
		var obj = new Klass();
		var i = TIMES;
		while(--i){
			obj.val += (i&0xfff);
		}
	}));
}

 cycloa.probe.measureっていうのは、実行時間を測ってミリ秒単位で返してくれるだけのユーティリティ関数です。最初のベンチマークでは変数に足し算する処理をクラスメソッドに移譲していますが、次のベンチマークでは移譲せずに直接書いてます。こんな例だと馬鹿馬鹿しいですが、規模が大きくなると前者みたいな方式の方が見通しがよくなったりするんですよね…。

 これを、Chrome21とFirefox14.0.1を用いてログを取ってみました。

Firefox14.0.01

calling func: 657
not calling func: 289
calling func: 772
not calling func: 284
calling func: 768
not calling func: 293

 関数呼び出しを加えただけでかなり遅くなります。C/C++やJavaのような感覚でgetter/setterなどを作ると一気に遅くなってしまいます。

 今回エミュレータでは、CPUのエミュレーション部分がメモリの読み書きを行う際のメソッドを、すべてコードジェネレータを用いてインライン展開しています。

 

  同様に、その他の通常ならば別のメソッドに分けるような処理も、すべて一つの関数内で行うようにしました。erbを用いて埋め込むなどしてできる限り可読性を下げないようにはしているものの、正直見難くなっているのが実情です…。

Chrome21

calling func: 592
not calling func: 592
calling func: 1327
not calling func: 590
calling func: 1035
not calling func: 587
calling func: 1037
not calling func: 585

 Chromeだと、最初の一回目だと差が出ない(インライン展開でも行なっているのでしょうか?)のですが、二回目以降関数呼び出しバージョンが極端に遅くなります。なぜだろと思ってプロファイラを有効にすると、なぜか差がでなくなってしまいます…。なぜなのかはよく分かりません。試しに、

var obj = new Klass();
function doBench() {
var memory = new Uint8Array(1024*1024);
log("calling func: "+cycloa.probe.measure(function(){
	var _obj = obj;
	var i = TIMES;
	while(--i){
		_obj.add(i&0xfff);
	}
}));
log("not calling func: "+cycloa.probe.measure(function(){
	var _obj = obj;
	var i = TIMES;
	while(--i){
		_obj.val += (i&0xfff);
	}
}));
}

 として、オブジェクトの生成を一度だけにしたとしても…

calling func: 575
not calling func: 565
calling func: 890
not calling func: 577
calling func: 881
not calling func: 572

 として、二度目以降やっぱり関数呼び出しバージョンが遅くなってしまいます。なぜでしょう…。

 

ローカル変数、使いましょう

 お次はこのコードです。

  • グローバル変数
  • ローカル変数(var variable = 0;)
  • thisオブジェクトのメンバ変数(this.variable)
  • ローカルオブジェクトのメンバ変数(var obj = 略;obj.variable)
  • thisオブジェクトのメンバオブジェクトのメンバ変数(this.obj.variable)

時間[ms] Chrome21 Firefox14
グローバル変数 302 622
ローカル変数 132 255
this.variable 586 280
var obj;obj.variable 580 280
this.obj.variable 655 292

 ChromeとFirefoxで随分特性が違いますが、できる限りローカル変数を使うのが一番なのは間違いありません。

 定数などはよくグローバル変数としてこのように、擬似名前空間を作った上で保持しておくのが割と綺麗な設計だと思いますが…

var namespace = {}; //ソフトウェア用の擬似名前空間
namespace.INT_MAX=0xffffffff; //32bitIntegerの最大値
namespace.Klass = function(){
	this.method = function(){
		var variable = namespace.INT_MAX; //定数を参照
		for(***){
			variable = (variable+1) & namespace.INT_MAX; //32ビットカウンターとして使う
		}
	};
};

 以上のように複数回定数を使う場合は、

var namespace = {}; //ソフトウェア用の擬似名前空間
namespace.INT_MAX=0xffffffff; //32bitIntegerの最大値
namespace.Klass = function(){
	this.method = function(){
		var tmpINT_MAX = namespace.INT_MAX; //一旦ローカル変数にコピー
		var variable = tmpINT_MAX; //上記のローカル変数の方を参照している
		for(***){
			variable = (variable+1) & tmpINT_MAX; //こちらもローカル変数を参照
		}
	};
};

 このように一旦ローカルに保持しています。…これだけでかなり見づらくなるのでおすすめしません…。

 さらに書き換えられる事の多い変数に関しては、

this.method = function(){
	//一旦ローカルにコピーする
	var variable = this.variable;
	//いっぱい書き換えられる
	variable += this.calc1();
	variable += this.calc2();
	//thisのメンバ変数に戻す
	this.variable = variable;
}

 のようにして、一旦ローカル変数にコピーして最後に再度戻すといった事もしています。書き戻す前にthis.variableにアクセスすると分かりづらいバグになるので非常に注意です。なんてまあ不毛なことを…

 

 さらに、機能ごとにオブジェクトを分割、たとえば今回はエミュレータなのでCPUとサウンドとビデオにクラスを分けて実装してしまうと…

function Video(){
	this.method = new run() {
		//描画
	};
	this.parameter = 0; //何か
}
function CPU(){
	this.method = new run() {
		//CPUの命令実行
	};
	this.parameter = 0; //何か
}
function Emulator(){
	this.videoModule = new Video();
	this.cpuModule = new CPU();
	this.run = function(){
		this.videoModule.run();
		this.cpuModule.run();
	};
}

 このように、this.subModule.run()のように呼びださなくてはいけなくなってしまい、1秒間に数千回呼ぶとコストが馬鹿にならないため…

function Emulator(){
	this.videoModule = new Video();
	this.cpuModule = new CPU();
	this.run = function(){
		//描画処理
		//...
		//CPUの命令実行
		//...
	};
	//変数名の衝突を回避
	this.__video__parameter = 0;
	this.__cpu__parameter = 0;
}

 というようにしてしまっています。こうすると1番目の関数呼び出しのコストも避けられます…が、まずモジュールごとの名前空間が無くなるため、そこに気を付けなければなりません。今回の例ではVideoとCPUのparameterという変数名が衝突しているため、__cpu__と__video__というように接頭辞を付けて区別してますがあまりにも不毛です

JSでファミコンエミュレータ書いてみた:CycloaJS

Posted on

タイトルのまんまです

去年書いて駒場祭で作り方に関する本も出したCycloa」というファミコンエミュレータを、今回JavaScriptに移植してみました。

20120822_01.png

ご好意により、 Denis GrachevさんがZXスペクトラム向けに開発し、ShiruさんとKulorさんがファミコンに移植したAlter Egoというアクションパズルゲームを一緒に配布させていただいています(これはオープンライセンスではありません)。

20120822_02.png

バイナリィランドのように対称性をテーマにしたゲームで、とっても面白いので皆さんやってみてください〜!

JavaScriptのエミュレータはJavaScript NES エミュレータや、jsnesなどが既にありますが、前者は軽量なもののスプライトの再現で一部端折っているところがあり、また後者は通常のPC用エミュレータと同レベルの正確なエミュレーションを行なっているものの、非常に重くミドルレンジクラスのPCでは60FPSのリアルタイムが出ないという問題がありました。

今回のエミュレータでは、通常のPC向けのエミュレーション精度を保ちつつ、どこまで高速化できるのか検証するのを主目的としました。

どれくらい高速化できたのかを確かめてみようというわけで、既存のエミュレータとのベンチマーク取って見ました。動作環境は次の通り。

  • Core2Duo E8400
  • DDR2-800 4GB
  • GeForce 9600GT
  • Fedora 17 x86_64
  • Chrome 21

C++版のオリジナルCycloaと、今回のCycloaJS、JavaScript NES エミュレータとjsnesについて、Google ChromeでFPSリミットを外してどれぐらいの速度が出るのかを調べます。AlterEgoの起動画面でのFPSを比較してみました。

20120822_03.png

  • jsnes: 58.8fps
  • CycloaJS: 169fps
  • Cycloa: 691fps
  • JavaScript NES Emulator: 194fps

jsnesはそもそもこのPCだとリアルタイムで描画できません。JavaScript NES Emulatorより遅いですが、このエミュレータよりスプライトの再現性が高いので、まあ良いのかなと…。流石にC++ネイティブの元のエミュレータには全然勝てません。それでも3〜4倍にまで縮まってるのは、流石Chromeと言ったところでしょうか…

ソースコード

ソースコードはgithubで公開中ですC++版も同様に公開中

具体的な手法に関しては

また後日ということで…とりあえず、もうJavaScriptはあんまり書きたくないですね…。HTML5こわい。。。

リーダブルコード:あたりまえのこと、でもだいじ

Posted on

 なんかネット上を見てたら評判が良いので買ってしまいました、「リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック」。

 具体例としょーもないイラスト(褒め言葉)を交えながら読みやすくてシンプルなコードを書くための軽い読み物です。ラノベより軽いです。

アタリマエのことしか書いてない

 本当に当たり前のことしか書いてない本です。ちゃんと説明する名前の変数を付ける、tmpとかの抽象的な名前は基本的に避けるべきだけど、スコープが短くて本当にtmpならそれはそれでアリ、一度に一つのことしかしないコードにしないと頭がこんがらがる、短絡評価とかを使った「頭いいコード」は逆に読みづらい、…突拍子のある事は一切書いてなくて、どれも本当に「まあそうだよねぇ」って言いながら読み進められます。…でもその「当たり前」の事が出来ないんですよねぇorz

 同様の「当たり前の事しか書いてない」っていう理由で「達人プログラマ」は読む必要無いんじゃない?って前書きました。達人プログラマは当時としては先進的(?)な内容を書いていたので今となってはその説明は要らないかな…という感じなのですが、こちらは各々の持ってる「読みやすいソースコードの基準、目安」みたいなものを改めて文章とソースコードを使って説明しなおしているような本で、一種のたたき台として、「あー、前そういうのあったなー」とか「あの前のコード、ここに出てる駄目コードそのものじゃん!」とか、あーでもないこーでもないと昔の所業を懺悔しながら読める本としてお勧めです。薄いし。

複数人で読んでコードレビューとか楽しそう

 そんなわけで、会社とかで複数人で一つのソースコードを書いているようなシチュエーションで、コード書いてる人が各々この本を読んで、今までのソースコードをレビューする、みたいな使い方をすると楽しそうです。こういう本が無いと何がどう悪いのかを0から説明しなければなりませんけど、レビューする人全員がこの本を読んでれば「ここはさ、あの本の**章に書いてある駄目なコードそのものじゃん?」みたいに簡単に説明できてやりやすそうです。

バグフィックスをしたさきゅばす2.0b2を公開しました

Posted on

リリースノート

公開から2ヶ月以上たったので、バグ修正バージョンのさきゅばす2.0b2を公開しました。

ほぼバグフィックスのみのバージョンです。その他、NGワードスクリプトのサンプルを増やしたりしたので参考にしてみてください。

変更履歴

  • Pythonを最新版に載せ替え、配布サイズを削減しました。
  • スペルミスなどを修正しました。
  • NGスクリプトのサンプルを追加しました。
  • 公式動画をDLできないバグを修正しました。
  • Windowsにおいて、NGスクリプトの改行コードがCRLFだった場合、エラーを発生させないように変更しました
  • ライセンス表示を、配布バイナリ内からWebサイト上に移動しました。

ダウンロード

 ダウンロードはSourceforge内のダウンロードページから行えます。

スクリーンショット

20120805.jpg

 ようせいさんかわいい

 …GUIの変更は殆どありません(ぇ

その他

 registっていう単語は無い(!)そうですが、ねこまたでバッチリ使ってしまっておりました…か、悲しい。

 ライセンス表示のためにファイルが一個増えてしまって、普通に読まれないReadmeがさらに読まれなくなるのではと思って減らしてみました。一緒に配布してるテキストは見づらいのでできるだけ減らしたいです。

 今回はPythonのバイナリをほぼ全とっかえなので全部配布し直すのは理にかなってるのですが、これだけバイナリが大きいとなるとやっぱり自動更新を考えた方がよさそうですね