[闲聊] 排程

楼主: laechan (挥泪斩马云)   2018-01-22 23:45:34
最近刚在 sanc 写好这个东西。
以前在改 tmi2-mudlib 时有稍微提过这东西
┌─────────────────────────────────────┐
│ 文章代码(AID): #1JZ-f8qq (mud) [ptt.cc] Re: [闲聊] tmi2-mudlib 的更改 │
│ 文章网址: https://www.ptt.cc/bbs/mud/M.1401940552.A.D34.html │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 文章代码(AID): #1JaHsSIj (mud) [ptt.cc] Re: [闲聊] tmi2-mudlib 的更改 │
│ 文章网址: https://www.ptt.cc/bbs/mud/M.1402019228.A.4AD.html │
└─────────────────────────────────────┘
简单的说,我有写一个系统物件叫 times_check.c,它的设计是,
透过启用心跳,它每一心跳时间都会去 call 自己的 heart_beat
,这时 heart_beat 可以这样写:
int heart_beat()
{
t=time();
if(判断到 t 这个时间有需要执行的呼叫时)
call_other(欲呼叫的目标,"times_check",...);
return 1;
}
上面的 times_check 函数是固定的。有了这支程式,比方以 sanc
的拍卖指令档 /cmds/std/_blarket.c 为例,该指令档内就可以新
增 times_check 函数:
int times_check(string str,mixed vars)
{
// 当 vars 是有值的时
if(sizeof(vars)>0)
return cmd_blarket(vars[0]);
// 当 vars 是无值的时, 我用这个来判断 times_check 函数是
// 否为第一次被 times_check.c 所呼叫, 第一次呼叫的话不会
// 带值
.
.
return 1;
}
以 sanc 的 blarket 指令为例,拍卖一件物品的流程可固定如下
blarket -auc 欲拍卖的物品档名 它会将物品叫出来拍卖
blarket -continue 广播目前的拍卖情况
blarket -continue 广播目前的拍卖情况
blarket -end 结标
我希望上面分别是 2 秒后、20 秒后、40 秒后、60 秒后来进行
时,则每一拍卖对 times_check.c 物件的设定就可这样做
t=time();
foreach(tmp in tmps) // 对每一个欲拍卖物品
{
times_check_ob->set_times_check("-auc "+物品档名,t+2);
times_check_ob->set_times_check("-continue",t+20);
times_check_ob->set_times_check("-continue",t+40);
times_check_ob->set_times_check("-end",t+60);
t=t+68; // 拍卖结束后 10 秒再进行下一个拍卖
}
也就是说,假设我有 10 件物品要卖,它就呼叫 times_check.c
10x4 = 40 次,“将之后要执行的东西通通先设好”,然后就可
以坐等 times_check.c 帮我们在约定好的时间,逐一执行该做的
事情。
则 times_check.c 的资料结构,合理的设计自然是
mapping data=([
"以 time() 字串化做为 key 值":({ 要做的事情1, 要做的事情2, ..}),
"以 time() 字串化做为 key 值":({ 要做的事情1, 要做的事情2, ..}),
.
.
]);
则时间到的时候:
str=""+time(); // 字串化
if(data[str]) // 代表这时间有需要执行的东西
{
foreach(need_to_do in data[str])
{
对 need_to_do 做资料解析;
call_other(要呼叫的目标,"times_check",要带的参数群..);
}
}
// 该做的事情做完了,就把 data[str] 拿掉
map_delete(data,str);
也就是说,我的设计是
一、我先写一支 times_check.c 它每秒都会呼叫一次自己的
heart_beat 函数。
二、然后比方我想让 sanc 的拍卖指令 blarket 支援排程拍
卖,我就指定呼叫的模式,让 times_check.c 在约好的
时间对 blarket 指令做指定的呼叫,再透过这个呼叫,
反过来对 times_check.c 做指定的设定。
三、剩下的事情就可以全部交给 times_check.c 在约好的时
间帮我执行拍卖。sanc 预定在月底正式执行排程拍卖。
四、既然 blarket 可以,就代表其它东西只要依样化葫芦,
  也可以做排程。
