as3 data container
변수란 무엇인가?
콜렉션에 들어가기 전에 변수가 무엇인지 생각해봅시다. 구조형 언어가 주류인 현재, 우리는 폰노이만 형식의 프로그램을 짜고 있습니다. 초 간단히 이를 정리하면 여러분이 하실 수 있는 일은 기껏해야 3가지 밖에 없다는 겁니다.
- 메모리에 있는 값을 가져온다.
- 연산을 통해 값을 변경한다.
- 메모리에 값을 쓴다.
이 세 가지 행위로부터 수많은 테크닉과 형식이 발전된 게 오늘날 소프트웨어 방법론입니다. 쨌든 이것을 기반으로 생각해보면 개발 시 가장 중요하게 생각해야 하는 부분은 메모리 컨트롤 이라는 점입니다.
메모리 컨트롤을 하는 기술은 크게 두 가지로 나눌 수 있습니다.
- 메모리의 위치를 조회하는 메모리주소 컨트롤
- 메모리의 값을 조회하는 메모리 값 컨트롤
메모리란 말하자면 거대한 플립플롭 공간입니다. 값을 기록하고 싶으면 먼저 메모리 공간에서의 위치를 확보해야 합니다. 메모리의 특정 위치를 메모리 세그먼트라 하는데 프로그래밍을 통해 코딩한 메모리 세그먼트 접근은 실제로 물리메모리 층에 명령을 내리게 되어 연산을 통해 해당 메모리 위치에 접근하게 됩니다.
하지만 이러한 과정은 너무나 귀찮고 반복적인 작업이기 때문에 c 이상의 고급언어는 변수라는 개념을 도입했습니다.
변수란 특정 메모리 위치의 별명입니다. 대부분의 고급언어 컴파일러는 내부적으로 변수명을 선언하면 특정 메모리 위치와 매핑하여 저장하는 메모리 매핑테이블을 생성하고 이후 컴파일을 하면서 그 변수명을 매핑된 메모리 주소로 번역하게 됩니다. 하지만 생각해보면 컴파일 시에도 여전히 진짜 메모리는 할당되어있지 않습니다. 진짜 메모리를 OS로부터 할당받는 시점은 실행시점이죠. 그럼 컴파일러는 대체 어떻게 매핑테이블을 처리한걸까요?
비밀은 실행 시점에 확보될 메모리가 있다고 가정하고 미리 예상하여 별명을 지어둔 상태로 컴파일을 하는 것입니다. 따라서 컴파일러가 생성한 코드는 실제로는 실행시점에서 미리 OS에게 컴파일 시 확보하기로 했던 만큼의 메모리를 먼저 할당 받아서 그 메모리를 예상했던 메모리 별명에 할당해주고 난 후 프로그램이 구동하도록 만들어 줍니다.
지금까지 설명한 변수라는 프로그래밍적인 개념은 vm언어든 c든 동일하게 적용되고 있습니다. 단지 차이점이라면 네이티브 언어는 OS에게 메모리를 할당받아 사용하기 때문에 OS의 룰에 맞게 초기 메모리 매핑을 진행하는데 비해 vm은 vm의 규칙에 맞게 진행한다는 정도죠. 최신 vm이나 OS에는 메모리를 물리적인 하드나 네트웍자원까지 포함하여 제공해주는 가상메모리할당 기능이 들어있곤 합니다. 개발자는 그저 변수를 선언하기만 하면 복잡한 일이 하부에서 벌어지는거죠.
지금까지 설명한 변수의 개념을 정확히 정리하면, 실행시점에 먼저 확보할 것으로 예상하고 가상으로 잡아둔 메모리주소를 가르치는 별명 이라고 할 수 있습니다.
동적 변수와 정적 변수
사실 위에 설명한 건 정적 변수라는 겁니다. 정적 변수도 엄밀히 따지면 런타임에 확보되는 거라 동적 변수 인거지만 특수하게 컴파일러가 생성한 코드가 반드시 다른 실행에 앞서 확보된다는 점이 다른 동적 변수와는 다릅니다. 정적 변수의 개념은 위에서 충분히 설명했으니 이제 정적 변수의 장단점을 생각해봅시다.
먼저 장점이라면 컴파일러가 확실하게 알고 있는 변수라는 점이겠죠. 따라서 컴파일 타임에도 엄격하게 검사를 할 수 있고, 런타임에도 최초 OS로 부터 메모리를 받을 때 실패하면 아예 실행이 되지 않도록 보호받을 수 있습니다. 한마디로 안정캡이죠.
단점은 뭘까요? 이름 속에 있습니다. 정적 이란건 개발자를 위해 풀어쓰자면 미리 다 알고 있다 라는 것입니다. 미리 알 수 없는 건 정적 변수로 사용할 수 없습니다. 대표적으로 학원을 차렸는데 수강생데이터 라는 건 몇 명이 수강할지 모르니 정적 변수로는 안된다는 거죠. 또한 정적 변수는 일반적으로 추상화 할 수 없습니다. 즉 그룹핑하거나 하나의 규칙을 적용하여 일괄로 반영하는 등의 행위가 불가능합니다. 코드로 설명하면 배열은 루프를 돌릴 수 있지만, a, b, c, d 라는 변수 4개가 있다면 이건 루프라는 하나의 방법론으로 컨트롤이 불가능하다는 거죠.
동적 변수는 어쩌면 정적 변수의 단점 때문에 등장했습니다. 미리 알 수 없는 변수가 주로 정적 변수의 대상입니다. 동적 변수는 런타임에 os나 vm에게 추가적인 메모리를 요구합니다. 따라서 동적 변수에 대해서는 반드시 추가적인 표시를 해둬야 컴파일러가 그 변수의 사용을 동적 변수로 처리합니다. 유명한 키워드 new 는 대표적인 동적 변수 할당 명령입니다. 언어별로 추가적인 동적 변수 할당 명령이 존재하고 특히 스크립트언어의 경우 일반적으로 vm프로그램이 정적인데 비해 vm위에서 구동되는 프로그램은 대부분 전체가 동적 할당을 하도록 되어있는 경우가 태반입니다. as3는 사실 내부 구조상으로 보면 모든 변수가 동적 변수입니다. 하지만 정적 변수처럼 선언하면 컴파일 시점에 검사해주는 기능이 있는 정도입니다.
정적 컨테이너
정적 컨테이너는 정적 변수의 두 번째 단점이었던, 추상화 불능에 대한 대안입니다. 자바, C 등에서 배열로 선언하는 경우 하나의 형에 대해 연속된 메모리주소공간을 할당함으로서 메모리의 위치를 찾을 때 동일한 규칙으로 접근할 수 있게 해줍니다. 정적 컨테이너는 정적 변수와 거의 특성이 같고 정적 변수의 두 번째 단점만 제거된 상태입니다. 하지만 여전히 정적 컨테이너를 사용하려면 미리 다 알고 있어야 합니다.
정적 컨테이너의 장점도 대부분 정적 변수와 같은 이유로 발생합니다. 안정캡은 기본이고 그 외에 매우 빠른 메모리 컨트롤이 가능합니다. 그 값이 그 메모리위치에 있다라는 걸 메모리매핑테이블에 의존하지 않고도 알아낼 수 있다는거죠.
as3는 ByteArray를 통해 정적 컨테이너를 제공하고 있습니다. 따라서 ByteArray를 컨테이너로 사용하게 되면 위의 장점을 누릴 수 있게 됩니다.
동적 컨테이너
정적 컨테이너도 미리 다 알고 있어야 하기 때문에 컨테이너에 담을 자료를 구체적으로 산정할 수 없는 경우엔 동적 컨테이너를 사용합니다. 동적 컨테이너는 기본적으로 Linked List 입니다. 즉 node를 new해서 연결해가며 증가시켜가는 방식이죠. 실제로 구현해보시면 length관리와 index관리가 상당한 부하를 준다는 사실을 알 수 있습니다.
as3에서 ByteArray를 제외하고 나머지 컨테이너는 전부 동적 컨테이너입니다. 위에 설명 드린 대로 기본적으로 as3는 모든 변수와 컨테이너가 동적 할당입니다. ByteArray 조차도 동적 할당 받은 후 컨트롤이 정적 컨테이너에 가까울 뿐입니다. 이러한 as3 컨테이너 및 그 후보를 살펴보면 다음과 같습니다.
- Array
- Vector
- Object
- String
- Dictionary
- DisplayObjectContainer
- EventDispatcher
- BitmapData
- XML
위에 열거한 컨테이너는 전부 자기만의 최적화된 용도가 있습니다. 이제 그걸 하나씩 보도록 하죠.
Array
배열은 기본적인 컨테이너로 플10이 나오기 전까진 주류 컨테이너였습니다. 주요기능은 순차인덱싱, 성긴배열, 길이관리 등입니다. 성긴배열이란 요소가 0,1,2,3 으로 들어가지 않고 0,5,10 이란 식으로 띄엄띄엄 들어가도 허용해주는 것을 말합니다. as3배열은 이 경우에도 length계산을 잘 해주고 for in 도 잘 돕니다. 배열은 내부적으로 구조를 예상해보면 관리자와 노드로 구성되어 있을 걸로 추측되는데 관리자가 길이관리와 인덱스 관리 및 각 노드의 참조 값을 보관하는 거대한 저장공간으로 되어있습니다. 이게 좀 아쉬운 부분인데 보통의 링크드리스트는 중앙 관리자가 전부 링크참조를 관리하지 않고 각 노드가 다음 노드의 참조를 관리하는 식으로 분산되어있습니다. 따라서 노드 사이에 삽입이나 삭제가 신속하게 이뤄지는 편입니다. 하지만 as3의 배열은 중앙의 참조리스트를 전부 바꿔야하기 때문에 splice, pop, unshift, shift 등이 매우 치명적인 속도 저하를 가져옵니다. 또한 indexOf도 관리자의 참조를 루프 돌며 노드를 순회하는 식이라 참조가 2단계로 일어나 매우 느립니다.
만약 배열에 인덱스를 할당하지 않고 문자키를 할당한 요소를 삽입하게 되면, 그 순간 관리자가 기능을 정지하고 외부에 Object로 리턴하게 됩니다. 따라서 Array객체에 문자키를 쓰는게 Object객체보다 느리고 관리자만큼 메모리를 쓸데없이 더 차지하게 됩니다. 하지만 조회 시에는 문자를 할당해도 자동 형반환을 하기 때문에 괜찮습니다. 즉 아래와 같은 코드는 괜찮습니다.
trace( arr[ ‘0’ ] );
하지만 이것도 골 때리는게 짜피 숫자변환을 해 줄거면 다 해주지 아래 것들은 안 해줍니다.
trace( arr[ ‘0x5’ ] ); //왜 5가 아닐까 trace( arr[ ‘aa’ ] ); //왜 0이 아닐까
Vector
벡터는 플10에서만 사용 가능한 타입으로 배열에 비해 2가지 기능을 추가했습니다.
- 엘레멘트의 형을 고정할 수 있다.
- 길이관리를 끌 수 있다.
엘레멘트의 형을 고정함으로서 동적 컨테이너가 뒤죽박죽으로 쓰이는걸 어느 정도 컴파일타임에 막을 수 있게 되었습니다. 이건 사실 엔터프라이즈 개발시 안정성을 위한 조치고 실제 성능상의 잇점은 길이관리를 끄는데서 발생합니다. 길이관리를 끔으로서 미리 필요한 만큼의 노드메모리를 최초에 확보할 수 있고 이후 복잡한 길이연산 및 노드관리를 하지 않는 부분 만큼 성능 향상이 일어납니다.
따라서 Vector를 도입했는데 길이관리를 끄지 않는 상황이라면 잇점을 절반도 못쓰고 있다고 생각하시면 됩니다( 이것과 관련해서 다른 포스팅을 했습니다. 여기 )
Object
Object를 컨테이너로 쓰는건 어찌보면 매우 당연하다 하겠습니다. 모든 as3는 동적 변수인 Object에서 파생되었는데 이 Object의 특징은 동적 변수로 선언되어 원하는만큼 메모리를 런타임에 할당받을 수 있고 이에 대해 컴파일러는 아무런 체크를 하지 않는다는 점입니다. 게다가 Object는 순수하게 데이터 외엔 아무것도 담지 않기 때문에 가장 메모리를 적게 먹는 데이터형이기도 합니다.
기능도 없고 컴파일러가 체크도 안하는 Object의 장점은 명확합니다. 빠르다! 라는거죠. Object는 배열, Vector보다 더 빠릅니다. Object는 내부적으로 엘레멘트를 추가할 때 반드시 문자열 또는 숫자키와 매핑하도록 되어있습니다. 숫자를 키로 사용해도 toString을 통해 문자열로 변환하여 처리합니다. (야꼬가 실험해보니 숫자키를 보존하고 있었습니다 ^^) 만약 루프 시 순차적으로 돌지 않아도 되고 길이 관리를 할 필요가 없다면 배열 대신 Object를 사용하는 것도 나쁘지 않습니다.
//배열의 경우 var a:Array; a[0]=3; a[1]=13; a[2]=23; a[3]=43; for each( o in a ) trace( o ); //Object의 경우 var b:Object; b[0]=3; b[1]=13; b[2]=23; b[3]=43; for each( o in b ) trace( o )
뭔가 위아래가 완전히 똑같지 않나요 ^^;
String
위에 열거한 기본 컨테이너 3종은 치명적인 단점이 있는데 검색 속도가 어마무지 느리다는 점입니다. 검색 메쏘드로 indexOf를 제공하고 있는데 일단 선형검색인데다가 관리자참조를 루프돌며 일일히 노드에 접근해 값을 비교하는 방식은 가히 살인적으로 느립니다. 이럴 때 검색속도를 약 2.5배 정도 향상시킬 방법이 있는데, indexOf를 제공해주는 또 다른 컨테이너인 String을 사용하는 것입니다.
var a:Array=[0,1,2,3,4,5,6,7]; //배열검색 trace( a.indexOf(4) ); var b:String=a.toString(); //문자열검색 trace( b.indexOf(‘4’) );
문자열이 중복되거나 겹치면 어쩌냐구요? 컴마라던가 구분자와 함께 잘 검색하도록 변경하세요. String은 동일한 자료사이즈에 대해서 벡터, 배열에 비해 2.5배정도 속도 향상이 있습니다. 검색 전용 컨테이너랄까요.
Dictionary
딕셔너리는 키를 객체참조로 지정할 수 있는 특이한 컨테이너입니다. 언제 쓰냐? 기본적으로 언제라도 사용하면 안됩니다. 왜냐면 너무 느리거든요. 지옥의 속도.. 딕셔너리를 사용해야만 하는 경우는 두 가지 밖에 없습니다.
- 약한 참조를 이용한 강제 가비지컬렉팅 구현 시
- 키에 반드시 객체 참조를 사용해야 하는 경우
1번 경우는 가장 쉽게 볼 수 있는 예가 트위너들입니다. 트위너들은 플레이어 내장 가비지컬렉터를 사용하지 않고 딕셔너리로 강제 메모리 해제가 되도록 별도로 가비지컬렉팅 비슷한 로직을 짜곤 합니다. 나중에 메모리풀링 및 강제 컬렉팅을 알아보죠 ^^;
현실적으로는 2번이 사용하는 목적이죠. 그럼 대체 언제! 키를 반드시 객체참조로 써야한단 말입니까!
첫번째 경우는 특정 기능부의 클래스가 유틸처럼 자신에게 등록된 객체에게 표준적인 기능을 처리해주는 경우에 매우 유용합니다. 이 경우 객체간의 관계를 느슨하게 하면서도 유연하게 처리할 수 있는 컨테이너 인거죠. 예를 들어 아래와 같은 클래스를 연상해봅시다.
class CSfilter{
static private var dic:Dictionary;
static public addDO( $do:DisplayObject ) :void{
dic[$do] = [];
}
static public addFilter ( $do:DisplayObject, $filter:String ):void{
if( dic[ $do ].indexOf($filter)===-1){
dic[ $do ].push($filter);
}
}
static public removeFilter( $do:DisplayObject, $filter:String ):void{
if( dic[$do].indexOf($filter)>-1){
dic[$do].splice(dic[$do].indexOf($filter),1);
}
}
static public apply( $do:DisplayObject ):void{
var filter:Array=[];
for each( filterName in dic[$do]){
filter.push(makeFilter(filterName));
}
$do.filters=filter;
}
}
위의 클래스는 귀찮은 displayObject의 필터를 전역수준에서 관리해줍니다. filters속성이 매번 새로운 배열을 할당하도록 되어있어 기존에 blur, glow가 적용된 상태에서 blur를 제거하고 dropShadow를 적용하려 한다던가 다시 blur를 적용할 때 무시하게 한다던가를 인스턴스별로 구현하는 건 그저 노가다입니다. 이러한 정보를 static으로 캐쉬에 잡아 처리해준다면 편리할텐데 이때 static컨테이너와 displayObject를 어떻게 연결 지을까요?
문자열키라면 고유키를 부여한 후 그 정보를 다시 DisplayObject의 어딘가 기록해둬야 해당 DisplayObject가 자신의 필터를 바꾸고 싶을 때 사용할 수 있을 텐데 DisplayObject 자체인 그러한 임시 변수가 존재하지 않습니다. 이러한 경우 인스턴스 자체를 키로 사용하는 것은 매우 유용하겠죠.
두 번째 경우는 검색시의 유리함입니다. 예를 들어 아래와 같은 형태의 배열을 생각해봅시다.
var a:Array=[SpriteA, SpriteB, SpriteC]; trace( a.indexOf(SpriteB) ); //1
이걸 짜피 저렇게 검색할 일이 많다면 반대로 생각해볼 수도 있습니다.
var d:Dictionary=new Dictionary; d[SpriteA]=0; d[SpriteB]=1; d[SpriteC]=2; trace( d[SpriteB] ); //1
속도는 당연히 키 접근인 딕셔너리쪽 코드가 빠릅니다. 딕셔너리가 느리다고 하는 건 배열이나 벡터에게 같은 형태로 검색이나 키 접근을 할 경우 인거지, 애시당초 키 접근을 하는 것과 값을 루프로 조회하는 건 비교할 수 없을 정도로 키 접근이 빠르죠.
DisplayObjectContainer
이것도 컨테이너냐구요? 이름에 컨테이너라고 씌여져있잖아요 ^^;
다른 단순 데이터 컨테이너에 비해 매우 무거운 편인 DisplayObjectContainer는 그 안에 현실적으로 넣을 수 있는 가장 가벼운 객체마저도 Shape수준으로 매우 무거운 컨테이너입니다. 하지만 왜 쓰냐구요? 몇 가지 재밌는 기능을 제공합니다. 특히 좌표변환계에 자동화된 기능을 많이 제공해줍니다. 데이터의 구조가 좌표계와 관련된 경우 localToGlobal, globalToLocal을 통해서 복잡한 변형 후의 좌표를 전역, 지역으로 쉽게 바꿀 수 있기 때문에 꼭 화면에 표시 하지 않는 경우에도 가끔 사용하는 편입니다. 대표적으로 골격계 애니메이션의 경우 하나의 뼈대가 움직이면 다음 뼈대가 움직이는 기준점도 같이 계산해야하는데 이런 경우에 뼈길이에 해당되는 지점에 보이지 않는 Shape를 하나 박아두면 뼈대의 회전이나 이동에 따라 다음 뼈대의 시작위치를 그저 globalToLocal(localToGlobal(shape)) 정도에서 알아낼 수 있게 되죠.
그 외에도 자식 추가삭제 및 swap에 대해 index를 자동으로 정렬하고 관리하는 기능과 name프로퍼티를 이용한 이름 접근 등도 컨테이너로서 매력적인 부분입니다. 보통 이 컨테이너는 자료자체를 위해 단독으로 쓰이기보다는 화면처리를 하는 요소를 따로 관리하지 않도록 name속성과 index속성을 적절히 활용하여 컨트롤하는 복합 컨테이너로 사용합니다. 만약 이러한 사용을 하지 않는다면 별도 기능을 위해 배열 등에 자식의 참조를 부가적으로 잡아줘서 처리해야 하는 등의 일도 빈번히 일어납니다.
EventDispatcher
이벤트 디스패처는 다채널 옵저버 모델로 아래와 같은 특성이 있습니다.
- dispatch시 Event에 문자열을 넘김으로서 여러 채널을 독립적으로 루프돌게 할 수 있다.
- 리스너는 특정 형이 아니라 범용적인 함수타입으로 광범위하게 호환된다.
- 리스너를 저장하는 디스패처 내부의 컨테이너는 2단계 키로 리스너를 저장할 수 있다. [이벤트명][인덱스]=리스너 정도의 구조다.
따라서 이런 내부 구조가 있다고 예상하고 생각해보면 컨테이너로 보이게 됩니다. 디스패처를 컨테이너로 쓰는 예를 볼까요?
//배열 이용
var a:Array=[5,6,7];
for each( o in a){
trace( o );
}
//결과 5,6,7
//디스패처 이용
function e0(e:Event):void{
trace(5);
}
function e1(e:Event):void{
trace(6);
}
function e2(e:Event):void{
trace(7);
}
var dis:EventDispatcher=new EventDispatcher;
dis.addEventListener(‘loop1’,e0);
dis.addEventListener(‘loop1’,e1);
dis.addEventListener(‘loop1’,e2);
dis.dispatchEvent(new Event(‘loop1’));
//결과 5,6,7
결과에 집중하세요. 원했던 목표에 동일하게 도달하는걸 알 수 있습니다. 간단히 정리하면 다음과 같은 경우에 이벤트 디스패처를 컨테이너로 쓰는 것이 유리합니다.
- 엘레멘트가 값 외에 알고리즘 또는 함수등의 행위를 동일하게 수반할 때
- 대규모 엘레멘트에 대해 for 더욱 빠른 루프를 원할 때
대략 언제부터 for보다 빨라지는지 통계치가 없습니다만, 보통 엄청 빠릅니다 ^^; 여러분이 짠 바이트코드보다 플레이어 내장객체의 루프가 빠른 건 당연하죠. 단지 이벤트 모델은 반드시 new Event를 필요로 해서 루프를 돌릴때마다 new비용을 지불해야하는게 문제인데 정확하게는 저 new의 비용보다 루프속도의 잇점이 더 좋아질만큼 데이터가 많아지면 그 이후는 무조건 이벤트리스너의 루프가 빠릅니다.
반대로 생각해보세요. 컨테이너를 만들어서 for를 돌립니다. 결국 for안에 순수하게 데이터만 등장할리가 없잖아요. for안에서 할 일을 데이터와 함께 빼두고 루프 시엔 그저 루프만 돌리자는 겁니다. 생소하시겠지만 오히려 설계 상으로 보면 건전합니다. 루프라는 로직은 오직 루프만 신경쓰고 루프 안에서의 로직은 별도로 관리하자는거죠. 여러분의 코드 안에도 아래와 같은 형태는 흔하지 않나요?
for each( o in arr){
someFunction(o);
}
이렇게 짜는 이유가 루프안에서의 로직을 별도로 관리하기 위해서라면 아예 처음부터 그 로직마저 루프 단위의 엘레멘트라고 보시면 슬슬 디스패처가 컨테이너로 보이기 시작합니다.
BitmapData
비트맵 데이터는 근본적으로 ByteArray의 특별한 형태입니다. 정확하게는 4바이트 정수형으로만 구성된 바이트어레이죠. 근데 왜 별도로 다루냐구요? 이 바이트어레이는 매우 특별한데 그 이유는 픽셀 쉐이더에게 넘겨줄 수 있기 때문입니다.
픽셀쉐이더는 cpu를 사용할지라도 바이트어레이를 루프 돌릴 때 as3보다 훨씬 효율적이고 빠르게 돌립니다. 따라서 정수값이 어마무지하게 많을 때 루프를 돌리기 난감한 상황에서 비트맵데이터에 setPixel로 쭉 값을 적은 뒤 루프돌며 할 로직을 pbj로 짜서 쉐이더잡한바퀴 돌려서 리턴 받으면 for로 돌리는 것의 수십배의 성능을 얻을 수 있습니다. 트래픽 통계데이터 처럼 레코드 건수가 몇백만건이나 되는 경우 xml보다, 문자열보다, AMF3보다 더 데이터를 압축해서 보낼 수단으로도 매우 좋습니다. 동적으로 생성된 png이미지 한 장 수신하는거죠. (제가 많이 쓰는 방법)
XML
그 외에도 매우 잘 알고 계신 XML이 있습니다. 여기까지 쓰니 힘들군요. 과감하게 생략하겠습니다.
관련된 글:
좋은 글 잘보았습니다. ;)
몇번 더 읽어야 되겠네요 ㅎ
ㅎㅎ 감사합니다. 몇가지 잘못되거나 부족한 점이 있을지도 모릅니다. 솔직히 as3자체 보다는 걍 원래 알고 있는 언어적 지식으로 매꾼 것도 많아서리..
와~ 너무 좋은글 감사합니다 형님~ ^^
땡큐~
먼가 도표같은 것도 그려서 넣을까 했으나 귀찮아서 도저히 못하겠더라구 ㅎㅎ
컴퓨터 구조에서 부터 as3까지 끌어올리신 내공팍팍 느껴지는 글이네요.
정말 아름다운 글입니다.!
Object쪽이나 몇군데 틀린 부분이 있는듯 ^^; 야꼬가 지적해준게 있군요. 수정은 나중에 ㅎㅎ 언제나 과찬에 감사드립니다.
잘봤습니다 ^-^ ㅋ
땡큐~
아 딕셔너리는 참 쓰기 괜찮타라고 생각하고 있었는데
퍼포먼스에 문제가 있었네요 ..
루프나 enterFrame으로 괴롭히는게 아니라면 퍼포먼스가 3배 느리던 5배 느리던 별로 상관없을지 몰라.
좋은 잘 보고 갑니다!
감사합니다.