20
2017
02

系统刷新与内存清除分析

原作者:未知

       有关系统更新一直是玩家乃至于新巫师们关心的问题。比如,为何每隔15分钟大多数房间里杀死的NPC会重生?跑到别处或被玩家背到别处的NPC怎么会跑回去?为什么有的NPC跑不回去?什么有的东西会重生?为什么又有的东西只要别的玩家放在身上?等等。
  目前主流MUDLIB都是ES系列的。从ES系列沿袭下来的更新都是通过ROOM的更新实现的。而ROOM的更新则是由MUDOS里的设置每隔一定时间(一般是15分钟)调用一次所有的有reset()函数的房间。而这个reset()函数则写在ROOM的标准继承文件里面。下面我们则来看看ROOM是如何实现房间里的生物、物品的重生或更新:
  在写这篇文章之前,正好在网上看到darks兄写的《ROOM的结构》,于是我这篇文章的不少地方也就写得很顺畅了,有些直接引用了《ROOM》一文的一些内容。为了尊重原作者,凡是引用或出自darks兄的原文内容我都用“”与绿色标出:
  ROOM的标准文件由于MUDLIB的不同,放在目录路径也不同,但大多情况下也就是/inherit/room/下或者与/obj/room/下两种可能而已。反正不检查一下在/include/下的globals.h,看这个文件里ROOM是定义在哪里就可以了,下面来看一看room.c的程序详解:

inherit F_DBASE;
//“这个是继承dbase标准继承,有了它,你才可使用set等函数为这个物件设定变数”(此问题日后做专题说明)。

inherit F_CLEAN_UP;
//“这个用来定时清除很久没被访问的room”,这个概念我们要在后面谈到。

static mapping doors;
//“这是一个有关房间里的门的全局变量,不是我们今天讨论的范围之内,你只要知道就行,我们在这个文件里还能找到与门相关的几个函数:

mixed set_door(string dir, string prop, mixed data)
mixed query_door(string dir, string prop)
mapping query_doors()
string look_door(string dir)
varargs int open_door(string dir, int from_other_side)
varargs int close_door(string dir, int from_other_side)
varargs int lock_door(string dir, string key, int from_other_side)
varargs int unlock_door(string dir, string key, int from_other_side)
int check_door(string dir, mapping door)
varargs void create_door(string dir, mixed data, string other_side_dir, int status)
int valid_leave(object me, string dir)
int query_max_encumbrance() { return 100000000000; }
//设置可容纳的重量,以上这些函数大多与门有关,我们今天都一一略过,下面才是我们今天要研究的与系统房间刷新相关的函数:

object make_inventory(string file)
{
  object ob;
  ob = new(file);
//根据传递来的路径名,将ob复制出来
  ob->move(this_object());
//复制出来的ob移于目的地
  ob->set("startroom", base_name(this_object()));
  return ob;
}
//这个函数用来产生一个房间里的物品。首先它需要别的函数在调用它的时候要传递给它一个需要产生的物件的路径。然后用new()复制出来,接着move到这个房间里,再接着给它设上startroom这个标记,这个标记就可以在这个房间定时呼叫自己房间里产生的npc可以使用return_home()这个函数时,正确回到原来的地方。

