ORM Part 2: 오브젝트 정의하기

오브젝트(Object) 정의

게임 내에서 사용할 오브젝트(Object)는 프로젝트 안에 있는 src/object_model/ 디렉토리의 JSON 파일을 통해 정의할 수 있습니다. 오브젝트의 형태는 일반적으로 다음과 같습니다.

{
  "ObjectName": {
    "AttributeName": "Type Flags",
    "AttributeName": "Type Flags",
    ...
  },
  "ObjectName": {
    "AttributeName": "Type Flags",
    "AttributeName": "Type Flags",
    ...
  },
  ...
}

위 내용 중 ObjectNameCharacterWeapon 등의 오브젝트를 뜻합니다. 그리고 AttributeNameTitle 이나 Level 과 같이 오브젝트가 가진 속성을 뜻합니다.

Important

Object, Attribute 이름은 가급적 Camel 형태로 입력하는 것이 좋습니다.

Camel이 아닌 형태로 명시된 ORM은 클래스의 이름이나 메서드 이름이 사용하기 어렵게 생성됩니다.

  • (나쁜 예) my_character 로 할 때 자동 생성되는 함수 이름: Getmy_character()

  • (나쁜 예) myCharacter 로 할 때 자동 생성되는 함수 이름: GetmyCharacter()

  • (좋은 예) MyCharacter 로 할 때 자동 생성되는 함수 이름: GetMyCharacter()

Tip

RFC에 명시된 JSON은 // .../* ... */ 형태의 주석을 사용할 수 없지만 아이펀 엔진 ORM 정의 파일에는 주석 사용이 가능합니다. 이는 개발 과정에서 오브젝트에 대한 설명 등을 남길 때 유용합니다.

속성(Attribute) 정의

속성(Attribute)은 아이펀 엔진이 제공하는 기본(Primitive) 형태 외에도 다른 오브젝트 나 배열(Array), 맵(Map)을 속성으로 지정할 수 있습니다.

기본(Primitive) 타입

  • Bool: 참/거짓을 의미합니다.

  • Integer: 64비트 정수 값을 의미합니다.

  • Integer64: Integer 와 동일하며 64비트 정수 값을 의미합니다.

  • Integer32: 32비트 정수 값을 의미합니다.

  • Integer16: 16비트 정수 값을 의미합니다.

  • Integer8: 8비트 정수 값을 의미합니다.

  • Double: 64비트 실수 값을 의미합니다.

  • Float: 32비트 실수 값을 의미합니다.

  • String(n): n 글자 짜리 문자열을 의미합니다.

  • DateTime: DateTime (WallClock::Value) 값을 의미 합니다.

Important

String(n) 의 경우 바이트(Byte) 단위가 아닌 유니코드 글자를 단위로 합니다. 아이펀 엔진에서는 UTF8 글자 수를 확인할 수 있도록 util::GetUtf8Length() 함수를 제공하고 있습니다.

명시된(n) 글자보다 더 긴 문자열을 저장하면 초과하는 부분을 제외한 나머지만을 저장합니다. 만약 명시적으로 이를 제어하고 싶다면 util::TruncateUtf8() 함수를 사용하면 됩니다.

Important

String 은 더 이상 지원되지 않으며 String(n) 을 쓰셔야 됩니다.

StringString(n) 은 혼용될 수 없기 때문에 기존에 생성된 프로젝트에서 String 을 쓰고 있다면 계속해서 String 을 쓰거나 모든 문자열을 String(n) 으로 변경해야 합니다.

Important

DateTime1970-01-01 00:00:00 부터 9999-12-31 23:59:59 까지의 값에서 동작합니다.

DateTime 이 아니라 UNIX 시간 정수 값을 나타내는 timestamp 를 정의하고 싶다면 Integer64 타입을 쓰시면 됩니다.

Note

기본 Integer 타입 외의 Integer8, Integer16, Integer32, Integer64 타입과 DateTime 타입은 1.0.0-2595 Experimental 버전 이상에서만 사용할 수 있습니다.

Note

DateTime 타입을 사용하는 배열 (Array) 과 맵 (Map) 은 1.0.0-xxxx Experimental 버전 이상에서만 사용할 수 있습니다.

 // 14 글자, 36 바이트 문자열
 std::string s = "다람쥐 헌 쳇바퀴에 타고파";
 BOOST_ASSERT(14 == util::GetUtf8Length(s));
 BOOST_ASSERT(36 == s.size());

 // 처음 세글자만 자르기
 std::string t = util::TruncateUtf8(s, 3);
 BOOST_ASSERT("다람쥐" == t);

오브젝트 타입

오브젝트에는 기본 타입 외에도 다른 오브젝트를 속성으로 사용할 수 있습니다. 이 경우 해당 속성은 오브젝트 타입을 갖게 됩니다. 참고로 속성으로 지정할 오브젝트 모델이 먼저 선언될 필요는 없습니다.

아래 예에서 MyChracter 속성은 Chracter 라는 오브젝트 타입이 됩니다.

"User": {
  "MyCharacter": "Chracter"
},
"Character": {
  "Hp": "Integer",
  "Mp": "Integer"
}

배열 (Array)

타입 뒤에 [] 을 추가하면 배열 형태가 됩니다.

