FlatBuffer

연두비두밥·2024년 4월 2일
post-thumbnail

목차

FlatBuffer란?
FlatBuffer를 사용하는 이유(장점)
단점
사용법


FlatBuffer란?

  • 최대 메모리 효율성을 위해 구글에서 개발된 크로스 플랫폼 직렬화 라이브러리이다.
    기존에도 프로토콜 버퍼(protobuf)라고 구글이 만들어 놓은 직렬화 라이브러리가 존재했다! 하지만 flatbuffer는 완전히 '게임'에 초점을 맞춘 직렬화 라이브러리이다.(cocos2D에서 사용하고 있다고 함)
  • 이를 사용하면 구문 분석/압축 해제하지 않고도 직렬화된 데이터에 직접 액세스 할 수 있으면서도 여전히 뛰어난 순방향/역방향 호환성을 유지할 수 있다.
  • 가장 큰 장점은 해당 내용을 작성하게 되면 자신이 원하는 플랫폼으로 라이브러리가 생성된다.
  • 널리 사용되는 여러 언어에 대한 코드 생성 및 런타임 라이브러리이다.
    공식홈페이지

FlatBuffer를 사용하는 이유(장점)

  • 여러 언어에 대한 코드 생성 및 런타임 라이브러리
    (이거 혁신이다.)
  • 구문 분석/압축 풀기 없이 직렬화된 데이터에 액세스
    쉽게 말해 Parser가 필요없다.
  • 메모리 효율성 속도
    데이터에 액세스하는데 필요한 유일한 메모리는 버퍼의 메모리인데 추가 할당이 필요없다.
    해당 내용에 Benchmarks
  • 유연성
    뛰어난 상위 및 하위 호환성을 얻을 수 있다. 이는 데이터 구조 설계 방법에 대한 선택의 폭이 넓다는 의미
    즉, 새 버전마다 업데이트를 할 필요가 없다. 이전 버전과 이후 버전에 호환성이 보장된다.
  • 작은 코드 공간
    생성된 코드의 양이 적고 최소한의 종속성으로 작은 헤더 하나만 있으면 통합이 매우 쉽다.
    실제로 Sample을 실행시키면 알겠지만, 생성된 Mydata_generated.h와 flatbuffers.h만 넣으면 된다. 매우 심플
  • 강력한 형식
    반복적이고 오류가 발생하기 쉬운 런타임 검사를 수동으로 작성하지 않고 컴파일 타임에 오류가 발생. 유용한 코드가 발생할 수 있다.
  • 사용하기 편리함
    생성된 코드를 사용하면 간결한 액세스 및 구성 코드가 가능하다.
    사실 이런 말보다 그냥 진짜 샘플 코드만 돌려보면 사용하기 편리한 것을 알 수 있다.
  • 종속성이 없는 크로스 플랫폼 코드
  • GitHub에서 오픈 소스로 제공된다.
  • 현재까지 업데이트 되고 있음
    얼마전에도 업데이트 됐다는걸 알 수 있음
  • 리플랙션을 지원한다.

단점

  • 단점이라 보긴 어렵지만, 당연하게도 함수는 구현되지 않는다. 커스텀 속성을 사용하거나 리플랙션을 사용해서 함수를 바인딩해야한다.

사용법

  • 사용하기는 아주 심플하다.
  • 공식사이트에서 flatc_window_exe를 다운로드를 받고 압축을 풀어 진행해야한다.
  1. 먼저 스키마(schema)를 작성한다.
    스키마는 공식 tutorial 사이트 스키마를 가져왔다.
namespace MyGame.Sample;

enum Color:byte { Red = 0, Green, Blue = 2 }

union Equipment { Weapon } // Optionally add more tables.

struct Vec3 {
x:float;
y:float;
z:float;
}

table Monster {
pos:Vec3; // Struct.
mana:short = 150;
hp:short = 100;
name:string;
friendly:bool = false (deprecated);
inventory:[ubyte];  // Vector of scalars.
color:Color = Blue; // Enum.
weapons:[Weapon];   // Vector of tables.
equipped:Equipment; // Union.
path:[Vec3];        // Vector of structs.
}

table Weapon {
name:string;
damage:short;
}

root_type Monster;

위와 같이 스키마 작성(확장자명은 fbs로 저장해야한다.)
2. 해당 샘플에 있는 컴파일러(flatc.exe)를 통해 컴파일 해준다.
(cmd를 켜서 해당 exe가 존재하는 폴더로 이동)

