Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ SMAUG ➜ Lua ➜ Designing a Nice Lua Implementation like Aardwolf's

Designing a Nice Lua Implementation like Aardwolf's

It is now over 60 days since the last post. This thread is closed.     Refresh page


Pages: 1  2 

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #15 on Fri 07 Sep 2012 11:40 PM (UTC)
Message
Fiendish said:

Does that just implement C++11's weak_ptr?


No, not as such. It was written (August 2003) before I had heard of C++11.

It uses a STL map to hold a integer-to-pointer conversion, where each pointer is assigned an incrementing "pointer ID".

Deleting the pointer removes it from the map, so if you then attempt to get it, you get back NULL.

The idea is you store this ID (where you would normally store a pointer) and convert it back at the last moment. The IDs are not re-used, and being a 64-bit number you can use a lot of them before you run out.

I don't necessarily know if this is the best solution, as I say, the general problem is non-trivial.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Fiendish   USA  (2,534 posts)  Bio   Global Moderator
Date Reply #16 on Sat 08 Sep 2012 05:04 AM (UTC)
Message
Quote:
No, not as such. It was written (August 2003) before I had heard of C++11.
well, yes, I suspect 2003 was a long time before 2011. :D
But I think this (non-owning null-verifying pointers) can be accomplished by weak pointers now with modern c++.

https://github.com/fiendish/aardwolfclientpackage
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #17 on Sun 09 Sep 2012 07:47 PM (UTC)

Amended on Wed 12 Sep 2012 09:09 PM (UTC) by Kasji

Message
Well I've got command hook setup inside of interpret(), and all is well. I am using stat() and S_ISDIR() to verify the file exists and is not a directory, then interpret() calls my function lua_command().

Relevant parts from my lua.c file:
extern "C" int luaCHSend(lua_State * L);

static const luaL_reg luaMudlib[] =
{
        {"send_to_char", luaCHSend},
        {NULL, NULL}
};

extern "C" int luaRegisterMudlib(lua_State * L)
{
        int i = 0;
        while (luaMudlib[i].name != NULL && luaMudlib[i].func != NULL)
        {
                lua_register(L, luaMudlib[i].name, luaMudlib[i].func);
                i++;
        }
        return RET_OK;
}

int luaOpen()
{
        // Create a new Lua state.
        g_luaState = luaL_newstate();
        if (!g_luaState)
        {
                log_lua("luaOpen: Failed to create lua_State!");
                return RET_ERR;
        }
        // Load standard libraries.
        luaL_openlibs(g_luaState);
        // Load mud library.
        lua_pushcfunction(g_luaState, luaRegisterMudlib);
        lua_call(g_luaState, 0, 0);
// None of these methods work?
//      luaRegisterMudlib(g_luaState);
//      luaL_register(g_luaState, "mud", luaMudlib);
        // Load mud interfaces.
//      luaRegisterCH(g_luaState);
//      luaRegisterRoom(g_luaState);
//      luaRegisterObj(g_luaState);

        lua_settop(g_luaState, 0);

        return RET_OK;
}

void lua_command(CHAR_DATA * ch, const char * command, const char * argument)
{
   char path[MAX_INPUT_LENGTH];
   char func[MAX_INPUT_LENGTH];
   int err;

   if (!g_luaState)
   {
      log_lua("lua_command: FATAL error: g_luaState == NULL!");
      return;
   }

   snprintf(path, MAX_INPUT_LENGTH, "%sdo_%s.lua", LUA_CMD_DIR, command);
   if ((err = luaL_loadfile(g_luaState, path)) != 0)
   {
      log_lua("lua_command: luaL_loadfile() error %d: %s", err, lua_tostring(g_luaState, -1));
      return;
   }

   snprintf(func, MAX_INPUT_LENGTH, "do_%s", command);
   lua_getglobal(g_luaState, func); // Get name of Lua func
   lua_pushlightuserdata(g_luaState, (void*)ch->GetId().Value()); // Push character ID
   lua_pushstring(g_luaState, argument); // Push argument
   if ((err = lua_pcall(g_luaState, 3, 0, 0)) != 0)
   {
      log_lua("lua_command: lua_pcall() error: %d: %s", err, lua_tostring(g_luaState, -1));
      return;
   }

   return;
}

