Adrian Parr’s Blog 에 올라온 ActionScript 3.0 Code 라이브러리 모음이다. 매우 유용해서 긁어왔다.

 

3D Engines

3D Game Engines

3D Animation Framework

3D Physics Engines

Animation Tweening Kits

2D Physics Engines

Security

Audio Libraries

Particle Systems

Data Visualization

Loading Kits

OOP Frameworks

Other APIs and libraries

 

출처 : http://www.adrianparr.com/?p=83

1. Application Domain 사용 예제

 

지난 호에서 우리는 Application domain에 대해서 알아보았다. 지금까지 설명한 내용을 확인하기 위해 몇가지 예제를 만들고자 한다.

 

 [그림 1] 메인과 모듈에서 정의되어 있는 Sprite 클래스

 

[그림 1]은 메인 프로그램과 모듈 프로그램에 각각 정의되어 있는 Sprite를 확장한 클래스를 보여주고 있다. 메인은 Yellow, Blue를 가지며 둥근 모양이다. 모듈은 Blue, Red, Green을 가지며 사각형 모양이다. 서로 클래스 명이 중복되는 것은 Blue이다.

 

메인과 모듈에서 모두 아래와 같은 형태로 4개의 Sprite를 확장한 클래스의 정의를 참조한다.

var ClassRef:Class =
ApplicationDomain.currentDomain.getDefinition("클래스명") as Class;

 

클래스 참조값을 이용해 인스턴스를 생성하여 메인과 모듈에서 그림을 그려준다. 모듈을 불러올 때 메인 프로그램의 Application domain의 설정에 따라 메인과 모듈에 정의된 Sprite 클래스들을 참조 권한이 달라지게 될 것이다.

 

아래 코드는 모듈 프로그램에서 정의된 BlueSprite 클래스의 정의다. draw() 함수를 호출하면 30×30의 파란색 사각형을 그려준다.

 

 

//BlueSprite.as – 모듈 프로그램에서 정의됨
package com.jidolstar.blue
{
        import flash.display.Sprite;

        public class BlueSprite extends Sprite
        {
               public function BlueSprite()
               {
                       super();
               }

               public function draw():void
               {
                       graphics.clear();
                       graphics.beginFill(0x0000ff, 1);
                       graphics.drawRect(0,0,30,30);
                       graphics.endFill();
               }
        }
}

 

아래 코드는 메인 프로그램에서 정의되는 BlueSprite이다. 모듈에서의 정의와 달리 draw()함수를 호출하면 파란색 원을 그려준다. 메인과 모듈에서 정의한 이름(BlueSprite)은 같지만 그 기능은 다르다는 것을 기억하자.

 

//BlueSprite.as – 메인 프로그램에서 정의됨
package com.jidolstar.blue
{
        import flash.display.Sprite;
        public class BlueSprite extends Sprite
        {
               public function BlueSprite()
               {
                       super();
               }
               public function draw():void
               {
                       this.graphics.clear();
                       this.graphics.beginFill(0x0000ff, 1);
                       this.graphics.drawCircle(15,15,15);
                       this.graphics.endFill();
               }
        }
}

 

이처럼 메인과 모듈에 Sprite 확장 클래스를 만들어둔다.

모듈 프로그램은 다음처럼 작성된다.

 

 

//TestModule.as
package {
	import com.jidolstar.blue.BlueSprite;
	import com.jidolstar.green.GreenSprite;
	import com.jidolstar.red.RedSprite;

	import flash.display.Sprite;
	import flash.system.ApplicationDomain;

	public class TestModule extends Sprite
	{
		private var blue:BlueSprite;
		private var red:RedSprite;
		private var green:GreenSprite;

		public function TestModule()
		{
			super();

			var BlueSpriteRef:Class;
			var RedSpriteRef:Class;
			var GreenSpriteRef:Class;
			var YellowSpriteRef:Class;
			var msg:String = "";
			var sprite:*;
			var x:int = 0;

			try
			{
				var ClassRef:Class = ApplicationDomain.currentDomain.getDefinition("클래스명") as Class;
				BlueSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.blue.BlueSprite") as Class;
				sprite = new BlueSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				addChild( sprite );
			}
			catch (e:Error)
			{
				msg+= "Module에서 com.jidolstar.blue.BlueSprite 로드 실패n"
			}

			try
			{
				RedSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.red.RedSprite") as Class;
				sprite = new RedSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				addChild( sprite );
			}
			catch (e:Error)
			{
				msg += "Module에서 com.jidolstar.red.RedSprite 로드 실패n"
			}                       

			try
			{
				GreenSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.green.GreenSprite") as Class;
				sprite = new GreenSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				addChild( sprite );

			}
			catch (e:Error)
			{
				msg += "Module에서 com.jidolstar.green.GreenSprite 로드 실패n"
			}       

			try
			{
				YellowSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.yellow.YellowSprite") as Class;
				sprite = new YellowSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				this.addChild( sprite );
			}
			catch (e:Error)
			{
				msg += "Module에서 com.jidolstar.yellow.YellowSprite 로드 실패n"
			}

			trace(msg);
		}
        }
}

 

메인, 모듈에서 정의되는 BlueSprite, RedSprite, GreenSprite, YellowSprite 클래스 정의를 모듈의 current domain으로부터 얻어와 성공하면 그림을 그려주는 형태로 되어 있다. 특별히 모듈에서는 BlueSprite, RedSprite, GreenSprite가 정의되도록 아래와 같은 코드가 있다.

 

private var blue:BlueSprite;
private var red:RedSprite;
private var green:GreenSprite;

 

메인 프로그램은 모듈과 거의 동일한 모습으로 만들어진다. 다른 점은 모듈을 읽어올 수 있는 부분과 Application domain을 설정하는 부분이 추가가 된다. Application domain을 적용하는 상황이 총 세가지이므로 중복되는 코드를 줄이기 위해 Base 클래스를 만들고 이 Base 클래스를 확장해 3개의 상황에 맞게 메인 프로그램 A,B,C를 작성한다. 그럼 Base 프로그램을 보자.

 

//MainAppBase.as
package
{
import com.jidolstar.blue.BlueSprite;
import com.jidolstar.yellow.YellowSprite;

import flash.display.DisplayObject;
import flash.display.Loader;
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.text.TextFormat;

public class MainAppBase extends Sprite
{
	private var blue:BlueSprite;
	private var yellow:YellowSprite;
	private var loader:Loader;
	protected var applicationDomain:ApplicationDomain;

	public function MainAppBase()
	{
		super();

		loader = new Loader();
		var request:URLRequest = new URLRequest("TestModule.swf");
		var context:LoaderContext = new LoaderContext();
		context.applicationDomain = applicationDomain;
		loader.contentLoaderInfo.addEventListener( Event.COMPLETE, completeHander );
		loader.load(request,context);
	}

	private function completeHander(event:Event):void
	{
		var BlueSpriteRef:Class;
		var RedSpriteRef:Class;
		var GreenSpriteRef:Class;
		var YellowSpriteRef:Class;
		var msg:String = "";
		var sprite:*;
		var x:int = 0;
		var ModuleRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("TestModule") as Class;
		var module:DisplayObject = new ModuleRef();
		//var module:DisplayObject = event.target.content as DisplayObject
		module.x = 0;
		module.y = 50;
		this.addChild( module );

		loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, completeHander );
		loader.unload();
		loader = null;

		try
		{
			BlueSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.blue.BlueSprite") as Class;
			sprite = new BlueSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg+= "Main에서 com.jidolstar.blue.BlueSprite 로드 실패n"
		}

		try
		{
			RedSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.red.RedSprite") as Class;
			sprite = new RedSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg += "Main에서 com.jidolstar.red.RedSprite 로드 실패n"
		}                       

		try
		{
			GreenSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.green.GreenSprite") as Class;
			sprite = new GreenSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg += "Main에서 com.jidolstar.green.GreenSprite 로드 실패n"
		}       

		try
		{
			YellowSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.yellow.YellowSprite") as Class;
			sprite = new YellowSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg += "Module에서 com.jidolstar.yellow.YellowSprite 로드 실패n"
		}

		trace(msg);
	}
}
}

 

 

completeHandler() 함수를 보면 모듈 프로그램에서 정의했던 것과 별반 다를 바 없다. 단, 불러온 모듈을 화면에 표시하기 위해 아래와 같은 코드가 추가가 되어 있다.

 

var module:DisplayObject = event.target.content as DisplayObject
module.x = 0;
module.y = 50;
this.addChild( module );

 

 

생성자에서 Loader 클래스를 이용해 해당 모듈 SWF를 불러오도록 만들었다. Application domain을 protected로 값을 받아 설정할 수 있도록 만든 것을 확인하기 바란다. 이렇게 만든 의도는 이 Base 클래스를 확장한 메인 프로그램의 생성자에 application domain을 미리 설정하라는 것을 의미한다.

 

아래는 앞서 만든 Base 클래스를 이용해 application domain을 새롭게 정의해서 사용하는 예이다. 생성자에 applicationDomain을 어떻게 설정했는지 확인하면 되겠다.

 

//MainAppUsageA.as
package {
	import flash.system.ApplicationDomain;
	public class MainAppUsageA extends MainAppBase
	{
		public function MainAppUsageA()
		{
			applicationDomain = new ApplicationDomain();
			super();
		}
	}
}
//MainAppUsageB.as
package {
	import flash.system.ApplicationDomain;
	public class MainAppUsageA extends MainAppBase
	{
		public function MainAppUsageA()
		{
			applicationDomain = ApplicationDomain.currentDomain;
			super();
		}
	}
}
//MainAppUsageC.as
package {
	import flash.system.ApplicationDomain;
	public class MainAppUsageA extends MainAppBase
	{
		public function MainAppUsageA()
		{
			applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
			super();
		}
	}
}

 

위에서 제시한 코드는 두 개의 ActionScript3 프로젝트를 만들어서 아래와 같은 구성으로 만든다.

 

[그림 2] 메인과 모듈 프로젝트 구성

 

위처럼 모듈 SWF파일을 메인 쪽에 복사해야 모듈을 불러서 동작시킬 수 있다. 3개의 메인 프로그램을 단독으로 실행할 수 있도록 하기 위해, 프로젝트 폴더 명에서 마우스 오른쪽 버튼을 눌러 컨텍스트 메뉴의 Properties를 클릭해서 뜨는 창의 좌측에 ActionScript Applications에 default로 지정된 것 말고도 나머지 2개를 추가해준다.

 

이렇게 하면 기본적인 테스트 환경이 완료가 된다. 위의 MainAppUsageA.as를 선택하고 실행해보자.

 

[그림 3] 메인과 모듈이 서로 다른 application domain 일 때 실행 화면

 

이것은 new ApplicationDomain()를 이용해 모듈의 application domain을 설정했을 때 결과이다. 메인과 모듈간에 application domain이 다르기 때문에 상대방에서 정의한 클래스를 참조할 수 없다. 윗부분에 메인부분이다. 메인에서 정의한 BlueSprite와 YellowSprite에 대한 정의만 사용하여 그림을 그렸다. 아래 부분은 모듈로 모듈에서 정의한 BlueSprite, RedSprite, GreenSprite 를 이용해 그림이 그려졌다는 것을 알 수 있다.

MainAppUsageB.as를 선택하고 실행해보자.

 

 

[그림 4] 메인과 모듈이 서로 같은 application domain 일 때 실행 화면

 

이것은 ApplicationDomain.currentDomain을 이용해 모듈의 application domain을 설정했을 때 결과이다.

위쪽의 메인 프로그램에서 그려준 것을 보면 자신이 정의한 BlueSprite와 YellowSprite를 이용해 둥근 모양의 그림을 그렸다. 또한 같은 application domain이므로 모듈에서 정의한 RedSprite와 GreenSprite를 이용해 사각형 모양의 그림도 그렸다. 메인 프로그램과 모듈에는 BlueSprite가 중복되었다는 것을 [그림 2]에서 미리 언급했었다. 이 경우 메인에 이미 BlueSprite가 정의된 상태였기 때문에 모듈의 BlueSprite는 무시되었다는 것을 확인할 수 있다.

 

MainAppBase.as의 completeHandler() 함수를 잘 살펴보면 loader.unload()를 호출하고 나서 모듈의 SWF파일을 Unload했음에도 불구하고 모듈에서 정의한 RedSprite, GreenSprite를 그대로 사용하고 있다는 것을 볼 수 있다. 즉, 부모의 application domain에 등록된 자식의 Class 정의는 그 자식이 Unload되더라도 가비지 컬렉션 대상이 되지 않는다.

 

마지막으로 MainAppUsageC.as를 선택하고 실행해보자.

 

 

[그림 5] 메인과 모듈이 application domain이 부모-자식 관계일 때 실행 화면

 

이것은 new ApplicationDomain(ApplicationDomain.currentDomain)를 이용해 모듈의 application domain을 설정했을 때 결과이다. Application domain이 부모-자식관계가 되어 메인 프로그램에서는 모듈에서 정의한 클래스를 참고할 수 없으므로 RedSprite와 GreenSprite 클래스의 정의를 읽을 수 없다. 하지만 모듈은 자식 application domain 에 있기 때문에 부모의 BlueSprite와 YellowSprite를 사용한다. 중복정의된 BlueSprite는 이미 메인 프로그램에 정의되어 있던 BlueSprite로 대체되어진다.

 

 

2. 정리하며

 

Application domain에 대해서 자세하게 알아보았다. 모듈화 프로그래밍을 하기 위해서는 반드시 숙지해야 하는 부분이다. ActionScript 3.0로 모듈 프로그래밍을 할 때는 Loader 클래스를 이용해야하는데 상당히 불편한 점이 많다. 하지만 Flex에서는 Module 및 ModuleManager, ModuleLoader 등을 이용해 모듈 프로그래밍을 더욱 간편하게 할 수 있도록 도와주고 있다. 하지만 내부적으로는 Loader 클래스를 사용하고 있기 때문에 Application domain의 개념을 알고 있어야 이 클래스들을 적절히 잘 사용할 수 있다.

 

<참고내용>

 

이 글은 adobeflex.co.kr에 기술문서로 올린 글입니다.

TextField에 TextFormat을 적용할 때 TextField의 defaultTextFormat 속성과 setTextFormat() 메소드를 사용한다. 예전에 차이점을 구분했었는데 맨날 Flex만 하다보니 까먹었었다. 이것때문에 1시간 정도 삽질했다. ㅡㅡ;

 

아래내용을 보고 defaultTextFormat ,setTextFormat()의 차이점을 명확히 이해하자.

 

http://butterguy.tistory.com/entry/텍스트-필드에-서식설정하기

ActionScript 3.0에는 MatrixTransform 클래스가 있다.

 

Matrix는 일종의 변환행렬(Transformation Matrix)이다. 3×3 행렬로 이동(translation), 확대/축소(scaling), 회전(rotation), 기울이기(shearing)등을 구현할 수 있다. Papervision3D나 Away3D와 같이 2D화면에 3D 효과가 가능했던 것은 Bitamp과 Matrix가 있기 때문이다. 이들 클래스를 어떻게 잘 조작하느냐에 따라 풍성한 화면효과를 구현할 수 있다.

 

Transform은 DisplayObject 계열의 객체에 만들어진 Matrix를 적용하는 역할을 한다. 즉 Matrix는 이동/회전등의 변환수단으로 작용하고 실제 이 Matrix를 이용해서 DisplayObject에 적용하는 것은 Transform인 것이다.

 

DisplayObject에는 tranform 속성이 있고 transform에는 matrix 속성이 있다. 그러므로 만들어진 Matrix를 DisplayObject에 적용하기 위해 다음과 같이 시도할 수 있다.

 

var content:DisplayObject = new Sprite as DisplayObject;
var mat:Matrix = new Matrix();
(mat 조작 생략)
content.tranform.matrix = mat;

 

 

위와 같이 하는 것만으로도 이동,확대/축소,회전,shearing,거울효과등을 적용할 수 있게 된다.

그럼 어떻게 Matrix를 만들어야 원하는 동작을 만들어낼 수 있을까? 다행히도 Matrix는 사용자가 조작하기 아주 쉽게 만들어져 있다.

 

회전을 하려면 Matrix의 rotate( 라디안각도 ), 이동을 하려면 Matrix의 translate( dx, dy ), 확대/축소하려면 Matrix의 scale( sx, sy ) 함수를 사용하면 된다.

 

보통 이 Matrix를 이용하지 않고도 DisplayObject의 rotation, x, y, scaleX, scaleY 속성을 사용해도 될때가 있다. 사릴 이 속성들은 결국 Matrix로 구현된다. Matrix가 다루기 어렵기 때문에 쉬운 사용을 위해 기본적인 인터페이스는 DisplayObject에 만들어준 것 뿐이다. 하지만 이 외에 사진의 중심으로 회전하던가 거울효과를 적용시키던가 shearing과 같은 효과를 주려면 이들 속성만 가지고는 해결할 수 있는 방법이 없거나 있다고 하더라도 비효율적인 방법일 소지가 많다. 그러므로 고급적으로 DisplayObject를 가공하려면 Matrix의 사용방법과 그 원리에 대해서 익숙해져야 한다.

 

Matrix는 3×3로 구성된다고 했다. 2D인데 3×3 행렬을 이용하는 이유는 이동(translation)이 포함되어 있기 때문이다.

 

일단 전반적인 지식은 아래 링크들을 참고하기 바란다. 아래 내용만 잘 알아도 DisaplyObject 객체를 가지고 기하학적 변형을 위한 기초는 알 수 있다.

 

DisplayObject 객체의 중심을 그의 부모 (0,0)점에 위치하고 회전 및 확대/축소

DisplayObject객체 중심을 객체의 좌측상단점으로 이동하는 행렬 T, 회전행렬 R, 확대/축소행렬 S라고 하자.

 

DisplayObject는 항상 좌측상단이 기준점이 된다. 그러므로 DisplayObject의 중심점 이동->회전->확대/축소를 적용하면 되겠다. 그러므로 이들을 모두 적용할 수 있는 행렬은 다음과 같다.

 

 

M = S x R x T = R x S x T

 

더 명확히 표현하자면 아래와 같다. (아래식에서 각각의 변환행렬을 곱한 결과는 그 아래 실제 결과와 다르게 나왔다. 실제결과를 도출하기 위한 뭔가 다른 설정이 있는 것 같은데 본인은 발견하지 못했다. 혹시 아는분 댓글 부탁한다.)

 

 

 

여기서 θ는 회전각도 radian값이고 sx와 sy는 각각 x축, y축 확대/축소 비율이다. cx와 cy는 DisplayObject 객체의 중심좌표값이다.

 

A=[x,y,1]값을 변환을 거쳐서 나온 결과 좌표값을 A’=[x’,y’,1]이라고 한다면 다음 관계가 성립한다.

 

A’= M x A

 

 

결국 중요한 것은 변환행렬 M을 만들어 내는 일이다.

 

그럼 위 행렬을 어떻게 DisplayObject에 적용할 수 있을까?

 

var content:DisplayObject = new Sprite as DisplayObject;
var cx:Number = content.width/2;
var cy:Number = content.height/2;
var sx:Number = 2;
var sy:Number = 3;
var theta:Number = 45 * Math.PI/180;
var mat:Matrix = new Matrix();
mat.translate( -cx, -cy );
mat.scale( sx, sy );
mat.rotate( theta );
content.tranform.matrix = mat;