출력하고 싶은 언어에 관련된 명령어 입력

  1. 관련 monster_generated.h 생성
  2. 공식 Tutorial을 보면서 어떤식으로 적용할지 익힌다.
    [첫번째]
    - 라이브러리, 생성된 파일등을 가져오거나 포함
    // ‘platc’에 의해 생성된 것이며, 이미 “flatbuffers/flatbuffers.h”를 포함하고 있다.
    #include “monster_generated.h” 
    [두번째]
    - 버퍼 구축
    FlatBufferBuilder 인스턴스를 생성해야한다.
    ```
    // 'FlatBufferBuilder'를 생성합니다. 이는
    // 몬스터의 FlatBuffer.
    flatbuffers::FlatBufferBuilder builder(1024);
    ```
    이제 직렬화를 진행할 수 있다.
    [세번째]
    - 클래스를 구축
    auto weaponOneName = 	builder.CreateString("Sword");
    short weaponOneDamage = 3;

    auto weaponTwoName = builder.CreateString("Axe");
    short weaponTwoDamage = 5;

	// 'CreateWeapon' 단축키를 사용하여 모든 필드가 설정된 무기를 생성
    auto sword = CreateWeapon(builder, weaponOneName, weaponOneDamage);
    auto axe = CreateWeapon(builder, weaponTwoName, weaponTwoDamage);
    
    //기존 배열에서 벡터를 생성하는 대신 요소를 하나씩 개별적으로 
    //직렬화하는 경우 버퍼가 뒤에서 앞으로 구축되므로 역순으로 발생한다는 점이 주의
    // Create a FlatBuffer's `vector` from the `std::vector`.
		std::vector<flatbuffers::Offset<Weapon>> weapons_vector;
		weapons_vector.push_back(sword);
		weapons_vector.push_back(axe);
		auto weapons = builder.CreateVector(weapons_vector);

[네번째]
- Monster에 필요한 나머지 객체 serialize진행
Monster를 직렬화하기 전에 먼저 그 안에 포함된 모든 객체를 직렬화해야 한다. Flatbuffer는 깊이 우선을 사용하여 데이터 트리를 직렬화한다.

auto position = Vec3(1.0f, 2.0f, 3.0f);

auto name = builder.CreateString("MyMonster");

unsigned char inv_data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
auto inventory = builder.CreateVector(inv_data, 10);

// Shortcut for creating monster with all fields set:
auto orc = CreateMonster(builder, &position, 150, 80, name, inventory,
                         Color_Red, weapons, Equipment_Weapon, axe.Union());

[추가적으로]

  • 모든 필드를 설정하고 싶지 않은 경우에는 table을 호출하는 대신 각 필드를 수동으로 설정할 수 있다.

    // 수동으로 생성
    MonsterBuilder monster_builder(builder);
    monster_builder.add_pos(&position);
    monster_builder.add_hp(hp);
    monster_builder.add_name(name);
    monster_builder.add_inventory(inventory);
    monster_builder.add_color(Color_Red);
    monster_builder.add_weapons(weapons);
    monster_builder.add_equipped_type(Equipment_Weapon);
    monster_builder.add_equipped(axe.Union());
    auto orc = monster_builder.Finish();
    
    //Finis()를 호출하여 빌더에게 이 몬스터가 완료되었 음을 알린다.
    // 참고 : 'orc'를 어떻게 생성했는지에 관계없이 여전히 호출해야한다.
    // 'FlatBufferBuilder'의 Finish()'
    builder.Finish(orc); // 'FinishMonsterBuffer(builder, orc);'를 호출 할 수도 있다.

    이제 버퍼를 어딘가에 저장하거나, 네트워크를 통해 전송하거나, 압축하거나, 원하는 작업을 수행할 준비가 되었다.

    [다섯번째]
    - 버퍼에 액세스

    // `Finish()` 이후에 호출되어야 합니다.
    uint8_t *buf = 	builder.GetBufferPointer();
    int 크기 = builder.GetSize(); // 버퍼의 크기를 반환합니다.
  • 만약 Finish()를 하지 않고 호출한다면, 에러를 발생

  • 이제 바이트를 파일에 쓰거나 네트워크를 통해 보낼 수 있다. 파일모드(또는 전송 프로토콜)가 텍스트가 아닌 BINARY 로 설정되어 있는지 확인하자.

    [여섯번째]
    - Monster FlatBuffer 읽기
    FlatBuffer를 성공적으로 생성했으므로 Orc 몬스터 데이터를 저장하고 네트워크를 통해 전송할 수 있다.

uint8_t *buffer_pointer = /* 방금 읽은 데이터 */ ;

auto monster = GetMonster(buffer_pointer);

// `monster`는 `Monster *` 유형입니다.
// 참고: 루트 객체 포인터는 `buffer_pointer`와 동일하지 않습니다.
// `GetMonster`는 `GetRoot<Monster>`를 호출하는 편의 함수이며,
// 후자는 루트가 아닌 유형에도 사용할 수 있습니다.
  • FlatBuffer에 Table은 가상테이블이라는 간접 참조를 통해 구현된다.

위와 같은 방법으로 사용할 수 있다.

전부 공식 튜토리얼에 나오는 부분이므로 사이트 참고하면 더 이해하기 쉬울것이다.
마지막으로

FlatBuffer를 공부하게 된 계기

  • 패킷을 C++, C#으로 생성해야하는데, 이를 적용하기 위해 protobuf를 찾다가, flatbuffer라는 아주 좋은 기술을 찾게 되었다. 특히 코드 생성을 자동으로 해준다는 점에서 혹하게 되었다.
    공식 자료는 친절한데, 생각보다 사용자 자료는 별로 없어서 자료란 자료는 열심히 찾아봤던 것 같다. (유튜브도 별로 없음.. 공식 유튜브는 7년전이 마지막)
    실제로 프로젝트에는 적용을 하지 않기로 하였지만, 좋은 공부가 되었다.
  • 참고로 ProudNet과 같이 FlatBuffer를 사용하려면, 패킷을 Proud::ByteArray를 사용하자.
    ->삽질을 너무 많이해서 혹시나 나와 같은 사람이 있을까봐...
profile
꾸준하고 싶은 사람

0개의 댓글