計算機のくせに

コンピュータの計算は、事情により結構いい加減なのは知っていたけど、
バグを追いかけた結果、目の当たりにするとなんともはや。
こういうのって個別に対応するしかないんでしょうか。


trace(1.005 * 100);
//期待する結果    100.5
//実際の結果    100.49999999999999

特にAS3から、各種プロパティが0-1の値を取るようになったので、
この問題にぶちあたりやすい気がする。

そもそも、何でこれに気づいたかというと、
1.005を小数点以下2桁の数値になるように四捨五入したくて、
100倍して、整数に丸めて、0.01倍してて気づいたんだよね。

ところで!
その後、調べてみたら、まさにそのためのメソッド
Number.toFixed()があった!

しか〜し


var num:Number = 1.005
trace(num.toFixed(2));

//結果 1.00

だめじゃん。。
これも同じことが起こってるんだろうね。

ちょっとテストしてみた。


trace(Number(0.005).toFixed(2));
trace(Number(1.005).toFixed(2));
trace(Number(2.005).toFixed(2));
trace(Number(3.005).toFixed(2));
trace(Number(4.005).toFixed(2));
trace(Number(5.005).toFixed(2));
trace(Number(6.005).toFixed(2));
trace(Number(7.005).toFixed(2));
trace(Number(8.005).toFixed(2));
trace(Number(9.005).toFixed(2));

// 結果
0.01
1.00
2.00
3.00
4.00
5.00
6.00
7.00
8.01
9.01

あわあわ。

カテゴリー: ActionScript3 | コメントする

外部swfにリンケージされているクラスをnewしたい

ちょっとはまったのでメモ。

ミッション

メインswfから外部swf(アセット用swf)を読み込み、読み込んだアセットswにあるクラス(MyClass)をnewしたい。
ただし、MyClassはライブラリにあるMovieClipシンボルとリンケージされている。

図にするとこんな感じ
mission.gif
で、Main.asからこんな感じにnewしたい、と。

var myClass:MyClass = new MyClass();

その場合、何が問題なのか?

アセットswfにコンパイルされているMyClassが、ライブラリにあるシンボルとリンケージされている場合、
読み込み元に記述されているMyClassが競合する。

なぜか?

アセットにコンパイルされているMyClassと、読み込み元にコンパイルされているMyClassは同じ名前空間の同じクラス。
この場合、アセットswfを読み込んでも、読み込み元のMyClassはアセットのMyClassで上書きされない。
つまり、newできない。(リンケージしなければ可能)

思いついた3つの解決策

1・読み込み元にはクラスのimport記述をせず、型指定もしない。
ようするに読み込み元のswfにはMyClassをコンパイルしない。

var classRef : Class = getDefinitionByName("MyClass") as Class;
myClass = new classRef() as MovieClip;

2・読み込み元とは別のApplicationDomainにアセットを読み込む
この場合、main.swfにコンパイルされたMyClassと、読み込み元にコンパイルされているMyClassは、別のApplicationDomainになるので競合しない。

3・アセットswfではダミーのクラスをリンケージしておく
たとえば、dammyMyClassなどという識別子でリンケージし、クラスファイル(dammyMyClass.as)は作成しない。
(Flashはasset.swfコンパイル時に、自動的にダミーのクラスを作ってくれる。)
で、MyClassクラス内で、dammyMyClassをnewする

結論

1は、コンパイル時のエラーチェックが出来ない。
2は、アセットが関連するクラスはすべて、別のApplicationDomainにも存在する必要があるので、現実的じゃない。
いまのとこ、3がいいと思う。
ただし、この場合、MyClassはMovieClipを継承したクラスではなくMovieClipのラッパーとなる。
まぁ、MovieClipをどうしても継承したい局面が思い浮かばないので問題なし。

いろいろ書いたけど、個人的には
リンケージはいっさい使用しない!
これが最強。
AS2の頃からリンケージを使ってMovieClipをnewするのはすかんかった。

