Recording

Commit volatile memory to persistent append-only log

0%

在 Stack Overflow 上回答了一个问题: How can I use an enum class in a boolean context?,记录下。

C++ 11 的 explicit operator bool() 可以让类类型的值使用在条件语句中,但无法对 enum 类型提供支持。

下面是我使用的一种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Error {
enum {
None = 0,
Error1 = 1,
Error2 = 2,
} Value;

/* implicit */ Error(decltype(Value) value = None) : Value(value) {}

explicit operator bool() {
return Value != None;
}
};

inline bool operator==(Error a, Error b) {
return a.Value == b.Value;
}

inline bool operator!=(Error a, Error b) {
return !(a==b);
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Error lastError();

if (auto err = lastError()) {
}

switch (lastError().Value) {
case Error::None:
break;
case Error::Error1:
break;
default:
break;
}

if (lastError() == Error::Error1) {
}
Read more »

每个全局功能(需要和其他玩家交互的功能,例如聊天,公会,副本和场景等)都作为一个独立服务来做,方便扩展。这个服务可以是逻辑服务器内嵌的(如果没有与其他服务器交互的需求),也可以是一个单独的服务器,甚至一个服务器集群。

在分区游戏中,很多时候某些全局功能都作为一个功能模块内嵌到逻辑上去了,这样在某些需要服务器间玩家交互的功能时,就会很难做。例如: 如果聊天/好友服务内嵌到逻辑服务器上时,玩家如果需要与其他服务器上的好友(在跨服功能中加为好友的)通信时就会很麻烦。

大多可能有很大压力的全局功能都采用服务器集群的做法,集群中的成员:

  • 一个或多个(分层的或平行的)管理服务器,职责:
    • 根据某种策略(id 哈希/映射,服务器压力统计等)路由外部请求到具体的工作服务器;
    • 工作服务器的新增(外部请求或根据工作强度向其他服务请求启动新的工作服务器);
    • 工作服务器的停机
  • 多个工作服务器,职责:
    • 根据请求 id,处理对应的模块实体;
    • 向管理服务器汇报服务器压力

实现:

gate

  • 多个 gate 管理服务器,外网接入:
    • 由 DNS 根据线路等指向或者固定 ip,client 接入时,会根据玩家的线路和 gate 的压力情况选择适合的 gate ip:port 返回给玩家;
    • gate 接入,外网 ip:port 注册
  • 多个 gate ,外网接入:
    • 玩家接入,认证等等;
    • 向 gate 管理服务器发送在线压力情况;
    • 和在线统计服务器和逻辑服务器合作,负责玩家的登入及之后的消息中转

登入

  • 一个或多个在线统计服务器:

    • 如果不存在玩家记录,根据策略(id 哈希,服务器压力等)选择一个逻辑服务器供玩家登录 [player: zzz, id: xxx, ref: yyy];
    • 如果存在玩家记录,直接返回其所在逻辑服务器 [player: zzz, id: xxx, ref: yyy+1](引用加1,防止逻辑服务器的错位删除)
    • 监控逻辑服务器的状态,逻辑服务器宕机后,标记其状态为宕机,避免玩家登入时再次接入已经宕机的逻辑服务器。
    • 做成无状态的服务,所有数据都存 Redis(这些数据都是在线玩家和所在逻辑服务器的映射,只需存在内存中),避免过多的逻辑,以及宕机后的自动恢复。 (服务与存储分开的关键是,数据能够原子的更新, Redis 的 Lua Script 都可以做到这点。当然,也可以写一个定制的内存服务程序来解决这点。)
  • 多个逻辑服务器:

    • 玩家个体逻辑的大部分功能都由该服务器实现 (如果玩家逻辑功能比较复杂,可以用动态语言+虚拟机方式实现,这样当某个玩家或某个功能出现异常时,缩小影响范围);
    • 在玩家登出后的有限时间内清空玩家数据,并向在线服务器(或 Redis)发送清空玩家对该服务器的引用[player: zzz, ref: yyy]

client 在 gate 完成登录认证,由 gate 向在线统计服务器发送请求选择逻辑服务器 [id: xxx, ref: yyy] , gate 向逻辑服务器发送玩家的登入请求 [player: zzz, ref: yyy] ,玩家进入游戏。

Read more »

Lua coroutine

Lua coroutine 是协作式多任务的一种实现。coroutine 经常被 C/C++ 这类没用原生轻量级多任务实现的宿主语言通过异步方式实现 Lua 层的同步调用。

这种同步调用的流程:

val, err = synchronous_call_in_lua(...)    -- function call in lua coroutine
    ==> coroutine.yield
        ==> C/C++ 发起异步请求会话
        ==| (time consumed)
        ==| C/C++ 异步会话完成(消息/回调)
    <== coroutine.resume
val, err = ... -- <== return from function
do_someting(...)

这个流程很像阻塞式的系统调用:

n = read(fd, buf, len)        // system call in userland
    ==> syscallenter(...)     // in kernel mode
        |=> sys_read(...)     // in system call
        |== kernel activity   // asynchronous wait if data is not available yet
        |==
    <== syscallret(...)       // return from syscall
n = ...                       // return from read in userland

C# async/await

async/await 是 C# 5 引入的,用来简化异步编程的设施,其本质是由编译器实施的 CPS 变换。

C# 5.0 introduces the async and await keywords. These keywords let you write asynchronous code that has the same structure and simplicity as synchronous code, as well as eliminating the “plumbing” of asynchronous programming. The await keyword simplifies the attaching of continuations.

即将如下代码:

Read more »

C++11 的 auto 和 decltype 可以做 “返回值类型推断”。

我写了个两参数版本的 std::accumulate

1
2
3
4
5
6
7
8
9
10
11
#include <numeric>
#include <type_traits>
namespace std {

template <typename InputIt>
auto accumulate(InputIt begin, InputIt end) -> typename std::remove_reference<decltype(*begin)>::type {
typedef typename std::remove_reference<decltype(*begin)>::type ValueType;
return accumulate(begin, end, ValueType());
}

}

配上点测试代码。

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
#include <vector>
#include <utility>
#include <iostream>
#include <iterator>
#include <initializer_list>

template <typename ValueType>
void test(std::vector<ValueType> const& v) {
std::cout << "=========================n";
std::copy(std::begin(v), std::end(v), std::ostream_iterator<ValueType>(std::cout, ", "));
std::cout << "n";

auto sum = std::accumulate(std::begin(v), std::end(v));
std::cout << "sum = " << sum << "n";
std::cout << "=========================nn";
}

template <typename ValueType>
void test(std::initializer_list<ValueType>&& values) {
std::vector<ValueType> v(std::forward<std::initializer_list<ValueType>>(values));
test(v);
}

int
main() {
test<int>(std::vector<int>{});
test<int>({1, 2, 3, 4});
test<int>(std::vector<int>{2,2,3});
test<double>({1.1, 1.2, 1.3});
test<std::string>({"aa", "bb", "cc", "dd"});
std::cout << "cpp : " << __cplusplus << std::endl;
}

EOF

Read more »

游戏中各种怪物都会有自己的掉落物品列表。

最原始的掉落列表设计可能就是,掉落金钱,掉落经验,掉落物品1(id + 数量),掉落物品2 …… 。我觉得这样的掉落表设计不够灵活,不能很好的对不同种类(分类?)的物品做不同的处理。

一种更好的掉落表可能是这样的:掉落分类1,掉落 id 1,掉落数量1,掉落分类2,掉落 id 2,掉落数量2,……

这样程序可以对不同的掉落分类进行不同的处理,并且 id 的含义可以局限于分类之下,例如:可以将“分类1”视为虚拟物品,“分类2”视为普通物品,“分类3”视为某种特殊物品,等等。

Read more »