看到QQ群里有同学问到国内LIB中的set_default_object有什么用,这是个好问题,有很多同学对它都不太清楚,在这里针对这个问题做一个关于MUD开发内存优化方面的介绍。
MUD所有载入游戏的对象都会占用内存,虽然现在服务器性能对多数MUD来说不是问题了,但也不用太任性,对该优化的还是要优化。
在内存优化上,主要是两方面:一是优化游戏中对象的数量,二是优化对象本身的内存占用。
在优化游戏中对象的数量上,主要思路是延迟加载对象,减少非必要对象的加载,比如打怪掉装备,我们不需要把装备放到怪物身上,而是在怪物被消灭时才载入要掉的装备。相关优化教程:
在优化对象本身内存占用上,主要思路是共享全局变量,减少非必要变量占用内存,比如游戏物品固定属性,在复制对象上不设定,而是从蓝图对象(原始对象)上读取。
标题中 set_default_object 就是共享全局变量的一种方式,比如以下代码:
#include <weapon.h>
#include <ansi.h>
inherit HAMMER;
void create()
{
set_name(HIG "碧绿" HIR "西瓜" NOR, ({ "bilv xigua", "bilv", "xigua", "hammer" }));
set_weight(35000);
if (clonep())
set_default_object(__FILE__);
else {
set("unit", "对");
set("long", HIG "这西瓜由精钢铸成,瓜上漆成绿黑间条之"
"色,共有一对,系以钢链。\n" NOR);
set("value", 3000);
set("material", "iron");
set("wield_msg", "$N拿出一对$n,试了试重量,然後握在手中。\n");
set("unwield_msg", "$N放下手中的$n。\n");
}
init_hammer(80);
setup();
}
代码很简单,如果是复制对象就没有下面一堆set(...),而是只有一个set_default_object(__FILE__)。
set_default_object 不是驱动提供的efun,而是LIB自己的优化方案,相关方法在游戏存档相关的F_DBASE文件中:
mapping dbase;
nosave mapping tmp_dbase;
// The default_ob provides the default values of the dbase. It is set to
// be master copy of an object.
nosave mixed default_ob;
mixed query_default_object() { return default_ob; }
void set_default_object(mixed ob)
{
if (!geteuid())
seteuid(getuid());
if (stringp(ob))
ob = get_object(ob);
if (objectp(ob))
{
default_ob = ob;
ob->set("no_clean_up", 1);
}
}
这里代码核心只有一句default_ob = ob;,设置复制对象的default_ob为原始对象。然后在query()方法中有以下关键的一句:
if (undefinedp(data) && default_ob)
data = default_ob->query(prop, 1);
就是如果在对象中查不到,但对象有设置default_ob,就从default_ob中查询,这样就实现了通过default_ob共享变量。
在游戏中可以通过memory_info()这个efun查看内存占用,大家可以自己测试效果,对比复制对象使用set_default_object(__FILE__)前后的内存占用。

使用共享变量,原始对象内存占用2772,复制对象占用1503。

不使用共享变量,原始对象内存占用2740,复制对象占用2292。
对游戏中数量很大的对象,使用内存优化的效果会更明显。因为使用 set_default_object 会多一次查询,对CPU占用会高一些,但基本没有什么影响。另外,如果你需要每次实列化新对象时设置随机值,不能存在default_object中,具体看以下案例:
为什么以下代码的装备随机属性不随机了?
有网友留言问我:“这件装备防御和命中是随机的,我在游戏里多次获得的时候,它们的属性都是相同的,这个是什么原因呢?而当我重启驱动后,再次获得这件装备,属性会变化,但是再多次获得又还是一样的属性。”
原因很简单,这里只在对象第一次实例化时设置了随机属性存在default_object中,后面每次调用都是读default_object的属性,所以不是每次都随机,如果要随机,你需要把随机相关的代码放在if (clonep())的语句块中:
if (clonep()) {
set_default_object(__FILE__);
// 设置随机属性
}
提示:游戏中常见的efun replace_program() 也是对象内存优化的一种。
记录一个set_default_ob相关的问题,游戏运行一段时间后玩家身上的物品单位会变为0,类似:
你身上带著下列这些东西(负重 2%):
九十九两白银(Silver)
二0包子(baozi)
出现这个情况就是因为物品关联的default_ob被destruct了,最直接的情况就是管理员在游戏中update了原始对象文件或updateall /更新了所有文件。这种因为管理员强制更新造成已有物品单位变为0是正常情况,但在玩家正常的游戏中是不应该出现这个情况的。
炎黄做为一个开源且运行这么久的MUD,按理说不会有这个问题,至少运行这么多年也没发现问题。最近有朋友反馈自己用炎黄架的站上出现了这个问题,总有物品莫名其妙的丢失了default_ob,偏偏不知道怎么重现这个问题,而且我自己也没测试出来问题,直到最近我的游戏中也有出现后,做了排查才发现问题所在。
对default_ob,默认是no_clean_up为1的,而no_clean_up为1的对象不会被自动清除,但是在feature/move.c中有一段对被清除对象重设no_clean_up值的代码:
if (default_ob = me->query_default_object())
default_ob->add("no_clean_up", -1);
这就可能造成default_ob被设置为可清除后其它玩家身上的对象丢失default_ob的问题,这个问题在只有一个玩家或有很多玩家时都发现不了,只有玩家很少时才能出现,这也造成游戏运行这么多年都没有玩家反馈。
典型的情况:游戏中只有2个玩家A和B,玩家A和B都有物品X,玩家A使用后X被清除,触发feature/move中的remove方法把物品X的default_ob设置为可清除,因为玩家少,A和B后面都没有获得新的物品X(当有新获得时default_ob会被重新设置为no_clean_up),造成clean_up时间到时清除了default_ob,然后玩家B身上的X丢失default_ob而出现问题。
最简单的解决办法:移除上面这段代码,或者判断default_ob是否还存在复制对象,对游戏中不存在复制对象的default_ob设置为可清除。
开发MUD的朋友都用过replace_program(ROOM);,这也是一种内存优化方案,但和set_default_object(__FILE__);是一种完全相反的方案,适用不同的场景。对比如下:
核心区别概览
| 特性 | replace_program() | set_default_object() |
|---|---|---|
| 设计哲学 | 运行时程序替换 | 模板数据共享 |
| 内存优化 | 代码段共享 | 数据段共享 |
| 使用场景 | 功能降级优化 | 批量实例化优化 |
| 函数类别 | efun | lfun |
详细对比
- 实现机制对比
void create() {
// set_default_object - 模板数据共享
if (clonep()) {
set_default_object(__FILE__); // 使用模板对象的变量
} else {
set("name", "熔火之心"); // 模板变量定义
}
// replace_program - 运行时替换程序
replace_program(ROOM); // 用房间模板替换当前房间
}
-
限制条件对比
限制类型 replace_program set_default_object 本地函数 ❌ 禁止任何lfun ✅ 允许任意lfun 继承要求 必须继承目标程序 无特殊要求 使用对象 不能用于simul_efun 无限制 执行时机 延迟执行 实例化时 -
内存优化效果
内存类型 replace_program set_default_object 代码段 ✅ 共享继承程序 ❌ 独立程序副本 数据段 ❌ 独立变量 ✅ 共享模板变量 总节省 60-80% 30-50% -
总结
维度 replace_program set_default_object 一句话总结 用别人的程序,保留自己的变量 用自己的程序,共享别人的变量 优化重点 程序代码 配置数据 灵活性 低(功能降级) 高(无限制) 适用对象 只是功能继承的对象,如:环境 大量克隆实例的对象,如:物品