Delay command execution over sockets
- by David
I've been trying to fix the game loop in a real time (tick delay) MUD. I realized using Thread.Sleep would seem clunky when the user spammed commands through their choice of client (Zmud, etc) e.g. east;south;southwest would wait three move ticks and then output everything from the past couple rooms.
The game loop basically calls a Flush and Fill method for each socket during each tick (50ms)
private void DoLoop()
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (running)
{
// for each socket, flush and fill
ConnectionMonitor.Update();
stopWatch.Stop();
WaitIfNeeded(stopWatch.ElapsedMilliseconds);
stopWatch.Reset();
}
}
The Fill method fires the command events, but as mentioned before, they currently block using Thread.Sleep.
I tried adding a "ready" flag to the state object that attempts to execute the command along with a queue of spammed commands, but it ends up executing one command and queuing up the rest i.e. each subsequent command executes something that got queued up that should've been executed before. I must be missing something about the timer.
private readonly Queue<SpammedCommand> queuedCommands = new Queue<SpammedCommand>();
private bool ready = true;
private void TryExecuteCommand(string input)
{
var commandContext = CommandContext.Create(input);
var player = Server.Current.Database.Get<Player>(Session.Player.Key);
var commandInfo = Server.Current.CommandLookup
.FindCommand(commandContext.CommandName, player.IsAdmin);
if (commandInfo != null)
{
if (!ready)
{
// queue command
queuedCommands.Enqueue(new SpammedCommand()
{
Context = commandContext,
Info = commandInfo
});
return;
}
if (queuedCommands.Count > 0)
{
// queue the incoming command
queuedCommands.Enqueue(new SpammedCommand()
{
Context = commandContext,
Info = commandInfo,
});
// dequeue and execute
var command = queuedCommands.Dequeue();
command.Info.Command.Execute(Session, command.Context);
setTimeout(command.Info.TickLength);
return;
}
commandInfo.Command.Execute(Session, commandContext);
setTimeout(commandInfo.TickLength);
}
else
{
Session.WriteLine("Command not recognized");
}
}
Finally, setTimeout was supposed to set the execution delay (TickLength) for that command, and makeReady just sets the ready flag on the state object to true.
private void setTimeout(TickDelay tickDelay)
{
ready = false;
var t = new System.Timers.Timer()
{
Interval = (long) tickDelay,
AutoReset = false,
};
t.Elapsed += makeReady;
t.Start(); // fire this in tickDelay ms
}
// MAKE READYYYYY!!!!
private void makeReady(object sender, System.Timers.ElapsedEventArgs e)
{
ready = true;
}
Am I missing something about the System.Timers.Timer created in setTimeout? How can I execute (and output) spammed commands per TickLength without using Thread.Sleep?