위 코드처럼 하면 변환행렬 M을 DisplayObject에 적용한 것과 같다. 다음과 같이 해도 동일한 동작을 하게 된다.

 

var content:DisplayObject = new Sprite as DisplayObject;
var cx:Number = content.width/2;
var cy:Number = content.height/2;
var sx:Number = 2;
var sy:Number = 3;
var theta:Number = 45 * Math.PI/180;
var cos:Number = Math.cos( theta );
var sin:Number = Math.sin(theta);
var mat:Matrix = new Matrix();
mat.a = sx * cos;
mat.b = sy * sin;
mat.c = –sx * sin;
mat.d = sy * cos;
mat.tx = - sx * cx * cos + sy * cy * sin;
mat.ty = - sx * cx * sin - sy * cy * cos;
content.tranform.matrix = mat;

이런 원리를 잘 알아두면 앞으로 DisplayObject의 기하학적 변형을 위한 방법을 익히는 것 뿐아니라 속도향상에도 도움이 될 수 있겠다.

 

예제 애플리케이션 제작

위 설명을 토대로 사진 중심으로 회전, 확대/축소등이 가능한 DisplayObject 객체를 만들고 테스트 해볼 수 있는 예제를 만들어보자.


아래는 만들어진 테스트용 애플리케이션이다. (사진 주인공은 제 딸 예진이  입니다. ^^)

 (서버를 바꾼뒤 이미지 로드를 실패하고 있습니다. 아래 소스는 정상적으로 동작하는 것입니다.)

 

 

아래 클래스는 이동/스케일링/회전/거울효과를 테스트 하기 위한 것이다. 위에서 다 설명했으므로 특별히 분석은 안하도록 하겠다.

 

package
{
	import flash.display.DisplayObject;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.net.URLRequest;
	import flash.utils.setTimeout;

	public class TestSprite extends Sprite
	{
		private var container:Sprite;
		private var image:Loader;
		private var originalWidth:Number = 0;
		private var originalHeight:Number = 0;
		private var _scale:Number = 1;
		private var _rotation:Number = 0;
		private var _horizontalMirror:Boolean = false;
		private var _verticalMirror:Boolean = false;
		private var isApplyMatrix:Boolean = false;

		public function TestSprite()
		{
			super();

			container = new Sprite;
			addChild( container );

			image = new Loader;
			container.addChild( image );
			image.contentLoaderInfo.addEventListener( Event.COMPLETE, onComplete );
			image.load( new URLRequest( "http://jidolstar.com/blog/wp-content/uploads/2009/03/yaejin.jpg" ) );
		}

		private function onComplete( event:Event ):void
		{
			event.target.removeEventListener( Event.COMPLETE, onComplete );

			//원본 사진의 크기
			originalWidth = event.target.content.width;
			originalHeight = event.target.content.height;

			//Matrix적용
			applyMatrix( container );
		}

		private function applyMatrix( target:DisplayObject ):void
		{
			var mat:Matrix = target.transform.matrix;
			var cos:Number = Math.cos( _rotation * Math.PI/180 );
			var sin:Number = Math.sin( _rotation * Math.PI/180 );
			var cx:Number = originalWidth/2;
			var cy:Number = originalHeight/2;

			//단위행렬로 바꿈
			mat.identity();

			//거울 효과 적용
			if( _horizontalMirror )
			{
				mat.a = -1;
				mat.tx = originalWidth;
			}
			if( _verticalMirror )
			{
				mat.d = -1;
				mat.ty = originalHeight;
			}

			//widget의 (0,0)위치 조정
			mat.translate( -cx, -cy );

			//스케일 적용
			mat.scale( _scale, _scale );	

			//회전 적용
			mat.rotate( _rotation * Math.PI/180 );

			//mat.translate( cy, cy );

			/*
			//주석부분은 위에 mat.translate(),mat.scale(), mat.rotate()을 호출한것과 동일하게 동작한다. 단 거울효과를 적용했을때는 똑같지 않다. 같은 효과를 내려면 Matrix.concat()을 이용해 행렬곱을 실시하면 되겠다.
 			mat.a = _scale * cos;
			mat.b = _scale * sin ;
			mat.c = _scale * sin * -1;
			mat.d = _scale * cos;
			mat.tx = -cx * _scale * cos + cy * _scale * sin;
			mat.ty = -cx * _scale * sin - cy * _scale * cos;
			*/

			//Matrix 적용
			target.transform.matrix = mat;	

			this.width = target.width;
			this.height = target.height;
			this.dispatchEvent( new Event( Event.RESIZE ) );	

			isApplyMatrix = false;
		}

		public override function set rotation(value:Number):void
		{
			_rotation = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		} 

		public function set scale(value:Number):void
		{
			_scale = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		}	

		public function set horizontalMirror( value:Boolean ):void
		{
			_horizontalMirror = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		}

		public function set verticalMirror( value:Boolean ):void
		{
			_verticalMirror = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		}
	}
}

아래는 위에서 정의한 TestSprite를 사용하는 Flex Application이다.

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init()" backgroundGradientColors="[0,0]">
	<mx:VBox width="100%" height="100%">
		<mx:UIComponent id="container" width="100%" height="100%" resize="onResize()"/>
		<mx:Form>
			<mx:FormItem label="scale">
				<mx:HSlider id="sldScale" minimum="0.5" maximum="3" value="1" change="test.scale=sldScale.value" liveDragging="true"/>
			</mx:FormItem>
			<mx:FormItem label="rotation">
				<mx:HSlider id="sldRotation" minimum="0" maximum="360" value="0" change="test.rotation=sldRotation.value" liveDragging="true"/>
			</mx:FormItem>
			<mx:FormItem label="horizontal Mirror">
				<mx:CheckBox id="chHorizontalMirror" change="test.horizontalMirror = chHorizontalMirror.selected"/>
			</mx:FormItem>
			<mx:FormItem label="vertical Mirror">
				<mx:CheckBox id="chVerticalMirror" change="test.verticalMirror = chVerticalMirror.selected"/>
			</mx:FormItem>
		</mx:Form>
	</mx:VBox>
	<mx:Script>
		<![CDATA[
			private var test:TestSprite;
			private function init():void
			{
				test = new TestSprite();
				container.addChild( test );
				test.addEventListener( Event.RESIZE, onResizeTest );
				onResize();
			}

			private function onResize():void
			{
				if( test )
				{
					test.x = container.width/2;
					test.y = container.height/2;
				}
			}

			private function onResizeTest( event:Event ):void
			{
				var w:Number = test.width;
				var h:Number = test.height;
				var hw:Number = w/2;
				var hh:Number = h/2;
				test.graphics.clear();
				test.graphics.lineStyle( 1, 0xff0000, 1 );
				test.graphics.drawRect( -hw, -hh, w, h );
				test.graphics.moveTo( -hw, 0 );
				test.graphics.lineTo( hw, 0 );
				test.graphics.moveTo( 0, -hh );
				test.graphics.lineTo( 0, hh );
			}
		]]>
	</mx:Script>
	<mx:Style>
		global
		{
			color:#ffffff;
		}

		ToolTip
		{
			color:#000000;
		}
	</mx:Style>
</mx:Application>

 

위에서 보여준 것과 달리 좌측상단을 기준으로 하고 사진중심만 회전하고 싶은 경우에는 TestSprite 클래스에서 mat.rotate() 부분 아래에 mat.translate( cx, cy )만 추가하면 된다. 결국 사진중심을 회전하기 위해 이동후, 회전 및 스케일링을 거친다음에 다시 자신의 위치로 옮겨오는 작업이 추가되는 것이다.

 

원리를 알면 많이 고민 안하고도 적용할 수 있다는거…
산수 좀 나온다고 거부하면 다음에도 고생한다. ^^

 

 

 

스타플(http://starpl.com)에서 제공하는 위젯중에 아래와 같은 “내별정보위젯”이 있다.

 


 

마우스를 올리면 내 별에 방문하거나 별받기를 할 수 있다.

 

스타플의 방문페이지와 별받기페이지에 접속시키기 위해 ExternalInterface.call()과 navigateToURL()을 아래와 같이 사용했다.

 

 

새창띄워 방문하기

ExternalInterface.call("window.open", 경로, "_blank", ""); 

 

방문하기

var request:URLRequest = new URLRequest( 경로 );
flash.net.navigateToURL( request, "_parent");

 

하지만 이러한 것들도 flash 컨텐츠를 웹페이지에 추가할때 쓰는 <object>태그와 <embed> 태그의 allowScriptAccess 속성과 allowNetworking 속성을 어떻게 지정하느냐에 따라 사용가능여부가 달라진다.

 

가령 이들 속성은 아래와 같은 방법으로 쓰여질 수 있다.

 

<object width="425" height="344">
<param name="allowScriptAccess" value="never" />
<param name="allowNetworking" value="internal" />
<param name="movie" value="swf경로"></param>
<param name="allowFullScreen" value="true"></param>
<embed src="swf경로"
    invokeURLs="false"
    autostart="false"
    allowScriptAccess="never"
    allowNetworking="internal"
    type="application/x-shockwave-flash"
    allowfullscreen="true"
    width="425"
    height="344"></embed>
</object>

 

각 속성에 대해서 자세히 설명하자면 다음과 같다.

 

 

allowScriptAccess 속성

 

이 속성은 SWF의 ExternalInterface을 통해 Javascript와 통신가능여부를 결정지어주는 속성이다. 이 속성은 다음 3가지 값중 하나가 된다.

  • never : SWF에서 ExternalInterface를 이용해 Javascript통신을 할 수 없다.
  • always : SWF에서 ExternalInterface를 이용해 Javascript통신이 언제든지 가능하다.
  • sameDomain : 같은 도메인에 있는 SWF일때 Javascript 통신이 가능하다.(기본)

 

allowNetworking 속성 

 

이 속성은 SWF에서 네트워크 기능 API 사용을 제한하는 옵션이다. 다음과 같은 3가지 값중 하나를 가진다.

  • all : 모든 네트워크 API 허용(기본)
  • internal : 브라우저 네비게시션이나 브라우저 상호작용 API 사용 제한
  • none : 모든 네크워크 API 제한

internal인 경우 제한 API

  • navigateToURL()
  • fscommand()
  • ExternalInterface.call()

none인 경우 제한 API (internal 포함)

  • sendToURL()
  • FileReference.download()
  • FileReference.upload()
  • Loader.load()
  • LocalConnection.connect()
  • LocalConnection.send()
  • NetConnection.connect()
  • NetStream.play()
  • Security.loadPolicyFile()
  • SharedObject.getLocal()
  • SharedObject.getRemote()
  • Socket.connect()
  • Sound.load()
  • URLLoader.load()
  • URLStream.load()
  • XMLSocket.connect()

 

이제 네이버에서 스타플 위젯을 통해 스타플에 방문할 수 없는 이유를 보자.

 

<EMBED
    invokeURLs="false"
    autostart="false"
    allowScriptAccess="never"
    allowNetworking="internal"
    src="경로"
    width="54"
    height="96"
    type="application/x-shockwave-flash"
    allowfullscreen="true">
</EMBED>

 

몇몇 분들이 이런 제한정책때문에 네이버 블로그에 있기를 꺼려하시는 분들이 계시다. 물론 보안상 어떻게 할 수 없다고는 할 수 있지만…


참고글

이 문서는 ActionScript 3.0에서 Custom Event를 만들어 사용할 시 Event 클래스의 clone() 메소드의 용법을 이해하는 것을 목적으로 한다. 자주 Event를 확장해서 만드는 사람이라면 꼭 읽어볼 내용이라고 생각한다.

 

1. Event 클래스의 clone() 메소드

 

flash.events.Event 클래스에는 clone() 함수가 정의되어 있다. 이 clone() 함수 내부에는 아마도 아래처럼 정의되어 있을 것이다. (Event클래스는 Native코드이므로 내부를 볼 수 없다. 다만 유추할 뿐이다.)

 

package flash.events
{
	public class Event
	{
		public var type:String;
		public var bubbles:Boolean;
		public var cancelable:Boolean;
		public var target:Object;
		public var currentTarget:Object;

		public function Event( type:String, bubbles:Boolean = false, cancelable:Boolean = false )
		{
			this.type = type;
			this.bubbles = bubbles;
			this.cancelable = cancelable;
		}
		public function clone():Event
		{
			return new Event( type, bubbles, cancelable );
		}
		...(다른 함수 생략)
	}
}

 

 

Event의 clone() 함수는 자신과 똑같은 객체를 새로 만들고 반환하는 역할을 한다.

아래 clone() 함수에 대해서 설명하고 있다.

clone() method

public function clone():Event

Language Version:

ActionScript 3.0

Runtime Versions:

AIR 1.0, Flash Player 9

Duplicates an instance of an Event subclass.

Returns a new Event object that is a copy of the original instance of the Event object. You do not normally call clone(); the EventDispatcher class calls it automatically when you redispatch an event—that is, when you call dispatchEvent(event) from a handler that is handling event.

The new Event object includes all the properties of the original.

When creating your own custom Event class, you must override the inherited Event.clone() method in order for it to duplicate the properties of your custom class. If you do not set all the properties that you add in your event subclass, those properties will not have the correct values when listeners handle the redispatched event.

In this example, PingEvent is a subclass of Event and therefore implements its own version of clone().

 

설명을 간단히 요약해 보면…

  1. Event의 clone() 함수는 Event 서브클래스의 객체를 복제한다.
  2. clone() 객체는 EventDispatcher 클래스의 dispatchEvent(event) 함수에 의해 재송출(redispatch)될 때 자동으로 호출된다.
  3. Event를 확장해 Custom Event를 만드는 경우 반드시 override 해야한다. override하지 않으면 clone()은 그 부모 Event의 clone()을 사용하므로 clone()을 호출 하더라도 Custom Event에 새로 추가된 속성(properties)을 설정할 수 없게된다.

 

여기에서 EventDispatcher의 dispatchEvent()와 Event의 clone()과는 어떤 관계가 있어보인다. 그리고 clone()을 override해야하는 이유와도 엮여 있다는 것을 생각해볼 수 있다.

 

사실 이런 설명만 가지고는 제대로 이해하기 어렵기 때문에 여기서는 EventDispatcher에서 dispatchEvent()가 동작하는 형태와 Event의 clone() 함수의 관계를 하나의 예제로 이해해 보도록 한다.

 

2. EventDispatcher 클래스의 dispatchEvent() 함수의 이해

Event 클래스의 clone()함수에 대한 용도에 대해 알기전에 먼저 아래 내용을 알아야한다.

 

Event 객체를 송출(Dispatch)하기 위해서 ActionScript 3.0에서는 IEventDispather 인터페이스와 EventDispatcher 클래스를 지원한다. IEventDispather를 구현하여 내부적으로 EventDispatcher 객체를 만들어 사용하는 클래스를 정의하거나 EventDispatcher 클래스 자체를 상속하여 만든 클래스라면 Event객체를 송출할 수 있다. 이벤트를 송출할 때 사용하는 것이 EventDispatcher에 정의된 dispatchEvent( event:Event ) 함수이다. dispatchEvent는 인자로 Event의 객체를 인자로 받아 이벤트를 송출한다.

 

그럼 dispatchEvent()는 내부적으로 어떻게 구현되는 것일까? 다음 내용을 보자.

 

dispatchEvent () method
public function dispatchEvent(event:Event):Boolean Language Version : ActionScript 3.0
Runtime Versions : AIR 1.0, Flash Player 9
Dispatches an event into the event flow. The event target is the EventDispatcher object upon which the dispatchEvent() method is called.
Parameters

event:Event — The Event object that is dispatched into the event flow. If the event is being redispatched, a clone of the event is created automatically. After an event is dispatched, its target property cannot be changed, so you must create a new copy of the event for redispatching to work.Returns

Boolean — A value of true if the event was successfully dispatched. A value of false indicates failure or that preventDefault() was called on the event.

Throws

Error — The event dispatch recursion limit has been reached.

 

위 dispatchEvent() 함수에 대한 설명이 담겨 있다. 인자값으로 event:Event가 있는데 이에 대한 설명(빨간부분)을 살펴보면 다음 내용으로 설명된다.

 

  1. Event 객체의 clone() 함수를 사용하는 조건은 해당 Event 객체를 다시 송출할 때이다.
  2. 다시 송출하게 되면 clone() 함수를 이용해 같은 형태의 Event 객체를 새로 생성하고 그 Event 객체의 target 속성을 이벤트를 송출하는 객체로 바꾼다.

 

이 설명으로 우리는 Event가 송출되는 과정에 사용되는 dispatchEvent() 메소드의 동작을 유추할 수 있다.

 

  1. 객체 A, 객체 B 가 있고 A는 이벤트를 송출하는 쪽, B는 이벤트를 받는 쪽이다라고 가정하자.- 이 객체들은 당연히 EventDispatcher를 확장했다.- A,B가 같은 객체일 수도 있고 다를 수도 있다. 다른 경우는 Visual 객체의 경우 이벤트 전파에 의해 가능해진다.

  2. 객체 A에서 이벤트 송출
    처음 A에서 생성된 Event에는 target속성이 정의되지 않는다. A의 dispatch() 함수로 이 Event 객체를 인자로 넘겨주면 clone()함수가 호출되지 않고 Event의 target은 A로 설정된다.
  3. 객체 B에서 이벤트 받음A에서 송출한 Event를 B에서 받는다. 이것은 addEventListener()로 가능하다는 것은 이미 알고 있다. addEventLisener()에 등록된 Event 처리 함수는 인자로 A에서 발생한 Event 객체를 받는다.받은 Event 객체를 다시 B에서 dispatchEvent()로 넘겨주게 되면 Event객체의 target속성이 B가 아닌 A이기 때문에 clone() 함수를 호출해서 새로운 Event 객체를 만들고 target을 B로 설정한다.

 

 

아하! 이쯤되면 EventDispatcher 클래스의 dispatchEvent() 함수가 어찌 생겼는지 알 수 있겠다. Event와 같이 EventDispatcher도 Native코드이므로 볼 수 없지만 어떻게 동작할지 유추만 하는 것이다!

 

 

package flash.events
{
	public var type:String;
	public var bubbles:Boolean;
	public var cancelable:Boolean;
	public class EventDispatcher implements IEventDispatcher
	{
		private target:IEventDispatcher;

		public function EventDispatcher(target:IEventDispatcher = null)
		{
			if( target )
				this.target = target;
			else
				this.target = this;
		}

		public function dispatchEvent(event:Event):Boolean
		{
			if( event == null ) return false;
			if( event.target == null )
			{
				event.target = target;
				event.currentTarget = target;
			}
			else
			{
				var newEvent:Event = event.clone();
				newEvent.target = target;
				newEvent.currentTarget = target;
			}
			dispatchEventFunction( event );
		}

		private function dispatchEventFunction( event:Event ):void
		{
			...(이벤트를 송출하는 알고리즘 구현)
		}

		public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
		{
			...(생략)
		}

		public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
		{
			...(생략)
		}

		public function willTrigger(type:String):Boolean
		{
			...(생략)
		}

		public function hasEventListener(type:String):Boolean
		{
			...(생략)
		}
	}
}

 

 

 

위 코드는 dispatchEvent() 메소드가 어떻게 동작할지 쉽게 알려주는 코드이다. 해석은 앞서 설명했으니 따로 하지 않겠다. (다시 언급하지만 이 클래스는 실제 EventDispatcher 클래스가 아니다. 어떻게 동작하는지 예를 보여주기 위한 코드일 뿐이다. )

 

 

이로써 Event 클래스에 정의된 clone()함수를 언제 쓰냐에 대한 답을 얻은 것이다.

 

 

3. Custom Event를 만들때 clone() 함수를 override 해야하는 이유

지금까지는 clone()함수를 어디서 사용하는가 안 것 뿐이다. 이 문서의 제목을 풀어서 말하면 “Event 클래스를 확장할 때 clone() 함수를 override 해야하는 이유”이다. 글의 목적은 Custom Event를 만들 때 clone() 함수를 override 해서 사용해야하는 이유를 아는 것이다.

 

clone() 함수를 override해야하는 상황을 만든 예를 들어보겠다. 예제는 Flex SDK 3.2 환경에서 작업했다.

 

아래는 Event를 확장한 CustomEvent이다.

 

package
{
	import flash.events.Event;
	public class CustomEvent extends Event
	{
		public static const TEST:String = "test";

		public function CustomEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
		{
			super(type, bubbles, cancelable);
		}

		override public function clone():Event
		{
			return new CustomEvent( type, bubbles, cancelable );
		}
	}
}

 

아래는 Button을 확장해서 만들었다. 이 버튼을 누르면 CustomEvent 객체를 생성해서 test라는 이벤트 type으로 송출해준다.

 

package
{
	import flash.events.Event;
	import flash.events.MouseEvent;

	import mx.controls.Button;

	[Event(name="test", type="CustumEvent")]

	public class AComponent extends Button
	{
		public function AComponent()
		{
			super();
			this.label = "날 눌러줘";
			this.addEventListener( MouseEvent.CLICK, onClick );
		}

		private function onClick( event:MouseEvent ):void
		{
			var customEvent:CustomEvent = new CustomEvent( CustomEvent.TEST, false, false );
			trace( "AComponent 1:",customEvent.target,customEvent.currentTarget );
			this.dispatchEvent( customEvent );
			trace( "AComponent 2:",customEvent.target,customEvent.currentTarget );
		}
	}
}

 

 

아래는 Panel을 확장해 만들었고 자식으로 위에서 만든 AComponent 객체를 등록했다. (이 예제에서 자식으로 등록된 사실은 중요하지 않다.) AComponent에 발생된 CustomEvent 이벤트를 받는다. 받자마자 그 이벤트를 다시 송출하고 있다.

 

package
{
	import flash.events.Event;
	import mx.containers.Panel;

	[Event(name="test", type="CustomEvent")]

	public class BComponent extends Panel
	{
		private var aComp:AComponent;

		public function BComponent()
		{
			super();
		}

		override protected function createChildren():void
		{
			super.createChildren();

			if( !aComp )
			{
				aComp = new AComponent;
				aComp.addEventListener( "test", customEventHandler );
				aComp.width = 200;
				aComp.height = 200;
				addChild( aComp );
			}
		}

		private function customEventHandler( event:Event ):void
		{
			trace( "BComponent 1: ",event.target,event.currentTarget );
			this.dispatchEvent( event ); //re-dispatch
			trace( "BComponent 2: ",event.target,event.currentTarget );
		}
	}
}

 

 마지막으로 Application이다.

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*">
	<local:BComponent test="trace(’Application: ‘,event.target,event.currentTarget )"/>
</mx:Application>

 

 

 

실행한뒤 버튼을 누르면 콘솔창에 아래와 같은 메시지가 나온다.

 

AComponent 1: null null

BComponent 1 : EventTest0.BComponent4.AComponent12 EventTest0.BComponent4.AComponent12

Application: EventTest0.BComponent4 EventTest0.BComponent4

BComponent 2 : EventTest0.BComponent4.AComponent12

EventTest0.BComponent4.AComponent12

AComponent 2: EventTest0.BComponent4.AComponent12

EventTest0.BComponent4.AComponent12

 

아래는 위 결과에 대한 설명이다.

 

  1. [AComponent 1] AComponent에서 Event 객체를 만들어 송출하는데 dispatchEvent를 호출하기 전에 target과 currentTarget 속성은 모두 null이다. 예상한 대로다.
  2. [BComponent 1]이 이벤트가 송출된 다음 BComponent에서 받게 되면 이 이벤트의 target과 currentTarget은 AComponent의 객체를 참조하고 있다.
  3. [Application] BComponent에서 AComponent에서 생성한 Event 객체를 인자로 dispatchEvent()를 호출하면 Application에서는 Event의 target, currentTarget 속성이 BComponent의 객체를 참고하고 있다.
  4. [BComponent 2] BComponent에서 AComponent의 Event 객체를 dispatchEvent()를 재송출한 다음에 Event객체의 target과 currentTarget속성을 보여주고 있다. 단지 clone()메소드를 이용해 생성했기 때문에 Event객체 자신의 속성은 바뀌지 않는다. 단지 Application으로 전달되는 Event는 clone()으로 새로 생성되었고 BComponent의 객체를 참조하고 있다는 것을 알 수 있다.
  5. [AComponent 2] 마지막 줄은 AComponent에서 Event객체를 만들어 dispatchEvent()함수로 만든 Event객체를 송출한 후에 Event객체의 target과 currentTarget 속성을 보여준다. 4번째와 같은 결과지만 dispatchEvent()내부에서 clone() 메소드가 호출된 것이 아니라 단지 AComponent 객체의 참조값을 target과 currentTarget에 설정했을 뿐이라는 것을 알 수 있다.

 

위에서 한가지 기억할 사항은 이벤트가 재송출(redispatch)하는 경우 target과 currentTarget이 변경된다는 것이다.

 

만약 CustomEvent에서 Event의 clone()를 override한 clone() 함수 정의를 빼보고 실행해보자.

 

AComponent 1: null null

TypeError: Error #1034: 유형 강제 변환에 실패했습니다. flash.events::Event@5115971을(를) CustomEvent(으)로 변환할 수 없습니다.
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:9298]
at BComponent/customEventHandler()[D:\EventTest\src\BComponent.as:34]

