게임엔진/DOTS

Entities - 컴포넌트 구조

tsyang 2023. 2. 25. 22:05

2021.09.26 - [이론/설계] - ECS (Entity Component System)

 

ECS (Entity Component System)

ECS 정의 "A different paradigm of writing code, where we model our programs in a data oriented way" 유니티 ECS 메뉴얼에서는 ECS를 위와 같이 정의했다. 데이터 지향 설계방식은 아래 글을 참고. https://tsyang.tistory.com/68 D

tsyang.tistory.com

 

2023.02.04 - [유니티/DOTS] - DOTS 1.0 - 2 (생성, MonoBehavour 연계)

 

DOTS 1.0 - 2 (생성, MonoBehavour 연계)

2023.01.29 - [유니티/DOTS] - DOTS 1.0 - 기본 (Component,System,Aspect,Job) DOTS 1.0 - 기본 (Component,System,Aspect,Job) 2023.01.01 - [유니티/DOTS] - DOTS 1.0 나온 기념 세팅법 DOTS 1.0 나온 기념 세팅법 ECS 1.0 pre-release 버전이

tsyang.tistory.com

 

 

 

데이터구조


월드(World)

  • 엔티티의 콜렉션임. 엔티티의 id는 한 월드 안에서만 유니크함.
  • 월드마다 EntitiyManager를 가짐.
  • 월드마다 System들의 Set을 가진다.

structural change

  • 메모리 청크를 재조직하는걸 structural change라고 함.
  • 이거 비싸다 그리고 메인스레드에서만 됨.

 

다음 경우에 structural change

  • 엔티티를 지우거나 만든다 . (추가할때는 청크 새로 만들기/ 지울때 : 청크사이에 갭이 있으면 마지막걸로 채움(list의 remove가 아니다) . 청크가 비어버리면 할당해제.
  •  컴포넌트를 더하거나 뺌 (엔티티 지우고 다시만드는거랑 똑같겠지 ) 
  • Shared 컴포넌트의 값을 설정함. (같은 Shared 컴포넌트 쓰는 애들끼리 청크로 묶으니까 당연)

 

Sync Point

  • 싱크포인트는 일단 스케쥴된 모든 잡들이 완료될때까지 기다리는 지점을 의미.
  • 당연히 성능에 좋지않음.
  • Structural change가 주 원인이다.
  • structural change가 일어나면 기존 레퍼런스 같은것들도 다 훼손됨. (Dynamic buffer)
  • 그래서 이걸 피하려면 entity command buffer를 써야함. 이건 structural change를 일으키는 애들을 모았다가 한번에 처리하는 용도

 

 

 

컴포넌트 종류


 

Unmanaged

  • IComponentData를 상속한 구조체가 해당된다. 지정할 수 있는 데이터 타입에 제한이 있다.

 

Managed

  • IComponentData를 상속한 클래스가 해당된다. 저장할 수 있는 데이터 타입에 제한이 없다.
  • 대신 Jobs안에서 사용할 수 없고 버스트 컴파일도 안 됨. GC에 의해 영향받음. 직렬화를 위해 인자 없는 생성자가 필요하다.
  • 외부 리소스에 참조하는 경우라면 ICloneable이랑 IDisposable을 구현해주는게 좋다. (entity가 복사되는 경우를 위해서)
  • 이 컴포넌트는 청크에 저장되지 않음. 대신에 World안에있는 거대한 array안에 다 보관함.
  • 그러면 chunk는 이런 array의 indices를 저장함.
  • 당연히 index 룩업을 하기 때문에 umanaged에 비해서 느림. (jobs를 못 쓰는거나 버스트 컴파일 사용 못함으로 인한 성능 저하는 덤)

 

Shared

  • 말그대로 엔티티들이 공유하는 컴포넌트. 최적화를 위해 쓴다.
  • 다른 공유 컴포넌트를 쓰는 엔티티끼리는 같은 청크에 있지 않는다. 즉, 어떤 청크가 있다면 얘네는 다 같은 공유 컴포넌트를 쓴다.
  • managed unmanaged 둘 다 가능 managed면 그냥 managed랑 똑같은 장단점 존재

Tag

  • 말 그대로 엔티티에 태그를 부여하는 용도.
  • 그냥 아무것도 없는 IComponent 상속 struct만들면 됨 . 얘네는 청크에 저장도 안 됨.

Cleanup

  • 이것도 일종의 태그라고 볼 수 있음.
  • ICleanupComponentData를 상속한 빈 struct를 만들면 된다.
  • 이게 붙어있는 애들의 엔티티를 삭제하려고 하면 엔티티를 지우는 대신 일단 non-cleanup 컴포넌트를 다 지운다. 그 다음cleanup컴포넌트까지 지워줘야 엔티티가 지워진다.
  • 삭제될 때 cleanup이 필요한 애들한테 붙여주면 유용하다.

 


DynamicBuffer

  • 크기 변경이 가능한 배열처럼 동작하는 컴포넌트.
  • Length랑 Capacity가 존재한다.
  • Capacity초기값은 보통 128Byte가 되도록 맞춰짐.
  • 처음에는 다이나믹 버퍼에 있는 데이터들을 청크에 직접 저장한다. 그러다가 사이즈가 커지면 데이터를 복사해서 청크 밖에 저장한다. 한 번 밖으로 나온 데이터는 다시 청크 안으로 안 들어간다.
  • 따라서 이런 경우가 생기면 청크에 낭비되는 공간이 생긴다.
  • 그러니까 웬만하면 Capacity를 초과하는 일이 없는 게 좋다. 
  • 만약 처음부터 Capacity가 클 것으로 예상되면 InternalBufferCapcity 어트리뷰트를 0으로 설정해서 첨부터 청크 밖에 저장하게 하는 게 좋다.

Chunk

  • 청크컴포넌트는 chuck마다 하나의 value를 저장한다. 목적은? 최적화
  • 공유 컴포넌트와 비슷하다고 할 수 있는데 조금 다르다.
  • 청크컴포넌트는 청크에 속함. (엔티티가 아니라)
  • 청크 컴포넌트 세팅하는건 structural change가 아님
  • unamanged만 됨
  • 엔티티의 아키타입이 바뀌거나 공유 컴포넌트가 바뀌면 다른 청크로 엔티티를 옮기는데 이때도 청크 컴포넌트 값은 안 바뀐다.
  • 공유 컴포넌트와 다르게 유니티가 복제하지 않음.

 


Enableable

  • 예네는 그냥 enable/disable만 스위치해주는 컴포넌트. 추가/제거보다 낫다. 왜냐? structural change가 없으니까~
  • IEnableableComponent를 구현하기만 하면 된당~ 이거 추가한다고 아키타입이 바뀌지는 않는다. 이말인 즉슨 entity command buffer를 쓸 필요도 없단 말.
  • 그치만 race를 방지하기위 해 메인 스레드에서만 enable값을 바꾼다.

Singleton Component

  • 월드당 하나만 있는 컴포넌트이다.
  • 당연히 여러 스레드에서 동시 접근하는 것을 주의해야 한다.