Skip to main content
Post Undeleted by Ben Aaronson
deleted 44 characters in body
Source Link
Ben Aaronson
  • 5.8k
  • 22
  • 40

You're alongMost of the right lines in identifying that IEnumerable andtime, a simple IEnumeratorforeach are very powerful concepts that can often help you in situations like this. However, having to directly deal withiteration over an IEnumeratorIEnumerable<T> is usually annoyingwhat you want for iterative logic. But in some cases- that's why things like IEnumerable, foreach and yieldtree-related algorithms are therean area this often occurs- a different data structure will serve you better. In this case, considerI'd suggest storing your children in a wrapper classStack. Then your loop can look like:

class OneTimeEnumerable<T> : IEnumerable<T> while(subTasks.Any())
    {
    private IEnumerator<T> _inner;
  var next = subtasks.Pop();
    public OneTimeEnumerable   Status subtaskStatus = next.Tick(IEnumerable<T> sourceai );
        switch( subtaskStatus ){
        _inner = source  case Status.GetEnumerator();SUCCESS: 
    }

    public IEnumerator<T> GetEnumerator()      break; //Break switch and move on to next subtask tick..
    {        case Status.FAILURE:
                return _inner;
subtaskStatus; //Back out early }when any subtask fails.. 
            case Status.RUNNING:
    IEnumerator IEnumerable           subtasks.GetEnumeratorPush(next)
 ; //Push it back {onto the stack so it'll be first in line next time
                return GetEnumerator();subtaskStatus; //Back out early and resume/retick running task time!
        }
    }

All this does is give you an IEnumerable<T> which always returns the same enumerator back (with its Current in the same position), rather thanAs a new one each time. Then you can store this, and foreach over it as usualside note, rather than havingif you do have cause to deal with too-low-level things like MoveNexts.

Note, by the way, that I'm using the genericuse IEnumerator and IEnumerable. You, you should pretty much always do thisuse the generic versions, in the same way that you'd always choose to use a List<Task> to hold tasks rather than a List<object>

You're along the right lines in identifying that IEnumerable and IEnumerator are very powerful concepts that can often help you in situations like this. However, having to directly deal with an IEnumerator is usually annoying- that's why things like IEnumerable, foreach and yield are there. In this case, consider a wrapper class:

class OneTimeEnumerable<T> : IEnumerable<T>
{
    private IEnumerator<T> _inner;

    public OneTimeEnumerable(IEnumerable<T> source)
    {
        _inner = source.GetEnumerator();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _inner;
    }
    
    IEnumerator IEnumerable.GetEnumerator()
     {
        return GetEnumerator();
    }
}

All this does is give you an IEnumerable<T> which always returns the same enumerator back (with its Current in the same position), rather than a new one each time. Then you can store this, and foreach over it as usual, rather than having to deal with too-low-level things like MoveNexts.

Note, by the way, that I'm using the generic IEnumerator and IEnumerable. You should pretty much always do this, in the same way that you'd always choose to use a List<Task> to hold tasks rather than a List<object>

Most of the time, a simple foreach iteration over an IEnumerable<T> is what you want for iterative logic. But in some cases- and tree-related algorithms are an area this often occurs- a different data structure will serve you better. In this case, I'd suggest storing your children in a Stack. Then your loop can look like:

    while(subTasks.Any())
    {
        var next = subtasks.Pop();
        Status subtaskStatus = next.Tick( ai );
        switch( subtaskStatus ){
            case Status.SUCCESS: 
                break; //Break switch and move on to next subtask tick..
            case Status.FAILURE:
                return subtaskStatus; //Back out early when any subtask fails.. 
            case Status.RUNNING:
                subtasks.Push(next); //Push it back onto the stack so it'll be first in line next time
                return subtaskStatus; //Back out early and resume/retick running task time!
        }
    }

As a side note, if you do have cause to use IEnumerator and IEnumerable, you should pretty much always use the generic versions, in the same way that you'd always choose to use a List<Task> to hold tasks rather than a List<object>

Post Deleted by Ben Aaronson
Source Link
Ben Aaronson
  • 5.8k
  • 22
  • 40

Your situation

Sometimes the best way to solve a problem is to avoid it. Why not make Status a property on Task rather than a return value? Then you can just check the status of each sub-task before ticking, and skip it if its status is SUCCESS

In General

You're along the right lines in identifying that IEnumerable and IEnumerator are very powerful concepts that can often help you in situations like this. However, having to directly deal with an IEnumerator is usually annoying- that's why things like IEnumerable, foreach and yield are there. In this case, consider a wrapper class:

class OneTimeEnumerable<T> : IEnumerable<T>
{
    private IEnumerator<T> _inner;

    public OneTimeEnumerable(IEnumerable<T> source)
    {
        _inner = source.GetEnumerator();
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _inner;
    }
    
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

All this does is give you an IEnumerable<T> which always returns the same enumerator back (with its Current in the same position), rather than a new one each time. Then you can store this, and foreach over it as usual, rather than having to deal with too-low-level things like MoveNexts.

Note, by the way, that I'm using the generic IEnumerator and IEnumerable. You should pretty much always do this, in the same way that you'd always choose to use a List<Task> to hold tasks rather than a List<object>

Etc.

For more general comments, your code is mostly good stylistically. Naming conventions, actual names, and so on, are good ("Sequencer" is possibly a little iffy, but I can't be sure without knowing exactly what it does differently from other potential CompositeTask implementations)

Two ways in which your code doesn't follow standard C# conventions is your use of all-uppercase for enum values, and having opening braces on the same line. Both of these are a matter of preference, but it's usually a good idea to follow language conventions.