目前 sanc 的 times_check.c 也应用在 boat 上面,跟其它
mud 一样,sanc 大陆与大陆之间也有定期航班的设计,一般
只需让 boat 继承 /std/boat.c 即可(tmi2-mudlib)。
但是 boat 基本上流程就是这样
抵达 A 地点, 设定船只的出口为 A 地点
几秒后, 广播即将驶离
几秒后. 关闭出口, 广播已驶离将前往 B 地点
几秒后, 广播航行中
几秒后, 广播航行中, 即将抵达 B 地点
抵达 B 地点, 设定船只的出口为 B 地点
几秒后, 广播即将驶离
几秒后. 关闭出口, 广播已驶离将前往 A 地点
几秒后, 广播航行中
几秒后, 广播航行中, 即将抵达 A 地点
抵达 A 地点, 设定船只的出口为 A 地点
.
.
上面看似为一段设定,实际上真正的一段设定只有
抵达 某 地点, 设定船只的出口为 某 地点
几秒后, 广播即将驶离
几秒后. 关闭出口, 广播已驶离将前往 下一 地点
几秒后, 广播航行中
几秒后, 广播航行中, 即将抵达 下一 地点
上面只要做好规划,后面其实都是 loop 的呼叫,那么自然可
以交给 times_check.c 来做。
我没记错的话,目前大部份 mud 的 boat 写法都是类似的,
比方以 fly_next 为例
void fly_next()
{
now=find_object_or_load(plane[i][1]);
tell_room(now, data["short"]+"靠港了。\n");
tell_room(this_object(),GRN"老船长: "+plane[i][0]+"到了。\n"NOR);
now->set("hide_exits/enter",base_name(this_object()));
now->set("long2",query("out_short"));
set("exits/out",plane[i][1]);
call_out("hurry_up_msg",plane[i][2]-5);
}
我不满意传统 boat 的原因就在于它是以 call_out 来做为主
要流控,而我不太喜欢使用 call_out,与其每一艘船都在那边
call_out,不如让 times_check.c 来控制所有的船,类似航管
员的角色,当船只很多且进出频繁时,同一时间必然有
1.有船正要进来
2.有船正要离开
优秀的航管员不会因为一次要顾很多艘船的进出就混乱,所以
就是看程式怎么写而已。
在撰写 times_check.c 时也要注意存取资料的频繁度,例如说
我希望 times_check.c 能在 1/31 晚上 21:00 帮我执行拍卖,
则我在 1/22 的现在设定好排程后,times_check.c 必然要储存
这项设定,才能在 1/31 晚上 21:00 帮我执行。
但是执行后的 set_times_check 却是不需要储存的,因为当天
晚上它就可以把该卖的东西都卖完了。所以我的写法是
times_check_ob->set_times_check 这种呼叫会储存起来
times_check_ob->set_times_no_save 这种呼叫不会储存
也就是说我在宣告变量时实际上就分为两个
mapping data;
static mapping tmp_data;
以 static 去宣告的变量,在进行 save_object 时是不会储存
的。所以我上面的程式段实际上为
t=time();
foreach(tmp in tmps) // 对每一个欲拍卖物品
{
times_check_ob->set_times_no_save("-auc "+物品档名,t+2);
times_check_ob->set_times_no_save("-continue",t+20);
times_check_ob->set_times_no_save("-continue",t+40);
times_check_ob->set_times_no_save("-end",t+60);
t=t+68; // 拍卖结束后 10 秒再进行下一个拍卖
}
既然 no_save 就代表一旦在执行中 update times_check.c,
这些设定就会被清除不会保留下来,是以一般都会对该物件加
设 set("pre_clean",1); 或类似的做法,来防止这类的物件被
系统自动 reset。
times_check.c 还有个需注意的事项,就是要避免让它在每一
心跳时间呼叫 heart_beat 时,执行 loading 很重的工作。
例如若只是一些简单的呼叫及简单的物件资料设定,那即便是
几十个物件也都是瞬间(毫秒)就能执行完毕,就不致于影响每
秒要做的事情。
(而且实务上,也很少每一秒都是 loading 很重)
以上,一点心得分享。
Laechan

Links booklink

Contact Us: admin [ a t ] ucptt.com