persist (lx_persist.hpp)

From The Foundry MODO SDK wiki
Jump to: navigation, search
There are security restrictions on this page


Persistent Data

All interesting applications have some persistent state which should be remembered across invocations. This system allows clients to save state as hierarchical parameter values in "cfg" files. These are logically the same as resource files, so there are three use cases:

Resources
Resources are config settings that are read from a fixed set of read-only files and available as a way to configure the application.
Configs
Configs are user settings that can be stored and recovered the next time the application is run, giving a sense of UI continuity.
Configurable Resources
If the user can save changes to the resources, then those will override the basic resources and the user has configured their application.

Defining Entries

The service provides methods to define the setup of the configuration.

(1) SDK: Declarations
 #define LXu_PERSISTENCESERVICE  "4CB5705E-C705-499D-9561-6FD369CEFE99"
 #define LXa_PERSISTENCESERVICE  "persistenceservice"

(2) SDK: ILxPersistenceService interface
         LXxMETHOD(  LxResult,
 ScriptQuery) (
         LXtObjectID              self,
         void                   **ppvObj);

Clients call this method to define a set of persistent attributes. The name will define a top-level atom in the config file which should be unique. The object can be a visitor which, when evaluated, should add entries and values.

It should be noted that this function should only be called once for any top-level name during the lifetime of the application. Attempting to call it again will return an error.

(3) SDK: ILxPersistenceService interface
         LXxMETHOD( LxResult,
 Configure) (
         LXtObjectID              self,
         const char              *name,
         LXtObjectID              obj);

During configuration, these calls define the nested hierarchy of entries. Each entry has a name and a type -- atom, hash or list. Ending the entry definition returns the persistent entry object which can be use to traverse persistent values.

(4) SDK: ILxPersistenceService interface
         LXxMETHOD( LxResult,
 Start) (
         LXtObjectID              self,
         const char              *name,
         unsigned int             type);
 
         LXxMETHOD( LxResult,
 End) (
         LXtObjectID              self,
         void                   **ppvObj);

(5) SDK: Declarations
 #define LXi_PERSIST_ATOM         0
 #define LXi_PERSIST_HASH         1
 #define LXi_PERSIST_LIST         2

While in an entry definition, values can be added. Currently only one value per entry is allowed. The type name is any of the value types.

(6) SDK: ILxPersistenceService interface
         LXxMETHOD( LxResult,
 AddValue) (
         LXtObjectID              self,
         const char              *typeName);

This user method on the service takes a C++ visitor object and wraps it for COM.

(7) User Service Class: PersistenceService method
         LxResult
 ConfigureVis (
         const char              *name,
         CLxImpl_AbstractVisitor *visitor)
 {
         CLxInst_OneVisitor<CLxGenericVisitor>  gv;
 
         gv.loc.vis = visitor;
         return Configure (name, gv);
 }

Alternate version of the End() method to get the persistent entry in more C++ friendly terms.

(8) User Service Class: PersistenceService method
         bool
 EndDef (
         CLxLoc_PersistentEntry  &entry)
 {
         LXtObjectID              obj;
 
         entry.clear ();
         if (LXx_FAIL (End (&obj)))
                 return false;
 
         return entry.take (obj);
 }

Empty persistence service Python user class.

(9) PY: PersistenceService method
 pass

Persistent Client

In most case the client will maintain persistent state in the host app and read it whenever it's needed. This allows the client to react to changes to the application persistent state (like if the user imports new configs) but it can be slower than desired for very complex resource definitions, for example. If that's the case the client will want to cache their state internally, but they then need to be able to react to config system events.

If this is required the client can pass a PersistenceClient object in the call to Configure() instead of a visitor. In this case the object itself will persist, and methods will be used to manage synchnonization.

(10) SDK: Declarations
 #define LXu_PERSISTENCECLIENT   "435E3BEE-8317-4DEB-92A8-74E115384F3F"
 #define LXa_PERSISTENCECLIENT   "persistenceclient"

Setup
Called to configure the entries and values, like Evaluate() from a visitor.
SyncRead
Called when the client should read its state from the application. This will be called after a successful initial setup.
SyncRead
Called when the client should write its cached state to the application.

(11) SDK: ILxPersistenceClient interface
         LXxMETHOD( LxResult,
 Setup) (
         LXtObjectID              self);
 
         LXxMETHOD( LxResult,
 SyncRead) (
         LXtObjectID              self);
 
         LXxMETHOD( LxResult,
 SyncWrite) (
         LXtObjectID              self);

Empty PersistenceClient Python user class.

(12) PY: PersistenceClient method
 pass

Accessing Entries

Access to persistent state is through a persistent entry interface. The value for sub-entries depends on the values of their parent entries, so those need to be set in order to get valid results for sub-entries.

(13) SDK: Declarations
 #define LXu_PERSISTENTENTRY     "BD3F1DF5-967B-441F-86A7-A091F2E6CDA5"

Entry objects also have an ILxAttributes interface for accessing their values, although for now all values are index 0.

Count() tells you how many values there are for this entry. For atoms this is 0 or 1, depending on whether it's defined. For lists and hashes this can be more than one.

(14) SDK: ILxPersistentEntry interface
         LXxMETHOD( LxResult,
 Count) (
         LXtObjectID              self,
         unsigned int            *count);

These methods pick the specific entry. Select() uses and index and works for lists or hashes, and Lookup() uses a key and is only for hashes. Entries that are atoms don't need to be explicitly selected.

(15) SDK: ILxPersistentEntry interface
         LXxMETHOD( LxResult,
 Select) (
         LXtObjectID              self,
         unsigned int             index);
 
         LXxMETHOD( LxResult,
 Lookup) (
         LXtObjectID              self,
         const char              *key);

These methods add new values -- Append() for atoms and lists, and Insert() for hashes.

(16) SDK: ILxPersistentEntry interface
         LXxMETHOD( LxResult,
 Append) (
         LXtObjectID              self);
 
         LXxMETHOD( LxResult,
 Insert) (
         LXtObjectID              self,
         const char              *key);

These methods remove values. Delete() removes this specific value, while Clear() removes all values of this type from the current parent.

(17) SDK: ILxPersistentEntry interface
         LXxMETHOD( LxResult,
 Delete) (
         LXtObjectID              self);
 
         LXxMETHOD( LxResult,
 Clear) (
         LXtObjectID              self);

For hash entries, this returns the hash itself.

(18) SDK: ILxPersistentEntry interface
         LXxMETHOD( LxResult,
 Hash) (
         LXtObjectID               self,
         const char              **key);

Easier way to get the entry count, with -1 for errors.

(19) User Class: PersistentEntry method
         int
 Number ()
 {
         unsigned int             n;
 
         if (LXx_OK (Count (&n)))
                 return n;
 
         return -1;
 }

Standard string lookup.

(20) User Class: PersistentEntry method
         LxResult
 Lookup (const std::string &key)
 {
         return CLxLoc_PersistentEntry::Lookup (key.c_str ());
 }

Standard string insertion.

(21) User Class: PersistentEntry method
         LxResult
 Insert (const std::string &key)
 {
         return CLxLoc_PersistentEntry::Insert (key.c_str ());
 }

Empty PersistentEntry Python user class.

(22) PY: PersistentEntry method
 pass