追記(2008/6/3):
と書いたものの、AS3のイベントモデルを堪能するにはMovieClipを継承しなければならない。最近は表示オブジェクトはコンポジットではなく継承するようにしています。なので、方法は3番だけど、MyClassはMovieClipを継承し、dammyMyClassをコンポジットするようにする。
これが今回のエントリについてのベストプラクティスだと思います。

カテゴリー: ActionScript3, AS3fromAS2 | コメントする

as演算子の使いどころ

キャスト(型変換)は、オブジェクト指向プログラミングには欠かせない。
AS3にはキャストの方法が2種類ある。

()を使ったキャスト

変換したいインスタンスを、変換したい型のあと()でくくる。
キャストに失敗すると例外(Error)を投げる。
例外をキャッチしないとランタイムエラーになる。

trace(MovieClip(new Sprite()));
//output
TypeError: Error #1034: 強制型変換に失敗しました。flash.display::Sprite@4c38ee49 を flash.display.MovieClip に変換できません。

as 演算子を使ったキャスト

asの前に、「変換したいインスタンス」、後に「変換したい型」で記述する。
キャストに失敗するとnullを返す。

trace(new Sprite() as MovieClip);
//output
null

ところで、この2つはどう使い分けるべきなんだろう?

()を使ったキャストを使う局面は、「キャストできることが前提の処理を書くとき」
つまり、アプリケーションの実行上、キャストが失敗するのは何らかの異常事態が起こっているときの場合で、
as演算子を使う局面は、キャストに成功するか失敗するかコンパイル時には予想できない場合なのかな?

例えば、コレクション内に、いろいろな動物オブジェクトがあり、
その中のTigerに対して処理を実行したい場合、

()を使ったキャスト

function giveFoodToTiger(zoo:Array)
{
    for(var i:int = 0; i < zoo.length; i ++)
    {
        try
        {
            var tiger:Tiger = Tiger(zoo[i]);
        }
        catch(e)
        {
            //クリティカルなエラーが発生!エラー処理を実行
            tiger = new Tiger();
        }
        finally
        {
            //トラにだけ餌をあげる
            tiger.giveFood();
        }
        
    }
}

as演算子を使ったキャスト

function giveFoodToTiger(zoo:Array)
{
    for(var i:int = 0; i < zoo.length; i ++)
    {
        //トラにだけ餌をあげる
        var tiger:Tiger = zoo[i] as Tiger;
        if(tiger) tiger.giveFood();
    }
}

()を使ったキャストは、コレクション内の動物オブジェクトが「トラだけ」なのを想定していて、Tigerクラスでなければ困る場合に使える。
as演算子を使ったキャストは、コレクション内の動物オブジェクトが「いろんな動物」なのを想定していて、その中の特定オブジェクトだけに何かしたい場合に使える。

あと、未検証だけど、
as演算子を使ったキャストの方が高速らしい。

カテゴリー: ActionScript3, AS3fromAS2 | 1件のコメント

AS3のfl.transitions.Tween、その注意点

AS3のfl.transitions.Tweenが途中で止まるんです。
毎回止まるわけじゃなく、気まぐれに止まるんです。
何が原因かしばらくわからんかった。
再現するサンプルを作ったから、
テレビの前のみんなも一緒に考えてみてくれ。

答えはCMの後で!!


以下のスクリプトは、
ドットを100個生成し、フェードインして表示させるものです。
アニメーションにはfl.transitions.Tweenクラスを使用しています。
Tweenの終了時にイベントを受け取って、トレースしています。
100個のTweenが終了し、100回トレースしてほしいのですが、
かなりの確率で100回トレースされないです。
ただしうまく100回トレースされることもあります。

import fl.transitions.*;
import fl.motion.easing.*;
import flash.utils.*;
stage.frameRate = 30;

//Tween終了カウンタ
var c:int = 0;

