Data changed design
From Emergent
Contents |
DataChanged Notification System
TA/CSS v4 introduces a new system for "subscribing to" and "publishing" notifications of data changes. This system typically obviates the need for individual data items to keep track of what other things will need to be updated when they themselves change -- this eliminates a lot of complicated and difficult to maintain code from v3.x.
In addition to proving information about data changes, the system provides an element of data abstraction that provides common properties and services that are needed throughout the gui system, such as diplay names, child counts, and clipboard capabilities and actions. The system is designed to be independent of existing class hiearchies -- therefore, the objects and classes involved are used either by delegation, encapsulation, or mixin interfaces. This allows, for example, the non-congruent types in the ta_type system to express the same basic functionality as those in taBase.
Here are the basic classes/interfaces used in this system:
- taDataLinkClient_PtrList -- contains a list of clients of a data item
- taDataLink -- classes that can provide DataLink services must provide one of these
- IDataLinkClient -- classes that wish to be notified of changes must implement this interface
- taiViewType -- a "bidding" class that provides datalinks, and panels, for types
taDataLink is specialized as follows:
taDataLink -- (ta_type.h) generic capability to do notifies, works in TA_GUI or TA_NON_GUI
taiDataLink -- (ta_qtviewer.h) adds gui capabilities, particularly clipboard handling capabilities
tabDataLink -- for taBase-derived objects
tabListDataLink -- adds support for the children of lists
tabGroupDataLink -- adds support for groups
taClassDataLink -- (ta_classbrowse.h) common subclass for all the ta_type guys
taTypeInfoDataLink -- handles TypeDef, MemberDef, etc.
taTypeSpaceDataLink_Base -- common subclass for the spaces
taTypeSpaceDataLink -- for TypeSpace objects
taMethodSpaceDataLink -- for MethodSpace objects
taMemberSpaceDataLink -- for MemberSpace objects
Note that the type-based links don't provide any notifications, since the type catalog is basically static (they support browsing of the types.)
ITypedObject -- provides a TypeDef and a "this" pointer -- used in many interfaces IDataLinkClient -- provides notifications of changes and deletes
IDataLinkClient Methods
- DataDestroying() -- signals to client that the data item is destroying
- DataChanged() -- communicates the type of change, and up to 2-change-specific parameters
Data Changes
from ta_list.h, here are the reason codes -- note that deletion is communicated on its own method (not via a code), for various reasons
enum DataChangedReason { /* reason why DataChanged being called, as well as defining ops (also used by taBase and other classes) --
some data change operations will emit multiple DataChanged calls */
DCR_ITEM_UPDATED = 0, // after user edits (or load) ex. taBase::UpdateAfterEdit call; ops not used
DCR_ITEM_REBUILT, // for complex items that support the STRUCT updates
DCR_CHILD_ITEM_UPDATED, // op1=item; can optionally be invoked by an owned object (usually a member, usually not list/group items) -- owner can ignore this, or do something with it
DCR_LIST_INIT,
DCR_LIST_ITEM_INSERT, // op1=item, op2=item_after, null=at beginning
DCR_LIST_ITEM_UPDATE, // op1=item
DCR_LIST_ITEM_REMOVE, // op1=item -- note, item not DisOwned yet, but has been removed from list
DCR_LIST_ITEM_MOVED, // op1=item, op2=item_after, null=at beginning
DCR_LIST_ITEMS_SWAP, // op1=item1, op2=item2
DCR_LIST_SORTED, // after sorting; ops not used
DCR_GROUP_INSERT, // op1=group, op2=group_after, null=at beginning
DCR_GROUP_UPDATE, // op1=group, typically called for group name change
DCR_GROUP_REMOVE, // op1=group -- note, item not DisOwned yet, but has been removed from list
DCR_GROUP_MOVED, // op1=group, op2=group_after, null=at beginning
DCR_GROUPS_SWAP, // op1=group1, op2=group2
DCR_GROUP_ITEM_INSERT, // op1=item, op2=item_after, null=at beginning
DCR_GROUP_ITEM_UPDATE, // op1=item
DCR_GROUP_ITEM_REMOVE, // op1=item -- note, item not DisOwned yet, but has been removed from list
DCR_GROUP_ITEM_MOVED, // op1=item, op2=item_after, null=at beginning
DCR_GROUP_ITEMS_SWAP, // op1=item1, op2=item2
DCR_GROUP_LIST_SORTED, // after sorting; ops not used
DCR_UPDATE_VIEWS, // no ops; sent for UpdateAllViews
DCR_STRUCT_UPDATE_BEGIN, // for some updating, like doing Layer->Build, better for gui to just do one
DCR_STRUCT_UPDATE_END, // update operation at the end of everything
DCR_DATA_UPDATE_BEGIN, // for some data changes, like various log updates, better for gui to just do one
DCR_DATA_UPDATE_END, // update operation at the end of everything
DCR_ITEM_DELETING // NOTE: not used in standard DataChanged calls, but may be used by forwarders, ex. taDataMonitor
#ifndef __MAKETA__
,DCR_LIST_MIN = DCR_LIST_INIT,
DCR_LIST_MAX = DCR_LIST_SORTED,
DCR_GROUP_MIN = DCR_GROUP_INSERT,
DCR_GROUP_MAX = DCR_GROUPS_SWAP,
DCR_LIST_ITEM_MIN = DCR_LIST_ITEM_INSERT,
DCR_LIST_ITEM_MAX = DCR_LIST_SORTED,
DCR_GROUP_ITEM_MIN = DCR_GROUP_ITEM_INSERT,
DCR_GROUP_ITEM_MAX = DCR_GROUP_LIST_SORTED
#endif
};
List and Group Changes
Extensive support for Lists and Groups has been included in the change system. Note that these notifications are emitted from the list/group object -- the objects in the list/group do not emit any particular change notifications as a result of being in a list/group. Also note that changes to the child objects themselves do not automatically result in notifications from the parent list/group; however, a special change code (DCR_CHILD_ITEM_UPDATED) is provided if a class wants to manually provide a notification when a child changes -- this must normally be implemented by overriding the DataChanged of the child class, and forwarding it as a CHILD_ITEM_UPDATE throught the parent's DC routine.
Lists provide a notification upon all major changes to list membership: add/insert, delete, and move (including sorts).
Groups inherit the List notifications, and provide two additional sets of notifications:
- GROUP_xxx -- for groups themselves (subgroup add/insert, subgroup delete, etc.) ; similar semantics to List items
- GROUP_ITEM_xxx -- these are similar to List operations, except they all occur on the root group -- this lets you monitor all item changes, anywhere in the group hierarchy, by simply monitoring the root group itself
DataLink Providers
- taBase defines methods for interacting with data links
- taOBase is the first class that implements support for data links
In general, no taDataLink object exists until a client "subscribes" to a data object. At that point, the following happens:
- . if the object already has a datalink, then skip the next step
- . the correct taiViewType object will create a datalink of the right type for the object Type (ex a tabListDataLink for a taList)
- . the client will get added to the list of clients kept in the datalink
Once a datalink is created for an object, it is typically never destroyed (until the object destroys); the list object in which clients are kept is dynamically created/deleted as needed.
When a change occurs in the data object:
- . it calls the correct routine (Delete or Change) on its datalink (if it has one)
- . the DataLink iterates over the list of clients, and sends the message to the client object
In taBase, the UpdateAfterEdit routine automatically sends a ITEM_UPDATED notification to all subscribers.
It is always permissible to manually issue notifications. This should be done, for example, if program code changes the values in an object, but doesn't call UAE. In this case, on taBase, it can manually call DataChanged(DCR_ITEM_UPDATED).
Batch Nested Updates
In addition to simple notifications (1 change -- 1 notify), the taDataView class also supports nesting of Structural and Data updates. These operations ALWAYS occur in pairs. (Issuing a Start without an End will result in runaway expansion of the universe; issuing an End without a Start will result in runaway contraction of the universe!)
A Data Update is a change to parameters of an object. An example might be a routine that updates the activations of an entire layer -- it could issue a StartDataUpdate, make the changes, then issue EndDataUpdate. A Structural Update involves adding or removing objects, such as adding a layer or projection, or resizing a layer. A Struct update occuring withing the scope of a Data Update turns the eventual change notification into a Struct update.
In order to avoid unnecessary updates in T3 (which can be slow because of the complexity of Inventor/OpenGL), only the final result of a series of changes is acted on. Therefore, taDataView has logic to track the Struct and Data changes, and also to see if they are within such changes in the parents -- the end result is that only changes necessary are actually rendered. The T3 system handles Data Updates by updating all the parameters of the Inventor objects (it issues a recursive Render_impl); T3 handles a Struct update by nuking and then rebuilding the Inventor objects.
Handling DataChanged
When handling DataChanged keep in mind that you are still inside a possibly deeply nested block of code involving changes to objects -- therefore, you should avoid making any changes to objects while handling notifications from those objects (or related objects, such as their parents, siblings, etc.)
Related Classes
taSmartRef
A SmartRef acts just like a smart pointer, but in addition, will automatically null itself if the object to which it points destroys. These can hold refs to any taBase object, and do not affect the lifetime of the object (they don't do refs). If embedded in a taBase, you can "own" the ref in the InitLinks in which case your UpdateAfterEdit will automatically get invoked if the pointer deletes. There is also a stub method on taBase that receives notifications from owned SmartRefs, so you can monitor all the DataChanged calls issues by the object if you want.
Becoming a DataLink client
A cheap way is described previously, namely, using a SmartRef to an object.
Many objects in the system implement the IDataLinkClient interface. This has only a few methods (DataChanged, DataDestroying) which you need to reimplement.
