lua-users home
lua-l archive

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


Hi,

Thanks for the env example. After looking through the code I'm still unclear how these new environments for c functions and user data is helpful getting GC on Lua objects.

I preferred to create tables and pass them back through my C API. But, the only way to get GC and associate them with my C object was with the weak-key table as someone else described in this thread. Is there something obvious that I am missing?

Thx

DC wrote:

Roberto Ierusalimschy wrote:

It is straightforward. Use the new "environment" for userdata. This is
already present in 5.1w5. See

http://lua-users.org/lists/lua-l/2005-03/msg00046.html

Has anyone used this is practice and can share an example? straightforward seems to be a reserved keyword for me.


I attached an example below. A simple userdata-based queue object.
You need to compile this as a module and load it with require "queue".
Have a look at the attached test script, too.

This was doable before, but with some difficulties (use the registry,
use an indirect table in an upvalue or abuse the metatable).
An implementation with closure-based objects is possible, too
(except that you can't have additional methods).

I wrote this example purely for educational purposes. If you
want to polish it, maintain it and release it -- please go ahead.
This may be a good start for an efficient container library
(i.e. container.queue(), container.stack(), ...).

The example shows how to use the new C function environment, too
(which is sort of an extra upvalue #0 that is always present).

Note: no, this module does _not_ work with Lua versions prior to
      Lua 5.1-work5. It won't even compile.

Bye,
     Mike


------------------------------------------------------------------------

/*
 * Simple queue module. Intended to demonstrate the new Lua 5.1-work5
 * C function environments and userdata environments.
 *
 * I wrote this just for educational purposes. It's neither
 * complete nor well tuned. Feel free to improve it.
 *
 * Copyright (C) 2005 Mike Pall. All rights reserved.
 * Released as open source under the MIT/X license.
 */

#include "lua.h"
#include "lauxlib.h"

/* Queue userdata structure. */
typedef struct q_s {
  int head;	/* Index of queue head element. */
  int tail;	/* Index of queue tail element. */
} q_t;

static q_t *queue_udata(lua_State *L)
{
  q_t *q;
  /* Compare the metatable against the C function environment. */
  if (lua_getmetatable(L, 1) &&
      lua_rawequal(L, -1, LUA_ENVIRONINDEX) &&
      (q = (q_t *)lua_touserdata(L, 1))) {
    lua_pop(L, 1);
    return q;
  }
  luaL_argerror(L, 1, "not a queue");
  return NULL;		/* Never reached. But the compiler doesn't know. */
}

static int queue_call(lua_State *L)
{
  q_t *q = queue_udata(L);
  if (lua_isnoneornil(L, 2)) {
    /* Queue get. */
    if (q->head >= q->tail) return 0;	/* Emtpy queue? */
    /* Get and remove queue head. */
    lua_getfenv(L, 1);
    lua_rawgeti(L, -1, q->head++);
    lua_pushnil(L);
    lua_rawseti(L, -3, -1);
    /* TODO: Add queue reorganization, shifting elements down to [1,*]. */
    /* if (head > 16 && head + head > tail) { ... shift down ... } */
    return 1;
  } else {
    /* Queue put (single element only). TODO: Allow multiple elements. */
    lua_getfenv(L, 1);
    lua_pushvalue(L, 2);
    lua_rawseti(L, -2, q->tail++);
    /* For convenience return the queue object itself. */
    lua_settop(L, 1);
    return 1;
  }
}

static int queue_len(lua_State *L)
{
  q_t *q = queue_udata(L);
  lua_pushinteger(L, q->tail - q->head);
  return 1;
}

static int queue_peek(lua_State *L)
{
  q_t *q = queue_udata(L);
  if (q->head >= q->tail) return 0;	/* Emtpy queue? */
  /* Peek at queue head. */
  lua_getfenv(L, 1);
  lua_rawgeti(L, -1, q->head);
  return 1;
}

static int queue_flush(lua_State *L)
{
  q_t *q = queue_udata(L);
  q->head = 1;
  q->tail = 1;
  /* Just create a new empty table. Garbage collection can be soooo nice. */
  lua_newtable(L);
  lua_setfenv(L, 1);
  /* For convenience return the queue object itself. */
  lua_settop(L, 1);
  return 1;
}

/* TODO: Add more methods. */
static luaL_reg queue_methods[] = {
  {"len",	queue_len},
  {"peek",	queue_peek},
  {"flush",	queue_flush},
  {NULL, NULL}
};

static int queue_new(lua_State *L)
{
  q_t *q;
  lua_settop(L, 0);
  /* Create userdata object holding the queue pointers. */
  q = (q_t *)lua_newuserdata(L, sizeof(q_t));
  q->head = 1;
  q->tail = 1;
  /* Attach the metatable to the userdata object. */
  lua_pushvalue(L, LUA_ENVIRONINDEX);
  lua_setmetatable(L, 1);
  /* Create an empty table and store it in the userdata environment. */
  lua_newtable(L);
  lua_setfenv(L, 1);
  return 1;
}

static luaL_reg queue_funcs[] = {
  {"new",	queue_new},
  {NULL, NULL}
};

LUA_API int luaopen_queue(lua_State *L)
{
  /* Create the metatable for queue objects. */
  lua_createtable(L, 0, 2);
  /* Set it as the default C function environment. */
  lua_pushvalue(L, -1);
  lua_replace(L, LUA_ENVIRONINDEX);
  /* mt.__index = mt */
  lua_pushvalue(L, -1);
  lua_setfield(L, -2, "__index");
  /* Fill in methods. */
  luaL_openlib(L, NULL, queue_methods, 0);
  lua_pushcfunction(L, queue_call);
  lua_setfield(L, -2, "__call");
  /* Create the module table. */
  luaL_openlib(L, "queue", queue_funcs, 0);
  return 1;
}



------------------------------------------------------------------------


-- A few tests for the example queue module.

local queue = require "queue"

local N = arg and arg[1] or 100

local q = queue.new()

assert(q:len() == 0)
assert(q:peek() == nil)
assert(q() == nil)

for i=1,N do
  q(i)
  assert(q:len() == i)
end

for i=1,N do
  assert(q:peek() == i)
  assert(q() == i)
end

for i=1,N do q(i) end
do
  local i = 1
  for o in q do
    assert(o == i)
    i = i + 1
  end
end
assert(q:len() == 0)

assert(q(1)(2)(3):len() == 3)
assert(q:flush():len() == 0)

for i=1,N do q(i) end
for i=1,10*N do q(q()) end
for i=1,N do assert(q() == i) end
assert(q:len() == 0)