Writing primitive types and structs into disk
I was looking for a way to write
floats/ints/strings
to a file, then read them as
floats/ints/strings
. I needed it because of the
2D Game Editors I built. I wanted to be able to generate a character or map file, that can be read, updated, saved, etc.
On top of the primitive types, I needed to also write a whole
struct
and be able to read it in 1 line of code as a
struct
(not have to read each property of the struct 1 by 1). That way the
Game Engine can load the characters/maps and get the game rolling!
Basically read and write as
ios::binary
. I couldnβt find anything that does that for
C++
. So I ended up writing it myself, thought I would share it :)
A couple of quick notes:
- You can ignore the βBMβ part, it was just a prefix for my game editor. I like to prefix file names to that I can type βBMβ¦β and will see every single class that I wrote, without having to refer to the namespace.
- The
BMLogging
is just a helper class that logs strings as needed.
#include <iostream>
#include <fstream>
using namespace std;
namespace BMGameEngine
{
enum BMFileMode
{
Open = 0,
Create,
Append
};
enum BMBinaryIOMode
{
BMBinaryIOMode_None = 0,
BMBinaryIOMode_Read,
BMBinaryIOMode_Write
};
class BMBinaryIO
{
// the output file stream to write onto a file
ofstream writer;
// the input file stream to read from a file
ifstream reader;
// the filepath of the file we're working with
string filePath;
// the current active mode.
BMBinaryIOMode currentMode;
public:
BMBinaryIO()
{
currentMode = BMBinaryIOMode_None;
}
// the destructor will be responsible for checking if we forgot to close
// the file
~BMBinaryIO()
{
if(writer.is_open())
{
BMLOG_ERROR(BMLoggingClass_BinaryIO, "You forgot to call close() after finishing with the file! Closing it...");
writer.close();
}
if(reader.is_open())
{
BMLOG_ERROR(BMLoggingClass_BinaryIO, "You forgot to call close() after finishing with the file! Closing it...");
reader.close();
}
}
// opens a file with either read or write mode. Returns whether
// the open operation was successful
bool open(string fileFullPath, BMBinaryIOMode mode)
{
filePath = fileFullPath;
BMLOG_INFO(BMLoggingClass_BinaryIO, "Opening file: " + filePath);
// Write mode
if(mode == BMBinaryIOMode_Write)
{
currentMode = mode;
// check if we had a previously opened file to close it
if(writer.is_open())
writer.close();
writer.open(filePath.c_str(), ios::binary);
if(!writer.is_open())
{
BMLOG_ERROR(BMLoggingClass_BinaryIO, "Could not open file for write: " + filePath);
currentMode = BMBinaryIOMode_None;
}
}
// Read mode
else if(mode == BMBinaryIOMode_Read)
{
currentMode = mode;
// check if we had a previously opened file to close it
if(reader.is_open())
reader.close();
reader.open(filePath.c_str(), ios::binary);
if(!reader.is_open())
{
BMLOG_ERROR(BMLoggingClass_BinaryIO, "Could not open file for read: " + filePath);
currentMode = BMBinaryIOMode_None;
}
}
// if the mode is still the NONE/initial one -> we failed
return currentMode == BMBinaryIOMode_None ? false : true;
}
// closes the file
void close()
{
if(currentMode == BMBinaryIOMode_Write)
{
writer.close();
}
else if(currentMode == BMBinaryIOMode_Read)
{
reader.close();
}
}
// checks whether we're allowed to write or not.
bool checkWritabilityStatus()
{
if(currentMode != BMBinaryIOMode_Write)
{
BMLOG_ERROR(BMLoggingClass_BinaryIO, "Trying to write with a non Writable mode!");
return false;
}
return true;
}
// helper to check if we're allowed to read
bool checkReadabilityStatus()
{
if(currentMode != BMBinaryIOMode_Read)
{
BMLOG_ERROR(BMLoggingClass_BinaryIO, "Trying to read with a non Readable mode!");
return false;
}
// check if we hit the end of the file.
if(reader.eof())
{
BMLOG_ERROR(BMLoggingClass_BinaryIO, "Trying to read but reached the end of file!");
reader.close();
currentMode = BMBinaryIOMode_None;
return false;
}
return true;
}
// so we can check if we hit the end of the file
bool eof()
{
return reader.eof();
}
// Generic write method that will write any value to a file (except a string,
// for strings use writeString instead)
template<typename T>
void write(T &value)
{
if(!checkWritabilityStatus())
return;
// write the value to the file.
writer.write((const char *)&value, sizeof(value));
}
// Writes a string to the file
void writeString(string str)
{
if(!checkWritabilityStatus())
return;
// first add a \0 at the end of the string so we can detect
// the end of string when reading it
str += '\0';
// create char pointer from string.
char* text = (char *)(str.c_str());
// find the length of the string.
unsigned long size = str.size();
// write the whole string including the null.
writer.write((const char *)text, size);
}
// reads any type of value except strings.
template<typename T>
T read()
{
checkReadabilityStatus();
T value;
reader.read((char *)&value, sizeof(value));
return value;
}
// reads any type of value except strings.
template<typename T>
void read(T &value)
{
if(checkReadabilityStatus())
{
reader.read((char *)&value, sizeof(value));
}
}
// read a string value
string readString()
{
if(checkReadabilityStatus())
{
char c;
string result = "";
while(!reader.eof() && (c = read<char>()) != '\0')
{
result += c;
}
#ifdef BMSYSTEM_DEBUG_MODE
//BMLOG_INFO(BMLoggingClass::BinaryIO, "string value read: " + result);
#endif
return result;
}
return "";
}
// read a string value
void readString(string &result)
{
if(checkReadabilityStatus())
{
char c;
result = "";
while(!reader.eof() && (c = read<char>()) != '\0')
{
result += c;
}
#ifdef BMSYSTEM_DEBUG_MODE
//BMLOG_INFO(BMLoggingClass::BinaryIO, "string value read: " + result);
#endif
}
}
};
}
Hereβs how you would use it to WRITE:
string myPath = "somepath to the file";
BMBinaryIO binaryIO;
if(binaryIO.open(myPath, BMBinaryIOMode::Write))
{
float value = 165;
binaryIO.write(value);
char valueC = 'K';
binaryIO.write(valueC);
double valueD = 1231.99;
binaryIO.write(valueD);
string valueStr = "spawnAt(100,200)";
binaryIO.writeString(valueStr);
valueStr = "helpAt(32,3)";
binaryIO.writeString(valueStr);
binaryIO.close();
}
Hereβs how you would use it to READ:
string myPath = "some path to the same file";
if(binaryIO.open(myPath, BMBinaryIOMode::Read))
{
cout << binaryIO.read<float>() << endl;
cout << binaryIO.read<char>() << endl;
double valueD = 0;
binaryIO.read(valueD); // or you could use read<double()
cout << valueD << endl;
cout << binaryIO.readString() << endl;
cout << binaryIO.readString() << endl;
binaryIO.close();
}
You could even write/read a whole struct
in 1 line:
struct Vertex {
float x, y;
};
Vertex vtx; vtx.x = 2.5f; vtx.y = 10.0f;
// to write it
binaryIO.write(vtx);
// to read it
Vertex vtxRead;
binaryIO.read(vtxRead); // option 1
vtxRead = binaryIO.read<Vertex>(); // option 2
Hope that helps, please donβt hesitate to provide feedback or drop a comment!
0