Quellcode durchsuchen

Added Stream interface.
File IO is now mostly done through the Stream interface instead of using fread() directly.
Android will now read GPB and text files from the content store instead of having to copy them to the SD card first. Eventually the SD card won't be required on Android.
Incomplete:
I still need to convert image and sound loading to use Stream instead of FILE.
Not tested on mac, iOS and linux yet.

Darryl Gough vor 13 Jahren
Ursprung
Commit
0512fca49d

+ 1 - 0
gameplay/gameplay.vcxproj

@@ -573,6 +573,7 @@
     <ClInclude Include="src\ScriptTarget.h" />
     <ClInclude Include="src\Slider.h" />
     <ClInclude Include="src\SpriteBatch.h" />
+    <ClInclude Include="src\Stream.h" />
     <ClInclude Include="src\Technique.h" />
     <ClInclude Include="src\TextBox.h" />
     <ClInclude Include="src\Texture.h" />

+ 3 - 0
gameplay/gameplay.vcxproj.filters

@@ -1622,6 +1622,9 @@
     <ClInclude Include="src\lua\lua_LoggerLevel.h">
       <Filter>lua</Filter>
     </ClInclude>
+    <ClInclude Include="src\Stream.h">
+      <Filter>src</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="src\Game.inl">

+ 83 - 99
gameplay/src/Bundle.cpp

@@ -32,7 +32,7 @@ namespace gameplay
 static std::vector<Bundle*> __bundleCache;
 
 Bundle::Bundle(const char* path) :
-    _path(path), _referenceCount(0), _references(NULL), _file(NULL), _trackedNodes(NULL)
+    _path(path), _referenceCount(0), _references(NULL), _stream(NULL), _trackedNodes(NULL)
 {
 }
 
@@ -49,10 +49,9 @@ Bundle::~Bundle()
 
     SAFE_DELETE_ARRAY(_references);
 
-    if (_file)
+    if (_stream)
     {
-        fclose(_file);
-        _file = NULL;
+        SAFE_DELETE(_stream);
     }
 }
 