//100ミリ秒ごとに一つTwennを生成する。100個まで
var myTimer:Timer = new Timer(100, 100);
myTimer.addEventListener("timer", createTween);
myTimer.start();

function createTween(__e:TimerEvent=null) {

    //Tween目視用スプライトを生成
    var spr:Sprite = new Sprite();
    var shape:Shape = new Shape();
    var gra:Graphics = shape.graphics;
    gra.beginFill(0xff0055);
    gra.drawCircle(0,0,5);
    spr.addChild(shape);
    addChild(spr);
    spr.x = Math.random()*500;
    spr.y = Math.random()*500;

    //Tweenを生成
    var twn:Tween = new Tween(spr, "alpha", Quadratic.easeInOut, 0, 1, 10, true);
    twn.addEventListener(TweenEvent.MOTION_FINISH, onFinish);

}

//Tween終了のイベントハンドラ
function onFinish(__e:TweenEvent):void {
    trace("Finish", ++c);
}

正解は、
「生成したTweenインスタンスが、ガベージコレクトの餌食になっている」
でした。

AS2版のTweenでは、この現象は起こっていませんでした。
Tweenクラスは、内部的にはonEnterFrameイベントを受け取って
トゥイーン処理が行われています。
AS2版のTweenでは、onEnterFrameのリスナであるために、
ガベージコレクトを免れていました。

AS3版のTweenでも、内部的にはevent.ENTER_FRAMEのリスナになっていますが、
リスナに追加される際に「弱い参照」で追加されています。
(今回のことで、addEventListenerするときに「弱い参照」が利用できることを知りました!)
「弱い参照」は通常の参照とは違い、参照カウントが加算されない参照みたいです。

このため、上のコードでは、
Tweenインスタンスを参照するのはfunction内のローカル変数のみであるため、
functionを抜けた後、ガベージコレクトされていたというオチでした。

余談ですが、
フレームレートを上げると、トレースされる数も減るため、
ガベージコレクトのタイミングはフレームレートの影響を受けるみたいですね。

カテゴリー: ActionScript3, AS3fromAS2 | コメントする

「排他」を表現するコレクションがあれば便利じゃね?

ふと、「排他」を表現するコレクションがあれば便利じゃね?と思いました。

var exclusiveCollection = new Exclusiveness();
exclusiveCollection.push("A");
exclusiveCollection.push("B");
exclusiveCollection.push("C");

こんな感じでコレクションを作って、

trace(exclusiveCollection.get("A"))

とすると、コレクション内の排他的なオブジェクトが返る。

//output
B,C

何が便利かというと、
たとえば、交差点にある信号機を実装するとして、
各ランプは排他的に点灯するとして、
各ランプは点灯するとイベントを配信するとする。

/**
 * ランプが点灯した
*/

private function onLightOn(e:LightEvent)
{
    //イベントを配信したランプ
    var light : Light = e.target as Light;

    //他のランプを消灯する
    switch(light)
    {
        case red:
            green.off();
            yellow.off();
            break;

        case green:
            red.off();
            yellow.off();
            break;

        case yellow:
            red.off();
            yellow.off();
            break;
    }

}

↑こういった長ったらしいコードが、
↓こんな感じに!

/**
 * ランプが点灯した
*/

private function onLightOn(e:LightEvent)
{
    //イベントを配信したランプ
    var light : Light = e.target as Light;

    //他のランプを取得して消灯する
    var other : Array = exclusiveCollection.get(light);
    other.every(
        function(element:*, index:int, arr:Array):void {
                   Light(element).off() ;
               })

}

最初のコードに比べると、コードが短くなるうえ、
排他的に処理していることが明示できて、いけてるのではないかと。
どうだろう?
(Array.every()は使ったことがないので使い方が間違ってるかもしれない。)

カテゴリー: ActionScript3 | コメントする

AS3の継承

メソッドのオーバーライド

