moonsharp icon indicating copy to clipboard operation
moonsharp copied to clipboard

Enumerator based coroutine yielding

Open imerr opened this issue 5 years ago • 2 comments

Hey! One of the features that's missing from moonsharp that'd make writing lua code a lot more elegant would be support for yielding a coroutine in clr functions In unity this is usually done by just having an enumerator/generator function (not sure of the 100% correct terminology):

    IEnumerator MyCoroutine(int counter) {
        for (int i = 0: i < counter; i++) {
            yield return i;
        }
    }

You can do something like this already, but you'll have to manually keep track of the iterator:

    for i in my_coroutine(10) do
        coroutine.yield(i)
    end

Conceptually it shouldn't be too hard to just add some "syntactical sugar" to implement something similar to the lua function above behind the scenes.. So I decided to give that a try and went digging First I looked at the custom converters and figured it'd be as easy as checking for an attribute on the method info and just giving it a different DynValue with the "for loop yield" part, but turns out classes don't even use that and I had to go a lot deeper

    [MoonSharpClrCoroutine]
    IEnumerator YieldExample(int counter) {
        for (int i = 0: i < counter; i++) {
            yield return i;
        }
    }

This is obviously a giant hack at the moment and I'm mainly looking for feedback to get this to a decent state where merging it might be considered The whole MethodMemberDescriptor part can be implemented better, but I didn't want to dig too deep into the scary parts for a proof of concept I'm also curious what the best way would be to generate the iterator wrapper part? From what I gathered the yield does need to be run in bytecode currently

imerr avatar Jun 09 '20 08:06 imerr

Oh, usecase

The usecase for this would be nicer yielding in api functions while we wait for things to happen/be ready A dialogue system is probably an easy example

dialogue:say("Hello please give me your name")
local name = dialogue:input()
dialogue:clear()
dialogue:say("Hi ".. name)

Currently dialogue:input() would be implemented with lua wrapper function calling coroutine.yield until we have an input response, but obviously this pretty much means you have to implement every api twice - once in c# for the actual api and once as a lua wrapper for yielding

imerr avatar Jun 09 '20 08:06 imerr

Yeah, in a final version you'd want to get rid of the lua code parsing and clean up the code all together This is just a proof of concept, it also has issues of not yielding null values, since those aren't valid in lua "iterators"

I wouldn't recommend using this as is

On 2 Sep 2020, at 22:04, Chris Smoak [email protected] wrote:

 @cesmoak commented on this pull request.

In src/MoonSharp.Interpreter/Interop/StandardDescriptors/ReflectionMemberDescriptors/MethodMemberDescriptorCoroutine.cs:

  •        var enumerateYielder = script.DoString(@"return function (callable) 
    
  • return function (...)
  •    for y in callable(...) do
    
  •        if coroutine.is_return_value(y) then
    
  •            return coroutine.get_return_value(y)
    
  •        else 
    
  •            coroutine.yield(y)
    
  •        end
    
  •    end
    
  • end +end", null, MethodInfo + "_yielder"); Does it make sense to parse this lua code one for performance reasons? Could this lua function be pulled out and kept as a global function to be used when needed? I've done something similar in my own code, though all on the lua side without the C# interconnection work this PR does to make it nice and clean.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or unsubscribe.

imerr avatar Sep 03 '20 05:09 imerr