#ifndef __GridInstanceData_h_ #define __GridInstanceData_h_ #include #include #include /* This handles the part of the GridInstance class which is just a data * structure. This code was pulled out of the GridInstance class and the * GridReaderBase.[Ch] files to make the code easier to read and easier to * test. * * These are all implementation details. They have no interest to anyone who * is not modifying GridReaderBase.[Ch]. * * GridInstanceDataTest.C contains the test code that goes with this unit. */ namespace GridInstanceData { // This contains a list of elements of type T. The list always starts at 0. // You can easily add new items at the end, one at a time. You can easily // truncate the list; the first N items stay the same but items after that // are all deleted. This supports a simple version of a transaction. This // keeps track of two versions of the list. One is the last committed // version. The other is the version we are changing. template < class T > class TransactionList { public: // This is a simple read only list. Indexes start at 0. class IList { public: virtual int getCount() const =0; // Precondition: index >= 0 and index < getCount(). If you call getAt() // and the precondtion is not true, this might fail an assertion, throw // an exception, return a semi-random number, etc. virtual T getAt(int index) const =0; }; private: // _main represents the list of committed items. std::vector< T > _main; // These are the recently added and not yet committed items. std::vector< T > _new; // This says how many items are the same in the committed list and the // in progress list. int _commonCount; class Committed : public IList { private: TransactionList &_owner; public: Committed(TransactionList &owner); virtual int getCount() const; virtual T getAt(int index) const; }; const Committed _committed; class InProgress : public IList { private: TransactionList &_owner; public: InProgress(TransactionList &owner); virtual int getCount() const; virtual T getAt(int index) const; }; const InProgress _inProgress; public: IList const &committed() const { return _committed; } IList const &inProgress() const { return _inProgress; } void append(T value); void deleteExcept(int count); void commit(); void rollBack(); TransactionList(); }; // Each cell in the table is stored in here. This is stored in standard // English reading order. Cell 0 refers to the first column in the first // row. Cell 1 refers to the second column in the first row. If there are // n columns, then cell n is the first column in the second row. typedef TransactionList< double > Cells; typedef TransactionList< int > PackedToPossible; // This is a simple wrapper around the Cells::IList type. This is, more or // less, what's available to the execution engine. Note that you can export // either the committed cells or the in progress cells through a grid. The // exact same functionality is available to either list. class Grid { private: Cells::IList const &_cells; const int _width; bool tooHigh(int row, int column) const; int maxRowForColumn(int column) const; // This assumes the inputs have already been vetted. Use reference() if // you are not sure if the inputs are good or not. double get(int row, int col) const; public: static double invalid(); Grid(Cells::IList const &cells, int width) : _cells(cells), _width(width) { } // If the indexes are out of range, this will return NaN. Otherwise this // will find the value in the grid and return that. double reference(int row, int column) const; // firstRow is higher in the table, i.e. older, i.e. a lower index. // Note that these are indexes and 0 is the first row. That's different // from what the end user sees. The end user says 0 to mean the current // row and 2 to mean two rows above that. Here 0 means the top row and 2 // means 2 rows below that. double sum(int firstRow, int lastRow, int column, bool skipBadValues) const; double count(int firstRow, int lastRow, int column) const; double average(int firstRow, int lastRow, int column, bool skipBadValues) const; double max(int firstRow, int lastRow, int column, bool skipBadValues) const; double min(int firstRow, int lastRow, int column, bool skipBadValues) const; void getValuesAll(std::vector< double > &result, int firstRow, int lastRow, int column) const; // The next piece of data goes into this row and column. int nextCol() const; int nextRow() const; // This is the number of rows with at least one cell filled in. int partialRowCount() const; }; enum class Use { InProgress, Committed }; enum class Round { Down, Up, Fail }; static const int NOT_FOUND = -2; // This takes care of the packed attribute. Packed is only used to decide // where to start working. Once you start on a specific row, you typically // walk through the rows in the Grid one at a time. // // Threading notes: // This does not contain any locks. It is up to the caller to add // appropriate locks. The working copy and the committed copy are totally // separate. Any number of changes can be made to the working copy while // other threads are reading the committed copy. Expected use: Any call // to read the committed copy or to update the committed copy via commit() // will be protected by a single linux mutex. GridInstance::tryWriteLock() // will protect an entire session of reading and updating the inProgress // data. You will acquire the linux mutex right before calling commit(), // while still holding the write lock. class PackableValues { public: // We return a complicated data structure, rather than a TclList, // because we assume whoever is calling this will add additional // information to each row. (More specifically, this file knows nothing // about the time associated with a row. Presumably the GridInstance // class will add that before reporting this data to the user.) // // Note that this reads from both the committed and inProgress data, so // lock this object appropriately before requesting a debugDump(). class DebugDump { private: struct ValuePair { double committed, inProgress; ValuePair() : committed(Grid::invalid()), inProgress(Grid::invalid()) { } }; std::map< std::pair< int, int >, ValuePair > _values; public: int width; std::set< int > possibleRowIndex; void getValues(int row, int col, double &committed, double &inProgress) const; void addCommitted(int row, int col, double value); void addInProgress(int row, int col, double value); void addValue(int row, int col, double value, Use whichValue); void clear(); }; private: const bool _packed; const int _width; Cells _cells; PackedToPossible _packedToPossible; PackedToPossible::IList const &getIndex(Use use) const; int _completedRowCountCommitted, _completedRowCountInProgress; void recomputeCompletedRowCount(); void debugDump(DebugDump &dump, Use use, Grid grid) const; public: PackableValues(bool packed, int width) : _packed(packed), _width(width), _completedRowCountCommitted(0), _completedRowCountInProgress(0) { } bool isPacked() const { return _packed; } int width() const { return _width; } int packedToPossible(int packed, Use use) const; // I'm looking for a particular time. The CandleTimer told me to look // for the nth possible row. But the item grid might be packed. This // tells me which row in the grid could have corresponds to that data. // If the row I'm looking for actually exists, I get that value back. // Otherwise there are different possibilities. // o If the grid is not packed, we always return the possible row as is. // That might be a negative number. That value might be higher or lower // than any real row index in the grid. // o If the grid is packed and the value is not present: // . If round is Down, we return the index right before the where the // given item would go if it were inserted into the grid. That would // be -1 if it was before the first row of data that's present. That // would be -1 if the grid is empty. // . If round is Up, we return the index right after where the given item // would go if it were inserted into the grid. That would be 0 if the // grid is empty. If there are n items in the grid, and we're looking // for an item that would be past all of them, this returns n. // . If the item is not present Round::Down always returns a value that // is one less than Round::Up would return. // . If round is Fail we return NOT_FOUND. // . Note that skipping rows at the end of the table does not affect // possibleToPacked() at all. // Note: The original code, GridInstance::possibleToPacked() in // GridReaderBase.C, would always round up. int possibleToPacked(int possible, Use use, Round round) const; Grid committed() const { return Grid(_cells.committed(), _width); } Grid inProgress() const { return Grid(_cells.inProgress(), _width); } // This will work for packed or non-packed objects. This keeps track of // skipped rows, which the Grids do not. The main point of this is to keep // track of the next possible row as we iterate though the list of possible // rows, possibly not adding any data to the grid for some of these rows. // This is also exposed via a TCL command and can be used for anything. int completedRowCount(Use use) const { return (use == Use::Committed) ?_completedRowCountCommitted:_completedRowCountInProgress; } void store(double value); void skipRow(); void deleteExceptPacked(int rowCount); void deleteExceptPossible(int rowCount); void commit(); void rollBack(); void debugDump(DebugDump &dump) const; }; }; ///////////////////////////////////////////////////////////////////// // Implementation ///////////////////////////////////////////////////////////////////// namespace GridInstanceData { ///////////////////////////////////////////////////////////////////// // TransactionList::Committed ///////////////////////////////////////////////////////////////////// template < class T > TransactionList< T >::Committed::Committed(TransactionList &owner) : _owner(owner) { } template < class T > int TransactionList< T >::Committed::getCount() const { return _owner._main.size(); } template < class T > T TransactionList< T >::Committed::getAt(int index) const { return _owner._main[index]; } ///////////////////////////////////////////////////////////////////// // TransactionList::InProgress ///////////////////////////////////////////////////////////////////// template < class T > TransactionList< T >::InProgress::InProgress(TransactionList &owner) : _owner(owner) { } template < class T > int TransactionList< T >::InProgress::getCount() const { return _owner._commonCount + _owner._new.size(); } template < class T > T TransactionList< T >::InProgress::getAt(int index) const { if (index < _owner._commonCount) return _owner._main[index]; else return _owner._new[index - _owner._commonCount]; } ///////////////////////////////////////////////////////////////////// // TransactionList ///////////////////////////////////////////////////////////////////// template < class T > void TransactionList< T >::append(T value) { _new.push_back(value); } template < class T > void TransactionList< T >::deleteExcept(int count) { if (count < 0) // This is what our intention is. If we didn't check for this case we // might set the size of our list to a negative number. count = 0; if (count < _commonCount) { // Delete all of the new items and at least one item that was going to // stay the same. _new.clear(); _commonCount = count; } else { // Don't touch the old items. (Some might have already been deleted. // that doesn't change.) Possibly delete some new items. count -= _commonCount; // Verify that the new size is less than the current size. This function // explicitly will not make the list longer. If the user requests that, // we silently ignore that. if (count < (int)_new.size()) // Delete some of the new items. _new.resize(count); } } template < class T > void TransactionList< T >::commit() { _main.resize(_commonCount); for (auto it = _new.begin(); it != _new.end(); it++) _main.push_back(*it); _new.clear(); _commonCount = _main.size(); } template < class T > void TransactionList< T >::rollBack() { _new.clear(); _commonCount = _main.size(); } template < class T > TransactionList< T >::TransactionList() : _commonCount(0), _committed(*this), _inProgress(*this) { } }; #endif