2013年12月26日木曜日

enchant.js でもインスタンスがどのクラスから作られたか知りたい

enchant.js では JavaScript の一風変わったオブジェクトシステムの上にクラスの仕組みを実装しています。これはこれで便利なのですが、表題に書いた点は不満です。

JavaScript の処理系は、インスタンスがどのコンストラクタから作られたかを表示する機能があります。例えば、次のようなコンストラクタ Dog と Cat を宣言し、それぞれのインスタンスを作り、変数 dog と cat に代入します。

function Dog () {}
function Cat () {}
var dog = new Dog();
var cat = new Cat();

変数 dog と cat をコンソールで見ると、次のようにコンストラクタ名が表示されています。

enchant.js でクラスとして作ると、こうはなりません。同じようにクラス(のコンストラクタ)を作り、それらのインスタンスを作ります。

var Dog = Class.create(Sprite, {
  // 省略
});
var Cat = Class.create(Sprite, {
  // 省略
});
var dog = new Dog();
var cat = new Cat();

コンソールで見ると、(Google Chrome の場合)Constructor と表示され、どんなオブジェクトなのか判然としません。Safari では、Object と表示されるので、これまた判然としません。

ここはやはり、最初に見たように表示してもらえる方が、デバッグの時は助かります。特に、配列などのデータの中にしまった時の差は顕著です。

当座の解決策

結構根が深い問題で、JavaScript の限界とも言えます。

enchant.js の enchant.Class.create メソッドを次のように(赤字の部分を)書き足します。

enchant.Class.create = function(superclass, definition, constructor) {
    // 省略
    var Constructor = typeof constructor === 'function' ? constructor :
      function () {
          if (this instanceof Constructor) {
              Constructor.prototype.initialize.apply(this, arguments);
          } else {
              return new Constructor();
          }
      };
    // 省略
    return Constructor;
};

コンストラクタが匿名関数であると、このような現象が発生します。関数は後から名前を変えることができません。そこで、名前を付けた関数を作ってそれを引数として渡せるように、引数 constructor を追加します。constructor に関数が渡されたときはそれを使い、それでない場合は、従前通りに動作するように作ります。

使い方は、次のようになります。

var Dog = Class.create(Sprite, {
  // 省略
}, function Dog () {
  Dog.prototype.initialize.apply(this, arguments);
});
var Cat = Class.create(Sprite, {
  // 省略
}, function Cat () {
  if (this instanceof Cat) {
    Cat.prototype.initialize.apply(this, arguments);
  } else {
    return new Cat();
  }
});
var dog = new Dog();
var cat = new Cat();

一番最初の例と同様に、どのコンストラクタから作られたかが表示されるようになりました。

第三引数に、enchant.Class.create のはらわたの一部が出てしまっていて見苦しいのですが、この辺が限界のように思います。enchant.js が作るコンストラクタでは、initialize メソッドを呼んでいるので、それを手で書いてあげます。オリジナルのコンストラクタでは、else 句の方に new を付け忘れた場合の救済措置ではないかと推察されるコードがあります(違うかも)。Dog の例のように、その部分を省略しても動きます。心配性な人は、Cat の方のように全部書いてもいいでしょう。

enchant.js で定義されている他のクラスも、同じように書き換えれば、ちゃんとコンストラクタの名前が表示されるようになります。例えば、Sprite を書き換えるならば、次のようにします。

enchant.Sprite = enchant.Class.create(enchant.Entity, {
  // 省略
}, function Sprite () {
  enchant.Sprite.prototype.initialize.apply(this, arguments);
});

あんまりきれいではないですが、一応これでデバッグの苦痛が少し緩和されます。もっとこうした方がいい、などのコメントをお待ちしています。

2013年12月7日土曜日

ssh し続ける(要管理者権限)

あるサーバに ssh のコネクションを貼り続ける必要があったので、ちょっと設定してみました。

前提

ssh の設定(鍵交換によるパスワードなしのログインなど)は済んでいるものと仮定します。やり方が分からない人は、ググって調べてください。

設定

青で書いているところは、パラメータなので、適宜変更して使用してください。

/Library/LaunchDaemons/com.example.keeping-ssh.plist を次のように作ります。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>com.example.keeping-ssh</string>
        <key>ProgramArguments</key>
        <array>
                <string>/usr/bin/ssh</string>
                <string>example.com</string>
                <string>ping</string>
                <string>-i</string>
                <string>60</string>
                <string>127.0.0.1</string>
        </array>
        <key>KeepAlive</key>
        <true/>
        <key>UserName</key>
        <string>user</string>
        <key>GroupName</key>
        <string>group</string>
</dict>
</plist>

ファイル名は何でもいいですが、普通は plist の中の Label と同じ名前にします。自分のドメインを持っていない人は、com.example のままで特に問題はないです。肝心の設定項目としては、次の 3ヶ所です。

  • host は ssh でログインしたいホスト名です
  • usergroup は(ローカルの Mac 側で)ssh のプロセスを動かすユーザ名とグループ名です。普段 Mac にログインする時に使っているもので OK です。

ファイルを作ったら、このファイルのオーナーを root:wheel に設定し、launchd に読み込ませます(または Mac を再起動してください)。

$ sudo chown root:wheel /Library/LaunchDaemons/com.example.keeping-ssh.plist
$ sudo launchctl load /Library/LaunchDaemons/com.example.keeping-ssh.plist

ssh でログインした先で、自分に向けて定期的に ping を打って、コネクションを維持しています(ここら辺は、各自用途に応じて適当なコマンドを動かしましょう)。それでも切断されてしまった場合は、KeepAlive を指定しているので、launchd が自動的に再接続を試みます。ログアウトしていても、ssh のコネクションは維持され続けます。

launchd は色々やってくれるので、スクリプトを書かなくても設定だけで済んでしまいます。楽ちんです。