void reset()
{
  mapping ob_list, ob;
  string *list;
  int i,j;

  set("no_clean_up", 0);
//“这个标记为零,即允许系统到了规定时间将这个文件扫出内存,那么这个文件内的所有东西都会消失。由于room标准继承有这句,似乎发现只要继承它的房间文件无论写为0/1都是无效的,因为都会在这里被清除成零。

  ob_list = query("objects");
//先取出一个这个房间初始设定的objects的映射集
  if( !mapp(ob_list) ) return;
//如果这个房间初始时就没有设定有生物物品,就说明根本无需要刷新,因此到此返回。

  if( !mapp(ob = query_temp("objects")) )
  ob = allocate_mapping(sizeof(ob_list));
//程序到后面才可看到ob = query_temp("objects")是如何出来的,在这里,我们先不管,你只要知道,如果是一个刚刚编译进内存的房间,是不会有ob这个映射集的,因此需要用allocate_mapping按照ob_list的多少为这个新设定的映射集ob分配内存大小。

  list = keys(ob_list);
//从ob_list映射中取出关键字组成一个新数组。

  for(i=0; i<sizeof(list); i++)
//开始循环检查这个数组里的每一项
  {
    if( undefinedp(ob[list[i]])
      && intp(ob_list[list[i]])
      && ob_list[list[i]] > 1 )
      ob[list[i]] = allocate(ob_list[list[i]]);
//如果房间里曾经定义了要产生物品,并且数量不止一个的话,就要进行ob[list[i]]这个物件数组的内存分配

    switch(ob_list[list[i]])
    {
    case 1:
//举例一个文件里:set("objects",(["/d/city/npc/bing":1]));,那么在这里,也就是ob_list[list[i]]这个值取出是1

      if( !ob[list[i]] )
        ob[list[i]] = make_inventory(list[i]);
//如果这一个对象已经不在了(玩家理解的就是被杀死了或被当作任务送掉了,巫师的理解就是被destruct了),就使用make_inventory()函数再重新制造一个放进来。这里注意了,仁去递过去的list[i]就是这一项物品的路径名,正因为有了路径名,make_inventory()函数才能正确制造出新的来。

      if( environment(ob[list[i]]) != this_object())
//反之如果还存在,但它目前所处之地却不是目前的这个房间

      {
        if(ob[list[i]]->is_character()
          &&!ob[list[i]]->return_home(this_object()))
        add("no_clean_up",1);
//这句判断该物体如果是生物,就呼叫生物的return_home()叫它回来,如果这个NPC不能回来并且返回值是0的话,就会给这个房间增加一次no_clean_up的记号,程序的原作者之所以要在这里增加房间的no_clean_up记号,估计它的意思就是不想让系统在房间不能成功召回自己的NPC的情况下清除它,因为它想在以后的刷新中再把它呼叫回来。但是实际上,大家注意到前面的程序了吧,只要产生了下一次呼叫reset()时,前面就会把no_clean_up设为0,因此这段ES的源程有些莫名其妙,但大家居然都没人改,也是怪事。

      }
      break;
      default:
//除此之外,也就是物件不止一个的话,举例相当于文件里:set("objects",(["/d/city/npc/bing":2]))或者3,4....这类的情况

      for(j=0; j<ob_list[list[i]]; j++)
      {
        if( !objectp(ob[list[i]][j]) )
        {
          ob[list[i]][j] = make_inventory(list[i]);
          continue;
        }
        if( environment(ob[list[i]][j]) != this_object())
        {
          if(ob[list[i]][j]->is_character()
          &&!ob[list[i]][j]->return_home(this_object()) )
          add("no_clean_up", 1);
        }
      }
//这里其实与物件只有一个是一样的,只是因为相同的物品不止一个,需要进行几次的循环判断而已。

     }
  }
  set_temp("objects", ob);
//看到这里,知道这个函数里ob映射集是如何来的了吧,实际上ob_list就是代表的这个房间里的的query("objects"),是一个字符串内容的映射集,而ob就是代表的这个房间里的query_temp("objects")它实际上一个object型的映射集。
}

  reset()函数结束了,其实在ROOM里,除了这两个函数,还有一个在一开始编译进内存后进行首次调用reset()函数的setup()函数之外,其它的函数都是有关门的,都是可以去掉并影响房间的主要功能的,ROOM标准继承的最主要功能就是定时检查自己房间里的物品是否还在?是否需要更新等等。而这个定时则就是由MUDOS定义并按时呼叫房间里的reset(),这个时间绝大多数被定义为十五分钟。
  我们通过上面的程序详解可以看出,当一个房间被编译成功进入内存之后,那么这个房间就将自身产生出来的各个物体(假如它有的话)记入一个query_temp("objects")的物件映射变量中,这个变量与我们写程序里的query("objects")是一一对应的,只不过query("objects")里记的是这此物件的
文件路径,而query_temp("objects")里记的是这些具体的物件。关于这两个映射的区别,有兴趣的新巫师可以找一个有很多NPC的房间按下面分别call两次,看看区别:
call here->query("objects")
call here->query_temp("objects")

  在reset()被调用时,程序就会循环地一个个地查找这些物件是否还在MUD中?如果这些物件都已经不存在了,那么,reset()函数就会通过呼叫make_inventory()函数将其再次制造出来,也就是我们看到了,更新时间一到,很多被杀死的NPC,用掉的东西都会在原处产生出来。
  而如果这些物件都还在MUD中,就会检查它们是否还在原处?如果不在的话,只要是生物,就呼叫它的return_home()函数(这个函数在所有NPC的标准继承
/inherit/char/npc.c里),叫它回来。并且要把这个房间作为参数传递过去,否则NPC会回不来。如果不是生物只得作罢(这就是房间产生出的物品如果被某一玩家放在身上,就再也不能重生的原因)。那么下面我们就来看一下npc.c里的return_home()函数:

