Last week, I posted about using an ‘Object’ class to encapsulate the variable-typed arguments for ResultActions. You guys posted some awesome feedback and I used it to improve the class. First, I renamed the class to ‘SingleValueContainer’ so users have a better sense of what it is. Second, following Fuzzie’s advice, I put all the values except for String, directly in the union. It’s the same or less memory cost and results in less heap allocations.
union { bool boolVal; byte byteVal; int16 int16Val; uint16 uint16Val; int32 int32Val; uint32 uint32Val; float floatVal; double doubleVal; char *stringVal; } _value;
You’ll notice that the stringVal isn’t actually a Common::String object, but rather a pointer to a char array. This saves a bit of memory at the cost of a couple strlen(), memcpy(), and String object assigment.
SingleValueContainer::SingleValueContainer(Common::String value) : _objectType(BYTE) { _value.stringVal = new char[value.size() + 1]; memcpy(_value.stringVal, value.c_str(), value.size() + 1); }
SingleValueContainer &SingleValueContainer::operator=(const Common::String &rhs) { if (_objectType != STRING) { _objectType = STRING; _value.stringVal = new char[rhs.size() + 1]; memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); return *this; } uint32 length = strlen(_value.stringVal); if (length <= rhs.size() + 1) { memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); } else { delete[] _value.stringVal; _value.stringVal = new char[rhs.size() + 1]; memcpy(_value.stringVal, rhs.c_str(), rhs.size() + 1); } return *this; }
bool SingleValueContainer::getStringValue(Common::String *returnValue) const { if (_objectType != STRING) warning("'Object' is not storing a Common::String."); *returnValue = _value.stringVal; return true; }
With those changes the class seems quite solid. (The full source can be found here and here). However, after seeing Zidane Sama’s comment, I realized that there was a better way to tackle the problem than variant objects. Instead of trying to generalize the action types and arguments and storing them in structs, a better approach is to create a class for each action type with a common, “execute()” method that will be called by the scriptManager when the Criteria are met for an ResultAction.
I first created an interface base class that all the different types would inherit from:
class ResultAction { public: virtual ~ResultAction() {} virtual bool execute(ZEngine *zEngine) = 0; };
Next, I created the individual classes for each type of ResultAction:
class ActionAdd : public ResultAction { public: ActionAdd(Common::String line); bool execute(ZEngine *zEngine); private: uint32 _key; byte _value; };
The individual classes parse out any arguments in their constructor and store them in member variables. In execute(), they execute the logic pertaining to their action. A pointer to ZEngine is passed in order to give the method access to all the necessary tools (modifying graphics, scriptManager states, sounds, etc.)
class ResultAction { ActionAdd::ActionAdd(Common::String line) { sscanf(line.c_str(), ":add(%u,%hhu)", &_key, &_value); } bool ActionAdd::execute(ZEngine *zEngine) { zEngine->getScriptManager()->addToStateValue(_key, _value); return true; }
Thus, in the script file parser I can just look for the action type and then pass create an action type, passing the constructor the whole line:
while (!line.contains('}')) { // Parse for the action type if (line.matchString("*:add*", true)) { actionList.push_back(ActionAdd(line)); } else if (line.matchString("*:animplay*", true)) { actionList.push_back(ActionAnimPlay(line)); } else if (.....) . . . }
While this means I have to create 20+ classes for all the different types of actions, I think this method nicely encapsulates and abstracts both the parsing and the action of the result.
I’m a bit sad that I’m not going to be using the ‘SingleValueContainer’ class, but if nothing else, I learned quite a bit while creating it. Plus, I won’t be getting rid of it, so it might have a use somewhere else.
This coming week I need to finish creating all the classes and then try to finish the rest of the engine skeleton. As always, feel free to comment / ask questions.
-RichieSams