最近剛在 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