Unity DOTS/Dots Custom Manual

UNITY DOTS - Dynamic Buffer Component 에 대해 알아보자.

개양반 2022. 8. 2.

관리되는 데이터가 저정된 Class 타입에서는 Job, BurtComplie, 멀티스레드 실행 등을 할 수 없다. 이러한 것들을 사용하려면 관리되지 않는 데이터만 저장한 Struct를 사용해야 한다. 즉, List<T> 같은 것을 쓸 수가 없다는 의미인데 그래서 불편하다. 그래서 존재하는 것이 바로 Dynamic Buffer Component 이다.  


Dynamic Buffer Component 정의하기

// 청크에 16개의 저장 공간을 만든다
[InternalBufferCapacity(16)]
public struct MyElement : IBufferElementData
{
    public int Value;
}

DymicBufferComponent는 IBufferElementData를 상속받아야 한다. Attribute로 InternalBufferCapacity(16)을 추가했는데 이는 해당 DymicBufferComponent에 저장된 요소가 16이라는 말이다. 만약, 배열의 크기가 16을 초과하면 청크 외부에 새로운 별도 배열에 복사되어 관리된다. 즉, 성능상 매우 좋지 않다는 의미이므로 배열의 크기를 알맞게 지정해야 한다. 예를 들면 스플레쉬 대상은 최대 4명까지이다. 등의 기획이 필요하다.

만약 InternalBufferCapacity를 지정하지 않으면 128바이트 내에서 알아서 지정해버린다.

 


Dynamic Buffer 기본 사용법

Dynamic Buffer는 ComponentData와 마찬가지로 추가, 제거, 쿼리를 할 수 있다.

// MyElement 유형의 요소가 있는 동적 버퍼가 있는 엔터티를 만듭니다.
EntityManager.CreateEntity(typeof(MyElement));    

// Entity에 Dynamic Buffer 추가
EntityManager.AddComponent<MyElement>(e);    

// Entity에 Dynamic Buffer 제거
EntityManager.RemoveComponent<MyElement>(e);

// Dynamic Buffer 쿼리 만들기
EntityQuery query = GetEntityQuery(typeof(MyElement));

 

DynamicBuffer 구조체는 개별 버퍼를 나타내며 버퍼에서 값을 읽고, 쓰고, 추가하고, 버퍼의 길이와 용량을 설정할 수 있습니다.

DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);

// myBuff 5번 인덱스 읽기
int x = myBuff[5].Value;

// myBuff 5번 인덱스 쓰기
myBuff[5] = new MyElement { Value = x + 1 };

// myBuff에 새값 추가하기
// InternalBufferCapacity에서 지정한 크기보다 커지면 청크 외부로 옮긴다.
myBuff.Add(new MyElement { Value = 100 });


// Effectively, set the range of usable indexes to 0 through 9.
// If necessary, will increase Capacity to accommodate the new Length.  
myBuff.Length = 10; 

// 배열 용량 재지정
myBuff.Capacity = 20;

쓰기 액세스의 경우 매개변수를 in 대신 ref로 만들어야 한다.


구조적 변경은 DynamicBuffer를 무효화한다

구조적 변경은 DynamicBuffer에서 참조하는 기본 배열을 파괴하거나 이동할 수 있기 때문에 구조적 변경 후에는 DynamicBuffer 구조체를 사용할 수 없습니다. 버퍼에 다시 액세스하려면 DynamicBuffer를 다시 획득해야 합니다.

DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);

// 구조 변경 발생
EntityManager.CreateEntity();

// 구조 변경 이전에 얻은 myBuffer는 무효화된다.
var x = myBuff[0];   // Exception!

// 구조 변경 이후에는 다시 얻어와야 한다.
myBuff = EntityManager.GetBuffer<MyElement>(e);

var y = myBuff[0];

 


Entities.ForEach 에서 DynamicBuffer 사용

Entities.ForEach에서 조회된 모든 Entity에게 동일한 버퍼가 필요한 경우 해당 버퍼를 기본 스레드의 로컬 변수로 캡처하면 된다.

var myBuff = EntityManager.GetBuffer<MyElement>(someEntity);  

Entities.ForEach((in SomeComp someComp) => {    
    // 여기서 myBuff를 사용하면 someComp를 가진 모든 entities가 동일한 myBuff를 사용할 수 있다.
}).Schedule();

ScheduleParallel로 멀티스레드에서 병렬로 실행할 경우  Buffer를 쓸 수 없다. 다만, EntityCommandBuffer.ParallelWriter를 이용해서 쓸 수 있다.

 


Generating an authoring component

GameObject에 Component를 추가할때 attribute로 [GenerateAuthoringComponent] 를 사용했듯이 Buffer도 [GenerateAuthoringComponent]를 사용해서 GameObject의 컴포넌트로 추가할 수 있다.

using Unity.Entities;

[InternalBufferCapacity(1)]
[GenerateAuthoringComponent]
public struct TestData : IBufferElementData
{
    public int Value;
}

 


EntityCommandBuffer로 Dynamic Buffer 수정

EntityCommandBuffer는 버퍼 구성 요소를 엔터티에 추가, 제거 또는 설정하는 명령을 기록할 수 있다. EntityManager도 가능하나 EntityManager는 메인스레드에서만 작동한다. 

EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);

// Dynamic Buffer 제거 
ecb.RemoveComponent<MyElement>(e);

// Dynamic Buffer 추가
DynamicBuffer<MyElement> myBuff = ecb.AddBuffer<MyElement>(e); 

// 길이 설정 및 값 지정
myBuff.Length = 20;
myBuff[0] = new MyElement { Value = 5 };
myBuff[3] = new MyElement { Value = -9 };

// Dynamic Buffer 값 설정 
// AddBuffer와 비슷하지만 Entity에 해당 Buffer가 없는 경우 에러가 발생한다. 
DynamicBuffer<MyElement> otherBuf = ecb.SetBuffer<MyElement>(otherEntity);

// 버퍼에 추가 할 수있는 값을 기록합니다.
// 엔티티에 아직 Mylement 버퍼가없는 경우 재생시 예외를 던집니다.
ecb.AppendToBuffer<MyElement>(otherEntity, new MyElement { Value = 12 });

 


Reinterpreting buffers

DynamicBuffer<T>는 T와 U가 같은 크기를 갖는 다른 DynamicBuffer<U>를 얻도록 '해석'될 수 있습니다. 이 재해석은 동일한 메모리에 별칭을 지정하므로 하나의 인덱스 i에 있는 값을 변경하면 다른 하나의 인덱스 i에 있는 값이 변경됩니다.

DynamicBuffer<MyElement> myBuff = EntityManager.GetBuffer<MyElement>(e);

// 각 MyElement 구조체가 4바이트이면 유효합니다.
DynamicBuffer<int> intBuffer = myBuff.Reinterpret<int>();

intBuffer[2] = 6;  // myBuff[2] = new MyElement { Value = 6 }; 와 같은 효과다.

// MyElement 값은 int 값 6과 동일한 4바이트를 갖습니다.
MyElement myElement = myBuff[2];
Debug.Log(myElement.Value);    // 6 출력

Reinterpret 방법은 원래 유형과 새 유형이 동일한 크기를 갖도록만 적용합니다. 예를 들어 두 유형이 모두 32비트이기 때문에 단위를 부동 소수점으로 재해석할 수 있습니다. 재해석이 귀하의 목적에 맞는지 여부를 결정하는 것은 귀하의 책임입니다.

 

댓글

💲 추천 글