AS3では、サブクラスでスーパークラスのメソッドをオーバーライドする場合、
明示的にoverrideキーワードを使用しなければならなくなった。
サブクラスを見るだけで、このメソッドはサブクラス独自のものか
スーパークラスのメソッドを上書きしたものかがわかるようになったので便利。

AS2の場合

//スーパークラス
class SuperClass
{
    //スーパークラスのメソッド
    private function method1():void
    {
        trace("method1");
    }
}


//サブクラス
class SubClass extends SuperClass
{
    function SubClass()
    {
        method1();
    }
    
    //サブクラスのメソッド
    private function method1():void
    {
        super.method1();
    }
}


//サブクラスの生成
var sc:SubClass = new SubClass();

//output
//method1

AS3の場合

package
{
    //スーパークラス
    public class SuperClass
    {
        /* スーパークラスのメソッド
         * privateだとAS2と違ってサブクラスからアクセスできない。
         * サブクラスからアクセスできるようにするにはprotectedまたはinternalキーワードを使用する。
         */
        protected function method1():void
        {
            trace("method1");
        }
    }
}


package
{
    //サブクラス
    public class SubClass extends SuperClass
    {
        public function SubClass()
        {
            method1();
        }
        /*サブクラスのメソッド
         * 上書きするメソッドにはoverrideキーワードをつける。
         * また、スーパークラスのメソッドのアクセス修飾子と一致させる必要がある。
         * (privateだとコンパイルエラー)
         */
        protected override function method1():void
        {
            super.method1();
        }
    }
}


//サブクラスの生成
var sc:SubClass = new SubClass();

//output
//method1

カテゴリー: AS3fromAS2 | コメントする

AS3のFunction

引数にデフォルト値を持てるようになった

AS3のFunctionは、引数にデフォルト値を持てるようになった。
デフォルトの値を割り当てた引数はオプションになり、
割り当てられなかった引数は必須引数になる。
必須引数が渡されない場合はコンパイルエラーとなる。

書式は以下の通り。


function defaultValues(x : inty : int = 3, z : int = 5) : void
{
    trace(x, y, z);
}
defaultValues(1); // 1 3 5

注意その1

デフォルト値を持つ引数は、引数リストの末尾に記述しなければならない。

function defaultValues(x : int = 3, y : int = 5, z : int) : void
{
    trace(x, y, z);
}
defaultValues(1); 

//    output
//    末尾がデフォルト値を持たない引数なので、コンパイルエラー

注意その2

デフォルト値として割り当てられる値は、コンパイル時定数でなければならない。
(実行時に生成される値は不可)


function defaultValues(a : Array = []) : void
{
    trace(a);
}
defaultValues(["a""b"]); 

//    output
//    デフォルト値が実行時に生成される値なので、コンパイルエラー

カテゴリー: AS3fromAS2 | コメントする

CS3でAS3Unitをつかってみる

AS3Unit

[attention!]
Flash CS3用のAS3Unitが公開されているようです。
現在は、このエントリーにあるようなsvnからのソースの取得の必要はありません。

なにはなくともテスト環境
ということで、Sparkprojectのyossyさんという方が公開してくれているAS3UnitをCS3で使ってみることにする。
こういうものを公開してくれる人はすばらしいですね。

ソースをダウンロード

AS3Unitのページに行き、AS3Unitをダウンロードする。
AS3UnitはFrexで使用する前提?のようなので、as3unit-1.01.swcではなく、as3unit-1.01-src.zipをダウンロードする。

テスト実行用のクラスを作成。

ドキュメントクラスになるMain.asを作成
AS3Unitのページのファーストステップを見ながら、テスト実行用のコードを記述する。

ドキュメントクラス:Main.as

package
{
import flash.display.Sprite;
import org.libspark.as3unit.runner.AS3UnitCore;
public class Main extends Sprite
{
public function Main()
{
AS3UnitCore.main(CalculatorTest);
}
}
}

