API調(diào)用海關(guān)技術(shù)
作用:將所有screep操作預(yù)定義,在每個(gè)tick末尾全部執(zhí)行咖气。
意義:運(yùn)算-調(diào)用分離区赵,可以更直觀的統(tǒng)計(jì)api的調(diào)用情況和運(yùn)算的執(zhí)行情況
這有點(diǎn)像海關(guān)一樣,所有的貨物都集中到一艘船上之后珠漂,這艘船才會(huì)發(fā)出晚缩。這個(gè)技術(shù)的源本是跨Tick執(zhí)行任務(wù)衍生出來(lái)的,為了讓跨Tick執(zhí)行任務(wù)的框架具有低耦合性媳危、高拓展性荞彼,將這個(gè)板塊分離了出來(lái)。
實(shí)現(xiàn)效果
//MODEL就是你定義的海關(guān)待笑,這里暫時(shí)用MODEL命名鸣皂,你可以將它掛到global上以便于上下文的調(diào)用
//MODEL.intent(tick,gameObjectId,intentData);
intent(1098888,'c49nsb54snw616',{
handler:'harvest',
targetId:'46whwjj464sjwns'
});
那么,海關(guān)可在任意一刻收到該intent并記錄暮蹂,然后在游戲時(shí)間為tick時(shí)使用data中的數(shù)據(jù)對(duì)gameObjectId指定的對(duì)象調(diào)用data中定義的方法寞缝。
有同學(xué)常問(wèn),這樣的intent的操作仰泻,怎么判斷返回值呢荆陆?
這里有兩個(gè)方案
- 回調(diào)式返回
- 狀態(tài)模擬技術(shù)
回調(diào)式返回,顧名思義集侯,就是在調(diào)用intent的入?yún)⒛┪蔡砑右粋€(gè)回調(diào)方法被啼,到了執(zhí)行該方法的時(shí)候,將返回值傳遞給回調(diào)方法浅悉,再去處理相關(guān)操作趟据。
但這樣做有個(gè)致命缺陷,它讓跨tick執(zhí)行任務(wù)的容錯(cuò)率大大降低术健,并且可能存在不停的內(nèi)嵌調(diào)用汹碱。
在我認(rèn)真分析了跨Tick執(zhí)行任務(wù)框架的真正意義后,我發(fā)現(xiàn)這樣做并不完善荞估。但狀態(tài)模擬技術(shù)在這上面的發(fā)揮就能相得益彰了咳促。
既然是跨Tick執(zhí)行任務(wù)稚新,那么我們的API就不能變得只對(duì)一個(gè)Tick有效,于是我們拋棄了原來(lái)所有的原生API跪腹,將其重寫褂删,把API也直接提升到跨Tick的層次上。例如
原來(lái)的harvest方法變成某1Tick調(diào)用一次冲茸,剩下的Tick就會(huì)自動(dòng)完成尋路和Harvest操作屯阀。
在最新的TickStream我們是這么做的
我們聚焦在Register上(目前最新版本將其更名為Registry),在TickStream我們不再需要調(diào)用API轴术,更多的是去定義难衰,定義一個(gè)房間,在房間里面定義一個(gè)Creep逗栽,在Creep上定義一個(gè)控制器盖袭,在控制器上定義和命名狀態(tài)機(jī)和實(shí)現(xiàn)功能。例如定義一個(gè)為Harvest的狀態(tài)彼宠,并用代碼實(shí)現(xiàn)Creep如何進(jìn)行Harvest鳄虱。具體如何實(shí)現(xiàn)請(qǐng)看下面
Registry.event([{
name:'CreepNotFree',
event:EVENT_CREEP_NOT_FREE,
attach:ATTACH_CREEP,
call:function(creep){
creep.state.now.cancel();
},{
name:'UnexpectedCancel',
event:EVENT_CREEP_UNEXPECTED_CANCEL,
attach:ATTACH_CREEP,
call:function(creep){
let before = creep.state.get.before();
creep.state.now.set(before);
}
}]);
Registry.handler([{
name:'harvest',
behavior:function(creep,target){
//判斷自己是否正在另一個(gè)進(jìn)程中
if(creep.state.get.now()!='harvest'){
//如果creep之前已經(jīng)有一個(gè)其他進(jìn)程了,則發(fā)送事件到Event處理器
return Event.creep(EVENT_CREEP_NOT_FREE,creep);
}
//尋找路線
creep.state.now.set('moveTo',target);
//由于是跨Tick執(zhí)行凭峡,你可以認(rèn)為上面這一行代碼執(zhí)行完成后Creep一定在target旁邊了
//harvest拙已,這個(gè)harvest不是用戶定義的,而是通過(guò)intent去調(diào)用了真實(shí)的api
creep.intent(Game.time,'harvest',target)
creep.state.now.done();
}
}]);
我們來(lái)分析一下這些代碼是怎么運(yùn)行的想罕,首先我們要了解creep的狀態(tài)棧悠栓。
狀態(tài)棧
狀態(tài)棧,表示了一個(gè)creep當(dāng)前的狀態(tài)調(diào)用鏈按价,這在跨Tick執(zhí)行任務(wù)中至關(guān)重要惭适。
如上文所述,我們給creep定義了一個(gè)harvest方法楼镐,harveat方法里面又通過(guò)
creep.state.now.set('moveTo',target)調(diào)用了moveTo方法(這個(gè)方法并沒(méi)有寫出來(lái)癞志,因?yàn)楸容^復(fù)雜)。
那么實(shí)際上TickStream在執(zhí)行過(guò)程中框产,這個(gè)Creep的狀態(tài)棧是這樣變化的
- at harvest
- at harvest at moveTo
- at harvest at moveTo at move
- at harveat at intent
你可以發(fā)現(xiàn)凄杯,moveTo這個(gè)方法在Harvest下面,表示moveTo這個(gè)行為是因?yàn)閔arvest引起的秉宿。
注意:harvest的behavior并不是每個(gè)tick都去執(zhí)行戒突,而是由TickStream框架驅(qū)動(dòng)并緩存執(zhí)行。只有當(dāng)global清空后描睦,或creep狀態(tài)被重新定向后才會(huì)重新執(zhí)行膊存。
我們主要看harvest方法中的內(nèi)容
這是第一部分
//判斷自己是否正在另一個(gè)進(jìn)程中
if(creep.state.get.now()!='harvest'){
//如果creep之前已經(jīng)有一個(gè)其他進(jìn)程了,則發(fā)送事件到Event處理器
return Event.creep(EVENT_CREEP_NOT_FREE,creep);
}
creep.state.get.now返回的是狀態(tài)棧的棧底,因此只有creep的底棧處于harvest狀態(tài)隔崎,這個(gè)behavior都能正常執(zhí)行今艺。
再看代碼中的
//如果creep之前已經(jīng)有一個(gè)其他進(jìn)程了,則發(fā)送事件到Event處理器
return Event.creep(EVENT_CREEP_NOT_FREE,creep);
你可以在上面的Regiatry中找到對(duì)應(yīng)的方法
function(creep){
creep.state.now.cancel();
}
這表示爵卒,如果creep當(dāng)前的狀態(tài)不是harvest虚缎,那么就取消當(dāng)前的狀態(tài)
在TickStream框架下,一個(gè)creep如果被其他Handler Cancel了钓株,那么會(huì)狀態(tài)會(huì)自動(dòng)變成發(fā)起cancel的Handler對(duì)應(yīng)的狀態(tài)实牡。
所以此時(shí)creep的狀態(tài)變成了harvest
此時(shí)的狀態(tài)棧
at harvest
繼續(xù)往下
creep.state.now.set('moveTo',...);
此時(shí)creep的狀態(tài)棧
at harvest at moveTo
在moveTo方法中,由tickstream轴合,調(diào)用海關(guān)以及狀態(tài)模擬技術(shù)铲掐,將未來(lái)幾個(gè)tick的尋路
已經(jīng)intent完成,并在每個(gè)tick結(jié)束后自動(dòng)調(diào)用值桩,并對(duì)狀態(tài)棧進(jìn)行壓棧
此時(shí)狀態(tài)棧
at harvest at moveTo at move
這個(gè)時(shí)候因?yàn)閕ntent的存在,其他handler直到move的handler的執(zhí)行結(jié)束后豪椿,對(duì) at move進(jìn)行出棧才能繼續(xù)執(zhí)行奔坟。所以這個(gè)時(shí)候其實(shí)所有handler方法都沒(méi)有執(zhí)行。
即使你刪除代碼creep也能繼續(xù)跑搭盾。
直到move完成后咳秉,此時(shí)creep已經(jīng)來(lái)到target旁邊
此時(shí)的狀態(tài)棧
at harvest at moveTo
moveTo隨即也結(jié)束出棧
at harvest
這個(gè)時(shí)候可以執(zhí)行harvest下面的intent調(diào)用api來(lái)真實(shí)的進(jìn)行harvest了
執(zhí)行完intent后使用
creep.state.now.doen()對(duì)harvest出棧
這個(gè)時(shí)候其他的handler才能對(duì)其進(jìn)行操作,當(dāng)然也可以被其他handler在中途取消鸯隅。
由于我們?cè)趀vent當(dāng)中添加了取消事件澜建,即使被取消,也會(huì)被重新壓棧執(zhí)行蝌以。
好了炕舵。
講原理可能會(huì)有點(diǎn)復(fù)雜,我表達(dá)能力可能不強(qiáng)跟畅,請(qǐng)各位敬請(qǐng)諒解咽筋。下次我會(huì)成體系的講述TickStream的工作細(xì)節(jié),當(dāng)然你也可以借鑒完成自己的跨Tick框架