0

i am a CS student trying to build a game about space battle that happen in "real time". i have nailed the base code and it works well with small battles ( less than 20 spaceships ) but the more spaceships i add, the longer the program takes to start the battle.

Here is some relevant code :

class Weapon
{
    SpaceShip Ship;
    public bool Loaded;
    public int Damage;
    int Hits;

    public int ReloadTime;
    public Task ReloadTask;


    public void Fire(SpaceShip Target)
    {
        if (!Loaded)
            return;

        ReloadTask = Task.Run(() =>
        {
            UseGun(Target);
        });
    }

    private void UseGun(SpaceShip Target)
    {
        Loaded = false;

        Target?.TakeDamage(Damage);

        Hits++;

        Thread.Sleep(ReloadTime);

        Loaded = true;
    }

    + some irrelevant methods
}

class SpaceShip
{
    public bool Alive;
    public int Armor;
    public int MaxArmor;
    public string Name;

    Weapon[] Weapons;

    public SpaceShip Target;
    public bool WeaponsActive; 

    public Weapon WaitForNextReload(int delayMax = -1)
    {
        var TaskArray = Weapons.Select(x => x.ReloadTask).ToArray();

        int index = Task.WaitAny(TaskArray, delayMax);
        return (index >= 0) ? Weapons[index] : null;
    }

    public void FireWeapons()
    {
        if (Weapons.Length == 0)
            return;

        Task.Run(() =>
        {
            foreach (Weapon W in Weapons)
            {
                W.Fire(Target);
            }

            while (ValidTarget())
            {
                Weapon W = WaitForNextReload();
                W?.Fire(Target);
            }
        });
    }

    private bool ValidTarget() 
    {
        return Alive && Target != null && Target.Alive;
    }

    + some irrelevant methods
}

    

As you can see i use a Task system to simulate each gun reloading in 'real time'. When i have too many Spaceships in a battle, the program will start with no problem, but the Spaceships will stay idle and don't fire at their target even tho their weapons are ready to fire. This delay increase the more Spaceships i have ( each entity can have upward of 10 weapons so 10 tasks x each spaceship ).

While the "waiting" is happenning, my cpu is at 0 - 3% usage.

Is there some invisible overhead with the tasks that slows me down ? How can i do to reduce the wait ?

  • 3
    I would say that issue here is not with task overhead but with [thread starvation](https://stackoverflow.com/questions/45036635/detecting-diagnosing-thread-starvation). – Guru Stron Jan 21 '21 at 17:54
  • 2
    Remove the `Task.Run`, make UseGun true async by declaring it as `public async Task UseGunAsync()` and replace Thread.Sleep with `await Task.Delay`. This way you don’t waste threads, which leads to thread starvation as Guru Stron mentioned. – ckuri Jan 21 '21 at 18:10
  • 2
    Also you shouldn’t use WaitAny, but WhenAny and make the calling method async too, as well as its calling method and so on. If you want to use async/Tasks it’s async all the way to the top. – ckuri Jan 21 '21 at 18:15
  • Are you doing this in pure C# or are you using Unity? also you might make your game more efficient by using a "Game Loop" to properly do state updates instead of handling endless tasks – Jonathan Alfaro Jan 21 '21 at 18:22
  • Thank you for your responses ! i didn't know thread starvation was a thing. – Benoît Soupart Jan 21 '21 at 19:43

1 Answers1

2

Thread.Sleep(ReloadTime);

This is a major code smell. This will block the thread, potentially forcing the threadpool to create more threads, that use more resources and may cause additional overhead. Using async and await Task.Delay(ReloadTime) avoids some of the overhead. There is a similar case with Task.WaitAny that can be replaced with Task.WhenAny

It is worth noting what you are actually doing here. You are not actually doing anything concurrently. Nothing compute or IO heavy at all. So it should be fairly simple to rewrite the code, removing Task.Run and inserting async and await where needed. In short, removing all cases where the thread will block.

However, what async/await is doing is creating a state machine behind the scene. This is really convenient since it is very easy to write, but it does imply some overhead. If you have high performance requirements it might be more efficient to write this state machine yourself instead. This is common in games since lots of game objects can be described as state machines. Commonly you would update these state machines at a fixed rate.

Edit The threadpool will limit the thread creation rate, it also have a limit on how many threads it will create per process. So you might run into cases where there are no threadpool threads available since they are all blocked. So your program will not progress until one of the existing threads unblocks. As you might realize this is quite inefficient.

JonasH
  • 28,608
  • 2
  • 10
  • 23