同步是和异步相对的概念,一般来说,编码里的同步和异步的区别是在于是否阻塞。
同步的代码会阻塞并等待任务的返回结果。
异步的代码一般只发起任务,不关心任务是否完成,可以通过回调/通道之类的方法获取处理结果。
在我们的机器人中,一般会用异步的方式抛出一系列的指令,然后需要一个同步的指令,阻塞任务队列,保证不会被其他的信息污染,来进行更详细的指令判断。
比如,我newhelljjs中有一个炼丹模块,代码如下
$.PushCommands(
$.To("1387"),
$.Do("give cao yao to xiao tong"),
$.To("1389"),
$.Do("i"),
$.Sync(),
$.Plan(PlanLiandan),
$.Sync(),
$.To("1388"),
$.Ask("yao chun", "炼丹"),
...
)
就很明显,用了两个$.Sync的同步指令,确保在新指令执行时,之前的指令已经处理完毕。
比如第一个$.Sync(),就是确保i指令已经从Mud服务端获取到了完整的回复,更新了角色的当前物品清单信息。
选择合适的指令
首先,我们的代码会需要选择一个合适的同步指令。这个指令要满足以下的要求:
什么时候都能使用
返回固定,没有消耗
对服务器压力低
平时不会使用,避免误触发
那么,对于没有提供对应低消耗指令的mud,我们一般会找这样的指令:
1.已经废弃的指令,使用时只有废弃提示。
2.有特殊格式要求的,不带参数会报错的。
注意,一个正经的机器人我们还是要注意服务器消耗的。不光光是公德心的问题,毕竟写的太丑的机器人也丢自己脸是不?
另外,除了同步指令,一般建议再找一个判忙的指令,要求和同步的指令类似,区别是在忙和不忙得情况下,回复会有不同。
毕竟忙(busy)也是很多mud的重要状态之一。而且很多时候,判忙也能代替同步指令的作用(更复杂的同步)。
合理的使用
使用同步/忙指令主要有以下特殊点:
避免连续重复使用,容易导致信号失真,特别是有可能进入状态重置的情况。
2.在无法避免信号失真的情况下,要保证机器能在短暂的失效后能重回正轨,有一定的容错性。
3.对于复杂场景,要有个统一的失效机制。也就是不要在等到同步信号之前就开始执行指令,在使用了同步机制后,要统一的在同步完成后再开始小一步。
举个例子,由于我的机器采用的是预期管理的模型驱动的,我特地建立一个Response的作用范围,然后效果如下:
let PlanTimes = new App.Plan(
App.Positions["Response"],
(task) => {
task.AddTrigger(matcherTimes, (tri, result) => {
if (result[1] == San.Data.WeaponName) {
task.Data = result[2]
}
})
App.Send(`l ${San.Data.Weapon}`)
App.Sync()
},
(result) => {
if (result.Task.Data) {
San.Data.Times = App.CNumber.ParseNumber(result.Task.Data)
App.HUD.Update()
}
App.Next()
}
)
利用我的期望失效的机制,强制在App.Sync()后,接收到同步信号时,通过Task.Data来判断结果和下一步的动作,避免会在同步信号前再发送一个同步指令,造成混乱。
避免滥用同步指令
同步指令一般只用在发送多个指令后进入不可预期的状态时的确认。
很多有明确预期的指令,比如移动,遍历,不应该以来同步指令,避免对服务器的不必要的压力和效率降低。
最典型的就是判断当前房间移动结束,或者房间的物品列表。
尽量使用 格式的变化(比如第一个出现的非房间物品)来判断。
比如
App.BindEvent("core.onexit", App.Core.Room.OnExit)
let matcherOnHeal = /^ (\S{2,8})正坐在地下(.+)。$/
let matcherOnYanlian = /^ (\S{2,8})正在演练招式。$/
let matcherOnObj = /^ ((\S+) )?(\S*[「\(].+[\)」])?(\S+)\(([^\(\)]+)\)( \[.+\])?(( <.+>)*)$/
//处理得到出口之后的信息(npc和道具列表)的计划
var PlanOnExit = new App.Plan(App.Positions.Connect,
function (task) {
task.AddTrigger(matcherOnObj, function (trigger, result, event) {
let item = new objectModule.Object(result[4], result[5], App.History.CurrentOutput).
WithParam("身份", result[2]).
WithParam("外号", result[3]).
WithParam("描述", result[6] || "").
WithParam("状态", result[7] || "").
WithParam("动作", "")
App.Map.Room.Data.Objects.Append(item)
event.Context.Set("core.room.onobject", true)
return true
})
task.AddTrigger(matcherOnHeal, function (trigger, result, event) {
let item = new objectModule.Object(result[1], "", App.History.CurrentOutput).
WithParam("动作", result[2])
App.Map.Room.Data.Objects.Append(item)
event.Context.Set("core.room.onobject", true)
return true
})
task.AddTrigger(matcherOnYanlian, function (trigger, result, event) {
let item = new objectModule.Object(result[1], "", App.History.CurrentOutput).
WithParam("动作", "演练招式")
App.Map.Room.Data.Objects.Append(item)
event.Context.Set("core.room.onobject", true)
return true
})
task.AddCatcher("line", function (catcher, event) {
let output = event.Data.Output
if (output.length > 4 && output.startsWith(" ") && output[4] != " ") {
return true
}
//未匹配过的行代表npc和道具结束
return event.Context.Get("core.room.onobject")
})
}, function (result) {
if (result.Type != "cancel") {
if (App.Map.Room.Name && !App.Map.Room.ID) {
let idlist = App.Map.Data.RoomsByName[App.Map.Room.Name]
if (idlist && idlist.length == 1) {
App.Map.Room.ID = idlist[0]
}
}
App.RaiseEvent(new App.Event("core.roomentry"))
}
})
是通过出现的行不是以4个空格开头(固定格式)来判断的。