Sign in to follow this  
Programmer16

LuaInterface and Coroutines

Recommended Posts

I've spent the last two days working on this and I've finally cobbled together a mediocre solution.

To start-off, I have 5 coroutine managing functions:

function SetupCoroutineTable()
coroutine_table = { };
end

function AddCoroutine(Item)
table.insert(coroutine_table, Item);
end

function RemoveCoroutine(Index)
table.remove(coroutine_table, Index);
end

function ResumeCoroutine(Index)
Item = coroutine_table[Index];
if coroutine.status(Item) ~= 'dead' then
coroutine.resume(Item);
end
end

function ResumeAllCoroutines()
for Index,Item in ipairs(coroutine_table) do
ResumeCoroutine(Index);
end
end





I call SetupCoroutineTable at the start of my program. The major issue I see here is that I don't manage the table at all (just keep inserting and only remove when told to.) I could add a managing function that clears the dead coroutines, but this is the least of my concerns right now.

Now for my sample. The main idea behind this is to use for scripting scenes (move player here, show message, do this, etc.) So, I went with counting.


-- the function that my code calls.
-- Would be somthing like an event's OnActivate function.
function foo()
count(10);
display('Finished counting to 10.\n');
count(21);
display('Finished counting to 21.\n');
end

-- This is the count function called above.
function count(value)
set_value(0); -- C# method
count_to(value); -- C# method
while get_value() < value do -- get_value() is another C# method
coroutine.yield();
end
end





This is one of my major issue: I don't want to have to write systems like this for everything that I want the script to be able to wait on (i.e. ideally count would be in the actual code.)

Now for the ugly part. I have a method in C#:

public void CallScriptFunction(string Name)
{
luaVM.DoString(@"local coro = coroutine.create(" + Name + ");
InsertCoroutine(coro);
coroutine.resume(coro);");
}





The main issues being that: it's very hacked together and it's not flexible (doesn't support calling a method with arguments.)

What I'm wondering is if anyone has any insight into a better solution (or some suggestions to improve what I have.)

Edited at 4:25am
I just got done trying a new system. It uses the main portion, but is more flexible. I implemented a class named WaitInfo which contains a delegate that returns a bool. This is returned from any method I want to allow to wait. Then, in my lua code, I pass the WaitInfo to my wait function. Here's the revised code


public delegate bool WaitPredicate();
public sealed class WaitInfo
{
WaitPredicate predicate;

public WaitInfo(WaitPredicate Predicate)
{
this.predicate = Predicate;
}

public bool IsFinished()
{
return predicate();
}
}

public WaitInfo CountTo(int Value)
{
TaskManager.Add(new CountTask((task) =>
{
++countValue;
Output += countValue.ToString() + " ";
if(countValue >= Value)
task.Kill();
}));

return new WaitInfo(new WaitPredicate(() => { return countValue >= Value; }));
}



The new WaitInfo class and CountTo method (note that I tried just returning a WaitPredicate, but I can't get Lua to call it. I don't get any errors, it's just never called. I was thinking maybe I need to call Invoke or something, but I didn't try it.)


function wait(winfo)
while not winfo:IsFinished() do
coroutine.yield();
end
end

function foo()
local winfo = count_to(10);
wait(winfo);
display('finished the first count!\n');

winfo = count_to(21);
wait(winfo);
display('finished the second count!\n');
end



The new wait function and update foo function.

This new system is much better than what I had, but I'm still looking for suggestions; especially on the coroutine-wrapping code I've implemented (read: the major hack I have in place.)

Edited at 4:56am
Ok, I just went back through and yes, I can simply return a WaitPredicate and then in wait I just call winfo:Invoke().

Also, is using DoString() as I am even stable? If so, I could omit the wait function and set it up using DoString() straight from the methods (i.e. inside of CountTo and such.)

I'd also like to note that a lot of code is just for testing this (like the CountTo method's code and what-not.) I'm just trying to get the system nailed down.

[Edited by - Programmer16 on October 17, 2010 4:01:55 AM]

Share this post


Link to post
Share on other sites
I'm going to stick with this for now (don't let this put you off from posting though; I'm still more than interested in comments and suggestions.) It's not the best solution, but it's working really well and I want to move onto other stuff.

Here's the final solution I have setup:

_coroutines = { };

function _coroutines_Start(Item)
local Coro = coroutine.create(Item);

for Index, Value in ipairs(_coroutines) do
if coroutine.status(Value) == 'dead' then
_coroutines[Index] = Coro;
coroutine.resume(Coro);
return;
end
end

table.insert(_coroutines, Coro);
coroutine.resume(Coro);
end

function _coroutines_Update()
for Index, Value in ipairs(_coroutines) do
if coroutine.status(Value) ~= 'dead' then
coroutine.resume(Value);
end
end
end

function wait(...)
for Index, Value in ipairs(arg) do
if type(Value) == 'number' then
Predicate = Sleep(Value);
while Predicate:Invoke() do
coroutine.yield();
end
else
while value:Invoke() do
coroutine.yield();
end
end
end
end



Then for my C# code:

// Store the _coroutines_Start and _coroutines_Update methods
LuaFunction startFunction, updateFunction;

// During initialization
startFunction = LuaVM.GetFunction("_coroutines_Start");
updateFunction = LuaVM.GetFunction("_coroutines_Update");

void StartCoroutine(Lua LuaVM, string FunctionPath)
{
LuaFunction = LuaVM.GetFunction(FunctionPath);
startFunction.Call(LuaFunction);
}

// During update
updateFunction.Call(new object[] {});

// Then, to start a coroutine, I just call StartCoroutine. For the example post initially:
StartCoroutine(LuaVM, "foo");



The only major kink now is that I cannot use this system with script functions that require arguments or are part of a table. The only way I can see around this would be:
+ Modify _coroutines_Start to take a table of arguments. Use unpack to pass them to coroutine.resume.
+ Modify StartCoroutines to take a list of arguments. Inside of the method retrieve the table that the method is attached to (would require some string parsing to retrieve the table's name.)

I don't know if this would actually work, but it's the only thing I can come up with at the moment. It's not an issue right now though, so I'll worry about it later.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this