Elysian Shadows

CompositeStateContainer, Adderall, and More Broken Promises

THE BETA IS IN PROGRESS!:

My good sirs, the Beta is in actuality Toolkit 3.0--in many regards, it's another rewrite. On 5/31/11 at 5:14 PM, I pulled my head out of my ass and began to work on ES again. At this time, I took a look at the Toolkit for the first real time since the middle of February. What I saw both inspired, and dismayed me. I was so pleased at how, finally, it resembled a usable tool. Yet the real visions I had for the Toolkit still seemed so far away. I wanted nothing more than to sit down and hammer out features--it's not longer about code for me, but getting the [i]features [/i]I want implemented--that truly matters. In a fury of blind ambition, I sat down and started to crack some code...

Only to find myself wondering, "C'mon, there's got to be a better way to do this." With that thought, I began searching through Qt examples, tutorials, docs, videos, forums, and everything else I could lay my hands on. With the influx of new Qt knowledge, I suddenly began to realize how powerful the SDK really is. I was dismayed to realize that so many things I'd SLAVED to implement could have been done in a fraction the time with a fraction the code. Yet this was also empowering. 

So I sat down with my dev-notebook and made lists. My hope was to scan in some of my pages like Falco tends to do (as they are quite pristine!), but my scanner is dead...z.z I'll replicate them here.

I began with a desire to rewrite the entire toolkit from scratch. Whereas this is obviously NOT the solution I am now taking, it's important to understand this was the mindset I had with the conception of the Beta. So my notes open with a boldly proclaimed page entitled "Toolkit V. 3.0!" followed directly by a list of simple, clearly stated goals:

Goals:

- to restructure the current Toolkit in a more reliable and robust fashion

- to fully take advantage of the framework Qt provides

- to get back up and running in a timely fashion

- to create an extensible design easy to deal with

Next, I simply had to clear my mind of all the obscure half-understandings and preconceptions I had in terms of design. Obviously the way I was approaching development was drastically different than what I found in the numerous examples and tutorials...But why? Which was better? Where do I start? How can I prevent myself from in the future coding something that does its job properly, yet does not give way to future development as it should?

Obviously these are questions all programmers face, and the niggas at Qt knew what they were doing. I understood this, and I had a broad understanding of what Qt had to offer...What I needed was a focus! So next comes a list entitled:

[size=20]What I'd Love to See:[/size]

- Undo/Redo Framework fully utilized

- Extensibility using Plugins

- Lost data restoration upon crash

- Clipboard, drag-n-drop, intuitive resource handling

- Overlays + Animation framework utilized

- Uniform debug logging

- Model-View-Controller paradigm properly utilized

- Quality performance on each OS and test machine

Note the title--what I'd "love" to see--as opposed to what actually needs to be done. With this list in mind, I started my development again. I took some time to read up on each of the above (to gain a general understanding of what kind of work would be involved)...Then presented my ideas to Falco. His response?

BITCH, YOU'RE NOT REWRITING THE TOOLKIT AGAIN.

^ Words which I have come to regard as the fundamental principal of my current development process. Once I'd fully reflected on their implications, I came to realize how detrimental another rewrite really would be, as well as how [i]unnecessary[/i]. What we need is a reliable Toolkit, not more code.

With this understanding, I then set for myself a course of action. This was in the form of a sequential 3-pronged:

1) RELEASE AND POST WHAT I ALREADY HAVE DONE, EXACTLY HOW IT IS.

That's the post I made the other day, and why there was so much disparity when you tried to build it yourself, dandy. "Does it compile? K, good, now lets examine it from a user's standpoint." <---All the work that went into this release, and all the work that should go into ALL Alpha "updates."

2) Begin working on my ambitious "BETA" [i]ASYNCHRONOUSLY [/i]with the Alpha. 

This is what's so beautiful. All features missing from the Alpha will obviously need to be in the Beta...And, simultaneously, the beta is supposed to be composed entirely out of modular, easy to extend code. So, instead of a rewrite from the ground up, it seems much more obvious that my entry point should be implementing core functionality missing from the Alpha. If each new feature I give give the Alpha from here on out is designed with the goal of the Beta in mind, I can then ignore the working components in the Alpha and focus entirely on what work must be done to implement said feature, as well as how to make this work standalone. So, with this understanding, I hope you'll come to realize that the Beta is both a [i]paradigm [/i]for the rest of the Alpha's development, as well as a [i]Goal[/i]. NOT a rewrite. 

3) Begin working on articles for the Toolkit. 

