Reading Persistant Data

From The Foundry MODO SDK wiki
Jump to: navigation, search


The objective of this article is to describe in laymans terms reading persistant xml data from the configuration files you might employ with your plugin. Primarily, I'll discuss the usage of the

LxResult CLxUser_PersistenceService.ConfigureVis(const char *name, CLxImpl_AbstractVisitor *visitor)

method for retrieving unique data that you may have stored and that doesn't fit well with the 'CLxReadUserValue' or 'CLxReadPreferenceValue' senario.

The basic usage premise of CLxUser_PersistenceService.ConfigureVis is that the 'service' finds the 'root' <atom> you specify in the 1st parameter and the Evaluate method of the simple visitor you pass as the 2nd parameter initializes the accessor objects and their implicit heirarchy into various CLxUser_PersistentEntry objects.

Defining XML Data

Sometimes when developing a .lx plugin you want to have stored data that you can easily modifiy and/or extend. Generally you will package your plugin as a Kit with an included config.cfg xml file for detailing your plugins forms, messages, help & user values etc.

There are 3 types of xml elements one may define. An <atom>, <list>, or a <hash>. See the lx_persist.hpp page for more on this.

As an example I've written a file i/o plugin that reads & writes a proprietry binary model file format for a gaming engine. During saving a .lxo scene the saver code looks for visible meshs gathering the names of these meshes. Then the plugin looks up these mesh names in a std::map<string, int> container. Vice versus, when reading the model file the loader portion of the plugin needs to deduce an appropriate name for the 'lod' being read so that it may name a new mesh appropriately in the modo scene. It does this by looking up the integer value of the lod read in a std::map<int, string> container.

To facilitate this mechanism I created the following xml fragment in the config file for the kit.

  <atom type="sy_p3d">
    <atom type="lods">
      <!-- NOTE: LoD Names maximum length of 256 chars -->
      <list type="lod">        <atom type="name">ViewCommander</atom>               <atom type="value">1510874058</atom>      </list>
      <list type="lod">        <atom type="name">ViewCommanderGeometry</atom>       <atom type="value">1511805380</atom>      </list>
      <list type="lod">        <atom type="name">ViewCommanderFireGeometry</atom>   <atom type="value">1512736703</atom>      </list>
      <list type="lod">        <atom type="name">ViewGunner</atom>                  <atom type="value">1148846080</atom>      </list>
      <list type="lod">        <atom type="name">ViewGunnerGeometry</atom>          <atom type="value">1515530671</atom>      </list>
      <list type="lod">        <atom type="name">ViewGunnerFireGeometry</atom>      <atom type="value">1512736703</atom>      </list>
      <list type="lod">        <atom type="name">ViewPilot</atom>                   <atom type="value">1149861888</atom>      </list>
      <list type="lod">        <atom type="name">ViewPilotGeometry</atom>           <atom type="value">1513668025</atom>      </list>
      <list type="lod">        <atom type="name">ViewPilotFireGeometry</atom>       <atom type="value">1514599348</atom>      </list>
      <list type="lod">        <atom type="name">ViewCargo</atom>                   <atom type="value">1150681088</atom>      </list>
      <list type="lod">        <atom type="name">ViewCargoGeometry</atom>           <atom type="value">1508073385</atom>      </list>
      <list type="lod">        <atom type="name">ViewCargoFireGeometry</atom>       <atom type="value">1509936030</atom>      </list>
      <list type="lod">        <atom type="name">ViewGeometry</atom>                <atom type="value">1504348095</atom>      </list>
      <list type="lod">        <atom type="name">FireGeometry</atom>                <atom type="value">1506210740</atom>      </list>
      <list type="lod">        <atom type="name">Geometry</atom>                    <atom type="value">1427211495</atom>      </list>
      <list type="lod">        <atom type="name">Memory</atom>                      <atom type="value">1482907561</atom>      </list>
      <list type="lod">        <atom type="name">LandContact</atom>                 <atom type="value">1491296169</atom>      </list>
      <list type="lod">        <atom type="name">Roadway</atom>                     <atom type="value">1495959487</atom>      </list>
      <list type="lod">        <atom type="name">Paths</atom>                       <atom type="value">1499684777</atom>      </list>
      <list type="lod">        <atom type="name">Hitpoints</atom>                   <atom type="value">1502485450</atom>      </list>
      <list type="lod">        <atom type="name">Subparts</atom>                    <atom type="value">1517393316</atom>      </list>
      <list type="lod">        <atom type="name">ShadowVolume-ViewCargo</atom>      <atom type="value">1518324638</atom>      </list>
      <list type="lod">        <atom type="name">ShadowVolume-ViewPilot</atom>      <atom type="value">1518797004</atom>      </list>
      <list type="lod">        <atom type="name">ShadowVolume-ViewGunner</atom>     <atom type="value">1519262666</atom>      </list>
      <list type="lod">        <atom type="name">Wreck</atom>                       <atom type="value">1519728327</atom>      </list>
      <list type="lod">        <atom type="name">ShadowVolume0.0</atom>             <atom type="value">1176256512</atom>      </list>
      <list type="lod">        <atom type="name">ShadowVolume10.0</atom>            <atom type="value">1176266752</atom>      </list>
      <list type="lod">        <atom type="name">Displacement</atom>                <atom type="value">1435600103</atom>      </list>
      <list type="lod">        <atom type="name">Underground</atom>                 <atom type="value">1440368475</atom>      </list>

The above data is meaningless to the rest of the modo application and is specifically for use with this particular plugin. The root element of this xml fragment is the <atom type="sy_p3d"></atom>. The 'service' acquires this element for you when you first call the .ConfigureVis method and this should only be done once. That is you should only have 1 CLxUser_PersistenceService instance attached to this root element at any one time.

Example Code

struct CP3D_PersistentData {
  CLxUser_PersistentEntry   lods;            //atom
  CLxUser_PersistentEntry     lod;           //list
  CLxUser_PersistentEntry       lod_name;    //atom
  CLxUser_PersistentEntry       lod_value;   //atom
  CLxUser_Attributes        lod_name_attribute;
  CLxUser_Attributes        lod_value_attribute;
} *pP3D_Persist_Data = 0;
class CP3D_PersistentVisitor : public CLxImpl_AbstractVisitor {
    //virtual   ~CP3D_PersistentVisitor() {};
    LxResult Evaluate() {
        Define accessors that encapsulate the varying elements
        of the persistent cfg data specifically relating to 'P3D' data.
        Note: The 'sy_p3d' root 'atom' element is already defined prior
            to this when the service is instantiated.
            So, this 'setup' evaluation method is defining the sub-elements.
      CLxUser_PersistenceService svc;
      svc.Start("lods", LXi_PERSIST_ATOM);
        svc.Start("lod", LXi_PERSIST_LIST);
          svc.Start("name", LXi_PERSIST_ATOM);
          svc.Start("value", LXi_PERSIST_ATOM);
      pP3D_Persist_Data->lod_name_attribute.set   (pP3D_Persist_Data->lod_name);
      pP3D_Persist_Data->lod_value_attribute.set  (pP3D_Persist_Data->lod_value);
CLxUser_Log P3D_Persistence_Log;
void ReadPersistent_LoD_Data() {
    This method is for caching the persistent data into 2 globally accessible std::map's.
  string sName, sLowercaseName;
  int iValue;
  char szName[256];
  int NoOf_LoD_Entries = pP3D_Persist_Data->lod.Number();
  if (NoOf_LoD_Entries != -1)
    for (int i0 = 0; i0 < NoOf_LoD_Entries; i0++)
      ZeroMemory(szName, 256);
      iValue = -1;
      if (pP3D_Persist_Data->lod_name_attribute.GetString(0, szName, 256) == LXe_OK) //Hopefully the element value string will never be longer than 256 chars.
        sName = string(szName);
      if ((pP3D_Persist_Data->lod_value_attribute.GetInt(0, &iValue) == LXe_OK) && !sName.empty())
        sLowercaseName = ToLower(sName);
        //This std::map is used during saving to find the integer value to write to the .p3d file based on the 'name' of the mesh for those
        //special lod names in .p3d files.
        LoDName2ValueMap.insert(pair<string,int>(sLowercaseName, iValue));
        //This std::map is used when loading a .p3d to find the special lod names to name the mesh item in modo.
        LoDValue2NameMap.insert(pair<int,string>(iValue, sName));
    P3D_Persistence_Log.Message(LXe_NOTAVAILABLE, "Config file 'lods' data unavailable.");
    pP3D_Persist_Data->lods_data = false;
void P3DPersistence_Setup() {
  if (pP3D_Persist_Data)
  pP3D_Persist_Data = new CP3D_PersistentData;
  CLxUser_PersistenceService      PersistenceService;
  CP3D_PersistentVisitor          P3DCfgVisitor;
  if (PersistenceService.ConfigureVis("sy_p3d", &P3DCfgVisitor) == LXe_OK)
    P3D_Persistence_Log.Message(LXe_NOTAVAILABLE, "Config file 'sy_p3d' data unavailable.");


The 'P3DPersistence_Setup' method is in the global scope. It calls into the CLxUser_PersistenceService. NOTE: It's by no means gauranteed that 'services' within the modo runtime are up and running when you want to access them. In this particular case if you make a call into the CLxUser_PersistenceService during the plugin modules initialize() method the service is unavailable and consequently cannot grab the root element. It's best to defer access to the persistence service till at least the constructor for your plugin's main class.

  • If the pointer pP3D_Persist_Data already exists then this setup method exits immediately. This is to satisfy the requirement that only ONE instantiation of the root element <atom type="sy_p3d"> will be active at any one time else a new pointer is created.
  • Next we grab a log object into P3D_Persistence_Log for the specified log sub-system.
  • Then we make a call to the service to configure the accessors in the struct. If successful then we call a method to read the persistent values and cache them in std:map containers.


The Evaluate method of the visitor is responsible for defining the accessor CLxUser_PersistentEntry & CLxUser_Attributes objects.

Essentially it starts and ends a definition for each element. Upon ending a element definition it instantiates a CLxUser_PersistentEntry.

If the particular element actually has a value, as is the case in the above example xml for the <atom> elements 'name' & 'value' then during the definition you must define the 'type' of this value.

Finally, later when you want to actually get access to these values you cannot do so through just the CLxUser_PersistentEntry alone because the values are attributes. So, you need to setup an accessor CLxUser_Attributes object for the given CLxUser_PersistentEntry as well.


Once the Evaluate method has finished successfully you are left with 'accessor' objects to the various xml elements that make up your plugins specific xml data.
You can either have helper methods that access these accessor objects on an as needed basis like the helper classes for User Values and Peference Values OR (as is the case in this example) you can read all the data you need immediately and cache it in your plugin as you like.


In this particular example I've cached the xml data by reading from the xml file via the accessor objects and storing these values in globally accessoble std:map containers. The code should be fairly self-explanatory.

Additional Info