Is it possible to execute a single lua statement from a host program?
Asked Answered
G

2

5

I am trying to embed a lua-based script system into my game engine. I would like the scripting to be able to have both blocking and non-blocking commands, for example:

character.walkTo(24, 359);  // Blocks until character arrives
c = 35; // Non blocking, execution goes on to the next statement

Since the "walkTo" needs to be "active" for more than 1 frame of execution, I would like to be able to run 1 statement at time from the Java host instead of the whole function. This is because it would be overkill to have real multithreading, which is not needed.

If I could execute just 1 statement, and keep the execution state "paused" until next statement execution, I would be able to implement blocking commands like "walkTo" by checking if the command is finished in the host, and if it is, go on to the next statement, otherwise, wait until the next frame iteration.

Is there any way to execute 1 statement a time from the Java host with LuaJ(or with any other Lua api), or am I forced to develop my own script engine with lex and yacc?

Any good idea is welcome, thanks!

Gunsmith answered 21/12, 2014 at 10:35 Comment(5)
Have you looked at Lua's coroutines? You could have character.walkTo yield, then resume from Java when the character is done walking.Charbonneau
@immibis Thank you, you pointed me to the correct direction I was looking for. Much appreciated.Gunsmith
I am now wondering if one can yield from a Java implementation of a lua function.Gunsmith
Just block "walkTo" until finished. Lua does not care how long it takes for a function to return.Legpull
@Legpull I would like the control flow to go back to the hostGunsmith
G
3

Seems like you are missing asynchronous pattern. If c=35 has to be executed once character occurs at (24,359), then the correct way is to pass function() c=35 end as third argument to walk method and your engine (that performs actual 'walking') will call that callback when appropriate.

character.walkTo(24, 359, function ()
    c = 35
end)

Otherwise, walk may schedule walking to engine and yield immediately, resuming on correct event. In this case you have to setup script worker-coroutine (you cannot yield in main state).

script = coroutine.wrap(function ()
    character.walkTo(24, 359) -- will yield and leave callable global 'script'
    c = 35
end)
script() -- resume first time
-- here script is over

...

-- this wrapper may be implemented in Lua or host language

function character.walkTo(x, y)
    engine.startActualWalkingTo(x, y)
    coroutine.yield() -- yields to host
    -- coroutine.resume() will continue here
end

...

-- somewhere in engine code (pseudo-code here)

for event in eventLoop do
    if character.x == endPoint.x and character.y == endPoint.y then
        script() -- resume again at c=35
    end
end

You may cancel the script anytime with script=nil.

yield() has some limitations, consult the manual.

Giustino answered 22/12, 2014 at 16:47 Comment(8)
I see, so you are suggesting to wrap the coroutine/yielding process as a lua library to offer to the scriper. I just wonder, is there any way to yield a lua function inside a host-implementation of that lua function? I failed to find anything like that with LuaJ.Gunsmith
Also luaj.sourceforge.net/api/3.0/org/luaj/vm2/… @GunsmithGiustino
I wonder how am I supposed to obtain a Thread object inside my lua function implementation, since its LuaJ that automatically instantiate it with the empty constructor... I guess I have to keep the object of type "Globals" static and access it globally? I mean, seriously, is this the expected design?Gunsmith
I don't know java, but from what I see, yield() is a static method. (And so is Lua's coroutine.yield())Giustino
getRunning()is static too. @GunsmithGiustino
You're absolutely right I was dumb. Though, I tried to do exactly what you said (with the 3.0 interface, so I have to call yield from Globals), and there is an error when yielding: "Cannot yield main thread". I start to fear the only way is to compile "coroutine.yield(script)" and execute that? It's quite cumbersome thoughGunsmith
Actually the same exact error happens with "coroutine.yield" called from the host implementation of "walkTo". I have no idea of how I am supposed to implement this. It does work as expected if I call "coroutine.yield" right after "character.walkTo" inside lua script.Gunsmith
I'm sorry, it works like a charm. I was try to stick with ScriptEngine interface instead of using LuaJ interface, and it looks like I cannot use them both at the same time. Thanks.Gunsmith
G
4

Bonus answer for everyone who is sticking with this problem.

Here is my exact solution:

-- test.lua --

onLookAt = function()
    character:walkTo(234, 35)
    print("Arrived!")
end

-- LuaTest.java --

