Unity DOTS/Dots Custom Manual

UNITY DOTS - ICompoentData 에 대해 알아보자.

개양반 2022. 7. 25.
728x90

아래의 글은 유니티 2021.3.5, Entities 0.51.0-preview Version을 기준으로 작성되었습니다.

 

IComponent에 대해 알아보기 전에 

Dots의 Component와 기존 Unity의 게임오브젝트의 Compoent의 차이점에 대해 알아보자.

DOTS의 Component 기존 유니티의 Component
일반적으로 구조체(Struct)의 인스턴입니다.
클래스의 인스턴스가 될 수도 있긴합니다.
클래스의 인스턴스입니다.
동작(로직)을 가지고 있지 않습니다. 동작(로직)을 가지고 있습니다.

 

Component 인스턴스

DOTS의 Component는 구조체 인스턴스와 클래스의 인스턴스가 될 수 있습니다. 

 

구조체 인스턴스

DOTS에서 성능상의 이점으로 가장 일반적으로 사용되는 방식입니다.

구조체 인스턴스는 관리되지 않는 구성 요소 형식의 데이터를 가지고 있습니다. 관리되지 않는 구성요소 형식은 bool, char, BlobAssetReference<T>, Collections.FixedString, Collections.FixedList, Fiexed array, Blittable types이 있으며 이와 동일한 제한 사항을 준수하는 기타 구조체도 포함될 수 있습니다.

 

클래스 인스턴스

DOTS에서 성능상의 단점 때문에 추천하지 않는 방식입니다. 

클래스 인스턴스는 관리되는 구성 요소 형식의 데이터를 가지고 있습니다. 위에 관리되지 않는 데이터 외의 모든 데이터를 말합니다. 클래스 인스턴스는 구조체와 달리 Chunk에 저장되지 않습니다. 대신 하나의 큰 배열에서 참조되므로 Job에서 액세스할 수 없으며, BurstCompile을 사용할 수 없으며, 가비지 수집이 필요합니다. 또한 직렬화가 되어야 한다. 그러므로 클래스 인스턴스는 구조체 인스턴스에 비해 성능상 비효율적이다.

 

주의 사항

ComponentData에 관리되지 않는 데이터와 관리되는 데이터를 조합해서 만들면 해당 인스턴스는 클래스 인스턴스가 되어 메인스레드에서만 동작하게 된다는 점이다. 이 말은 성능상의 이점을 얻을 수 없다는 것이다.

Text는 관리되는 데이터라 구조체 인스턴스가 될 수 없다.

또한 구조체, 클래스 인스턴스를 SystemBase.Entities.ForEach에서 동시에 사용하게 된다면 BurstCompile, Job을 사용할 수 없을 뿐더러 메인스레드에서만 작동하게 만들어야 한다. 이럴 경우도 DOTS의 성능상의 장점을 활용하지 못한다는 의미이기도 하다.

예로 점수를 얻으면 UGUI.TEXT의 정보를 갱신하는 시스템이 있다고 과정하자. 점수를 증가하는 로직과 변경된 점수로 UI Text를 변경하는 로직이 하나의 Entities.ForEach에 있으면 해당 로직은 버스트컴파일을 사용할 수 없으며 메인 스레드에서만 동작하도록 해야 한다.

WithoutBurst : 버스트를 사용안함, Run() : 메인스레드에서 동작하도록 한다.

관리되는 데이터와 관리되지 않는 데이터는 각각의 ComponentData로 나눠서 관리하는 것이 좋으며 System도 관리되는 항목은 메인스레드에서 관리되지 않는 항목은 멀티스레드와 BurstComplie을 사용해서 DOTS의 성능상의 이점을 최대한 얻도록 해야 한다. (몰론 번거롭다) 

 

참고

DOTS는 Preview 상태로 개발이 진행되고 있는 단계의 패키지이다. 현재는 Animation, 피직스 등이 DOTS에서 동작하도록 개발이 진행되고 있으며 그 외 Navigation 등 다양한 유니티의 패키지들이 멀티스레드, JOB, BurstComplie이 되도록 만들어질거라 예상하고 있다. 그때까지는 위의 유니티의 다양한 패키지를 사용하려면 번거롭지만 클래스 인스턴스로 만들어서 메인스레드에서만 동작하도록 구현해야 한다.


IComponent는 ArcheType이 되어 Chunk에 저장된다.

Entity의 ComponentData 집합을 Archetype 이라고 합니다. 그리고 Archetype은 Chunk라는 메모리 블록에 저장됩니다. Chunk는 자동으로 관리되므로 직접 제어할수가 없습니다. Entity의 Component 조합이 변경되면 프로그램은 자동으로 Archetype을 변경하고 해당 Archetype과 일치하는 chunk로 이동하거나 새로운 메모리 Chunk로 이동시킵니다.

Component의 필드값을 변경하는것은 구조적 변경이 되지 않습니다.

 

Tag Components

필드가 없는 IComponentData를 태그 컴포넌트라고 합니다. Tag Component에는 데이터가 없기 때문에 Chunk에는 Tag Component에 대한 Component 배열이 저장되지 않습니다. 

 

Entity에서 Component를 제거하거나, 추가하는 방법

기본 스레드에서 EntityManager를 이용해서 삭제하거나, 추가할 수 있습니다.

EntityManager를 통해 Component를 추가하는 예제

 

EntityManager를 통한 Component 구조 변경은 기본스레드에서만 가능하다는 문제점이 있습니다. 그래서 Entity의 구조적 변경을 멀티스레드에서 진행하려면 EntityCommandBuffer를 통해 이뤄져야 합니다.

 

참고

EntityCommandBuffer에 대해서는 추후에 상세하게 다루겠지만 간단하게 설명하자면 해당 스레드에서 쓰기 작업을 EntityCommandBuffer에 예약하여 해당 프레임이 종료되면 쓰기 작업을 진행하도록 하는 것입니다. 구조 변경 및 멀티스레드에서 일어날 수 있는 경쟁문제를 해결할 수 있습니다.

 

ComponentData는 System 로직에 따라 작게 나누는 것이 좋다.

캐릭터의 능력치를 하나의 Component Data에 추가했다면 메모리 낭비가 발생할 수 있다.

예를 들어, 공격력을 증가하는 시스템에서는 MoveSpeed가 필요없다. 하지만 하나의 ComponentData에 각 능력치가 함께 필드로 존재하므로 EntityQuery로 검색하면 불필요한 MoveSpeed까지 포함되는 문제가 발생한다.

공격력 증가 시스템에는 MoveSpeed가 필요없지만 딸러온다.

 

그러므로 시스템에서 자주 같이 사용하는 그룹끼리 묶어서 소형으로 만드는 것이 좋다.

댓글

💲 추천 글