예를 들어 Integer[], String(12)[], Character[] 는 각각 기본타입으로 주어지는 정수 배열, 길이 12글자짜리 문자열 배열, 그리고 사용자가 지정한 Character 라는 타입의 배열을 의미합니다.

배열 타입의 속성은 C++/C# 클래스가 생성될 때에도 InsertAt() 이나 EraseAt() 과 같이 인덱스를 이용한 배열 작업을 지원합니다.

Important

배열의 경우 중간에 있는 내용을 삭제하거나 추가하게 되면 배열 재정렬 때문에 느려질 수 있습니다. 자세한 내용은 Using Array and Map 을 참고하세요.

맵 (Map)

<KeyType, ValueType> 형태로 지정합니다.

KeyType 에는 기본 타입만 가능하며, ValueType 에는 기본 타입뿐만 아니라 오브젝트 타입도 가능합니다.

예를 들어 <Integer, String(4)><String(64), Item> 는 각각 정수를 Key 로 가지면서 길이 4짜리 문자열을 Value 로 갖는 맵 타입과, 길이 64짜리 문자열이 Key 이면서 Item 이 Value 인 맵 타입을 의미합니다.

속성 플래그(Flag) 지정

키(Key)

해당 속성을 키(key)로 지정합니다. 키로 지정된 오브젝트는 키를 사용하여 오브젝트를 가져올 수 있는 함수들이 제공되며 MySQL에는 Non-Clustered IndexUnique Constraint 로 설정됩니다.

Important

키는 중복 값이 허용되지 않으며 정수 또는 문자열 속성에만 지정할 수 있습니다. 또한 생성 이후에는 값을 변경할 수 없습니다.

아래 예제는 Character 의 Name 을 키로 지정한 경우입니다.

"Character": {
    "Name": "String(20) Key"
}

복합키(Composite Key)

여러 속성을 하나의 복합키(Composite Key)로 지정합니다. 복합키는 키와 동일한 특징을 가지고 있으며 두 개 이상의 속성을 키로 등록할 때 사용할 수 있습니다.

Important

한 오브젝트에 복합키와 키를 동시에 사용할 수 없습니다.

아래 예제는 Map 오브젝트의 X와 Y 속성을 복합키로 지정한 경우입니다.

  "MAP": {
      "X": "String(10)",
      "Y": "String(10)",
      ...
      "COMPOSITE_KEY":[
        "X", "Y"
      ]
  }

참조 (Foreign)

기본 타입이 아닌 다른 오브젝트를 속성으로 지정할 경우 참조(Foreign)를 사용할 수 있습니다. 참조 오브젝트는 오브젝트의 ID 값만 읽어오며 오브젝트 내용은 가져오지 않으므로 ID를 통해 직접 가져와야 합니다. 이러한 방식은 모든 오브젝트를 한꺼번에 읽는 것을 방지하기 때문에 부하를 최소화할 수 있습니다.

예를 들어, 다음과 같이 Character 와 Weapon 이라는 모델이 있다고 해보겠습니다.

"Character": {
    "MyWeapon": "Weapon"
},
"Weapon": {
    "Durability: "Integer"
}

이 경우 Character 를 DB 에서 읽어오게 되면 Weapon 타입인 MyWeapon 도 같이 읽어오게 됩니다.

위 방식은 명확한 소유 관계의 경우 유용하게 사용할 수 있지만 공유 오브젝트나 타 오브젝트가 소유한 오브젝트를 가져올 경우 문제가 발생할 수 있습니다. 이러한 상황에서는 참조 의 의미로 Foreign 을 지정할 수 있습니다.

"Character": {
    "MyWeapon": "Weapon Foreign"
},
"Weapon": {
    "Durability: "Integer"
}

위와 같이 Foreign 으로 지정하는 경우 Character 를 읽어오더라도 MyWeapon 을 자동으로 읽어오지 않습니다. 대신 MyWeapon 의 오브젝트 ID 를 저장해두고 나중에 MyWeapon 을 DB 에서 읽어와야 되는 경우 활용할 수 있게 해줍니다.

Tip

배열이나 맵의 경우는 Item[] Foreign 이나 <Integer, Item> Foreign 처름 배열과 맵 정의 뒤에 Foreign 을 씁니다.

모델 정의 예제

아래는 CharacterItem 이란 Object 를 정의한 예제입니다. 이후 이 챕터와 ORM 관련 챕터들은 이 오브젝트 모델을 가정합니다.

src/object_model/example.json

{
  "User": {
    "Id": "String(16) KEY",
    "MyCharacter": "Character"
  },

  "Character": {
    "Name": "String(16) Key",
    "Exp": "Integer",
    "Level": "Integer",
    "Hp": "Integer",
    "Mp": "Integer",

    "Inventory": "Item[]",

    "QuickSlot": "<Integer, Item>",
    "EquippedItem": "<String(9), Item>"
  },

  "Item": {
    "Type": "String(16)"
  }
}

약 20 줄 정도의 간단한 오브젝트 모델 정의만으로 User, Character, Item 오브젝트 구현이 완료되었습니다. 이제 아이펀 엔진이 제공하는 데이터베이스 쓰기, 캐시, 분산처리, 동시 처리 등의 고급 기능을 JSON 파일을 통해 자동 생성되는 클래스를 사용하는 것만으로 사용할 수 있습니다.