And lua_funs.c:
extern "C" int luaCHSend(lua_State * L)
{
        CHAR_DATA * ch;
        const char * arg;
        char buf[MAX_STRING_LENGTH];
        uint64 id;
bug("luaCHSend"); // Just to see if we ever make it here, which we aren't...
        if (!L)
        {
                log_lua("luaCHSend: FATAL error: L == NULL");
                return 0;
        }

        if (lua_gettop(L) != 2)
        {
                log_lua("luaCHSend: Invalid number of arguments.");
                luaL_error(L, "luaCHSend: Invalid number of arguments.");
                return 0;
        }

        id = (uint64) lua_touserdata(L, -2);
        ch = char_map[id];
        if (ch == NULL)
        {
                log_lua("luaCHSend: NULL ch.");
                luaL_error(L, "luaCHSend: NULL ch.");
                return 0;
        }

        if (IS_NPC(ch))
                return 0;

        arg = lua_tostring(L, -1);
        if (arg == NULL)
        {
                log_lua("luaCHSend: NULL argument.");
                luaL_error(L, "luaCHSend: NULL argument.");
                return 0;
        }

        snprintf(buf, MAX_STRING_LENGTH, "%s\n\r", arg);

        send_to_char(buf, ch);

        return 0;
}

Continued in next post...
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #18 on Sun 09 Sep 2012 07:48 PM (UTC)
Message
And here is ../lua/commands/do_lua_test.lua:
--- do_lua_test, function to test the lua implementation

function do_lua_test(ch, argument)
    --- NEITHER of these don't get called. At least one should be?
    mud.send(ch, "This is a test!")
    send(ch, "Another test!")
    return
end

It never complains about anything. It executes do_lua_test I am guessing because lua_pcall() never complains, but nothing ever shows up and luaCHSend() is never reached. And of course, I don't expect both mud.send() and send() to work, but one of them should, depending on if lua_register() or luaL_register() is used.

As you can see from the comments in luaOpen(), I've tried different ways of making it work, but nothing does work.

From Nick's code, there is this:
static int RegisterLuaRoutines (lua_State *L)
  {

  lua_newtable (L);  /* environment */
  lua_replace (L, LUA_ENVIRONINDEX);

  /* this makes environment variable "character.state" by the pointer to our character */
  lua_settable(L, LUA_ENVIRONINDEX);

  /* register all mud.xxx routines */
  luaL_register (L, MUD_LIBRARY, mudlib);
  
  /* using interpret now
  RegisterLuaCommands (L);
  */

  luaopen_bits (L);     /* bit manipulation */

  return 0;
  
}  /* end of RegisterLuaRoutines */

void open_lua  ( CHAR_DATA * ch)
  {
  lua_State *L = luaL_newstate ();   /* opens Lua */
  ch->L = L;
  
  if (ch->L == NULL)
    {
    fprintf( stderr, "Cannot open Lua state\n");
    return;  /* catastrophic failure */
    }

  luaL_openlibs (L);    /* open all standard libraries */

  /* call as Lua function because we need the environment  */
  lua_pushcfunction(L, RegisterLuaRoutines);
  lua_pushstring(L, CHARACTER_STATE);  /* push address */
  lua_pushlightuserdata(L, (void *)ch);    /* push value */
  lua_call(L, 2, 0);
 
  /* run initialiation script */
  if (luaL_loadfile (L, LUA_STARTUP) ||
      CallLuaWithTraceBack (L, 0, 0))
      {
      const char * sError = lua_tostring(L, -1);
      
      fprintf( stderr, "Error loading Lua startup file:\n %s\n", 
              sError);
      
      if (IS_IMMORTAL(ch))
        {
        set_char_color( AT_YELLOW, ch );
        ch_printf (ch, "Error loading Lua startup file:\n %s\n", 
                  sError); 
        }  /* end of immortal */

      }

  lua_settop (L, 0);    /* get rid of stuff lying around */
        
  }  /* end of open_lua */


There are only two big differences between Nick's code and mine that I have noticed.
1) He's got some table stuff going on with LUA_ENVIRONINDEX.
2) He executes a startup script to bind his mudlib functions to global functions.

I'd appreciate any help with this. Also, what is the LUA_ENVIRONINDEX stuff going on here? I don't quite understand it.
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #19 on Sun 09 Sep 2012 08:52 PM (UTC)

Amended on Sun 09 Sep 2012 08:53 PM (UTC) by Nick Gammon

Message
Template:codetag To make your code more readable please use [code] tags as described here.


In particular the bit about "Convert Clipboard Forum Codes".

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #20 on Sun 09 Sep 2012 09:57 PM (UTC)
Message
Nice, thanks.

I found one error in my code.
   if ((err = lua_pcall(g_luaState, 3, 0, 0)) != 0)

Should be this:
   if ((err = lua_pcall(g_luaState, 2, 0, 0)) != 0)

Interestingly, this raises an error message now:
Lua: lua_command: lua_pcall() error: 2: attempt to call a nil value
   Path: ../lua/commands/do_lua_test.lua