at flash.events::EventDispatcher/dispatchEventFunction()at flash.events::EventDispatcher/dispatchEvent()

at mx.core::UIComponent/dispatchEvent()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:9298]

at AComponent/onClick()[D:\STARPL_DEV_02\Timeline\src03\EventTest\src\AComponent.as:23]

BComponent 1 : EventTest0.BComponent4.AComponent12 EventTest0.BComponent4.AComponent12

BComponent 2 : EventTest0.BComponent4.AComponent12 EventTest0.BComponent4.AComponent12

AComponent 2: EventTest0.BComponent4.AComponent12 EventTest0.BComponent4.AComponent12

 

위처럼 CustomEvent에 clone() 함수를 override하지 않으면 이벤트를 다시 송출할 때 유형 강제 변환 에러(TypeError)가 발생한다.

 

결과적으로 clone() 함수를 override를 해야하는 이유는 받은 Event 객체를 다시 재송출하는 경우에 clone() 함수를 호출하기 때문이다.

 

그럼 어찌 유형 강제 변환 에러(TypeError)가 나는 것일까? 사실 위에서 예를 든 EventDispatcher 클래스의 dispatchEvent() 메소드는 이런 에러가 날 수 없다. CustomEvent의 객체가 Event로 형변환이 일어나지 않을 이유가 없지 않은가? 본인 생각에 이 형변환 에러가 발생시키는 이유는 clone() 메소드에서 엉뚱한 이벤트의 객체를 생성하지 않고 자신 이벤트만을 clone으로 만들어 사용하게 하기 위해 강제성을 부여한 것이 아닌듯 싶다. 가령 다음과 같이 clone()을 만들면 안되게 한다는 것이다.

 

package
{
	import flash.events.Event;
	public class CustomEvent extends Event
	{
		public static const TEST:String = "test";

		public function CustomEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
		{
			super(type, bubbles, cancelable);
		}

		override public function clone():Event
		{
			return new OtherCustomEvent( type, bubbles, cancelable );
		}
	}
}

 

 

위처럼 자기는 CustomEvent인데 clone으로 OtherCustomEvent를 clone으로 삼아 만들면 안되지 않은가? EventDispather의 dispatchEvent는 개발자가 엉뚱하게 프로그래밍하는 것을 막기위해 컴파일 단계에서는 확인할 수 없다. 하지만 런타임시에 전혀 엉뚱하게 clone()함수를 override하는 것을 방지하기 위해 다른 Event Class로 clone함수를 이용하는 경우 TypeError가 나도록 배려(?)한 것이다.

 

이런 TypeError가 날 수 있게 EventDispatcher의 dispatchEvent() 함수를 아래와 같이 만들어봤다.

 

public function dispatchEvent(event:Event):Boolean
{
	if( event == null ) return false;
	if( event.target == null )
	{
		event.target = target;
		event.currentTarget = target;
	}
	else
	{
		var newEvent:Event = event.clone();
		if( getQualifiedClassName(event) != getQualifiedClassName(newEvent) )
		{
			var msg:String = "TypeError: Error #1034: 유형 강제 변환에 실패했습니다. ";
			msg += getQualifiedClassName(event) + "를(을)";
			msg += getQualifiedClassName(event) + "(으)로 변경할 수 없습니다."
			throw new TypeError( msg );
		}
		newEvent.target = target;
		newEvent.currentTarget = target;
	}
	dispatchEventFunction( event );
}

 

 

getQualifiedClassName()은 객체의 클래스 이름을 반환해준다. 그러므로 원래 event객체와 clone()으로 만든 객체가 다르다면 TypeError를 발생시킨다. 이러한 이유로 clone()을 override 하지 않고 다시 Event를 송출하는 경우에는 TypeError가 발생하는 것이다.

 

 

정리하자

Event의 clone() 함수를 override 하는 이유는

  1. Custom Event가 복제될 때 복제된 Event의 속성이 원본 Event 속성과 같은 속성을 가질 수 있도록 하기 위해
  2. 이벤트를 재송출(redispatch)시에 TypeError 직면하지 않기 위해

 

4. clone()함수와 Event 전파와의 관계

많은 분들이 Event전파시 전파가 이뤄질 때마다 clone() 함수가 호출되는 것으로 생각하고 있는 듯하다. 실제로 Flex관련 서적이나 블로그에 보면 그런 내용이 담겨있는 것을 알 수 있다. 여기서는 clone()함수와 Event 전파에 어떤 관계가 있는가 간단한 예제로 알아보고자 한다. 결론을 먼저 말한다면 이들간에는 관계가 없다.

먼저 다음을 살펴보자.

 

ActionScript 3.0 부터 Event를 송출하기 위해 다음과 같은 과정을 거친다.

 

  • 이벤트 생성
    flash.events.Event 클래스를 사용하거나 그것을 확장해서 만든 Custom Event 클래스를 만든다.
    예) var event:MyEvent = new MyEvent( MyEvent.MYTYPE );
  • 이벤트 송출
    Event 클래스나 Custom Event 클래스를 가지고 객체를 생성하여 EventDispatcher를 확장한 클래스의 dispatchEvent()메소드를 이용해 이벤트를 송출한다.
    예) eventdispatcher.dispatchEvent( event );
  • 이벤트 전파
    이벤트 전파는 Visual 객체 즉, DisplayObject를 상속한 모든 객체에서만 가능하다. DisplayObject는 Eventdispatcher 클래스를 확장해서 만들어졌기 때문에 “이벤트 송출”을 할 수 있다.여기서 전파란 부모-자식 관계에 있는 객체들간에 이벤트 전달방법이다. 가령, A-B-C-D 의 순으로 부모-자식 관계를 형성했다면 C 객체에서 이벤트를 송출하는 경우 A->B->C->B->A 형태로 이벤트가 전파된다. 이벤트 전파방식을 세부적으로 나누면 capture, target, bubble 로 나뉠 수 있는데 앞에 A->B(부모->자식들)가 capture 과정, C가 target과정, B->A(자식들->부모)는 bubble 과정을 의미한다. C에서 발생했기 때문에 그의 자식인 D로는 이벤트 전파가 이뤄지지 않는다.이벤트 전파는 반드시 DisplayObject를 확장한 Visual 객체에서만 가능하다. 그게 아니라면 이벤트 전파 단계는 이뤄지지 않으며 앞서 설명한 capture, target, bubble과정중 target 과정만 이루어진다.
  • 이벤트 청취
    송출된 이벤트를 받는 것은 EventDispatcher를 확장한 클래스여야 한다. EventDispatcher에 있는 addEventListener() 메소드를 이용해 송출된 이벤트를 청취하며 더이상 청취하지 않으려면 removeEventListener() 메소드를 호출하면 된다. 이벤트를 송출한 객체와 청취하는 객체가 다른 것은 이벤트 전파단계를 거치는 Visutal 객체만 가능하며 Visual객체가 아니라면 이벤트를 송출한 객체와 청취한 객체가 같다.
    예) eventdispatcher.addEventListener( MyEvent.MYTYPE, myEventHandler );

 

 

지금까지 이벤트를 “생성-송출(재송출 포함)-청취” 하는 단계만 언급했지, “이벤트 전파”는 언급하지 않았다. 이벤트 전파는 Visual 객체만 가능하다. 그럼 위 설명대로 A-B-C-D순으로 부모-자식관계가 형성이 되어 있다면 이벤트 전파시 이벤트가 A->B->C->B->A 형태로 나아갈 때, clone() 메소드가 호출되는지 예제를 들어 알아보겠다.

 

먼저 Custom Event를 아래와 같이 만든다. 주목할 점은 clone()을 override하는데 trace(”clone 호출됨”)을 실행하고 있다는 것이다. 이것을 넣은 이유는 이벤트 전파시 clone() 메소드가 호출되는가 확인하기 위함이다.

 

 

package
{
	import flash.events.Event;

	public class CustomEvent extends Event
	{
		public static const TEST:String = "test";

		public function CustomEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false)
		{
			super( type, bubbles, cancelable );
		}

		override public function clone():Event
		{
			trace("clone 호출됨");
			return new CustomEvent( type, bubbles, cancelable );
		}
	}
}

 

아래 클래스는 Button을 확장해서 만들었다. 중요하게 볼 것은 위에서 만든 CustomEvent 클래스를 송출한다는 점이다. CustomEvent의 2번째 인자는 bubbles이다. 즉, 이벤트 전파가 가능하게 하기 위해서는 이 인자를 true로 설정해야한다. (마우스 이벤트가 송출되는게 아니다.)

 

package
{
	import flash.events.MouseEvent;

	import mx.controls.Button;

	public class MyButton extends Button
	{
		public function MyButton()
		{
			this.label = "날 눌러줘";
			this.addEventListener( MouseEvent.CLICK, onClick );
		}

		private function onClick( event:MouseEvent ):void
		{
			var customEvent:CustomEvent = new CustomEvent( CustomEvent.TEST, true, false );
			this.dispatchEvent( customEvent );
		}
	}
}

 

 

아래 예제는 위에서 만든 Button을 HBox로 감싸서 자식으로 추가하고 Button에서 발생하는 CustomEvent를 Application과 HBox, 그리고 Button등 모든 전파단계(capture,target,bubble)에서 청취할 수 있도록 하고 있다. 참고로 addEventListener()의 3번째 인자는 cature이다. 이것을 true로 하면 capture단계에서 이벤트 청취를 할 수 있다. false이면 target-bubble과정만 청취가 가능하다.

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:local="*"
layout="absolute"
creationComplete="init()">

	<mx:HBox id="hbox">
		<local:MyButton id="button"/>
	</mx:HBox>

	<mx:Script>
	<![CDATA[
		import flash.events.EventPhase;

		private function init():void
		{
			this.addEventListener( "test", eventHandler );
			hbox.addEventListener( "test", eventHandler );
			button.addEventListener( "test", eventHandler );
			this.addEventListener( "test", eventHandler,true );
			hbox.addEventListener( "test", eventHandler,true );
			button.addEventListener( "test", eventHandler,true );
		}

		private function eventHandler( event:CustomEvent ):void
		{
			var eventPhase:String;
			switch( event.eventPhase )
			{
				case EventPhase.CAPTURING_PHASE:
					eventPhase = "capture";
					break;
				case EventPhase.AT_TARGET:
					eventPhase = "target";
					break;
				case EventPhase.BUBBLING_PHASE:
					eventPhase = "bubble";
					break;
			}
			trace( "target:", event.target, ", currentTarget", event.currentTarget, ", phase:", eventPhase );
		}
	]]>
	</mx:Script>
</mx:Application>

 

위 애플리케이션을 실행하고 버튼을 클릭해보자. 그럼 콘솔창에 다음과 같은 메시지가 보일 것이다.

 

target: EventTest0.hbox.button , currentTarget EventTest0 , phase: capture

target: EventTest0.hbox.button , currentTarget EventTest0.hbox , phase: capture

target: EventTest0.hbox.button , currentTarget EventTest0.hbox.button , phase: target

target: EventTest0.hbox.button , currentTarget EventTest0.hbox , phase: bubble

target: EventTest0.hbox.button , currentTarget EventTest0 , phase: bubble

 

위 메세지에서 target은 변하지 않는다. 왜냐하면 target은 이벤트가 최초 발생한 곳을 참조하기 때문이다. 이 작업은 dispatchEvent()에서 한다는 것을 앞서 설명했다.

 

두번째 currentTarget은 각각 이벤트 전파때마다 다르다. 즉, currentTarget이 지칭하는 것은 현재 이벤트가 지나가고 있는 객체를 참조한다. dispatchEvent()함수에서 최초로 설정되지만 이벤트 전파가 진행됨에 따라 계속 변한다.

 

phase는 위상으로 이벤트 전파가 어떻게 이뤄지고 있는지 보여준다.

 

만약 이벤트 전파를 통해 Event의 clone()이 호출된다면 여기 결과에 “clone 호출됨”메시지가 포함되어야 하지만 보여지지 않고 있다.

 

결론적으로 Event의 clone()은 Event를 재송출(redispatch)와 밀접한 관계가 있다. 하지만 Event 전파와 clone()과는 아무 상관 없으며 각각의 전파 단계(위상)가 변함에 따라 currentTarget과 phase 속성은 변경된다.

 

참고내용

빵집 개발자 양병규님께서 이 글의 이전 글인 Adobe Alchemy 속도 테스트에 대해 이런 답변을 달아주셨다.


컴파일러에 대해서 조금 관심을 가져보시면 알 수 있을텐데요…
음… 뭐랄까…
그 최적화라는 것이 불필요한 일은 안하고 같은 기능이라면 더 빠른 기능으로 대치하고.. 그런일을 하는 것이 컴파일러의 최적화인데요..
제가 보기에는
for( i=0; i < 1000000; i++ )
{
result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}
이 부분이.. 최적화를 하면 루프를 1000000번 돌지 않고 한방에 끝낼것같습니다. 최적화알고리즘중에서 루프문안에서의 내용이 반복을 해도 변화가 없는 경우에는 루프를 한방에 끝내도록 하는 알고리즘이 있습니다. 이 예가 그런 예일것 같은데요..

암튼.. ActionScript에는 그런 최적화가 없는데 이미 오랜 역사동안 만들어진 C/C++언어의 최적화 기술이 적용되면 분명히 나은 결과를 보일 것 으로 예상됩니다. ^^;(물론 코딩하는 사람의 실력이 좋을 수록 최적화의 효과가 떨어지겠지만)

