Как перебрать таблицу, измененную с помощью luaL_ref и luaL_unref?

Я использую C API Lua для расширения Lua. В моем модуле я хочу заполнить таблицу, используя luaL_ref, и удалить поля, используя luaL_unref. Я также хочу иметь возможность перебирать эту таблицу, надеюсь, используя lua_next.

Итерация по таблице является проблемой из-за luaL_unref. В Lua принято «удалять» поля таблицы, назначая nil (поскольку неинициализированные поля таблицы оцениваются как nil). Функция next достаточно умна, чтобы пропустить nil. Я ожидал, что luaL_unref присвоит nil полям таблицы, на которые нет ссылок, но, похоже, это присваивает целое число. Значение этого целого числа кажется недокументированным.

Рассмотрим следующий код:

/* tableDump prints a table: */
/* key: value, key: value, ... */

lua_newtable(L);

lua_pushboolean(L, 0);
int ref1 = luaL_ref(L, -2);

lua_pushinteger(L, 7);
int ref2 = luaL_ref(L, -2);

lua_pushstring(L, "test");
int ref3 = luaL_ref(L, -2);

tableDump(L, -1);

luaL_unref(L, -1, ref1);
tableDump(L, -1);

luaL_unref(L, -1, ref3);
tableDump(L, -1);

luaL_unref(L, -1, ref2);
    tableDump(L, -1);

printf("done.\n");

Выход:

1:  false,  2:  7,  3:  `test',  
3:  `test',  2:  7,  0:  1,  
3:  1,  2:  7,  0:  3,  
3:  1,  2:  3,  0:  2,  
done.

Что тут происходит? Как я мог обойти это? Есть ли какой-то трюк, чтобы перебирать ссылки и игнорировать ссылки? Должен ли я прекратить использовать luaL_ref и luaL_unref?


Изменить

Прежде всего, спасибо за ваши ответы!

Может быть, я задал неправильный вопрос.

Позвольте мне быть немного более конкретным. У меня есть пользовательские данные клиента, которым необходимо управлять многими пользовательскими данными подписки. Подписки создаются методом подписки клиента. Подписки удаляются методом отказа от подписки клиента. Пользовательские данные подписки в основном являются деталями реализации, поэтому они не отображаются в клиентском API. Вместо этого клиентский API использует ссылки на подписку, отсюда и использование luaL_ref для заполнения таблицы подписки.

ref = client:sub(channel, func)
cleint:unsub(ref)

Вот подвох. Я хотел бы, чтобы клиент автоматически отменял все оставшиеся подписки на __gc (иначе пользователь получит ошибку сегментации). Так что, похоже, мне нужно перебрать подписки. Я действительно злоупотребляю API здесь? Есть лучший способ сделать это?


person tprk77    schedule 07.10.2012    source источник
comment
Прекратите злоупотреблять API. Целью luaL_ref и luaL_unref является не заполнение таблицы. Это создание простой справочной системы, позволяющей хранить значения по ссылке и извлекать их позже. Вы не должны не перебирать таблицу; вы должны использовать возвращенную ссылку для доступа к записям таблицы.   -  person Nicol Bolas    schedule 07.10.2012


Ответы (2)


Функции luaL_ref и luaL_unref определены в lauxlib.c. Они работают, отслеживая свободный список ссылок, который они хранят в таблице, с которой они работают. Эти функции относительно короткие, поэтому я включу их здесь.

LUALIB_API int luaL_ref (lua_State *L, int t) {
  int ref;
  t = abs_index(L, t);
  if (lua_isnil(L, -1)) {
    lua_pop(L, 1);  /* remove from stack */
    return LUA_REFNIL;  /* 'nil' has a unique fixed reference */
  }
  lua_rawgeti(L, t, FREELIST_REF);  /* get first free element */
  ref = (int)lua_tointeger(L, -1);  /* ref = t[FREELIST_REF] */
  lua_pop(L, 1);  /* remove it from stack */
  if (ref != 0) {  /* any free element? */
    lua_rawgeti(L, t, ref);  /* remove it from list */
    lua_rawseti(L, t, FREELIST_REF);  /* (t[FREELIST_REF] = t[ref]) */
  }
  else {  /* no free elements */
    ref = (int)lua_objlen(L, t);
    ref++;  /* create new reference */
  }
  lua_rawseti(L, t, ref);
  return ref;
}

LUALIB_API void luaL_unref (lua_State *L, int t, int ref) {
  if (ref >= 0) {
    t = abs_index(L, t);
    lua_rawgeti(L, t, FREELIST_REF);
    lua_rawseti(L, t, ref);  /* t[ref] = t[FREELIST_REF] */
    lua_pushinteger(L, ref);
    lua_rawseti(L, t, FREELIST_REF);  /* t[FREELIST_REF] = ref */
  }
}

