Unit Schemes -- Slot Class Architecture
Phil Weinstein, CADSWES -- see also source code samples

Document History / Status:

Contents

Overview

This document describes the enhancements to the Slot class hierarchy for support of Unit Schemes.

With the introduction of Unit Schemes in RiverWare 6.1, a slot's numeric values can be presented using one of several distinct "schemes" of units and other display attributes defined by the user. A Value Display Attribute Scheme provides the following attributes for numeric slots -- and for slot columns in multiple-column numeric slots:

In the RiverWare session, one particular value display attribute scheme is active at any one time. This will be one of the following:

Basic Data Structure: Value Display Attribute Group ("AttribGroup")

Value Display Attributes will be encapsulated in a new ValueDisplayAttributeGroup class.

class NumDisplayAttribs
{
  private:
    ScaledUnitPtr _scaledUnit; // scale (double) and Unit
    int _dispPrecision;        // number of fractional decimal digits
    char _dispFormatChar;      // 'f'-float, 'e'-scientific notation
 
    bool _isDefined;  // 'false' indicates required referal to a different 
                      // source of display attributes.
public: NumDisplayAttribs(); NumDisplayAttribs (ScaledUnitPtr, char fmt, int prec); ScaledUnitPtr scaledUnit() const { return _scaledUnit; } int dispPrecision() const { return _dispPrecision; } char dispFormatChar() const { return _dispFormatChar; }
bool isDefined() const { return _isDefined; } };

This class is immutable. (It doesn't define field-setter methods). A default constructor is required for use in collection classes (e.g. QList) and for the "not defined" state which indicates that the display attributes need to be provided from a lower-priority source. The default constructed value has a _scaledUnit of (1.0 NOUNITS), a precision of 2, the "float" format, and an "isDefined" value of 'false'.

The legacy "display format" attribute is a QString -- by convention, a single character of the values 'f' (float), 'e' (scientific notation) or 'i' (integer). The NumDisplayAttribs will instead represent the "display format" attribute as a char. The 'i' (integer) format will show up only in AttribsList coming from the legacy "local" scheme. The new "mapped" schemes will not generate integer format configurations. Note that configuring a slot or slot column as "integer" does not enforce the assignment of only integer values to the slot. It only causes fractional digits to be hidden (with rounding) in the display of the value -- and that is equivalent to using the "float" format with a precision of zero.

The AttribGroup data fields are ordered to optimize "packing" of that data within memory. (It would otherwise have been logical to place the '_dispFormatChar' field before the '_dispPrecision' field, and to put the '_isDefined' field first).

Technical Requirements

  1. AttribsList for a slot and a slot's columns will be available from the slot instance, via public methods.
  2. There will be distinct public methods for accessing:
  3. The set of AttribGroup methods will support both "primary" units and "alternate" ("alt") units -- for slots that support alternate units.
  4. Performance. Display attributes are required only for displaying slot data (in the GUI) and for certain I/O operations. As there will be a lot of computation in applying the "mapped" schemes to all of the slots and slot columns in the model, those computations for any particular slot should be deferred until actually needed -- at least at the slot level.
  5. The AttribGroup caching mechanism on slots (described in this document) should be "code safe" in the sense that it should not be possible to access a stale cached value (which had been computed using a no-longer current display attribute scheme). Also the only way to set cached AttribGroup values should be to have them computed in the intended way.

Slot base class -- "Column Aware"

While some virtual methods to support multiple-column slots have been introduced in the past (e.g. for Table Slot repeatable "column block" support), this enhancement will formalize the concept of any slot potentially having multiple columns. The mechanism being proposed in this document will make use of these new column-related virtual functions:

class Slot : public Root
{
   ... ... ...

   virtual int colCount() const { return (1); }
virtual unit_type primUnitType (int /*col*/=0) const { return NOUNITS; }
virtual unit_type altUnitType (int /*col*/=0) const { return NOUNITS; } ... ... ...
};

The 'colCount()' method needs to be overridden in only slot subclasses supporting multiple columns, i.e. AggSeriesSlot, MultiSlot and TableSlot.

The 'primUnitType()' method needs to be overridden in all numeric Slot classes, i.e. all but ListSlot, excluding slot subclasses further derived from those sufficiently defining this method.

The 'altUnitType()' method needs to be overridden only in the SeriesSlot and AggSeriesSlot classes. It should return NOUNITS for slot instances not supporting "Alt" units. (In the case of alternate units, NOUNITS means that the slot column doesn't support 'alt' units rather than supporting unitless values).

Private Slot Internal Class and Instance:
    Mapped Value Display Attribute Cache ("AttribCache")

The AttribsList for a slot's columns which are computed for the current (or recently current) "mapped" scheme will be cached within this new internal private "AttribCache" class, for which there will be one private instance:

private:
  class Slot::MappedValDisplayAttribCache
  {
     private:
       const Slot* const _slot;  // owner slot
       int _mappedSchemeSerialNum;
       QList<NumDisplayAttribs> _colAttribsList_prim; // primary
       QList<NumDisplayAttribs> _colAttribsList_alt;  // alt units 
 
     public:
       MappedValDisplayAttribCache (Slot* s);
       const NumDisplayAttribs& colGroupRef_prim (int col); // primary
       const NumDisplayAttribs& colGroupRef_alt  (int col); // alt units
 
     private:
       void checkCurrent();
  };
 
private:
   mutable MappedValDisplayAttribCache _mappedValDisplayAttribCache;

The '_slot' field is required by the AttribCache's internal mechanism to compute its column AttribGroup lists. It is provided via the constructor when the slot's AttribCache instance is created (in the slot constructor's AttribCache initializer, using the slot's "this" pointer).

The cached lists of AttribsList -- one for primary units on each of the slot's columns, and one for alt units on each of the slot's columns -- are known to be current by comparing the saved "Value Display Scheme Serial Number" with the global version of that number stored on the SchemeManager (see below) -- and also insuring that the number of columns in the cache matches the number of columns in the slot. This is assessed by the "checkCurrent()" method (see below) called at the beginning of the AttribGroup "getter" methods.

The AttribCache class doesn't provide any "setter" methods. Its AttribGroup list values can only be computed internally.

// private
void Slot::MappedValDisplayAttribCache::checkCurrent()
{
   // IF the locally saved scheme serial number doesn't match the
   // manager's serial number OR IF the number of columns in the slot
   // has changed, then recompute the column attribute groups for the 
   // slot (using the Value Display Attribute Scheme Manager).
 
   ValDisplayAttribSchemeMgr* mgr = ValDisplayAttribSchemeMgr::inst();
 
   if ( (_mappedSchemeSerialNum != mgr->mappedSchemeSerialNum())    ||
        (_colAttribsList_prim.size() != _slot->colCount()) )
   {
      _colAttribsList_prim = 
         mgr->computeColAttribsList (_slot, false); // not alt units

      if (_slot->supportsAltUnits())
      {
         _colAttribsList_alt = 
            mgr->computeColAttribsList (_slot, true); // alt units
      }
 
      _mappedSchemeSerialNum = mgr->mappedSchemeSerialNum();
   } 
}

The implementation the "checkCurrent()" method (above) makes use of another new virtual Slot class method:

  virtual bool supportsAltUnits() const { return false; }

It is assumed that whether or not a given slot supports "Alt" units is a constant property. The default implementation of this method (which returns 'false') needs to be overridden only in the SeriesSlot method, which will effectively return 'true' for only certain accounting system SeriesSlots.

The only access to the AttribCache's AttribsList will be through these two methods which first call the "checkCurrent()" method:

// public
const NumDisplayAttribs& 
  Slot::MappedValDisplayAttribCache::colGroupRef_prim (int col)
{
   checkCurrent();

   if (rwAssert (col >= 0) &&
       rwAssert (col < _colAttribsList_prim.size()))
   {
      return _colAttribsList_prim [col];
   }

   static const NumDisplayAttribs NullValDispAttribGroup;
   return (NullValDispAttribGroup);
}

// public
const NumDisplayAttribs& 
  Slot::MappedValDisplayAttribCache::colGroupRef_alt (int col)
{
   checkCurrent();

   // Note: Don't fail an assertion for the column index being beyond
   // the size of the _colAttribsList_alt list.  That list will be
   // empty if Alt units are not supported on a slot.  Attempting to
   // access Alt units at this level when they don't exist is allowed.

   if (rwAssert (col >= 0) &&
       (col < _colAttribsList_alt.size()))
   {
      return _colAttribsList_alt [col];
   }

   static const NumDisplayAttribs NullValDispAttribGroup;
   return (NullValDispAttribGroup);
}

Slot class AttribGroup Accessors

These public Slot methods allow read-only access to AttribsList for the slot and the slot's columns:

// Returns the cached "mapped" display attributes for the specified column
// which were computed in accordance with the current (or most recently 
// current) globally active mapped Value Display Attribute Scheme.

const NumDisplayAttribs& mappedNumDisplayAttribs_prim (int col=0) const;
const NumDisplayAttribs& mappedNumDisplayAttribs_alt (int col=0) const;
// Returns the display attributes configured on the slot for the specified
// column as a Value Display Attribute Group.

virtual
const NumDisplayAttribs& localNumDisplayAttribs_prim (int col=0) const = 0; virtual
const NumDisplayAttribs& localNumDisplayAttribs_alt (int col=0) const = 0;
// Returns either the "local" or "mapped" Value Display Attribute Group
// for the specified column, depending on the globally active Value 
// Display Attribute Scheme.

const NumDisplayAttribs& activeNumDisplayAttribs_prim (int col=0) const;
const NumDisplayAttribs& activeNumDisplayAttribs_alt (int col=0) const;

As demonstrated in the implementation of one of the "mapped" AttribGroup methods (first group above), if the current Mapped Scheme doesn't provide an AttribGroup for the particular slot column, the "local" AttribGroup is returned instead (i.e. the display attributes configured on the slot):

// public
const NumDisplayAttribs&
  Slot::mappedNumDisplayAttribs_prim (int col /*=0*/) const
{
   const NumDisplayAttribs& mappedAttribGroup =
      _mappedValDisplayAttribCache.colGroupRef_prim (col);

   if (mappedAttribGroup.isDefined())
   {
      return mappedAttribGroup;
   }

   // If the current Mapped Scheme doesn't provide display attributes
   // for the requested slot column, then defer to the Local Scheme.
   return (localNumDisplayAttribs_prim (col));
}

The "local" AttribGroup methods must be implemented in the slot subclasses. It is recommended, for run-time efficiency and consistency, that the individual unit and display attribute fields (including lists of fields, for multiple-column slots) be "repackaged" within AttribGroup or AttribGroup list fields. If that isn't done, then it might not make sense for these methods to return a reference to (or pointer to) an AttribGroup, as an actual AttribGroup instance would have to "live" somewhere. Also, we should take this opportunity to change the "format" fields from QStrings to a single-byte 'char'.

For reference, here are the current display attribute fields on the SeriesSlot and the TableSlot:

SeriesSlot:
    // primary scaled unit (type, standard and user units, scale)
    ScaledUnitPtr _scaledUnit;
    
    // Format & Precision
    QString _usrFormat;
    int _usrPrecision;
    
    // alternate scaled unit (type, standard and user units, scale)
    ScaledUnitPtr _altScaledUnit;

    // alternate format and precision
    QString _alt_format;
    int _alt_precision;
TableSlot:
   // Scale & Units
   cwArray<ScaledUnitPtr> *_scaledUnits;
   
   // Format & Precision
   cwArray<QString> *_usrFormat;
   cwArray<int> *_usrPrecision;

The "active" AttribGroup methods switch between the "mapped" and "local" schemes, depending on state information provided by the Scheme Manager, for example:

// public
const NumDisplayAttribs&
  Slot::activeNumDisplayAttribs_prim (int col /*=0*/) const
{
   if (ValDisplayAttribSchemeMgr::inst()->localSchemeActive())
      return localNumDisplayAttribs_prim (col); // virtual call

   return mappedNumDisplayAttribs_prim (col);
}

SchemeManager Responsibilities

The Value Display Attribute Scheme Manager ("SchemeManager") is a singleton which manages the set of named "mapped" Unit Schemes. It also supports the identification of the current "active" scheme (which could also be the "local" scheme), and supports the maintenance of the AttribCaches on all of the slot instances in the model. Only this latter responsibility is relevant for this section. Relevant members are defined here in this "stub" implementation:

class ValDisplayAttribSchemeMgr
{
  private:
   static ValDisplayAttribSchemeMgr* _instance;
   int _mappedSchemeSerialNum;
 
  private:
   ValDisplayAttribSchemeMgr() : _mappedSchemeSerialNum (0) {}
   ~ValDisplayAttribSchemeMgr() { _instance = NULL; }
 
  public:
   static ValDisplayAttribSchemeMgr* inst();
    
   int mappedSchemeSerialNum() const { return _mappedSchemeSerialNum; }
 
   QList<NumDisplayAttribs> 
      computeColAttribsList (const Slot*, bool alt) const; 
 
   bool localSchemeActive() const;
};

The '_mappedSchemeSerialNum' field is incremented each time the user selects a different "mapped" scheme to be the active scheme -- and each time the active "mapped" scheme is edited by the user. The serial number doesn't need to be incremented when changing to the "local" scheme, or changing back to the same "mapped" scheme, unless that mapped scheme has since been edited. By the way, there is no problem with the serial number "wrapping" (overflowing) to huge negative numbers after 2 billion plus changes. That won't happen anyway.

The 'localSchemeActive()' value is just one part of the "active" scheme selection data maintained by the SchemeManager. If some "mapped" scheme is active, the "local" scheme will not be active. As currently designed, slots' AttribCaches don't need to know which "mapped" scheme is active, though we could consider recording that information in the slots' AttribCaches -- perhaps just the mapped scheme's name for diagnostic purposes. (However, we wouldn't want to implement an unnecessary management mechanism with slots, to track that mapped scheme's life-cycle or name changes).

The maintenance of the set of named "mapped" schemes and support for the selection of the "active" scheme are not discussed here, but those will also be supported by the SchemeManager.

--- (end) ---