@@ -61,7 +60,7 @@ bool Bundle::readArray(unsigned int* length, T** ptr)
 {
     GP_ASSERT(length);
     GP_ASSERT(ptr);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     if (!read(length))
     {
@@ -71,7 +70,7 @@ bool Bundle::readArray(unsigned int* length, T** ptr)
     if (*length > 0)
     {
         *ptr = new T[*length];
-        if (fread(*ptr, sizeof(T), *length, _file) != *length)
+        if (_stream->read(*ptr, sizeof(T), *length) != *length)
         {
             GP_ERROR("Failed to read an array of data from bundle (into an array).");
             SAFE_DELETE_ARRAY(*ptr);
@@ -85,7 +84,7 @@ template <class T>
 bool Bundle::readArray(unsigned int* length, std::vector<T>* values)
 {
     GP_ASSERT(length);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     if (!read(length))
     {
@@ -95,7 +94,7 @@ bool Bundle::readArray(unsigned int* length, std::vector<T>* values)
     if (*length > 0 && values)
     {
         values->resize(*length);
-        if (fread(&(*values)[0], sizeof(T), *length, _file) != *length)
+        if (_stream->read(&(*values)[0], sizeof(T), *length) != *length)
         {
             GP_ERROR("Failed to read an array of data from bundle (into a std::vector).");
             return false;
@@ -108,7 +107,7 @@ template <class T>
 bool Bundle::readArray(unsigned int* length, std::vector<T>* values, unsigned int readSize)
 {
     GP_ASSERT(length);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
     GP_ASSERT(sizeof(T) >= readSize);
 
     if (!read(length))
@@ -119,7 +118,7 @@ bool Bundle::readArray(unsigned int* length, std::vector<T>* values, unsigned in
     if (*length > 0 && values)
     {
         values->resize(*length);
-        if (fread(&(*values)[0], readSize, *length, _file) != *length)
+        if (_stream->read(&(*values)[0], readSize, *length) != *length)
         {
             GP_ERROR("Failed to read an array of data from bundle (into a std::vector with a specified single element read size).");
             return false;
@@ -128,12 +127,12 @@ bool Bundle::readArray(unsigned int* length, std::vector<T>* values, unsigned in
     return true;
 }
 
-static std::string readString(FILE* fp)
+static std::string readString(Stream* stream)
 {
-    GP_ASSERT(fp);
+    GP_ASSERT(stream);
 
     unsigned int length;
-    if (fread(&length, 4, 1, fp) != 1)
+    if (stream->read(&length, 4, 1) != 1)
     {
         GP_ERROR("Failed to read the length of a string from a bundle.");
         return std::string();
@@ -146,7 +145,7 @@ static std::string readString(FILE* fp)
     if (length > 0)
     {
         str.resize(length);
-        if (fread(&str[0], 1, length, fp) != length)
+        if (stream->read(&str[0], 1, length) != length)
         {
             GP_ERROR("Failed to read string from bundle.");
             return std::string();
@@ -173,8 +172,8 @@ Bundle* Bundle::create(const char* path)
     }
 
     // Open the bundle.
-    FILE* fp = FileSystem::openFile(path, "rb");
-    if (!fp)
+    Stream* stream = FileSystem::open(path);
+    if (!stream)
     {
         GP_ERROR("Failed to open file '%s'.", path);
         return NULL;
@@ -182,46 +181,34 @@ Bundle* Bundle::create(const char* path)
 
     // Read the GPB header info.
     char sig[9];
-    if (fread(sig, 1, 9, fp) != 9 || memcmp(sig, "\xABGPB\xBB\r\n\x1A\n", 9) != 0)
+    if (stream->read(sig, 1, 9) != 9 || memcmp(sig, "\xABGPB\xBB\r\n\x1A\n", 9) != 0)
     {
+        SAFE_DELETE(stream);
         GP_ERROR("Invalid GPB header for bundle '%s'.", path);
-        if (fclose(fp) != 0)
-        {
-            GP_ERROR("Failed to close file '%s'.", path);
-        }
         return NULL;
     }
 
     // Read version.
     unsigned char ver[2];
-    if (fread(ver, 1, 2, fp) != 2)
+    if (stream->read(ver, 1, 2) != 2)
     {
+        SAFE_DELETE(stream);
         GP_ERROR("Failed to read GPB version for bundle '%s'.", path);
-        if (fclose(fp) != 0)
-        {
-            GP_ERROR("Failed to close file '%s'.", path);
-        }
         return NULL;
     }
     if (ver[0] != BUNDLE_VERSION_MAJOR || ver[1] != BUNDLE_VERSION_MINOR)
     {
+        SAFE_DELETE(stream);
         GP_ERROR("Unsupported version (%d.%d) for bundle '%s' (expected %d.%d).", (int)ver[0], (int)ver[1], path, BUNDLE_VERSION_MAJOR, BUNDLE_VERSION_MINOR);
-        if (fclose(fp) != 0)
-        {
-            GP_ERROR("Failed to close file '%s'.", path);
-        }
         return NULL;
     }
 
     // Read ref table.
     unsigned int refCount;
-    if (fread(&refCount, 4, 1, fp) != 1)
+    if (stream->read(&refCount, 4, 1) != 1)
     {
+        SAFE_DELETE(stream);
         GP_ERROR("Failed to read ref table for bundle '%s'.", path);
-        if (fclose(fp) != 0)
-        {
-            GP_ERROR("Failed to close file '%s'.", path);
-        }
         return NULL;
     }
 
@@ -229,15 +216,12 @@ Bundle* Bundle::create(const char* path)
     Reference* refs = new Reference[refCount];
     for (unsigned int i = 0; i < refCount; ++i)
     {
-        if ((refs[i].id = readString(fp)).empty() ||
-            fread(&refs[i].type, 4, 1, fp) != 1 ||
-            fread(&refs[i].offset, 4, 1, fp) != 1)
+        if ((refs[i].id = readString(stream)).empty() ||
+            stream->read(&refs[i].type, 4, 1) != 1 ||
+            stream->read(&refs[i].offset, 4, 1) != 1)
         {
+            SAFE_DELETE(stream);
             GP_ERROR("Failed to read ref number %d for bundle '%s'.", i, path);
-            if (fclose(fp) != 0)
-            {
-                GP_ERROR("Failed to close file '%s'.", path);
-            }
             SAFE_DELETE_ARRAY(refs);
             return NULL;
         }
@@ -247,7 +231,7 @@ Bundle* Bundle::create(const char* path)
     Bundle* bundle = new Bundle(path);
     bundle->_referenceCount = refCount;
     bundle->_references = refs;
-    bundle->_file = fp;
+    bundle->_stream = stream;
 
     return bundle;
 }
@@ -281,8 +265,8 @@ void Bundle::clearLoadSession()
 
 const char* Bundle::getIdFromOffset() const
 {
-    GP_ASSERT(_file);
-    return getIdFromOffset((unsigned int) ftell(_file));
+    GP_ASSERT(_stream);
+    return getIdFromOffset((unsigned int) _stream->position());
 }
 
 const char* Bundle::getIdFromOffset(unsigned int offset) const
@@ -318,8 +302,8 @@ Bundle::Reference* Bundle::seekTo(const char* id, unsigned int type)
     }
 
     // Seek to the offset of this object.
-    GP_ASSERT(_file);
-    if (fseek(_file, ref->offset, SEEK_SET) != 0)
+    GP_ASSERT(_stream);
+    if (_stream->seek(ref->offset, SEEK_SET) == false)
     {
         GP_ERROR("Failed to seek to object '%s' in bundle '%s'.", id, _path.c_str());
         return NULL;
@@ -331,7 +315,7 @@ Bundle::Reference* Bundle::seekTo(const char* id, unsigned int type)
 Bundle::Reference* Bundle::seekToFirstType(unsigned int type)
 {
     GP_ASSERT(_references);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     for (unsigned int i = 0; i < _referenceCount; ++i)
     {
@@ -339,7 +323,7 @@ Bundle::Reference* Bundle::seekToFirstType(unsigned int type)
         if (ref->type == type)
         {
             // Found a match.
-            if (fseek(_file, ref->offset, SEEK_SET) != 0)
+            if (_stream->seek(ref->offset, SEEK_SET) == false)
             {
                 GP_ERROR("Failed to seek to object '%s' in bundle '%s'.", ref->id.c_str(), _path.c_str());
                 return NULL;
@@ -352,22 +336,22 @@ Bundle::Reference* Bundle::seekToFirstType(unsigned int type)
 
 bool Bundle::read(unsigned int* ptr)
 {
-    return fread(ptr, sizeof(unsigned int), 1, _file) == 1;
+    return _stream->read(ptr, sizeof(unsigned int), 1) == 1;
 }
 
 bool Bundle::read(unsigned char* ptr)
 {
-    return fread(ptr, sizeof(unsigned char), 1, _file) == 1;
+    return _stream->read(ptr, sizeof(unsigned char), 1) == 1;
 }
 
 bool Bundle::read(float* ptr)
 {
-    return fread(ptr, sizeof(float), 1, _file) == 1;
+    return _stream->read(ptr, sizeof(float), 1) == 1;
 }
 
 bool Bundle::readMatrix(float* m)
 {
-    return fread(m, sizeof(float), 16, _file) == 16;
+    return _stream->read(m, sizeof(float), 16) == 16;
 }
 
 Scene* Bundle::loadScene(const char* id)
@@ -419,7 +403,7 @@ Scene* Bundle::loadScene(const char* id)
         }
     }
     // Read active camera.
-    std::string xref = readString(_file);
+    std::string xref = readString(_stream);
     if (xref.length() > 1 && xref[0] == '#') // TODO: Handle full xrefs
     {
         Node* node = scene->findNode(xref.c_str() + 1, true);
@@ -453,14 +437,14 @@ Scene* Bundle::loadScene(const char* id)
 
     // Parse animations.
     GP_ASSERT(_references);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
     for (unsigned int i = 0; i < _referenceCount; ++i)
     {
         Reference* ref = &_references[i];
         if (ref->type == BUNDLE_TYPE_ANIMATIONS)
         {
             // Found a match.
-            if (fseek(_file, ref->offset, SEEK_SET) != 0)
+            if (_stream->seek(ref->offset, SEEK_SET) == false)
             {
                 GP_ERROR("Failed to seek to object '%s' in bundle '%s'.", ref->id.c_str(), _path.c_str());
                 return NULL;
@@ -483,7 +467,7 @@ Node* Bundle::loadNode(const char* id, Scene* sceneContext)
 {
     GP_ASSERT(id);
     GP_ASSERT(_references);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     clearLoadSession();
 
@@ -499,7 +483,7 @@ Node* Bundle::loadNode(const char* id, Scene* sceneContext)
         Reference* ref = &_references[i];
         if (ref->type == BUNDLE_TYPE_ANIMATIONS)
         {
-            if (fseek(_file, ref->offset, SEEK_SET) != 0)
+            if (_stream->seek(ref->offset, SEEK_SET) == false)
             {
                 GP_ERROR("Failed to seek to object '%s' in bundle '%s'.", ref->id.c_str(), _path.c_str());
                 SAFE_DELETE(_trackedNodes);
@@ -517,7 +501,7 @@ Node* Bundle::loadNode(const char* id, Scene* sceneContext)
 
             for (unsigned int j = 0; j < animationCount; j++)
             {
-                const std::string id = readString(_file);
+                const std::string id = readString(_stream);
 
                 // Read the number of animation channels in this animation.
                 unsigned int animationChannelCount;
@@ -532,7 +516,7 @@ Node* Bundle::loadNode(const char* id, Scene* sceneContext)
                 for (unsigned int k = 0; k < animationChannelCount; k++)
                 {
                     // Read target id.
-                    std::string targetId = readString(_file);
+                    std::string targetId = readString(_stream);
                     if (targetId.empty())
                     {
                         GP_ERROR("Failed to read target id for animation '%s'.", id.c_str());
@@ -627,7 +611,7 @@ bool Bundle::skipNode()
 {
     const char* id = getIdFromOffset();
     GP_ASSERT(id);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     // Skip the node's type.
     unsigned int nodeType;
@@ -638,12 +622,12 @@ bool Bundle::skipNode()
     }
 
     // Skip over the node's transform and parent ID.
-    if (fseek(_file, sizeof(float) * 16, SEEK_CUR) != 0)
+    if (_stream->seek(sizeof(float) * 16, SEEK_CUR) == false)
     {
         GP_ERROR("Failed to skip over node transform for node '%s'.", id);
         return false;
     }
-    readString(_file);
+    readString(_stream);
 
     // Skip over the node's children.
     unsigned int childrenCount;
@@ -673,7 +657,7 @@ Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
 {
     const char* id = getIdFromOffset();
     GP_ASSERT(id);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     // If we are tracking nodes and it's not in the set yet, add it.
     if (_trackedNodes)
@@ -725,7 +709,7 @@ Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
 
     // Read transform.
     float transform[16];
-    if (fread(transform, sizeof(float), 16, _file) != 16)
+    if (_stream->read(transform, sizeof(float), 16) != 16)
     {
         GP_ERROR("Failed to read transform for node '%s'.", id);
         SAFE_RELEASE(node);
@@ -734,7 +718,7 @@ Node* Bundle::readNode(Scene* sceneContext, Node* nodeContext)
     setTransform(transform, node);
 
     // Skip the parent ID.
-    readString(_file);
+    readString(_stream);
 
     // Read children.
     unsigned int childrenCount;
@@ -954,7 +938,7 @@ Model* Bundle::readModel(const char* nodeId)
 {
     // Read mesh.
     Mesh* mesh = NULL;
-    std::string xref = readString(_file);
+    std::string xref = readString(_stream);
     if (xref.length() > 1 && xref[0] == '#') // TODO: Handle full xrefs
     {
         mesh = loadMesh(xref.c_str() + 1, nodeId);
@@ -1035,7 +1019,7 @@ MeshSkin* Bundle::readMeshSkin()
     // Read joint xref strings for all joints in the list.
     for (unsigned int i = 0; i < jointCount; i++)
     {
-        skinData->joints.push_back(readString(_file));
+        skinData->joints.push_back(readString(_stream));
     }
 
     // Read bind poses.
@@ -1072,7 +1056,7 @@ MeshSkin* Bundle::readMeshSkin()
 
 void Bundle::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
 {
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     for (size_t i = 0, skinCount = _meshSkins.size(); i < skinCount; ++i)
     {
@@ -1144,12 +1128,12 @@ void Bundle::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
                         seekTo(nodeId.c_str(), ref->type);
 
                         // Skip over the node type (1 unsigned int) and transform (16 floats) and read the parent id.
-                        if (fseek(_file, sizeof(unsigned int) + sizeof(float)*16, SEEK_CUR) != 0)
+                        if (_stream->seek(sizeof(unsigned int) + sizeof(float)*16, SEEK_CUR) == false)
                         {
                             GP_ERROR("Failed to skip over node type and transform for node '%s' in bundle '%s'.", nodeId.c_str(), _path.c_str());
                             return;
                         }
-                        std::string parentID = readString(_file);
+                        std::string parentID = readString(_stream);
 
                         if (!parentID.empty())
                             nodeId = parentID;
@@ -1185,7 +1169,7 @@ void Bundle::resolveJointReferences(Scene* sceneContext, Node* nodeContext)
 
 void Bundle::readAnimation(Scene* scene)
 {
-    const std::string animationId = readString(_file);
+    const std::string animationId = readString(_stream);
 
     // Read the number of animation channels in this animation.
     unsigned int animationChannelCount;
@@ -1223,7 +1207,7 @@ Animation* Bundle::readAnimationChannel(Scene* scene, Animation* animation, cons
     GP_ASSERT(animationId);
 
     // Read target id.
-    std::string targetId = readString(_file);
+    std::string targetId = readString(_stream);
     if (targetId.empty())
     {
         GP_ERROR("Failed to read target id for animation '%s'.", animationId);
@@ -1331,11 +1315,11 @@ Mesh* Bundle::loadMesh(const char* id)
 
 Mesh* Bundle::loadMesh(const char* id, const char* nodeId)
 {
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
     GP_ASSERT(id);
 
     // Save the file position.
-    long position = ftell(_file);
+    long position = _stream->position();
     if (position == -1L)
     {
         GP_ERROR("Failed to save the current file position before loading mesh '%s'.", id);
@@ -1395,7 +1379,7 @@ Mesh* Bundle::loadMesh(const char* id, const char* nodeId)
     SAFE_DELETE(meshData);
 
     // Restore file pointer.
-    if (fseek(_file, position, SEEK_SET) != 0)
+    if (_stream->seek(position, SEEK_SET) == false)
     {
         GP_ERROR("Failed to restore file pointer after loading mesh '%s'.", id);
         return NULL;
@@ -1408,7 +1392,7 @@ Bundle::MeshData* Bundle::readMeshData()
 {
     // Read vertex format/elements.
     unsigned int vertexElementCount;
-    if (fread(&vertexElementCount, 4, 1, _file) != 1)
+    if (_stream->read(&vertexElementCount, 4, 1) != 1)
     {
         GP_ERROR("Failed to load vertex element count.");
         return NULL;
@@ -1423,13 +1407,13 @@ Bundle::MeshData* Bundle::readMeshData()
     for (unsigned int i = 0; i < vertexElementCount; ++i)
     {
         unsigned int vUsage, vSize;
-        if (fread(&vUsage, 4, 1, _file) != 1)
+        if (_stream->read(&vUsage, 4, 1) != 1)
         {
             GP_ERROR("Failed to load vertex usage.");
             SAFE_DELETE_ARRAY(vertexElements);
             return NULL;
         }
-        if (fread(&vSize, 4, 1, _file) != 1)
+        if (_stream->read(&vSize, 4, 1) != 1)
         {
             GP_ERROR("Failed to load vertex size.");
             SAFE_DELETE_ARRAY(vertexElements);
@@ -1445,7 +1429,7 @@ Bundle::MeshData* Bundle::readMeshData()
 
     // Read vertex data.
     unsigned int vertexByteCount;
-    if (fread(&vertexByteCount, 4, 1, _file) != 1)
+    if (_stream->read(&vertexByteCount, 4, 1) != 1)
     {
         GP_ERROR("Failed to load vertex byte count.");
         SAFE_DELETE(meshData);
@@ -1461,7 +1445,7 @@ Bundle::MeshData* Bundle::readMeshData()
     GP_ASSERT(meshData->vertexFormat.getVertexSize());
     meshData->vertexCount = vertexByteCount / meshData->vertexFormat.getVertexSize();
     meshData->vertexData = new unsigned char[vertexByteCount];
-    if (fread(meshData->vertexData, 1, vertexByteCount, _file) != vertexByteCount)
+    if (_stream->read(meshData->vertexData, 1, vertexByteCount) != vertexByteCount)
     {
         GP_ERROR("Failed to load vertex data.");
         SAFE_DELETE(meshData);
@@ -1469,13 +1453,13 @@ Bundle::MeshData* Bundle::readMeshData()
     }
 
     // Read mesh bounds (bounding box and bounding sphere).
-    if (fread(&meshData->boundingBox.min.x, 4, 3, _file) != 3 || fread(&meshData->boundingBox.max.x, 4, 3, _file) != 3)
+    if (_stream->read(&meshData->boundingBox.min.x, 4, 3) != 3 || _stream->read(&meshData->boundingBox.max.x, 4, 3) != 3)
     {
         GP_ERROR("Failed to load mesh bounding box.");
         SAFE_DELETE(meshData);
         return NULL;
     }
-    if (fread(&meshData->boundingSphere.center.x, 4, 3, _file) != 3 || fread(&meshData->boundingSphere.radius, 4, 1, _file) != 1)
+    if (_stream->read(&meshData->boundingSphere.center.x, 4, 3) != 3 || _stream->read(&meshData->boundingSphere.radius, 4, 1) != 1)
     {
         GP_ERROR("Failed to load mesh bounding sphere.");
         SAFE_DELETE(meshData);
@@ -1484,7 +1468,7 @@ Bundle::MeshData* Bundle::readMeshData()
 
     // Read mesh parts.
     unsigned int meshPartCount;
-    if (fread(&meshPartCount, 4, 1, _file) != 1)
+    if (_stream->read(&meshPartCount, 4, 1) != 1)
     {
         GP_ERROR("Failed to load mesh part count.");
         SAFE_DELETE(meshData);
@@ -1494,19 +1478,19 @@ Bundle::MeshData* Bundle::readMeshData()
     {
         // Read primitive type, index format and index count.
         unsigned int pType, iFormat, iByteCount;
-        if (fread(&pType, 4, 1, _file) != 1)
+        if (_stream->read(&pType, 4, 1) != 1)
         {
             GP_ERROR("Failed to load primitive type for mesh part with index %d.", i);
             SAFE_DELETE(meshData);
             return NULL;
         }
-        if (fread(&iFormat, 4, 1, _file) != 1)
+        if (_stream->read(&iFormat, 4, 1) != 1)
         {
             GP_ERROR("Failed to load index format for mesh part with index %d.", i);
             SAFE_DELETE(meshData);
             return NULL;
         }
-        if (fread(&iByteCount, 4, 1, _file) != 1)
+        if (_stream->read(&iByteCount, 4, 1) != 1)
         {
             GP_ERROR("Failed to load index byte count for mesh part with index %d.", i);
             SAFE_DELETE(meshData);
@@ -1540,7 +1524,7 @@ Bundle::MeshData* Bundle::readMeshData()
         partData->indexCount = iByteCount / indexSize;
 
         partData->indexData = new unsigned char[iByteCount];
-        if (fread(partData->indexData, 1, iByteCount, _file) != iByteCount)
+        if (_stream->read(partData->indexData, 1, iByteCount) != iByteCount)
         {
             GP_ERROR("Failed to read index data for mesh part with index %d.", i);
             SAFE_DELETE(meshData);
@@ -1601,7 +1585,7 @@ Bundle::MeshData* Bundle::readMeshData(const char* url)
 Font* Bundle::loadFont(const char* id)
 {
     GP_ASSERT(id);
-    GP_ASSERT(_file);
+    GP_ASSERT(_stream);
 
     // Seek to the specified font.
     Reference* ref = seekTo(id, BUNDLE_TYPE_FONT);
@@ -1612,7 +1596,7 @@ Font* Bundle::loadFont(const char* id)
     }
 
     // Read font family.
-    std::string family = readString(_file);
+    std::string family = readString(_stream);
     if (family.empty())
     {
         GP_ERROR("Failed to read font family for font '%s'.", id);
@@ -1621,23 +1605,23 @@ Font* Bundle::loadFont(const char* id)
 
     // Read font style and size.
     unsigned int style, size;
-    if (fread(&style, 4, 1, _file) != 1)
+    if (_stream->read(&style, 4, 1) != 1)
     {
         GP_ERROR("Failed to read style for font '%s'.", id);
         return NULL;
     }
-    if (fread(&size, 4, 1, _file) != 1)
+    if (_stream->read(&size, 4, 1) != 1)
     {
         GP_ERROR("Failed to read size for font '%s'.", id);
         return NULL;
     }
 
     // Read character set.
-    std::string charset = readString(_file);
+    std::string charset = readString(_stream);
 
     // Read font glyphs.
     unsigned int glyphCount;
-    if (fread(&glyphCount, 4, 1, _file) != 1)
+    if (_stream->read(&glyphCount, 4, 1) != 1)
     {
         GP_ERROR("Failed to read glyph count for font '%s'.", id);
         return NULL;
@@ -1649,7 +1633,7 @@ Font* Bundle::loadFont(const char* id)
     }
 
     Font::Glyph* glyphs = new Font::Glyph[glyphCount];
-    if (fread(glyphs, sizeof(Font::Glyph), glyphCount, _file) != glyphCount)
+    if (_stream->read(glyphs, sizeof(Font::Glyph), glyphCount) != glyphCount)
     {
         GP_ERROR("Failed to read glyphs for font '%s'.", id);
         SAFE_DELETE_ARRAY(glyphs);
@@ -1658,19 +1642,19 @@ Font* Bundle::loadFont(const char* id)
 
     // Read texture attributes.
     unsigned int width, height, textureByteCount;
-    if (fread(&width, 4, 1, _file) != 1)
+    if (_stream->read(&width, 4, 1) != 1)
     {
         GP_ERROR("Failed to read texture width for font '%s'.", id);
         SAFE_DELETE_ARRAY(glyphs);
         return NULL;
     }
-    if (fread(&height, 4, 1, _file) != 1)
+    if (_stream->read(&height, 4, 1) != 1)
     {
         GP_ERROR("Failed to read texture height for font '%s'.", id);
         SAFE_DELETE_ARRAY(glyphs);
         return NULL;
     }
-    if (fread(&textureByteCount, 4, 1, _file) != 1)
+    if (_stream->read(&textureByteCount, 4, 1) != 1)
     {
         GP_ERROR("Failed to read texture byte count for font '%s'.", id);
         SAFE_DELETE_ARRAY(glyphs);
@@ -1685,7 +1669,7 @@ Font* Bundle::loadFont(const char* id)
 
     // Read texture data.
     unsigned char* textureData = new unsigned char[textureByteCount];
-    if (fread(textureData, 1, textureByteCount, _file) != textureByteCount)
+    if (_stream->read(textureData, 1, textureByteCount) != textureByteCount)
     {
         GP_ERROR("Failed to read texture data for font '%s'.", id);
         SAFE_DELETE_ARRAY(glyphs);

+ 1 - 1
gameplay/src/Bundle.h

@@ -429,7 +429,7 @@ private:
     std::string _path;
     unsigned int _referenceCount;
     Reference* _references;
-    FILE* _file;
+    Stream* _stream;
 
     std::vector<MeshSkinData*> _meshSkins;
     std::map<std::string, Node*>* _trackedNodes;

+ 5 - 4
gameplay/src/Effect.cpp

@@ -200,10 +200,11 @@ static void writeShaderToErrorFile(const char* filePath, const char* source)
 {
     std::string path = filePath;
     path += ".err";
-    FILE* file = FileSystem::openFile(path.c_str(), "wb");
-    int err = ferror(file);
-    fwrite(source, 1, strlen(source), file);
-    fclose(file);
+    std::auto_ptr<Stream> stream(FileSystem::open(path.c_str(), FileSystem::WRITE));
+    if (stream.get() != NULL && stream->canWrite())
+    {
+        stream->write(source, 1, strlen(source));
+    }
 }
 
 Effect* Effect::createFromSource(const char* vshPath, const char* vshSource, const char* fshPath, const char* fshSource, const char* defines)

+ 395 - 30
gameplay/src/FileSystem.cpp

@@ -1,6 +1,7 @@
 #include "Base.h"
 #include "FileSystem.h"
 #include "Properties.h"
+#include "Stream.h"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -69,6 +70,78 @@ void makepath(std::string path, int mode)
 static std::string __resourcePath("./");
 static std::map<std::string, std::string> __aliases;
 
+/**
+ * 
+ * @script{ignore}
+ */
+class FileStream : public Stream
+{
+public:
+    friend class FileSystem;
+    
+    ~FileStream();
+    virtual bool canRead();
+    virtual bool canWrite();
+    virtual bool canSeek();
+    virtual void close();
+    virtual size_t read(void* ptr, size_t size, size_t count);
+    virtual char* readLine(char* str, int num);
+    virtual size_t write(const void* ptr, size_t size, size_t count);
+    virtual bool eof();
+    virtual size_t length();
+    virtual long int position();
+    virtual bool seek(long int offset, int origin);
+    virtual bool rewind();
+
+    static FileStream* create(const char* filePath, const char* mode);
+
+private:
+    FileStream(FILE* file);
+
+private:
+    FILE* _file;
+    bool _canRead;
+    bool _canWrite;
+};
+
+#ifdef __ANDROID__
+
+/**
+ * 
+ * @script{ignore}
+ */
+class FileStreamAndroid : public Stream
+{
+public:
+    friend class FileSystem;
+    
+    ~FileStreamAndroid();
+    virtual bool canRead();
+    virtual bool canWrite();
+    virtual bool canSeek();
+    virtual void close();
+    virtual size_t read(void* ptr, size_t size, size_t count);
+    virtual char* readLine(char* str, int num);
+    virtual size_t write(const void* ptr, size_t size, size_t count);
+    virtual bool eof();
+    virtual size_t length();
+    virtual long int position();
+    virtual bool seek(long int offset, int origin);
+    virtual bool rewind();
+
+    static FileStreamAndroid* create(const char* filePath, const char* mode);
+
+private:
+    FileStreamAndroid(AAsset* asset);
+
+private:
+    AAsset* _asset;
+};
+
+#endif
+
+/////////////////////////////
+
 FileSystem::FileSystem()
 {
 }
@@ -230,15 +303,52 @@ bool FileSystem::fileExists(const char* filePath)
 #endif
 }
 
-FILE* FileSystem::openFile(const char* path, const char* mode)
+Stream* FileSystem::open(const char* path, size_t mode)
 {
-    GP_ASSERT(path);
+    char modeStr[] = "rb";
+    if ((mode & WRITE) != 0)
+        modeStr[0] = 'w';
+#ifdef __ANDROID__
+    return FileStreamAndroid::create(resolvePath(path), modeStr);
+#else
+    std::string fullPath(__resourcePath);
+    fullPath += resolvePath(path);
+    
+#ifdef WIN32
+    gp_stat_struct s;
+    if (stat(fullPath.c_str(), &s) != 0)
+    {
+        fullPath = __resourcePath;
+        fullPath += "../../gameplay/";
+        fullPath += path;
+        
+        int result = stat(fullPath.c_str(), &s);
+        if (result != 0)
+        {
+            fullPath = __resourcePath;
+            fullPath += "../gameplay/";
+            fullPath += path;
+            if (stat(fullPath.c_str(), &s) != 0)
+            {
+                return NULL;
+            }
+        }
+    }
+#endif
+    FileStream* stream = FileStream::create(fullPath.c_str(), modeStr);
+    return stream;
+#endif
+}
+
+FILE* FileSystem::openFile(const char* filePath, const char* mode)
+{
+    GP_ASSERT(filePath);
     GP_ASSERT(mode);
 
     std::string fullPath(__resourcePath);
-    fullPath += resolvePath(path);
+    fullPath += resolvePath(filePath);
 
-    createFileFromAsset(path);
+    createFileFromAsset(filePath);
     
     FILE* fp = fopen(fullPath.c_str(), mode);
     
@@ -247,14 +357,14 @@ FILE* FileSystem::openFile(const char* path, const char* mode)
     {
         fullPath = __resourcePath;
         fullPath += "../../gameplay/";
-        fullPath += path;
+        fullPath += filePath;
         
         fp = fopen(fullPath.c_str(), mode);
         if (!fp)
         {
             fullPath = __resourcePath;
             fullPath += "../gameplay/";
-            fullPath += path;
+            fullPath += filePath;
             fp = fopen(fullPath.c_str(), mode);
         }
     }
@@ -268,32 +378,20 @@ char* FileSystem::readAll(const char* filePath, int* fileSize)
     GP_ASSERT(filePath);
 
     // Open file for reading.
-    FILE* file = openFile(filePath, "rb");
-    if (file == NULL)
+    std::auto_ptr<Stream> stream(open(filePath));
+    if (stream.get() == NULL)
     {
         GP_ERROR("Failed to load file: %s", filePath);
         return NULL;
     }
-
-    // Obtain file length.
-    if (fseek(file, 0, SEEK_END) != 0)
-    {
-        GP_ERROR("Failed to seek to the end of the file '%s' to obtain the file length.", filePath);
-        return NULL;
-    }
-    int size = (int)ftell(file);
-    if (fseek(file, 0, SEEK_SET) != 0)
-    {
-        GP_ERROR("Failed to seek to beginning of the file '%s' to begin reading in the entire file.", filePath);
-        return NULL;
-    }
+    size_t size = stream->length();
 
     // Read entire file contents.
     char* buffer = new char[size + 1];
-    int read = (int)fread(buffer, 1, size, file);
+    size_t read = stream->read(buffer, 1, size);
     if (read != size)
     {
-        GP_ERROR("Failed to read complete contents of file '%s' (amount read vs. file size: %d < %d).", filePath, (int)read, (int)size);
+        GP_ERROR("Failed to read complete contents of file '%s' (amount read vs. file size: %u < %u).", filePath, read, size);
         SAFE_DELETE_ARRAY(buffer);
         return NULL;
     }
@@ -301,15 +399,9 @@ char* FileSystem::readAll(const char* filePath, int* fileSize)
     // Force the character buffer to be NULL-terminated.
     buffer[size] = '\0';
 
-    // Close file and return.
-    if (fclose(file) != 0)
-    {
-        GP_ERROR("Failed to close file '%s'.", filePath);
-    }
-
     if (fileSize)
     {
-        *fileSize = size; 
+        *fileSize = (int)size; 
     }
     return buffer;
 }
@@ -383,4 +475,277 @@ void FileSystem::createFileFromAsset(const char* path)
 #endif
 }
 
+//////////////////
+
+FileStream::FileStream(FILE* file)
+    : _file(file), _canRead(false), _canWrite(false)
+{
+    
+}
+
+FileStream::~FileStream()
+{
+    if (_file)
+    {
+        close();
+    }
+}
+
+FileStream* FileStream::create(const char* filePath, const char* mode)
+{
+    FILE* file = fopen(filePath, mode);
+    if (file)
+    {
+        FileStream* stream = new FileStream(file);
+        const char* s = mode;
+        while (s != NULL && *s != '\0')
+        {
+            if (*s == 'r')
+                stream->_canRead = true;
+            else if (*s == 'w')
+                stream->_canWrite = true;
+            ++s;
+        }
+
+        return stream;
+    }
+    return NULL;
+}
+
+bool FileStream::canRead()
+{
+    return _file && _canRead;
+}
+
+bool FileStream::canWrite()
+{
+    return _file && _canWrite;
+}
+
+bool FileStream::canSeek()
+{
+    return _file != NULL;
+}
+
+void FileStream::close()
+{
+    fclose(_file);
+    _file = NULL;
+}
+
+size_t FileStream::read(void* ptr, size_t size, size_t count)
+{
+    if (!_file)
+        return 0;
+    return fread(ptr, size, count, _file);
+}
+
+char* FileStream::readLine(char* str, int num)
+{
+    if (!_file)
+        return 0;
+    return fgets(str, num, _file);
+}
+
+size_t FileStream::write(const void* ptr, size_t size, size_t count)
+{
+    if (!_file)
+        return 0;
+    return fwrite(ptr, size, count, _file);
+}
+
+bool FileStream::eof()
+{
+    if (!_file || feof(_file))
+        return true;
+    return ((size_t)position()) >= length();
+}
+
+size_t FileStream::length()
+{
+    size_t len = 0;
+    if (canSeek())
+    {
+        long int pos = position();
+        if (seek(0, SEEK_END))
+        {
+            len = position();
+        }
+        seek(pos, SEEK_SET);
+    }
+    return len;
+}
+
+long int FileStream::position()
+{
+    if (!_file)
+        return -1;
+    return ftell(_file);
+}
+
+bool FileStream::seek(long int offset, int origin)
+{
+    if (!_file)
+        return false;
+    return fseek(_file, offset, origin) == 0;
+}
+
+bool FileStream::rewind()
+{
+    if (canSeek())
+    {
+        ::rewind(_file);
+        return true;
+    }
+    return false;
+}
+
+////////////////////////////////
+
+#ifdef __ANDROID__
+
+FileStreamAndroid::FileStreamAndroid(AAsset* asset)
+    : _asset(asset)
+{
+}
+
+FileStreamAndroid::~FileStreamAndroid()
+{
+    if (_asset)
+        close();
+}
+
+FileStreamAndroid* FileStreamAndroid::create(const char* filePath, const char* mode)
+{
+    AAsset* asset = AAssetManager_open(__assetManager, filePath, AASSET_MODE_RANDOM);
+    if (asset)
+    {
+        FileStreamAndroid* stream = new FileStreamAndroid(asset);
+        return stream;
+    }
+    return NULL;
+}
+
+bool FileStreamAndroid::canRead()
+{
+    return true;
+}
+
+bool FileStreamAndroid::canWrite()
+{
+    return false;
+}
+
+bool FileStreamAndroid::canSeek()
+{
+    return true;
+}
+
+void FileStreamAndroid::close()
+{
+    AAsset_close(_asset);
+    _asset = NULL;
+}
+
+size_t FileStreamAndroid::read(void* ptr, size_t size, size_t count)
+{
+    int result = AAsset_read(_asset, ptr, size * count);
+    return result > 0 ? ((size_t)result) / size : 0;
+}
+
+char* FileStreamAndroid::readLine(char* str, int num)
+{
+    if (num <= 0)
+        return NULL;
+    char c = 0;
+    size_t maxCharsToRead = num - 1;
+    for (size_t i = 0; i < maxCharsToRead; ++i)
+    {
+        size_t result = read(&c, 1, 1);
+        if (result != 1)
+        {
+            str[i] = '\0';
+            break;
+        }
+        if (c == '\n')
+        {
+            str[i] = c;
+            str[i + 1] = '\0';
+            break;
+        }
+        else if(c == '\r')
+        {
+            str[i] = c;
+            // next may be '\n'
+            size_t pos = position();
+
+            char nextChar = 0;
+            if (read(&nextChar, 1, 1) != 1)
+            {
+                // no more characters
+                str[i + 1] = '\0';
+                break;
+            }
+            if (nextChar == '\n')
+            {
+                if (i == maxCharsToRead - 1)
+                {
+                    str[i + 1] = '\0';
+                    break;
+                }
+                else
+                {
+                    str[i + 1] = nextChar;
+                    str[i + 2] = '\0';
+                    break;
+                }
+            }
+            else
+            {
+                seek(pos, SEEK_SET);
+                str[i + 1] = '\0';
+                break;
+            }
+        }
+        str[i] = c;
+    }
+    return str; // what if first read failed?
+}
+
+size_t FileStreamAndroid::write(const void* ptr, size_t size, size_t count)
+{
+    return 0;
+}
+
+bool FileStreamAndroid::eof()
+{
+    return position() >= length();
+}
+
+size_t FileStreamAndroid::length()
+{
+    return (size_t)AAsset_getLength(_asset);
+}
+
+long int FileStreamAndroid::position()
+{
+    return AAsset_getLength(_asset) - AAsset_getRemainingLength(_asset);
+}
+
+bool FileStreamAndroid::seek(long int offset, int origin)
+{
+    return AAsset_seek(_asset, offset, origin) != -1;
+}
+
+bool FileStreamAndroid::rewind()
+{
+    if (canSeek())
+    {
+        return AAsset_seek(_asset, 0, SEEK_SET) != -1;
+    }
+    return false;
+}
+
+#endif
+
 }

+ 24 - 0
gameplay/src/FileSystem.h

@@ -1,6 +1,8 @@
 #ifndef FILESYSTEM_H_
 #define FILESYSTEM_H_
 
+#include "Stream.h"
+
 namespace gameplay
 {
 
@@ -13,6 +15,12 @@ class FileSystem
 {
 public:
 
+    enum StreamMode
+    {
+        READ = 1,
+        WRITE = 2
+    };
+
     /**
      * Destructor.
      */
@@ -107,6 +115,22 @@ public:
      */
     static bool fileExists(const char* filePath);
 
+    /**
+     * Opens a byte stream for the given resource path.
+     *
+     * If <code>path</code> is a file path, the file at the specified location is opened relative to the currently set
+     * resource path.
+     *
+     * @param path The path to the resource to be opened, relative to the currently set resource path.
+     * @param mode The mode used to open the file.
+     * 
+     * @return A stream that can be used to read or write to the file depending on the mode.
+     *         Returns NULL if there was an error. (Request mode not supported).
+     * 
+     * @script{ignore}
+     */
+    static Stream* open(const char* path, size_t mode = READ);
+
     /**
      * Opens the specified file.
      *

+ 48 - 35
gameplay/src/Properties.cpp

@@ -6,6 +6,19 @@
 namespace gameplay
 {
 
+/**
+ * Reads the next character from the stream. Returns EOF if the end of the stream is reached.
+ */
+static signed char readChar(Stream* stream)
+{
+    if (stream->eof())
+        return EOF;
+    signed char c;
+    if (stream->read(&c, 1, 1) != 1)
+        return EOF;
+    return c;
+}
+
 // Utility functions (shared with SceneLoader).
 /** @script{ignore} */
 void calculateNamespacePath(const std::string& urlString, std::string& fileString, std::vector<std::string>& namespacePath);
@@ -29,13 +42,14 @@ Properties::Properties(const Properties& copy)
     rewind();
 }
 
-Properties::Properties(FILE* file)
+
+Properties::Properties(Stream* stream)
 {
-    readProperties(file);
+    readProperties(stream);
     rewind();
 }
 
-Properties::Properties(FILE* file, const char* name, const char* id, const char* parentID) : _namespace(name)
+Properties::Properties(Stream* stream, const char* name, const char* id, const char* parentID) : _namespace(name)
 {
     if (id)
     {
@@ -45,7 +59,7 @@ Properties::Properties(FILE* file, const char* name, const char* id, const char*
     {
         _parentID = parentID;
     }
-    readProperties(file);
+    readProperties(stream);
     rewind();
 }
 
@@ -63,16 +77,16 @@ Properties* Properties::create(const char* url)
     std::vector<std::string> namespacePath;
     calculateNamespacePath(urlString, fileString, namespacePath);
 
-    FILE* file = FileSystem::openFile(fileString.c_str(), "rb");
-    if (!file)
+    std::auto_ptr<Stream> stream(FileSystem::open(fileString.c_str()));
+    if (stream.get() == NULL)
     {
         GP_ERROR("Failed to open file '%s'.", fileString.c_str());
         return NULL;
     }
 
-    Properties* properties = new Properties(file);
+    Properties* properties = new Properties(stream.get());
     properties->resolveInheritance();
-    fclose(file);
+    stream->close();
 
     // Get the specified properties object.
     Properties* p = getPropertiesFromNamespacePath(properties, namespacePath);
@@ -93,9 +107,9 @@ Properties* Properties::create(const char* url)
     return p;
 }
 
-void Properties::readProperties(FILE* file)
+void Properties::readProperties(Stream* stream)
 {
-    GP_ASSERT(file);
+    GP_ASSERT(stream);
 
     char line[2048];
     int c;
@@ -108,14 +122,14 @@ void Properties::readProperties(FILE* file)
 
     while (true)
     {
-        skipWhiteSpace(file);
+        skipWhiteSpace(stream);
 
         // Stop when we have reached the end of the file.
-        if (feof(file))
+        if (stream->eof())
             break;
 
         // Read the next line.
-        rc = fgets(line, 2048, file);
+        rc = stream->readLine(line, 2048);
         if (rc == NULL)
         {
             GP_ERROR("Error reading line from file.");
@@ -213,20 +227,20 @@ void Properties::readProperties(FILE* file)
                     // If the namespace ends on this line, seek back to right before the '}' character.
                     if (rccc && rccc == lineEnd)
                     {
-                        if (fseek(file, -1, SEEK_CUR) != 0)
+                        if (stream->seek(-1, SEEK_CUR) == false)
                         {
                             GP_ERROR("Failed to seek back to before a '}' character in properties file.");
                             return;
                         }
-                        while (fgetc(file) != '}')
+                        while (readChar(stream) != '}')
                         {
-                            if (fseek(file, -2, SEEK_CUR) != 0)
+                            if (stream->seek(-2, SEEK_CUR) == false)
                             {
                                 GP_ERROR("Failed to seek back to before a '}' character in properties file.");
                                 return;
                             }
                         }
-                        if (fseek(file, -1, SEEK_CUR) != 0)
+                        if (stream->seek(-1, SEEK_CUR) == false)
                         {
                             GP_ERROR("Failed to seek back to before a '}' character in properties file.");
                             return;
@@ -234,13 +248,13 @@ void Properties::readProperties(FILE* file)
                     }
 
                     // New namespace without an ID.
-                    Properties* space = new Properties(file, name, NULL, parentID);
+                    Properties* space = new Properties(stream, name, NULL, parentID);
                     _namespaces.push_back(space);
 
                     // If the namespace ends on this line, seek to right after the '}' character.
                     if (rccc && rccc == lineEnd)
                     {
-                        if (fseek(file, 1, SEEK_CUR) != 0)
+                        if (stream->seek(1, SEEK_CUR) == false)
                         {
                             GP_ERROR("Failed to seek to immediately after a '}' character in properties file.");
                             return;
@@ -255,20 +269,20 @@ void Properties::readProperties(FILE* file)
                         // If the namespace ends on this line, seek back to right before the '}' character.
                         if (rccc && rccc == lineEnd)
                         {
-                            if (fseek(file, -1, SEEK_CUR) != 0)
+                            if (stream->seek(-1, SEEK_CUR) == false)
                             {
                                 GP_ERROR("Failed to seek back to before a '}' character in properties file.");
                                 return;
                             }
-                            while (fgetc(file) != '}')
+                            while (readChar(stream) != '}')
                             {
-                                if (fseek(file, -2, SEEK_CUR) != 0)
+                                if (stream->seek(-2, SEEK_CUR) == false)
                                 {
                                     GP_ERROR("Failed to seek back to before a '}' character in properties file.");
                                     return;
                                 }
                             }
-                            if (fseek(file, -1, SEEK_CUR) != 0)
+                            if (stream->seek(-1, SEEK_CUR) == false)
                             {
                                 GP_ERROR("Failed to seek back to before a '}' character in properties file.");
                                 return;
@@ -276,13 +290,13 @@ void Properties::readProperties(FILE* file)
                         }
 
                         // Create new namespace.
-                        Properties* space = new Properties(file, name, value, parentID);
+                        Properties* space = new Properties(stream, name, value, parentID);
                         _namespaces.push_back(space);
 
                         // If the namespace ends on this line, seek to right after the '}' character.
                         if (rccc && rccc == lineEnd)
                         {
-                            if (fseek(file, 1, SEEK_CUR) != 0)
+                            if (stream->seek(1, SEEK_CUR) == false)
                             {
                                 GP_ERROR("Failed to seek to immediately after a '}' character in properties file.");
                                 return;
@@ -292,18 +306,18 @@ void Properties::readProperties(FILE* file)
                     else
                     {
                         // Find out if the next line starts with "{"
-                        skipWhiteSpace(file);
-                        c = fgetc(file);
+                        skipWhiteSpace(stream);
+                        c = readChar(stream);
                         if (c == '{')
                         {
                             // Create new namespace.
-                            Properties* space = new Properties(file, name, value, parentID);
+                            Properties* space = new Properties(stream, name, value, parentID);
                             _namespaces.push_back(space);
                         }
                         else
                         {
                             // Back up from fgetc()
-                            if (fseek(file, -1, SEEK_CUR) != 0)
+                            if (stream->seek(-1, SEEK_CUR) == false)
                                 GP_ERROR("Failed to seek backwards a single character after testing if the next line starts with '{'.");
 
                             // Store "name value" as a name/value pair, or even just "name".
@@ -331,20 +345,19 @@ Properties::~Properties()
     }
 }
 
-void Properties::skipWhiteSpace(FILE* file)
+void Properties::skipWhiteSpace(Stream* stream)
 {
-    int c;
-
+    signed char c;
     do
     {
-        c = fgetc(file);
-    } while (isspace(c));
+        c = readChar(stream);
+    } while (isspace(c) && c != EOF);
 
     // If we are not at the end of the file, then since we found a
     // non-whitespace character, we put the cursor back in front of it.
     if (c != EOF)
     {
-        if (fseek(file, -1, SEEK_CUR) != 0)
+        if (stream->seek(-1, SEEK_CUR) == false)
         {
             GP_ERROR("Failed to seek backwards one character after skipping whitespace.");
         }

+ 5 - 4
gameplay/src/Properties.h

@@ -4,6 +4,7 @@
 #include "Base.h"
 #include "Matrix.h"
 #include "Vector2.h"
+#include "Stream.h"
 
 namespace gameplay
 {
@@ -383,17 +384,17 @@ private:
      * Constructors.
      */
     Properties();
-    Properties(FILE* file);
+    Properties(Stream* stream);
     Properties(const Properties& copy);
 
     /**
      * Constructor. Read from the beginning of namespace specified
      */
-    Properties(FILE* file, const char* name, const char* id = NULL, const char* parentID = NULL);
+    Properties(Stream* stream, const char* name, const char* id = NULL, const char* parentID = NULL);
 
-    void readProperties(FILE* file);
+    void readProperties(Stream* stream);
 
-    void skipWhiteSpace(FILE* file);
+    void skipWhiteSpace(Stream* stream);
 
     char* trimWhiteSpace(char* str);
 

+ 167 - 0
gameplay/src/Stream.h

@@ -0,0 +1,167 @@
+#ifndef Stream_H_
+#define Stream_H_
+
+namespace gameplay
+{
+
+/**
+ * Stream is an interface for reading and writing a sequence of bytes.
+ * 
+ * Use FileSystem::open() to create a stream.
+ * 
+ * @script{ignore}
+ */
+class Stream
+{
+public:
+
+    /**
+     * Destructor. The stream should be closed when it is destroyed.
+     */
+    virtual ~Stream() {};
+
+    /**
+     * Returns true if this stream can preform read operations.
+     * 
+     * @return True if the stream can read, false otherwise.
+     */
+    virtual bool canRead() = 0;
+
+    /**
+     * Returns true if this stream can perform write operations.
+     * 
+     * @return True if the stream can write, false otherwise.
+     */
+    virtual bool canWrite() = 0;
+
+    /**
+     * Returns true if this stream can seek.
+     * 
+     * @return True if the stream can seek, false otherwise.
+     */
+    virtual bool canSeek() = 0;
+
+    /**
+     * Closes this stream.
+     */
+    virtual void close() = 0;
+    
+    /**
+     * Reads an array of <code>count</code> elements, each of size <code>size</code>.
+     * 
+     * \code
+     * int numbers[3];
+     * if (stream->read(numbers, sizeof(int), 3) != 3)
+     *     print("Error reading from file");
+     * \endcode
+     * 
+     * @param ptr   The pointer to the memory to copy into.
+     *              The available size should be at least (<code>size * count</code>) bytes.
+     * @param size  The size of each element to be read, in bytes.
+     * @param count The number of elements to read.
+     * 
+     * @return The number of elements read.
+     * 
+     * @see canRead()
+     */
+    virtual size_t read(void* ptr, size_t size, size_t count) = 0;
+
+    /**
+     * Reads a line from the stream.
+     * 
+     * A new line is denoted by by either "\n", "\r" or "\r\n".
+     * The line break character is included in the string.
+     * The terminating null character is added to the end of the string.
+     * 
+     * @param str The array of chars to copy the string to.
+     * @param num The maximum number of characters to be copied.
+     * 
+     * @return On success, str is returned. On error, NULL is returned.
+     * 
+     * @see canRead()
+     */
+    virtual char* readLine(char* str, int num) = 0;
+    
+    /**
+     * Writes an array of <code>count</code> elements, each of size <code>size</code>.
+     * 
+     * \code
+     * int numbers[] = {1, 2, 3};
+     * if (stream->write(numbers, sizeof(int), 3) != 3)
+     *     print("Error writing to file");
+     * \endcode
+     * 
+     * @param ptr   The poiner to the array of elements to be written.
+     * @param size  The size of each element to be written, in bytes.
+     * @param count The number of elements to write.
+     * 
+     * @return The number of elements written.
+     * 
+     * @see canWrite()
+     */
+    virtual size_t write(const void* ptr, size_t size, size_t count) = 0;
+
+    /**
+     * Returns true if the end of the stream has been reached.
+     * 
+     * @return True if end of stream reached, false otherwise.
+     */
+    virtual bool eof() = 0;
+
+    /**
+     * Returns the length of the stream in bytes.
+     * 
+     * Zero is returned if the length of the stream is unknown and/or it cannot be seeked. 
+     * 
+     * Example: The length of a network stream is unknown and cannot be seeked.
+     * 
+     * @return The length of the stream in bytes.
+     */
+    virtual size_t length() = 0;
+
+    /**
+     * Returns the position of the file pointer. Zero is the start of the stream.
+     * 
+     * @return The file indicator offset in bytes. 
+     */
+    virtual long int position() = 0;
+
+    /**
+     * Sets the position of the file pointer.
+     * 
+     * Use canSeek() to determine if this method is supported.
+     * 
+     * @param offset The number of bytes to offset from origin.
+     * @param origin The position used as a reference for offset.
+     *               The supported vaules are the same as fseek().
+     *                - <code>SEEK_SET</code> relative to the beginning of the file.
+     *                - <code>SEEK_CUR</code> relative to the current position of the file pointer.
+     *                - <code>SEEK_END</code> relative to the end of file.
+     * 
+     * @return True if successful, false otherwise.
+     * 
+     * @see canSeek()
+     */
+    virtual bool seek(long int offset, int origin) = 0;
+
+    /**
+     * Moves the file pointer to the start of the file.
+     * 
+     * Use canSeek() to determine if this method is supported.
+     * 
+     * @return True if successful, false otherwise.
+     * 
+     * @see canSeek()
+     */
+    virtual bool rewind() = 0;
+
+protected:
+    Stream() {};
+private:
+    Stream(const Stream&);            // Hidden copy constructor.
+    Stream& operator=(const Stream&); // Hidden copy assignment operator.
+};
+
+}
+
+#endif