Click here to Skip to main content
15,896,493 members
Articles / Artificial Intelligence

Writing a Multiplayer Game (in WPF)

Rate me:
Please Sign up or sign in to vote.
4.93/5 (131 votes)
16 Mar 2012CPOL25 min read 216.8K   17.1K   246  
This article will explain some concepts of game development and how to apply and adapt them for multiplayer development.
using System;
using System.Reflection;
using System.Threading;
using System.Security.Permissions;

namespace Pfz.Threading
{
    /// <summary>
    /// Class that allows thread-aborts to be done in a relativelly safe manner.
    /// </summary>
    public static class SafeAbort
    {
        /// <summary>
        /// Aborts a thread only if it is safe to do so, taking the abort mode into account (which may
        /// range from only guaranteeing that IDisposable objects will be fully constructed, up to 
        /// guaranteeing all "using" blocks to work and even doing some user validations).
        /// Returns if the Thread.Abort() was called or not.
        /// </summary>
        public static bool AbortIfSafe(Thread thread, SafeAbortMode mode=SafeAbortMode.RunAllValidations, object stateInfo=null)
        {
            if (thread == null)
                throw new ArgumentNullException("thread");

            // If for some reason we are trying to abort our actual thread, we simple call Thread.Abort() directly.
            if (thread == Thread.CurrentThread)
                thread.Abort(stateInfo);

            // We check the state of the thread, ignoring if the thread also has Suspended or SuspendRequested in its state.
            switch (thread.ThreadState & ~(ThreadState.Suspended | ThreadState.SuspendRequested))
            {
                case ThreadState.Running:
                case ThreadState.Background:
                case ThreadState.WaitSleepJoin:
                    break;

                case ThreadState.Stopped:
                case ThreadState.StopRequested:
                case ThreadState.AbortRequested:
                case ThreadState.Aborted:
                    return true;

                default:
                    throw new ThreadStateException("The thread is in an invalid state to be aborted.");
            }

            try
            {
                #pragma warning disable 618
                    thread.Suspend();
                #pragma warning restore 618
            }
            catch(ThreadStateException)
            {
                switch (thread.ThreadState & ~(ThreadState.Suspended | ThreadState.SuspendRequested))
                {
                    case ThreadState.Aborted:
                    case ThreadState.Stopped:
                        // The thread terminated just when we are trying to Abort it. So, we will return true to tell that the "Abort" succeeded.
                        return true;
                }

                // we couldn't discover why Suspend threw an exception, so we rethrow it.
                throw;
            }

            // We asked the thread to suspend, but the thread may take some time to be really suspended, so we will wait until it is no more in "SuspendRequested" state.
            while (thread.ThreadState == ThreadState.SuspendRequested)
                Thread.Sleep(1);

            // The Thread ended just when we asked it to suspend. So, for us, the Abort succeeded.
            if ((thread.ThreadState & (ThreadState.Stopped | ThreadState.Aborted)) != ThreadState.Running)
                return true;

            try
            {
                var stack = new System.Diagnostics.StackTrace(thread, false);
                var frames = stack.GetFrames();

                // If we try to Abort the thread when it is starting (really soon), it will not have any frames. Calling an abort here caused some dead-locks for me,
                // so I consider that a Thread with no frames is a thread that can't be aborted.
                if (frames == null)
                    return false;

                bool? canAbort = null;
                // In the for block, we start from the oldest frame to the newest one.
                // In fact, we check this: If the method returns IDisposable, then we can't abort. If this is not the case, then if we are inside a catch or finally
                // block, we can abort, as such blocks delay the normal abort and, so, even if an internal call is inside a constructor of an IDisposable, we
                // will not cause any problem calling abort. That's the reason to start with the oldest frame to the newest one.
                // And finally, if we are not in a problematic frame or in a guaranteed frame, we check if the method has try blocks. If it has, we consider
                // we can't abort. Note that if you do a try/catch and then an infinite loop, this check will consider the method to be inabortable.
                for (int i = frames.Length - 1; i >= 0; i--)
                {
                    var frame = frames[i];
                    var method = frame.GetMethod();

                    // Static constructors naturally delay the normal abort, so if we are inside one, we can abort.
                    // I test by name, as I didn't find an way to test more directly, and I discovered that 
                    // static constructors return IsConstructor as false.
                    if (method.IsStatic && method.IsSpecialName && method.Name == ".cctor")
                    {
                        canAbort = true;
                        break;
                    }

                    // if we are inside a constructor of an IDisposable object or inside a function that returns one, we can't abort.
                    if (method.IsConstructor)
                    {
                        ConstructorInfo constructorInfo = (ConstructorInfo)method;
                        if (typeof(IDisposable).IsAssignableFrom(constructorInfo.DeclaringType))
                        {
                            canAbort = false;
                            break;
                        }
                    }
                    else
                    {
                        MethodInfo methodInfo = (MethodInfo)method;
                        if (typeof(IDisposable).IsAssignableFrom(methodInfo.ReturnType))
                        {
                            canAbort = false;
                            break;
                        }
                    }

                    // Checks if the method, its class or its assembly has HostProtectionAttributes with MayLeakOnAbort.
                    // If that's the case, then we can't abort.
                    var attributes = (HostProtectionAttribute[])method.GetCustomAttributes(typeof(HostProtectionAttribute), false);
                    foreach (var attribute in attributes)
                    {
                        if (attribute.MayLeakOnAbort)
                        {
                            canAbort = false;
                            break;
                        }
                    }
                    attributes = (HostProtectionAttribute[])method.DeclaringType.GetCustomAttributes(typeof(HostProtectionAttribute), false);
                    foreach (var attribute in attributes)
                    {
                        if (attribute.MayLeakOnAbort)
                        {
                            canAbort = false;
                            break;
                        }
                    }
                    attributes = (HostProtectionAttribute[])method.DeclaringType.Assembly.GetCustomAttributes(typeof(HostProtectionAttribute), false);
                    foreach (var attribute in attributes)
                    {
                        if (attribute.MayLeakOnAbort)
                        {
                            canAbort = false;
                            break;
                        }
                    }

                    var body = method.GetMethodBody();
                    if (body == null)
                        continue;

                    // if we were inside a finally or catch, we can abort, as the normal Thread.Abort() will be naturally delayed.
                    int offset = frame.GetILOffset();
                    foreach (var handler in body.ExceptionHandlingClauses)
                    {
                        int handlerOffset = handler.HandlerOffset;
                        int handlerEnd = handlerOffset + handler.HandlerLength;

                        if (offset >= handlerOffset && offset < handlerEnd)
                        {
                            canAbort = true;
                            break;

                        }

                        if (canAbort.GetValueOrDefault())
                            break;
                    }
                }

                if (canAbort == null)
                {
                    if (mode == SafeAbortMode.AllowUsingsToFail)
                        canAbort = true;
                    else
                    {
                        // we are inside an unsure situation. So, we will try to check the method.
                        var frame = frames[0];
                        var method = frame.GetMethod();
                        var body = method.GetMethodBody();
                        if (body != null)
                        {
                            var handlingClauses = body.ExceptionHandlingClauses;
                            if (handlingClauses.Count == 0)
                            {
                                canAbort = true;

                                // Ok, by our tests we can abort. But, if the mode is RunAllValidations and there are user-validations, we must
                                // run them.
                                if (mode == SafeAbortMode.RunAllValidations)
                                {
                                    var handler = Validating;
                                    if (handler != null)
                                    {
                                        SafeAbortEventArgs args = new SafeAbortEventArgs(thread, stack, frames);
                                        handler(null, args);

                                        // The args by default has its CanAbort set to true. But, if any handler changed it, we will not be able to abort.
                                        canAbort = args.CanAbort;
                                    }
                                }
                            }
                        }
                    }
                }

                if (canAbort.GetValueOrDefault())
                {
                    try
                    {
                        // We need to call abort while the thread is suspended, that works, but causes an exception, so we ignore it.
                        thread.Abort(stateInfo);
                    }
                    catch
                    {
                    }

                    return true;
                }

                return false;
            }
            finally
            {
                #pragma warning disable 618
                    thread.Resume();
                #pragma warning restore 618
            }
        }

        /// <summary>
        /// Aborts a thread, trying to use the safest abort mode, until the unsafest one.
        /// The number of retries is also the expected number of milliseconds trying to abort.
        /// </summary>
        public static bool Abort(Thread thread, int triesWithAllValidations, int triesIgnoringUserValidations, int triesAllowingUsingsToFail, bool finalizeWithNormalAbort = false, object stateInfo = null)
        {
            if (thread == null)
                throw new ArgumentNullException("thread");

            for (int i = 0; i < triesWithAllValidations; i++)
            {
                if (AbortIfSafe(thread, SafeAbortMode.RunAllValidations, stateInfo))
                    return true;

                Thread.Sleep(1);
            }

            for (int i = 0; i < triesIgnoringUserValidations; i++)
            {
                if (AbortIfSafe(thread, SafeAbortMode.IgnoreUserValidations, stateInfo))
                    return true;

                Thread.Sleep(1);
            }

            for (int i = 0; i < triesAllowingUsingsToFail; i++)
            {
                if (AbortIfSafe(thread, SafeAbortMode.AllowUsingsToFail, stateInfo))
                    return true;

                Thread.Sleep(1);
            }

            if (finalizeWithNormalAbort)
            {
                thread.Abort(stateInfo);
                return true;
            }

            return false;
        }

        /// <summary>
        /// Event invoked by AbortIfSafe if user validations are valid and when it is unsure if the thread
        /// is in a safe situation or not.
        /// </summary>
        public static event EventHandler<SafeAbortEventArgs> Validating;
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions