사용자 도구

사이트 도구


kb:luacustomchunkreader

Lua Custom Chunk Reader

VirtualFileSystem 안에 있는 파일에서 루아 파일을 읽어들이기.

생각해 보면 여러 가지 방법이 있다.

  1. 팩 파일 안에다 루아 텍스트 파일을 두고, 그걸 모두 메모리로 읽어들여서, dostring 혹은 luaL_loadbuffer 함수를 이용한다.
  2. 팩 파일 안에다 루아 텍스트/청크 파일을 두고, 그걸 임시 디렉토리로 빼낸 다음, dofile 혹은 luaL_loadfile 함수를 이용한다.
  3. 팩 파일 안에다 루아 텍스트/청크 파일을 두고, 사용자 읽기 함수를 제공하여, lua_load 함수를 호출한다.

여기서 다루고자 하는 것은, 마지막 경우다. 나름대로 정석적인 방법?

lua_load/lua_Reader

typedef const char* (*lua_Reader)(lua_State *L, void* data, size_t* size);
int lua_load (lua_State* L, lua_Reader reader, void* data, const char* chunkname);

lua_load 함수는 팩 파일 같은 걸 사용하지 않는 이상, 직접 호출할 일은 거의 없는 함수다. luaL_loadbuffer, loadL_loadfile 함수 내부에서 알아서 처리해주기 때문이다. lauxlib.c 파일에 이 두 함수가 구현되어 있는데, 바이너리 파일을 읽어들이는 부분만 옮겨보자면 다음과 같다.

typedef struct LoadF 
{
    int extraline;
    FILE *f;
    char buff[LUAL_BUFFERSIZE];
} LoadF;
 
static const char *getF (lua_State *L, void *ud, size_t *size) 
{
    LoadF *lf = (LoadF *)ud;
    (void)L;
    if (lf->extraline) 
    {
        lf->extraline = 0;
        *size = 1;
        return "\n";
    }
    if (feof(lf->f)) return NULL;
    *size = fread(lf->buff, 1, LUAL_BUFFERSIZE, lf->f);
    return (*size > 0) ? lf->buff : NULL;
}
 
LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) 
{
    LoadF lf;
    int status, readstatus;
    int c;
    int fnameindex = lua_gettop(L) + 1;  /* index of filename on the stack */
    lf.extraline = 0;
    if (filename == NULL) 
    {
        lua_pushliteral(L, "=stdin");
        lf.f = stdin;
    }
    else 
    {
        lua_pushfstring(L, "@%s", filename);
        lf.f = fopen(filename, "r");
        if (lf.f == NULL) return errfile(L, "open", fnameindex);
    }
 
    c = getc(lf.f);
    if (c == '#') /* Unix exec. file? */
    {  
        lf.extraline = 1;
        while ((c = getc(lf.f)) != EOF && c != '\n') ;  /* skip first line */
        if (c == '\n') c = getc(lf.f);
    }
    if (c == LUA_SIGNATURE[0] && lf.f != stdin) /* binary file? */
    {  
        fclose(lf.f);
        lf.f = fopen(filename, "rb");  /* reopen in binary mode */
        if (lf.f == NULL) return errfile(L, "reopen", fnameindex);
        /* skip eventual `#!...' */
        while ((c = getc(lf.f)) != EOF && c != LUA_SIGNATURE[0]) ;
        lf.extraline = 0;
    }
    ungetc(c, lf.f);
 
    status = lua_load(L, getF, &lf, lua_tostring(L, -1));
    readstatus = ferror(lf.f);
    if (lf.f != stdin) fclose(lf.f);  /* close file (even in case of errors) */
    if (readstatus) 
    {
        lua_settop(L, fnameindex);  /* ignore results from `lua_load' */
        return errfile(L, "read", fnameindex);
    }
    lua_remove(L, fnameindex);
    return status;
}

getF 라는 함수가 청크 리더 함수다. 파일 포인터를 저장하고, 함수 호출이 들어올 때마다 파일에서 내용을 읽어들여 버퍼에다 복사한 다음, 버퍼 포인터를 반환하는 걸 볼 수 있다. 파일에 더 이상 읽어들일 것이 없다면 NULL을 반환한다.

즉 lua_Reader 함수에서 해야하는 일은…

  • 인수로 받은 유저 데이터 포인터를 이용해, 어디서부터 데이터를 읽어들여야 할지 판단한다.
  • 스트림에 남은 데이터가 있다면 size 변수 값을 남은 데이터의 길이로 세팅하고, 데이터 포인터 제일 앞쪽을 반환한다.
  • 스트림에 남은 데이터가 없다면 NULL을 반환한다.

이걸 참고로 사용자 함수를 만들어 보자면…

struct MEMORY_FILE
{
    char* start;     // 가상 파일 내용의 맨 앞쪽 포인터
    size_t total;    // 가상 파일의 전체 길이
    size_t offset;   // 현재까지 읽어들인 데이터의 길이
    char buff[1024]; // 이번에 읽어들인 데이터를 복사한 버퍼
};
 
static const char* MemoryReader(lua_State* L, void* ud, size_t* size)
{
    MEMORY_FILE* file = reinterpret_cast<MEMORY_FILE*>(ud);
 
    *size = std::min(file->total - file->offset, 1024);
    if (*size > 0)
    {
        memcpy(file->buff, file->start + file->offset, *size);
        file->offset += *size;
        return file->buff;
    }
    else
    {
        return NULL;
    }
}
 
int my_loadfile(lua_State* L, vfs* filesystem, const char* filename) 
{
    MEMORY_FILE file;
    file.start = vfs->get_file_start(filename); // VFS에서 파일을 읽어들였다고 치자.
    file.total = vfs->get_file_size(filename); // VFS에서 파일 사이즈를 알아냈다고 치자.
    file.offset = 0;
    return lua_load(L, MemoryReader, &file, filename);
}

대충 이런 느낌? 쓸데없이 메모리에 복사를 하고 있는데, 그냥 사용법을 표시하기 위해…

문제점

모듈 경로는 도대체 어떻게 되는 것이냐!


kb/luacustomchunkreader.txt · 마지막으로 수정됨: 2014/11/06 17:01 (바깥 편집)