fengari-lua / fengari

🌙 φεγγάρι - The Lua VM written in JS ES6 for Node and the browser
MIT License
1.81k stars 64 forks source link

Performance Tips? #160

Closed grilme99 closed 5 years ago

grilme99 commented 5 years ago

Hey,

I'm new to Fengari (and Lua VMs in general) and was wondering about some general performance tips. Here is my code (it is running in a child process):

function start() {
  // Create the new Lua state
  const L = lauxlib.luaL_newstate()

  // Function for loading libraries
  var loadLib = function (name, lib) {
    lauxlib.luaL_requiref(L, fengari.to_luastring(name), lib, 1);
    lua.lua_pop(L, 1); 
  };

  // Load the string, coroutine, table, math, utf8 and base libraries
  base.luaopen_base(L)
  loadLib(lualib.LUA_STRLIBNAME, lualib.luaopen_string);
  loadLib(lualib.LUA_COLIBNAME, lualib.luaopen_coroutine);
  loadLib(lualib.LUA_TABLIBNAME, lualib.luaopen_table);
  loadLib(lualib.LUA_MATHLIBNAME, lualib.luaopen_math);
  loadLib(lualib.LUA_UTF8LIBNAME, lualib.luaopen_utf8);

  // Set globals
  flua_setglobals(L, {
    // Wait function
    wait: (sec) => {
      sec = (sec || 0.05).clamp(0.05, Number.MAX_SAFE_INTEGER)
      setTimeout(() => {
        lua_resume(L, null, 0)
      }, sec * 1000)
      return lua_yield(L, 0)
    }

  })

  lua.lua_atnativeerror(L, L => {
    console.log(lua.lua_touserdata(L, 1))
    return 1
  })

  let execute = (code) => {
    // Run script
    lauxlib.luaL_loadstring(L, fengari.to_luastring(code))
    lua_resume(L, null, 0)
  }

  process.on('message', data => {
    data = data.toString()

    if (data === 'stop') {
      lua.lua_close(L)
      return process.exit(0)
    } else {
      execute(data)
    }
  })
}

Do you have any advice on making this more efficient or just speeding it up in general?

daurnimator commented 5 years ago

I don't have any general advice to share here. This might be a better question for reddit or the lua mailing list?

grilme99 commented 5 years ago

How do I do error checking? Currently, it doesn't check for errors in Lua itself.

daurnimator commented 5 years ago

How do I do error checking? Currently, it doesn't check for errors in Lua itself.

lua_resume returns an error code: do it the same as you would in normal lua.

grilme99 commented 5 years ago

Sorry, I'm fairly new to this type of thing. What would I do in normal Lua?

For example, say my script is:

print('Hello world'

That should error, but currently, all I get in the console is: TypeError: Cannot read property '4' of null. How would I get a proper error?

daurnimator commented 5 years ago

How are you calling into lua? In your above example you use luaL_loadstring followed by lua_resume: have a look at their return values!

grilme99 commented 5 years ago

When everything is correct, they both return 0, however when I do print('Hello world', luaL_loadstring returns 3 and lua_resume returns 2. How can I extract useful information from this?

daurnimator commented 5 years ago

See https://www.lua.org/manual/5.3/manual.html#luaL_loadstring

This function returns the same results as lua_load.

The return values of lua_load are:

  • LUA_OK: no errors;
  • LUA_ERRSYNTAX: syntax error during precompilation;
  • LUA_ERRMEM: memory allocation (out-of-memory) error;
  • LUA_ERRGCMM: error while running a __gc metamethod. (This error has no relation with the chunk being loaded. It is generated by the garbage collector.)
grilme99 commented 5 years ago

What is the best way to extract a proper error message that I can print to the console, though?

daurnimator commented 5 years ago

The same way you do in normal lua.

Otherwise, it pushes an error message.

i.e. after you get a non LUA_OK return value, the error value is on the stack. If it is a string (it might not be!) you could use lua_tostring(L, -1) to get the string, then you probably want to convert it to a javascript string, and pop the error from the stack.

grilme99 commented 5 years ago

Okay, well I kind of have it working now. Here is my code:

if (ok !== lua.LUA_OK) {
    console.log("Error: %s\n", lua.lua_tojsstring(L, -1))
    lua_pop(L, 1)
}

When I run the same script as before, it outputs Error: attempt to call a string value, but it should output something like [string "<eval>"]:1: ')' expected near '<eof>'. How do I fix that?

daurnimator commented 5 years ago

My guess is you only checked the return of lua_resume, not luaL_loadfile (you should check both)

daurnimator commented 5 years ago

You have to check and act on it before doing the lua_resume.

daurnimator commented 5 years ago
// process.send(`${lua.lua_tojsstring(L, -1)}`.red)

Why is that commented out? Perhaps that is why it doesn't output anything?

grilme99 commented 5 years ago

I fixed it, I miss that bit!

grilme99 commented 5 years ago

I have another small issue.

let ok2 = lua_resume(L, null, 0)
    if (ok2 !== lua.LUA_OK) {
      process.send(`${lua.lua_tojsstring(L, -1)}`.red)
      lua_pop(L, -1)
      return
    }

For some reason, even though there aren't any errors in the code, it will output 1 as an error when running this code:

while true do
    print('hi')
    wait(1)
end

image

It fixed itself if I remove

if (ok2 !== lua.LUA_OK) {
      process.send(`${lua.lua_tojsstring(L, -1)}`.red)
      lua_pop(L, -1)
      return
    }

so I'm guessing thats whats causing it.

daurnimator commented 5 years ago

You should probably look at the implementation of your wait function.