2010年11月27日土曜日

flex-mojos と asdoc

公式ページ
http://flexmojos.sonatype.org/

flexmojos-maven-plugin のある場所
http://repository.sonatype.org/content/groups/flexgroup/org/sonatype/flexmojos/flexmojos-maven-plugin/

com.adobe.flex.framework系のある場所
以下あたりを参考にして、バージョンを決めればよさそう。
https://repository.sonatype.org/content/groups/flexgroup/com/adobe/flex/framework/framework/

mvn test と mvn verify ができる最低限の pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    
    <!-- pom.xml参考 : http://dev.yoolab.org/maven/content/repositories/releases/org/as3commons/as3commons-project/1.0.1/as3commons-project-1.0.1.pom -->
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.objectfanatics</groupId>
    <artifactId>sandbox</artifactId>
    <packaging>swc</packaging>
    <name>ObjectFanatics Sandbox Project</name>
    <version>0.0.1-SNAPSHOT</version>
    <inceptionYear>2010</inceptionYear>
    <url>http://www.objectfanatics.com/</url>
    <description>ObjectFanatics Sandbox Project</description>
    
    <properties>
        <!-- flex-mojos version -->
        <flex-mojos.version>3.5.0</flex-mojos.version>

        <!-- flex version -->
        <flex.version>3.5.0.12683</flex.version>

        <!-- flashplayer version -->
        <flashplayer.version>${flashplayer.version.major}.${flashplayer.version.minor}.${flashplayer.version.revision}</flashplayer.version>
        <flashplayer.version.major>10</flashplayer.version.major>
        <flashplayer.version.minor>0</flashplayer.version.minor>
        <flashplayer.version.revision>0</flashplayer.version.revision>
    </properties>
    
    <!-- 開発者情報 -->
    <developers>
        <developer>
            <id>makoto.sato</id>
            <name>Makoto Sato</name>
            <email>makoto.sato [at] objectfanatics.com</email>
            <url>http://www.objectfanatics.com</url>
            <organization>ObjectFanatics Ltd.</organization>
            <organizationUrl>http://www.objectfanatics.com</organizationUrl>
            <timezone>+9</timezone>
        </developer>
    </developers>

    <dependencies>
        
        <!-- Player/Air Global dependency. -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>playerglobal</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
            <classifier>${flashplayer.version.major}</classifier>
            <!--<classifier>${flashplayer.version.major}.${flashplayer.version.minor}</classifier>-->
        </dependency>
        
        <!-- for unit testing. http://asunit.org/ -->
        <dependency>
            <groupId>com.asunit</groupId>
            <artifactId>asunit</artifactId>
            <version>20071011</version>
            <type>swc</type>
            <scope>test</scope>
        </dependency>
        
        <!-- Dependencies for Flex. This includes flex classes. ex: ListCollectionView. -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
        </dependency>
        
        <!-- Dependencies for Flex. This includes resource bundle. Not having this dependency causes some errors. ex: [ERROR] Unable to resolve resource bundle "collections" for locale "en_US". -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <type>rb.swc</type>
            <scope>external</scope>
        </dependency>

        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>utilities</artifactId>
            <version>${flex.version}</version>
            <type>swc</type>
            <scope>external</scope>
        </dependency>

        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>flex</artifactId>
            <version>${flex.version}</version>
            <type>swc</type>
            <scope>external</scope>
        </dependency>

        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>zip</type>
            <classifier>configs</classifier>
        </dependency>
        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>rpc</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
        </dependency>
        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>rpc</artifactId>
            <version>${flex.version}</version>
            <type>rb.swc</type>
            <scope>external</scope>
        </dependency>

    </dependencies>
    
    <build>
        <sourceDirectory>src/main/actionscript</sourceDirectory>
        <testSourceDirectory>src/test/actionscript</testSourceDirectory>
        <defaultGoal>test</defaultGoal>

        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.sonatype.flexmojos</groupId>
                <artifactId>flexmojos-maven-plugin</artifactId>
                <version>${flex-mojos.version}</version>
                <extensions>true</extensions>
                <dependencies>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>compiler</artifactId>
                        <version>${flex.version}</version>
                        <type>pom</type>
                    </dependency>
                </dependencies>
                <configuration>
                    <configurationReport>true</configurationReport>
                    <targetPlayer>${flashplayer.version}</targetPlayer>
                    <locales>
                        <param>en_US</param>
                    </locales>
                    <updateSecuritySandbox>true</updateSecuritySandbox>
                    <includeAsClasses>
                        <source>
                            <directory>src/main/actionscript</directory>
                        </source>
                    </includeAsClasses>
                </configuration>
                </plugin>
                <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
            </plugin>
        </plugins>

    </build>
    
    <repositories>
        <repository>
            <id>flex-mojos-repository</id>
            <url>http://repository.sonatype.org/content/groups/flexgroup</url>
        </repository>
    </repositories>
    
</project>

asDocの生成
flex-mojosでやろうとしたが、3系だとVectorとかのクラスを認識せず、4系だとspark.cssが見つからないとか言われる。本家のバグレポートでは、3系でバグレポートがでてるのに4.0でFixedということでバグがクローズされてる。このような開発状況を見るかぎりは flex-mojo を利用するのはリスクが高いですね、、、。今回はとりあえずasDocの生成をflex-sdkを直接使って行うだけに留めるけど、将来的には構成管理全般からflex-mojosを外すことにしよう。
"C:\Program Files\flex_sdk_4.1.0.16076\bin\asdoc.exe" -source-path . -doc-sources .
ちなみに、as3のソースコードの内容によって以下のようなエラーが出ることがある。
エラー: toplevel.xml を作成できませんでした : String index out of range: -41
原因は未確認。

2010年11月10日水曜日

非同期呼び出しについてのメモ

非同期呼び出しが絡んだ設計方法についてのメモです。

※以下、実装がマルチスレッドなのかシングルスレッドなのかは無視して考えましょう。

いい感じの本

Enterprise Integration Patterns とかいい感じっす。ECMA Script 系で必要とされるような非同期処理を扱う雰囲気の本ではなくて JMS や MQ 系のメッセージイングサーバを意識した本なのですが、最初の方の章が非同期処理に関する説明がいい感じで載っています。

