0

I'm developing a server for a multiplayer game, where I want to give each player his own Lua thread, that I'd like to fill with some unique globals, e.g. the player's name and id. Basically, I want to be able to set thread local variables from the host application, without any additional code in the actual scripts I'm writing, which I can then use over the duration of the play session, whenever I call a function. Is that possible with Lua?

// Example of how it naturally doesn't work

var L = Lua.LuaLNewState();
Lua.LuaRegister(L, "print", WriteLine);
dostring(L, "print('hello, world')", null);

var x = Lua.LuaNewThread(L);
Lua.LuaPushLiteral(x, "charId");
Lua.LuaPushNumber(x, 123);
Lua.LuaRawSet(x, Lua.LUA_GLOBALSINDEX);
dostring(x, "print('x1 '..charId)", null); // call to a function that uses charId

var y = Lua.LuaNewThread(L);
Lua.LuaPushLiteral(y, "charId");
Lua.LuaPushNumber(y, 456);
Lua.LuaRawSet(y, Lua.LUA_GLOBALSINDEX);
dostring(y, "print('y1 '..charId)", null); // call to a function that uses charId

dostring(x, "print('x2 '..charId)", null); // I want this to still be 123

Update: My application is multi-threaded, multiple players can run scripts at the same time. My understanding of setting the environment of a function in Lua is that it changes it globally, that won't work in my case.

Mars
  • 912
  • 1
  • 10
  • 21
  • See also here: http://stackoverflow.com/questions/24356520/thread-locals-in-lua – siffiejoe Aug 29 '15 at 17:10
  • It's my understanding that changing the environment of a function is global. Maybe I should've clarified this, we're not talking about a thread-safe application here, setting the environment of a function before calling it is not an option, as multiple threads can run scripts at the same time. – Mars Aug 29 '15 at 19:58
  • 1
    The code in the answer of the linked post replaces the global environment of the running script only once before any coroutines are created. And even that is optional if you are ok with using `TL.variablename` instead of plain `variablename` for accessing a thread-local variable. – siffiejoe Aug 29 '15 at 20:19
  • Ah, now I get what the code does, it creates a table that dynamically switches between environment tables, depending on where it's running, fascinating. Seems to be working. One more question, if I call setfenv with 0 instead of 1, it changes the global table to TL everywhere, otherwise I'd have to either call setfenv from inside every function, or outside *for* every function, correct? Is replacing the "entire G" with TL a problem? – Mars Aug 29 '15 at 21:17
  • 1
    Second question first: There may be performance implications for accessing global variables (because of the dynamic environment switching). Other than that the code mimics normal Lua behavior, and since only files that do `setfenv( 1, TL )` are affected, there shouldn't be any problems. (If you want to store to global variables instead of thread-local ones, you must use `_G.variable = x` instead of `variable = x`, though.) – siffiejoe Aug 29 '15 at 21:59
  • 1
    If you `setfenv( 0, TL )`, any function created after that via `load`, `loadfile`, `loadstring`, or `require` will get the `TL` environment (unless those functions are called from coroutines created before the `setfenv` call). This may include third-party code, which expects to be able to write to the global environment without using the syntax `_G.variable = x`, so there is a chance for problems there ... – siffiejoe Aug 29 '15 at 22:20
  • 1
    `setfenv( 1, TL )` (on the top level) only affects the current script file (and all Lua functions created within), which is ususally what you want. `setfenv( 0, TL )` will also affect other modules loaded after that, but for the file containing the `setfenv( 0, TL )` call you still have to use `setfenv( 1, TL )` additionally. – siffiejoe Aug 29 '15 at 22:30
  • Although... no matter where I call setfenv with 1, it doesn't seem to work, if the setup is not in the same "file" (loadstring) as the function I'm calling. It only works with 0, which seems sub-optimal. – Mars Aug 30 '15 at 09:21

2 Answers2

0

Are you using an existing game engine? If so it would probably be worth using existing libraries. For example, if using LÖVE2D you could use something like this or this.

You can create separate environments using setfenv or _ENV (depending on Lua version) and optionally use coroutines within them.

cabbageforall
  • 620
  • 1
  • 5
  • 12
  • Thanks for your answer, but it doesn't really answer my question. I don't need a client library, and I'm not sure sefenv does what I'm looking for. If I change the environment for a function in one thread it changes it for all other threads as well, doesn't it? – Mars Aug 29 '15 at 16:30
  • Since Lua 5.2 it's my understanding that each function can have it's own environment. If you need different environments in different threads, separate functions could be created, one for each thread (or wrap the generic function in a thread-specific function, etc)? – cabbageforall Aug 29 '15 at 18:32
  • Each function can have its own environment, yes, but it's my understanding that changing the environment of a function is global. Maybe I should've clarified this, we're not talking about a thread-safe application here, setting the environment of a function before calling it is not an option, as multiple threads can run scripts at the same time. – Mars Aug 29 '15 at 19:58
0

I have found another way to accomplish this, that feels less "hacky" to me. Lua has a constant called LUAI_EXTRASPACE, which is used to prepend the Lua state with a specified amount of bytes. I'm now using a normal C Lua library that I compiled myself, where I changed that constant to 4, to make space in front of the actual Lua state struct, to save a reference to an object in my application, that I can query whenever I need it in a function.

Store

var NL = Lua.lua_newthread(L);
unsafe
{
    var ptr = (int*)NL.ToPointer();
    ptr -= 8;
    *ptr = index;
}
return NL;

Retrieve

var index = 0;
unsafe
{
    var ptr = (int*)L.ToPointer();
    ptr -= 4;
    index = *ptr;
}
var obj = objects[index];

While this doesn't allow me to freely push variables, I can easily write functions that make use of the stored int, to get an object from an array, and pass values from it to Lua. Alternatively, if there's only a few specific variables you need, you could also make more space.

Since this is per Lua state, every player who has his own thread can have his own variables.

Mars
  • 912
  • 1
  • 10
  • 21