nLuaServer
[Language Support]
Detailed Description
nLuaServer provides Lua 5 based scripting services for Nebula
Installation
This module should be checked out to nebula2/code/contrib/nluaserver, after that just re-run nebula2/update.tcl and you should be ready to go. Open up the luaserver solution/workspace in the appropriate build directory (nebula2/build/XXXX) and build everything. You do not need to download and build Lua itself, that's integrated into this module.
The Lua core (located under nebula2/code/contrib/nluaserver/inc/lua and nebula2/code/contrib/nluaserver/src/lua) is subject to the terms found in nebula2/code/contrib/nluaserver/inc/lua/COPYRIGHT
Distribution
nebthunker.lua and console.lua found under nebula2/code/contrib/nluaserver/bin are required for the proper functioning of nLuaServer and hence must be shipped with your product. You can move them anywhere you like so long as your scripts can find them :)
Preamble
For documentation on the Lua language see the Lua documentation.
You will frequently encounter the terms thunking and thunk in this document, in this context thunking refers to creating a thunk given a NOH object, and a thunk is a Lua table that acts as a wrapper for a NOH object.
There are 2 kinds of commands in nLuaServer: Those that deal with NOH objects as simple text and those that deal with NOH objects by thunking a Lua table to behave more like a class instance.
Simple Commands
These provide scripts with access to the Nebula kernel, which allows you to create, destroy NOH objects and navigate through the NOH in script.
- new(classname, path)
Create a new object of the specified class at the specified path in the NOH. Returns true on success and nil on failure.
example:new('n3dnode', 'camera') new('nglserver', '/sys/servers/gfx')
- delete(path)
Delete the NOH object at the specified location in the NOH. Returns true on success and nil on failure.
example:delete('/usr/scene/shader')
- sel(path)
Set the current working directory of the NOH to the specified path. Returns true on success and nil on failure.
example:sel('/sys/servers/gfx')
- exit()
Exit Nebula. Exit does not return a value.
- puts(string)
Echo the provided string to the Kernel server's log handler chain.
example:puts('fish <)))<')<br>
- ls()
Return a table containing the names of the children of the current working directory in the NOH.
- conls()
Same as ls() but will also print out the output to the console.
- call(funcname, ...)
Execute the command given by funcname on the currently selected object in the NOH.
example:-- acts on current object sel('/sys/servers/gfx') call('opendisplay') -- with arguments sel('/usr/camera') call('txyz', 4.3, 2, 1)
- concall(funcname, ...)
Same as call() but will also print out the output of funcname to the console (if there is any output).
- mangle(assignpath)
This is a hook to nFileServer2's ManglePath method. This will expand any assigns in the specified path and return the result.
example:get(mangle('home:/data/tekdemos/light.n'))
- conmangle(assignpath)
Same as mangle() but will also print the resulting path to the console.
- exists(path)
Returns true if the specified NOH object exists, false otherwise.
example:exists('/sys/servers/audio')
- conexists(path)
Same as exists() but will also print true/false to the console.
- up()
Same as sel('..')
- pushcwd(path)/popcwd()
Just exposes nKernelServer::Push/PopCwd(). Note that unlike nKernelServer::PopCwd() popcwd() will return nothing.
example:/> sel('/another/place') /another/place> pushcwd('/some/where/in/paradise') /some/where/in/paradise> popcwd() /another/place>
Thunks
Thunks provide a number of advantages.
- A much nicer syntax for manipulating NOH objects via their script interface than that provided by call(). In effect thunking creates an OO system for Lua.
- Repeated access of Nebula script commands is faster with thunks, however the time taken to create a thunk maybe significant. You are best to test this out for yourself.
- Lua functions can be associated with thunks allowing you to extend a NOH object's script interface at run-time, with the ability to call the new commands from C++.
Thunk Commands
These provide scripts with the ability to create, destroy and manipulate thunks.
- delete(thunk)
Works just like delete(path) above but takes a thunk instead.
example:delete(nebula.usr.scene.object)
- psel()
Return a thunk for the current working directory of the NOH.
- sel(thunk)
This is the same as sel(path) but take a thunk instead.
example:sel(nebula.sys.servers.file2)
- get(filename)
Load and run a persistency script and return a thunk for the NOH object created on success. Return nil on failure.
example:someThunk = get('data/some.n')
- lookup(path)
Return a thunk of the NOH object residing at the provided path. This will not change the current working directory in Nebula.
example:audioThunk = lookup('/sys/servers/audio')
- pushcwd(thunk)
Same as pushcwd(path) but takes a thunk instead.
- pin(thunk)
Pins a thunk.
- unpin(thunk)
Unpins a thunk.
- IsZombieThunk(thunk)
Return true if thunk is a zombie, false if it isn't and nil if some error occured while trying to figure it out.
- PruneNebThunks()
Remove any zombie thunks from the internal thunk store.
- begincmds(className, numCmds)
Prepare to add the specified number of commands to the specified class.
- addcmd(className, cmdName)
Add the specified command declaration to the specified class, begincmds(className, numCmds) must've been called for the specified class before adding any commands.
- endcmds(className)
Finish adding commands to the specified class.
Pinning
When a thunk is pinned a reference to it is stored in the internal thunk store, unpinning removes the reference from the afforementioned store. lookup() and psel() first look in the thunk store to see if a thunk for the corresponding NOH object already exists, and if it does return it instead of creating a new thunk. This allows you to maintain a 1:1 mapping between NOH objects and thunks, and is essential for defining and using script-side commands (described below).
Zombie Thunks
If a NOH object is destroyed from C++ while a corresponding thunk remains alive then the thunk will refer to a non-existent NOH object. You won't be able to use the thunk, trying to do so will result in an error, this sort of thunk is called a zombie thunk. Ideally you should avoid creating zombies, but if that fails IsZombieThunk() and PruneNebThunks() will help you slay the living dead. You can use IsZombieThunk() to find out if a particular thunk is a zombie. PruneNebThunks() will clean out zombies from the internal thunk store, it should be followed by a garbage collection phase to actually destroy the thunks.
Adding Script-Side Commands to Objects
The Lua server allows you to add additional commands to a class's script interface at run-time (via begincmds(), addcmd(), endcmds()). You can then implement the new commands in script per object. These commands can then be called from C++ via the familiar nCmdProto/nCmd interface. In order for all of this to work there must be a 1:1 mapping between objects and thunks, this is accomplished by pinning. If you want to implement new commands for a particular object first obtain a thunk for that object, then pin that thunk so the Lua server can find it later and implement the commands. An example of this is provided in the luatest project.
Just to give you an idea of what this stuff can be used for... I used it for implementing event handlers for my GUI system widgets in script (or C++), you'll probably find some other uses for it :).
nebthunker.lua
This script should be run on startup before trying to run any other script (refer to the Luatest startup.lua script for an example). nebthunker.lua defines the metatable for thunks, this metatable allows for a cleaner syntax than that provided when using call(). When nebthunker.lua is executed it creates a new nebula thunk that refers to the NOH root, this is done for your convenience. nebthunker.lua also defines the PruneNebThunks() function.
nebthunker.lua allows for syntax like this:
nebula.sys.servers.gfx:opendisplay()
where a dot operator is used as the path separator and a ':' is used to specify the method name.
Note that for each path component (except the first) a lookup() is performed, so when you write nebula.sys.servers.gfx what actually happens is lookup('/sys'); lookup('/sys/servers'); lookup('/sys/servers/gfx'), it will be faster to just write lookup('/sys/servers/gfx') however sometimes looks are more important :). The quick solution to avoiding performance issues on repeated calls of this nature is to simply keep a reference to thunk.
example:
a = nebula.sys.servers.gfx a:opendisplay() a:setclearcolor(1, 1, 1, 1)
console.lua
This script file defines the conls(), conmangle(), conexists() and up() functions, if you want to make use of these functions be sure to load this script at startup, e.g. dofile(mangle('home:bin/console.lua'))
Standard Lua Libraries
By default nLuaServer loads baselib, strlib, tablib, iolib and mathlib for convenience.
If you want to add additional libraries to the server you can do so by adding them in nLuaServer::nLuaServer().
Debugging Lua code
Unfortunately there's currently no debugger for Lua code in the nLuaServer, you're pretty much stuck with puts(). However to make debugging slightly less painful whenever an error occurs in Lua code a stack trace will be printed out in addition to the error message.
Here's a sample error and the corresponding stack trace...
nLuaServer encountered a problem... [string "-- start.lua..."]:135: attempt to call global `Ooh_I_am_so_naughty' (a nil value) -- Stack Trace -- [C] - #-1: Ooh_I_am_so_naughty (global/C) [string "-- start.lua..."] - #135: NaughtyFunctionC (global/Lua) [string "-- start.lua..."] - #139: GoodFunctionB (global/Lua) [string "-- start.lua..."] - #143: GoodFunctionA (global/Lua) [string "-- start.lua..."] - #147: GenerateStackTrace (global/Lua) [string "GenerateStackTrace()"] - #1: ??? (???/main)
short_src - #currentline: name (namewhat/what)
If you want error messages and stack traces to be as helpful as possible you should always identify each script file by putting it's name as a comment on the first line of the script file, or any other short one line comment that uniquely identifies that file. For example...
-- startup.lua ... the rest of the file ...
Luatest
This little project provides an example of the new features of the server. Run nshlua as follows:
nshlua
Once it starts up press F11 to bring up the console, now do the following:
> dofile(mangle('home:code/contrib/nluaserver/data/luatest/start.lua'))
> start()
> sel('/test')
> call('testscriptcmds')
(output should indicate all 4 tests passed)
> call('makezombies')
> PruneNebThunks()
(output should indicate 2 zombies were found)
> GenerateStackTrace()
(generates a Lua error and prints out a stack trace)
> stop()
> exit()
It's a bit of a pain to type in the long path for the dofile() so if you plan on running the luatest more than once you might want to copy nebula2/code/contrib/nluaserver/data/luatest to nebula2/data/luatest, and then modify the assign at the top of start.lua to point to the new location, that way you have less to type :)
(c) 2004 Vadim Macagon & Matthew T. Welker
Classes | |
| class | nCmdProtoLua |
| A factory for nCmd objects that correspond to Lua implemented script commands. More... | |
| class | nLuaServer |
| Lua 5 wrapper for Nebula. More... | |
| class | nLuaTest |
| A little test suite for testing the Lua server. More... | |