0

Im trying to make a level loading system, where the level file is stored as some binary file that has the class of the object to be spawned and its position and scale. One thing I cant figure out is how to convert a class type into something that can be written to a binary file and also retrieved later.

I tried converting it to a string, but then how do I turn that typeid(type).name() back into the class type to spawn?

Open to any methods of doing this

fes dfadfa
  • 21
  • 4
  • To do this one option could be serializing the class object and storing it in file/DB and de-serializing back to the class object when required to load. It will also take care of the class type logic i.e. whatever class you deserialize into it will be your object would be able to call the method on it. I have myself used this approach for providing an undo option in a game. – Amit Upadhyay Jun 09 '20 at 11:51
  • if someone finds the above solution inappropriate, then please let me know what issue you see with the above solution? – Amit Upadhyay Jun 09 '20 at 11:52
  • On which operating systems?? You could consider using some plugin tricks. See [this answer](https://stackoverflow.com/a/62261537/841108) to a similar question – Basile Starynkevitch Jun 09 '20 at 12:11

2 Answers2

2

There are a few things going on here, so I took the liberty of writing a short snippet.

In order to read in a bunch of objects into a single container like the below when you don't know their type, they all need to derive from a base class. Alternatively you could pass the deserialize function different vectors (or other containers) to store the different types. Whatever works.

The deserialize function then uses a switch like @darune suggested to construct the appropriate type.

There are all sorts of serializing / deserializing libraries out there which will allow you to be more sophisticated than this and store more complex groups of objects. I often use rapidjson for this sort of thing. boost.serialize is another option (again as @darune suggested).

#include <fstream>
#include <iostream>
#include <vector>

using std::ifstream;
using std::vector;
using std::cout;
using std::endl;

class Object
{
public:
    double position;
    double scale;

    Object(const double& position, const double& scale)
    : position(position), scale(scale) { }

    virtual ~Object() {}
};

class Foo : public Object
{
public:
    static const int id = 0;
    Foo(const double& position, const double& scale)
    : Object(position, scale) { }

};

class Bar : public Object
{
public:
    static const int id = 1;
    Bar(const double& position, const double& scale)
    : Object(position, scale) { }
};

Object* deserialize(int id, const double& position, const double& scale)
{
    switch (id)
    {
        case Foo::id:
            return new Foo(position, scale);
        case Bar::id:
            return new Bar(position, scale);
        default:
            return nullptr;
    };
}

And then an example of reading in from a text file looks like

int main(void)
{
    vector<Object*> objects;

    ifstream fin;
    fin.open("objects.txt");
    if (!fin)
        throw std::runtime_error("Unable to open file");


    // Read in the id, position and scale from a file
    int id;
    double position, scale;

    while (!fin.eof())
    {
        fin >> id >> position >> scale;
        Object* object = deserialize(id, position, scale);
        if (object != nullptr)
            objects.push_back(object);
    }

    fin.close();

    // Print out the objects
    for (auto pobj: objects)
        cout << pobj->position << " " << pobj->scale << endl;

    // Don't forget to clean up
    for (auto object: objects)
        delete object;
}

In this case objects.txt was a text file that is just whitespace delimited id position scale (with no header). For example

1 0.4 10
0 0.1 5
0 0.1 1

Reading in from binary is similar.

ChrisD
  • 927
  • 4
  • 10
  • Ok awesome, also how could you automate the creation of cases in the switch? Maybe like a macro to add the object type – fes dfadfa Jun 09 '20 at 15:31
  • I don't think you can ever fully automate it, writing a macro would probably take as much effort as just defining the switch cases and would obfuscate the logic. If you are dealing with a large number of classes you should definitely consider a serialization library. Take a look at the "non-intrusive" example for boost::serialize [here](https://www.boost.org/doc/libs/1_73_0/libs/serialization/doc/tutorial.html). – ChrisD Jun 10 '20 at 14:15
1

I tried converting it to a string, but then how do I turn that typeid(type).name() back into the class type to spawn?

You have to make this yourself somehow (as there is nothing in the language that can "go the other way" so to speak, that is construct an object from a typeid), so eg. using a switch statement (or if/else structure), ala:

switch (my_type_id) {
//... add cases here for the types you need
}

The issue with using typeid is that it is not persisent. Therefore it is recommended you build your own list/enum of the types you need.

If you are not making your own ID/enum type and wants to use typeid, I recommend you use boost typeindex instead https://www.boost.org/doc/libs/1_59_0/doc/html/boost_typeindex.html, that will probably save yourself for some surprises debugging. The simplest is probably still to just make your own enum for the types you need.

For more advanced solutions even you could look at the boost serialize library.

darune
  • 10,480
  • 2
  • 24
  • 62