public static void luaTest() {
    Globals g = JsePlatform.standardGlobals();
    g.load(new CoroutineLib());

    g.set("character", CoerceJavaToLua.coerce(new Character(g)));
    g.load(Gdx.files.internal("test.lua").reader(), "test.lua").call();
    g.load("co = coroutine.wrap(onLookAt)").call();
    g.get("co").call();
    try {
        // Simulate time that passes while the script is paused
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    g.get("co").call();
}

public static class Character{
    Globals g;

    public Character(Globals g){
        this.g = g;
    }

    public void walkTo(int x, int y) {
        System.out.println("Started walking.");
        g.yield(LuaValue.NONE);
    }
}

-- Output --

Started walking.

(2 seconds later)

Arrived!

One thing you should be very careful about:

  • Do NOT use java's ScriptEngine interface if you want to accomplish this. ScriptEngine interface doesn't provide API for obtaining the implicitly allocated Globals instance that you need for yielding, and making a new instance of Globals and using that for yielding is obviously pointless.
Gunsmith answered 24/12, 2014 at 4:1 Comment(2)
Very helpful! but I'd like to add two things: 1. For an accurate representation of waiting, you want to sleep inside a thread inside walkTo, with the g.yield() immediately after the sleep, and 2. If you don't want the user to access the coroutine library be sure to g.set("coroutine", LuaValue.NIL); before the first g.get("co").call();.Sw
Thanks for the insight! For point 1), actually I was trying to simulate a "mt non blocking" wait call, so that the script stops, but the caller thread goes on doing other things (i.e.: process the walking progress), which I just simulated with sleep(2000), and finally notifying that the walk progress has ended by calling g.get("co").call() to unyield the script threadGunsmith
G
3

Seems like you are missing asynchronous pattern. If c=35 has to be executed once character occurs at (24,359), then the correct way is to pass function() c=35 end as third argument to walk method and your engine (that performs actual 'walking') will call that callback when appropriate.

character.walkTo(24, 359, function ()
    c = 35
end)

Otherwise, walk may schedule walking to engine and yield immediately, resuming on correct event. In this case you have to setup script worker-coroutine (you cannot yield in main state).

script = coroutine.wrap(function ()
    character.walkTo(24, 359) -- will yield and leave callable global 'script'
    c = 35
end)
script() -- resume first time
-- here script is over

...

-- this wrapper may be implemented in Lua or host language

function character.walkTo(x, y)
    engine.startActualWalkingTo(x, y)
    coroutine.yield() -- yields to host
    -- coroutine.resume() will continue here
end

...

-- somewhere in engine code (pseudo-code here)

for event in eventLoop do
    if character.x == endPoint.x and character.y == endPoint.y then
        script() -- resume again at c=35
    end
end

You may cancel the script anytime with script=nil.

yield() has some limitations, consult the manual.

Giustino answered 22/12, 2014 at 16:47 Comment(8)
I see, so you are suggesting to wrap the coroutine/yielding process as a lua library to offer to the scriper. I just wonder, is there any way to yield a lua function inside a host-implementation of that lua function? I failed to find anything like that with LuaJ.Gunsmith
Also luaj.sourceforge.net/api/3.0/org/luaj/vm2/… @GunsmithGiustino
I wonder how am I supposed to obtain a Thread object inside my lua function implementation, since its LuaJ that automatically instantiate it with the empty constructor... I guess I have to keep the object of type "Globals" static and access it globally? I mean, seriously, is this the expected design?Gunsmith
I don't know java, but from what I see, yield() is a static method. (And so is Lua's coroutine.yield())Giustino
getRunning()is static too. @GunsmithGiustino
You're absolutely right I was dumb. Though, I tried to do exactly what you said (with the 3.0 interface, so I have to call yield from Globals), and there is an error when yielding: "Cannot yield main thread". I start to fear the only way is to compile "coroutine.yield(script)" and execute that? It's quite cumbersome thoughGunsmith
Actually the same exact error happens with "coroutine.yield" called from the host implementation of "walkTo". I have no idea of how I am supposed to implement this. It does work as expected if I call "coroutine.yield" right after "character.walkTo" inside lua script.Gunsmith
I'm sorry, it works like a charm. I was try to stick with ScriptEngine interface instead of using LuaJ interface, and it looks like I cannot use them both at the same time. Thanks.Gunsmith

© 2022 - 2024 — McMap. All rights reserved.