Files
bullet3/examples/SharedMemory/plugins/fileIOPlugin/fileIOPlugin.cpp
Erwin Coumans 762c21601f cache zipfile, and also allow to read zipfile from memory
this allows zipfiles to be stored anywhere in already registed fileIO (such as zipfiles inside other zipfiles)
see Bullet/examples/pybullet/examples/fileIOPlugin.py
enable vhacd, fileIO and zipFileIO in premake version of PyBullet
fix potential memory leak in mtl loading
2020-03-30 14:24:20 -07:00

702 lines
16 KiB
C++

#include "fileIOPlugin.h"
#include "../../SharedMemoryPublic.h"
#include "../b3PluginContext.h"
#include <stdio.h>
#include "../../../CommonInterfaces/CommonFileIOInterface.h"
#include "../../../Utils/b3ResourcePath.h"
#include "Bullet3Common/b3HashMap.h"
#include <string.h> //memcpy/strlen
#ifndef B3_EXCLUDE_DEFAULT_FILEIO
#include "../../../Utils/b3BulletDefaultFileIO.h"
#endif //B3_EXCLUDE_DEFAULT_FILEIO
#ifdef B3_USE_ZIPFILE_FILEIO
#include "zipFileIO.h"
#endif //B3_USE_ZIPFILE_FILEIO
#ifdef B3_USE_CNS_FILEIO
#include "CNSFileIO.h"
#endif //B3_USE_CNS_FILEIO
#define B3_MAX_FILEIO_INTERFACES 1024
struct WrapperFileHandle
{
CommonFileIOInterface* childFileIO;
int m_childFileHandle;
};
struct InMemoryFile
{
char* m_buffer;
int m_fileSize;
};
struct InMemoryFileAccessor
{
InMemoryFile* m_file;
int m_curPos;
};
struct InMemoryFileIO : public CommonFileIOInterface
{
b3HashMap<b3HashString,InMemoryFile*> m_fileCache;
InMemoryFileAccessor m_fileHandles[B3_MAX_FILEIO_INTERFACES];
int m_numAllocs;
int m_numFrees;
InMemoryFileIO()
:CommonFileIOInterface(eInMemoryFileIO,0)
{
m_numAllocs=0;
m_numFrees=0;
for (int i=0;i<B3_FILEIO_MAX_FILES;i++)
{
m_fileHandles[i].m_curPos = 0;
m_fileHandles[i].m_file = 0;
}
}
virtual ~InMemoryFileIO()
{
clearCache();
if (m_numAllocs != m_numFrees)
{
printf("Error: InMemoryFile::~InMemoryFileIO (numAllocs %d numFrees %d\n", m_numAllocs, m_numFrees);
}
}
void clearCache()
{
for (int i=0;i<m_fileCache.size();i++)
{
b3HashString name = m_fileCache.getKeyAtIndex(i);
InMemoryFile** memPtr = m_fileCache.getAtIndex(i);
if (memPtr && *memPtr)
{
InMemoryFile* mem = *memPtr;
freeBuffer(mem->m_buffer);
m_numFrees++;
delete (mem);
m_numFrees++;
m_fileCache.remove(name);
}
}
}
char* allocateBuffer(int len)
{
char* buffer = 0;
if (len)
{
m_numAllocs++;
buffer = new char[len];
}
return buffer;
}
void freeBuffer(char* buffer)
{
delete[] buffer;
}
virtual int registerFile(const char* fileName, char* buffer, int len)
{
m_numAllocs++;
InMemoryFile* f = new InMemoryFile();
f->m_buffer = buffer;
f->m_fileSize = len;
b3HashString key(fileName);
m_fileCache.insert(key,f);
return 0;
}
void removeFileFromCache(const char* fileName)
{
InMemoryFile* f = getInMemoryFile(fileName);
if (f)
{
m_fileCache.remove(fileName);
freeBuffer(f->m_buffer);
delete (f);
}
}
InMemoryFile* getInMemoryFile(const char* fileName)
{
InMemoryFile** fPtr = m_fileCache[fileName];
if (fPtr && *fPtr)
{
return *fPtr;
}
return 0;
}
virtual int fileOpen(const char* fileName, const char* mode)
{
//search a free slot
int slot = -1;
for (int i=0;i<B3_FILEIO_MAX_FILES;i++)
{
if (m_fileHandles[i].m_file==0)
{
slot=i;
break;
}
}
if (slot>=0)
{
InMemoryFile* f = getInMemoryFile(fileName);
if (f)
{
m_fileHandles[slot].m_curPos = 0;
m_fileHandles[slot].m_file = f;
} else
{
slot=-1;
}
}
//printf("InMemoryFileIO fileOpen %s, %d\n", fileName, slot);
return slot;
}
virtual int fileRead(int fileHandle, char* destBuffer, int numBytes)
{
if (fileHandle>=0 && fileHandle < B3_FILEIO_MAX_FILES)
{
InMemoryFileAccessor& f = m_fileHandles[fileHandle];
if (f.m_file)
{
//if (numBytes>1)
// printf("curPos = %d\n", f.m_curPos);
if (f.m_curPos+numBytes <= f.m_file->m_fileSize)
{
memcpy(destBuffer,f.m_file->m_buffer+f.m_curPos,numBytes);
f.m_curPos+=numBytes;
//if (numBytes>1)
// printf("read %d bytes, now curPos = %d\n", numBytes, f.m_curPos);
return numBytes;
} else
{
if (numBytes!=1)
{
printf("InMemoryFileIO::fileRead Attempt to read beyond end of file\n");
}
}
}
}
return 0;
}
virtual int fileWrite(int fileHandle,const char* sourceBuffer, int numBytes)
{
return 0;
}
virtual void fileClose(int fileHandle)
{
if (fileHandle>=0 && fileHandle < B3_FILEIO_MAX_FILES)
{
InMemoryFileAccessor& f = m_fileHandles[fileHandle];
if (f.m_file)
{
m_fileHandles[fileHandle].m_file = 0;
m_fileHandles[fileHandle].m_curPos = 0;
//printf("InMemoryFileIO fileClose %d\n", fileHandle);
}
}
}
virtual bool findResourcePath(const char* fileName, char* resourcePathOut, int resourcePathMaxNumBytes)
{
InMemoryFile* f = getInMemoryFile(fileName);
int fileNameLen = strlen(fileName);
if (f && fileNameLen<(resourcePathMaxNumBytes-1))
{
memcpy(resourcePathOut, fileName, fileNameLen);
resourcePathOut[fileNameLen]=0;
return true;
}
return false;
}
virtual char* readLine(int fileHandle, char* destBuffer, int numBytes)
{
int numRead = 0;
int endOfFile = 0;
if (fileHandle>=0 && fileHandle < B3_FILEIO_MAX_FILES )
{
InMemoryFileAccessor& f = m_fileHandles[fileHandle];
if (f.m_file)
{
//return ::fgets(destBuffer, numBytes, m_fileHandles[fileHandle]);
char c = 0;
do
{
int bytesRead = fileRead(fileHandle,&c,1);
if (bytesRead != 1)
{
endOfFile = 1;
c=0;
}
if (c && c!='\n')
{
if (c!=13)
{
destBuffer[numRead++]=c;
} else
{
destBuffer[numRead++]=0;
}
}
} while (c != 0 && c != '\n' && numRead<(numBytes-1));
}
}
if (numRead==0 && endOfFile)
{
return 0;
}
if (numRead<numBytes)
{
if (numRead >=0)
{
destBuffer[numRead]=0;
}
return &destBuffer[0];
} else
{
if (endOfFile==0)
{
printf("InMemoryFileIO::readLine readLine warning: numRead=%d, numBytes=%d\n", numRead, numBytes);
}
}
return 0;
}
virtual int getFileSize(int fileHandle)
{
if (fileHandle>=0 && fileHandle < B3_FILEIO_MAX_FILES )
{
InMemoryFileAccessor& f = m_fileHandles[fileHandle];
if (f.m_file)
{
return f.m_file->m_fileSize;
}
}
return 0;
}
virtual void enableFileCaching(bool enable)
{
(void)enable;
}
};
struct WrapperFileIO : public CommonFileIOInterface
{
CommonFileIOInterface* m_availableFileIOInterfaces[B3_MAX_FILEIO_INTERFACES];
int m_numWrapperInterfaces;
WrapperFileHandle m_wrapperFileHandles[B3_MAX_FILEIO_INTERFACES];
InMemoryFileIO m_cachedFiles;
bool m_enableFileCaching;
WrapperFileIO()
:CommonFileIOInterface(0,0),
m_numWrapperInterfaces(0),
m_enableFileCaching(true)
{
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
m_availableFileIOInterfaces[i]=0;
m_wrapperFileHandles[i].childFileIO=0;
m_wrapperFileHandles[i].m_childFileHandle=0;
}
//addFileIOInterface(&m_cachedFiles);
}
virtual ~WrapperFileIO()
{
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
removeFileIOInterface(i);
}
m_cachedFiles.clearCache();
}
int addFileIOInterface(CommonFileIOInterface* fileIO)
{
int result = -1;
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
if (m_availableFileIOInterfaces[i]==0)
{
m_availableFileIOInterfaces[i]=fileIO;
result = i;
break;
}
}
return result;
}
CommonFileIOInterface* getFileIOInterface(int fileIOIndex)
{
if (fileIOIndex>=0 && fileIOIndex<B3_MAX_FILEIO_INTERFACES)
{
return m_availableFileIOInterfaces[fileIOIndex];
}
return 0;
}
void removeFileIOInterface(int fileIOIndex)
{
if (fileIOIndex>=0 && fileIOIndex<B3_MAX_FILEIO_INTERFACES)
{
if (m_availableFileIOInterfaces[fileIOIndex])
{
delete m_availableFileIOInterfaces[fileIOIndex];
m_availableFileIOInterfaces[fileIOIndex]=0;
}
}
}
virtual int fileOpen(const char* fileName, const char* mode)
{
//find an available wrapperFileHandle slot
int wrapperFileHandle=-1;
int slot = -1;
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
if (m_wrapperFileHandles[i].childFileIO==0)
{
slot=i;
break;
}
}
if (slot>=0)
{
//first check the cache
int cacheHandle = m_cachedFiles.fileOpen(fileName, mode);
if (cacheHandle>=0)
{
m_cachedFiles.fileClose(cacheHandle);
}
if (cacheHandle<0)
{
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
CommonFileIOInterface* childFileIO=m_availableFileIOInterfaces[i];
if (childFileIO)
{
int childHandle = childFileIO->fileOpen(fileName, mode);
if (childHandle>=0)
{
if (m_enableFileCaching)
{
int fileSize = childFileIO->getFileSize(childHandle);
char* buffer = 0;
if (fileSize)
{
buffer = m_cachedFiles.allocateBuffer(fileSize);
if (buffer)
{
int readBytes = childFileIO->fileRead(childHandle, buffer, fileSize);
if (readBytes!=fileSize)
{
if (readBytes<fileSize)
{
fileSize = readBytes;
} else
{
printf("WrapperFileIO error: reading more bytes (%d) then reported file size (%d) of file %s.\n", readBytes, fileSize, fileName);
}
}
} else
{
fileSize=0;
}
}
//potentially register a zero byte file, or files that only can be read partially
m_cachedFiles.registerFile(fileName, buffer, fileSize);
}
childFileIO->fileClose(childHandle);
break;
}
}
}
}
{
int childHandle = m_cachedFiles.fileOpen(fileName, mode);
if (childHandle>=0)
{
wrapperFileHandle = slot;
m_wrapperFileHandles[slot].childFileIO = &m_cachedFiles;
m_wrapperFileHandles[slot].m_childFileHandle = childHandle;
} else
{
//figure out what wrapper interface to use
//use the first one that can open the file
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
CommonFileIOInterface* childFileIO=m_availableFileIOInterfaces[i];
if (childFileIO)
{
int childHandle = childFileIO->fileOpen(fileName, mode);
if (childHandle>=0)
{
wrapperFileHandle = slot;
m_wrapperFileHandles[slot].childFileIO = childFileIO;
m_wrapperFileHandles[slot].m_childFileHandle = childHandle;
break;
}
}
}
}
}
}
return wrapperFileHandle;
}
virtual int fileRead(int fileHandle, char* destBuffer, int numBytes)
{
int fileReadResult=-1;
if (fileHandle>=0 && fileHandle<B3_MAX_FILEIO_INTERFACES)
{
if (m_wrapperFileHandles[fileHandle].childFileIO)
{
fileReadResult = m_wrapperFileHandles[fileHandle].childFileIO->fileRead(
m_wrapperFileHandles[fileHandle].m_childFileHandle, destBuffer, numBytes);
}
}
return fileReadResult;
}
virtual int fileWrite(int fileHandle,const char* sourceBuffer, int numBytes)
{
//todo
return -1;
}
virtual void fileClose(int fileHandle)
{
if (fileHandle>=0 && fileHandle<B3_MAX_FILEIO_INTERFACES)
{
if (m_wrapperFileHandles[fileHandle].childFileIO)
{
m_wrapperFileHandles[fileHandle].childFileIO->fileClose(
m_wrapperFileHandles[fileHandle].m_childFileHandle);
m_wrapperFileHandles[fileHandle].childFileIO = 0;
m_wrapperFileHandles[fileHandle].m_childFileHandle = -1;
}
}
}
virtual bool findResourcePath(const char* fileName, char* resourcePathOut, int resourcePathMaxNumBytes)
{
if (m_cachedFiles.findResourcePath(fileName, resourcePathOut, resourcePathMaxNumBytes))
return true;
bool found = false;
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
if (m_availableFileIOInterfaces[i])
{
found = m_availableFileIOInterfaces[i]->findResourcePath(fileName, resourcePathOut, resourcePathMaxNumBytes);
}
if (found)
break;
}
return found;
}
virtual char* readLine(int fileHandle, char* destBuffer, int numBytes)
{
char* result = 0;
if (fileHandle>=0 && fileHandle<B3_MAX_FILEIO_INTERFACES)
{
if (m_wrapperFileHandles[fileHandle].childFileIO)
{
result = m_wrapperFileHandles[fileHandle].childFileIO->readLine(
m_wrapperFileHandles[fileHandle].m_childFileHandle,
destBuffer, numBytes);
}
}
return result;
}
virtual int getFileSize(int fileHandle)
{
int numBytes = 0;
if (fileHandle>=0 && fileHandle<B3_MAX_FILEIO_INTERFACES)
{
if (m_wrapperFileHandles[fileHandle].childFileIO)
{
numBytes = m_wrapperFileHandles[fileHandle].childFileIO->getFileSize(
m_wrapperFileHandles[fileHandle].m_childFileHandle);
}
}
return numBytes;
}
virtual void enableFileCaching(bool enable)
{
m_enableFileCaching = enable;
if (!enable)
{
m_cachedFiles.clearCache();
}
}
};
struct FileIOClass
{
int m_testData;
WrapperFileIO m_fileIO;
FileIOClass()
: m_testData(42),
m_fileIO()
{
}
virtual ~FileIOClass()
{
}
};
B3_SHARED_API int initPlugin_fileIOPlugin(struct b3PluginContext* context)
{
FileIOClass* obj = new FileIOClass();
context->m_userPointer = obj;
#ifndef B3_EXCLUDE_DEFAULT_FILEIO
obj->m_fileIO.addFileIOInterface(new b3BulletDefaultFileIO());
#endif //B3_EXCLUDE_DEFAULT_FILEIO
return SHARED_MEMORY_MAGIC_NUMBER;
}
B3_SHARED_API int executePluginCommand_fileIOPlugin(struct b3PluginContext* context, const struct b3PluginArguments* arguments)
{
int result=-1;
FileIOClass* obj = (FileIOClass*)context->m_userPointer;
printf("text argument:%s\n", arguments->m_text);
printf("int args: [");
//remove a fileIO type
if (arguments->m_numInts==1)
{
int fileIOIndex = arguments->m_ints[0];
obj->m_fileIO.removeFileIOInterface(fileIOIndex);
}
if (arguments->m_numInts==2)
{
int action = arguments->m_ints[0];
switch (action)
{
case eAddFileIOAction:
{
//if the fileIO already exists, skip this action
int fileIOType = arguments->m_ints[1];
bool alreadyExists = false;
for (int i=0;i<B3_MAX_FILEIO_INTERFACES;i++)
{
CommonFileIOInterface* fileIO = obj->m_fileIO.getFileIOInterface(i);
if (fileIO)
{
if (fileIO->m_fileIOType == fileIOType)
{
if (fileIO->m_pathPrefix && strcmp(fileIO->m_pathPrefix,arguments->m_text)==0)
{
result = i;
alreadyExists = true;
break;
}
}
}
}
//create new fileIO interface
if (!alreadyExists)
{
switch (fileIOType)
{
case ePosixFileIO:
{
#ifdef B3_EXCLUDE_DEFAULT_FILEIO
printf("ePosixFileIO is not enabled in this build.\n");
#else
result = obj->m_fileIO.addFileIOInterface(new b3BulletDefaultFileIO(ePosixFileIO, arguments->m_text));
#endif
break;
}
case eZipFileIO:
{
#ifdef B3_USE_ZIPFILE_FILEIO
if (arguments->m_text[0])
{
result = obj->m_fileIO.addFileIOInterface(new ZipFileIO(eZipFileIO, arguments->m_text, &obj->m_fileIO));
}
#else
printf("eZipFileIO is not enabled in this build.\n");
#endif
break;
}
case eCNSFileIO:
{
#ifdef B3_USE_CNS_FILEIO
result = obj->m_fileIO.addFileIOInterface(new CNSFileIO(eCNSFileIO, arguments->m_text));
#else//B3_USE_CNS_FILEIO
printf("CNSFileIO is not enabled in this build.\n");
#endif //B3_USE_CNS_FILEIO
break;
}
default:
{
}
}//switch (fileIOType)
}//if (!alreadyExists)
break;
}
case eRemoveFileIOAction:
{
//remove fileIO interface
int fileIOIndex = arguments->m_ints[1];
obj->m_fileIO.removeFileIOInterface(fileIOIndex);
result = fileIOIndex;
break;
}
default:
{
printf("executePluginCommand_fileIOPlugin: unknown action\n");
}
}
}
return result;
}
B3_SHARED_API struct CommonFileIOInterface* getFileIOFunc_fileIOPlugin(struct b3PluginContext* context)
{
FileIOClass* obj = (FileIOClass*)context->m_userPointer;
return &obj->m_fileIO;
}
B3_SHARED_API void exitPlugin_fileIOPlugin(struct b3PluginContext* context)
{
FileIOClass* obj = (FileIOClass*)context->m_userPointer;
delete obj;
context->m_userPointer = 0;
}