Trying LUA again.. and failing

BlitzMax Forums/BlitzMax Beginners Area/Trying LUA again.. and failing

QuietBloke(Posted 2006) [#1]
Hi all,

after about a year I have returned to trying to use LUA.

I have had a little more sucess.. it runs now.. but I am still unsure what exactly it is capable of.

Let me try to explain. Ive just been reading 'MUD Game programming' and I figured I might try to write a simple MUD in BMax. The problem comes when in the book the author uses Python and C++ to write scripts for everything. eg. If a player tries to pick up an object the game calls a script function canpickup and passes it the player object and the object to be picked up. The script then finds the player encumberance values ( current and max ) by reading through a MAP and then sees if the object weight plus the current 'Encumberance' will exceed the 'MaxEncumberance' and returns true or false.

And there is my problem... can I pass a TYPE to LUA ?.. and if I can then if the player Type contains a TMAP can lua access it ? I dont want to have to pass specific values based on what script function Im calling because that would make the script calling pointless as every new function would require code changes in BMax.

btw.. Im currently trying to use the scriptEngine... I dont know if thats the best way to do it or whether I should call lua at a lower level.

and while Im at it... if I want to use the script engine do I have to call setscripttext everytime I want to call a different function ( I was planning on having seperate script files for every function ) or do I have to put all the functions into 1 HUGE script file ?

Finally.. when you call SetScriptText you give it 2 parameters... can someone explain why ?... I mean when you run RunScript it doesnt take a parameter so why do I have to give it a name when I set it ?.. or is this the way I can load lots of scripts into the engine ?.. anyway.. Ive only been playing around for an hour or so so maybe I can answer that last question myself tommorow when I get some time.

Thanx for any help

QuietBloke


SculptureOfSoul(Posted 2006) [#2]
Heya QuietBloke. I too am working on a MUD server, and am modeling it loosely off of the MUD illustrated in Mr. Penton's book.

I don't necessarily have a lot of good answers for you as I've decided to switch languages recently, and never fully got up to speed with Lua prior to doing so.

Anyhow, you can "sort of" pass types to Lua. Actually, you don't pass the type into Lua per se, what happens is that space (a block large enough to hold the type) is allocated on the virtual stack. A pointer is returned to your application, and it is through this pointer that you can access that data from in your application. Lua can access the data through a variable - a variable that is assigned to the same block of data on the stack when it calls your "type creation" function. This type of data is called "user data," and Lua manages the memory.

For a much better overview of this, take a look at the following piece from the book "Programming in Lua."
http://www.lua.org/pil/28.1.html



Actually, the whole book is available for free there, (just click the Programming in Lua link at the top of the page). For more clarification you can head back to Chapter 24 as it's the beginning of the section on interfacing C with Lua. Or, as I'd recommend, start from the beginning. You'll understand Lua much better that way.

Anyhow, back to the issue at hand. The problem with this user data is that Bmax doesn't offer pointers to user defined types. So if you have type TPlayer, you can't utilize the pointer returned to use by calling lua_newuserdata(). This means that you'll have to resort to implementing any and all functions that act on a player in Lua itself if you go this route, since you can't access the object in Bmax at all. As far as I can tell there is no way around this with Bmax. Lua creates the data structure on it's virtual stack. You can't do something like passing the address of the TPlayer to Lua and then let it access that memory directly - at least not with the user data option.

You can look into lightuserdata, but from what I understand of it it's either not going to work well with BMax or end up being quite hack'ish (due to BMax's lack of true pointer support)

The approach I was planning to use was to pass the unique EntityID of any and all objects needed by the script to Lua, and then have Lua pass those values back in it's callback functions.

Let me clarify that a bit...basically, all of the core functionality (and action you'd like a Lua script to act on) would have to be defined as a BMax function. So a trivial example that modifies a player stat might look like so

ModifyStat( lua_state:byte ptr)
local player_id:int 
local Player:TPlayer

player_id = int(lua_tonumber( lua_state, 1)) 
rem
Lua is passing the player id of the player this function
 is acting on as the first argument 
(the second parameter in lua_tonumber is the index of the stack element we are accessing...
the first or bottommost stack element in this case).
We cast to the returned value to int because lua returns doubles (it's only number type).
endrem

rem
'look up the actual player we're acting on from the player database
'assume GPlayerDB is the player DB
endrem
Player = GPlayerDB.GetPlayerById( player_id )

rem
'now let's get the second item on the stack. In this case assume that the Lua script is passing the array index of
 the player stat we want to modify in stack index 2, and 
the new value for that array index in stack index 3
endrem
local statIndex:int = int(lua_tonumber( lua_state, 2))
local newStatValue:int = int(lua_tonumber(lua_state,3))
Player.statArray[statIndex] = newStatValue


This function would need to get registered with lua (see programming in lua for that process) and then you can call it from within Lua - and lua will pass the arguments passed to the function call in the script to your Bmax function - where the leftmost argument in the lua script will be at stack index 1, the next argument at stack index 2, and so on.

So the call in the lua script, assuming the function was registered as "ModifyPlayerStat" might look like so

ModifyPlayerStat( player.player_id, player_stats["strength"]) <---where player_stats["strength"] returns the index value of that stat stored in the TPlayer type in Bmax


Obviously this is a bit of a hack'ish work around, and doesn't let you easily extend the MUD because, as as yOu were worried about everything requires changes in the Bmax code. It is workable though - you just have to have all of the core physics (mud driver stuff) implemented in BMax and make it callable by Lua.

I wish there was a better solution, but given Max's lack of pointers the only solution I could see is creating yet another layer between Lua and BMax in C, and utilizing that C code to convert the return value of lua_newuserdata to a specific BMax type.

The other solution, and this is what I was planning on implementing, was to utilize an extremely flexible entity type in BMax (structured around a hash table that could hold any type of value for any index.) This would allow the calls from Lua to Bmax to be much more generic, such as

if player.getvalue( player.id, "Encumbrance") > 50 then
player.sendstring( "Sorry, you are too weighted down to perform that action." )

Basically, I was planning on emulating the flexibility of the Lua table type in Bmax itself. This seemed like the most flexible approach given the limitations I mentioned above.

All in all, I didn't find a great way to interface Lua and BMax in such a manner that allows extensibility from the Lua side. Lua works great with Bmax in cases where you know upfront what functions you might want or need Lua to call in Bmax, but an extensible MUD is definitely not one of those cases.

Anyhow, for a variety of reasons I'm switching my MUD over to a different language. First, BMax doesn't have multi-threading, which can be a killer when you are accessing a database with a slow query (I'm using MySQL as the DB for my mud.) Secondly, polling all of your active sockets every frame or tick or whatever is just a waste of CPU power - and I can't afford to waste those cycles because down the road I'd like to have really extensive AI, dynamic weather, and other CPU intensive processes running in the background. Third - interfacing BMax with Lua was proving to be too ugly and slow.

Sorry if this was a long read with few answers, but that's where I got before deciding that I needed to jump ship. I may be missing some elegant solution though, seeing as I'm both a relative newbie to BMax and a definite newb to Lua - so don't take this as a definitive answer.

*all code written on the fly and likely is error prone _ for illustrative purposes only ;)


SculptureOfSoul(Posted 2006) [#3]
Oh and if you are planning on sticking with Bmax, I strongly recommend using a hashtable structure instead of a TMap to store player data and such. The lookup for a hashtable is O(n) time (constant time) while a map is going to be O(log n). Not a huge difference but given the frequency with which lookups occur (especially if you follow the design outlined in "Mud Game Programming") it should make a noticeable difference.

If you're interested I can provide you with the hash table type I worked on. If you know about C++ STL structures, it's basically a hybrid of a STL:Map and an STL:Multi-map -- i.e. it can store either a single piece of data per key, or multiple pieces of data per key. It's fast, too - as fast or faster than hash look-ups in Lua (which every table access is).

The only functionality it's missing is the ability to automatically grow. I've got the Grow() method coded, but I don't currently run any checks when inserting to see if it needs to grow as the table gets larger.


QuietBloke(Posted 2006) [#4]
thanx for that reply.. that was very useful. In fact after I wrote my message and went to sleep I realized Id misread the part in the book about retrieving player stats and that in fact he too exposes methods to get/set/check for stats just as your ModifyStat example does.

I am only planning on writing a small scale MUD ( just to see how to write one ) so I dont mind too much about the lack of multithreading though the hashtable type you wrote does sound like it would be useful as ( like you said ) i will be accessing data from scripts a LOT.

Im still not totally sure how I am supossed to implement lua in the game. ie .. load all the scripts I need as 1 huge file or load all the scripts individually into memory then use SetScriptText/RunScript for each call. Thanx for the book link... seems I have a lot more reading to do.

Thanx Again and
Good luck with your MUD