int return_home(object home)
//注意,括号里的home就是呼叫它回家的那个房间,当时是叫this_object()
{
  if( !environment()|| environment()==home ) return 1;
//再次检查:是否在一个存在的环境里?是否已经回来了?如果是,则什么也不做,返回!
  if( !living(this_object())|| is_fighting()) return 0;
//如果NPC处于昏迷或战斗状态,则不回来,返回值是0,综合room.c,原房间会增加no_clean_up记号;
  message("vision", this_object()->name() + "急急忙忙地离开了。\n",environment(), this_object());
  return move(home);
}

  谈到这里,大家可以发现,所谓房间的更新,实际上只是房间里的物体进行更新,这个房间没有任的变化。也就是说,如果在房间更新的时候,我们站在这个房间里,或者我们扔了一个不属于任何房的物品在这个房间里,都不会受到影响,这些物品与我们在更新前后都不会消失。这个与我们巫师进update here是本质性的两回事(updata here就是更新了房间)。

  那么,有时有的玩家就会说,我曾得到一个很好的宝物,离线不能保存,我就把它扔在一个很少有去的地方,结果,每次再去连线再去找的时候,大多数时候都找不到,不会是被别人捡去吧?这里就及到另一个概念:MUD里的资源清除。
 
  大家知道,在LPMUD里,所有的程序都必须装载进内存里才会工作。因此,MUD的内存资源便就是最主要的资源。更合理地分配和使用内存便成为一个MUD效率高低的体现。
  MUDOS为了节约内存的耗用,对于每一个占用内存的对象,包括是房间、物品、人物、指令等等,如果相当长的时间内没有被其它程序参考到(参考的含义:就是包括别人进入、看到、或者使用到这个房间、物品、或指令,还包括各个程序等等)的话,也就是这个对象很长时间没有活动了,MUDOS就会调用这个对象的clean_up()函数(由于大多数的程序都会继承这个函数标准文件),如果该函数返回1,则下次同样情况还会调用该对象的clean_up;如果返回0,则永远不再调用。那么,我们就来看一下/feature/下面的clean_up.c文件,这个文件只有一个函数:

int clean_up()
{
  object *inv;
  int i;

  if( !clonep() && this_object()->query("no_clean_up") )
    return 1;
//如果这个对象不是clone出来并且有"no_clean_up"记号的,则返回1(返回1的含义上面说过了)

  if(interactive(this_object())) return 1;
//如果对象是互动物件,比如玩家,就返回1

  if(environment()) return 1;
//如果对象处在一个环境里,也返回1

  inv = all_inventory();
//取出这个对象里面所有的物件
  for(i=sizeof(inv)-1; i>=0; i--)
  if(interactive(inv[i])) return 1;
//循环检查这些物件,只要其中有一个互动物件,就返回1

  destruct(this_object());
  return 0;
//全部检查完了后,就决定正式摧毁自身,释放出这个对象所占用的内存,并返回0
}

  我们再次复习一下clean_up()函数返回1的含义,如果clean_up()函数返回1,则MUDOS在这一次的调用时不会做其的任何举动,但到了下一次想调用的时间里,还将再次调用这个对象的clean_up()函数。那么从这可以看出,有以下四种情况不会将其清除出内存:
一、非clone出来并且有no_clean_up参数的对象;
二、玩家永远不会
三、处于一个还存在的环境里
四、自己里面存在着玩家
  也就是MUDOS定时摧毁内存不需要的对象是由外向内的,比如一个房间,系统只要检查这个房间里没有no_clean_up参数、里面没有玩家就可清除它,而房间里的物品、NPC都会因环境的不存在而消失。这个清除的定时时间一般都为两个小时。当然要视不同的MUDOS里的设置而看的。
  再说一点题外话,如果一个房间长时间没有玩家走进来,当然会被MUDOS清出内存,而突然又有玩家进来呢?很简单,它会在一瞬间被编译进内存,进入一个已经存在在内存里的房间与进入一个刚刚编译出来进入内存的房间对于我们的玩家来说,是察觉不出它们之间的差异的。   

« 上一篇下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。