新規Flaファイルを作成し、firstTest.flaという名前で保存。
パブリッシュ設定を開き、以下の項目を設定。

  • ダウンロードしたソースのsrcディレクトリにクラスパスを通す。
  • ドキュメントクラスをMain.asにする。

setDocumentClass.gif

テストクラスを作成

おなじく、ファーストステップのサンプルに従って、テストクラスとテスト対象クラスを作成する。

テストクラス:Calculator.as (ファーストステップより)

package
{
import org.libspark.as3unit.test;
import org.libspark.as3unit.assert.assertEquals;
use namespace test;
public class ClaculatorTest
{
test function add():void
{
var calc:Calculator = new Calculator();
assertEquals(3, calc.add(1, 2));
}
}
}

コンストラクタにタイポがあり、CalculatorTestがClaculatorTestになっているのでコピペ注意。

テスト対象クラス:Calculator.as (ファーストステップより)

package
{
public class Calculator
{
public function add(a:Number, b:Number):Number
{
return 0;
}
}
}

テストの実行

firstTest.flaをプレビューしてみる。
コンパイラに怒られた。
error1.gif

うーむ。
TestListenerがみつからないそうだ。
こいつのフルパスはorg.libspark.asunit.framework.TestListener。
ダウンロードしたAS3Unit1.01ソースコードにはorg.libspark.asunitパッケージが含まれてない様子。

Sparkprojectのページに戻ってみるとASUnitというものがあった。
これかな?

さっそく、asunit-1.01-src.zipをダウンロード。
org.libsparkパッケージ以下にダウンロードしたasunitパッケージを加える。

プレビューしてみる。
… エラーが増えた w
error2.gif

org.libspark.asunit.framework.AS3UnitTestAdapterとかがない様子。
うーん。これ、CS3じゃ使えないんだろうか。。

また、Sparkprojectのページに戻ってうろうろしていると、
おぉ!AS3Unitのリポジトリが公開されているではないですか!
これ、checkoutしていいんだよね?
どきどきしながら、

svn checkout "as3unit.libspark.org/svn/as3/trunk"

ソースがずらずら落ちてきました。
AS3UnitTestAdapterも含まれてるっぽい。

org.libspark.asunit.framework以下の

  • AS3UnitTestAdapter.as
  • AS3UnitTestAdapterCache.as
  • AS3UnitTestCaseFacade.as

を加えて、3度目のプレビュー。

.E
Time: 0.01
There was 1 failure:
1) add(CalculatorTest)
Error: expected:<3> but was:<0>
at global/org.libspark.as3unit.assert::fail()
at global/org.libspark.as3unit.assert::failNotEquals()
at global/org.libspark.as3unit.assert::assertEquals()
at CalculatorTest/http://as3unit.libspark.org/test::add()
at Function/http://adobe.com/AS3/2006/builtin::apply()
at org.libspark.as3unit.inter.runners::Method/invoke()
at org.libspark.as3unit.inter.runners::TestMethodRunner/org.libspark.as3unit.inter.runners:TestMethodRunner::executeMethodBody()
at org.libspark.as3unit.inter.runners::TestMethodRunner/org.libspark.as3unit.inter.runners:TestMethodRunner::runUnprotected()
at org.libspark.as3unit.inter.runners::BeforeAndAfterRunner/runProtected()
at org.libspark.as3unit.inter.runners::TestMethodRunner/::runMethod()
at org.libspark.as3unit.inter.runners::TestMethodRunner/run()
at org.libspark.as3unit.inter.runners::TestClassMethodsRunner/org.libspark.as3unit.inter.runners:TestClassMethodsRunner::invokeTestMethod()
at org.libspark.as3unit.inter.runners::TestClassMethodsRunner/run()
at ::BeforeClassAndAfterClassRunner/TestClassRunner.as$98:BeforeClassAndAfterClassRunner::runUnprotected()
at org.libspark.as3unit.inter.runners::BeforeAndAfterRunner/runProtected()
at org.libspark.as3unit.inter.runners::TestClassRunner/run()
at org.libspark.as3unit.inter.runners::CompositeRunner/run()
at org.libspark.as3unit.runner::AS3UnitCore/runWithRunner()
at org.libspark.as3unit.runner::AS3UnitCore/run()
at org.libspark.as3unit.runner::AS3UnitCore/runClasses()
at Function/http://adobe.com/AS3/2006/builtin::apply()
at org.libspark.as3unit.runner::AS3UnitCore/runMain()
at org.libspark.as3unit.runner::AS3UnitCore$/main()
at Main$iinit()
FAILURES!!!!
Tests run: 1, Failure: 1

