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, 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.
 Entire forum ➜ SMAUG ➜ Lua ➜ Success or Failure stories

Success or Failure stories

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


Posted by ThomasWatts   USA  (66 posts)  Bio
Date Thu 15 Nov 2007 06:12 PM (UTC)
Message
I never saw many success or failure stories about Lua integration or even just plain Lua.

For example, after using Nick's v2 source for SmaugFUSS, I wedged it into Smaug1.4a with only minor changes. Since then I have heavily changed every related function except those directly dealing with the debug library as they work beautifully as is.
I'm diggin this myself, are you?
Tell us!
Top

Posted by Nick Gammon   Australia  (23,046 posts)  Bio   Forum Administrator
Date Reply #1 on Thu 15 Nov 2007 07:31 PM (UTC)
Message
Glad you are enjoying using it.

I certainly found doing a few subsystems, like the task system, there "where is" system, and the hints system, very simple under Lua.

I frequently read on this forum about problems people are having using "straight C" to make improvements, like managing strings, managing memory allocation, managing pointers, and so on. Almost all of this would be eliminated if you used Lua to add enhancements.

The Lua interface I have already described in other threads offers lots of ways of manipulating the virtual world, and you can always write more interface routines in C, if you need to do something in Lua that is not currently supported.

- Nick Gammon

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

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #2 on Fri 16 Nov 2007 03:28 AM (UTC)
Message
Everything Nick has said. Plus, I have found coroutines to be extremely helpful in implementing stateful command handling. It's a lot nicer to implement a "search" command by yielding a coroutine and resuming it later than having to lug around state in the player, and have a giant switch statement at the beginning of the command. ("nanny" is another good place where this is very helpful)

My only reticence is in losing all typing. I would like some kind of fixed attributes for things I am relatively confident are not going to change, like networking and other low-level stuff. I suppose I could do that stuff in C/C++ but then you get into issues of having to embed the two, even though it's pretty easy.



In non-MUD stuff, Lua has been very useful to me for rapid prototyping, as well as for configuration files. The rapid prototyping is perhaps more interesting. One project in particular was an AI assignment where the basic idea was to control a 4-legged robot navigating a hazardous environment. We spent an awful lot of time writing heuristics, and it got really tiresome to have to keep recompiling things just to tweak a heuristic. So I embedded a simple Lua interpreter, extended it with functions to access domain information (like "where am I", "what's the line of sight from here to there", etc.) And then, I moved all of the heuristics and such into Lua. It made it much, much faster to develop them, and the embedding/extending process was very easy.

I've done similar things in other projects, but the general idea was more or less the same. Step 1: add a Lua interpreter. Step 2: extend the Lua interpreter with domain functions. Step 3: add hooks from C/C++ into the Lua functions. Very easy and sped things up a lot when we needed to very rapidly experiment with different options.

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by ThomasWatts   USA  (66 posts)  Bio
Date Reply #3 on Fri 16 Nov 2007 05:36 AM (UTC)
Message
I'd be interested to know how you would implement 'nanny' with coroutine. If the login was always straight forward it'd be easy, but if they get the username wrong, or the password wrong, etc. If lua had a continue or goto statement I could see it working inside a while loop (nothing need for a goto), yield when needed. Also, if anyone is interested, I've gotten to the point where the lua state side handling is good enough that I've been able to code a working command (quit). I would love feedback on what I've done so far if anyone has some space online I could upload to. (no more than 5 meg)
Top

Posted by David Haley   USA  (3,881 posts)  Bio
Date Reply #4 on Fri 16 Nov 2007 06:00 AM (UTC)
Message
You do something like this:


local name

while true do
  input = WaitForInput() -- this yields the coroutine in a special way
  if IsGoodName(input) then
    name = input
    break
  end
  PrintErrorMessage()
end

SendMessage("Your name is " .. name)


of course it's more or less complicated depending on what kind of error conditions you need. But basically, you just keep yielding for input until you get something you like. The "special yield" I mentioned above is just some way of informing the command scheduler that the next line of input should be sent to this coroutine for resuming.

(An interesting consequence is that you could in principle have several commands pending, although I don't know why you'd want to do that offhand much less how you'd interact with it.)

David Haley aka Ksilyan
Head Programmer,
Legends of the Darkstone

http://david.the-haleys.org
Top

Posted by Nick Gammon   Australia  (23,046 posts)  Bio   Forum Administrator
Date Reply #5 on Fri 16 Nov 2007 06:44 AM (UTC)
Message
Yes, that was how I did it in a tentative MUD server I wrote in Lua.

The complexity is if you are trying to make nanny a thread, but basically have C as your main loop, and not Lua.

I think it could be done fairly easily, check out lua_resume in the Lua reference manual. Basically the C code would create a Lua coroutine and give it enough information to allow it to talk to the player (eg. some sort of descriptor code). The coroutine would ask questions, as David indicated (eg. "What is your name") and then yield while it waited for a reply.

Meanwhile, back on the C side, if the lua_resume call returns with LUA_YIELD, you know the thread is still running, so you await user input. Next time you get user input (eg. the player's name) you push that onto the stack, and then resume the coroutine again. You also might push a different thing (eg. nil) if a certain time elapses, so you can prompt the player if he is taking too long to respond.

Finally when the "nanny" script returns successully, it could return all the stuff it found out from the player (eg. his name, password, that sort of stuff). You would then retrieve that from the stack and put it to use.

I haven't got a working example, but it doesn't sound too hard to do.

You might need to write some C helper routines (eg. check if a player of that name already exists, check if a password is correct, that sort of stuff).

- Nick Gammon

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

Posted by Lasher   USA  (22 posts)  Bio
Date Reply #6 on Mon 16 Jun 2008 11:12 AM (UTC)
Message

Hope this is OK to post here as it is not Smaug based and does not use the Lua mud system mentioned here, but is a Lua success story.

All of the game scripting in Aardwolf MUD is Lua based. Other than the rewrite itself, it has been the best single project we ever implemented.

Performance has been fantastic. With literally millions of prog executions per day, hundreds of paused couroutine threads running at any given time (pauses in game progs), 3,500 old "mobprog" style progs converted to Lua plus around a thousand more since it was added, we experience no lag from GC whatsoever and the debug lib is reporting just under 10M usage at time of writing with a peak around 16M during the night. Not sure what muds already use Lua and which don't. If Realms is using Nick's Smaug implementation, then you already know all this, but if not and you wondered how it scales on a larger mud, perfectly.

It was also quite interesting to see how some of our approaches were almost identical while others were wildly different. For example, we have a single global state and reference char/object/room/mud/etc attributes through a metatable function that looks up the function to get that data mud side. So if you do 'ch.level' in a prog, the '.' triggers metatable __index which drops back to the MUD to find the function and datatype for 'level' in a table then return the results.

The tables themselves (one for each object type) are just plain old sequential lookups. If it reaches a size where performance becomes an issue, they could be converted into a simple hash tables back on the MUD with no other changes needed. We did try pushing all the data into Lua when progs are run, but this way was significantly faster. Always pulling it "real time" from the MUD also means we dont have to worry about situations were a Lua prog calls something back in the MUD which in turn updates information that has already been pushed to Lua.

On the other hand, we handle pauses almost identically to a post I saw on the forum - a table of threads/timers in the game itself for paused progs. This also afforded us a "faster" / "slower" option for players that simply decreases the timer by varying amounts on each pass - not everyone reads at the same pace.

The 'hanging pointer' issue is also resolved similarly, each game object has a unique gid that is hashed back in C. "Pointers" stored across prog invocations or pauses in progs reference these. We didn't need to add these for Lua as they were already in place for reply pointers and similar.

Bottom line, if you're wondering whether it is worth the work to convert to Lua, it absolutely is. Our builders love it and even mention side benefits like being able to put a "real" programming language on their resume. They are just now starting to realize they can create arbitrary commands in a single room only using lua, and even override game commands.

It is fast, stable, and mainstream. We've had several people who know MUDs convinced we took an LPMud and gave it a "diku-style" look and feel after going through our starting area -- that is always a compliment. Hope this isn't coming over as trying to "show off", that isn't the point. This stuff is exciting and I just can't rave enough about how great Lua has been for the MUD and the potential it opens up if you can find people creative enough to use it well.

Top

Posted by Nick Gammon   Australia  (23,046 posts)  Bio   Forum Administrator
Date Reply #7 on Mon 16 Jun 2008 09:29 PM (UTC)
Message
Thank you very much Aylorian for your detailed message. It is common to put a lot of work into something like the Lua integration with Smaug, and then only hear back a handful of complaints from people who can't make it work, and then nothing.

I am very pleased it works so well. I certainly think that coroutines are the way to go. Once you get into them, it is hard to see any other reasonable way of doing things. Since coroutines can keep their own "state" (ie. local variables) they can do a lot of things with a particular character or mob, without having to store temporary data in the C part of the MUD.

By the sound of it, your decision to use metatables has got around the major sticking point I could see with fully integrating Lua with Smaug, that is the difficulty of keeping two copies of the data (like level). You mention using __index in the metatable, presumably you would use __newindex if you wanted to change something (like the level).

Quote:

The tables themselves (one for each object type) are just plain old sequential lookups.


Did you ever convert to C++? Another way of speeding things up is to use STL, providing the code is C++ and not C.

For example, the "map" type does a very fast lookup based on a key.

I did a fairly length treatment about STL here:

http://www.gammon.com.au/forum/?bbtopic_id=111

The other nice thing about STL is the string type, where you can append to strings, and otherwise use them without having to worry about their lengths, or reallocating memory for them.


- Nick Gammon

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

Posted by Lasher   USA  (22 posts)  Bio
Date Reply #8 on Mon 16 Jun 2008 09:56 PM (UTC)
Message
>By the sound of it, your decision to use metatables has got
> around the major sticking point I could see with fully
> integrating Lua with Smaug, that is the difficulty of
> keeping two copies of the data (like level). You mention
> using __index in the metatable, presumably you would use
> __newindex if you wanted to change something (like the
> level).

Exactly as you said. For example, ch and room are initialized with:

static const luaL_reg luaCHGetset[] = {
{ "__index", luaCHGet },
{ "__newindex", luaCHSet },
{ NULL, NULL }
};

static const luaL_reg luaRoomGetset[] = {
{ "__index", luaRoomGet },
{ "__newindex", luaRoomSet },
{ NULL, NULL }
};

I vaguely remember looking at __CALL (or similar) but didn't want to use anything not documented in the reference manual just in case. I don't remember what the need for that was now, so it can't have been that important.

The get/set functions are basically:

...
GETSTACKCH(C,1,"Expected CH","property access");

propName = luaL_checkstring(l,2);
if (ISNULL(propName)) return 0;

for (prop = chProps; prop->name != NULL; prop++) {
if (compares(prop->name,propName)) break;
}
...

Propnames is a simple table of property name, the variable type to return to Lua, a get function and a set function if set is allowed on that property. A friend of mine came up with this approach and I was skeptical about speed, but it really does work.

We never did go C++, but converting the property tables to a simple 27 way (alpha+other) hash would be trivial and bring down access time further. I have the tables ordered with the most commonly used properties at the beginning so we haven't really seen a need.

The fixed types for obj, ch, room, exit and mud make chaining commands very powerful too, for example:

-- give item academy-3 to razor, anywhere in game.
-- give is give(OBJ,CH,opts)
give(oload("academy-3"), getmob("razor",WORLD+PLRONLY))

Obviously checking each for nil would be cleaner, but this would fail gracefully with an error log if the object did not exist or the player was not around. Or:

-- load a copy of self in random room in zone.
-- used at death to make sure this mob always exists
-- somewhere without making it unkillable.
mload(self.key, getroom("aylor-"..math.random(1,100)))

The 'zonekey-number' is what we use instead of vnums. Vnum ranges would make it even easier.

Can't recommend strongly enough taking the leap and converting to Lua. We havent even touched on the potential of lua sockets for web/forum integration, social network
widgets etc yet.
Top

Posted by Nick Gammon   Australia  (23,046 posts)  Bio   Forum Administrator
Date Reply #9 on Tue 17 Jun 2008 03:28 AM (UTC)
Message
That sounds great, and it sounds like you have done a much neater job than I did when I did the Lua task system a while ago.

I had lots of individual routines to pull in data from the C side via function calls, eg.


race = mud.race (char_or_mob)


The system you describe, and which I see on your web site is much neater, eg.


race = ch.race


I wouldn't worry too much about the __call metamethod. Although it is fleetingly documented in the Lua Reference Manual, I have never found a need to use it.




By the way, since you are using coroutines a lot, I wonder if you noticed something I just found out the other day (you probably have).

Anyway, for future reference, I was playing with coroutines a fair bit when testing my MUD stress test program, and was a bit irritated when something inside a coroutine crashed, and I wasn't sure why. A small example will demonstrate:


function send (s)
  print ("sending " .. s)   --> line 2
end -- send

function a ()
  send ("hi there")  --> line 6
end -- a

function b ()
  send (nil)   --> line 10
end -- b

function f ()
  b ()  --> line 14
end -- f

thread = coroutine.create (f)

result = assert (coroutine.resume (thread))  --> line 19


The coroutine here is function f, and if you run this you get this error message:


Run-time error
World: smaug 2
Immediate execution
[string "Immediate"]:19: [string "Immediate"]:2: attempt to concatenate local 's' (a nil value)
stack traceback:
        [C]: in function 'assert'
        [string "Immediate"]:19: in main chunk


All we know from this message is that the coroutine.resume at line 19 failed, and it was because of a problem with a nil value in line 2. But where is the traceback? In my example both functions a and b call "send" so I don't know which one to blame (line 6 or line 10). Obviously I do in this short example, but in a more complex case I wouldn't.

Thankfully you can traceback a failed coroutine. I will replace line 19 with the following code:


local ok, err = coroutine.resume (thread)
if not ok then
   print (debug.traceback (thread))
   assert (ok, err)
end -- if


Now if I run the code I see this:


stack traceback:
        [string "Immediate"]:2: in function 'send'
        [string "Immediate"]:10: in function 'b'
        [string "Immediate"]:14: in function <[string "Immediate"]:13>
Run-time error
World: smaug 2
Immediate execution
[string "Immediate"]:22: [string "Immediate"]:2: attempt to concatenate local 's' (a nil value)
stack traceback:
        [C]: in function 'assert'
        [string "Immediate"]:22: in main chunk


Now it is clear that function b (at line 10) is the culprit and this particular instance of b was called from line 14.

This makes the debugging much easier. Of course, this is the sort of message that could be logged or sent to an admin for corrective action.


- Nick Gammon

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

Posted by Orik   USA  (182 posts)  Bio
Date Reply #10 on Wed 18 Jun 2008 02:26 PM (UTC)
Message
Not to get off hand with what Nick has been saying, but to get back to the original post. I have been using lua just for the questing system alone and it has really been a lot of fun to have chain quests with good rewards at the end of the chain.

I look forward to seeing more come out of the lua scripting down the road. I am happy with it and have found good use for it. I don't have any examples to show but I work on my code as a hobby and sometime later I'll actually run it in beta. Life is too busy to get much done on it right now though.

I just wanted to say that I do enjoy the hard work that has been put into this. It's been really fun to see and use.
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.


36,927 views.

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.