Are there best practices for implementing an asynchronous game engine loop?
Asked Answered
V

2

7

I have implemented a game engine loop as follows:

public static Boolean Start ( )
{
    if (hasBoard)
    {
        //  start engine on worker thread
        asyncTask = new AsyncResult ( stopEngine, asyncTask );
        isRunning = ThreadPool.QueueUserWorkItem ( startEngine, asyncTask );

        if (isRunning)
        {
            Console.WriteLine ( "[{0}] Engine started",
                DateTime.Now.ToString ( "hh:mm:ss" ) );
        }
        else
        {
            Console.WriteLine ( "[{0}] Engine failed to start",
                DateTime.Now.ToString ( "hh:mm:ss" ) );
        }
    }

    return isRunning;
}

public static void Stop ( )
{
    Console.WriteLine ( "[{0}] Engine stopping",
        DateTime.Now.ToString ( "hh:mm:ss" ) );

    asyncTask.SetAsCompleted ( null, false );
}

private static void startEngine ( Object task )
{
    while (!( (IAsyncResult)task ).IsCompleted)
    {
        Thread.Sleep ( 10000 );

        Console.WriteLine ( "[{0}] Engine running",
            DateTime.Now.ToString ( "hh:mm:ss" ) );
    }
}

private static void stopEngine ( IAsyncResult iaResult )
{
    //  clean up resources

    Console.WriteLine ( "[{0}] Engine stopped", 
        DateTime.Now.ToString ( "hh:mm:ss" ) );

    isRunning = false;
}

I am using the AsyncResult class recommended by Jeff Richter in his article, Implementing the CLR Asynchronous Programming Model. In order to be able to stop the engine from the UI, the implementation I used was a bit off from the standard asynchronous pattern. This implementation works as expected, but when I divert from a standard practice, I revert to the SO community to ensure I am doing things the right way.

Are there any issues with this implementation that anyone can see?

Vial answered 2/1, 2011 at 3:33 Comment(4)
in a game engine loop, wouldn't you simply spawn a new thread rather than using a pool thread?Lodi
@Mitch: I'm not really sure. I believe that spawning a new thread is standard. I don't know that there is anything wrong with using a Threadpool thread. This game engine is super lightweight. Also, since a WinForms UI is in control, I thought this would be a good way to work the game loop.Vial
Why are you using any threads in a game loop? If you need any (which should really be questioned well), the threads should likely be pre-spawned threads for a specific well-defined purpose (e.g. always 2 threads, etc. that live continuously) that work off a queue or other synchronization. However, best is likely just to keep it "KISS".Bria
@pst: probably because this is my first attempt at building and understanding a very simple game engine.Vial
G
11

Since this sounds like a project you have control over, I would suggest you ditch APM and use the Task-based model provided in .NET4. This is the recommended approach for .NET4 instead of APM. The Task class is part of the Task Parallel Library (TPL) but it's great for these basic asynchronous jobs as well.

private CancellationTokenSource cts;

public void StartEngine()
{
    if (cts == null)
    {
        cts = new CancellationTokenSource();
        Task.Factory.StartNew(() => GameLoop(cts.Token), cts.Token);
    }
}

private void GameLoop(CancellationToken token)
{
    while (true)
    {
        token.ThrowIfCancellationRequested();
        Thread.Sleep(1000);
        Debug.WriteLine("working...");
    }
}

public void StopEngine()
{
    if (cts != null)
    {
        cts.Cancel();
        cts = null;
    }
}
Gain answered 2/1, 2011 at 6:8 Comment(2)
Agreed, but I would add a LongRunning option when creating the task.Puissant
I made the modification to add the LongRunning option: Task.Factory.StartNew ( ( ) => startEngine ( cancelSource.Token ), cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default );Vial
D
2

I would say that It's implied that tasks are short-lived when using the thread pool since the number of threads in it is limited.

In you case I would use a BackgroundWorker instead since you mentioned winforms. BW handles synchronization for you and are therefore able to update the GUI without using InvokeRequired etc.

Desperate answered 2/1, 2011 at 6:17 Comment(3)
It's implied that tasks are short-lived when using the thread pool - not according to Jeff Richter...unless I really missed something. However, I do see your point in keeping threadpool thread-tasks short.Vial
@IAbstract: Do you have a reference for that? Would be interesting to read.Desperate
I would have to find it again - it's been well over a year and a half since I made the comment. The implication that a task is short lived could be due to the default treatment - i.e., we have to set a task as long running if we expect long execution time. There must have been something meaningful in what I commented about, but I'll try to find what I referring to. ;)Vial

© 2022 - 2024 — McMap. All rights reserved.