おいらも読みなおしたいんだけど、日本に置いてきたので今は読めません(´・ω・`)

同期呼び出しのみの場合
スレッド1:処理A -> 処理B -> 処理C のように実行される。

ある処理が非同期処理に依存する場合

すべての処理が非同期だが、すべての処理が依存関係を持つ場合。
スレッド1:処理Aに処理Bをコールバック登録してフォーク 
スレッド2:     処理A実行 -> 処理Bに処理Cをコールバック登録してフォーク
スレッド3:            処理B実行 -> 処理Cにフォーク
スレッド4:                   処理C実行
この手の処理が、かなり面倒。そもそも概念的に非同期処理でないものを実装上強引に非同期処理にしているだけであり、同時並行処理のメリットが全く無い(シングルスレッド環境などの制約条件下でしかたなく対応するケースがほとんどだと思われ)。しかし、依存する処理をコールバックに変換するだけなので、同期呼び出しの場合からの機械的な変換は可能。この手の設計になる場合、同期処理で擬似コードを書いてから、その後機械的に非同期処理に変換したほうがいいかも。

非同期呼び出しにより処理がフォークする場合

各処理に依存関係がない場合は以下のように4つのスレッドが別々に動作する。
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A
スレッド3:                               -> 処理B
スレッド4:                                               → 処理C
スレッド1の動作は一瞬にして行われるので、処理A,B,Cはほぼ同時に開始する。このような非同期処理は、人間から見てもそこそこ直感的。多くの場合シーケンシャルに動作させた場合よりも高い性能が得られる。

非同期呼び出し処理がジョインする場合

処理A,B,Cのすべてが完了したあとに次の処理が行われる場合の例
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A -> 合流条件確認 -> (失敗)
スレッド3:                               -> 処理B                         -> 合流条件確認 -> (成功) -> 合流後の処理
スレッド4:                                                -> 処理C -> 合流条件確認 -> (失敗)
非同期処理のパフォーマンスを享受しつつ、処理の同期もおこなってしまうというおいしいところ取りですね。ジョインする条件の確認や、その条件を構成する情報の登録などをする必要があるのでコーディングレベルでは少しめんどくさいのですが、事前に設計をきちんとやっておけば問題ないでしょう。

設計方針考察

まずは、fork と join の発生する処理は避けて実装する。非同期処理を利用するとしても陸上のリレーのようにコールバックを用いて処理を継続させる方法であれば、join 条件のチェックが必要無いので設計がシンプルになる。この方法で、一通り実装して、実装した機能が仕様を満たしていることを確認する。

以下書きかけ。
  1. 設計対象の1つの処理を決定する。
  2. 処理内にて順次処理とフォークする処理とジョイン後の処理を、最外殻のレベルで分類し、各々の処理の塊をグループとする。
  3. 上記グループのうち、最外殻のジョイン処理とそれにジョインされる処理を1つのグループとして扱う。
  4. 複数のグループが残った場合、上記の各グループをメソッド化し、それそれを順次実行する。(各々が依存関係を持たないため並行動作が可能)
  5. 単一の順次処理の場合はそのまま記述すればよい。非同期処理は必要ない。
  6. 単一のジョインされるグループが残った場合、ジョイン実行条件のための情報(ジョイン実行条件判断クロージャから見えるスコープのフラグなど)を用意し、ジョイン実行条件を判断するクロージャを用意し、ジョイン実行条件のための情報の変更を通知するためのクロージャを用意し、ジョイン後の処理のクロージャを用意し、残りのグループを各メソッドとして作成して引数にジョイン実行条件判断用のクロージャと情報通知用クロージャとジョイン後の処理のクロージャを渡すようにする。
以降、上記の1からをネストして行う。
この説明じゃおいら自身以外にはわからんよねw まぁ、個人的なメモなので、、、。

まとめ

同一スレッドでの順次処理、フォーク、ジョインの3種類を適切に考えて設計すればよいということですね。

このような概念を用いた設計はプログラミング言語で記述することは直感的ではないと思うので(少なくともおいらには)、コーディングに入る前にアクティビティ図のような形で設計してみたほうがよいと思います。

2010年11月4日木曜日

Flash IDE 上でのアルファを変えるだけの簡単なボタンの作成

今回は、Flashの勘所を友人に教わりつつ、簡単なボタンのコンポーネントを作成してみました。

概要
任意のSpriteを継承したオブジェクトに対して、マウスのロールオーバー時に alpha の値を変更する振る舞いを付加するコンポーネントおよびライブラリです。

仕様
AlphaTransitiveButtonクラス
  • 概要
    • ロールオーバー時に alpha が変化する Sprite です。
    • ボタンモードはオンになります。
  • メリット
    • Flash IDE 上のシンボルからリンクするだけで、シンボルに対してロールオーバー時に alpha が 0.5 に変化する振る舞いを与えることができます。
    • 自前の ActionScript を作成する必要がありません。
  • 備考
    以下の場合、AlphaTransitiveButton クラスを使用せずに ButtonUtils の addXxxBehavior() 系のメソッドを利用の検討を推奨します。
    • 既に継承を使用しているために継承が使えない場合。
    • Alpha を独自に設定したい場合。
    • 継承を使いたくない場合。(例:10種類の振る舞いを追加するために10回継承するということを避けたい場合)
ButtonUtilsクラス
  • 概要
    • 指定された Sprite クラス(もしくは Sprite を継承したクラス)にロールオーバー時に alpha が変化する振る舞いを付加します。
    • ボタンモードはオンになります。
  • 備考
    • Flash IDE 上のシンボルに対して本メソッドのデフォルトの振る舞いを追加する場合は AlphaTransitiveButton クラスの利用も検討してみてください。
    サンプル

    • 上のボタンは AlphaTransitiveButton クラスにリンクしています。
    • 下のボタンは、ボタンのシンボルの1フレーム目で ButtonUtils#addAlphaTransitiveButtonBehavior(target, rollOverAlpha) を呼び出し、rollOverAlpha を0.8に設定しています。
    ソース

    AlphaTransitiveButtonクラス
    package {
        
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        
        /**
         * ロールオーバー時に alpha が変化する Sprite です。
         * ボタンモードはオンになります。
         * 
         * メリット
         *  ・ Flash IDE 上のシンボルからリンクするだけで、シンボルに対してロールオーバー時に alpha が 0.5 に変化する振る舞いを与えることができます。
         *  ・ 自前の ActionScript を作成する必要がありません。
         * 
         * 備考
         *  ・ 以下の場合、AlphaTransitiveButton クラスを使用せずに ButtonUtils の addXxxBehavior() 系のメソッドを利用の検討を推奨します。
         *    ・ 既に継承を使用しているために継承が使えない場合。
         *    ・ Alpha を独自に設定したい場合。
         *    ・ 継承を使いたくない場合。(例:10種類の振る舞いを追加するために10回継承するということを避けたい場合)
         * 
         * @see ButtonUtils#addAlphaTransitiveButtonBehavior(Sprite, Number)
         */
        public class AlphaTransitiveButton extends Sprite {
            
            public function AlphaTransitiveButton() {
                ButtonUtils.addAlphaTransitiveButtonBehavior(this);
            }
        }
    }
    

    ButtonUtilsクラス
    package {
        
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        
        /**
         * ボタン関連のユーティリティクラスです。
         */
        public class ButtonUtils extends Sprite {
            
            /**
             * 指定された Sprite クラス(もしくは Sprite を継承したクラス)にロールオーバー時に alpha が変化する振る舞いを付加します。
             * ボタンモードはオンになります。
             * Flash IDE 上のシンボルに対して本メソッドのデフォルトの振る舞いを追加する場合は AlphaTransitiveButton クラスの利用も検討してみてください。
             * 
             * 注: target が Dynamic Text の TextArea の場合など、特定の条件下でマウスオーバー時にマウスカーソルの形状が変化しない場合があります。(flash側の問題)
             * 
             * @param target 対象となる Sprite
             * @param rollOverAlpha ロールオーバー時の alpha の値
             * @see AlphaTransitiveButton
             */
            public static function addAlphaTransitiveButtonBehavior(target:Sprite, rollOverAlpha:Number = 0.5) {
                target.buttonMode = true;
                target.addEventListener(MouseEvent.ROLL_OVER, function(e:MouseEvent):void {
                    e.currentTarget.alpha = rollOverAlpha;
                });
                target.addEventListener(MouseEvent.ROLL_OUT, function(e:MouseEvent):void {
                    e.currentTarget.alpha = 1;
                });
            }
        }
    }
    
    アーカイブ

    2010年11月1日月曜日

    Flash IDE 上でのMVCについて考えてみた

    Flash IDE 上でのMVCについて考えてみた(その2) が最新です。

    近々、Flash IDE で開発を行うことになりました。

    開発に入る前の実験として、ステージ上に色々とコンポーネントを配置して、Documentクラスに ActionScript のコードを記述して、なんとなく動くものを作っていたというのがこれまでの話。しかし、これでは保守性に難があるなぁと思う今日この頃。

    保守性を向上させようと考えた場合に、すぐに思い浮かぶのはコンポーネント化。コンポーネント内部はシンプルなMVCがよさそう。

    ということで、実験してみました。

    方式
    • Flash IDE 上で作成したシンボルから as ファイルにリンクを張る方式。
    • 名前空間は Flash IDE 上の LIBRARY 内にフォルダを作成して対応する。

    今回作成するサンプル
    • TextArea に最初 0 が表示され、マウスでクリックするたびに値が 1 増えるアプリ。


    シンボル
    • LIBRARY 内のフォルダ名
      • com.objectfanatics.samplecomponent
    • シンボル名
      • View
    • リンク先のクラス
      • com.objectfanatics.samplecomponent.view.View

    クラス構成
    • com.objectfanatics.samplecomponent.model パッケージ
      • com.objectfanatics.samplecomponent.model.Model
        • コンポーネントのモデルのインタフェース。
        • モデルは可能な限り実装を意識しないで設計したいため、あえてインタフェースを作成。
      • com.objectfanatics.samplecomponent.model.ModelImpl
        • コンポーネントのモデルの実装。
      • com.objectfanatics.samplecomponent.model.ModelListener
        • Model の変更内容を監視するためのリスナーインタフェース
    • com.objectfanatics.samplecomponent.view パッケージ
      • com.objectfanatics.samplecomponent.view.View
        • Flash IDE 上で作成したシンボルからリンクされるクラス。
        • View として存在すると同時に、コンポーネント全体を構築する役割も持つ。
      • com.objectfanatics.samplecomponent.view.ModelListenerImpl
        • Model の変更を監視し、Model の変更内容に応じて View を更新するクラス。
        • ModelListenerインタフェースの実装クラス。
    • com.objectfanatics.samplecomponent.controller パッケージ
      • com.objectfanatics.samplecomponent.controller.Controller
        • View の内部要素に対してリスナーを登録し、イベントを監視する。
        • View からのイベントを処理し、必要に応じて Model を更新する。 

    クラスのソース
    package com.objectfanatics.samplecomponent.model {
     
     public interface Model {
      
      /**
       * ModelListener をセットします。
       * @param modelListener ModelListener
       */
      function setModelListener(modelListener:ModelListener):void;
      
      /**
       * 値を返します。
       * @return 値
       */
      function getNumber():uint;
      
      /**
       * 値を1増やします。
       */
      function increment():void;
     }
    }
    

    package com.objectfanatics.samplecomponent.model {
     
     public class ModelImpl implements Model {
      
      private var modelListener:ModelListener;
      
      /**
       * 保持される値
       */
      private var number:uint = 0;
      
      /**
       * Model#getNumber():uint の実装
       */
      public function getNumber():uint {
       return number;
      }
      
      /**
       * Model#setModelListener(modelListener:ModelListener):void の実装
       */
      public function setModelListener(modelListener:ModelListener):void {
       this.modelListener = modelListener;
      }
      
      /**
       * Model#increment():void の実装
       */
      public function increment():void {
       this.modelListener.numberChanged(++number);
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.model {
     
     /**
      * Model の変更内容を監視するためのリスナー
      */
     public interface ModelListener {
      
      /**
       * 値が変更された場合に呼び出されます。
       * @param number 現在の値
       */
      function numberChanged(number:uint);
     }
    }
    

    package com.objectfanatics.samplecomponent.view {
     
     import flash.display.Sprite;
     
     import com.objectfanatics.samplecomponent.model.Model;
     import com.objectfanatics.samplecomponent.model.ModelImpl;
     import com.objectfanatics.samplecomponent.model.ModelListener;
     import com.objectfanatics.samplecomponent.controller.Controller;
     
     /**
      * FlashIDE 上の LIBRARY にて、フォルダ名が com.objectfanatics.samplecomponent、名前が View のシンボルに対応するクラス。
      * View という名前ですが、FlashIDE上で作成したシンボルからのリンクとなっているため、コンポーネント全体の初期化も行っています。
      * 純粋な View としての処理は、 Model には依存しますが Controller には依存しません。
      */
     public class View extends Sprite {
      
      private const model:Model = new ModelImpl();
      
      public function View():void {
       initView();
       model.setModelListener(new ModelListenerImpl(this));
       new Controller(this, model);
      }
      
      private function initView():void {
       this.numberText.text = "" + model.getNumber();
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.view {
    
     import com.objectfanatics.samplecomponent.model.ModelListener;
      
     /**
      * Model の変更内容を監視し View に反映させるための ModelListener の実装クラス。
      */
     public class ModelListenerImpl implements ModelListener {
      
      /**
       * 変更対象の View
       */
      private var view:View;
      
      /**
       * コンストラクタ
       * @param view 変更対象の View
       */
      public function ModelListenerImpl(view:View):void {
       this.view = view;
      }
      
      /**
       * Model の値が変更された時に呼び出され、View 上の表示を変更します。
       * @param currentNumber 現在のの Model の値
       */
      public function numberChanged(currentNumber:uint) {
       view.numberText.text = "" + currentNumber;
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.controller {
     
     import flash.events.MouseEvent;
     
     import com.objectfanatics.samplecomponent.view.View;
     import com.objectfanatics.samplecomponent.model.Model;
     
     public class Controller { 
      
      private var model:Model;
      
      private var view:View;
      
      public function Controller(view:View, model:Model):void {
       
       // Model と View の格納
       this.model = model;
       this.view = view;
       
       // View のクリックの検出設定
       view.addEventListener(MouseEvent.CLICK, function():void{model.increment()});
      }
     }
    }
    

    今回の所感

    単純な仕様のコンポーネントであれば、この方法でも十分な保守性が得られるのではないかと思いました。しかし、複雑になるとどうなるか全く見当も付きません。実際に複雑なケースに適用してみて実際に問題が生じたときに、その問題をシンプルに表現できる例題を作成して考察してみようと思います。

    全体的な仕組みとして再利用が効きそうなポイントが多々あるのですが、ことごとくうまくいきませんでした。たとえば、Viewクラスの内部は Template Method を適用すればコード量抑えられるしロジックを完全に隠蔽できるので保守性が上がりそうなものです。しかし、abstract class が使えないために Template Method のメリットがかなり小さくなってしまいます。通常のクラスで Template Method を試してもみたのですが、Generics が使えないために手動での強引な型変換が必要となり、ソースがかなり長く見辛くなってしまいました。

    まだ as3 を触り始めてまもないので、当面は粛々と実験していきたいと思います。

    アーカイブ