그리고..
저는 아무리 봐도 C/C++로 코딩한 것이 결국 몽땅 ActionScript로 만들어지는 것이 맞는 것 같습니다. SWF에는 ActionScript 이 외에 다른 실행코드는 존재하지 않습니다. 최종적으로 만들어진 결과가 *.SWF(*.SWC)이지 않습니까? SWF File Format Specification Version 10 문서 278페이지를 다 훑어봐도 ActionScript 이외에는 실행코드가 없습니다. 머.. DLL을 임포트한다거나 별도의 실행코드용 외부 라이브러리가 있다거나… 그런건 전혀 없습니다.

malloc 함수 같은 경우의 예를 들어보면 ActionScript에는 malloc과 동일한 역할을 하는 어셈블명령은 없습니다. 하지만 ActionInitArray와 같이 긴 변수를 사용할 수 있는 명령으로 ‘구현’은 할 수 있을겁니다. 아마도 그런 방법으로 구현되지 않았나싶습니다.

어쨋든 Alchemy는 C/C++을 100% ActionScript로 변환해 주는게 맞고.. 함수사용, 변수사용, 최적화, 수학함수의 구현, 각종 기초 알고리즘등 기초적인 부분에서 훨씬 우월하기 때문에 좋은 효과를 나타내고 있는 것으로 생각됩니다.

시간이 나면 Alchemy에서 만들어진 SWF 파일을 분석해 보면 아주 좋은 공부가 될것같습니다. ^^;

양병규님의 의견중 최적화에 대한 것을 다시 살펴보자면 -O3 옵션으로 인해 최적화 알고리즘이 적용되어 for문이 없어지고 result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3); 만 남는다는 것을 지적하고 ActionScript 3.0에는 이런 최적화 기능이 없기 때문에 실제로 돌려보면 C코드는 1번만 수행하는 반면 ActionScript 3.0에서는 원래대로 1백만번 루프를 돈다는 것이다.

 

또한 여러 상황에 비추어볼때 Alchemy는 C/C++ 코드를 100% ActionScript 3.0으로 변경하는게 맞다고 말씀해 주었다.

 

이 의견을 읽고 맞는 말인 것 같다고 생각해서 코드를 다음과 같이 수정했다.

 

1. C 코드 최적화를 방지해서 다시 속도 테스트

speedtest.c

#include <math.h>
#include <stdlib.h>
#include "AS3.h"

static AS3_Val c_speed_test(void* self, AS3_Val args)
{
    int i;
    double result = 0;
    for( i=0; i < 10000000; i++ )
    {
        result += sin( i ) * cos( i );
    }
    return AS3_Number(result);
}

int main()
{
    AS3_Val method = AS3_Function( NULL, c_speed_test );
    AS3_Val result = AS3_Object( "c_speed_test: AS3ValType", method );
    AS3_Release( method );
    AS3_LibInit( result );
    return 0;
}

 

AlchemySpeedTest.as

package {
    import cmodule.speedtest.CLibInit;

    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.getTimer;

    public class AlchemySpeedTest extends Sprite
    {
        public function AlchemySpeedTest()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            var loader:CLibInit = new CLibInit();
            var lib:Object = loader.init();
            var startTime:Number;

            startTime = getTimer();
            var cResult:String = "c returned value : " + lib.c_speed_test(null) + " delay time(ms) : " + (getTimer()-startTime);

            startTime = getTimer();
            var asResult:String = "as3 returned value : " + as3_speed_test() + " delay time(ms) : " + (getTimer()-startTime);

            var textField:TextField = new TextField();
            textField.text = cResult + "\n" + asResult;
            textField.width = 500;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.wordWrap = true;
            textField.defaultTextFormat = new TextFormat( null, 12 );
            addChild(textField);

        }
        public function as3_speed_test():Number
        {
            var i:int;
            var result:Number = 0;
            for (i = 0; i < 10000000; i++)
            {
                result += Math.sin( i ) + Math.cos( i );
            }
            return result;
        }
    }
}

 

이 코드에서 달라진 것은 gcc로 c를 컴파일 할 때 -O3와 같은 최적화 알고리즘 적용을 하더라도 전과 같이 for문이 삭제되는 것을 방지하도록 했다.

 

이전 코드

for( i=0; i < 1000000; i++ )
{
        result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}

 

새로 바꾼 코드

for( i=0; i < 10000000; i++ )
{
        result += sin( i ) * cos( i );
}

새로 바꾼 코드는 sin(), cos()함수에 계속 변화되는 값을 넣어주었고 그 결과가 누적되도록 했다. 이로써 최적화를 하더라도 for문이 적용되지 않는 경우는 없을 것이다.

컴파일은 아래와 같이 한다.

gcc speedtest.c –03 –Wall –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

결과는 다음과 같다.

c returned value : 0.24755567269650028 delay time(ms) : 3425
as3 returned value : 2.873882594355213 delay time(ms) : 3184

예상대로 gcc –O3 로 인해 최적화가 적용되었던 것이다. 양병규 님의 말에 힘이 실리는 듯하다.

 

2. ActionScript 3.0 Native Vector3D, Matrix3D와 C++의 Vector3D, Matrix3D 속도테스트

 

Flash Player 10부터 추가된 ActionScript 3.0 API중에 Vector3D, Matrix3D가 있다. 이 클래스들은 3D 표현을 위한 다양한 기능을 제공하고 있다. Native 코드이므로 소스코드는 직접 볼 수 없다.

 

ActionScript 3.0의 Vector3D와 Matrix3D 대신 C++로 Vector3D, Matrix3D를 만들어서 서로 수행속도를 비교해보도록 하겠다.

 

아래는 Vector3D와 Matrix3D C++코드, 그리고 이들을 속도 테스트 할 수 있는 C++코드, ActionScript 3.0과 C++과 속도 테스트 할 수 있도록 제작한 ActionScript 3.0 코드가 나열되어 있다.

 

Vector3D.h

#ifndef VECTOR3D_H_
#define VECTOR3D_H_

#include "AS3.h"

class Vector3D {

public :
  Vector3D();
  Vector3D(double x, double y, double z);
  Vector3D(const AS3_Val& as3Vector);
  virtual ~Vector3D();

  double dot(const Vector3D& v) const;
  Vector3D cross(const Vector3D& v) const;
  double modulus() const;
  Vector3D normalise() const;

  void setX(double x);
  void setY(double y);
  void setZ(double z);
  double getX() const;
  double getY() const;
  double getZ() const;

private :
  double _x;
  double _y;
  double _z;

};

#endif /*VECTOR3D_H_*/

 

Matrix3D.h

#ifndef MATRIX3D_H_
#define MATRIX3D_H_

#include "Vector3D.h"

class Matrix3D {

public :
  Matrix3D();
  virtual ~Matrix3D();

  void setRotationX(double degrees);
  void setRotationY(double degrees);
  void setRotationZ(double degrees);

  void setIdentity();

  Vector3D transformVector(const Vector3D& vector) const;

private :
  double _M00;
  double _M01;
  double _M02;
  double _M10;
  double _M11;
  double _M12;
  double _M20;
  double _M21;
  double _M22;

};

#endif /*MATRIX3D_H_*/

 

Vector3D.cpp

#include "Vector3D.h"
#include <cmath>

Vector3D::Vector3D() :
  _x(0),
  _y(0),
  _z(0) {
}

Vector3D::Vector3D(const AS3_Val& as3Vector) {
  AS3_ObjectValue(as3Vector, "x:DoubleType, y:DoubleType, z:DoubleType", &_x, &_y, &_z);
}

Vector3D::Vector3D(double x, double y, double z) :
  _x(x),
  _y(y),
  _z(z) {
}

Vector3D::~Vector3D() {
}

double Vector3D::dot(const Vector3D& v) const {
  return v._x*_x + v._y*_y + v._z*_z;
}

Vector3D Vector3D::cross(const Vector3D& v) const {

  Vector3D result;
  result._x = _y*v._z - _z*v._y;
  result._y = _z*v._x - _x*v._z;
  result._z = _x*v._y - _y*v._x;
  return result;
}

double Vector3D::modulus() const {
  return std::sqrt(_x*_x + _y*_y + _z*_z);
}

Vector3D Vector3D::normalise() const {
  double mod = modulus();
  return Vector3D(_x/mod, _y/mod, _z/mod);
}

void Vector3D::setX(double x) {
  _x = x;
}

void Vector3D::setY(double y) {
  _y = y;
}

void Vector3D::setZ(double z) {
  _z = z;
}

double Vector3D::getX() const {
  return _x;
}

double Vector3D::getY() const {
  return _y;
}

double Vector3D::getZ() const {
  return _z;
}

Matrix3D.cpp

#include "Matrix3D.h"
#include <cmath>

Matrix3D::Matrix3D() :
  _M00(1),
  _M01(0),
  _M02(0),
  _M10(0),
  _M11(1),
  _M12(0),
  _M20(0),
  _M21(0),
  _M22(1) {
}

Matrix3D::~Matrix3D() {
}

void Matrix3D::setIdentity() {
  _M00 = 1;
  _M01 = 0;
  _M02 = 0;
  _M10 = 0;
  _M11 = 1;
  _M12 = 0;
  _M20 = 0;
  _M21 = 0;
  _M22 = 1;
}

void Matrix3D::setRotationX(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M11 = cos(radians);
  _M12 = -sin(radians);
  _M21 = sin(radians);
  _M22 = cos(radians);
}

void Matrix3D::setRotationY(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M00 = cos(radians);
  _M02 = sin(radians);
  _M20 = -sin(radians);
  _M22 = cos(radians);
}

void Matrix3D::setRotationZ(double degrees) {
  setIdentity();
  double radians = degrees / 180 * M_PI;
  _M00 = cos(radians);
  _M01 = -sin(radians);
  _M10 = sin(radians);
  _M11 = cos(radians);
}

Vector3D Matrix3D::transformVector(const Vector3D& vector) const {
  Vector3D result;
  result.setX(_M00*vector.getX() + _M01*vector.getY() + _M02*vector.getZ());
  result.setY(_M10*vector.getX() + _M11*vector.getY() + _M12*vector.getZ());
  result.setZ(_M20*vector.getX() + _M21*vector.getY() + _M22*vector.getZ());
  return result;
}

alchemy_speed_test.cpp

#include "AS3.h"
#include "Vector3D.h"
#include "Matrix3D.h"

AS3_Val speedTest1(void* self, AS3_Val args) {

  // Declare AS3 variables
  AS3_Val as3Vector1;
  AS3_Val as3Vector2;
  // Extract variables from arguments array
  AS3_ArrayValue(args, "AS3ValType, AS3ValType", &as3Vector1, &as3Vector2);
  // Create native C++ objects with AS3 parameters
  Vector3D vector1(as3Vector1);
  Vector3D vector2(as3Vector2);
  Vector3D vector3;
  // Speed test : calculate cross products and normalise
  for (int i = 0; i < 1000000; i++) {
    vector3 = vector1.cross(vector2);
    vector3 = vector3.normalise();
    vector1 = vector2;
    vector2 = vector3;
  }

  // Obtain a class descriptor for the AS3 Vector3D class
  AS3_Val vector3DClass = AS3_NSGet(AS3_String("flash.geom"), AS3_String("Vector3D"));
  AS3_Val params = AS3_Array("");
  // Construct a new AS3 Vector3D object with empty parameters
  AS3_Val result = AS3_New(vector3DClass, params);
  // Set the x, y and z properties of the AS3 Vector3D object, casting as appropriate
  AS3_Set(result, AS3_String("x"), AS3_Number(vector3.getX()));
  AS3_Set(result, AS3_String("y"), AS3_Number(vector3.getY()));
  AS3_Set(result, AS3_String("z"), AS3_Number(vector3.getZ()));

  // Release what’s no longer needed
  AS3_Release(params);
  AS3_Release(vector3DClass);
  // return the AS3 Vector
  return result;
}

AS3_Val speedTest2(void* self, AS3_Val args) {

  // Declare AS3 variable
  AS3_Val as3Vector;
  // Extract variables from arguments array
  AS3_ArrayValue(args, "AS3ValType", &as3Vector);

  // Create native C++ object with AS3 parameters
  Vector3D vector(as3Vector);
  Vector3D copy = vector;
  Matrix3D rotationX;
  Matrix3D rotationY;
  Matrix3D rotationZ;
  // Speed test : calculate rotation matrices and transform vector
  for (int i = 0; i < 1000; i++) {
    vector = copy;
    for (double ang = 0; ang < 180; ang++) {
      rotationX.setRotationX(ang);
      rotationY.setRotationY(ang);
      rotationZ.setRotationZ(ang);
      vector = rotationX.transformVector(vector);
      vector = rotationY.transformVector(vector);
      vector = rotationZ.transformVector(vector);
    }
  }

  // Obtain a class descriptor for the AS3 Vector3D class
  AS3_Val vector3DClass = AS3_NSGet(AS3_String("flash.geom"), AS3_String("Vector3D"));
  AS3_Val params = AS3_Array("");
  // Construct a new AS3 Vector3D object with empty parameters
  AS3_Val result = AS3_New(vector3DClass, params);
  // Set the x, y and z properties of the AS3 Vector3D object, casting as appropriate
  AS3_Set(result, AS3_String("x"), AS3_Number(vector.getX()));
  AS3_Set(result, AS3_String("y"), AS3_Number(vector.getY()));
  AS3_Set(result, AS3_String("z"), AS3_Number(vector.getZ()));

  // Release what’s no longer needed
  AS3_Release(params);
  AS3_Release(vector3DClass);
  // return the AS3 Vector
  return result;
}

/**
* Main entry point for Alchemy compiler. Declares all functions available
* through the Alchemy bridge.
*/
int main() {
  // Declare all methods exposed to AS3 typed as Function instances
  AS3_Val speedTest1Method = AS3_Function(NULL, speedTest1);
  AS3_Val speedTest2Method = AS3_Function(NULL, speedTest2);

  // Construct an object that contains references to all the functions
  AS3_Val result = AS3_Object("speedTest1:AS3ValType, speedTest2:AS3ValType", speedTest1Method, speedTest2Method);

  // Release what’s no longer needed
  AS3_Release(speedTest1Method);
  AS3_Release(speedTest2Method);

  // Notify the bridge of what has been created — THIS DOES NOT RETURN!
  AS3_LibInit(result);

  // Should never get here!
  return 0;
}

AlchemySpeedTest.as

package {

  import cmodule.alchemy_speed_test.CLibInit;
  import flash.display.Sprite;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.geom.Matrix3D;
  import flash.geom.Vector3D;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.utils.getTimer;

  public class AlchemySpeedTest extends Sprite {

    private var vectorUtils:Object;

    public function AlchemySpeedTest() {

      // Set up the stage
      stage.align = StageAlign.TOP_LEFT;
      stage.scaleMode = StageScaleMode.NO_SCALE;

      // Create the Alchemy bridge to C++ methods
      var loader:CLibInit = new CLibInit;
      vectorUtils = loader.init();

      // Create a text field          
      var timerText1:TextField = new TextField();
      var timerText2:TextField = new TextField();
      var timerText3:TextField = new TextField();
      var timerText4:TextField = new TextField();
      timerText1.autoSize = TextFieldAutoSize.LEFT;
      timerText2.autoSize = TextFieldAutoSize.LEFT;
      timerText3.autoSize = TextFieldAutoSize.LEFT;
      timerText4.autoSize = TextFieldAutoSize.LEFT;
      timerText1.y = 0;
      timerText2.y = 30;
      timerText3.y = 60;
      timerText4.y = 90;
      addChild(timerText1);
      addChild(timerText2);
      addChild(timerText3);
      addChild(timerText4);
      var time0:int;
      var totalTime1:int;
      var totalTime2:int;
      var totalTime3:int;
      var totalTime4:int;

      // Perform the speed test
      time0 = getTimer();
      var vector1:Vector3D = speedTest1();
      totalTime1 = getTimer() - time0;

      time0 = getTimer();
      var vector2:Vector3D = speedTest2();
      totalTime2 = getTimer() - time0;

      time0 = getTimer();
      var vector3:Vector3D = nativeSpeedTest1();
      totalTime3 = getTimer() - time0;

      time0 = getTimer();
      var vector4:Vector3D = nativeSpeedTest2();
      totalTime4 = getTimer() - time0;
      // Display elapsed time and final vector
      timerText1.text = "1. Alchemy Time taken = " + totalTime1 + " vector = (" + vector1.x + ", " + vector1.y + ", " + vector1.z + ")";
      timerText2.text = "1. Native  Time taken = " + totalTime3 + " vector = (" + vector3.x + ", " + vector3.y + ", " + vector3.z + ")";
      timerText3.text = "2. Alchemy Time taken = " + totalTime2 + " vector = (" + vector2.x + ", " + vector2.y + ", " + vector2.z + ")";
      timerText4.text = "2. Native  Time taken = " + totalTime4 + " vector = (" + vector4.x + ", " + vector4.y + ", " + vector4.z + ")";
    }

    /**
     * Speed test using C++ to iteratively calculate the cross products of two vectors
     */
    private function speedTest1():Vector3D {
      var vector1:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      var vector2:Vector3D = new Vector3D(0.987, 0.654, 0.321);

      return vectorUtils.speedTest1(vector1, vector2);
    }

    /**
     * Speed test using C++ to iteratively calculate rotation matrices and apply these to a vector
     */
    private function speedTest2():Vector3D {
      var vector:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      return vectorUtils.speedTest2(vector);    
    }

    /**
     * Speed test using AS3 to iteratively calculate the cross products of two vectors
     */
    private function nativeSpeedTest1():Vector3D {
      var vector1:Vector3D = new Vector3D(0.123, 0.456, 0.789);
      var vector2:Vector3D = new Vector3D(0.987, 0.654, 0.321);
      var vector3:Vector3D;
      var time0:int = getTimer()
      for (var i:int = 0; i < 1000000; i++) {
        vector3 = vector1.crossProduct(vector2);
        vector3.normalize();
        vector1 = vector2;
        vector2 = vector3;
      }
      return vector3;
    }

    /**
     * Speed test using AS3 to iteratively calculate rotation matrices and apply these to a vector
     */
    private function nativeSpeedTest2():Vector3D {
      var vector:Vector3D = new Vector3D(0.123, 0.456, 0.789);

      var copy:Vector3D = vector.clone();

      var rotationX:Matrix3D = new Matrix3D();
      var rotationY:Matrix3D = new Matrix3D();
      var rotationZ:Matrix3D = new Matrix3D();

      for (var i:int = 0; i < 1000; i++) {
        vector = copy.clone();
        for (var ang:Number = 0; ang < 180; ang++) {
          rotationX.identity();
          rotationX.appendRotation(ang, Vector3D.X_AXIS);
          rotationY.identity();
          rotationY.appendRotation(ang, Vector3D.Y_AXIS);
          rotationZ.identity();
          rotationZ.appendRotation(ang, Vector3D.Z_AXIS);
          vector = rotationX.transformVector(vector);
          vector = rotationY.transformVector(vector);
          vector = rotationZ.transformVector(vector);
        }
      }
      return vector;
    }

  }
}

위 코드에서 alchemy_speed_test.cpp에 speedTest1(), speedTest2()가 만들어져 있고 AlchemySpeedTest.as 안에는 C++ 코드를 호출할 수 있는 함수와 ActionScript 3.0 Native Vector3D와 Matrix3D 속도를 테스트해보는 nativeSpeedTest1(), nativeSpeedTest2() 함수가 정의되어 있다.

 

speedTest1()과 nativeSpeedTest1()은 2개의 Vector간에 dot product 연산과 Normalisation을 1백만번 반복해서 결과를 반환하다. 반면 speedTest2()와 nativeSpeedTest2()는 x,y,z축 회전에 대한 Matrix를 3개 만들어 Vector를 회전을 반복한 다음 결과를 반환한다.

 

C++파일들을 컴파일 하기 위해 Makefile을 만들었다.

.SUFFIXES: .cpp .c .o

NAME=alchemy_speed_test
PATH:=${ALCHEMY_HOME}/achacks:${PATH}
CC = gcc
CCPP = g++
FLAGS = -O3 -Wall
OBJS =  $(NAME).o Matrix3D.o Vector3D.o

.cpp.o:
    $(CCPP) $(FLAGS) -c $<
.c.o:
    $(CC) $(FLAGS) -c $<

$(NAME) : $(OBJS)
    $(CC) $(FLAGS) -swc -o $(NAME).swc $(OBJS)

clean:
    rm $(OBJS) $(NAME).swc
    rm swfbridge.log
    rm *achacks*
    rm -r _sb_*

Window사용자중 Cygwin을 사용하시는 분은 make 패키지를 설치해야 이 Makefile을 구동할 수 있다.

 

프롬프트 상에서 make 명령을 하면 Makefile을 구동하여 C++파일을 컴파일 할 수 있다.  

 


 

만약 make를 사용하지 않으면 아래와 같이 컴파일해도 된다.

g++ –O3 –Wall –c alchemy_speed_test.cpp
g++ –O3 –Wall –c Matrix3D.cpp
g++ –O3 –Wall –c Vector3D.cpp
gcc –O3 –Wall –swc –o alchemy_speed_test.swc alchemy_speed_test.o Matrix3D.o Vector3D.o

 

마지막으로 컴파일된 alchemy_speed_test.swc 을 AlchemySpeedTest.as에 추가해서 컴파일 한다.

mxmlc.exe –library-path+=alchemy_speed_test.swc –target-player=10.0.0 AlchemySpeedTest.as

 

생성된 AlchemySpeedTest.swf 를 Flash Player 10에서 구동하면 다음과 같은 결과가 나온다.

1. Alchemy Time taken = 200 vector = (-0.40824829046386324, 0.8164965809277259, -0.4082482904638632)

1. Native  Time taken = 1065 vector = (-0.4082482904638631, 0.816496580927726, -0.4082482904638629)

2. Alchemy Time taken = 290 vector = (-0.6365049502756422, 0.662044570582514, -0.04630804289556738)

2. Native  Time taken = 893 vector = (-0.6365604400634766, 0.6619919538497925, -0.04631434381008148)

 

결국 결과는 다음과 같다.

Vector dot Product 연산과 Normalisation의 경우
- Alchemy : 201 ms
- Native : 1090 ms

회전행렬 생성과 Vector 변환의 경우
- Alchemy : 301 ms
- Native : 908 ms

이전과 다르게 수행속도의 차이를 보이고 있다. 이 결과만 놓고 볼 때 필요할 때는 C/C++로 만들어 최적화 할 수 있도록 만들 수 있겠다는 생각을 가진다. 하지만 많은 차이를 보이지 않아 실제 속도해결에 있어서 필요성에 대해서는 의문을 가질 수 있겠다. 아래서부터 Alchemy를 더욱 이해해 보도록 하겠다.

 

Alchemy를 깊게 이해하자.

 

Adobe labs에서 Alchemy에 대한 소개를 보면 ActionScript 3.0 코드보다 더욱 빠를 것이다라고 언급하고 있다. 하지만 지금까지의 결과를 볼 때 실제로는 기대만큼 효과적으로 빠른 것 같지는 않다. Alchemy를 사용해서 수행속도에 대한 이점을 이끌어내기 위해서는 Alchemy 본질을 어느 정도 이해할 필요가 있다고 생각했다.

 

아래 Alchemy에 대한 내용은 나름대로 자료를 찾아서 안되는 영어로 번역해보고 분석해봐서 정리한 내용이다. 혹시 잘못된 내용이 있을 수 있다. 따끔한 지적도 마다하지 않겠다. ^^

 

Alchemy를 이용해 C/C++ 코드를 SWF로 컴파일 하는 과정은 다음과 같다.

  1. C/C++ 코드를 LLVM(Low Level Virtual Machine) 바이트 코드로 변환한다.(확장자 .bc)
  2. LLVM 코드를 ActionScript 3.0 코드로 변환한다. (확장자 .as)
  3. ActionScript 3.0 코드를 ActionScript Byte Code로 변환한다.(확장자 .abc)
  4. 마지막으로 swc를 만든다. (확장자 .swc)

Alchemy의 중요 요소중 하나는 LLVM(Low Level Virtual Machine)이다. LLVM은 컴파일러 컴포넌트로 여러 언어를 중간코드로 변경하여 언어와 플랫폼에 독립적이고 컴파일, 링크시 런타임등 각 시점에서 최적화를 시켜준다. 자체적인 중간코드(intermediate representation)을 바탕으로 프로그래밍 언어와 CPU에 독립적인 최적화기, GCC기반의 C/C++ 프론트엔드(front-end), 그리고 X86, X86-64, PowerPC, ARM, IA-64, SPARC, Mips, Alpha, c 소스 백엔드(back-end), 또한 메모리에 코드를 생성해 실행할 수 있는 JIT 백엔드 등을 포함하고 있다.

 

Alchemy 설치 디렉토리를 $ALCHEMY_HOME 이라고 한다면 $ALCHEMY_HOME/achacks 내에 gcc가 있다. gcc는 sh코드로 만들어져 있고 이 안에서 C/C++코드를 SWC로 컴파일하기 위한 모든 과정이 들어간다. gcc 파일에서 첫번째 하는 과정은 해당 C/C++코드를 LLVM 바이트 코드(.bc)로 바꾸는 일이다. 이때 사용되는 도구가 $ALCHEMY_HOME/bin/llvm-gcc와 llvm-ld이다. 예를 들어 아래와 같은 코드가 실행된다.

$ llvm-gcc -v -emit-llvm -nostdinc -I$ALCHEMY_HOME/avm2-libc/include -I/usr/local/include –include $ALCHEMY_HOME/avm2-libc/avm2/AVM2Env.h echotest.c -c -o echo.o

$ llvm-ld -o=echotest -O5 -internalize-public-api-list=_start,malloc,free,__adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3, __fixdfdi,__fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf, __iordi3,__lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__qdivrem, __adddi3,__anddi3,__ashldi3,__ashrdi3,__cmpdi2,__divdi3,__qdivrem,__fixdfdi, __fixsfdi,__fixunsdfdi,__fixunssfdi,__floatdidf,__floatdisf,__floatunsdidf,__iordi3, __lshldi3,__lshrdi3,__moddi3,__muldi3,__negdi2,__one_cmpldi2,__subdi3, __ucmpdi2,__udivdi3,__umoddi3,__xordi3,__subdi3,__ucmpdi2,__udivdi3, __umoddi3,__xordi3,__error $ALCHEMY_HOME/avm2-libc/lib/avm2-libc.l.bc echotest.o

위 과정은 llvm-gcc로 echotest.c를 echotest.o로 만들고 llvm-ld로 echotest.o를 avm2-libc.l.bc와 링크해서 echotest와 echotest.bc를 만들어준다. echotest는 echotest.bc를 실행하는 sh파일이다.

 

참고로 LLVM 바이트 코드인 echotest.bc는 $ALCHEMY_HOME/bin/llvm-dis를 이용해 이 도구는 disassemble하는 역할을 한다.  LLVM 홈페이지에 online demo를 보면 쉽게 이런 과정을 볼 수 있다. online demo에서 LLVM disassemble를 보면 다음 c코드가 어떻게 LLVM 바이트 코드로 바뀌는지 확인해볼 수 있다.

#include <stdio.h>
int main(int argc, char **argv) {
    printf("%d\n", 1);
}

다음은 위 코드를 LLVM 바이트 코드로 바꾼 것은 disassemble 한 것이다.

