C# Loop with threading

Started by
5 comments, last by Nicholas Kong 10 years, 9 months ago

I have been researching game loops and thread. I post my code below to accomplish this. Does this look correct to anyone or have a better solution for multithreading a game loop?



// Loop
using System;

namespace Client.Plateform.Window.Lifetime
{
    public class Loop : Client.Plateform.Window.OperatingSystem.Threads
    {
        /// <summary>
        ///
        /// </summary>
        private static bool _IsExit = false;

        /// <summary>
        ///
        /// </summary>
        public static bool IsExit
        {
            get
            {
                return _IsExit;
            }
            set
            {
                _IsExit = value;
            }
        }

        /// <summary>
        ///
        /// </summary>
        public Loop()
        {
        }

        /// <summary>
        ///
        /// </summary>
        public void Excute()
        {
            base.RenderThread.Start();
            base.LogicThread.Start();
            base.ResourceThread.Start();
            base.SoundThread.Start();
        }
    }
}
// Thread
using System;
using System.Threading;

namespace Client.Plateform.Window.OperatingSystem
{
    public class Threads
    {
        #region Global Var

        private ThreadStart _RenderThreadStart = null;
        private Thread _RenderThread = null;

        private ThreadStart _SoundThreadStart = null;
        private Thread _SoundThread = null;

        private ThreadStart _ResourceThreadStart = null;
        private Thread _ResourceThread = null;

        private ThreadStart _LogicThreadStart = null;
        private Thread _LogicThread = null;

        #endregion

        #region Accessors

        /// <summary>
        /// asdfadsf
        /// </summary>
        public Thread RenderThread
        {
            get
            {
                return _RenderThread;
            }
        }

        /// <summary>
        /// asdfadsf
        /// </summary>
        public Thread SoundThread
        {
            get
            {
                return _SoundThread;
            }
        }

        /// <summary>
        /// asdfadsf
        /// </summary>
        public Thread ResourceThread
        {
            get
            {
                return _ResourceThread;
            }
        }

        /// <summary>
        /// asdfadsf
        /// </summary>
        public Thread LogicThread
        {
            get
            {
                return _LogicThread;
            }
        }

        #endregion

        #region Constructor

        /// <summary>
        ///
        /// </summary>
        public Threads()
        {
            _RenderThreadStart = new ThreadStart(() => RenderMethod());
            _RenderThread = new Thread(_RenderThreadStart);

            _SoundThreadStart = new ThreadStart(() => SoundMethod());
            _SoundThread = new Thread(_SoundThreadStart);

            _ResourceThreadStart = new ThreadStart(() => ResourceMethod());
            _ResourceThread = new Thread(_ResourceThreadStart);

            _LogicThreadStart = new ThreadStart(() => LogicMethod());
            _LogicThread = new Thread(_LogicThreadStart);
        }

        #endregion

        #region Method

        /// <summary>
        ///
        /// </summary>
        private void RenderMethod()
        {
            while (!Plateform.Window.Lifetime.Loop.IsExit)
            {
                Console.WriteLine("Render Method");
                Thread.Sleep(3000);
            }
        }

        /// <summary>
        ///
        /// </summary>
        private void SoundMethod()
        {
            while (!Plateform.Window.Lifetime.Loop.IsExit)
            {
                Console.WriteLine("Sound Method");
                Thread.Sleep(8000);
            }
        }

        /// <summary>
        ///
        /// </summary>
        private void ResourceMethod()
        {
            while (!Plateform.Window.Lifetime.Loop.IsExit)
            {
                Console.WriteLine("Resource Method");
                Thread.Sleep(6000);
            }
        }

        /// <summary>
        ///
        /// </summary>
        private void LogicMethod()
        {
            while (!Plateform.Window.Lifetime.Loop.IsExit)
            {
                Console.WriteLine("Logic Method");
                Thread.Sleep(13000);
            }
        }

        #endregion
    }
}


Thou Curator : Indie Game Development Studio
http://www.thoucurator.com

Advertisement

That's Certainly Multithreaded ...

However i don't think anyone from this code could tell you "Yep thats gunna work".

How are you planning on dealing with race conditions between differing threads?

E.g. the Render thread and Logic thread both need to access the same Object, Render() needs its position to draw it, and Logic() needs it for the physics.

Also, at the end of Exectue() you need some sort of WaitTillAllThreadsFinished() or the loop could start again while the threads are still running? and then your in trouble.

Also it might be work looking into the following classes in C# for multithreading

- ThreadPool.QueueUserWorkItem:: Starts a thread once a pool becomes available.

