Search Unity

Clone an IEnumerator

Discussion in 'Scripting' started by Diablo404, Oct 24, 2014.

  1. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    HI everyone,

    I need to clone an IEnumerator because the original one gets "consumed" after it's used.

    I found a C# Example in this thread(Ben Maddow answer): http://stackoverflow.com/questions/...erablet-instance-saving-a-copy-of-the-iterati

    So I implemeted it this way:

    Code (CSharp):
    1. public static T Clone<T>(T source) where T : class, IEnumerator
    2.     {
    3.         var sourceType = source.GetType().UnderlyingSystemType;
    4.         var sourceTypeConstructor = sourceType.GetConstructor(new Type[] { typeof(Int32) });
    5.         var newInstance = sourceTypeConstructor.Invoke(new object[] { -2 }) as T;
    6.  
    7.         var nonPublicFields = source.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
    8.         var publicFields = source.GetType().GetFields(BindingFlags.Public | BindingFlags.Instance);
    9.         foreach (var field in nonPublicFields)
    10.         {
    11.             var value = field.GetValue(source);
    12.             field.SetValue(newInstance, value);
    13.         }
    14.         foreach (var field in publicFields)
    15.         {
    16.             var value = field.GetValue(source);
    17.             field.SetValue(newInstance, value);
    18.         }
    19.         return newInstance;
    20.     }
    And test to use it this way:

    Code (CSharp):
    1. IEnumerator MyMethod ( int para )
    2. {
    3.      print("got=" + para );
    4.      yield return 0;
    5. }
    6.  
    7. void Start()
    8. {
    9.      IEnumerator clone = Clone( MyMethod(15) );
    10. }
    But I get a NullReferenceException : Object not set to an instance of an object at the line:
    var newInstance = sourceTypeConstructor.Invoke(new object[] { -2 }) as T;

    I never used Reflection and I don't know how to handle this.

    And maybe, if by the way there is a solution to clone or create a new instance from the original IEnumerator, I'm okay to hear it.

    Thanks in advance guys

    EDIT -> ANSWER at the bottom of the page from @JohnnyA:

     
    Last edited: Oct 25, 2014
  2. numberkruncher

    numberkruncher

    Joined:
    Feb 18, 2012
    Posts:
    953
    Why are you attempting to clone this enumerator? A new one is created each time you invoke the MyMethod(15).
    Code (csharp):
    1. var enum1 = MyMethod(15);
    2. var enum2 = MyMethod(15);
    3. var enum3 = MyMethod(15);
     
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    1) that method doesn't have to be generic seeing as you don't really use the generic for anything. You could just accept IEnumerator as the parameter, and return an IEnumerator.

    2) If you have the function, why not just create a new instance of it by calling the method again? This would effectively be a clone.

    3) Your function appears to be for cloning a generic object by reflecting out and copying its fields (there's an added issue with the implementation because the reflection done does not account for any private fields of the types this object inherits from... the bindingflags used do not account for those.). This is an issue seeing as the 'iterator function' you're cloning doesn't really have much for fields representing its state. It's a bit more complex than that.

    4) Because 'iterator methods' do not implement the 'Reset' method of the IEnumerator interface (it throw an exception), any clone of an iterator method would a) not get any of the first entries in the enumerator that you have already moved over and b) you'd waste up the enumerator in the cloning process (operating all parts of the code in the iterator method).



    tldr; - number 2 is the most important. Why are you even doing this? Just call the method again, a whole new iterator is created.
     
  4. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Actually it works, but I wasn't specific enough. I actually import my method from a method parameter, and then use it to add a listener to a button click like so:
    Code (CSharp):
    1.     IEnumerator MyMethod ( int para )
    2.     {
    3.          print("got=" + para );
    4.          yield return 0;
    5.     }
    6.  
    7.     void AddAction( IEnumerator myAction )
    8. {
    9. gameObject.GetComponent(Button).onClick.AddListener( delegate() {  StartCoroutine( myAction )  } );
    10. }
    11.    
    12.     void Start()
    13.     {
    14.         AddAction( MyMethod ( 15 ) );
    15.     }
    16.  
    And then, MyMethod is only called at first click;

    If I change the delegate for this:
    gameObject.GetComponent(Button).onClick.AddListener( delegate() { var tempIEnum = myAction; StartCoroutine( tempIEnum ) } );

    I have the same issue!
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    I don't see how this has anything to do with your initial question about cloning...
     
  6. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Since the IEnumerator is consumed. I'd like to use the clone in the delegate each time it is called. But, as I said atthr beginning, I'm open to other solutions. The only thing is that I want to preseve the way the Action Method is called: Action ( MyMethod(12) ). And not passing lambdas inside the Action argument.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    I see what you want...

    Why have the AddAction take the enumerator at all? How about something like this?

    Code (csharp):
    1.  
    2.     IEnumerator MyMethod ( int para )
    3.     {
    4.          print("got=" + para );
    5.          yield return 0;
    6.     }
    7.  
    8.     void AddAction(System.Action action)
    9.     {
    10.         gameObject.GetComponent<Button>().onClick.AddListener(action);
    11.     }
    12.  
    13.     void Start()
    14.     {
    15.         AddAction(() => { StartCoroutine(MyMethod(15)); });
    16.     }
    17.  
    This way add action can dual purpose for general actions, or for actions that start coroutines.
     
  8. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Yep, it would work. But as I said in my previous post I want to keep the simple syntax AddAction( Method(15) ) and put the complex part into the AddAction Method. I know it seems a stubborn way of thinking, but it's to simplify the repetitive use of AddAction. Even if it's imply not so efficient way of scripting behind the scene since it's just call at click and not in update.

    If someone have any idea to make it happen, it would definetely make , at least, my month!
     
  9. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    As was said before... you can't clone the enumerator created from an 'iterator method'. Just can't be done.

    I'm offering you the alternative.
     
  10. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    I understand it and apreciate your help. But I can't imagine that it's not doable! I mean, the easy way to pass the function. Maybe passing an IEnumerator is not the best way. But I tried everything, passing a Func<IEnumerator, int>, but neither got it to work. I'm stuck on this one for days and now I wait for a messiah. :confused:
     
  11. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Here's another option. You can create a delegate that represents a coroutine. I do this for other things, and you could use it as well. The delegate is basically this:

    Code (csharp):
    1.  
    2. public delegate IEnumerator CoroutineMethod();
    3.  
    Your AddAction would look like this then:

    Code (csharp):
    1.  
    2. void AddAction(CoroutineMethod routine)
    3. {
    4.     gameObject.GetComponent<Button>().onClick.AddListener(() => { routine(); });
    5. }
    6.  
    7. void Start()
    8. {
    9.     AddAction(this.MyMethod);
    10. }
    11.  
    Downside is that this doesn't support paramters. What we can do for that is:

    Have a generic CoroutineMethod (similar to System.Action with generic inputs)

    Code (csharp):
    1.  
    2. public delegate IEnumerator CoroutineMethod<T>(T param);
    3. public delegate IEnuemrator CoroutineMethod<T1,T2>(T1 param1, T2 param2);
    4. ... so on so forth
    5.  
    And

    Code (csharp):
    1.  
    2. void AddAction(System.Delegate routine, object[] args)
    3. {
    4.     gameObject.GetComponent<Button>().onClick.AddListener(() => {
    5.         var e = routine.DynamicInvoke(args) as IEnumerator;
    6.         if(e != null) StartCoroutine(e);
    7.     }
    8. }
    9.  
    10. void Start()
    11. {
    12.     AddAction(new CoroutineMethod<int>(MyMethod), new object[]{15});
    13. }
    14.  

    Really though... I find all these interfaces to be dirty. Where as the nice simple Action based one is a clean interface. I don't see what's so not "clean" about:

    Code (csharp):
    1.  
    2. void Start()
    3. {
    4.     AddAction(() => { MyMethod(15); });
    5. }
    6.  
    It's pretty straight forward.
     
    Last edited: Oct 24, 2014
    TimTamTomo likes this.
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Your original question about cloning an IEnumerator that was created from an iterator method... sorry, no, it can not be cloned. It's not the way that the iterator method works in .net/mono.
     
  13. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Well, I didn't play wih Delegate passed through method. Interesting. But yep definetely not helping the syntax. Too bad..
    And to be clear, I'm not really saying that the use of lambdas is not clean, but not easy to understand regarding the syntax for the beginners, which is what the script I'm trying to do is for.

    For now, if I can't find any other way, which I would prefer not, is to pass as I said, an Func < object, Ienumerator> + a second object argument.

    So the call will be something like this : AddAction( MyMethod, 12 ); But this imply defining lots of AddActions with diferents objects like int, float, string, Component, etc.

    But yep, as your last post says, the question was about cloning an IEnumerator, and aparently it's not doable..! Thanks for your help
     
    Last edited: Oct 24, 2014
  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    If you want an in depth reason why... take a look here. This goes over what is actually generated when you compile an iterator method.

    http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

    As you can see a special class type is created, and the instance of it is a fairly complex object. The cloning of which is difficult. Especially since the state of it is stored in its fields. So a clone wouldn't revert back to the start of the enumerator, but instead preserve the current state.
     
  15. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Yep, I've been through this post ( Ben Maddox point it out when answering the stackoverflow link I posted ). But as hard as I tried to understand, I didn't got a fairly bit of anything hapenning here. I'm a self taught young developper and this thing exceeds by far my knowledge!
     
  16. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Note in the delegate example I showed. You could also overload the 'AddAction' method with multiple generic types.

    Code (csharp):
    1.  
    2. void AddAction(CoroutineMethod method);
    3.  
    4. void AddAction<T>(CoroutineMethod<T> method, T param);
    5.  
    6. void AddAction<T1, T2>(CoroutineMethod<T1, T2> method, T1 param1, T2 param2);
    7.  
    Still though I find this convoluted. A simple example in the documentation demonstrating how to pass in a coroutine doesn't seem to hard. It also means that people like me who have a custom 'RadicalCoroutine' also have support to. I don't call Coroutines directly, instead I call an extension method called 'StartRadicalCoroutine'.

    http://jupiterlighthousestudio.com/unity-radical-coroutines/
     
  17. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Thanks for this I'll look at this. specificaly since it looks usefull for AI implementation. Though, again, and you can throw a rock at me for being stubborn, if I understood it well, user should call your delegate like so AddAction<int>( this.MyMethod, 12);

    Which is already to complicated and where I prefere passing a Func<object, IEnumerator> as I showed few messages ago.( where the syntax would be AddAction( MyMethod, 13). Even if I have to define S*** loads of different possible parameters type :p.

    And for the last thing you pointed out, the flexibility regarding actual user own implements compatibility. The script will defenitely be for beginners and will probably not match the needs of advanced programers!
     
  18. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    I tested this code with System.Collections class Stack : ICollection, IEnumerable, ICloneable.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class temp : MonoBehaviour {
    5.  
    6.     // Use this for initialization
    7.     void Start () {
    8.  
    9.         // Creates and initializes a new Stack.
    10.         Stack myStack = new Stack();
    11.         myStack.Push("Hello");
    12.         myStack.Push("World");
    13.         myStack.Push("!");
    14.         PrintValues(myStack);
    15.    
    16.     }
    17.  
    18.     void PrintValues( IEnumerable myCollection )  {
    19.  
    20.         Debug.Log("Call 1");
    21.         foreach ( System.Object obj in myCollection ) Debug.Log( (string)obj );
    22.         Debug.Log("Call 2");
    23.         foreach ( System.Object obj in myCollection ) Debug.Log( (string)obj );
    24.  
    25.     }
    26.  
    27. }
    And IEnumerator doesn't get consumed.
     
  19. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    That IEnumerable is a Stack, not an iterator method. They're different beasts all together.
     
    TimTamTomo likes this.
  20. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    This! And it doesn't really helps regarding what I'm trying to achieve. But thanks for your contribution!
     
  21. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    But, when you implement IEnumerable, you must also implement IEnumerator.

    You can read in code example in microsoft c sharp reference: http://msdn.microsoft.com/en-us/library/system.collections.ienumerable(v=vs.110).aspx

    Perhaps, you should implement IEnumerable, IEnumerator not alone. And, you could also implement ICloneable.
     
    Last edited: Oct 24, 2014
  22. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Well I don't have a clue about how I could switch that to a function with parameters.. any hints?
     
  23. IsGreen

    IsGreen

    Joined:
    Jan 17, 2014
    Posts:
    206
    I created this script with Iteration class, with IEnumerable(and IEnumerator) and ICloneable implemented.

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4.  
    5. public class Iteration : IEnumerable,ICloneable{
    6.  
    7.     public delegate void Handler(int n);
    8.     Handler[] iterations;
    9.  
    10.     public Iteration(Handler iteration, int count){
    11.  
    12.         this.iterations = new Handler[count];
    13.         for(int i=0; i<count; i++) this.iterations[i] = iteration;
    14.  
    15.     }
    16.  
    17.     public Iteration(Handler[] iterations){
    18.  
    19.         this.iterations = new Handler[iterations.Length];
    20.         for(int i=0; i<iterations.Length; i++) this.iterations[i] = iterations[i];
    21.  
    22.     }
    23.  
    24.     // Implementation for the GetEnumerator method.
    25.     IEnumerator IEnumerable.GetEnumerator()
    26.     {
    27.  
    28.         return (IEnumerator) GetEnumerator();
    29.  
    30.     }
    31.  
    32.     public IterationEnum GetEnumerator()
    33.     {
    34.  
    35.         return new IterationEnum(this.iterations);
    36.  
    37.     }
    38.  
    39.     object ICloneable.Clone(){
    40.  
    41.         return new Iteration(this.iterations);
    42.  
    43.     }
    44.  
    45.     public static Iteration Create(Handler iteration, int count){
    46.  
    47.         return new Iteration(iteration,count);
    48.  
    49.     }
    50.  
    51. }
    52.  
    53. public class IterationEnum : IEnumerator{
    54.  
    55.  
    56.     public Iteration.Handler[] iterations;
    57.  
    58.     public IterationEnum(Iteration.Handler[] iterations){
    59.  
    60.         this.iterations = iterations;
    61.  
    62.     }
    63.  
    64.     // Enumerators are positioned before the first element
    65.     // until the first MoveNext() call.
    66.     int position = -1;
    67.  
    68.     public bool MoveNext()
    69.     {
    70.         position++;
    71.         return (position < iterations.Length);
    72.     }
    73.  
    74.     public void Reset()
    75.     {
    76.         position = -1;
    77.     }
    78.  
    79.     object IEnumerator.Current
    80.     {
    81.         get
    82.         {
    83.             return Current;
    84.         }
    85.     }
    86.  
    87.     public Iteration.Handler Current
    88.     {
    89.         get
    90.         {
    91.             try
    92.             {
    93.                 return iterations[position];
    94.             }
    95.             catch (System.IndexOutOfRangeException)
    96.             {
    97.                 throw new System.InvalidOperationException();
    98.             }
    99.         }
    100.     }
    101.  
    102. }
    103.  
    104.  
    105. public class temp : MonoBehaviour {
    106.  
    107.  
    108.     void myMethod1(int number){
    109.      
    110.         Debug.Log("myMethod1: "+number);
    111.      
    112.     }
    113.  
    114.     void myMethod2(int number){
    115.  
    116.         Debug.Log("myMethod2: "+number);
    117.  
    118.     }
    119.  
    120.     void Start(){
    121.  
    122.  
    123.         //Repeat two times method1
    124.         Iteration iterationMethod1 = Iteration.Create(myMethod1,2);
    125.         //Repeat three times method2
    126.         Iteration iterationMethod2 = Iteration.Create(myMethod2,3);
    127.  
    128.         foreach(Iteration.Handler iteration in iterationMethod1) iteration(1);
    129.         foreach(Iteration.Handler iteration in iterationMethod1) iteration(2);
    130.  
    131.         foreach(Iteration.Handler iteration in iterationMethod2) iteration(3);
    132.         foreach(Iteration.Handler iteration in iterationMethod2) iteration(4);
    133.  
    134.     }
    135.  
    136. }
    The iterations are a delegate Handler array.

    The foreach statement repeats a group of embedded statements for each element in an array or an object collection that implements the System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> interface.

    And IEnumerator doesn't get consumed, because IEnumerable return new Enumerator class.

    Really do not quite understand you intend with this.
     
    Last edited: Oct 24, 2014
  24. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    What I mean is that Stack is different from an iterator method. THey're completely different things. I linked above how an iterator method is implemented under the hood when compiled and why this is an issue.

    Proving that any regular IEnumerable or IEnumerator can be reset doesn't mean anything.

    As I said above, an iterator method can NOT be reset and it instead throws an exception.

    See here:
    http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

    Code (csharp):
    1.  
    2.         [DebuggerHidden]
    3. void IEnumerator.Reset()
    4.  {
    5. thrownew NotSupportedException();
    6.  }
    7.  
     
  25. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,041
    Lets be clear this approach is terribly convoluted and it seems pretty much ridiculous to even consider using it; that said the only thing wrong with your initial example was you were making an assumption about the constructor which doesn't hold (in the example you copied the code uses an IEnumerable<int> you are using IEnumerator for which the compiler generates a no-argument constructor).

    To fix replace lines 4 and 5:
    Code (csharp):
    1.  
    2.        var sourceTypeConstructor = sourceType.GetConstructors ()[0];
    3.        var newInstance = sourceTypeConstructor.Invoke (null);
    4.  
    (obviously this code is assuming the first constructor in the array is the right one, for the sake of this example it works fine).

    The fact that you couldn't pick that up given the error message tells you exactly where the error is suggests you shouldn't even be thinking about this kind of code.
     
    Diablo404 likes this.
  26. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Well, My friends! IT'S WORKING! I mean... at least half of it!

    I replaced the two lines @JohnnyA pointed out and cloned the ienumerator before startcoroutine inside the delegate and it wonderfully worked. Haha, I couldn't believe it. So many days waiting for this to work.

    Ok now the sad point :) It's not working when called from JS ... ( TargetParameterCountException: parameters do not match signature ). Is there a workaround?


    @IsGreen I'd have a look at the Iteration Class you implemented!

    Edit: For further informations, when printing sourceType, if I call AddAction from C# I get:

    Code (csharp):
    1. ScriptName+<CallBackMethodName>c__Iterator0
    When I call from UnityScript I get:

    Code (csharp):
    1. ScriptName+$CallBackMethodName$1+$
    So it seems that when the IEnumerator in C# and UnityScript are not handled the same way

    Edit2 : Another different thing, when I print sourceTypeConstructor from c# I get : Void .ctor() , but when I print it from JS I get : Void .ctor(Int32) .

    Of course both method are IEnumerator typed and accept a int parameter.


    I think that I'm not far from getting it to work! I need a last helping hand so I can finally sleep :D
     
    Last edited: Oct 25, 2014
  27. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Just remember what I brought up before. You still can't reset the iterator method.
     
  28. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    True but cloning it seems to work, at least for the c# call. Now I just need to find a way to make it work when called from JS.
     
  29. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    1) I only say that because if someone passes in a half processed routine, it will always clone from that same point in the routine.

    2) I'd suggest getting an IL inspector (a program that shows you the compiled code in its intermediate form) and see what is different about the class created for the iterator method. That should help figure out what's different from JS and C#.
     
    TimTamTomo likes this.
  30. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Ok sorry I didn't get your first point. And alright, I'll give a look at an IL Inspector hoping I'll find something usefull with it.
     
  31. Diablo404

    Diablo404

    Joined:
    Mar 15, 2013
    Posts:
    136
    Up. So far, didn't find a way. I wasn't able tu use Mono.Cecil to find out how Unity traduce IEnumerator for UnityScript