できた!
なんか、表示されなくてもいいはずのようなものが表示されているけど、とりあえず動いてるっぽい。

最後にテストが通るようにCalculatorクラスを実装し、再度テスト。

.
Time: 0.008
OK (1 test)

動いてるなぁ。
これでいいのだろうか?
一抹の不安はありますが、どんどん使っていく事にします。
yossyさん、ありがとう。
なにかできたら、sparkProjectに参加したいなぁ。

カテゴリー: ActionScript3 | コメントする

AS3fromAS2

ようやくCS3を導入しました。
周回遅れを取り戻さないと。

AS2からAS3への移行についてのカテゴリを増やして、
どんどんメモっていく予定。



/* 未定義のNumber型のデフォルト値が、AS3ではNaNに変わった。
 * AS2ではundefinedを返していた。
 * AS2で初期化の有無を調べるためにundefinedと評価していた人は注意!
*/

//未初期化のNumber
var num:Number;

//AS2ではundefined、AS3ではNaNを返す。
//AS2で初期化の有無を調べるためにundefinedと評価していた人は注意!
trace("Number ---------");
trace("the default value of Number is " + num);
trace("the default value of Number is 'undefined' : " + (num == undefined));
trace("the default value of Number is 'NaN' : " + isNaN(num));

//未初期化のObject
var obj:Object;

//AS2ではundefined、AS3ではnullを返す。
//ただし、undefined == null は true を返すため、
//初期化の有無を調べるためにundefinedと評価することができる。
trace("\nObject ---------");
trace("the default value of Object is " + obj);
trace("the default value of Object == 'undefined' : " + (obj == undefined));
trace("the default value of Object === 'undefined' : " + (obj === undefined));

//未初期化のString
var str:String;

//AS2ではundefined、AS3ではnullを返す。
//Objectと同じ。
trace("\nString ---------");
trace("the default value of String is " + str);
trace("the default value of String == 'undefined' : " + (str == undefined));
trace("the default value of String === 'undefined' : " + (str === undefined));

/* output
 * Number ---------
 * the default value of Number is NaN
 * the default value of Number is 'undefined' : false
 * the default value of Number is 'NaN' : true
 * 
 * Object ---------
 * the default value of Object is null
 * the default value of Object == 'undefined' : true
 * the default value of Object === 'undefined' : false
 * 
 * String ---------
 * the default value of String is null
 * the default value of String == 'undefined' : true
 * the default value of String === 'undefined' : false
 * 
*/

カテゴリー: AS3fromAS2 | コメントする

Tools4Layer

このあいだ(といってもだいぶ前になるけど)にエントリーした
‘タイムライン派のためのちょっとだけ便利なJSFLコマンド’のエクステンションが、
Adobe Exchangeで公開されました。
いくつかのバグが修正されているので、興味がある方はこちらの方をダウンロードして下さい。

名前がおかしいから変えろと言われたり、英語のドキュメント用意せいっていわれたりして、結構めんどうでした。
こんなちっちゃなツールでもこんなに大変なのに、よくみんなライブラリレベルのものを公開できるなぁ。
頭が下がります。

Tools4Layer

カテゴリー: flash | 1件のコメント