Another Form of DataData paired with an instruction is all well and good for allowing flexibility on a per-instruction basis. But what about flexibility between instructions? In order to achieve this, we need data that is accessible by all instructions, for reading and possibly writing. This data is therefore reasonably placed at the level of a running script. The ownership of this data should be dealt with carefully. Unlike an Instruction's data, we would like this new data to be write-able in addition to being readable. If the ownership is carelessly placed at the hands of a script, then issues may arise when trying to enhance the features your system is capable of, such as when implementing some type of pseudo-multi-processing (parallel execution of scripts). This is because any changes to the script data in one "process" will affect any other "processes" running this same script. For this reason, we would like to abstract a script's executional state. If and when we do implement such a feature, we can safely create executional states for each process being run. This script state will own the variable data we'd like to use, while the script itself will merely store a count describing how much data it needs when executing. The script state should also include some utilities for manipulating this data, otherwise what's the point of having it? Our class may look something like this: // a script's executional state class ScriptState { public: // initialization void SetDataSize(size_t varCount) { _varData.resize(varCount); } // data access void SetVar(size_t i, char val) { _varData[i] = val; } char GetVar(size_t i) const { return _varData[i]; } const std::vector<char>& DataArray() const { return _varData; } private: std::vector<char> _varData; }; For current demonstrative purposes, char variables will be sufficient. Variables can be set or retrieved by index. If you'd like, you can even retrieve the data in a semi-string form. Keep in mind that it isn't necessarily null-terminated, however. An aside regarding organization:
Now, to make use of this in our VirtualMachine class, we will simply add a ScriptState as a data member. At the moment, since we aren't dealing with parallel executions of scripts, we can get away with this. Later, when implementing this parallel script execution, we will have to relocate this member. For now, to make use of it, we simply initialize its data size at the start of execution: void VirtualMachine::Execute(size_t scriptId) { SelectScript(scriptId); // select our _instrPtr by script ID // initialize variable data _curState.SetDataSize(_scriptPtr->VarCount()); _instr = _instrPtr; // set our iterator to the beginning . . . } A Helpful ToolBefore we go on to make any new instructions to play around with this variable data, we should take care of one minor, yet very crucial thing. As anyone who has ever had to debug his or her code should know, the debugging process can be a real pain. Utilities to aid in debugging can help a great deal, so we should definitely have a utility built to view the data values stored in a ScriptState at any given time. Something like this should suffice for now: void ExposeVariableState(const ScriptState& state) const { std::vector<char>::const_iterator itr; int n = 0; // used to denote indexed position of value for (itr = state.DataArray().begin(); itr != state.DataArray().end(); ++itr, ++n) { std::cout << n << ": "; std::cout << static_cast<int>(*itr); // cast for numeric value std::cout << std::endl; } } Little things like these can save you a lot of trouble later on when you just can't seem to get a script to work correctly.
|