; ModuleID = ‘/tmp/webcompile/_5616_0.bc’
target datalayout = "e-p:32:32:32-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-f32:32:32-f64:32:64-v64:64:64-v128:128:128-a0:0:64-f80:32:32"
target triple = "i386-pc-linux-gnu"
@.str = internal constant [4 x i8] c"%d\0A\00"        ; <[4 x i8]*> [#uses=1]

define i32 @main(i32 %argc, i8** %argv) nounwind {
entry:
    %0 = tail call i32 (i8*, …)* @printf(i8* noalias getelementptr ([4 x i8]* @.str, i32 0, i32 0), i32 1) nounwind        ; <i32> [#uses=0]
    ret i32 undef
}

declare i32 @printf(i8*, …) nounwind

 

다음으로 $ALCHEMY_HOME/bin에 있는 llc 도구를 이용해 ActionScript 3.0 코드를 만든다.

$ llc -march=avm2 -avm2-use-memuser -o=echotest.as -avm2-package-name=cmodule.echotest echotest.bc

다음 코드가 만들어진 ActionScript 3.0 코드이다.

package cmodule.speedtest {
// Start of file scope inline assembly
import flash.utils.*
import flash.display.*
import flash.text.*
import flash.events.*
import flash.net.*
import flash.system.*

public var gdomainClass:Class;
public var gshell:Boolean = false;

public function establishEnv():void
{
  try
  {
    var ns:Namespace = new Namespace("avmplus");
    gdomainClass = ns::["Domain"];
    gshell = true;
  }
  catch(e:*) {}
  if(!gdomainClass)
  {
    var ns:Namespace = new Namespace("flash.system");
    gdomainClass = ns::["ApplicationDomain"];
  }
}

establishEnv();

……(생략)

public const _c_speed_test:int = regFunc(FSM_c_speed_test.start)

public final class FSM_c_speed_test extends Machine {

    public static function start():void {
            var result:FSM_c_speed_test = new FSM_c_speed_test
        gstate.gworker = result
    }

    public var i0:int

    public static const intRegCount:int = 1
    public var f0:Number, f1:Number, f2:Number, f3:Number

    public static const NumberRegCount:int = 4
    public final override function work():void {
        Alchemy::SetjmpAbuse { freezeCache = 0; }
        __asm(label, lbl("_c_speed_test_entry"))
        __asm(push(state), switchjump(
            "_c_speed_test_errState",
            "_c_speed_test_state0",
            "_c_speed_test_state1"))
    __asm(lbl("_c_speed_test_state0"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_0_F"))
        mstate.esp -= 4; __asm(push(mstate.ebp), push(mstate.esp), op(0×3c))
        mstate.ebp = mstate.esp
        mstate.esp -= 0
        f0 =  (0)
        i0 =  (0)
    __asm(jump, target("_c_speed_test__XprivateX__BB75_1_F"), lbl("_c_speed_test__XprivateX__BB75_1_B"), label, lbl("_c_speed_test__XprivateX__BB75_1_F"));
        f1 = f0
        f2 =  (Number(i0))
        f0 = f2
        //InlineAsmStart
    f0 =  Math.sin(f0);

    //InlineAsmEnd
        f3 = f0
        f0 = f2
        //InlineAsmStart
    f0 =  Math.cos(f0);

    //InlineAsmEnd
        f0 =  (f3 * f0)
        i0 =  (i0 + 1)
        f0 =  (f0 + f1)
        __asm(push(i0==10000000), iftrue, target("_c_speed_test__XprivateX__BB75_3_F"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_2_F"))
        __asm(jump, target("_c_speed_test__XprivateX__BB75_1_B"))
    __asm(lbl("_c_speed_test__XprivateX__BB75_3_F"))
        mstate.esp -= 8
        __asm(push(f0), push(mstate.esp), op(0×3e))
        state = 1
        mstate.esp -= 4;(mstate.funcs[_AS3_Number])()
        return
    __asm(lbl("_c_speed_test_state1"))
        i0 = mstate.eax
        mstate.esp += 8
        mstate.eax = i0
        mstate.esp = mstate.ebp
        mstate.ebp = __xasm<int>(push(mstate.esp), op(0×37)); mstate.esp += 4
        //RETL
        mstate.esp += 4
        mstate.gworker = caller
        return
    __asm(lbl("_c_speed_test_errState"))
        throw("Invalid state in _c_speed_test")
    }
}

……(생략)

 

 

Alchemy로 C 또는 C++ 코드의 컴파일 결과는 기본적으로 ActionScript 와 AVM2 바이트 코드로 만들어진 하나의 클래스가 된다. 이 과정에서 결과물인 SWC는 C와 C++이 C 라이브러리와 POSIX를 지원에 필요할 수 있는 것도 함께 컴파일 되므로 크기가 커진다. 가령 54줄의 C코드를 Alchemy를 통해 컴파일 하면 27415줄의 ActionScript 코드가 만들어진다. 방금 위에서 이러한 현상을 확인할 수 있었다.

 

다음으로 asc.jar를 이용해 만들어진 ActionScript 파일을 swf로 만든다. 이 과정에서 playerglobal.abc와 global.abc를 포함한다.

$ java -Xms16M -Xmx1024M -jar $ALCHEMY_HOME/bin/asc.jar -AS3 -strict -import $ALCHEMY_HOME/flashlibs/global.abc -import $ALCHEMY_HOME/playerglobal.abc -d -config Alchemy::Shell=false -config Alchemy::NoShell=true -config Alchemy::LogLevel=0 -config Alchemy::Vector=true -config Alchemy::NoVector=false -config Alchemy::SetjmpAbuse=false -swf cmodule.echotest.EchoTest,800,600,60 echotest.as

다음으로 만들어진 echotest.swf를 ActionScript 바이트 코드로 만들어주고 라이브러리와

$ GetABC2.pl echotest echotest.swf.abc

$ALCHEMY_HOME/achacks에 있는 swctmpl.swf를 위에서 만든 abc로 대체해 새로운 swf를 만들고 SWF 버전 태그를 10으로 한다.

$ PutABC2.pl $ALCHEMY_HOME/swctmpl.swf temp.swf echotest.swf.abc cmodule/echotest/CLibInit 
$ V10SWF.pl temp.swf library.swf

마지막으로 다음과 같은 catalog.xml과 함께 최종적으로 SWC를 만든다.

<? xml version = "1.0"encoding = "utf-8"?>
<swc xmlns = "http://www.adobe.com/flash/swccatalog/9">
  <versions>
    <swc version = "1.0" />
    <flex version = "2.0"    build = "143452"/>
  </ versions>
  <features>
    <feature-script-deps />
    <feature-files />
  </ features>
  <libraries>
    <library path = "library.swf">
      <script name = "cmodule/echotest/ CLibInit" mod = "1177909560000">
        <def id = "cmodule.hello:CLibInit" /> 
        <dep id = "Date"    type = "e"/> 
        <dep id = "Date"    type = "s"/> 
        <dep id = "flash.utils : Dictionary" type = "e"/> 
        <dep id = "flash.utils : Dictionary" type = "s"/> 
:
(생략)
:
        <dep id = "AS3" type = "n"/> 
        <dep id = "Object" type = "i"/> 
      </ script>
    </ library>
  </ libraries>
  <Files>
  </ files>
</ swc>

$ zip echotest.swc catalog.xml library.swf

 

이처럼 Alchemy를 통해 얻는 결과물은 일반적으로 SWC이다(SWF의 경우 사용가치가 떨어진다.). 이 SWC는 Flex 3(Flash Player 10을 겨냥해서 만들어져야 함), Flex 4(아직 개발중인 “Gumbo”) 그리고 Flash CS4에서 사용할 수 있다.

 

C와 C++로 만들어졌기 때문에 Alchemy로 컴파일 된 SWF가 일반 C/C++의 규칙과 제한을 따른다고 생각하면 곤란하다. C/C++는 일반 SWF와 같이 Flash Player의 보안샌드박스안에 포함되고 모든 화면출력결과물은 display list를 통과하도록 만들어진다. 그리고 네트워크 및 파일엑세스도 이미 존재하는 클래스를 사용하게 된다. Alchemy로부터 컴파일된 라이브러리에서 만들어진 코드는 적절한 메모리 덩어리를 ByteArray로 할당한다. 이것은 Alchemy가 어떻게 포인터, malloc, free등이 ActionScript 에서 어떻게 작업되는가 알려주는 개념이 된다. 이말은 즉 C/C++언어가 Alchemy를 통해 100% ActionScript 3.0으로 변경되어 원천적으로 가지고 있는 SWF의 제약사항을 넘어갈 수는 없다는 것을 의미한다.

 

그럼에도 불구하고 Alchemy의 AVM2 코드가 Flash 와 Flex에서 만들어진 AVM2 코드보다 어떻게 수행속도가 빠를 수 있는지 궁금할 거다. 실제로는 더 빠르지 않다. 그럼 Adobe 에서 주장하는 “수행속도가 빠를 것이다”라는 것은 거짓말인가? 100% ActionScript 3.0으로 바뀌는데 수행속도가 빠를 수 없다고 생각할 수 있다. 하지만 이 부분에서 수행속도의 개선을 찾으면 안된다. 실제로는 Alchemy의 LLVM은 많은 최적화를 이루는 반면 Flash와 Flex의 컴파일러는 최적화 과정이 없다. 또한 Alchemy는 ActionScript 3.0 자체가 가지는 오버헤드의 문제로 인한 수행속도 저하를 극복하는데 쓰일 수 있다.

 

그럼 Alchemy의 수행속도에 대해 기대할 수 있는 부분은 구체적으로 무엇일가? 바로 메모리 엑세스와 함수 호출이다. Alchemy를 통해 컴파일된 코드는 ByteArray를 적용하기 위해 Flash Player 10에 적용된 새로운 ByteArray를 사용한다. 마치 이것은 RAM처럼 이용된다. ActionScript 3.0의 함수를 호출할 때 그들의 인자가 박싱(boxed) 와 언박싱(unboxed)를 하게 된다. 하지만 Alchemy로 만들어진 코드는 그러한 과정이 없다. 이 두가지 이유로 Alchemy로 만들어진 결과물이 수행속도 향상에 도움이 될 수 있다는 것이다.

 

ActionScript 코드와 Alchemy 코드간에 통신과정에서 수행속도가 떨어질 수 있다. 이 과정은 마샬링(marshaling)이 된다. 마샬링은 어떤 한 언어에서 작성된 출력 데이터를 다른 언어의 입력단으로 전달해야하는 경우 데이터 변환과정이 생기는 것을 말하는데 ActionScript 코드와 Alchemy 코드간에 빈번한 상대방의 함수 호출은 이런 과정으로 인해 전체적인 수행속도를 죽이는 일을 발생시킬 수 있다. 그러므로 어떤 처리를 위해 C/C++코드에서 한꺼번에 처리하는 과정이 들어가도록 코딩하고 상대방 함수로 넘겨주는 파라미터의 수를 줄여주는 것이 속도향상에 도움이 될 것이다.

 

Alchemy는 매우 강력하지만 마법사는 아니다. Alchemy의 이점이 무엇인지 잘 파악해서 사용하는 것이 현명하다. 본인이 생각할 때 Alchemy를 사용해야 하는 경우는 다음과 같다.

1. Alchemy로 포퍼먼스 향상에 어느 정도 이득이 있는 경우
2. 기존 C/C++ 라이브러리를 ActionScript 3.0으로 바꾸기 힘들거나 별도의 변경 없이 그래로 Flash/Flex/AIR 프로젝트에서 사용하고 싶은 경우

위 2가지 경우 중 1가지라도 있다고 판단하면 Alchemy를 활용해서 자신의 Adobe RIA 프로젝트에 접목시킬 수 있다고 생각한다. 2번째의 경우 C/C++ 개발자와 Adobe RIA 개발자간에 협업도 기대할 수 있는 부분이다.

 

한가지 더 언급할 것은 Alchemy는 아직 정식 배포상태가 아니라는 점이다. 그래서 약간 개발하기도 불편하고 버그도 간혹 보인다. 하지만 정식 배로가 된다면 그러한 문제점은 해결될 것이라 생각하고 지금까지 보여줬던 것 보다 더욱 큰 포퍼먼스 향상을 기대할 수 있다. 추후에는 Flash Catalyst에 포함되어 개발에 큰 도움이 될 것이다.

 

정리하며

 

Alchemy의 장점은 메모리 엑세스와 함수호출에 따른 수행속도의 개선과 기존 C/C++ 라이브러리를 그대로 활용할 수 있다는 점에 있다. 이 장점을 잘 살려서 현업에 적용할 수 있도록 적절한 예제를 찾아보려한다.

 

이 글을 적으면서도 Alchemy의 컴파일 과정 및 내용에 대해서 확실히 이해할 수 없었다. 하지만 실망하지 않는다. 어쨌던 이러한 노력은 나중에 큰 도움이 될 것이다라고 생각하기 때문이다.

 

이 글을 보신 분 중 공유할 수 있는 내용이나 지적사항이 있다면 언제든지 댓글 환영한다. ^^


한국에도 Alchemy를 활용한 많은 예제와 현업에서 적용 예가 많이 나왔으면 한다.

 

참고내용

 

아래 테스트 코드는 문제가 있었습니다. 왜냐하면 GCC 최적화 옵션에 대해서 명확히 이해를 못하고 사용했기 때문입니다. 그래서 잘못된 지식을 전하는 것을 우려해 그냥 내용만 훑어보시되 “Adobe Alchemy 수행속도 테스트와 깊이 이해하기”글을 덧붙여 읽어주시길 바랍니다. 제가 잘못된 내용을 적었다면 지적해주셨으면 합니다. 그리고 실무에 썼거나 테스트를 해보신 분이라면 그에 대해 소개도 부탁드릴께요.

Alchemy는 C/C++ 코드를 AVM2(ActionScript 3 Virtual Machine) 환경에서 동작하도록 해주는 도구이다. Alchemy를 이용해 기존에 있는 C/C++ 코드를 Adobe AIR, Adobe Flex, Adobe Flash 프로젝트에서 직접 활용할 수 있는 SWF 또는 SWC를 만들어낼 수 있다. 이에 대한 더욱 자세한 사항은 본인이 게재한 “C/C++와 Flash 플랫폼과의 만남. Alchemy 1부” 를 참고하기 바란다.

 

이 글은 Alchemy를 이용해 만들어진 SWC를 이용했을 때와 순수 ActionScript 3.0으로만 코딩했을 때 수행속도 차이를 비교해보는 것을 목적으로 한다.

 

Alchemy 속도 테스트

 

과연 Alchemy를 이용해서 C언어를 SWC로 변환한 코드의 수행속도에 진전이 있을까? 아래 c코드와 ActionScript 3.0 코드는 이를 테스트할 수 있도록 한다. c언어와 ActionScript 3.0에서 동일하게 ”sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3)” 식을 1백만번 반복한다.

 

컴파일하고 수행하는 방법은 “C/C++와 Flash 플랫폼과의 만남. Alchemy 1부”를 참고한다.

1. speedtest.c

#include <math.h>
#include "AS3.h"

static AS3_Val c_speed_test(void* self, AS3_Val args)
{
    int i;
    double result;
    for( i=0; i < 1000000; i++ )
    {
        result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
    }
    return AS3_Number(result);
}

int main()
{
    AS3_Val method = AS3_Function( NULL, c_speed_test );
    AS3_Val result = AS3_Object( "c_speed_test: AS3ValType", method );
    AS3_Release( method );
    AS3_LibInit( result );
    return 0;
}

 

2. AlchemySpeedTest.as

package {
    import cmodule.speedtest.CLibInit;

    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.getTimer;

    public class AlchemySpeedTest extends Sprite
    {
        public function AlchemySpeedTest()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;

            var loader:CLibInit = new CLibInit();
            var lib:Object = loader.init();
            var startTime:Number;

            startTime = getTimer();
            var cResult:String = "c returned value : " + lib.c_speed_test(null) + " delay time(ms) : " + (getTimer()-startTime);

            startTime = getTimer();
            var asResult:String = "as3 returned value : " + as3_speed_test() + " delay time(ms) : " + (getTimer()-startTime);

            var textField:TextField = new TextField();
            textField.text = cResult + "\n" + asResult;
            textField.width = 500;
            textField.autoSize = TextFieldAutoSize.LEFT;
            textField.wordWrap = true;
            textField.defaultTextFormat = new TextFormat( null, 12 );
            addChild(textField);

        }
        public function as3_speed_test():Number
        {
            var i:int;
            var result:Number;
            //var sin:Function = Math.sin;
            //var cos:Function = Math.cos;
            for (i = 0; i < 1000000; i++)
            {
            result = Math.sin( 0.332 ) + Math.cos( 0.123 ) + Math.tan( 0.333 ) * Math.log(3);
            }
            return result;
        }
    }
}

 

speedtest.c를 아래처럼 Alchemy로 컴파일하고 만들어진 speedtest.swc를 AlchemySpeedTest.as와 함께 mxmlc로 컴파일 한다.

gcc speedtest.c –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

아래는 위 프로그램을 시행한 결과이다.

c returned value : 1.6983678388082433 delay time(ms) : 559
as3 returned value : 1.6983678388082433 delay time(ms) : 694

이 결과는 의외의 결과이다. C와 ActionScript 3.0과 같은 수행속도를 보였기 때문이다. 하지만 이 결과는 C코드를 컴파일 할 때 최적화하지 않았기 때문이다. 아래처럼 –03 –Wall 옵션을 넣고 다시 수행해보자. -O3는 최적화 옵션이고 –Wall(또는 –W)는 모든 경고를 출력하도록 한다.

gcc speedtest.c –03 –Wall –swc –o speedtest.swc
mxmlc.exe –library-path+=speedtest.swc –target-player=10.0.0 AlchemySpeedTest.as

컴파일한 SWF파일을 실행한 결과는 다음과 같다.

c returned value : 1.6983678388082433 delay time(ms) : 1
as3 returned value : 1.6983678388082433 delay time(ms) : 601

 

결론

 

위 실험 결과를 통해 Alchemy를 이용해 C/C++의 수행속도를 잘 활용하면 좋다는 것을 알 수 있었다. 가령, ActionScript 3.0으로 구동 시에 많은 부하가 있을 가능성이 있는 부분을 C로 만들어 Alchemy를 통해 최적화된 SWC로 라이브러리화 해서 사용한다면 여러분의 Flash/Flex/AIR 애플리케이션의 속도를 크게 향상시킬 수 있다. 구체적으로 Audio/Video, 데이터 형식 변환, 데이터 조작, XML 분석 및 암호화 기능, 물리 시뮬레이션등이 들어가는 코드에서 포퍼먼스 향상을 기대할 수 있겠다.

아래는 Alchemy가 C/C++코드를 ActionScript 3.0 코드로 완벽하게 변경하는가에 대한 의견이다.

(글쓴이 의견)

어떤 분은 Alchemy로 SWF를 만들어준다고 해서 C/C++코드가 전부 ActionScript 3.0으로 변환될 것이다라는 생각을 가질 수 있다. 하지만 실험 결과만 가지고 볼 때 틀리다는 것을 유추할 수 있다. Alchemy는 C/C++ 코드를 AVM2 환경에서 동작하도록 만들되 ActionScript 3.0과 함께 사용할 수 있도록 만들어준다. 즉 C/C++를 적절하게 변경하고 ActionScript 3.0과 통신할 수 있는 통로구를 마련해주는 것이 Alchemy의 목적이다. 만약 ActionScript 3.0으로만 변경되는 것이라면 빠른 수행속도를 기대하기 어려울 것이다.

(양병규님 의견)

컴파일러에 대해서 조금 관심을 가져보시면 알 수 있을텐데요…
음… 뭐랄까…
그 최적화라는 것이 불필요한 일은 안하고 같은 기능이라면 더 빠른 기능으로 대치하고.. 그런일을 하는 것이 컴파일러의 최적화인데요..
제가 보기에는
for( i=0; i < 1000000; i++ )
{
result = sin( 0.332 ) + cos( 0.123 ) + tan(0.333) * log(3);
}
이 부분이.. 최적화를 하면 루프를 1000000번 돌지 않고 한방에 끝낼것같습니다. 최적화알고리즘중에서 루프문안에서의 내용이 반복을 해도 변화가 없는 경우에는 루프를 한방에 끝내도록 하는 알고리즘이 있습니다. 이 예가 그런 예일것 같은데요..

암튼.. ActionScript에는 그런 최적화가 없는데 이미 오랜 역사동안 만들어진 C/C++언어의 최적화 기술이 적용되면 분명히 나은 결과를 보일 것 으로 예상됩니다. ^^;(물론 코딩하는 사람의 실력이 좋을 수록 최적화의 효과가 떨어지겠지만)

그리고..
저는 아무리 봐도 C/C++로 코딩한 것이 결국 몽땅 ActionScript로 만들어지는 것이 맞는 것 같습니다. SWF에는 ActionScript 이 외에 다른 실행코드는 존재하지 않습니다. 최종적으로 만들어진 결과가 *.SWF(*.SWC)이지 않습니까? SWF File Format Specification Version 10 문서 278페이지를 다 훑어봐도 ActionScript 이외에는 실행코드가 없습니다. 머.. DLL을 임포트한다거나 별도의 실행코드용 외부 라이브러리가 있다거나… 그런건 전혀 없습니다.

malloc 함수 같은 경우의 예를 들어보면 ActionScript에는 malloc과 동일한 역할을 하는 어셈블명령은 없습니다. 하지만 ActionInitArray와 같이 긴 변수를 사용할 수 있는 명령으로 ‘구현’은 할 수 있을겁니다. 아마도 그런 방법으로 구현되지 않았나싶습니다.

어쨋든 Alchemy는 C/C++을 100% ActionScript로 변환해 주는게 맞고.. 함수사용, 변수사용, 최적화, 수학함수의 구현, 각종 기초 알고리즘등 기초적인 부분에서 훨씬 우월하기 때문에 좋은 효과를 나타내고 있는 것으로 생각됩니다.

시간이 나면 Alchemy에서 만들어진 SWF 파일을 분석해 보면 아주 좋은 공부가 될것같습니다. ^^;

Alchemy가 유용한 것은 C/C++ 코드를 ActionScript 3.0와 함께 쉽게 사용할 수 있게끔 해주는 것과 C/C++자체가 가지는 빠른 수행속도를 Flash 컨텐츠에서 이용할 수 있는 것으로 생각한다. Alchemy가 앞으로 사용 편의성 향상과 여러 환경에서 문제없이 개발할 수 있도록 개선된다면 Adobe RIA기술이 한 단계 업그레이드 될 것이라 생각한다.

 

한가지 주의할 사항은 전부 C/C++로 만드는 것은 좋지 못하다. Alchemy를 적용하기에 앞서 본문에서 실험한 것과 같은 방법으로 여러가지 수행 테스트를 해보고 순수하게 ActionScript 3.0으로 한 것보다 큰 포퍼먼스를 기대할 수 있다고 판단했을 때 적용하는 것이 좋다. 때로는 어떤 코드냐에 따라 별로 큰 차이가 없을 수 있기 때문이다.

국내에 Alchemy를 응용한 많은 예제와 애플리케이션들이 나오길 바란다.

 

참고할 사이트

 

이 문서는 Adobe 연구 프로젝트 코드명중 하나인 “Alchemy”에 대해서 소개한다.

 

Alchemy는 2008년 미국에서 열린 Adobe MAX 컨퍼런스 행사에서 최초로 소개되었고 C/C++ 코드를 오픈 소스 ActionScript Virtual Machine(AVM2)용으로 컴파일 목적으로 연구 중인 프로젝트이다.

 

Alchemy는 운영체제(OS)에 의존성이 없는 C/C++ 라이브러리를 Flash Player 10 또는 Adobe AIR 1.5에서 실행되는 SWF 또는 SWC 형태로 컴파일할 수 있도록 해준다. 컴파일은 GCC(GNU Compiler Collection)를 이용해 LLVM(Low Level Virtual Machine)으로 변환한 뒤 이것을 AVM 바이트 코드로 변경시켜주는 과정이 들어간다.

주로 Audio/Video, 데이터 형식 변환, 데이터 조작, XML 분석 및 암호화 기능, 물리 시뮬레이션 등 부하가 일어나는 계산에 응용하면 적합하다. 이러한 계산을 할 때, ActionScript 3.0보다 훨씬 빠르지만 순수한 C/C++ 코드보다 2~10배 정도 느릴 수 있다.

 

C와 C++로 만들어진 코드는 Alchemy를 통해 ActionScript 3.0 환경에서 사용할 수 있도록 SWF 또는 SWC로 컴파일 되므로 이를 가져다가 Adobe Flex, Flash, AIR 환경에서 개발할 수 있게 된다.

 

Alchemy로 제작된 결과물은 Flash Player 10이나 AIR 1.5 이상에서 구동되며, Windows, Mac OS X, Linux등의 다양한 운영체제에서 사용할 수 있다.

현재 Alchemy는 아직 미완성된 버전이다. 정식 배포가 되기 전까지는 개발의 불편함과 발생할 오류 등에 대해 감수할 수 밖에 없다.

 

이 문서는 총 2부로 나뉘어 쓰여진다. 1부에서는 개발환경 구축 및 간단한 예제 소개를 통해 Alchemy에 대해서 이해하는 것을 목적으로 한다. 2부에서는 간단한 응용을 통해 실무에 어떻게 적용될 수 있는지 알아보도록 하겠다.

Alchemy은 사전적으로 연금술(鍊金術)이라 불리운다. 비금속을 인공적 수단으로 귀금속으로 전환하는 것을 목표로 삼는 연금술은 그 의미로 봐서는 과학적으로 가능하지 않다. 하지만 인간의 이러한 노력은 과학적 접근을 통해 물질에 대한 명확한 분석을 가능하게 하는데 큰 역할을 했다고 생각한다. Alchemy는 한국어로 알크미 정도로 발음하면 될 것 같다.

Adobe에서 연구 프로젝트 이름으로 Alchemy라는 용어를 왜 썼을까? C/C++과 Flash를 섞어 금과 같이 가치 있는 것을 만들겠다는 의미가 내포되어 있는 듯 하다.

 

Alchemy 소개 동영상

 

Alchemy에 대해서 소개하는 동영상을 보도록 하자. 영어듣기가 어려우신 분들은 뛰어넘어가도 좋겠다.

 

아래는 Adobe 이벤젤리스트인 라이언 스튜어트(Ryan Stewart)가 Alchemy에 대해서 소개한 동영상을 담고 있다.

http://tv.adobe.com/#vi+f1472v1033

 

아래는 Adobe MAX 컨퍼런스 행사에서 Alchemy에 대해서 소개하는 동영상이다.

 

http://kr.youtube.com/watch?v=0hX-Uh3oTcE

 

아래는 Automate Studios의 최고기술경영자이자 최고 소프트웨어 설계 책임자인 Brandel Hall이 Alchemy를 소개하는 동영상이다.

 

http://labs.adobe.com/technologies/alchemy/videos/brandenhall/

 

Alchemy를 이용하여 C코드를 Flash로 변환한 게임 : Doom 1

 

Alchemy에 대한 흥미를 돋구기 위한 것으로 이것만 한 것이 없는 것 같다. 93년 작품인 DOS에서 돌아갔던 Doom 1이다. 아래 그림은 C코드로 만들어진 Doom 게임을 Alchemy를 이용해 Flash로 변환하여 실행한 모습을 스크린샷 한 것이다.

 

이처럼 Alchemy를 이용하면 기존 C/C++코드를 다양하게 활용할 수 있다.
아래 링크를 통해 Doom 1을 실행해자.

이 코드는 제작자가 Alchemy 개발환경에서 직접 컴파일할 수 있도록 소스를 공개해두었다.

Doom 게임을 직접 Flash 기반으로 포팅하는 소스도 공개가 되어 있다 참고하길 바란다. 이것은 Alchemy와 상관이 없다.

 

Alchemy 활용을 위한 지침

 

1. 기존 C/C++ 코드 이용

이미 C/C++로 만들어진 코드가 많이 있다. 암호화 알고리즘이나 압축 알고리즘 등은 이미 C/C++로 만들어져 있는데 이러한 코드를 일일히 ActionScript 3.0으로 바꾼다는 것은 비능률적이다. Alchemy는 이용하면 기존 C/C++ 코드를 쉽게 ActionScript 3.0에서 활용할 수 있는데 큰 도움을 줄 수 있다.

2. C/C++로 최적화

C/C++은 ActionScript 3.0보다 더 최적화된 코드를 만들어낼 수 있다. 가령 ByteArray를 다루는 일이나 많은 연산을 처리하는 작업에서 큰 포퍼먼스 향상을 기대할 수 있다. 게다가 C/C++ 특성상 직접 메모리 관리가 된다. 이러한 이유로 복잡한 Audio/Video 데이터 형식 변경 및 데이터 조작, 속도를 요하는 과학 시뮬레이션 핵심 코드 등에 적절하게 활용하면 매우 유용하게 쓸 수 있다. 구체적으로 복잡한 연산은 C/C++이 담당하고 렌더링은 ActionScript 3.0이 담당하게 한다면 전체 ActionScript 3.0으로 구현하는 것보다 몇 배의 포퍼먼스 향상도 기대할 수 있다. C/C++로 만들어진 코드와 ActionScript 3.0 간에 빈번한 통신은 오히려 속도를 저해시키는 요인이 될 수 있으며 제작만 어려워지는 상황이 발생할 수도 있으니 잘 구분해서 활용해야겠다.

3. C/C++로 만들었기 때문에 다 할 수 있는 것은 아니다.

C/C++을 이용하지만 AVM 환경에서 돌아가는 것을 목적으로 하기 때문에 몇가지 제약이 따른다. 가령 Win32 API 기반의 C코드나 DirectX 코드를 변환해 사용할 수 없다. 또한 Flash Player의 보안샌드박스에 위반하는 작업도 할 수 없다.

 

Alchemy 입문을 위한 참고 사항

 

Alchemy를 사용하기 위해 다음 항목을 참고하면 되겠다.

  1. Alchemy는 Flash Player 10 환경 및 Adobe AIR 1.5 Runtime 환경에서 동작하므로 해당 프로그램을 다운로드 받는다.
  2. 자신의 운영체제(Windows/Mac OSX/Linux 지원)에 맞는 Alchemy 툴킷(toolkit)을 다운로드 받는다.
  3. Getting Stated 문서를 통해 Alchemy 설치 및 사용방법을 익힌다. 이 문서에는 Alchemy 설치방법과 C/C++코드를 SWC로 변환하는 방법, 그리고 SWC를 이용한 간단한 ActionScript 3.0 예제를 보여준다.
  4. 질문이나 정보공유를 위해 Alchemy 포럼 페이지를 이용한다.
  5. Alchemy에 대한 간단한 라이브러리를 다운로드 받을 수 있고 또한 라이브러리 공유를 할 수 있다.

 

Alchemy 시스템 요구사항

 

Alchemy 개발환경을 구축하기 위해 운영체제 별로 다음과 같은 사양을 요구한다.

1. Windows

Window XP SP2(본인은 SP3)에서만 테스트 되었다. Vista의 경우는 테스트 되지 않았다. 다음 패키지들이 요구된다.

  • Cygwin 개발 도구
  • Java 1.4 이상
  • Perl(또한 Compress::Raw::Zlib 패키지를 설치해야 한다.)
  • Flash Player 10
  • 기타추가사항 : Adobe AIR 1.5 이상 런타임, Adobe AIR 1.5 SDK 이상 또는 Adobe Flex SDK 3.2 이상

2. Mac OSX

Mac OSX 환경에서는 다음 패키지들이 필요하다.

  • Perl 5.8 이상(Compress::Raw::Zlib.pm 포함)
  • Java 1.5 이상
  • Xcode 3.0 이상
  • Flash Player 10 이상
  • 기타추가사항 : Adobe AIR 1.5 이상 런타임, Adobe AIR 1.5 SDK 이상 또는 Adobe Flex SDK 3.2 이상

3. Linux

Ubuntu 8.0.4 버전에서만 테스트 되었다. 다음 패키지가 필요하다.

  • Java 1.4 이상
  • Perl(또한 Compress::Raw::Zlib 패키지를 설치해야 한다.)
  • Flash Player 10 이상
  • 기타추가사항 : Adobe AIR 1.1 beta 런타임, Adobe AIR 1.1 beta SDK와 Flex SDK 3.2 이상

 

Microsoft Windows에서 Alchemy 설치

 

MS Windows XP 환경에서 설치 방법을 설명하겠다. 물론 Mac OS X나 Linux환경에서도 가능하다. Mac OS X와 Linux에서는 gcc 개발이 가능하므로 Windows와 달리 Cygwin과 같은 프로그램을 설치할 필요가 없다는 점이 편리하다.

 

1. 설치 요구사항(MS Window 환경인 경우)

2. Cygwin 설치

Cygwin을 다운로드 받아 설치한다. Cygwin은 Window 상에서 Linux와 비슷한 환경을 제공해주는 프로그램이라고 할 수 있다. 그러므로 Linux환경에 어느 정도 경험있는 사람이라면 Cygwin도 쉽게 적응할 수 있을 것이다. Window환경에서 Alchemy를 동작시키기 위해서는 Cygwin이 필요하지만 Linux, Mac OS X에서는 필요없다.

 

 

인터넷을 이용해 설치하도록 한다.

 

 

설치할 폴더를 지정한다.

 

 

설치를 위한 패키지 파일을 다운로드로 하기 위한 임시 폴더를 지정한다.

 

 

인터넷 연결을 선택한다. Direct Connection을 선택해 사용하면 되겠다.

 

 

패키지를 다운로드 할 미러 사이트를 선정한다. daum.net에서 지원하므로 이용하자.

 

아래와 같이 Select Packages가 뜨면 필요한 패키지를 선택해 설치할 수 있도록 한다.(앞서 Perl, zip, gcc/g++, vi 에디터를 설치해야 한다고 언급했다.)

 

먼저 Devel 카테고리에 [+]기호를 눌러 펼친 다음 아래에 살펴보면 Package이름으로 gcc-core: C Compiler와 gcc-g++: C++ Compiler가 있다. 해당 패키지에 Skip으로 된 문자를 클릭해서 3.4.4-3과 같이 버전이 표시되도록 한다. Skip은 설치에서 제외한다는 의미이다.

 

 

다음으로 Perl 카테고리의 Default를 클릭하면 Install로 변한다. 이렇게 해서 하위에 있는 Perl관련 패키지들이 전부 설치될 것이다.

 

 

zip 패키지를 설치하기 위해 Archive 카테고리 [+] 버튼을 눌러 펼친 다음 zip: A file compression and packaging utility compatible with PKZIP 을 찾아 Skip을 클릭하여 2.32-2로 바꾸도록 한다.

 

 

vi 에디터를 사용하기 위해 Editors 카테고리의 [+] 버튼을 눌러 아래에 vim: Vi IMproved를 찾은 뒤 Skip을 눌러 7.2-3로 바꿔 설치할 수 있도록 한다.

 

 

 

다음 버튼을 눌러 설치를 한다.

 

 

Cygwin 설치가 완료되었다.

 

 

설치가 완료되면 바탕화면에 아래와 같은 실행 아이콘이 생길 것이다.

 

 

설치는 C드라이브에 설치되었다. 설치된 cygwin 내부를 살펴보면 디렉토리 구조가 Linux와 비슷하다는 점이 흥미롭다.

 

 

Cygwin에 다른 패키지를 설치하고 싶을 때는 Cygwin 홈페이지로 가서 설치 실행파일 다운로드 받아 앞서 했던 것을 반복하면 되겠다. 물론 이전 설치한 것은 굳이 재설치할 필요 없다.

 

Cygwin의 자세한 사용법은 Java를 설치 후 해보도록 하자.

 

3. Java 설치

Windows 환경에서 Alchemy는 Java 환경이 구축되어 있어야 한다. 참고로 MAX OS X의 경우에는 Xcode 2.4+ 버전이 필요하다.

http://java.sun.com/javase/downloads/index.jsp

상단 페이지로 가서 Java Standard Edition Develop Kit(JDK)를 Windows용으로 다운로드 받아 설치한다.

 

설치 후 환경 변수를 설정해야 한다. 환경 변수를 설정하는 이유는 운영체제에 Java가 설치된 경로를 등록함으로써 DB나 다른 프로그램과의 연동에 대한 편의성을 제공할 수 있기 때문이다. Linux나 Max OC X에도 이러한 환경 변수 설정이 있으니 인터넷 검색을 통해 찾아보길 바란다.

 

내컴퓨터->(마우스 오른쪽 클릭)속성->고급으로 들어간 다음 환경변수 버튼을 클릭한다.

 

 

아래처럼 환경변수 설정 창이 나온다.

 

 

위 화면에 사용자 변수와 시스템 변수가 있다. 사용자 변수는 로그인한 사람에게만 적용하기 위한 것익 시스템 변수는 전체 사용자에게 적용하는 변수라고 생각하면 되겠다. 시스템 변수에서 새로 만들기 버튼을 누른 다음 다음처럼 변수값을 등록한다.

  • 변수 이름 :  JAVA_HOME
  • 변수 값 :   JDK가 설치된 경로를 넣으면 되겠다.

 

다음으로 시스템 변수 중에 Path를 선택하여 편집 버튼을 누르면 다음과 같은 창이 뜬다.  아래처럼 변수 값으로 %JAVA_HOME%\bin; 를 앞부분에 추가한다.

 

CLASSPATH 시스템 변수를 등록한다. 시스템 변수에서 새로 만들기 버튼을 눌러 아래와 같이 입력한다.

  • 변수 이름 : CLASSPATH
  • 변수 값 : %classpath%;

 

모두 입력했으면 확인 버튼을 눌러 변수 설정을 종료한다.

 

 

위에서 설정한 환경 변수가 제대로 설정되어 있는지 확인하기 위해 콘솔창을 띄운다. 시작->실행->cmd입력->확인하면 되겠다.

 

 

cmd 창에서 아래와 같이 “java –version”, “javac –version”을 입력했을 때 에러 없이 잘 보여준다면 제대로 환경변수가 설정된 것이다.

 

이로써 Java 설치를 마친다.

4. Java를 설치한 뒤에 Cygwin 터미널 실행.

Cygwin은 Linux환경과 비슷하다. 윈도우에서 돌아가는 Linux라고 생각하고 접근하면 되겠다. 아래처럼 “ls –al”, “pwd”등으로 자신의 계정에 있는 파일 정보를 볼 수 있다.

 

각각의 드라이브에 접근 할 수 있다. 가령, C드라이브에 접근하기 위해 “cd /cygdrive/c”을 입력하면 된다.

이처럼 Cygwin을 이용하면 Window에서 Linux와 비슷한 환경이 제공된다. 그러므로 Linux 환경을 접해본 사람이라면 Cygwin을 다루는데 그리 어렵지 않을 것이다.

 

5. Flex SDK 설치

Flex SDK를 다운로드 받아서 C 드라이브에 설치하자. Flex SDK 3.2 이상 버전이어야 한다. Flex Builder 3를 이미 설치한 사람은 C:\Program Files\Adobe\Flex Builder 3\sdks 안에 SDK 3.2 가 있을 수 있겠다. 편의성을 위해 아래처럼 C 드라이브안에 flexsdk 이름으로 만들어 두자.  이 폴더는 Cygwin에서 접근하면 “/cygdrive/c/flexsdk”이 된다. 자유롭게 다른 경로에 설치할 수 있으므로 앞으로 /cygdrive/c/flexsdk를  $FLEX_HOME 별칭을 사용하겠다.

 

Cygwin에서는 Linux 명령어인 ls로 C 드라이브 내용을 볼 수 있다. 가령 “ls /cygdrive/c/flexsdk/”를 입력하면 금방 C드라이브에 설치한 flexsdk 내용을 확인할 수 있겠다.

 

 

6. Alchemy 다운로드 및 설치

http://labs.adobe.com/downloads/alchemy.html

위 링크로 들어가서 Windows Alchemy Toolkit을 다운로드 받는다. 다운로드 받은 다음 압축을 푼다. 본인은 편의성을 위해 cygwin 설치폴더 내에 alchemy폴더를 만들어 다운로드 받은 Alchemy Toolkit 파일을 복사했다. C:\cygwin\alchemy 를 Cygwin에서는 /alchemy/로 접근할 수 있으므로 앞으로 /alchemy/를 $ALCHEMY_HOME 으로 별칭을 두어 사용하겠다.

 

7. Alchemy 환경설정

Alchemy 개발환경 설정은 비교적 간단하다. 몇가지 환경설정만 하면 Cygwin에서 Alchemy 개발환경이 만들어진다. 아래 과정을 진행하면서 주의할 사항은 왜 그렇게 하는가를 따져보면서 읽어보는게 좋겠다. 왜냐하면 이러한 과정은 앞으로 Alchemy 개발환경 적응에 도움이 되기 때문이다.

 

$ALCHEMY_HOME으로 찾아간다. cd 명령어로 이동하면 되겠다. c:\cygwin\alchemy에 설치했었으므로 “cd /alchemy”로 이동하면 된다. 만약 d 드라이브에 설치했다면 “cd \cygdrive\d\alchemy”로 이동할 수 있다.

 

$ALCHEMY_HOME 안에는 config 파일이 있다. ls로 확인해보길 바란다.
아래와 같이 config를 실행하면 된다.

$ cd /alchemy
$ ./config

실행하면 몇 가지 에러 메시지를 보이면서 종료한다.

 

걱정하지 말자. config를 실행한 이유는 Alchemy설치를 위한 파일을 생성하기 위함이다. 아래처럼 ls로 $ALCHEMY_HOME 폴더 안에 파일목록을 살펴보면 전에 없었던 alchemy-setup 파일이 생성된 것을 확인할 수 있다.

 

Cygwin 설치할 때 vi 에디터를 설치했다. 그러므로 다음 명령어로 alchemy-setup 파일을 수정할 수 있다. 혹시 vi 에디터를 설치하지 않았다면 다른 에디터를 이용하길 바란다. 단, 외부에서 수정시 수정할 파일은 chmod를 이용해 권한 설정을 해주어야 편집이 가능하다.

$ cd /alchemy
$ vi alchemy-setup

아래와 같이 alchemy-setup내에 Flex SDK에 포함된 ADL(AIR Debug Launcher)을 추가하도록 한다.

export FLEX_HOME=/cygdrive/c/flexsdk
export ADL=$FLEX_HOME/bin/adl.exe

 

alchemy-setup 파일 설정을 마쳤다. 이번에는 자신의 계정으로 돌아가서 숨김 파일인 .bashrc 파일을 연다. 본인의 경우 /home/jidolstar 가 개인 계정이므로 해당 계정으로 이동한 다음 vi 에디터로 .bashrc를 열도록 하겠다.

$ cd /home/jidolstar
$ ls –al
$vi .bashrc

.bashrc 맨 아래에 아래와 같이 입력한다. 단 alchemy 설치 경로는 c:\cygwin\alchemy 일 때를 가정한다.

source /alchemy/alchemy-setup
PATH=$ALCHEMY_HOME/achacks:$FLEX_HOME/bin:$PATH
export PATH

 

환경설정 PATH로 $ALCHEMY_HOME/achacks 와 $FLEX_HOME/bin을 등록했다.

$ALCHEMY_HOME/achacks은 Alchemy에 사용하는 gcc 또는 g++ 툴이 존재한다. 즉 Cygwin에서 제공하는 gcc대신 Alchemy용 전용 gcc를 활용하기 위한 환경설정인 것이다. .bashrc에 설정을 했으므로 본인 계정으로 접속한 경우에는 항상 Alchemy 전용 gcc를 이용하게 되는 것이다. 만약 때에 따라서 Cygwin에서 제공하는 gcc를 이용할 경우가 있다면 $ALCHEMY_HOME/achacks는 빼고 alc-on와 alc-off 명령을 이용해 필요할 때마다 Alchemy 전용 gcc와 Cygwin gcc를 번갈아 가면서 이용할 수 있다.

 

alc-on, alc-off를 좀더 이해하기 위해 설명을 덧붙이겠다. 앞서 설정한 alchemy-setup을 유심히 봤다면 그 안에 아래와 같은 코드를 볼 수 있었을 것이다.

alias alc-home=’cd $ALCHEMY_HOME’
alias alc-on=’export PRE_ALCHEMY_PATH=$PATH; export PATH=$ALCHEMY_HOME/achacks:$PATH’
alias alc-off=’export PATH=$PRE_ALCHEMY_PATH’

별칭(Alias)으로 alc-home, alc-on, alc-off를 지정한 것을 볼 수 있다. cygwin 콘솔창 안에서 이들 명령을 시행하면 해당 명령시 실행한다. alc-home의 경우 Alchemy가 설치된 홈으로 이동한다. alc-on은 Alchemy 전용 gcc를 이용하도록 경로를 수정해준다. 그리고 alc-off는 Cygwin gcc를 이용하도록 한다. 단 alc-on 전에 alc-off를 사용해버리면 모든 경로정보가 사라지게 된다. 이런 경우에는 Cygwin을 종료했다가 다시 실행하면 되겠다.

 

$FLEX_HOME/bin은 /cygdrive/flexsdk/bin에 있는 flex 컴파일러인 mxmlc.exe를 어느 경로에서든 실행할 수 있게 하기 위함이다. 이 환경설정은 사용하는 것이 좋겠다. 그렇지 않으면 Flex 컴파일을 위해 mxmlc.exe가 있는 경로를 찾아가야 할 것이다.

Alchemy 설정을 모두 마쳤다. 설정이 적용될 수 있도록 Cygwin을 종료했다가 다시 실행하면 설정이 적용된다.

 

Alchemy 예제 테스트

Alchemy를 테스트 해보기 위해 Alchemy 설치시 이미 포함되어 있는 예제 프로그램을 이용해 보겠다. C, ActionScript 코드에 대한 자세한 설명은 배제하고 Alchemy를 이용하는 방법만 소개한다. 필요하다면 예제 프로그램을 스스로 분석하는 것이 도움이 될 것이다. 본 예제는 Adobe 이벤젤리스트인 마이크 챔버스(Mike Chambers)에 의해 작성되었다.

 

1.  C언어로 작성된 코드를 Alchemy를 이용해 SWC로 컴파일하기

먼저 $ALCHEMY_HOME/samples/stringecho 로 이동한 뒤 gcc를 이용해 해당 코드를 SWC 파일로 컴파일 해보자.

 

$ALCHEMY_HOME/samples/stringecho 안에는 아래 코드와 같은 stringecho.c가 존재한다. 이 코드의 역할은 매우 단순하다. 코드를 대충봐도 알겠지만 결국 사용할 함수는 echo()이다. AS3_로 붙은 함수들은 모두 Alchemy C 라이브러리에서 제공하는 함수들임을 예상할 수 있겠다.

//Simple String Echo example
//mike chambers
//mchamber@adobe.com

#include <stdlib.h>
#include <stdio.h>

//Header file for AS3 interop APIs
//this is linked in by the compiler (when using flaccon)
#include <AS3.h>

//Method exposed to ActionScript
//Takes a String and echos it
static AS3_Val echo(void* self, AS3_Val args)
{
    //initialize string to null
    char* val = NULL;
    //parse the arguments. Expect 1.
    //pass in val to hold the first argument, which
    //should be a string
    AS3_ArrayValue( args, "StrType", &val );
    //if no argument is specified
    if(val == NULL)
    {
        char* nullString = "null";
        //return the string "null"
        return AS3_String(nullString);
    }
    //otherwise, return the string that was passed in
    return AS3_String(val);
}

//entry point for code
int main()
{
    //define the methods exposed to ActionScript
    //typed as an ActionScript Function instance
    AS3_Val echoMethod = AS3_Function( NULL, echo );

// construct an object that holds references to the functions
    AS3_Val result = AS3_Object( "echo: AS3ValType", echoMethod );

// Release
    AS3_Release( echoMethod );

// notify that we initialized — THIS DOES NOT RETURN!
    AS3_LibInit( result );

// should never get here!
    return 0;
}

위 코드를 SWC로 컴파일하기 위해 다음 명령어를 실행한다. –O3 옵션은 최적화를 위한 것이고 -Wall은 모든 경고 메시지를 보여주기 위함이다. -swc를 붙였으므로 SWC로 만들어질 것이다. –o는 출력파일이름을 지정한다. -O3는 꼭 붙여주는 습관을 가지는게 좋다. 그렇지 않으면 수행속도가 떨어질 수 있다. 최소한 –O2 이상 주도록 하자.

$ cd /alchemy/samples/stringecho
$ gcc stringecho.c –O3 –Wall –swc –o stringecho.swc

위처럼 나오면 컴파일이 완료된 것이고 결과적으로 stringecho.swc가 생성된다. Alchemy에서 컴파일은 다음과 같은 과정을 거친다. 참고만 하도록 한다.

  1. C언어를 LLVM(Low Level Vitual Machine) Byte Code로 변환한다. (확장자 .bc)
  2. LLVM 코드를 ActionScript 3.0 코드로 변환한다.(확장자 .as)
  3. ActionScript 코드를 ActionScript Byte Code로 변환한다.(확장자 .abc)
  4. 마지막으로 swc을 만든다. (확장자 .swc)

2. 컴파일된 SWC를  ActionScript 3.0 프로젝트에 적용해보기

$ALCHEMY_HOME/samples/stringecho 안에 컴파일 결과물인 stringecho.swc를 이용해 간단한 ActionSciript에 포함하여 컴파일 해보자.

 

$ALCHEMY_HOME/samples/stringecho/as3 안에 가보면 EchoTest.as 가 있다. 이 ActionScript 파일은 방금 만든 stringecho.swc에 정의된 echo()함수를 사용한다.

package
{
    import flash.display.Sprite;
    import cmodule.stringecho.CLibInit;
    public class EchoTest extends Sprite
    {
        public function EchoTest()
        {
            var loader:CLibInit = new CLibInit;
            var lib:Object = loader.init();
            throw new Error(lib.echo("foo"));
        }
    }
}

아래처럼 실행하면 되겠다. 참고로 mxmlc.exe를 바로 실행할 수 있는 이유는 환경설정을 위해 개인 계정에 존재하는 .bashrc 파일에 환경설정시 경로로 $FLEX_HOME/bin 를 설정했기 때문이다.

$ cd /alchemy/samples/stringecho/as3
$ mxmlc.exe -library-path+=../stringecho.swc –target-player=10.0.0 EchoTest.as

–target-player=10.0.0 옵션을 넣은 이유는 Alchemy 결과물은 반드시 Flash Player 10 이상 버전에서 동작할 수 있기 때문이다. 컴파일이 성공하면 아래와 같이 EchoTest.swf가 만들어진다.

 

 

만들어진 EchoTest.swf를 실행해보면 throw new Error(lib.echo("foo")); 부분 때문에 에러창이 나온다. 아래 창과 같이 “foo”가 나왔다면 제대로 실행된 것이다. 다른 에러가 나왔다면 Flash Player 10인지 확인해보자.

 

3. 컴파일된 SWC를 Flex 프로젝트에 적용해보기

이번에는 stringecho.swc를 Flex Builder 3 Profession에서 Flex 프로젝트를 만들어 적용해보도록 하겠다.

 

아래와 같이 Flex Builder 3 Professional을 실행한 다음 stringecho_test라는 이름으로 Flex 프로젝트를 만들고 libs 폴더에 stringecho.swc를 복사한다.

그런 다음 아래와 같이 MXML를 작성한다.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    creationComplete="init();">
    <mx:Script>
        <![CDATA[
            import cmodule.stringecho.CLibInit;
            private function init():void
            {
                var loader:CLibInit = new CLibInit;
                var lib:Object = loader.init();
                throw new Error(lib.echo("foo"));
            }
        ]]>
    </mx:Script>
</mx:Application>

이때 아래 2가지 조건이 맞아야 한다.

첫째. Flex SDK의 버전은 3.2 이상이어야 한다.
둘째. Flash Player version은 10.0.0 이상이어야 한다.

이 조건들이 맞지 않으면 에러를 낼 것이다. 에러가 발생한다면 아래와 같이 메뉴에서 Project->Properties로 들어가 창이 뜨면 좌측메뉴에 Flex Compiler를 선택한 뒤 아래처럼 설정한다.

  • Flex SDK version을 Flex SDK 3.2로 선택한다.
  • Compiler Options에 –-target-player=10.0.0 을 삽입한다.
  • HTML wrapper의 Require Flash Player version을 10.0.0으로 맞춘다.

만약 Flex SDK 3.2가 없다면 다운로드 받아 C:\Program Files\Adobe\Flex Builder 3\sdks 내부에 복사해두고 메뉴에서 Windows->Preferances로 들어가 아래와 같은 새창이 뜨면 좌측 카테고리에서 Flex->Installed Flex SDKs를 선택한다. 우측에 Add버튼을 눌러 sdk경로를 맞춰준 다음 Flex 3.2를 기본으로 선택해준다.

 

 

위 환경 설정을 모두 맞추면 에러가 발생하지 않을 것이다.

실행후 아래와 같이 Error: foo 메시지가 나왔다면 프로그램이 제대로 실행된 것이다.

 

 

Alchemy Tools에 대해

Alchemy SDK에는 많은 명령 실행을 위한 도구가 포함되어 있다. 여기에는 C/C++파일을 Flash와 호환하기 위한 디버깅 도구 등도 포함한다. 더욱 자세한 내용은 Developing with Alchemy:Tools를 참고하도록 한다.

 

1. 별칭(Alias)

$ALCHEMY_HOME 디렉토리 하위에 존재하는 alchemy-setup 파일을 열어 아래 부분에 살펴보면 다음 설정이 존재한다. 이 부분은 별칭(alias)을 설정하는 것으로 alc-home, alc-on, alc-off 을 등록하도록 한다.

alias alc-home=’cd $ALCHEMY_HOME’
alias alc-on=’export PRE_ALCHEMY_PATH=$PATH; export PATH=$ALCHEMY_HOME/achacks:$PATH’
alias alc-off=’export PATH=$PRE_ALCHEMY_PATH’

alc-home : Alchemy를 설치한 디렉토리로 이동한다. 본 문서에 설치하는 방법대로 설치하면 “cd /alchemy” 한 것과 같은 동작이다.

 

alc-on/alc-off : Alchemy 동작 On/Off 기능이다. alc-on을 실행하면 $ALCHEMY_HOME/achacks 내에 있는 gcc 빌드 관련 도구를 이용하게 되며 alc-off를 실행하면 원래 gcc를 이용하게 한다.

 

아래는 alc-home, alc-on, alc-off 명령어를 이용할 때 상태를 보여준다. alc-home 명령어 실행시 Alchemy 설치 디렉토리로 이동한 것을 확인하자. 그리고 alc-on/alc-off 명령어를 실행시에 어떤 gcc를 이용하게 되는지 확인한다.

 

 

하지만 이 문서대로 착실하게 따라온 사람은 아래처럼 나올 것이다. 그 이유는 .bashrc 파일 설정과 관련 있다. “PATH=$ALCHEMY_HOME/achacks:$FLEX_HOME/bin” 는 강제적으로 $ALCHEMY_HOME/achacks로 경로로 잡아주기 때문에 alc-on, alc-off 명령어가 적용되지 않는 것이다. 만약 alc-on, alc-off 명령을 적용하길 원한다면 $ALCHEMY_HOME/achacks을 빼고 Cygwin을 다시 실행하면 되겠다.

 

 

환경변수에 경로가 잘 설정되었는지 확인하기 위해 echo $PATH를 아래와 같이 이용해봐도 되겠다.

2. $ALCHEMY_HOME/achacks 에 포함된 도구

이 경로에 포함된 도구는 앞서 설명했듯이 gcc 빌드 관련 도구들이다. alc-on 명령어를 실행할 때 사용할 수 있다.

gcc : 매우 중요한 명령으로 다음과 같은 기능을 수행한다.

  • 라이브러리 경로를 /usr/local을 $ALCHEMY_HOME/usr/local 지정한다.
  • LLVM 바이트코드를 생성하는 llvm-gcc와 llvm-g++를 호출한다.
  • ActionScript를 생성하는 적절한 매개변수를 가지는 llvm 도구를 호출한다.
  • ABC(ActionScript Byte Code) 파일을 생성하기 위해 Alchemy ActionScript 컴파일러를 호출한다.
  • “-swc” 옵션이 있는가 없는가에 따라 ABC파일을 SWF 또는 SWC로 패키지화 한다.
    -swc는 매우 중요한 옵션으로 SWC로 패키지화 한다는 것을 의미한다. 기본값은 실행가능한 SWF를 생성한다. 결과가 exe(실행파일)이지만 실행파일 헤더에 swfbridge와 연결하기 위한 shabang이 있다.

    모든 다른 옵션은 llvm-gcc로 전달된다.

    g++ : 전달된 인수를 그대로 gcc에 전달한다.
    uname : 운영체제가 FreeBSD 6.2에 있는 것처럼 작동한다.
    pkg.pl, tmpl.pl, swctmpl.swf : SWF와 SWC를 생성을 도와주는 역할을 한다.
    기타 다른 스크립트 : 기본적으로 $ALCHEMY_HOME에 대한 경로를 /usr에 맵핑하는 스크립트이다.

 

3. $ALCHEMY_HOME/bin 에 포함된 도구

Alchemy 설정을 수행하면 이 경로가 포함이 된다(alchemy-setup과 .bashrc 파일 참고). 사용중인 다른 도구에 간섭받지 않도록 설정되어 있다.

 

alc-util : Alchemy에 영향을 미치는 환경 변수들을 보여준다. 아래는 실행한 모습이다.

 

swfbridge : Alchemy에서 생성된 실행 파일을 실행하는데 사용한다. 기본 목적은 ADL(AIR Debug Launcher)를 사용하여 SWF를 시작할 수 있도록 임시 AIR 애플리케이션을 만드는 것이다. 두 번째 목적은 로컬 소켓 연결을 위해 특정 API를 호출하는 것이지만 향후 배포에서 제외될 가능성이 있다.

 

Alchemy 샘플 디렉토리($ALCHEMY_HOME/samples/HelloFlash)로 가면 아래와 같이 swfbridge를 간단하게 테스트할 수 있는 코드(HelloFlash.c)가 있다. 일반 c코드와 동일하다.

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    printf("Hello Flash!\n");
}

이 코드를 실행하기 위해 다음과 같이 실행한다.

cd $ALCHEMY_HOME/samples/HelloFlash
gcc HelloFlash.c
./a.exe

 

이처럼 Alchemy를 통해 생성된 exe파일은 swfbridge에서 실행된다. swfbridge는 exe파일의 head에 있는 shabang에 의해 실행된다.

 

gluegen : 적은 노력으로 C/C++ 라이브러리를 ActionScript로 만들기 위한 컴파일 도구이다. 확장자가 .gg를 가지며 이를 응용한 예제는 2부에서 소개하도록 하겠다.

zpipe : 이 도구는 Zlib 압축 알고리즘을 사용하여 표준 입력(STDIN)을 통해 압축 또는 압축 풀기를 하여 표준 출력(STDOUT)으로 내보내는 역할은 한다. 2개의 옵션이 있다. zpipe -h는 도움말을 보여주고 zpipe -d는 압축풀기(decompress)를 하도록 한다.

예제 : zpipe <uncompressed> compressed

zpipe.pl : zpipe와 같은 역할을 하지만 Perl과 Compress::Raw::Zlib에서 구현된다. 사용법은 동일하다.

ExplSWF.pl : 전달된 SWF를 확장한다. SWF의 태그가 현재 디렉토리에 파일로 내보내진다.

예제 : ExplSWF.pl src.swf

ImplSWF.pl : 전달된 SWF를 압축한다. 현재 디렉토리 내에 태그 파일이 SWF에 포함된다.

예제 : ImplSWF.pl dst.swf

GetABC2.pl : SWF에서 ABC를 추출한다.

예제 : GetABC2.pl src.swf dst.abc

PutABC2.pl :원본이 되는 SWF를 주어진 ABC로 대체된 새로운 SWF를 만든다.

예제 : PutABC2.pl src.swf dst.swf newabc.abc

PutBIN.pl : 원본이 되는 SWF를 주어진 바이너리 부분으로 대체된 새로운 SWF를 만든다.

예제 : PutBIN.pl src.swf dst.swf data.bin symname

ShrSWF.pl : SWF 파일을 압축한다.

예제 : ShrSWF.pl src.swf dst.swf

V10.pl : SWF 버전 태그를 10으로 한다.

예제 : V10.pl src.swf dst.swf

 

 

1부를 정리하며

 

지금까지 Adobe Alchemy 개발환경 구축과 함께 간단한 예제 및 Alchemy 도구들에 대해 소개했다. 2부에서는 Adobe Alchemy 공식페이지에서 제공하는 라이브러리를 활용하고 간단한 응용을 통해 Alchemy에 대해 심층적으로 학습해보는 시간을 가지도록 하겠다.

 

관련 페이지

추가사항 1 : Alchemy 속도 테스트(잘못된 예제)

 

이 글을 통해 많은 분들이 Alchemy 수행속도 및 내부구조에 대해서 관심을 보였습니다. 기대에 부응코자 Alchemy가 Flash 애플리케이션에 어느 정도 속도향상을 줄 수 있는가 알아보기 위해 간단한 테스트를 했습니다.

 

    추가사항 2 : Alchemy 속도 테스트 및 깊이 이해하기

    제가 GCC에 대해서 별로 접근해본 적이 없어서 위 Alchemy 속도 테스트에서 잘못된 예제를 보여드렸네요. 최적화에 대한 잘못된 지식으로 인해 잘못된 테스트로 글을 적은 점 사과하며 아래글도 함께 보시면 이해에 도움이 되겠습니다.
  • Adobe Alchemy 수행속도 테스트와 깊이 이해하기

 

 

WISIA에서 “플래시 디자이너,개발자가 꼭 봐야할 미국 플래시 웹사이트”에 대한 내용이 있어서 보고 있다가 개인적으로 독특하고 눈에 들어오는 UI를 가지는 웹사이트가 있어서 소개한다.

 

일명 Communicator World(http://www.communicatorworld.com/). 접속하는데 로딩 압박이 있긴 하다. 조금만 참고 기다리면 깔끔하고 이쁜 Flash로 만들어진 웹페이지를 볼 수 있다. 메인화면이 3D로 하나의 별과 같이 만들어진 것을 볼 수 있다. 깔끔하고 이쁘며 각 케릭터가 돌아다니는 것을 보면 귀엽기까지 하다.

 

아래 이미지들은 Communicator World에 있는 별처럼 생긴 UI만 캡쳐한 화면이다.

 

개인적으로 다가온 이 사이트에 매력은 내용보다는 UI이다. 왜냐하면 본인은 스타플(http://starpl.com) 웹사이트를 만드는 개발자중 한 명으로써 스타플의 중요 매타포인 별(star)의 UI에 지극히 관심이 많기 때문이다. 스타플은 실제로 관측된 별을 가입과 동시에 나눠주고 있다. 아래는 스타플에서 보여주고 있는 나의 별이다.

 

스타플에서 별은 별 소유자가 직접 꾸밀 수 있다. 자신의 활동량에 따라서 증가하는 별가루라는 가상화폐를 이용해 별스킨, 별을 꾸미는 아이템, 그리고 위젯을 구입하여 직접 별을 꾸미는 것이다. 굉장히 독특하고 깔끔한 UI를 가지지만 Communicator World에서 보여주고 있는 3D느낌의 별과 비교할 때 꾸미는 것 외에 뭔가 재미요소를 던져주지는 못하는 느낌이 든다.

 

스타플은 놀이동산이다.


놀이동산에는 다양한 놀이기구들이 있다. 놀이동산은 만들어졌지만 아직 놀이기구들이 매우 부족한 것이 스타플의 현실이다. 스타플도 나름대로 재미요소를 던져주기 위해 노력이 많이 들어갔다.

 

자신의 기록을 담아가는 타임라인

 

 

내 별을 언제나 꾸밀 수 있는 별꾸미기

 

 

내 별친구들의 소식을 실시간으로 알려주는 알림이 서비스.

 

 

스타플 안에서 발행한 글들에 대해 보여주는 메타센터

 

 

블로그의 RSS 피드를 이용해 내 별과 연결하여 내 블로그에 언제나 접속할 수 있도록 한 외부블로그 유입서비스.

 

 

 

실제로 관측된 별을 보여주는 별지도(실제 지도 서비스 처럼 이동,확대,축소가 된다.)

 

 

내 별을 꾸미기 위해 스타플 가상 화폐인 별가루(활동량에 따라 증가)를 이용해 마음껏 살 수 있는 별스킨, 아이템, 위젯들을 판매하는 별마트.

전체적으로 스타플 놀이동산은 잘 만들어 놨지만 놀이기구가 부족해 보인다. 어떻게 하면 스타플 놀이동산에 사용자들에게 재미를 줄 수 있는 놀이기구를 만들어낼 수 있을까?

 

앞서 소개한 Communicator World의 별처럼 보이는 UI는 뭔가 답을 주는 듯했다. 이와 같은 UI에 뭔가 놀 수 있는 것을 추가해준다면 좋을 듯 싶다. 하지만 생각은 생각일 뿐, 서비스를 하는 입장에서 접속속도 및 포퍼먼스를 생각하지 않은 수 없다. 스타플의 별지도, 별, 별꾸미기 같은 컨텐츠는 Flex라는 Flash결과물을 만들어낼 수 있는 프로그램으로 만들어졌다. Flash의 랜더링 속도는 3D를 감당하기에는 아직 부족하다. 그래서 섣불리 도입할 수는 없다. 하지만 우리에게 생각의 폭을 넓혀주는 사이트인 것은 분명하다.

 

스타플 놀이동산에 어떤 놀이기구가 만들어질지 지켜봐 주길 바란다.

 

제가 직접 참여해서 만든 사이트 홍보입니다. ^^

Flex/Flash로 개발을 담당했고요~~

스타플에서 홍보이벤트 중입니다.

1등 문화상품권(10만원)+스타플 카드지갑 입니다.

여러분도 도전해보심이 어떨까요?


Starpl. 밤하늘의 별을 당신에게 드립니다.

밤하늘의 별을 주는 재미있는 서비스가 있어 소개합니다.

스타플은 실제 별 관측 데이터를 이용해서, 정말 존재하는 별 중에 하나를 나에게 준답니다.
나의 별에다가 나의 이야기를 담고, 우주를 여행하며 사람들도 만나는 흥미로운 서비스에요.

별찾기 성향테스트

나만의 별을 찾아라!

회원가입을 누르면, 성향 테스트를 거쳐 나만의 별을 받게 됩니다.
그림으로 푸는 성향테스트가 독특하고 재미있어요.

예시 별 1. 예시 별1의 타임라인

나를 담는 별

별을 방문한 모습입니다. 별 위에 보이는 바가 타임라인이라고 하는 건데,
타임라인에 추억과 일상, 생각 들을 기록할 수 있습니다. 글을 쓸 때 시간을 설정하거나, 첨부사진의
촬영시간을 적용하면 그 때 그 시간의 타임라인에 글이 쓰여져 내 인생의 히스토리를 만들 수 있어요.
블로그를 가지고 있다면, 블로그를 타임라인에 등록하여 새 글이 자동으로 타임라인에 담기게 할 수도 있어요.

예시 별 2. 예시 별3.

나의 취향대로 꾸미는 나만의 별

여러 별들의 모습입니다. 스타플에는 나만의 별을 꾸밀 수 있도록 다양하고 예쁜 아이템과 위젯들이 많습니다.
아이템은 별가루로 구입할 수 있는데, 별가루는 처음에 가입하면 왕창 주는데,
다 써 버려도 스타플 사용하다보면 저절로 계속 쌓이더라구요. ^^

스타플 별지도

다른 별들을 만날 수 있는 공간, 별지도

별지도에 가보면 많은 별들이 있습니다. 저 별들에도 다 사람들이 살고 있다고 생각하니 신기하네요.
별지도 위의 상단메뉴를 클릭하면
다른 별들이 남긴 기록들을 볼 수가 있는데요, 말풍선을 클릭하니 그 별로 방문이 되는군요.

똑똑한 소식 배달부 알림이

별지도 왼쪽에 알림이를 통해 나의 별친구들의 새 기록과 댓글, 생일 등의 소식들을
바로 바로 알려줘서 알림이에서 손을 땔 수가 없더라구요.
또, 관심 가는 별이 있으면 관심별로 등록해 알림이로 쉽게 받아 볼 수 도 있답니다.

너무 너무 이쁘고 개성 넘치는 스타플!
나만의 별을 받고 싶으신 분들, 나의 이야기를 담을 특별한 공간을 찾으신 분들께
꼭 추천해드리고 싶은 서비스입니다.

별증서

나의 별임을 인증하는 별증서 이벤트도 진행 중이니, 한번 방문해 보세요.

스타플 방문하기

+ Recent posts