12. ORM Part 2: Defining Object

You define an object model in JSON under the src/object_model/ directory of the source tree. Object model definition should have a name and properties like this:

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

Here, ObjectName becomes the name of a defined Object like Character or Weapon. And AttributeName becomes the name of a property like Title or Level.

Important

Names for Object and Attribute should be in the Camel format.

Otherwise, auto-generated class names or method names would look not pretty.

  • (Bad example) For the name my_character, auto-generated function will be Getmy_character()
  • (Bad example) For the name myCharacter, auto-generated function will be GetmyCharacter()
  • (Good example) For the name MyCharacter, auto-generated function will be GetMyCharacter()

Tip

iFun Engine ORM allows comments like // ... or /* ... */, while the original JSON syntax does not. This is useful when adding comments for defined object models.

12.1. Attribute Type

Attribute can be of primitive type or of object type using other object model name. Array or Map are also possible.

12.1.1. Primitive types

  • Bool: Means true/false.

  • Integer: Means an integral number.

  • Double: Means a real number.

  • String(n): Means a string whose length is n Unicode characters.

    Important

    String(n) specifies the number of Unicode characters instead of the number of bytes. You can check the length of Unicode string using util::GetUtf8Length().

    If you try to store a string longer than n Unicode characters, the string will be stored after being truncated. If you need to explicitly truncate a string, you can use util::TruncateUtf8().

    Important

    String is not supported anymore, and you must use String(n).

    If you have used String in your project, you should keep using it or replace the former with the latter since String and String(n) are not interchangeable.

 // 14 Unicode characters while 36 bytes.
 std::string s = "다람쥐 헌 쳇바퀴에 타고파";
 BOOST_ASSERT(14 == util::GetUtf8Length(s));
 BOOST_ASSERT(36 == s.size());

 // Takes the first 3 Unicode characters
 std::string t = util::TruncateUtf8(s, 3);
 BOOST_ASSERT("다람쥐" == t);

12.1.2. Object type

In addition to the primitive types, it’s possible to use other object model name as type. In this case, corresponding attribute is treated as an instance of the given object model. It is not required to put a definition of the target object model before using it.

In the example below, MyChracter is of an object type named Chracter.

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

12.1.3. Array type

You can append [] to a type name to make an array of the type.

For example, Integer[], String(12)[], Character[] are an array of integers, an array of string whose length is at most 12 Unicode characters, and an array of Character objects, respectively.

For an array type, ORM generates C++/C# methods that support array operations taking an index (E.g., InsertAt() or EraseAt())

Important

For an array, it triggers an array re-ordering and becomes inefficient if adding a new element in the middle or removing an element from the middle. For details, please refer to Array & Map.

12.1.4. Map type

<KeyType, ValueType> defines a map whose key is of KeyType and value is of ValueType.

For KeyType, only primitive type is possible, while ValueType can hold either primitive type or object type.

For example, <Integer, String(4)> and <String(64), Item> mean a map taking integers as keys and Unicode string of at most 4 characters as values, and a map taking Unicode strings of at most 64 characters as keys and Item objects as values, respectively.

12.2. Attribute Flag

12.2.1. Key

Make the field as a key. Key field must be unique in the key space and cannot be altered after being set. Field tagged as a key has a matching auto-generated fetch method. Keys are treated as Non-Clustered Index has the Unique Constraint on MySQL.

In the example below, Character’s Name is set as a key.

"Character": {
    "Name": "String Key"
}

12.2.2. Foreign

If a field is of object type (that is, type name is the name of other object), Foreign flag can be used. Without the flag, iFun Engine automatically tries cascaded reads that read whole fields and follow the fields of object types. This goes on and on. With the flag set, however, iFun Engine does not automatically trigger cascaded reads, but hold the target object’s UUID. This reduces unexpected database reads and allows you to control when to read the object using the UUID.

For example, suppose we have types named Character and Weapon like below:

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

In this example, when reading Character from the database, MyWeapon of the Weapon type will be automatically fetched.

If objects have ownership relationships, this automatic, cascaded reading can be convenient. Unless object solely belongs to another object, however, or if object is simply referred by another object, this cascaded reading can be problematic. In this case, Foreign can be set as the meaning of reference to another object.

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

In the example above, MyWeapon is not automatically fetched even when fetching Character. Instead, MyWeapon holds the UUID of the target object and you can read it from the database using the UUID.

Tip

For an array or a map referencing other objects, you can use prepend Foreign after an array definition of a map definition like Item[] Foreign or <Integer, Item> Foreign.

12.3. Object Model Definition Example

In the example below, we define Character and Item object models. We will assume these models in the following ORM-related chapters.

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)"
  }
}

We have complete User, Character, Item objects only with around 20 lines of JSON. iFun Engine will emit auto-generated classes for the models and it will transparently handle database accesses, caching, locking in a distributed environment, and multi-threading.