Question

I am implementing a fiber system for running game AI scripts and I ran into a small problem.

I am using yield return x to signify a wait x frames in the scripts. I wanted to encapsulate the logic of calculating x frames as other events can modify how many frames to wait. My ideal solution would be something as follows:

public Boolean removeScript = false;

public IEnumerable update()
{
    while(true)
    {
        shootAtPlayer()
        wait(30)
    }
}

private IEnumerable wait(int x)
{
    yield return removeScript ? -1 : x;
}

wait() either instructs to yield return 30 or returns -1 if the script is no longer needed. The iterator would handle the -1 return by removing the script. If x > 0 the iterator would decrement the return every frame until 0 then call update() again.

However this doesn't work as of course the yield return from wait() is not propagated to the update method. This leads to code duplication, worse modularity and less readable code:

public IEnumerable update()
{
    while(true)
    {
        shootAtPlayer()
        yield return removeScript ? -1 : x;
    }
}

I was wondering if there is a better way to structure this? or is there a language feature I am missing that would be useful in this case?

Was it helpful?

Solution

In this specific case, the solution is relatively simple: make Wait() return just the integer:

public IEnumerable Update()
{
    while (true)
    {
        ShootAtPlayer();
        yield return Wait(30);
    }
}

private int Wait(int x)
{
    return removeScript ? -1 : x;
}

In more complicated cases, you can use foreach, though this makes the syntax much more verbose:

public IEnumerable Update()
{
    while (true)
    {
        ShootAtPlayer();

        foreach (var x in Wait(30))
            yield return x;
    }
}

private IEnumerable Wait(int x)
{
    yield return removeScript ? -1 : x;
}

As you can see, you can (ab)use yield return to implement fibers, but yield return was never meant for this, so it won't work that well.

What was made for this kind of asynchronous continuations is the new async-await. With that, your code could look for example something like this:

public async Task Update()
{
    while (true)
    {
        ShootAtPlayer();
        await Wait(30);
    }
}

private async Task Wait(int x)
{
    await fiber.Wait(removeScript ? -1 : x);
}

As a final note, I think the way you're using removeScript is not a good idea. End of a script should be signified by the Update() method actually completing (the enumerable doesn't have any more items, or the Task completes), not by returning a magic value.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top