Sadly there isn’t a lot new that I can post since I’ve hardly had time to work on my pet project during the last week - a few friends from Texas and California came over for the Oktoberfest and it suffices to say that I drank a lot of beer last week.

Nonetheless there is some progress and I’ve finished support for stepping in break mode (although I still have no idea what enum_STEPKIND.STEP_BACKWARDS is about since I’ve never heard of stepping backwards before).

There is no screenshot this time because you wouldn’t really see a difference, except if I did, before/after screenshots to show that the stepping works.

A few coding notes as always:

  • The current TorqueScript telnet debugger doesn’t really tell you whether it breaks because of stepping or because it has hit a breakpoint, so I have to figure it myself. It is pretty straightforward though because stepping usually implies a certain callstack change (or the lack thereof) when finished:

    • “Step Into” implies that the new callstack height > old height
    • “Step Over” implies no change
    • “Step Out” implies of course: new callstack height < old height

So it’s pretty easy to figure out whether the stepping was finished or interrupted by a breakpoint.

  • The class diagram designer/viewer in Visual Studio 200x is awesome. If you haven’t tried it, you’ve missed a really helpful feature of Visual Studio. Lately I’ve been using it to comment my public functions and parameters, etc. and for small refactorings (like name changes) in general.

Last but not least I think I’ve written a neat communication class for synchronous and asynchronous message processing which I’d like to share (if you’re going to use it though, please tell me and/or send me some feedback):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace BlackHC.VSTorqueScript.DebugEngine
{
    class Communication
    {
        [Flags]
        public enum ReturnAction
        {
            ContinueSearch = 0,
            AcceptMessage = 1,
            RemoveHandler = 2,
            Done = AcceptMessage | RemoveHandler
        }

        /// <summary>
        /// Delegate Communication will call to send a message
        /// </summary>
        public delegate void SendMessageDelegate(string message);
        /// <summary>
        /// Delegate which Communication will call to read a message
        /// </summary>
        public delegate string ReadMessageDelegate();

        public delegate void ReplyMessageHandler(string message);
        public delegate ReturnAction MessageHandler(Communication sender, string message);

        /// <summary>
        /// Events in this list are invoked in reverse order (to allow FILO-style message handler processing).
        /// Lock MessageHandlers when adding message handlers
        /// </summary>
        public event MessageHandler MessageHandlers;

        private readonly ReadMessageDelegate ReadMessage;
        private readonly SendMessageDelegate SendMessage;

        private Thread asyncMessageReceiver;
        private object asyncLock = new object();

        /// <param name="readMessage">Delegate to read messages from</param>
        /// <param name="sendMessage">Delegate to send messages with</param>
        public Communication(ReadMessageDelegate readMessage, SendMessageDelegate sendMessage)
        {
            ReadMessage = readMessage;
            SendMessage = sendMessage;
        }

        /// <summary>
        /// Start processing incoming messages by starting the asynchronous processing thread
        /// </summary>
        public void StartReceiving()
        {
            if (asyncMessageReceiver == null)
            {
                asyncMessageReceiver = new Thread(ReceiveMessages);
                asyncMessageReceiver.Start();
            }
        }

        /// <summary>
        /// End the asynchronous receiver thread
        /// </summary>
        public void Close()
        {
            if (asyncMessageReceiver != null)
            {
                asyncMessageReceiver.Abort();
            }
        }

        /// <summary>
        /// Send a simple message
        /// </summary>
        public void Send(string message)
        {
            SendMessage(message);
        }

        /// <summary>
        /// Send a request and wait for a reply that starts with the expected string. Time-out after a specified period of time
        /// </summary>
        /// <param name="request">Request to send</param>
        /// <param name="replyStartsWith">Start of the reply to wait for</param>
        /// <param name="millisecondsTimeout">Timeout time in msecs (-1 for infinite)</param>
        /// <param name="timedOut">Out variable to be set depending of whether the request timed-out or not</param>
        /// <returns>remaining part of the found message (after the replyStartsWith part)</returns>
        public string SyncRequest(string request, string replyStartsWith, int millisecondsTimeout, out bool timedOut)
        {
            AutoResetEvent waitHandler = new AutoResetEvent(false);
            String reply = "";

            MessageHandler handler = delegate(Communication sender, string message)
            {
                if (message.StartsWith(replyStartsWith))
                {
                    reply = message.Substring(replyStartsWith.Length);
                    waitHandler.Set();
                    return ReturnAction.Done;
                }
                else
                {
                    return ReturnAction.ContinueSearch;
                }
            };

            lock (asyncLock)
            {
                Send(request);
                MessageHandlers += handler;
            }

            timedOut = waitHandler.WaitOne(millisecondsTimeout, false);
            if (!timedOut)
            {
                // remove the handler again, since we timed-out
                lock (asyncLock)
                {
                    MessageHandlers -= handler;
                }
            }
            return reply;
        }

        public string SyncRequest(string request, string replyStartsWith, int millisecondsTimeout)
        {
            bool timedOut;
            return SyncRequest(request, replyStartsWith, millisecondsTimeout, out timedOut);
        }

        // TODO: add a time-out here, too? [9/18/2008 Andreas]
        /// <summary>
        /// Starts an asynchronous request.
        /// </summary>
        /// <param name="request">Message request to be sent</param>
        /// <param name="replyStartsWith">Start of the reply to wait for</param>
        /// <param name="replyMessageHandler">Message handler that is passed the rest of the message (after the replyStartsWith part)</param>
        public void AsyncRequest(string request, string replyStartsWith, ReplyMessageHandler replyMessageHandler)
        {
            MessageHandler handler = delegate(Communication sender, string message)
            {
                if (message.StartsWith(replyStartsWith))
                {
                    string reply = message.Substring(replyStartsWith.Length);
                    replyMessageHandler(reply);
                    return ReturnAction.Done;
                }
                else
                {
                    return ReturnAction.ContinueSearch;
                }
            };

            lock (asyncLock)
            {
                Send(request);
                MessageHandlers += handler;
            }
        }

        private void ReceiveMessages()
        {
            while (true)
            {
                try
                {
                    string message;
                    try
                    {
                        message = ReadMessage();
                    }
                    catch (System.Exception)
                    {
                        // the connection has been closed or something similar has happened, so terminate the thread
                        return;
                    }

                    if (message != null)
                    {
                        ProcessMessage(message);
                    }
                    else
                    {
                        // lost connection, terminate the thread, too
                        return;
                    }

                }
                catch (System.Exception e)
                {
                    // don't let Visual studio die here
                    Trace.WriteLine("Exception in ReceiveMessages: " + e.ToString());
                }
            }
        }

        private void ProcessMessage(string message)
        {
            if (MessageHandlers != null)
            {
                System.Delegate[] invocationList;
                lock (asyncLock)
                {
                    invocationList = (System.Delegate[])MessageHandlers.GetInvocationList().Clone();
                }

                foreach (System.Delegate eventDelegate in invocationList.Reverse())
                {
                    MessageHandler handler = (MessageHandler)eventDelegate;

                    ReturnAction action = handler.Invoke(this, message);
                    if ((action & ReturnAction.RemoveHandler) != 0)
                    {
                        // FIXME: is this lock necessary? [9/18/2008 Andreas]
                        lock (asyncLock)
                        {
                            MessageHandlers -= handler;
                        }
                    }
                    if ((action & ReturnAction.AcceptMessage) != 0)
                    {
                        break;
                    }
                }
            }
            else
            {
                Trace.WriteLine(String.Format("Communication: Unhandled message - '{0}'", message));
            }
        }
    }
}