看板 mud 關於我們 聯絡資訊
最近剛在 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 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 122.117.106.224 ※ 文章網址: https://www.ptt.cc/bbs/mud/M.1516635944.A.0A0.html
smmoon : 只能推了 114.32.144.77 01/23 08:13
ghostmote : 好久沒看到技術文118.163.154.205 01/23 17:07
總算有時間可以補充點東西。 欲使用排程的物件 times_check.c │ │ │對 times_check.c 設定時間及工作 │ ├───────────────→┤呼叫 set_times_check 函數 │ │ │ 然後等約定的時間到的時候 │ │ │ │times_check.c 第一次呼叫該物件 │ ├←───────────────┤ 根據該呼叫 │ │ └─→ │times_check.c->set_times_no_save│ │令 times_check.c 多次呼叫上面的 │ │函數去設定幾秒後要做哪些工作 │ ├───────────────→┤然後 times_check.c 移除該排程 │ │只留 set_times_no_save 的設定 │ │ │ 然後等約定的時間陸續到了時 │ │ │ 都是去呼叫 │times_check.c 在約定的時間呼叫 │ times_check ├←───────────────┤ 函數 │times_check.c 在約定的時間呼叫 │ ├←───────────────┤ │times_check.c 在約定的時間呼叫 │ ├←───────────────┤ │ . │ │ . │ │ . │ │ │ │ 直到全部的呼叫都已結束 │ 然後不同的工作排程允許做疊合,彼此之間不會互相影響。 上面可以 work 的條件就是 times_check.c 會週期時間去判斷當下的 時間(time())是不是有什麼工作要做,有三種實現做法 1.利用擁有心跳的物件會每一秒呼叫一次自己的 heart_beat 函數 2.或是利用 preload 先載入該物件,然後在其 create 函數內使用 call_out 的語法,再於其 call_out 的目標函數內亦使用 call_out (也就是 loop call out) 3.或是利用其它 call_out 物件,例如典型的 weather_d.c 天氣物 件,假設該天氣物件每 60 秒(每分鐘)都會固定 call_out 什麼函 數,那就可以利用該函數來做為排程控制。(精準度設為"分"即可) 然後再以排程拍賣為例,一般的 mud,多是使用 call_out 做為拍賣 流控的手段,而且會判斷拍賣期間是不是有玩家持續出價,有的話就 會一直延後拍賣結束的時間。 這種做法並不利於使用約定好的排程來控制。 所以 sanc 有另外寫一支拍賣用的程式 blarket,它的流程全部都可 由拍賣舉辦者來控制,亦即要五分鐘後結標、十分鐘後結標、甚至是 30 分鐘後才結標都可以,只要下 blarket -end 即可結標。 (blarket 語源為 black market = 黑市,即取黑市拍賣會的意思) 它的最大好處就是使用者方便控制流程,又不會使用到 call_out 這 個 loading 較高的東西,在 sanc 以這個指令舉辦特殊的拍賣會是 wiz 專用的,玩家則可以 blarket -bet 來競標,沒記錯的話我也有 把這支程式放在 tmi2_mudlib_v3_改 裡頭。(因為作者是我) ※ 編輯: laechan (122.117.106.224), 01/23/2018 18:08:50
typers : 推L大對mud的熱情 36.234.4.226 01/23 19:44
typers : 但heart_beat為單位的排程,真心不推 36.234.4.226 01/23 19:49
laechan : 現在電腦效能都很好滴122.117.106.224 01/23 20:10
kyoe : 後期開發的mud大多已經利用h_b來取 60.250.92.145 01/25 10:17
kyoe : 代callout,我們叫他timed/scheduled 60.250.92.145 01/25 10:21
kyoe : 不過都是新的才用,舊的懶改,哈哈 60.250.92.145 01/25 10:25
kyoe : 還是要推推,現在在搞的人越來越少了 60.250.92.145 01/25 10:28
我個人目前有想過在 sanc 實裝「地上的物品經過一段時間後會 風化」的設定,以前是想用被動觸發式,物品被 move 到地上時 就 set("move_times",time());,但是這樣就需要 init 函數的 輔助,因此只能侷限在特定種類的物品。 至於 call_out 或 heart_beat 則差不多是同時期想到的其它做 法,用 heart_beat 主要是把該物品比方屍體 corpse 當成生物 ,該物品本身就有心跳,那自然可以順利判斷,而且把它當生物 本身也有很多好處(雖然不合理)。 再之後,則產生以 times_check.c 來跑的做法: 1.玩家陣亡時,呼叫出屍體,並做 set_times_no_save 的設定 並輔以 set("remove_times",time()+幾秒後); 2.times_check 依照設定,分段去 call_other(這具屍體,"times_check",帶不同的參數); 3.最終 times_check 會去移除屍體。在上述執行過程中,如果 times_check 被 update 或 reborn,屍體也會被 look_d.c 依照 query("remove_times") 的判斷來移除。 (玩家進入房間時會呼叫 look_d.c 的 look_room 函數觀看 房間,利用這一點) 以上是閒聊,剛好有想到。 其實最終都會回到一個關鍵問題:如果同一時間出現大量屍體呢? (所以 sanc 沒有屍體...雖然不合理) 國外有篇文章 http://lpmuds.net/smf/index.php?topic=1171.0 比方以底下這句 The "Performance" file in the root directory of FluffOS's source (couldn't find online copy) says not to give objects heartbeats unless really necessary, and to do as little in the heart_beat function as possible. 那其實以下我覺得都對 1.盡量不使用 heart_beat (比方使用 call_out) 2.盡量減少使用 heart_beat (比方只讓少數物件有 heart_beat) 那我個人的看法是 call_out 跟 heart_beat 都可以用,但也都 應該少用,unless really necessary。比方只是單純希望幾秒後 去呼叫什麼,秒數不長,呼叫完就沒了,那我就會用 call_out, 但如果這類呼叫一直出現(比方喝藥水、施法的過程中都會出現) ,我就會考慮 call_out 以外的做法,比方在 sanc 我採取的做 法就是瞬喝藥水、瞬施法術(雖然不合理): > cast pray [瞬間] 你緩緩唸道: 深眠於心靈深處的潛在力量,藉神之力甦醒吧! 你開始唸起古老的咒文: ~ 9 ~ ~ ~ 祈禱術 ~ 你感覺自己體內的魔力變得活性化,頭腦也靈活了不少! [然後馬上要再 cast 時] > cast pray 你要等一下才可以再施法喔。 heart_beat 也是一樣,通常某些我覺得不得已需使用的場合, 該物件都不會是恆久存在的(times_check.c例外),也就是說就 算我讓某物件 set_heart_beat(1),我也會讓它盡量快點消失, 或者是將它消失的機制寫得完善一點。(例如sanc的副本系統) 我一般在使用 heart_beat 時會參考兩個標的 1.user 的 heart_beat 2.monster 的 heart_beat sanc 玩家的 heart_beat 函數其實密密麻麻寫了蠻多段東西, 我大概就是以這個來評估我寫的其它 heart_beat 物件,跑起 來會不會出問題,目前看起來大概沒啥問題。 最後,目前 sanc 避免心跳過程中產生錯誤的做法只有一個: catch(call_other(目標物件,"函數",...)); 總而言之就是懶人做法 :) ※ 編輯: laechan (122.117.106.224), 01/25/2018 11:44:39
msrvoice : 技術文, 好久沒看到了, 居還還看懂 180.217.170.77 01/25 22:44
taily : l大,程式參數都滿長的:D 210.64.180.213 01/26 17:37
kruz : 推一下 24.6.220.133 01/30 03:29
darkmoon1725: 推推 59.115.58.244 02/02 21:48