Эти функции действительно умны, потому что они не требуют дополнительной памяти (кроме таблицы, с которой они работают). Однако иногда нежелательно хранить список свободных ссылок в той же таблице, что и значения, на которые ссылаются, например, когда необходимо выполнить итерацию по значениям, на которые ссылаются.

Я написал luaX_ref и luaX_unref, чтобы решить эту проблему. Они работают почти так же, как luaL_ref и luaL_unref, за исключением того, что они хранят список бесплатных ссылок в отдельной таблице, l для списка. (Для моего проекта я поместил их в отдельный исходный файл и включил при необходимости. Я не рекомендую изменять lauxlib.c.)

static int abs_index(lua_State * L, int i){
  return i > 0 || i <= LUA_REGISTRYINDEX ? i : lua_gettop(L) + i + 1;
}

LUALIB_API int luaX_ref(lua_State *L, int t, int l){
  int ref;
  t = abs_index(L, t);
  l = abs_index(L, l);
  if(lua_isnil(L, -1)){
    lua_pop(L, 1); /* remove from stack */
    return LUA_REFNIL; /* 'nil' has a unique fixed reference */
  }
  lua_rawgeti(L, l, FREELIST_REF); /* get first free element */
  ref = (int) lua_tointeger(L, -1); /* ref = l[FREELIST_REF] */
  lua_pop(L, 1); /* remove it from stack */
  if(ref != 0){ /* any free element? */
    lua_rawgeti(L, l, ref); /* remove it from list */
    lua_rawseti(L, l, FREELIST_REF); /* (l[FREELIST_REF] = l[ref]) */
  }else{ /* no free elements */
    ref = (int)lua_objlen(L, l);
    ref++; /* create new reference */
  }
  lua_pushboolean(L, 1);
  lua_rawseti(L, l, ref); /* l[ref] = true */
  lua_rawseti(L, t, ref); /* t[ref] = value */
  return ref;
}

LUALIB_API void luaX_unref(lua_State *L, int t, int l, int ref){
  if(ref >= 0){
    t = abs_index(L, t);
    l = abs_index(L, l);
    lua_rawgeti(L, l, FREELIST_REF);
    lua_rawseti(L, l, ref);  /* l[ref] = l[FREELIST_REF] */
    lua_pushinteger(L, ref);
    lua_rawseti(L, l, FREELIST_REF);  /* l[FREELIST_REF] = ref */
    lua_pushnil(L);
    lua_rawseti(L, t, ref); /* t[ref] = nil */
  }
}

Теперь посмотрите на использование:

lua_newtable(L); /* 1 */
lua_newtable(L); /* 2 */

lua_pushboolean(L, 0);
int ref1 = luaX_ref(L, 1, 2);

lua_pushinteger(L, 7);
int ref2 = luaX_ref(L, 1, 2);

lua_pushstring(L, "test");
int ref3 = luaX_ref(L, 1, 2);

tableDump(L, 1);
tableDump(L, 2);

luaX_unref(L, 1, 2, ref1);
tableDump(L, 1);
tableDump(L, 2);

luaX_unref(L, 1, 2, ref3);
tableDump(L, 1);
tableDump(L, 2);

luaX_unref(L, 1, 2, ref2);
tableDump(L, 1);
tableDump(L, 2);

printf("done.\n");

Выход:

1:  false,  2:  7,  3:  `test',
1:  true,  2:  true,  3:  true,
2:  7,  3:  `test',
3:  true,  2:  true,  0:  1,
2:  7,
3:  1,  2:  true,  0:  3,

3:  1,  2:  3,  0:  2,
done.
person tprk77    schedule 09.10.2012

Чтобы «обеспечить уникальность возвращаемого ключа», luaL_ref должен вести список используемых и впоследствии luaL_unref удаленных ключей. Похоже, что этот список начинается с t[0] и продолжается до тех пор, пока цепочка индексов не приведет к nil. Этот список хранится в той же таблице, что и активные ссылки.

Если вы хотите продолжать «злоупотреблять API», как заметил Никол, и зависеть от поведения, определенного реализацией, вы можете следовать этому связанному списку, чтобы увидеть, является ли ключ удаленной ссылкой при повторении таблицы. Или, чтобы избежать зависимости от поведения, определяемого реализацией, и повысить производительность, вы можете сохранить отдельную таблицу удаленных ссылок и пропускать их при повторении таблицы, хотя вам нужно будет игнорировать заголовок списка в t[0].

Если вам действительно нужно повторять ссылки, возможно, вам лучше использовать другой механизм. Вы можете просто поместить все свои пары ссылка/значение в отдельную таблицу и установить значение в отдельной таблице на nil при удалении ссылки. Затем вы можете просто перебрать отдельную таблицу.

person Doug Currie    schedule 07.10.2012