14. ORM Part 3: 接口类

根据 ORM Part 2: 定义对象 的说明,定义 Object Model和建立的话iFun Engine的ORM自动形成Class。 这叫 接口类

假设项目的名称是 {{ProjectName}} , 使用JSON定义的对象模型名称是 {{ObjectName}} ,包括接口类自动形成的文件如下。

  • src/{{ProjectName}}_object.h

  • src/object_model/{{ProjectName}}_object.cc

  • src/object_model/common.h

  • src/object_model/{{ObjectName}}.h

  • mono/{{ProjectName}}_object.cs

Note

自动形成的文件不是在建立的目录保存,而是在资源目录下保存。这是为了像 Visual Studio 一样支持自动完成的IDE环境。

Warning

Interface class 只能在 活动线索 动作。不能在其他线索使用。

14.1. 接口类的方法

本部门的说明假设如下对象模型。

"ObjectName" : {
  "KeyAttribute1Name" : "KeyAttribute1Type Key",
  "KeyAttribute2Name" : "KeyAttribute2Type Key",
  "Attribute3Name" : "Attribute3Type",
  "Attribute4Name" : "Attribute4Type"
}

Note

KeyAttribute1Type, KeyAttribute2Type, Attribute3Type, Attribute4Type 意味着基本类型或者其他模型的名称。

Note

下面参数目录中参数后边的 ROLLBACK 意味着在调用该参数时有可能会发生ROLLBACK。 具体的内容请参考 事务回滚

14.1.1. 在数据库(DB)形成对象

产生对象的method形成 Create(…) 的名称。 如果存在由Key指定的属性的话,为了获得把Key属性作为参数的形式,产生代码。

static Ptr<ObjectName> Create(const KeyAttribute1Type &key1_value,
                              const KeyAttribute2Type &key2_value) ROLLBACK;
public static ObjectName Create(KeyAttribute1Type key1_value,
                                KeyAttribute2Type key2_value) ROLLBACK;

Important

当已有由Key指定的属性时,如果有一个已经具有该Key值的对象的话,则产生失败并返回NULL值。

下面的例子中 ch1 和 ch2 会正常产生,但是, 因为ch3 Key属性的Name值跟 ch1 一样, 所以产生失败并返回NULL。

1
2
3
4
5
6
Ptr<Character> ch1 = Character::Create("legend");
Ptr<Character> ch2 = Character::Create("killer");

// 因为 ch1 已经产生了以 "legend" 作为 Key 属性的 Name, 因此下面的 ch3 就返回NULL。
Ptr<Character> ch3 = Character::Create("legend");
BOOST_ASSERT(not ch3);
1
2
3
4
5
6
Character ch1 = Character.Create ("legend");
Character ch2 = Character.Create ("killer");

// 因为 ch1 已经产生了以 "legend" 作为 Key 属性的 Name, 因此下面的 ch3 就返回NULL。
Character ch3 = Character.Create ("legend");
Log.Assert (ch3 == null);

14.1.2. 在数据库读取对象

读取对象的方法(Method)是分成两个。 一个是 Fetch(…), 另一个是 FetchBy…(…) 。 前者是跟样式无关当对象ID已经知道的情况下读取对象,后者是在模型定义文件中使用由Key指定读取对象的情况。 并且,产生读取每个对象和通过 batch 工作读取多个对象情况的方法。

14.1.2.1. 使用对象 ID 读取一个对象

使用下面的 Fetch(…) 参数可以读取一个对象。 如果对象不存在的话,就返回null。

static Ptr<ObjectName> Fetch(
    const Object::Id &id,
    LockType lock_type = kWriteLock) ROLLBACK;
