Jump to content
  • Advertisement
  • entries
    743
  • comments
    1924
  • views
    584208

I reinvented JSON :)

Sign in to follow this  
Aardvajk

1023 views

As always happens when I'm starting a new game, I've started a new level editor. This one, however, is based on my [font='courier new']Gx[/font] and [font='courier new']QGx[/font] frameworks and so I'm hoping to keep it generic enough that it might be the last one I ever have to write (yeah, right) :)

I'm going to need to have human-readable configuration files for the editor and I hate working with XML, especially with the Qt interfaces. I also find it very hard to read. So I've reimplemented an old system I invented years ago as part of the [font='courier new']Gx[/font] framework, called [font='courier new']Gx::Settings[/font].

It is more like JSON than XML. Here's an example of a script:

window{ width = 640; height = 480; maximized = false; controls { title = "This is a literal"; }}levels{ "C:/levels/level1.dat"; "C:/levels/level2.dat"; "C:/levels/level3.dat";}Each entry starts with either an ID (starts with letter or underscore, contains letters, numbers and underscores) or a literal. In the second case, an unnamed node is created with the provided value. In the former case, you have an optional assignment of a literal to the value, then an optional brace enclosed list of entries that become child nodes of the node.

The semicolons are pretty much ignored and are there to aid readability. They are required because two string literals separated only by whitespace are concatenated into one string, so in the "levels" example above, they need to be there.

The code-side interface to this is a recursive [font='courier new']Gx::Settings[/font] object. This is a PIMPL-based class implementing the rule of five:

namespace Gx{class Settings{public: Settings(); Settings(const Settings &s); Settings(Settings &&s); ~Settings(); Settings &operator=(const Settings &s); Settings &operator=(Settings &&s); // snipprivate: class Rep; Rep *rep;};}[font='courier new']Gx::Settings[/font] offers a non-const-only [font='courier new']operator[](const std::string &id)[/font] which either finds or creates a new node. You can also assign a [font='courier new']std::string[/font] to it, which sets its value. There is then a template assign method that allows you to directly assign any supported type.

Similarly there are template [font='courier new']value()[/font] and [font='courier new']canConvert()[/font] methods for retrieving the value directly into a supported type.

It is based upon [font='courier new']Gx::lexical_cast[/font] which uses the standard-library stringstreams for doing conversions, so any type that provides overloads for the stream inseration and extraction operators can be used.


void f(){ Gx::Settings root; Gx::Vec3 v(1, 2, 3); // assume stream operators provided for type root["hello"]["world"] = v; if(root["hello"]["world"].canConvert()) { Gx::Vec3 t = root["hello"]["world"].value(); }}[font='courier new']Gx::Settings[/font] also provides [font='courier new']iterator[/font] and [font='courier new']const_iterator[/font] for traversing its children. From the "levels" example above:


void listLevels(Gx::Settings &root){ for(auto &i: root["levels"]) { std::cout << i.value() << "\n"; }}The child nodes are stored in each parent in the order they are created. Calling the [font='courier new']operator[][/font] with an empty string argument appends an unnamed node to the end of the list.

Finally there is a static method for loading from a stream. The following loads a configuration file and prints it recursively as a tree:

void dump(Gx::Settings &node, int indent){ for(const auto &i: node) { std::cout << i.id() << " = " << i.value() << "\n"; dump(node, indent + 4); }}void f(){ std::ifstream is("data.txt"); if(is.is_open()) { std::string error; Gx::Settings root = Gx::Settings::fromStream(is, error); if(root.empty()) { std::cerr << "Error " << error << "\n"; return; } dump(root, 0); }}This is the entire header for [font='courier new']Gx::Settings[/font] itself. There are also some internal-only files that deal with parsing and constructing the node tree from a file, but they obviously don't form any of the public interface of the library.

namespace Gx{class Settings{public: class iterator { public: iterator &operator++(){ ++index; return *this; } iterator operator++(int){ return iterator(parent, index++); } iterator &operator--(){ --index; return *this; } iterator operator--(int){ return iterator(parent, index--); } iterator operator+(unsigned int s) const { return iterator(parent, index + s); } iterator operator-(unsigned int s) const { return iterator(parent, index - s); } bool operator==(const iterator &o) const { return parent == o.parent && index == o.index; } bool operator!=(const iterator &o) const { return parent != o.parent || index != o.index; } Settings &operator*() const; Settings *operator->() const; private: friend class Settings; iterator(Settings *parent, unsigned int index) : parent(parent), index(index) { } Settings *parent; unsigned int index; }; class const_iterator { public: const_iterator &operator++(){ ++index; return *this; } const_iterator operator++(int){ return const_iterator(parent, index++); } const_iterator &operator--(){ --index; return *this; } const_iterator operator--(int){ return const_iterator(parent, index--); } const_iterator operator+(unsigned int s) const { return const_iterator(parent, index + s); } const_iterator operator-(unsigned int s) const { return const_iterator(parent, index - s); } bool operator==(const const_iterator &o) const { return parent == o.parent && index == o.index; } bool operator!=(const const_iterator &o) const { return parent != o.parent || index != o.index; } const Settings &operator*() const; const Settings *operator->() const; private: friend class Settings; const_iterator(const Settings *parent, unsigned int index) : parent(parent), index(index) { } const Settings *parent; unsigned int index; }; Settings(); Settings(const Settings &s); Settings(Settings &&s); ~Settings(); Settings &operator=(const Settings &s); Settings &operator=(Settings &&s); Settings &operator[](const std::string &id); bool contains(const std::string &id) const; bool empty() const; Settings &operator=(const std::string &value); Settings &operator=(const char *value); template Settings &operator=(const T &t){ return (*this) = lexical_cast(t); } template T value() const { return lexical_cast(val()); } template bool canConvert() const { return can_lexical_cast(val()); } iterator begin(); iterator end(); const_iterator begin() const; const_iterator end() const; std::string id() const; static Settings fromStream(std::istream &is, std::string &error);private: std::string val() const; class Rep; Rep *rep;};}So we have both easily-readable and editable text file sources for defining arbitrary hierachical data and a fairly nice and neat interface for manipulating this data in the host application and a simple way to extend it to support custom types by just requiring stream insertion and extraction operators to convert a custom type to and from a string.

And so [font='courier new']Gx[/font] progresses. Time to get back into the editor now :)

Thanks for stopping by.

Sign in to follow this  


2 Comments


Recommended Comments

Why would you have the window size in the xml and not a flexible var?

Are these default values?

Share this comment


Link to comment

Why would you have the window size in the xml and not a flexible var?
Are these default values?


Just an example, not an actual file. What do you mean, a flexible var?

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!