These are to be for the new website. I originally was interested in writing "ES Level Editor: Iterations!"

This would be an article describing definitively the various forms of the level editor, the logic behind each, as well as technical details for those interested. '

Whereas I feel this is still a good idea, I'm not yet to this 3rd goal...So who knows what article I'll feel like making first. :P It sure as shit feels good to be back in the swing of things.

WHERE AM I NOW?

Working on the beta, of course! As I previously stated, my top priority atm is to fix the "Directory View." 

Whereas this is a simple process in and of itself, let's not forget that the Beta is an entirely new development paradigm, as well as a series of goals. In order to make the "Directory View" work, I've come to realize part of the problem is that the data it represents is not accurately encapsulated. Not only this, but let's also look from a user's standpoint at what the Directory View is supposed to do: 

- facilitate changing projects, levels, areas

- allow basic editing, such as changing project/level/area name

- replicate how the project is supposed to look on the local disc drive

- "intuitive" drag-n-drop functionality to allow for addition/removal of projects/levels/areas

Most of these actions have absolutely NOTHING to do with the actual TreeView (afterall, it is only a VIEW). In order to implement the features I want in the Directory View, I need to examine all of the related code. I need to make sure that the Directory View is completely stand-alone, modular, re-usable, made using Qt's paradigm properly...And, doing this, I'm able to clearly identify a long chain of processes that are currently inadequate. 

Whereas I will not get into WHY it is, (Falco would be better suited to explain the back-end workings of this process than I would), I have identified and begun working to rectify said processes.

My work starts with the concept of Project, Level, and Area. I've always known these three things had commonalities; yet their differences were such that I was unable to actually create a form of abstraction. "They all have a Name, Project and Level both have kids, they all have data...." wasn't enough to justify the introduction of a new abstract layer. Sooo similar, yet so different. 

It wasn't until we try to implement some much more advanced features that these commonalities become apparent. WITHOUT some form of abstraction, the redundancy code, overhead, and sheer foresight necessary to accomplish a task is simply daunting. The best part? It's physically NOT POSSIBLE to have forseen this--our design decisions were always the most intelligent with the given problem. Considering the "Beta" as both a goal and paradigm, however, the "given problem" is DRASTICALLY changed. Two ways to look at the same thing; the code will work either way, but an extra abstract layer will allow me to implement the necessary features and keep rewrites to a minimal. 

SOOOOOOOOOOOOOOOOOOOO.....

So conclude this post, allow me to show you some code! I'm happy with the shape it's taking, and will want to draw your attention to several details. 

What I've implemented I have called (for lack of a better name) a CompositeStateContainer

The implementation is nowhere near done. However, I have finally finished a standalone basis, and I hope to be done with visible results in the next several days. 

/* This class is intended to abstract away the common functionality between Project, Level and Area, as well as to define their hierarchical relationship, utilize the signal/slots paradigm, and facilitate document syncronization. */ //Marcel class CompositeStateContainer : public QObject {     Q_OBJECT     Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) signals:     void nameChanged(QString); public:     CompositeStateContainer();     void setName(const QString &name);     QString getName();     bool childNameAvailable(QString name); public slots:     void testStub(QString n){qDebug() << "In the test stub!";} protected:     bool _requestName(QString _name); private:     QString _name; }; QDebug operator<<(QDebug dbg, CompositeStateContainer &csc); 

Believe it or not, this simple class is revolutionary when compared to the structure of the rest of the Toolkit. Why? Because it utilizes FULLY the framework supplied by Qt.

First, notice the use of the Meta-data system. This is absolutely VITALLY important when it comes to implementing undo/redo, save-state, auto recovery, plugins, animation framework. 

Q_OBJECT Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) 

Properties are much like "attributes" in C#. They are NOT implemented with RTTI--the MOC changes this directly into standard C++ using some cool c-style macros (yeah, in order to make this simple class, I did have to learn exactly how all that shit's done.) I have discerned that, in order to always play nice within Qt's various frameworks, using the base "QObject"'s ability to contain a parent and children is the best approach to represent the nature of a Project/Level/Area. However, I need to adapt it to better suite our problem.

Consider:

1) A project must contain atleast one Level to function within the Toolkit 

2) A level must contain atleast one Area to function within the Toolkit

3) Two levels cannot have the same name within a Project

4) Two areas cannot have the same name within a Level

5) Each project, level, and area correspond to actual folder on the local disc

6) Each Project/Level/Area has their own separate loading/saving mechanism and unique attributes.

7) A level must become selected before allocations for tilesheets/objectsheets are to commence

8) an area requires the associated level to have allocated tilesheets/objectsheets before it can be selected

9) an area must become selected to be rendered

As I have stated, I am not yet done with the Implementation of  CompositeStateContainer. By the time I am, I will have all 10 bullets above implemented, and probably will have discovered more. However, lets take a look at what I have done. The key is to define ALL of these interactions in ONE abstract manner, without ANY knowledge outside of your own independant attributes. 

WELCOME TO PROGRAMMING, KIDS.

Focus on bullets 3 and 4. 5. I have enforced these policies utilizing Qt's framework to the fullest extent. 

These bullets require the following concepts:

-parenting

-the idea of a "name"

-location in a hierarchy influencing available names

-limited name availability

-further restricted name boundaries: only valid folder names are a valid name

So, a look at the magic:

#include  #include  #include  #include  #include  #include "composite_state_container.h" CompositeStateContainer::CompositeStateContainer() { } bool CompositeStateContainer::_requestName(QString _name) {     CompositeStateContainer *p=             qobject_cast(parent());     return ( !p ) ? true : p->childNameAvailable(_name); } bool CompositeStateContainer::childNameAvailable(QString name) {     QList children =             findChildren(name);     return (children.isEmpty()); } void CompositeStateContainer::setName(const QString &name){     qDebug() << "in setObjectName for " << getName();     if(_requestName(name)) {         emit( nameChanged(name));         Q_ASSERT_X( !name.contains(QRegExp("[^a-zA-Z0-9_]")),"name","Invalid name!" );         _name=name;     } else {         qWarning() << "CompositeStateContainer namechange failure! "                       " From " << this->getName() << " to " << name;     } } QString CompositeStateContainer::getName() {     return _name; } QDebug operator<<(QDebug dbg, CompositeStateContainer &csc ){     qDebug().nospace() << "n"     "n"     "--+n"     "----objectName: " << csc.getName() << "nn"     "--+";     csc.dumpObjectTree();     qDebug().nospace() << "n"     "--+";     csc.dumpObjectInfo();     qDebug().nospace() <<     "";     return dbg.space(); } 

Lets start off by looking at the abstract, magical 

bool CompositeStateContainer::_requestName(QString _name) {     CompositeStateContainer *p=             qobject_cast(parent());     return ( !p ) ? true : p->childNameAvailable(_name); } 

Reading this monstrosity in English is:

"Cast my parent pointer to another CompositeStateContainer. If you give me back null, then either  1) my parent was null (not set) OR 2) the cast failed--my parent is NOT a CompositeStateContainer. Either way, i don't give a fuck--we hereby logically establish no restrictions on name when an object has no parent or has a parent of the wrong type. If I did NOT get null back, then ask my parent object if the requested name is available."

Well, since these objects are all CompositeStateContainer, lets looks at how you "ask a parent":

bool CompositeStateContainer::childNameAvailable(QString name) {     QList children =             findChildren(name);     return (children.isEmpty()); } 

Well, that's simple as shit: "search my child objects for an object of type "CompositeStateContainer" with the given name. Return the results as a list. Check if the list is empty--if it is, the name's free. Otherwise, it's not."

Wheeeeeeel. These two interactions together allow us to have two of the same objects that, based on their connection, disallow certain names. 

Feel like I just got off on a giant tangent? What's any of this got to do with the Directory View?

Well, part of the problem with the view is that objects have funny rules restricting their names in same cases, but not in others. The way we'd previously defined this connection was unique to each Project, Level, and Area--nothing universal. Why should it be? That's a simple as shit task....

Well, suddenly we need to synchronize the name of earch Project, Level, and Area with the label displayed in the tree. If you change the label in the tree, the actual name changes, and vice versa. That means this same restrictive relationship MUST be established within the TreeView as well as within the objects...Unless somehow, magically, the tree "automatically" knows to update anytime a name is changed. 

Falco's original solution was a pretty standard one: A Project/Level/Area "is a" TreeNode in addition to its other types. This means inheritance--now we can sync the Tree's nodes within the actual Project/Level/Area rather than redefining the given relationship. 

This solution breaks down, however, when you want to treat the TreeNodes independently  of the Project/Level/Area. Lets say the user types something fucking stupid into the node's field: "()89807*(()*()*()*()*)*" Obviously, we cannot name a file this. The field still temporarily had this text..The project/level/area IS A field...That means for a moment, just until a validator is called, the name of the project was "()89807*(()*()*()*()*)*", which breaks the system. How do we fix? Hax--made some intermediary step between setting the actual field, and use accessors....But wait! Now we've introduced a new problem: the TreeView must now somehow know what data it is representing. In order to call an accessor, it must know what object it's accessing. This is no longer "stand alone" or "modular." 

This is a very common problem with GUI programming, and there's a shitton of different solutions. What's yours? Qt and I prefer our method:

IN COMES SIGNALS , SLAWTS, AND PROPERTIES.

This situation (hopefully) helped to illustrate the depth and complexity a simple design choice, such as "how do I set my object's "Name" and have it display?"" In addition, this is a classic example of when Signals, Slots, and properties should be the design answer.

Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) 

We've declared to the MOC's meta data system that a new property exists. Properties are used all over in Qt--you can set things up so that when you change your property a unique event is emitted and processed by Qt's main event structure, you can establish a default/reset value, allow notifications when said property is changed, etc. By using this paradigm to solve the problem I've detailed, magically my code fits into the entire Qt event system. Should run more efficiently than manually coding intermediary steps, inheriting, etc. Qt's "state" system (my next post will be all about this) can now work properly with your property.

In this case, I've defined a "property" called "name." This is so that if I were to create a "plugin" in the future and not know what type of object I'm referrencing, I can still check for it's name:

object->setProperty("name", "level 17"); 

(note I'm using object (of type QObject), not of type CompositeStateContainer). My TreeView no longer need to know how to ask my Level for its name via accessors! =D 

Not only this, but the only way to change this property is through 

Q_PROPERTY(  ... WRITE setName ... 

(I can now validate, like this:)

void CompositeStateContainer::setName(const QString &name){     qDebug() << "in setObjectName for " << getName();     if(_requestName(name)) {         emit( nameChanged(name));         Q_ASSERT_X( !name.contains(QRegExp("[^a-zA-Z0-9_]")),"name","Invalid name!" );         _name=name;     } else {         qWarning() << "CompositeStateContainer namechange failure! "                       " From " << this->getName() << " to " << name;     } } 

1337 regular expressions, properly used debug system, Q_ASSERT macro to aid in debuggery...hellll  yea. 

And, finally, EVERY SINGLE TIME THIS PROPERTY IS CHANGED FROM ANYWHERE, even from objects that have no bussiness accessing my name or have no idea what object I am, the signal:

emit( nameChanged(name)); 

as defined by the property flag:

Q_PROPERTY(  ... NOTIFY nameChanged ...  

is "emitted.' All "listeners" (things connected to this signal) are notified--in this case, TreeView can redraw itself appropriately (IF the nature of the TreeView decides it WANTS to, as it's still independent of the actual project/level/area!)

[size=15]COOL. BUT YOU HAVE NOT EXPLAINED WHY THIS NEW ENTITY IS CALLED A -STATE- CONTAINER.[/size]

That's right, and that'll require a muuuch more detailed post than this. I'm tired, it's 3:00 am, I haven't started reviewing for my math test........

But for a dick tickler: I've figured out how to use Qt's "state machines" to implement all of our project/level/area's desired logic. This will allow us to extend their capabilities via plugins later (should we want) and to implement undo/redo, stay in syn without effort on us (the programmer)'s part, and overall just be Qt Badasses.

OH I NOTICED DEBUGGERY HUR DUR!

QDebug operator<<(QDebug dbg, CompositeStateContainer &csc ){     qDebug().nospace() << "n"     "n"     "--+n"     "----objectName: " << csc.getName() << "nn"     "--+";     csc.dumpObjectTree();     qDebug().nospace() << "n"     "--+";     csc.dumpObjectInfo();     qDebug().nospace() <<     "";     return dbg.space(); } 

yup. if you have debug mode enabled, you can now do:

qDebug () << "My level is fucked up! Take a look: " << myLevel; 

i'll explain the cool debug system later, but for now know that using the same style as you have always will work, as will using qDebug() directly. We'd tried to do some cute stuff and implement our own message/debug system with cool flags (critical, fatal, normal)...But Qt's already implemented this. Use qCritical()<< qFatal()<< and just check qt docs for more info. ;p

GOOD NIGHT GENTLEMEN! ;p

Discussion Topic

Falco Girgis
Falco Girgis is the founder and lead software architect of the Elysian Shadows project. He was previously employed in the telecom industry before taking a chance on Kickstarter and quitting his job to live the dream. He is currently pursuing his masters in Computer Engineering with a focus on GPU architecture.