- ManualResetEvent

Multithreading is hard. Getting the synchronization between completely independent threads right is even harder.

Personally, I firmly believe that thinking in terms of tasks (whether they are actual .Net-Tasks or something else doesn't matter) is a lot simpler, safer and makes the code scale to more cores.

In my little engine I do something like this:


Task fillRenderQueueScene = ...
Task fillRenderQueueShadows = ...
Task fillRenderQueueOther = ...
Task updateUI = ...
Task updateAI = ...
Task flushRenderQueue = ...
Task updateWorld = ...

fillRenderQueueScene.Start();
fillRenderQueueShadows.Start();
fillRenderQueueOther.Start();
updateUI.Start();
updateAI.Start();

var renderQueueTasks = new[] { fillRenderQueueScene, fillRenderQueueShadows, fillRenderQueueOther };
Task.Factory.ContinueWhenAll(renderQueueTasks, tasks => flushRenderQueue.Start());

var updateTasks = new[] { updateUI, updateAI };
Task.Factory.ContinueWhenAll(updateTasks, tasks => updateWorld.Start());

var finalTasks = new[] { flushRenderQueue, updateWorld };
Task.WaitAll(finalTasks);

current project: Roa

I have to agree here that tasks/jobs are the way to go if you want a scalable and easy to maintain solution for a game loop.

Implementing such a task system is another story though, I have no idea to which degree C# supports this natively.

Here are some links which introduce the concept of task-based game schedulers really well:

http://software.intel.com/en-us/articles/do-it-yourself-game-task-scheduling/

http://www.gdcvault.com/play/1012321/Task-based-Multithreading-How-to

I gets all your texture budgets!

I have to agree here that tasks/jobs are the way to go if you want a scalable and easy to maintain solution for a game loop.

Implementing such a task system is another story though, I have no idea to which degree C# supports this natively.

Here are some links which introduce the concept of task-based game schedulers really well:

http://software.intel.com/en-us/articles/do-it-yourself-game-task-scheduling/

http://www.gdcvault.com/play/1012321/Task-based-Multithreading-How-to

the dregree depends on wich version of .NET he pretends to support, on 4.5 it's as easy as using synchronous(normal) methods.

Multithreading certainly is quite hard and there seems to be a lot of ways to make it happen. From all the ways I have experimented with "lockstep" or "double buffered" approach or what ever you want to call it was certainly the easiest to implement with atleast some performance benefits at the cost of more memory usage.

What you do is have the whole gamestate twice in memory. One for reading and one for writing. Have a main thread that only controls other threads using manual reset events or mutexes or any kind of threadsafe signaling systems tough I would personally recommend CountdownEvent as it has the ability to keep track of how many threads are running making a lot of things easier. And make every thread run a single timeframe wait for all of them to complete, swap the buffers and then start the next.

Main loop looking something like this.

1.signal all threads to process next timeframe.
2.wait all threads to complete the timeframe.
3.swap read/write.
4.goto 1.

And the threads are distributed to process different parts.
For processing the actual data example(threadnumber=number of this current thread starting from 0 up to whatever number of threads you want. Unitcount=number of objects that need to be processed. numberofthreads=the number of threads)
We could have for(int i=threadnumber;i<unitcount;i+=numberofthreads) in the logic thread to divede the units to different threads.
The only thing you need to make sure of is that the threads only write changes to those units they have been assigned to. They can however read from each and every unit they need data from to do the neccessary processing.

For rendering and sound we offcourse use the read buffer and since it is never modified during a timeframe you wont run into problems there.
There will never be any race conditions because it does not matter in wich order the same variables are read from the read buffer and the threads never write to variables other threads would write.

The downsides of this approach is that it uses double the memory(propably not an issue for small indie games) and has a lot higher memory bandwith usage(Modernd cpus and their prefetching caches mitigate this a lot if not completely) than the other common approaches. But it is almost trivial to implement even if it is done as an aftertought. It propably won't give you the same performance boost as you would get from the other approaches like using threadpools with tasks but you should try if this is enough for your project.

You can optimize this quite a bit by dividing your data to those that never change and those that do change and only double buffer the latter.

During my experiments this approach was quite efficient when your game was bottlenecked by the game logic.

PS. If you need any random numbers or if you need deltatime for physics calculate those in the main loop or you might end up scratching your head as to why different objects sometimes roll the same results that were supposed to be random or sometimes go faster than others even when they are supposed to go at the same speed.

You can always use the XNA's game loop. When I started with XNA using C#, it had a lot of complex stuff taken care of.

This topic is closed to new replies.

Advertisement