lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


> On Aug 19, 2015, at 3:06 PM, Philipp Janda <siffiejoe@gmx.net> wrote:
> 
> Hi!
> 
> Am 19.08.2015 um 20:56 schröbte Tim Hill:
>> 
>> As I said in my earlier post, the original motivation for userdata
>> was to allow Lua to provide opaque storage for C libraries. The
>> operative word here is “opaque” .. Lua can store the data and manage
>> its lifetime (via the GC), but not access it. This provided a string
>> guarantee that the C library could rely on the integrity of the data
>> in the userdata when called, since Lua code could not corrupt it.
> 
> I think there's still some confusion. Let me try: Forget slice, think user-defined tag. The original userdata has tag 0, and you can push additional aliases (same pointer, different tag). Naturally, the userdata gets collected when the last reference (tags are ignored for this) vanishes. The userdata is still opaque, Lua doesn't know/care what's in it. What you *can* do as the user is to check in your (meta-)methods, which tag the userdata that was passed as argument has. E.g. if you get the original userdata (<0x1234|0>) in your `__index` metamethod, you would handle the keys "x" (pushing/returning a number) and "y" (pushing/returning the userdata with alias number 1: <0x1234|1>). If the alias number is 1, you'd handle the "z" key (pushing/returning a string). This would roughly correspond to the following C struct:
> 

yes, I was guessing that was the intent, but the OP didn’t really make it clear. In which case the pretty trivial solution is to use indirection. Each userdata object is a structure as follows:

typedef struct tagUDRef {
	MyUserData *pRoot;	// Root pointer to MyUserData (same in all UDRefs that point to it)
	void *pInterior;			// Interior pointer to some arbitrary part of MyUserData, different for each UDRef
} UDRef;

The actual user object is as follows:

typedef struct tagMyUserData {
	int nRefCount;			// Number of UDRef structs that point to this memory block
	… // Actual data goes here (or pointers to it etc)
} MyUserData;

Every Lua userdata object is actually a UDRef struct, all of which point (via pRoot) to a MyUserData struct. Each of these UDRef structs is refcounted in nRefCount (that is, nRefCount is the number of UDRef structs that point to it). Each UDRef object also contains an “interior” pointer into whatever part of MyUserData it wants to reference for that particular userdata. Now all you have is two functions: one to create a new reference to an interior part of the struct, the other is __gc to handle releasing the memory when the refcount reaches zero.

This scheme allows all userdata pointers, including the “base” one to be peers; as long as any are live the data is retained, as soon as all are GCed, the data is released. It’s easy to create a new userdata pointer from ANY of the existing ones (as they all contain the root pointer). There is also no need for weak tables of refs or any of that odd book-keeping. It can work with any data structure you care to name, as long as you can wrap the nRefCount into it (one way to “hide” the nRefCount is to over-allocate memory and adjust the pointer so the refcount is at a negative offset from the root pointer).

I’ve not included code here, as it’s pretty trivial. One wrinkle is to make __gc idempotent to copy with people who do silly things like use the debug library to call __gc directly.

Simple, robust, fast :)

—Tim