Я пишу библиотеку iOS для встраивания Lua в игры и столкнулся с проблемой, связанной с пользовательскими данными.Я хочу, чтобы пользователи могли обрабатывать объекты моей библиотеки как обычные таблицы (в сценариях Lua) для установки атрибутов и для того, чтобы эти атрибуты были доступны базовым объектам в библиотеке.Для случаев пользовательский сценарий может иметь
line = display.newLine
line.width = 3
Тогда поле width
должно быть доступно из кода библиотеки (Objective C / C).
У меня есть эта работа, вроде, но после запуска в течение нескольких секунд я получаю ошибку EXC_BAD_ACCESS, поэтому, очевидно, я получаю доступ к освобожденному объекту или имею какой-то другой тип повреждения памяти, но я не могу понять, почему.
У меня естьурезал мой код до одного примера, чтобы воспроизвести ошибку.Сначала у меня есть базовый объект Objective C, который реализует функциональность библиотеки.Заголовок показан ниже:
#import "lua.h"
#include "lualib.h"
#include "lauxlib.h"
@interface GeminiLine : NSObject {
int selfRef;
int propertyTableRef;
lua_State *L;
}
@property (nonatomic) int propertyTableRef;
-(id)initWithLuaState:(lua_State *)luaStat;
-(double)getDoubleForKey:(const char*) key withDefault:(double)dflt;
@end
Этот класс хранит ссылку на объект lua_State и целочисленные ссылки Lua на его соответствующие пользовательские данные Lua и пользовательское значение (таблицы, связанные с пользовательскими данными).Ссылка propertyTableRef
используется для доступа к атрибутам объекта (таблица пользовательских значений).
Реализация приведена ниже:
#import "GeminiLine.h"
@implementation GeminiLine
@synthesize propertyTableRef;
-(id)initWithLuaState:(lua_State *)luaState {
self = [super init];
if (self) {
L = luaState;
}
return self;
}
-(double)getDoubleForKey:(const char*) key withDefault:(double)dflt {
lua_rawgeti(L, LUA_REGISTRYINDEX, propertyTableRef);
//lua_pushstring(L, key);
//lua_gettable(L, -2);
lua_getfield(L, -1, key);
if (lua_isnil(L, -1)) {
return dflt;
}
return lua_tonumber(L, -1);
}
-(void)dealloc {
luaL_unref(L, LUA_REGISTRYINDEX, propertyTableRef);
[super dealloc];
}
@end
Единственный метод без жизненного цикла - это метод getDoubleForKey
, который обращается к пользовательскому значению Lua, связанному с пользовательскими данными для объекта.
Код C для привязки объекта к Lua приведен здесь:
///////////// lines ///////////////////////////
static int newLine(lua_State *L){
NSLog(@"Creating new line...");
GeminiLine *line = [[GeminiLine alloc] initWithLuaState:L];
GeminiLine **lLine = (GeminiLine **)lua_newuserdata(L, sizeof(GeminiLine *));
*lLine = line;
[Gemini shared].line = line;
luaL_getmetatable(L, GEMINI_LINE_LUA_KEY);
lua_setmetatable(L, -2);
// append a lua table to this user data to allow the user to store values in it
lua_newtable(L);
lua_pushvalue(L, -1); // make a copy of the table becaue the next line pops the top value
// store a reference to this table so we can access it later
line.propertyTableRef = luaL_ref(L, LUA_REGISTRYINDEX);
// set the table as the user value for the Lua object
lua_setuservalue(L, -2);
NSLog(@"New line created.");
return 1;
}
static int lineGC (lua_State *L){
NSLog(@"lineGC called");
GeminiLine **line = (GeminiLine **)luaL_checkudata(L, 1, GEMINI_LINE_LUA_KEY);
[*line release];
return 0;
}
static int lineIndex( lua_State* L )
{
NSLog(@"Calling lineIndex()");
/* object, key */
/* first check the environment */
lua_getuservalue( L, -2 );
if(lua_isnil(L,-1)){
NSLog(@"user value for user data is nil");
}
lua_pushvalue( L, -2 );
lua_rawget( L, -2 );
if( lua_isnoneornil( L, -1 ) == 0 )
{
return 1;
}
lua_pop( L, 2 );
/* second check the metatable */
lua_getmetatable( L, -2 );
lua_pushvalue( L, -2 );
lua_rawget( L, -2 );
/* nil or otherwise, we return 1 here */
return 1;
}
// this function gets called with the table on the bottom of the stack, the index to assign to next,
// and the value to be assigned on top
static int lineNewIndex( lua_State* L )
{
NSLog(@"Calling lineNewIndex()");
int top = lua_gettop(L);
NSLog(@"stack has %d values", top);
lua_getuservalue( L, -3 );
/* object, key, value */
lua_pushvalue(L, -3);
lua_pushvalue(L,-3);
lua_rawset( L, -3 );
NSLog(@"Finished lineNewIndex()");
return 0;
}
// the mappings for the library functions
static const struct luaL_Reg displayLib_f [] = {
{"newLine", newLine},
{NULL, NULL}
};
// mappings for the line methods
static const struct luaL_Reg line_m [] = {
{"__gc", lineGC},
{"__index", lineIndex},
{"__newindex", lineNewIndex},
{NULL, NULL}
};
int luaopen_display_lib (lua_State *L){
// create meta tables for our various types /////////
// lines
luaL_newmetatable(L, GEMINI_LINE_LUA_KEY);
lua_pushvalue(L, -1);
luaL_setfuncs(L, line_m, 0);
/////// finished with metatables ///////////
// create the table for this library and popuplate it with our functions
luaL_newlib(L, displayLib_f);
return 1;
}
Ключевыми методами здесь являются newLine
и lineNewIndex
.В newLine
я создаю объект C GeminiLine
, соответствующий объекту Lua, и сохраняю указатель на него в пользовательских данных Lua.Я также храню указатель на новый объект в одноэлементном объекте Gemini
, который предоставляет основной программе доступ к выполнению сценариев Lua.Этот класс приведен здесь:
Gemini *singleton = nil;
@interface Gemini () {
@private
lua_State *L;
}
@end
@implementation Gemini
@synthesize line;
- (id)init
{
self = [super init];
if (self) {
L = luaL_newstate();
luaL_openlibs(L);
}
return self;
}
+(Gemini *)shared {
if (singleton == nil) {
singleton = [[Gemini alloc] init];
}
return singleton;
}
-(void)execute:(NSString *)filename {
int err;
lua_settop(L, 0);
NSString *luaFilePath = [[NSBundle mainBundle] pathForResource:filename ofType:@"lua"];
setLuaPath(L, [luaFilePath stringByDeletingLastPathComponent]);
err = luaL_loadfile(L, [luaFilePath cStringUsingEncoding:[NSString defaultCStringEncoding]]);
if (0 != err) {
luaL_error(L, "cannot compile lua file: %s",
lua_tostring(L, -1));
return;
}
err = lua_pcall(L, 0, 0, 0);
if (0 != err) {
luaL_error(L, "cannot run lua file: %s",
lua_tostring(L, -1));
return;
}
}
@end
Для моей тестовой программы я создал приложение, используя шаблон единого представления.Я изменил метод applicationDidFinishLaunching
на AppDelegate
для вызова тестового сценария следующим образом:
-(void) update {
double width = [[Gemini shared].line getDoubleForKey:"width" withDefault:5.0];
NSLog(@"width = %f", width);
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
[[Gemini shared] execute:@"test"];
timer = [NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:@selector(update)
userInfo:nil
repeats:YES];
....
Я также включил таймер, который срабатывает 100 раз в секунду, и метод update
в качестве цели,Метод update
извлекает атрибут, установленный в скрипте lua width
, и регистрирует его с помощью NSLog
.
Используемый мной скрипт test.lua
приведен ниже:
display = require('display')
line = display.newLine()
line.width = 3;
Теперь, когда я запускаю этот код, он выполняется правильно в течение нескольких секунд, распечатывая правильное сообщение и соответствующую ширину строки, но затем происходит сбой с ошибкой EXC_BAD_ACCESS в строке NSLog(@"width = %f", width);
метода update
.Сначала я подумал, что, возможно, объект line
собирался мусором, но метод lineGC
регистрирует это, а это не так.Поэтому я убежден, что проблема заключается в том, как я использую значение пользователя в моих пользовательских данных Lua, либо при настройке, либо при доступе.
Может кто-нибудь увидеть ошибку в том, как я это реализовал?
РЕДАКТИРОВАТЬ
Чтобы подтвердить, что мои пользовательские данные не являются сборщиком мусора, я отключил сборщик мусора еще до загрузки скрипта с помощью lua_gc(L, LUA_GCSTOP, 0);
.Тем не менее получаю точно такую же проблему.
Я забыл упомянуть ранее, что я использую Lua 5.2.
Включение каждого флага отладки памяти с помощью «Редактировать схему» означает, что ошибка происходит вследующая базовая функция кода Lua при вызове setsvalue2s
, который на самом деле является макросом:
LUA_API void lua_getfield (lua_State *L, int idx, const char *k) {
StkId t;
lua_lock(L);
t = index2addr(L, idx);
api_checkvalidindex(L, t);
setsvalue2s(L, L->top, luaS_new(L, k));
api_incr_top(L);
luaV_gettable(L, t, L->top - 1, L->top - 1);
lua_unlock(L);
}