LuaObjCBridge & LuaCore Updates

IinTextMediumImage've been working with the LuaObjCBridge (by Tom McClean) and LuaCore (by Gus Mueller). I've encountered some bugs, limitations, and things that needed improvement. As I'm already forgetting what changes I've made, I decided I better document them and push them back out for the public to test and improve. Because my changes may potentially break compatibility (not necessarily a bad thing though because some things just didn't work correctly in the original code base), I've pushed my changes into a separate repository.

Meanwhile, I've been using a lot of these changes to test ideas for the next generation LuaObjCBridge which will use BridgeSupport and likely libffi. If you are interested in contributing, please don't hesitate to contact me.


The files containing the LuaObjCBridge can be found here:

http://www.assembla.com/wiki/show/LuaCocoa


And my LuaCore snapshot (which contains its own up-to-date copy of the LuaObjCBridge) can be found here:

http://www.assembla.com/wiki/show/LuaCore


Changes to the LuaObjCBridge:

Obj-C Runtime vs Foundation code path:
Switched back to the old Runtime code path. There is a bug somewhere with the Foundation based code that seems to cause a corrupt Lua state. I've seen it manifest itself in something simple like calling the same function from Lua to Obj-C twice in a row. I don't have time to track down the issue. In reality, we need to work on the next-gen bridge anyway, so fixing this up is a low priority for me. But if somebody wants to volunteer, I can provide some stale test code that triggered the problem.

LUA_OBJC_RETAIN_AND_RELEASE_INSTANCES:
Fixed the LUA_OBJC_RETAIN_AND_RELEASE_INSTANCES stuff. This code never worked. The code should have been using full UserData but it wasn't. This meant that __gc was never called and nothing was ever release and your application would leak like crazy. The code has now been changed to use full UserData for objects to allow the __gc method to be called. Also, I made this compatible with Obj-C 2.0 garbage collection so you can compile your host app in either mode. CFRetain and CFRelease have been inserted into the correct places to make sure objects that cross the bridge stay alive when accessible in Lua. This same code works for non-gc mode too. I think this is a first for bridge languages as PyObjC and RubyCocoa currently don't support this.

Remember to not call retain/release on the Lua side when using this. (Actually if you're balanced, it might be okay...however, you need to understand the special cases for alloc/copy/new below.)

LUA_OBJC_DECREMENT_ON_ALLOC_OR_COPY_TO_RELY_ON_LUA_GC:
Used in conjunction with LUA_OBJC_RETAIN_AND_RELEASE_INSTANCES
Added special cases for alloc, allocWithZone, copy, copyWithZone, mutableCopy, mutableCopyWithZone, and new to not increase the retain count under these calls. This prevents an unwanted retain from happening if you are doing everything on the Lua side. Things should just work correctly with memory management on the Lua side if you use this.


Returning struct types:
There seemed to be a serious bug with returning struct types across the bridge. The bridge's call to objc_msgSendv_stret didn't account for a quirk about the API which expects that the beginning of the marg list contains enough empty space for the return struct which is where it actually puts the return value, and not the first parameter. Also, the code for direct return and Intel needed to be activated.
There was also an equality bug (< vs <=).
And there were also some bugs for determining the size of things. I couldn't figure out how to fix the existing code, so I copied the code from the Foundation side of the implementation for these broken cases which seems to work for this.


Full Userdata for Pointer Types:
Changed Pointer Types to use full userdata instead of light userdata. (LUA_OBJC_USE_FULLUSERDATA_FOR_POINTER_TYPES) This is important because I wanted to add metatables for Core Foundation types. (See LuaCore.) In particular, I really wanted a __gc metamethod so I could call CFRelease at the proper time to avoid manual memory management. But since light userdata doesn't support metatables, this wasn't possible. If you don't have any C code that tries to directly access the data in the underlying light userdata pointer, then this change is hopefully safe for you. If you do have code that does this, then this change will likely break your code and you will want to disable this compile switch.


Added two public API methods:
lua_objc_isObject
lua_objc_pushpropertylist_or_id


lua_objc_isObject 

The following method will let you query if an object on the Lua stack is an Obj-C object or not. (Be warned, because the full userdata option was implemented as somewhat as an afterthought, this method may not be as reliable as it could be.)

In lua, you can call objc.isObject(some_var)

lua_objc_pushpropertylist_or_id

This will push as a property-list (NSNull, NSString, NSNumber, NSDictionary, NSArray) if it is a propertylist (offering the bridge conversions to nil, string, number, table). Otherwise it will push as a generic id.



Changes to LuaCore:

  • Some bug fixes, such as remembering to pop the stack on error returns, etc.
  • Lua 5.1 has a different paradigm for opening libraries. The luaopen_* convention must be used.
  • Altered behavior of calling Lua functions to not trigger errors if the function is not defined. (More analogous to subclassing and delegates...if not defined, nothing happens.)
  • Added some bridging support and advanced metatables for  the struct types: NSPoint, CGPoint, NSSize, CGSize, NSRect, CGRect, and CATransform3D. See the bottom of simpletest.lua to see all the cool things you can do.
  • Also exposed some official APIs for CATransform3D.
  • Added CGAffineTransform metatable and some APIs.
  • Added Core Foundation metatable and a few APIs from CFBase.h. The CFTypeRef metatable is designed as a generic metatable for any CF-compatible type (which includes CGTypes). It's most important contribution is that it defines a __gc metamethod that calls CFRelease. The SetMetatable will optionally do a CFRetain on the object which will keep the object under Lua control until the __gc is called. The default is to call CFRetain.
  • Added additional CallFunction variations. Also added a generic one based on the call_va example in Programming in Lua modified to support Obj-C objects and automatically restores the stack.

Enhanced the error system:

  • Allows users to easily provide their own lua_error function (to lua_pcall)
  • Overrides the default lua_error message to do a stack trace. (Useful if you have an error that isn't caught until levels down in function calls)

  • Delegate system for specifying Cocoa error handler. After lua error is handled, it fires a callback in Cocoa providing the error string, file name, line number, and function name the error seems to have originated from

Issues:

Some behavior changes, e.g. no errors on calling functions that aren't defined.

Legacy bindings don't behave the same way, e.g. NSRange.

Uses Leopard and Obj-C 2.0 in some places (Core Animation bindings, Obj-C 2.0 properties.) These could probably be cleaned up, wrapped in ifdefs, but I needed to get this written quickly.

Uses Lua 5.1 in a few places. Some things like setfield/getfield could be cleaned up easily. I'm not so sure about the custom stack trace error function. Ironically, it was written to give stack trace information more like Lua 5.0 where global function names were reported.



Special Notes:

Please be aware that any non-Obj-C types returned through the LuaObjCBridge directly will lack the fancy metatables I've written (NSRect, CATransform3D, CFTypeRef, etc) for the proper type. This is because the underlying LuaObjCBridge has no knowledge of what types are being passed through the bridge. To work around this, you can call the custom SetMetatable methods I provide for the appropriate types.  Be aware that these calls are dangerous in that you are allowed to set the wrong metatable on the wrong type of object, so don't do this.

NSPoint/CGPoint, NSRect/CGRect, NSSize/CGSize are not fully bridged. There is some attempt to automatically convert the types, but the underlying metatables are different so equality operations will always return false if you compare an NS against a CG. If you need to convert the types, use the explicit functions provided to do so.

Copyright © PlayControl Software, LLC / Eric Wing