At least, I think that's the way it should be? Based on the reference manual...
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #21 on Sun 09 Sep 2012 10:10 PM (UTC)
Message
I added a new check to lua_command(), and found the problem. Or at least closer to it.
   snprintf(func, MAX_INPUT_LENGTH, "do_%s", command);
   lua_getglobal(g_luaState, func);
   if (!lua_isfunction(g_luaState, -1))
   {
      lua_pop(g_luaState, -1);
      log_lua("lua_command: Function not found: %s", func);
      return;
   }

It catches on this. As you can see in the lua file above, the function is there. What am I missing...?
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #22 on Sun 09 Sep 2012 10:44 PM (UTC)
Message
Ok, I changed
   if ((err = luaL_loadfile(g_luaState, path)) != 0)
   {
      log_lua("lua_command: luaL_loadfile() error %d: %s\n\r   Path: %s", err, lua_tostring(g_luaState, -1), path);
      return;
   }

To this:
   if ((err = luaL_dofile(g_luaState, path)) != 0)
   {
      log_lua("lua_command: luaL_dofile() error %d: %s\n\r   Path: %s", err, lua_tostring(g_luaState, -1), path);
      return;
   }

Now I'm getting this error:
Lua: lua_command: lua_pcall() error: 2: ../lua/commands/do_lua_test.lua:5: attempt to call global 'send' (a nil value)
   Path: ../lua/commands/do_lua_test.lua

So I'm getting the original issue of luaCHSend() not being registered I guess.
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #23 on Tue 11 Sep 2012 02:56 PM (UTC)
Message
Problem resolved. Just a bit of silliness on my part I guess. The original problem was not that luaCHSend() wasn't being called, but that do_lua_test() wasn't being called because the lua script has to not only be loaded, but also executed, in order to call individual functions in the script. While stumbling around in the dark with the problem, I happened to change luaCHSend()'s binding from send() to send_to_char(), but never updated the script, so I fixed the issue with do_lua_test() not being found, but created a new problem by changing the binding. All is well now. Lua is now working within the MUD, and once I add in a few more mud functions for Lua, and finish the basic metatables for ch (victim), self (mob), room, and obj, I will release my implementation.

This will require updating CHAR_DATA, ROOM_INDEX_DATA, and OBJ_DATA with the tObject<> class, and will also require the aforementioned structs to have constructors/destructors. CREATE() and DISPOSE() operations on the structs have to be replaced with new/delete operators. From there, all metamethods and functions callable from Lua only possess the ID of the entity, and have to look it up in the appropriate map.

I've already done this with CHAR_DATA, so I am one third done there.

Things are progressing nicely now. :)
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #24 on Sun 16 Sep 2012 06:22 PM (UTC)
Message
Alright, looks like things are pretty good, here's my test script:
-- do_lua_test, function to test the lua implementation

function send(char, ...)
    send_to_char(char.id, table.concat {...} .. "\n\r")
   return
end -- send

function do_lua_test(ch, argument)
   if argument then
      send(ch, "&RYou typed: " .. argument)
   end
   send(ch, "&GYour name is " .. ch.name .. ", and you are a " .. ch.gender .. " " .. ch.race .. ".")
   och = get_char_world(ch, "tech")
   if och then
      send(ch, "&YWe found: " .. och.name)
   end
   ch:say("Testing")
   return
end -- do_lua_test


And here is the output:
lua_test Some arg
You typed: Some arg
Your name is Kasji, and you are a male Noghri.
We found: a skittish verpine tech
You say, "Testing"


I'm ready to release this implementation as is. I am implementing this with other code (eg a custom combat system) and I don't want to release something full of stuff that doesn't apply to everyone else's codebases. Does anyone know of a place I can upload the code?
Top

Posted by Kasji   (35 posts)  Bio
Date Reply #25 on Sat 22 Sep 2012 04:14 AM (UTC)

Amended on Sat 22 Sep 2012 11:41 PM (UTC) by Kasji

Message
I went ahead and reworked my system. I made a C++ wrapper class for coroutines, and I implemented a sleep function for Lua. I created a separate class for an updater that includes as well, a way to create coroutines that are then added to the update list. It's working well with one exception. I can create coroutines very quickly and it's fine, but if I spam creating them, say create 10 in one second, the code crashes. I don't really understand why it crashes, but it's crashing on lua_resume().

lua_coroutine.c:
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <setjmp.h>
#include "mud.h"
#include "lua.h"

extern lua_State * g_luaState;
extern jmp_buf g_luaEnv;
extern tPointerOwner<L_COROUTINE> coroutine_map;

DECLARE_LUA_FUN(luaPanic);

