🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Tangent: Yield, part 2

Published January 23, 2009
Advertisement
yield sucks. I mean it's good and all, but implementing it is deceptively tricky. There's a few ways to do it, and they're all (from as far as I can tell) about the same level of difficulty/trickiness. And none of them really fit into the nice stack based execution frame of mind.

There's three main ways I've seen in my (limited) research/ponderings:

1. The thready way. yield is a special case of co-routines. Its natural then to implement co-routines via threads (or some emulating more lightweight mechanism). One thread calls the yielding method, which starts in its own thread until it hits a yield and then waits to be signal/pulse/awoke'd by a second call.

The problem here is that threads tend to be a little heavyweight for this; it requires the runtime to know about threads; and some mechanism to know when to join/terminate the thread (since the yielding method isn't guaranteed to reach its conclusion).

2. The C# way. C# does something a little funky to make yield work. The declaration itself creates an anonymous type where the parameters are member fields, and there's a 'return value' and a 'state' variable. It then generates a method based on the contents of the declaration which is mostly a big case statement and a bunch of jumps in-between it. A yield changes the 'state' variable which the giant case statement uses to effectively start the method at different points.

Slimy spaghetti to implement, but wildly elegant since it doesn't require changes to the stack execution model to get the behavior. The problem is that it requires case statements and jumps to spaghetti it, and Tangent's runtime has neither (directly). It could be done with a series of methods and a delegate, but that's similarly icky.

3. The naive way. Conceptually yield works by pausing execution and returning control to the caller without resetting the state of the method. Naively it should be possible to reproduce that directly. Keep the bits of bytecode around with their execution pointers. A second call just starts them back up where they left.

The problem here is (like many naive things) the implementation details are a bit more complex than the conceptual details. You can't just pause everything, the top most stack frame needs to increment the execution pointer 1 before returning control. And you have to consider that these things might be nested or two yield methods might exist for one caller. Plus 'restarting' at an arbitrary depth without a whole lot of if(yield){}else{} in everything that ever gets invoked....


In the end, the naive way best fit with the runtime's design. I made a subtype of Block with the modified behavior and added state necessary for a yield. Here's the simple test app that hand-crafts a yielding block. It gives a little insight to the structure of the underlying 'bytecode'. Next in the queue is making code generation so that Tangent syntax can effectively reproduce this.

namespace YieldTest1{    class Program {        static void Main(string[] args) {            Tangent.Lang.Object.GenerateDefaultRoot();            YieldBlock TestYieldBlock = new YieldBlock();            //            // {             //   yield 1;            //   yield 2;            //   yield 3;            // }            //                        TestYieldBlock.Statements.Add(new Yield(new IntLiteral(1)));            TestYieldBlock.Statements.Add(new Yield(new IntLiteral(2)));            TestYieldBlock.Statements.Add(new Yield(new IntLiteral(3)));                        TestYieldBlock.MethodInstance = new Tangent.Lang.Object(Tangent.Lang.Type.Method);            TestYieldBlock.MethodInstance.Members["ExecutionBlock"] = new Tangent.Lang.dotNetObject(Tangent.Lang.Type.YieldBlock);            (TestYieldBlock.MethodInstance.Members["ExecutionBlock"] as dotNetObject).StoredObject=TestYieldBlock;            while (!TestYieldBlock.Done) {                TestYieldBlock.Invoke(null);                if (!TestYieldBlock.Done) {                    Console.WriteLine((TestYieldBlock.MethodInstance.Members["ReturnValue"] as dotNetObject).StoredObject);                }            }        }    }}


123
Previous Entry Tangent: foreach.
0 likes 0 comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement