Untitled

posted in Beals Software
Published September 13, 2009
Advertisement
Gah, it's been almost a month again.

I've been working a lot over the past 3 weeks though, so I kind of have an excuse (not really, seeing as how no matter how long I work at my job, I still come home and put in between 4-12 hours programming; depending on the day (I average about 6 hours programming time every night.)

Anyway, the only thing I've accomplished code-wise is...well, really nothing. I stripped my string utility functions out of my code base (because I discovered boost::algorithm::string, which replaced pretty much every one of them.)


While screwing off with little ideas that pop in my head (like I usually do), I can up with an enum-value class and a flag-value class (trying to 'fix' the enum situation in C++ is something I keep coming back to; for some reason I find it fun. Plus, I learn lots of little quirks of the language while doing so.)

Here it is in all of its (horrid, horrid) glory:
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=namespace dbeals{    //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=    template     struct EnumValue    {        EnumerationType Value;        EnumValue()        {        }        EnumValue(EnumerationType Value)        {            this->Value = Value;        }        EnumValue(const string &Name, EnumerationType Value)        {            this->Value = Value;            std::pair::iterator, bool> Result = EnumValue::_container().insert(std::make_pair(Value, Name));            if(!Result.second)                throw std::exception((format("The enum value '%1%' has already been registered with the name '%2%'.") % Value % Result.first->second).str().c_str());        }        const string &ToString() const        {            std::map::iterator Itor = _container().find(Value);            if(Itor == _container().end())            {                try                {                    static string Dummy = "";                    Dummy = boost::lexical_cast(this->Value);                    return Dummy;                }                catch(boost::bad_lexical_cast)                {                    throw std::exception((format("Failed to cast the value '%1%' to a string.") % this->Value).str().c_str());                }            }            return Itor->second;        }        static EnumerationType Parse(const string &Text)        {            string Copy = String::trim_copy(Text);            for(std::map::iterator Itor = _container().begin(); Itor != _container().end(); ++Itor)            {                if(String::equals(Copy, Itor->second))                    return Itor->first;            }            throw std::exception((format("The enum-value '%1%' does not exist in this enumeration.") % Text).str().c_str());        }        operator EnumerationType() const        {            return this->Value;        }        const EnumValue &operator =(const EnumValue &Value)        {            this->Value = Value.Value;            return *this;        }        const EnumValue &operator =(EnumerationType Value)        {            this->Value = Value;            return *this;        }    private:        static std::map &_container()        {            static std::map Enum;            return Enum;        }    };    //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=    template     struct FlagValue    {        EnumerationType Value;        FlagValue()        {        }        FlagValue(EnumerationType Value)        {            this->Value = Value;        }        FlagValue(const string &Name, EnumerationType Value)        {            this->Value = Value;            std::pair::iterator, bool> Result = FlagValue::_container().insert(std::make_pair(Value, Name));            if(!Result.second)                throw std::exception((format("The enum value '%1%' has already been registered with the name '%2%'.") % Value % Result.first->second).str().c_str());        }        string ToString() const        {            string Output = "";            for(std::map::iterator Itor = _container().begin(); Itor != _container().end(); ++Itor)            {                if(this->Value & Itor->first)                {                    if(!Output.empty())                        Output += "|";                    Output += Itor->second;                }            }            return Output;        }        static EnumerationType Parse(const string &Text)        {            std::vector Tokens;            String::split(Tokens, Text, compare_with('|'));            EnumerationType Output = (EnumerationType)0;            string Copy;            for(std::vector::iterator StrItor = Tokens.begin(); StrItor != Tokens.end(); ++StrItor)            {                for(std::map::iterator Itor = _container().begin(); Itor != _container().end(); ++Itor)                {                    if(String::equals(String::trim(*StrItor), Itor->second))                        Output |= Itor->first;                }            }            return Output;        }        operator EnumerationType() const        {            return this->Value;        }        FlagValue operator |(const FlagValue &Value) const        {            return FlagValue(EnumerationType(this->Value | Value.Value));        }        const FlagValue &operator |=(const FlagValue &Value)        {            this->Value |= Value.Value;            return *this;        }        FlagValue operator |(EnumerationType Value) const        {            return FlagValue(EnumerationType(this->Value | Value));        }        const FlagValue &operator |=(EnumerationType Value)        {            this->Value |= Value;            return *this;        }        FlagValue operator &(const FlagValue &Value) const        {            return FlagValue(EnumerationType(this->Value & Value.Value));        }        const FlagValue &operator &=(const FlagValue &Value)        {            this->Value &= Value.Value;            return *this;        }        FlagValue operator &(EnumerationType Value) const        {            return FlagValue(EnumerationType(this->Value & Value));        }        const FlagValue &operator &=(EnumerationType Value)        {            this->Value &= Value;            return *this;        }        const FlagValue &operator =(const FlagValue &Value)        {            this->Value = Value.Value;            return *this;        }        const FlagValue &operator =(EnumerationType Value)        {            this->Value = Value;            return *this;        }    private:        static std::map &_container()        {            static std::map Enum;            return Enum;        }    };}


I made a couple examples using some macros to make it easier to read:
namespace BuildTypes{    enum enum_type { };    db_EnumValue(enum_type, DebugBuild);    db_EnumValue(enum_type, ReleaseBuild);} db_DeclEnumType(BuildTypes::enum_type, BuildType);int main(){    BuildType MyBuildType;#if defined(_DEBUG) || defined(DEBUG)    MyBuildType = BuildTypes::DebugBuild;#else    MyBuildType = BuildTypes::ReleaseBuild;#endif    std::cout<    return 0;}


And some flags:
namespace BuildOptions{    enum enum_type { };    db_FlagValue(enum_type, IncludeComments);    db_FlagValue(enum_type, IncludeWarnings);    db_FlagValue(enum_type, IncludeErrors);} db_DeclFlagType(BuildOptions::enum_type, BuildOption);int main(){    BuildOption Options;    Options = BuildOptions::IncludeComments | BuildOptions::IncludeErrors;        BuildOptions ParseTest1 = BuildOption::Parse("IncludeComments");    BuildOptions ParseTest2 = BuildOption::Parse("includeComments"); // Fails; case-sensitive    BuildOptions ParseTest3 = BuildOption::Parse("IncludeComments | IncludeWarnings|IncludeErrors"); // Works fine; spaces are trimmed        std::cout<"\n"
;
std::cout<<(BuildOptions::IncludeWarnings | BuildOptions::IncludeErrors).ToString()< return 0;
}



This has pretty much everything wrong with it that wrapping intrinsic types does, plus probably a bunch of other stuff. One of the other things I note is I'm pretty sure the parse method has a large chance of failing (seeing as how I register the values in their constructor and I'm pretty positive I'm not garaunteed that all of these are constructed when I'm wanting them to be.)
The ToString() and Parse() methods actually aren't too slow though; at each interval of iterations it ran only a fraction of the time longer (less than a quarter of a second.)

I honestly wish it didn't have the problems I'm pretty sure it has, because I'd stick with it then. Either way, I had fun doing it and learned a couple things, so the time wasn't COMPLETELY wasted (ok, it was; shut up [razz].)


I spent the other night trying to come up with a nice content system; I pretty much failed. I had something similar to XNA's content system, but shittier. Plus, it's kind of hard to emulate the OpaqueData parts (I can, and it's pretty type-safe as long as I use RTTI and typeid(), but that slows it down.) I'm thinking about having another go at it, but using boost::any instead of a byte-storage class.


Another lab-project I've been working on is a robust buffer system. The idea behind it was coming up with a buffer class that I could easily use to create/manage memory pools (error buffers, serialization, etc), read the contents of a file (deserialization), stream linear chunks from a file (serializing/deserializing lists), and stream chunks from a file in a grid-based manner (streaming maps.) Pretty much just trying to come up with some generalized solutions for some of the things I've thought about implementing into a game (or know I'll need in a game; like serialization.)


Figured out some of the things I've been curious about (getting the name of the operating system, enumerating disk-drives, calculating available disk-space, calculating available memory, and calculating processor speed.)


Lol, I always look back at what I've done over a period of time and think to myself "If I spent half of the energy on making a game that I put into screwing around ('learning stuff'), I'd have finished SOMETHING by now." Most of my energy seems to go into writing frameworks and wrapping shit.

On the up-side, the design document for The Alchemist is almost finished and after tonight (Sunday night), I have 4 days off. I'm going to try really hard to get the GDD finished and even harder to get the TDD done before I head back to work.

Too much blathering and straying and not enough sleeping; I've been up for about 20 hours and it's time for bed. Gnight!
0 likes 2 comments

Comments

Slather
The RTTI and typeid() bit sounds like a form of reflection. I often wonder if it's possible/worth it to do in C++, as apposed to writing a language (C#) that has it built-in.
September 14, 2009 02:33 AM
Programmer16
Quote:Original post by Slather
The RTTI and typeid() bit sounds like a form of reflection. I often wonder if it's possible/worth it to do in C++, as apposed to writing a language (C#) that has it built-in.


Yea, reflection was pretty much was I was aiming at (simplified, of course.)

I have no doubt that it's possible to do in C++ (either by altering the language or rewriting EVERYTHING yourself, wrapping stuff, etc to use some sort of system), the question would be: is it worth the loss in efficiency/speed? (This question can be looked past however, seeing as how RTTI is only an option and not a requirement.)

However, AFAIK, reflection in C# and such are based around the 'object' hierarchy and as such, in C++, you would pretty much loose type-safety (if object pointers were used) or speed/efficiency (if object instances were used; this one also looses type safety though, unless implemented via something like boost:any.)

C++0x might change the latter though; via move constructors.

I've only really seen a two good uses for reflection (in game code): template programming and serialization. I've seen more abuse out of it than anything though. Take what I was trying to do for an example; reflection would have suited it perfectly, but was a major abuse and wouldn't have actually given me what I wanted (well, it would have, but it would have been bloated.) The main reason is because what I really wanted wasn't reflection, it was type-safety.

A little better explanation is that, I didn't want an object that could store an arbitrary type of data which I could retrieve (heterogenous is the term, I think.) What I want is an object that can store specific data of an arbitrary type that would be set and only need to be know during programming-time.

An example of each:

// NOT what I wanted
void Foo(OpaqueData &Data)
{
    Data = 10L; // Storing arbitrary data with the type long.
}

OpaqueData Data;
Foo(Data);
if(Data.IsOfType<string>())
{
    // do something
}
else if(Data.IsOfType<int>())
{
    // do something else
}



While this is pretty much a ultra-simplified version of reflection and gives me what I want, it's bloated. I say it's bloated because this class, by definition, is used in the specific form "I put this data in it and, somewhere else in the program, either I or someone using it takes that data out."

// What I wanted
void Foo(OpaqueData &Data)
{
    Data = 10L; // Storing arbitrary data with the type long.
}

OpaqueData Data;
Foo(Data); // Foo, by definition sets Data to a long. The documentation for the function should state as such.

long Result = Data.CastTo<long>();


Specific data is put in, and attempted to be taken out. It doesn't matter what type of data is put in, just that it is a specific type (that is well document.

void Foo(OpaqueData &Data)

Parameters:
    Data : A reference to an OpaqueData object where the information will be stored. When Foo returns, Data will contain a long.



Gah, I'm over-complicating this lol (made even worse by the fact that my examples aren't even a good situation for using this type of class.) Simply put:
what I had: Put any data in, act differently according to what type was entered.
What I want: Put specifc data in, try to retrieve it, fail if I can't (because it's of the wrong type.)

Anyway, I'm in the middle of writing a new entry that has the new version that I got working last night and uses better examples for the intended usage.

Sorry, I wandered way off there lol. In the end, I think reflection would be great in C++, especially when it comes to template programming, but I don't see it happening for a couple reasons:
1) The complexity of the language would grow exponentially (from the compiler point of view anyway)
2) There would undoubtedly be a huge increase in compile-time
3) Modules that need true reflection (instead of just type-safety) are really more suited for a different language, like C#, anyway.

The exception to rule 3 being templates, but by enhancing templates and template specialization, I don't think we would need reflection at all really.
September 14, 2009 05:05 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement