lua-users home
lua-l archive

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


Am 20.08.2015 um 00:54 schröbte Tim Hill:

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 :)

Yes, close. But you are mixing two different approaches here. For the tag-approach `pInterior` should be an integer tag. The pointer won't help you much because you don't know what to do with it. It lacks any type information (it's a void pointer, and the metatable is that of the base object). You could try to calculate the difference between `pRoot` and `pInterior` to figure out where you are actually pointing to (won't work if you are dealing with a union), but an id is simpler.

But the charm of the proposal was that no memory needs to be allocated to push the userdata with a different tag. Allocating a new userdata for every `__index` access is wasteful, and you really should do some caching using a table (I'd choose a non-weak table in this case, though).

However, the tagged userdata approach could be useful in some circumstances nevertheless, e.g. for simulating pointers to base-classes when binding a C++ library.


—Tim


Philipp