lua_coroutine::lua_coroutine(const char * type, uint64 id) : tObject<L_COROUTINE>(coroutine_map)
{
   clock_gettime(CLOCK_MONOTONIC, &(this->m_created));
   snprintf(this->m_owner_type, 10, "%s", type);
   this->m_owner_id = id;
   if (g_luaState)
   {
      this->m_L = lua_newthread(g_luaState);
      lua_atpanic(this->m_L, luaPanic);
   }
   else
      this->m_L = NULL;
   this->m_state = 0;
   this->m_started = FALSE;
   return;
}

lua_coroutine::~lua_coroutine()
{
}

int lua_coroutine::run(const char * funcname, int nargs)
{
   int jmp;

   jmp = setjmp(g_luaEnv);
   if (jmp == 1)
   {
      log_lua("Long jump successful.");
      return 0;
   }

   snprintf(this->m_func, 32, "%s", funcname);

   lua_getglobal(this->m_L, funcname);
   if (!lua_isfunction(this->m_L, -1))
   {
      log_lua("lua_coroutine::run: Function not found: %s", funcname);
//      lua_settop(g_luaState, 0);
      return 1;
   }
   if (nargs > 0)
      lua_insert(this->m_L, (-1 - nargs));

   this->m_state = lua_resume(this->m_L, nargs);
   this->m_started = TRUE;
   return this->m_state;
}

int lua_coroutine::resume()
{
   int jmp;

   if (this->m_started == FALSE || this->m_state != LUA_YIELD)
      return 0;

   jmp = setjmp(g_luaEnv);
   if (jmp == 1)
   {
      log_lua("Long jump successful.");
      return 0;
   }

   // If coroutine.yield returned any values, they need to be popped.
// while (lua_gettop(this->m_L) > (1 + this->m_nargs))
//    lua_pop(this->m_L, -1);

   this->m_state = lua_resume(this->m_L, 0);
   return this->m_state;
}

int lua_coroutine::status()
{
   if (this->m_L)
      this->m_state = lua_status(this->m_L);
   return this->m_state;
}

bool lua_coroutine::started()
{
   return this->m_started;
}

const timespec& lua_coroutine::created()
{
   return this->m_created;
}

uint64 lua_coroutine::owner_id()
{
   return this->m_owner_id;
}

const char * lua_coroutine::owner_type()
{
   return this->m_owner_type;
}

lua_State * lua_coroutine::L()
{
   return this->m_L;
}

const char * lua_coroutine::func()
{
   return this->m_func;
}

bool lua_coroutine::operator==(const lua_coroutine &rhs)
{
   return this->GetId().Value() == rhs.GetId().Value();
}


lua_update.c:
         {
            msg = lua_tostring(cor->L(), -1);
            log_lua("lua_updater::update(): %d: %s\n\r    Owner Type: %s  Owner ID: %llu", status, msg, cor->owner_type(), cor->owner_id());
         }
         it = this->killthread(it);
         continue;
      }
   }

   return;
}

L_COROUTINE * lua_updater::newthread(const char * type, uint64 id)
{
   L_COROUTINE * cor = new L_COROUTINE(type, id);
   this->m_coroutine_list.push_back(cor->GetId().Value());

   return cor;
}

void lua_updater::killthread(uint64 id)
{
   delete coroutine_map[id];
   this->m_coroutine_list.remove(id);
   return;
}

L_COR_IT lua_updater::killthread(L_COR_IT it)
{
   delete coroutine_map[(*it)];
   return this->m_coroutine_list.erase(it);
}

void lua_update()
{
   g_luaUpdater->update();

   return;
}


Here's a copy of the stack from a core dump:
#0  0x00002b935acbd999 in ?? () from /usr/lib64/liblua-5.1.so
#1  0x00002b935acbd137 in ?? () from /usr/lib64/liblua-5.1.so
#2  0x00002b935acbd31a in lua_resume () from /usr/lib64/liblua-5.1.so
#3  0x000000000060faa0 in lua_coroutine::resume (this=0xd0ff800) at lua_coroutine.c:84
#4  0x0000000000619dfe in lua_updater::update (this=0xcf19740) at lua_update.c:37
#5  0x000000000061a093 in lua_update () at lua_update.c:84
#6  0x000000000060c0cc in update_handler () at update.c:2775
#7  0x00000000004d18bb in game_loop () at comm.c:580
#8  0x00000000004d0912 in main (argc=5, argv=0x7fff6861d488) at comm.c:252


Edit:
Here's the function I use to yield scripts in Lua (mud:time() is basically just (tv_sec + (tv_usec/1000000)) from gettimeofday()):
function sleep(ch, seconds)
   local start = mud:time()

   while mud:time() - start < seconds do
      coroutine.yield()
   end
   return
end -- sleep
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


78,608 views.

This is page 2, subject is 2 pages long:  [Previous page]  1  2 

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.