我正在为NPC聊天编写服务器模拟器的脚本接口。玩家可以通过点击NPC发起NPC聊天。 NPC可以发送文本对话框。这些文本对话框还包含一个End Chat
按钮,用于在脚本完成执行之前结束聊天,或者播放器可以正常继续文本对话框直到它们结束。
当玩家中断聊天时,会发送一个特殊的数据包。
我创建了一个名为WaitableResult
的类,它利用ManualResetEvent
阻止当前线程直到给定结果,然后返回结果:
public sealed class WaitableResult<T> where T : struct
{
public T Value { get; private set; }
private ManualResetEvent mEvent;
public WaitableResult()
{
mEvent = new ManualResetEvent(false);
}
public void Wait()
{
mEvent.WaitOne();
}
public void Set(T value)
{
mEvent.Set();
this.Value = value;
}
}
这是我的脚本类:
internal sealed class NpcScript : ScriptBase
{
public WaitableResult<bool> BoolResult { get; private set; }
private Npc mNpc;
private Player mPlayer;
public NpcScript(Npc npc, Player player)
: base(string.Format(@"..\..\scripts\npcs\{0}.lua", npc.Script), true)
{
mNpc = npc;
mPlayer = player;
mPlayer.NpcConversation = this;
this.Expose("answer_no", false);
this.Expose("answer_yes", true);
this.Expose("answer_decline", false);
this.Expose("answer_accept", true);
this.Expose("say", new Func<string, bool>(this.Say));
this.Expose("askYesNo", new Func<string, bool>(this.AskYesNo));
}
public override void Dispose()
{
base.Dispose();
mPlayer.NpcConversation = null;
}
private bool Say(string text)
{
this.BoolResult = new WaitableResult<bool>();
using (OutPacket outPacket = mNpc.GetDialogPacket(ENpcDialogType.Standard, text, 0, 0))
{
mPlayer.Client.SendPacket(outPacket);
}
this.BoolResult.Wait();
return this.BoolResult.Value;
}
private bool AskYesNo(string text)
{
this.BoolResult = new WaitableResult<bool>();
using (OutPacket outPacket = mNpc.GetDialogPacket(ENpcDialogType.YesNo, text))
{
mPlayer.Client.SendPacket(outPacket);
}
this.BoolResult.Wait();
return this.BoolResult.Value;
}
}
public abstract class ScriptBase
{
private string mPath;
private Thread mThread;
private MoonSharp.Interpreter.Script mScript;
public ScriptBase(string path, bool useThread = false, CoreModules modules = CoreModules.None)
{
mPath = path;
if (useThread) mThread = new Thread(new ThreadStart(() => mScript.DoFile(mPath)));
mScript = new MoonSharp.Interpreter.Script(modules);
}
public void Execute()
{
if (mThread != null)
{
mThread.Start();
}
else
{
mScript.DoFile(mPath);
}
}
public virtual void Dispose()
{
if (mThread != null)
{
mThread.Abort();
mThread = null;
}
}
protected void Expose(string key, object value)
{
mScript.Globals[key] = value;
}
}
这是一个脚本的例子:
say('test')
say('some more stuff')
say('good bye')
当玩家启动与NPC的聊天并完成它而不中断它(也就是使用End Chat
按钮关闭它)时,线程应该自行中止(因为它完成了所有的指示)。
然而,当玩家在完成执行之前中止聊天时,我正在调用Dispose
按钮手动中止线程 - 但我不确定这是正确的方法。
每次玩家开始聊天时,我的内存使用量也会增加1 MB,所以这也很奇怪。
答案 0 :(得分:0)
由于 Quantic 已经解决,通过Thread.Abort
中止线程会引发意外 - 甚至是危险的结果。 Quantic 还解决了处理线程的首选模式,通常是线程循环并检查终止标志,如果您正在执行任意Lua脚本,这是不可能的。
您可以选择创建简约调试器并将其附加到脚本中。连接此调试器后,将为正在运行的脚本中的每条指令调用IDebugger.GetAction()
方法。 如果在GetAction()
来电期间你提出了Exception
,则调试程序将彻底终止该脚本。
Here is an example使用这种技术停止长时间运行的脚本的标记,简约调试器。如您所见,大多数实现都是空的。在您的情况下,您可以只使用一个布尔标志来指示必须终止脚本,而不是使用正在运行的指令计数器。
public DebuggerAction GetAction(int ip, SourceRef sourceref)
{
if( _abortScript )
throw new MyException(); // abort cleanly
// Proceed running the next statement
return new DebuggerAction() {
Action = DebuggerAction.ActionType.StepIn,
};
}
关于您遇到的内存增加,这是手动生成线程each thread initializes its own stack space and on a 32-bit process, the default is precisely 1MB的一个不便之处。有Thread constructor overload允许您自定义堆栈大小;根据脚本的复杂程度,可以将此值设置为较小的值,而不会有堆栈溢出情况的风险。最小化内存使用的另一个选择是使用Task.Run
而不是生成自己的线程(这使用了线程池),但是不使用Thread.Abort
将强制强>,你不想去杀死汇集的线程!