public static ObjectName Fetch(
    System.Guid object_id,
    funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;

14.1.2.2. 使用对象 ID 读取多个对象

使用下面的 Fetch(…) 参数可以一次读取多个对象。

如果对象不存在的话,在**result*上该ID对应的值成为null。

static void Fetch(
    const std::vector<Object::Id> &ids,
    std::vector<std::pair<Object::Id, Ptr<ObjectName> > > *result,
    LockType lock_type = kWriteLock) ROLLBACK;

如果对象不存在的话,在返回的Dictionary上该对象值成为null。

public static Dictionary<System.Guid, ObjectName> Fetch(
    SortedSet<System.Guid> object_ids,
    funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;

14.1.2.3. 使用Key属性读取一个对象

如果在对象模型的JSON里有由key指定的属性的话,产生如下方法(method)。 这样的话,能读取跟 Key 值一致的对象。 如果对象不存在的话,就返回 null。

下面是由 Key 指定的 KeyAttribute1Name 的情况下自动产生的方法。

static Ptr<ObjectName> FetchByKeyAttribute1Name(
    const KeyAttribute1Type &value,
    LockType lock_type = kWriteLock) ROLLBACK;
public static ObjectName FetchByKeyAttribute1Name(
    KeyAttribute1Type value,
    funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;

14.1.2.4. 使用Key属性读取多个对象。

跟上面的参数一样,但是可以一次读取多个对象。

如果对象不存在的话,在**result*上该Key对应的值成为null。

static void FetchByKeyAttribute1Name(
    const std::vector<KeyAttribute1Type> &values,
    std::vector<std::pair<KeyAttribute1Type, Ptr<ObjectName> > > *result,
    LockType lock_type = kWriteLock) ROLLBACK;

如果对象不存在的话, 在返回的Dictionary上该对象值成为null。

public static Dictionary<KeyAttribute1Type, ObjectName> FetchByKeyAttribute1Name(
    SortedSet<KeyAttribute1Type> values,
    funapi.LockType lock_type = funapi.LockType.kWriteLock) ROLLBACK;

14.1.2.5. Fetch 的时候对象 cache

iFun Engine 的 ORM 为了提高性能,在 cache 储存对象和再使用。 考虑到 Cache,ORM 读取对象的循序如下。

  1. 在ORM 管理的 cache储存器里有对象的话,从cache读取。

  2. 在其他服务器 ORM cache上有对象的话,会发送 RPC 后,读取对象。

  3. 对象没有在任何服务器的 cache里的话,从数据库读取后保存到 ORM cache。

  4. 在数据库也不存在的对象的话,就返回NULL。

Note

想理解 iFun Engine 什么时候在 cache 里删除对象的话,请您参考 贮藏数据

14.1.2.6. 当 Fetch 时 LockType

LockType 如下3种类型。

  • kWriteLock: 修改对象属性的一部分或者删除对象的时候使用。 锁定的时候,其他地方不能读取和使用对象。

  • kReadLock: 只能读取对象值的时候使用。 锁定的时候,其他地方可以读取该对象,但不能使用。

  • kReadCopyNoLock: 不是锁定而是创建副本后工作。因为在副本进行写作没有意义,因此副本是只阅读专用和不能使用写作。其他地方可以对该对象进行写作。

    在一秒之内短时间对象被改变也不会出现问题的情况下很有效率。 举个例子,表示朋友最后登入时间的话,锁定每个朋友对象不如使用副本。虽然使用副本储存器使用量会多,但是可以免于锁定产生的串行化。

Important

虽然 kWriteLock 是基本值,为了提高性能我们建议必要的情况下采用,其他情况下最好使用, kReadLock 或者 kReadCopyNoLock

例子) 通过 Key 属性,使用 Name 读取:

Ptr<Character> ch = Character::FetchByName("legend")
Character ch = Character.FetchByName("legend");

例子) 使用 Key 属性,但是为了一次读取多个对象和提高性能使用 kReadCopyNoLock :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
std::vector<string> names;
names.push_back("name1");
names.push_back("name2");
names.push_back("name3");

std::vector<std::pair<string, Ptr<Character> > > object_pairs;
Character::FetchByName(names, &object_pairs, kReadCopyNoLock);

for (size_t i = 0; i < object_pairs.size(); ++i) {
  const string &name = object_pairs[i].first;
  const Ptr<Character> &ch = object_pairs[i].second;
  if (ch) {
    ...
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
SortedSet<string> names = new SortedSet<string>();
names.Add("name1");
names.Add("name2");
names.Add("name3");

Dictionary<string, Character> object_dict = null;
object_dict = Character.FetchByName(
    names, funapi.LockType.kReadCopyNoLock);

foreach(KeyValuePair<string, Character> object_pair in object_dict)
{
  if (object_pair.Value) {
    ...
  }
}

Tip

对使用锁定改进性能的具体说明, 请您参考 使用正确地锁定类型

14.1.3. 在数据库删除对象

在数据库删除对象使用如下方法。

void Delete();
public void Delete();

如果, 删除的对象把其他对象当作属性的时候,属性的其他对象也一起被删除。 但是,根据在 Attribute 的 Flag 说明, 指定 Foreign 的话,不是所有关系,而不会一起被删除。

C++ 活着 C#的语法上,删除对象也不能自动把包含对象的变数作为 null。 具体内容请您参考如下 检查 NULL

14.1.4. 检查 NULL

  1. 对象一直 Ptr<ObjectName> 的形式来使用。 通过跟 ObjectName::kNullPtr 或者 Ptr<ObjectName>() 比较,可以判断 Ptr<ObjectName> 是否 NULL。

  2. 如果 Ptr<ObjectName> 不是 ObjectName::kNullPtr 的话, 这意味着该指针有效。 但是刚才在数据库删除对象的话,发生指针有效但对象不存在的情况。 通过如下方法可以确认对象是否存在。

bool IsNull() const;
  1. 对象总是写成 ObjectName 类型的变数形式。 使用 C# 的 null 可以确认该变数是否 null 。

  2. ObjectName 的变量不是 null 的话, 这意味着该指针有效。 但是刚才在数据库删除对象的话,发生指针有效但对象不存在的情况。 通过如下方法可以确认对象是否存在。

public bool IsNull();

例子

1
2
3
4
5
6
7
Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
  return;
}

ch->Delete();
BOOST_ASSERT(ch->IsNull());
1
2
3
4
5
6
7
Character ch = Character.FetchByName("legend");
if (ch == null) {
  return;
}

ch.Delete ();
Log.Assert (ch.IsNull());

14.1.5. 返回对象ID

所有对象都有固有 ID 值。 这 ID 的形式是 UUID,可以获得如下。

const Object::Id &Id() const;
public System.Guid Id;

因为所有对象都有固有ID, 对象模型没有 Key 属性也通过默认的 Fetch(...) 从数据库可以读取对象。

14.1.6. 读取/写作对象属性

凭属性产生 getter 和 setter。 Getter 的话,在属性前边有 Get 并有该属性的类型为返回值。 Setter 的话,在属性前边有 Set 并有该属性的类型为因数。

下面是 AttributeName3 的情况。

Attribute3Type GetAttribute3Name() const;
void SetAttribute3Name(const Attribute3Type &value);
public GetAttribute3Type GetAttribute3Name();
public void SetGetAttribute3Name(Attribute3Type value);

例子) 如果经验值为100以上的话,升级后经验值进行初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Ptr<Character> ch = Character::FetchByName("legend");
if (not ch) {
  return;
}

string char_name = ch->GetName();

if (ch->GetExp() > 100) {
  ch->SetLevel(ch->GetLevel() + 1);
  ch->SetExp(0);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Character ch = Character.FetchByName ("legend");
if (ch == null) {
  return;
}

string char_name = ch.GetName ();

if (ch.GetExp () > 100) {
  ch.SetLevel (ch.GetLevel() + 1);
  ch.SetExp (0);
}

Note

如果变更对象的属性,变更的内容在 ORM cache 和数据库上都会反映。如果该对象是从其他服务器 ORM cache 读取的对象的话,该服务器的 ORM cache 会更新。

14.1.6.1. 没有定义为 Foreign 的数组或者Map

没有指定为 Foreign 的数组和Map产生如下 Getter。

  • Get{{AttributeName}}(): Fetch 所有元素对象后 返回 ArrayRef<Ptr<{ObjectType}> > 或者 MapRef<{KeyType}, Ptr<{ObjectType}> >

14.1.6.2. 定义为 Foreign 的数组或者Map

指定为 Foreign 的数组或者 Map 产生如下两种类型。

  • Get{{AttributeName}(): 不 Fetch 元素对象把对象 ID 值

返回为 ArrayRef<Object::Id>, MapRef<{KeyType}, Object::Id>

  • Fetch{AttributeName}(): Fetch 所有元素对象返回 ArrayRef<Ptr<{{ObjectType}}> > 或者 MapRef<{{KeyType}}, Ptr<{{ObjectType}}> >

14.1.7. 保存的对象 refresh

iFun Engine 的 ORM 不是在多种服务器环境下通过数据库分享数据,而是 使用 RPC 读取在远程服务器 caching 的对象,通过这种方式会减少数据库的负荷并分享服务器之间的数据

因此, 通过使用 ORM的 Create(...) 来产生或者使用 Fetch(...) / FetchBy...(...) 来调用的对象只能在事件处理程序里有效。则,在事件处理程序里可以保证该对象的使用,但是离开事件处理程序范围,从外部可以访问该对象。于是,iFun Engine 建议产生和访问对象的时候,记住对象 ID 或者 key属性 在事件处理程序里作 Create(…) / Fetch(…) / Fetch…(…)

但是按照情况有可能会把对象存在全程变量后从多个地方访问。在这种情况下使用如下两个方法可以 refresh 对象。

bool IsFresh() const;

bool Refresh() ROLLBACK;
public bool IsFresh();

public bool Refresh(funapi.LockType lock_type) ROLLBACK;

IsFresh() 不用 Refresh 该对象可以访问的话就返回 true 。如果 IsFresh() 参数返回 false 的话, 一定调用 Refresh() 参数后使用。

例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
std::map<string /*account id*/, Ptr<Character> > g_characters;

void OnLevelRequested(const Ptr<Session> &session, const Json &message) {
  string account_id = message["account_id"].GetString();

  Ptr<Character> ch = g_characters.find(account_id);

  // 先确认这对象能否在本 Message Handler 访问后,不能访问的话,调用 Refresh()。
  if (not ch->IsFresh()) {
    ch->Refresh();
  }

  Json response;
  response["level"] = ch->GetLevel();

  session->SendMessage("level", response);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
Dictionary<string, Character> the_characters;

public void OnLevelRequested(Session session, JObject message) {
  string account_id = (string) message ["account_id"];

  Character ch = null;
  if (the_characters.TryGetValue (account_id, out ch))
  {
    return;
  }

  // 先确认这对象能否在本 Message Handler 访问后,不能访问的话,调用 Refresh()。
  if (!ch.IsFresh ())
  {
    ch.Refresh (funapi.LockType.kWriteLock);
  }

  JObject response = new JObject ();
  response ["level"] = ch.GetLevel ();

  session.SendMessage ("level", response);
}

14.1.8. 使用JSON 对象进行初始化

为了对象初始化,可使用个别属性提供的 setter 但是,本方法是在有很多属性情况下很麻烦。 因此,iFun Engine 的 ORM 使用 JSON 产生一次对象初始化的方法。

Tip

通过本方法,可以简便地进行用户初始化或者新道具初始化的工作。

而且,从外部 JSON 抵用初始化的数据的话,策划人员只要修改 JSON 就可以变更默认值。这样的话工程师不用写死的工作。 对于管理策划数据的 JSON 文件方法, 请您参考 内容支持Part4: 策划数据

struct OpaqueData;
static Ptr<OpaqueData> CreateOpaqueDataFromJson(const Json &json);

bool PopulateFrom(const Ptr &opaque_data);

Note

将来会支持。

为了使用 JSON 进行初始化的时候需要如下两个阶段。

  1. 使用 CreateOpaqueDataFromJson(...) 参数,把 JSON 数据创建为该对象的 OpaqueData

  2. 使用 PopulateFrom(opaque_data) 在对象输入创建的 OpaqueData

JSON跟着该对象模型的属性结构。 则,如 {"属性名称": "值"} 即可。 如果这时候属性不是基本类型的话,输入如下即可。

  • 如果属性为 Array 的话,使用 JSON Array 输入其值。

  • 如果属性为 Array 的话,使用 JSON Object 输入其值。

  • 如果属性为其他模型的话,使用 JSON Object 输入其值。

    Important

    如果属性为其他模型和该模型有 Key 属性的话,不能使用这种方法。

    各别对象的 Key 一定会固有。但是,通过 JSON 对象初始化的时候,相当于创建有同样 Key 值的对象。

例子: 按职业人物初始化

character_init_data.json

{
  "Warrior": {
    "Level": 1,
    "Exp": 0,
    "Hp": 1000,
    "Mp": 100
  },
  "Wizard": {
    "Level": 1,
    "Exp": 0,
    "Hp": 100,
    "Mp": 1000
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void OnCreateCharacter(const Ptr<Session> &session, const Json &message) {
  // 假设以 message 的 character_name 形式发送人物的 Name。
  string character_name = message["character_name"].GetString();

  // 1. 首先创建人物 object。
  Ptr<Character> ch = Character::Create(character_name);

  if (ch) {
    Ptr<const Json> json_data = ResourceManager::GetJsonData("character_init_data.json");
    BOOST_ASSERT(json_data);

    // 属于在 JSON 数据的战士人物进行初始化为 "Warrior" 。



    // 如果是魔法师的话,把 "Warrior" 换成 "Wizard" 即可。
    // 1. 使用 JSON 创建 OpaqueData 。
    Ptr<Character::OpaqueData> opaque_data =
      Character::CreateOpaqueDataFromJson((*json_data)["Warrior"]);
    BOOST_ASSERT(opaque_data);

    // 2. 在新创建的 Character Object 里输入 OpaqueData 后,进行初始化。
    bool success = ch->PopulateFrom(opaque_data);
    BOOST_ASSERT(success);
  }
}

Note

将来会支持。

14.2. 数组 & 映射

我们已经解释了在 读取/写作对象属性 对各别属性产生 getter 和 setter 。 在基本类型的情况下,getter 就返回基本类型值,setter 也收到基本类型值。 但是,如果数组和映射类型的话,返回数组和映射类型的对象,通过该对象的方法,管理数组和映射。

14.2.1. 数组 : ArrayRef<Type>

我们还解释了使用 JSON 模型定义文件指定属性的时候,如 Type[ ] 添加 [ ] 就成为数组。 这样定义的数组的属性以 ArrayRef<Type> 形式产生。

比如说,String[] 成为 ArrayRef<string>Character[] 成为 ArrayRef< Ptr<Character> >

ArrayRef<T> 有如下组员。

14.2.1.1. 返回大小

返回在数组保存的 Element 的数。

size_t Size() const
UInt64 Length

14.2.1.2. 确认元素是否在特定位置

确认元素是否在特定位置。 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。

bool Has(size_t index) const
bool Has(ulong index)

14.2.1.3. 读取特定位置的元素

获得在数组 index 保存的 element 。 index 是当[0, SIZE - 1]时有效。

T GetAt(size_t index) const
T GetAt(ulong index)

14.2.1.4. 在特定位置写元素

在数组的 index 位置写值 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。

void SetAt(size_t index, const T &value)
void SetAt(ulong index, T value)

14.2.1.5. 在特定位置添加元素

在数组 添加 index 值。 结果增加数字的长度。 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。

Important

如果数组中间发生 InsertAt(...) 的话, 会重整数组,这时发生负荷。

void InsertAt(size_t index, const T &value)
void InsertAt(ulong index, T value)

14.2.1.6. 删除特定位置的元素

删除数组的 index 元素和减少数组。 index 是当[0, SIZE - 1]时有效。 如果 index 不可用的话,留 FATAL 日志后把服务器会关闭。

元素是其他 object 的时候,如果 delete_objecttrue 的话, 调用元素 object 的 Delete() 参数后一起删除。

Important

如果数组中间发生 EraseAt(...) 的话, 会重整数组,这时发生负荷。

void EraseAt(size_t index, bool delete_object = true)
void EraseAt(ulong index, bool delete_object = true)

14.2.1.7. 全数组空出

删除所有 element 后把数组的 SIZE 作为 0 。

元素是其他 object 的时候,如果 delete_objecttrue 的话, 调用元素 object 的 Delete() 参数后一起删除。

void Clear(bool delete_object = true);
void Clear(bool delete_object = true)

14.2.1.8. (方便功能)返回数组的首次元素

获得首次 element。如 GetAt(0)

T Front() const;
T Front()

14.2.1.9. (方便功能)返回数组的最后元素

获得最后 element。 如 GetAt(SIZE - 1)

T Back() const;
T Back()

14.2.1.10. (方便功能)在数组首次添加元素

在首次添加元素。增加数组的大小。如 InsertAt(0, ...)

void PushFront(const T &value);
void PushFront(T value)

14.2.1.11. (方便功能)在数组最后添加元素

在最后添加元素。增加数组的大小。如 InsertAt(SIZE, ...)

void PushBack(const T &value);
void PushBack(T value)

14.2.1.12. (方便功能)在数组寻找首次空的插槽。

返回首次寻找 slot 的 index 。如果 slot 空的话, 返回 -1 。

Important

如果 Array 的 value 设置为如下资料性和默认值的时候,被认识空的 slot 。

资料性

默认值

Bool

false

Integer

0

Double

0.0

String

“”

User-Defined Object

null

int64_t FindFirstEmptySlot() const;
long FindFirstEmptySlot()

14.2.2. Map: MapRef<KeyType, ValueType>

我们已解释了使用 JSON 模型定义文件指定属性类型的时候,指定如 <KeyType, ValueType> 成为 Map 形式。

Map 的话,在 Model 定义为 <KeyType, ValueType>KeyType 会成为 Bool, Integer, Double, String(n) 的基本类型。ValueType 除了这些外,还可以是对象类型(即另一个对象模型)。由此定义的映射属性以 MapRef<KeyType, ValueType> 的形式创建。

比如说,定于 <Integer, String> 的话, 是 MapRef<int64_t, string> , 定义 <String, Character>MapRef<string, Ptr<Character> >

MapRef<KeyType, ValueType> 有如下组员。

14.2.2.1. 确认在特定 key 是否有元素

确认在 Map 该 Key 是否存在。

bool Has(const KeyType &key) const
bool Has(KeyType key)

14.2.2.2. 读取在特定 key 的元素

Map 의 해당 key 에 저장된 element 를 얻습니다. 만약 key 가 유효하지 않으면 LOG 와 함께 Crash 합니다. 获取在 map 的该 Key 保存的 element。 如果 key 不可用的话, LOG也会冲突。

ValueType GetAt(const KeyType &key) const
ValueType GetAt(KeyType key)

14.2.2.3. 在特定 key 插入元素

在 Map 上变更该 key 值。 如果其 key 没有 element 的话,要添加。

void SetAt(const KeyType &key, const ValueType &value)
void SetAt(KeyType key, ValueType value)

14.2.2.4. 특정 키의 원소 삭제 删除特定 key 的元素

在 Map 上删除该 key 。 value 是对象形式的时候,delete_objecttrue 的话, 调用该 Object 的 Delete() 参数后删除。 如果 key 不存在的话,会返回 false 。

bool EraseAt(const KeyType &key, bool delete_object = true)
bool EraseAt(KeyType key, bool delete_object = true)

14.2.2.5. 清空全 MAP

删除全 element,把 Map 的 SIZE 作为 0 。

value 是对象形式的时候, delete_objecttrue 的话, 调用该 Object 的 Delete() 参数后删除

void Clear(bool delete_object = true)
void Clear(bool delete_object = true)

14.2.2.6. 返回全 Key 值

返回在 Map 上的所有的 Key。

Tip

读取全 Key 的工作负荷会很重。 因此,我们建议每逢使用调用参数不如保存已经调用的值后再使用。

std::vector<KeyType> Keys() const
SortedSet<TKey> Keys

14.2.2.7. 사이즈 반환

Map 저장된 Element 의 수를 반환합니다.

size_t Size() const
UInt64 Length

Note

1.0.0-2885 Experimental 이상 버전에서만 사용할 수 있습니다.

14.2.2.8. (方便功能)在整数 Map 上寻找首次空的插槽

Important

这是 KeyType 为 Integer 的时候,才可以用。

从0起增加每1个增加。 如果不包括 map 上的 key 的话,就返回其 key 值。

int64_t FindFirstEmptySlot() const
long FindFirstEmptySlot()

14.3. 接口类使用例子

14.3.1. 仓库初始化

下面的代码创建 Character 和 10个间的 Inventory。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void CreateCharacter() {
  Ptr<Character> ch = Character::Create("legend");
  if (not ch) {
    return;
  }

  ArrayRef<Ptr<Item> > inventory = ch->GetInventory();

  // 在 Inventory 创建10个间的空插槽。
  for (size_t i = 0; i < 10; ++i) {
    // Item::kNullPtr 是 NULL 值为 Ptr<Item> 的常数。
    inventory.PushBack(Item::kNullPtr);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public void CreateCharacter()
{
  Character ch = Character.Create ("legend");
  if (ch == null)
  {
    return;
  }

  ArrayRef<Item> inventory = ch.GetInventory ();

  // 在 Inventory 创建10个间的空插槽。
  for (int i = 0; i < 10; ++i)
  {
    inventory.PushBack(null);
  }
}

14.3.2. 在 Inventory 发送道具

下面的代码调用 Character 后在 Inventory 的 3号插槽创建新道具和发送。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void AddItem() {
  Ptr<Character> ch = Character::FetchByName("legend");
  if (not ch) {
    return;
  }

  ArrayRef<Ptr<Item> > inventory = ch->GetInventory();

  Ptr<Item> item = Item::Create();
  inventory.SetAt(3, item);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public void AddItem()
{
  Character ch = Character.FetchByName("legend");
  if (ch == null)
  {
    return;
  }

  ArrayRef<Item> inventory = ch.GetInventory();

  Item item = Item.Create();
  inventory.SetAt(3, item);
}

14.3.3. 在仓库中删除道具

下面的代码调用 Character 后删除在 Inventory 中的7号插槽和消除道具。 如果想把道具移动到其他插槽或者其他人物的话,要扣删除的代码。

Important

在仓库里删除道具不是使用 EraseAt(index) 而是使用 SetAt(index, null) 。 前者是把数组长度删除1,因此相当于删除仓库的插槽。 所以在下面例子表示在7号插槽里使用 SetAt(index, null) 设置 null。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void DeleteItem() {
  Ptr<Character> ch = Character::FetchByName("legend");
  if (not ch) {
    return;
  }

  ArrayRef<Ptr<Item> > inventory = ch->GetInventory();

  Ptr<Item> item = inventory.GetAt(7);
  if (item) {
    inventory.SetAt(7, Item::kNullPtr);
    item->Delete();
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public void DeleteItem()
{
  Character ch = Character.FetchByName ("legend");
  if (ch == null)
  {
    return;
  }

  ArrayRef<Item> inventory = ch.GetInventory ();

  Item item = inventory.GetAt (7);
  if (item != null)
  {
    inventory.SetAt (7, null);
    item.Delete ();
  }
}

14.3.4. 空的仓库插槽和发送道具

下面的代码调用 Character 在仓库中首次找到空的插槽里发送道具。 如果没有空的插槽的话, 增加仓库后最后仓库里发送道具。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void AddItem() {
  Ptr<Character> ch = Character::FetchByName("legend");
  if (not ch) {
    return;
  }

  Ptr<Item> item = Item::Create();

  ArrayRef<Ptr<Item> > inventory = ch->GetInventory();

  int64_t empty_slot_index = inventory.FindFirstEmptySlot();
  if (empty_slot_index == -1) {
    inventory.PushBack(item);
  } else {
    inventory.SetAt(empty_slot_index, item);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public void AddItem()
{
  Character ch = Character.FetchByName ("legend");
  if (ch == null)
  {
    return;
  }

  Item item = Item.Create();

  ArrayRef<Item> inventory = ch.GetInventory();

  long empty_slot_index = inventory.FindFirstEmptySlot();
  if (empty_slot_index == -1) {
    inventory.PushBack(item);
  }
  else
  {
    inventory.SetAt(empty_slot_index, item);
  }
}

14.3.5. 创建装备道具的插槽

下面代码先创建 Character 然后再创建在头,胸,右手,左手装备道具的插槽。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void CreateCharacter() {
  Ptr<Character> ch = Character::Create("legend");
  if (not ch) {
    return;
  }

  MapRef<string, Ptr<Item> > equips = ch->GetEquippedItem();

  equips.SetAt("head", Item::kNullPtr);
  equips.SetAt("chest", Item::kNullPtr);
  equips.SetAt("righthand", Item::kNullPtr);
  equips.SetAt("lefthand", Item::kNullPtr);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void CreateCharacter()
{
  Character ch = Character.Create ("legend");
  if (ch == null)
  {
    return;
  }

  MapRef<string, Item> equips = ch.GetEquippedItem();

  equips.SetAt ("head", null);
  equips.SetAt ("chest", null);
  equips.SetAt ("righthand", null);
  equips.SetAt ("lefthand", null);
}

14.3.6. 装备道具

下面代码调用 Character 把在 Inventory 里5号插槽的道具装备到右手。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void EquipWithItem() {
  Ptr<Character> ch = Character::FetchByName("legend");
  if (not ch) {
    return;
  }

  ArrayRef<Ptr<Item> > inventory = ch->GetInventory();
  MapRef<string, Ptr<Item> > equips = ch->GetEquippedItem();

  if (inventory.Has(5)) {
    Ptr<Item> item = inventory.GetAt(5);
    inventory.SetAt(5, Item::kNullPtr);
    equips.SetAt("righthand", item);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public void EquipWithItem()
{
  Character ch = Character.FetchByName ("legend");
  if (ch == null)
  {
    return;
  }

  ArrayRef<Item> inventory = ch.GetInventory ();
  MapRef<string, Item> equips = ch.GetEquippedItem ();

  if (inventory.Has (5))
  {
    Item item = inventory.GetAt (5);
    inventory.SetAt (5, null);
    equips.SetAt ("righthand", item);
  }
}

14.3.7. 在空的快槽里装备道具

下面代码创建 Character 后在 QuickSlot 上找到的首次空的插槽里装备道具。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void CreateCharacter() {
  Ptr<Character> ch = Character::Create("legend");
  if (not ch) {
    return;
  }

  Ptr<Item> item = Item::Create();

  MapRef<int64_t, Ptr<Item> > quick_slot = ch->GetQuickSlot();

  int64_t empty_slot_index = quick_slot.FindFirstEmptySlot();
  quick_slot.SetAt(empty_slot_index, item);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public void CreateCharacter()
{
  Character ch = Character.Create ("legend");
  if (ch == null)
  {
    return;
  }

  Item item = Item.Create();

  MapRef<ulong, Item > quick_slot = ch.GetQuickSlot ();

  long empty_slot_index = quick_slot.FindFirstEmptySlot();
  quick_slot.SetAt(empty_slot_index, item);
}

14.3.8. 创建人物

下面是收到创建 Character 数据包后,产生 Character Object 的例子。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void OnCreateCharacter(const Ptr<Session> &session, const Json &message) {
  string character_name = message["character_name"].GetString();
  LOG(INFO) << "chracter name : " << character_name;

  Ptr<Character> ch = Character::Create(character_name);

  Json response;

  if (ch) {
    // 人物创建完了。
    // 输入基本人物信息后,在仓库添加新手用刀。
    response["result"] = true;
    ch->SetLevel(1);
    ch->SetHp(100);
    ch->SetMp(100);
    ch->SetExp(0);

    Ptr<Item> novice_sword = Item::Create();

    Ptr<Item> hp_potion = Item::Create();

    // 在 Inventory 上首次插槽里发送新手用刀。
    ch->GetInventory().InsertAt(0, novice_sword);

    // 把新手用装备到 "右手" 。
    ch->GetEquippedItem().SetAt("RightHand", novice_sword);

    // 注意) 在 Inventory 和 EquippedItem 里各个输入 novice_sword
    // 这两个意味着同样的对象。(不是复制)

    // 在快槽首次曹操里添加 hp 药水。
    ch->GetQuickSlot().SetAt(0, hp_potion);
  } else {
    // 因已经有同样名称的人物而没有创建。
    response["result"] = false;
    response["reason"] = character_name + " already exists";
  }

  session->SendMessage("create_character", response);
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public void OnCreateCharacter(Session session, JObject message) {
  string character_name = (string) message["character_name"];
  Log.Info("chracter name = {0}", character_name);

  Character ch = Chracter.Create(character_name);
  JObject response = new JObject ();

  if (ch != null) {
    // 创建人物
    // 设置任务,在仓库添加新手用刀。
    response ["result"] = true;
    ch.SetLevel (1);
    ch.SetHp (100);
    ch.SetMp (100);
    ch.SetExp (0);

    Item novice_sword = Item.Create ();

    Item hp_potion = Item.Create ();

    // 在 Inventory 上首次插槽里发送新手用刀。
    ch.GetInventory().InsertAt (0, novice_sword);

    // 把新手用装备到 "右手" 。
    ch.GetEquippedItem().SetAt ("RightHand", novice_sword);

    // 注意) 在 Inventory 和 EquippedItem 里各个输入 novice_sword
    // 这两个意味着同样的对象。(不是复制)

    // 在快槽首次曹操里添加 hp 药水。
    ch.GetQuickSlot().SetAt (0, hp_potion);
  }
  else
  {
    //  因已经有同样名称的人物而没有创建。
    response ["result"] = false;
    response ["reason"] = character_name + " already exists";
  }

  session.SendMessage ("create_character", response);
}

14.3.9. 增加人物经验值和级别

人物的经验值增加 100 ,如果经验值超过 3000 的话,会升级。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void OnKill(const Ptr<Session> &session, const Json &msg) {
  string id = msg["UserId"].GetString();
  string ch_name = msg["CharacterName"].GetString();

  Ptr<User> user = User::FetchById(id);
  if (not user) {
    return;
  }

  Ptr<Character> ch = user->GetMyCharacter();
  if (not ch) {
    return;
  }

  ch->SetExp(ch->GetExp() + 100);
  if (ch->GetExp() >= 3000) {
    ch->SetLevel(ch->GetLevel() + 1);
    ch->SetExp(ch->GetExp() - 3000);
  }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void OnKill(Session session, JObject message)
{
  string id = (string) message ["UserId"];
  string ch_name = (string) message ["CharacterName"];

  User user = User.FetchById (id);
  if (user == null)
  {
    return;
  }

  Character ch = user.GetMyCharacter ();
  if (ch == null)
  {
    return;
  }

  ch.SetExp (ch.GetExp () + 100);
  if (ch.GetExp () >= 3000)
  {
    ch.SetLevel (ch.GetLevel () + 1);
    ch.SetExp (ch.GetExp () - 3000);
  }
}