user6240174
user6240174

Reputation: 93

How to make table and variable values to persist when calling a lua function from c++?

I am calling a lua function from a c++ script. Within the lua function, I need to update certain variables and tables based on the data that I am passing to the function from C++. The data has to be passed to the lua function in a continuous loop, and each successive update to the lua variables and tables must take into account the changes made by the previous updates. How to accomplish this in a robust way? One clumsy way might be just to pass the values around between C and lua, or save and load them repeatedly, but since my data tables are expected to be really large that really may not be a good option. Please suggest solutions.

PS: I cannot simply write the entire code in lua since the C++ code is meant to run rosservices and subscribe to rostopics. I think it may be difficult to achieve the same in lua, and although there is a package called roslua apparently it is not compatible with ros indigo.

 function test_upvalues(a)
 print(a)
 local x=10
 local function increment(t)
 x=x+t
 end
 --increment(a)
 print(x)
return(increment)
end

Calling this from C++

lua_getglobal(L, "test_upvalues");
lua_pushvalue(L, -1);

lua_pushnumber(L,2); //push a value to increment state
lua_call(L,1,0);

lua_pushvalue(L, -1);
lua_pushnumber(L,3); //push another value to increment state further
lua_call(L,1,0);

Here variable x is the state whose state I want to retain after incrementing it by the value passed from C++. What I am not getting is where is the local function increment(t) being invoked? should be within the outer lua function or elsewhere? when I comment out the line increment(t) within the outer lua function, I simply get 2, 10, 3, 10 as the output, whereas on with increment(t) I am just getting 2, 12, 3, 13. Clearly the static variable behaviour is not getting implemented correctly. Where am I going wrong? Thanks.

Upvotes: 3

Views: 3280

Answers (1)

md5i
md5i

Reputation: 3083

Expanding on the use of upvalues to carry state, here is some pseudo-code to get you started.

Lua function factory which returns a function (myfunction) which has persistent state as an upvalue (mystate, mystate2):

function create_myfunction ()
   local mystate = {} -- initial value of state
   local mystate2 = 0 -- Can have more than one upvalue
   local function myfunction (args)
       -- modify mystate and mystate2 based on args here
   end
   return myfunction
end

Creating an instance of myfunction with state from Lua:

called_from_c_function = create_myfunction();

Creating an instance of myfunction wih state from C:

lua_getglobal(L, "create_myfunction");
lua_call(L, 0, 1); // Zero arguments, one result

Calling this function from C in a loop

// Assume myfunction is on the top of the stack
for (...) { // Loop
    lua_pushvalue(L, -1); // Copy function on stack (so we can use it again, as it gets consumed by the call)
    // Now push an argument to the function on the stack here
    lua_pushfoo(L, ...); // Just an example
    lua_call(L, 1, 0); // Call myfunction with 1 argument, returning 0
    // We should be stack-neutral at this point, with myfunction on top
}

Let me know if anything here is unclear. I'm making some assumptions about what sort of state you are trying to maintain, and there are a bunch of other things that can be handled with a little extra work (like returning the state to C, for example).


Addendum

In your example calling the Lua code from C++, there are a couple of problems. Here is what I believe you wanted:

lua_getglobal(L, "test_upvalues"); // Put factory function on stack
lua_pushnumber(L, 1); // Argument 'a' (not sure what you are using it for)
lua_call(L, 1, 1); // Call factory.  Consumes argument 'a', returns function 'increment'

lua_pushvalue(L, -1); // Copies 'increment' on stack
lua_pushnumber(L, 2); //push a value to increment state
lua_call(L,1,0); // Calls 'increment' function with increment

lua_pushvalue(L, -1); // Copies 'increment' on stack
lua_pushnumber(L, 3); //push another value to increment state further
lua_call(L,1,0); // Calls 'increment' function with increment

In order to see the results of these increments, you'll want to print them out inside the increment function. The factory function gets called once. The function it returns gets called multiple times.

If you want to be able to set the initial state and retrieve the value afterward, you might want a lua function something like this:

function increment_factory(value)
  local function get_state()
    return value
  end
  local function increment(x)
    value = value + x
  end
  return get_state, increment
end

Here the factory function returns two functions, one to increment the state, and the other to retrieve it. value is an upvalue to both get_state and increment. (If you want the value of the state after each increment, it is easier to return it from increment.) This would be used like this:

lua_getglobal(L, "increment_factory");
lua_pushnumber(L, INITIAL_STATE);
lua_call(L, 1, 2);  // One argument, two returned functions

lua_pushvalue(L, -1); // Copy 'increment'
lua_pushnumber(L, 2); // Push value to increment
lua_call(L, 1, 0);    // Call 'increment'

lua_pushvalue(L, -1); // Copy 'increment'
lua_pushnumber(L, 3); // Push value to increment
lua_call(L, 1, 0);    // Call 'increment'

lua_pushvalue(L, -2); // Copy 'get_state'
lua_call(L, 0, 1);    // Call 'get_state'

// Now the current state is on the top of the stack

Upvotes: 1

Related Questions