深入浅出制作全自动Mud机器人-同步
-
同步是和异步相对的概念,一般来说,编码里的同步和异步的区别是在于是否阻塞。
- 同步的代码会阻塞并等待任务的返回结果。
- 异步的代码一般只发起任务,不关心任务是否完成,可以通过回调/通道之类的方法获取处理结果。
在我们的机器人中,一般会用异步的方式抛出一系列的指令,然后需要一个同步的指令,阻塞任务队列,保证不会被其他的信息污染,来进行更详细的指令判断。
比如,我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个空格开头(固定格式)来判断的。
-
J jarlyyn 被引用 于这个主题