Programming Guide


Part Persistency

 

The part persistency recipes are listed as follows:

Multiple Kind Support

OpenDoc provides a structured storage model which enables part editors to persistently store multiple representations of their content. To present this feature in a simple and easy to use manner to users, part editors must follow a few rules in general, and be more particular in their implementation of a handful of their methods. This document contains the list of part editor responsibilities, and sample code which would allow a part editor to fulfill these responsibilities.

Terminologies

The terms you need to be familiar with are:

Kind

Specific data format or type

Category

Set of similar kinds. A kind can be in multiple categories.

Preferred kind of a part

The preferred kind of a part designates the data format which the editor running the part must use to 'ternalize the part's content. The preferred kind is stored in kODPropPreferredKind property of the part's storage unit. If the kODPropPreferredKind property does not exist, then the editor can presume that the preferred kind of the part is the value type of the first value in the contents property (kODPropContents) of the part's storage unit.

Registration

Registration of a part handler. See the "Dynamic Binding" recipes for more information.

Standard Kinds

Standard kinds are those data formats which, either through an official decree or by some de facto means, have become widely used and accepted. Here are some examples:
Industry Standard Kinds ASCII, TIFF, JPEG, etc.

Supporting a standard kind has many advantages.

Supporting a standard kind means there is a greater chance that your editor can read parts created by other editors. This enables users to share documents created by your part editor.

Coding Required

To correctly support multiple kinds, there are methods your editor needs to ensure proper behavior.

InitPart
   

This method is called when stationery is created. Your editor should make sure it does the following:

  1. Set the preferred kind. Write the appropriate ISO string into the kODPropPreferredKind property of your part.

  2. Create a value whose type is that kind in the contents property

  3. Initialize the content value with valid initial contents. Often leaving the value empty is a valid; it depends on the particular data format. Alternatively, perhaps as an optimization, your editor may just set an internal dirty flag, and then perform the above three steps in its externalize method when it realizes there is no contents property.

InitPartFromStorage
   

This method is called when a part is read back in from an existing document. This happens whenever a user opens a document or creates a new document. Your editor should make sure it does the following:

  1. Get the preferred kind. Read the value from the kODPropPreferredKind property of your part. If no property exists, then use the value type of the first value of the contents property as the preferred kind. Cache the preferred kind in an instance variable.

  2. Focus your part's storage unit to the value of the contents property whose value type is the preferred kind.

  3. Read the contents of that first value and create the in-memory data structures necessary to represent that content.

    Note:

    It is possible that your editor was bound to a part which previously had a different editor. In this case, the binding subsystem will automatically notify the user. In this case, if your editor does not support the preferred kind, then use the highest fidelity kind in the contents property which your editor does support as the de facto preferred kind. Do not update the preferred kind property until externalize or ChangeKind is called on your part.

Externalize
   

This method may be called at any time. Depending on the Save model of the current document, and the idle-time optimizations which may or may not be present, your part may be told to externalize only when the user Saves a document, or as often as every minute. The point is, do not assume that when you are told to externalize, that it is for any one particular reason. As an optimization, your editor should probably keep an fDirty flag which is set whenever the user changes the part's content, and cleared whenever Externalization is completed. If your fDirty flag is clear then your externalize method should be a no op. Your editor should make sure it does the following:

  1. Clean up the storage unit. You need to prepare the storage unit for clean externalization from your part editor. You should only have to do this the first time externalize is called on your part whenever it is brought into memory. Remove any values that are not being updated. Specifically, this means remove any values which have value types/kinds which your editor does not support, or are of lower fidelity than kinds which you wish externalize.

  2. Add values if necessary. Use AddValue to create or recreate the value types you wish to externalize in proper fidelity order (first value, highest fidelity, to last value, lowest fidelity). Fidelity ordering is important, because OpenDoc binding and translation use the ordering of the values in the contents property to determine which value is probably of the highest fidelity to determine which editor would best edit any given part. These first two steps are called prepping your storage unit; there will be examples in other methods where you need to reprep your storage unit.

  3. Externalize your content in the format of the preferred kind. Do not write a different kind. There are ways for a user to explicitly change the preferred kind of a part. These will be discussed later. For now keep in mind that only the user should be changing the preferred kind of a part, and only then through an explicit action.

  4. OPTIONAL: Write out alternative kinds. As discussed above, your editor may want to write out one or more kinds in addition to the preferred kind. The typical part editor should by default only write out either the one preferred kind, or the preferred kind, and one interchange (or standard) kind. If you want your editor to write out many more representations than that, we recommend that the editor present a user interface in their settings or preferences dialog to allow the user to pick a set of kinds which your editor externalizes by default.

ExternalizeKinds
   

This method is called when the user does a Save a copy as multiple formats, or when a data interchange utility goes through a document asking all the parts to externalize themselves in a set of kinds extracted from a set of standard interchange kinds. Your editor should make sure it does the following:

  1. Externalize the set of kinds specified. Make sure that the fidelity ordering of the values in your contents property is maintained by creating the values for these kinds in the right order. You may need to reprep your contents property and recreate the values in order to make sure they are in the appropriate fidelity ordering. Be sure to write out these kinds in addition to the preferred kind, not instead of the preferred kind.

  2. Ignore unsupported kinds. Ignore any kinds in the set which you do not support.

ChangeKind
   

This method is called when the user changes the preferred kind of a part. This is usually done from the Part/Document Properties notebook, although do not assume it is the only user interface that can cause this method to be called. Your editor should make sure it does the following:

  1. Externalize the part in the given kind. Make sure that the fidelity ordering of the values in your contents property is maintained by creating the values for these kinds in the right order. You may need to reprep your contents property and recreate the values in order to make sure they are in the appropriate fidelity ordering.

  2. Set that kind to be the preferred kind. Write the given kind into the preferred kind property of the part.

CloneInto
   

This method is similar to externalize. It is usually called in response to a data interchange action such as using the clipboard or drag and drop. See the "Data Interchange" recipes for precise detail about implementing the CloneInto method. For the purposes of the multiple kind support however, make sure your editor does the following:

  1. Write the same kinds as if you were Externalizing plus any standard kinds you support. It is more important to write out standard kinds during CloneInto than externalize because when your CloneInto method is called, it is more likely that your part is in the middle of a data interchange operation, and should therefore be trying extra hard to enable the user to move content to a different editor or application.

  2. SetPromiseValue for each kind if you are using promises.

Resources Required

Your part editor tells OpenDoc what kinds it supports in response to its clsGetODPartKinds metaclass method. This method should return a sequence of PartKindInfo structures that contain the appropriate information for each part kind.

Multiple Kinds User Interface Description

Earlier we stated that the user should be the only thing which changes the preferred kind of a part. Here is how they do it. To change the preferred kind of an embedded part, the user does the following:

  1. Select the part.
  2. Choose Selection Properties from the Edit menu.
  3. Select from the choices in the "Kind:" listbox of the Type page.
  4. Optionally change the editor; some editors work better than others when it comes to certain kinds.

To change the preferred kind of the root part document, the user does the following:

  1. Choose Document properties from the Document menu.
  2. Select from the choices in the "Kind:" listbox of the Type page.
  3. Optionally change the editor; some editors work better than others when it comes to certain kinds.

In any discussion of preferred kinds, it makes sense to also describe preferred editors. Using the OpenDoc Preferences notebook the user can set a particular editor to be the preferred editor for a particular kind. Whenever the user encounters a part of that kind, whose previous editor is unavailable, the user set preferred editor for that kind is used to run that part. Similarly, the user can set a particular editor to be the preferred editor for a whole category, such as Other Plain Text. This means that whenever the user encounters a part whose kind is in the Plain Text category, whose previous editor is unavailable, and there is no preferred editor for that kind, the user set preferred editor for that category (Plain Text) is used.

Part Storage Model

Parts, like all other persistent objects have a storage unit in which they can store their content data and state persistently. However, parts are intimately involved in actions like binding, translation, data interchange (clipboard, drag and drop). As a result, parts need to satisfy many more requirements than the other persistent objects, and so they need to be more disciplined about how they use their storage units.

Rules and Requirements

  1. Parts need to be able to store multiple representations of their content, ordered by fidelity.

  2. Each representation of a part's content must be a full representation, that is it cannot just be a delta of another representation.

  3. It must be possible to extract exactly one representation from the part's storage unit, without understanding the format of any of the representations in the part.

  4. It must be possible to remove all but one representation from the part's storage unit, without understanding the format of any of the representations in the part.

  5. It must be possible to distinguish annotations/meta-information on/about the part, and content of the part.

  6. Annotations/meta-information on/about a part may go away at any time, independent of the part editor, and therefore cannot be depended on to store critical content. However, they can be used to store non-critical optimizations such as caches.

  7. Property names are ISO strings which constrain the naming conventions we can place on properties to that of ISO strings. The only naming conventions we can apply to ISO strings is that of prefix ownership, that is TC corporation may decide and control the format and meaning of all ISO strings beginning with TC.

  8. To achieve document interoperability across various platforms, in particular between machines with different endianess, parts have to adhere to the following guidelines:

    Values written out by parts must be in Little Endian format regardless of the endianess of the machine. OpenDoc provides a set of utility functions for writing standard type values to storage units. These functions will perform the data conversion to Little Endian format when necessary. The functions are:

    Alternatively, if a part needs to write out values to the storage unit without using the functions listed above, OpenDoc provides a set of conversion macros. These macros have to be called BEFORE writing out the values to the storage unit. Following are the macros that have to be called:

    Following is an example of using the conversion macro to write data of signed short type to the current focus value of the storage unit:

    ODSShort value = 12;
    ODSUForceFocus(ev, storageUnit, property, valueType);
    value = ConvertODSLongToStd(value);
    StorageUnitSetValue(storageUnit, ev, sizeof(ODSShort), &value);
    

    Similarly, values extracted from the storage unit have to be converted on a Big Endian machine before they can be used. Again, OpenDoc provides both utility functions and conversion macros for parts to use.

    The utility functions read standard type values from storage units and will format the data in Big Endian format if necessary. They are:

    If conversion macros are used, they should be called AFTER reading the values from the storage units. The list of conversion macros are:

    Below is an example of how to use the conversion macro:

     if (ODSUExistsThenFocus(ev, storageUnit, property, valueType);
     {
        ODSShort value;
        StorageUnitGetValue(storageUnit, ev, sizeof(ODSShort), &value);
        value = ConvertODSShortFromStd(value);
     }
    

Details of the Part Storage Model

As a result of the above rules/requirements, parts store their contents as follows:

No content should be stored outside of the contents property.

All other properties on a part's storage unit are for meta-information/annotations and should not contain content.

Example:

property OpenDoc:Property:Contents
         value OSA:Kind:TestDrag
         value OSA:Kind:StyledText
         value OSA:Kind:PlainText

Questions and Answers

Part Init and Externalizing

This document contains some sample code showing how a part editor externalizes and internalizes itself.

The sample code shown here is for a very simple part editor. Its assumptions may not be valid for a part editor with more functionality:

Proper Treatment of Permissions

The part is responsible for caching the draft permissions in InitPartFromStorage. The sample code does this by assigning a field:

fPermissions = storageUnit->GetDraft(ev)->GetPermissions(ev);

in the InitPartFromStorage. Whenever the potential for changing user content exists, the part should check to make sure fPermissions >= kDPSharedWrite before changing any of the content. Also, as a side effect, if fPermissions < kDPSharedWrite then fDirty should never be able to become kODTrue, and as a result, the externalize method will never write to the contents property.

PartWrapper

In addition to a storage unit, InitPart and InitPartFromStorage methods on ODPart also takes an extra parameter, partWrapper. The partWrapper parameter represents a delegator object which OpenDoc uses to insulate the rest of OpenDoc from having to have a direct pointer to your ODPart object. This feature helps implement features such as being able to change the editor of part without closing and reopening the document. Plus, it makes an excellent bottleneck for platform developers. For the most part, all the part wrapper does is delegate methods of the part function to your ODPart object.

The part editor must save this parameter in the part's internal field; you will see this in the sample code below. Whenever an OpenDoc function requires the part editor to pass in an ODPart* representing the part, instead of passing in somSelf, the part editor must pass in partWrapper stored in the part's internal field. An example is when registering the part for idle time. A part editor should never pass somSelf in as a parameter to an OpenDoc function call.

Sample Code for Initialization


// Includes the appropriate .XH files from OpenDoc
#include <StorageU.xh>
#include <StdProps.xh>
#include <POUtils.h>
  ...
// Define the part kind.
// This should probably be in your part's ....def.H file which
should
// be #included  in your .r file for your nmap resources
#define kMyPartKind "Sample:Kind:CMyKind"
  ...
void MyPartInitPart(MyPart* somSelf,
                    Environment* ev,
                    ODStorageUnit* storageUnit,
                    ODPart* partWrapper)
{
  ...
  // Call your parent's init routine
  parent_InitPart(somSelf, ev, storageUnit, partWrapper);

  // Set your dirty flag
  fDirty = kODTrue;

  // Save off the part-wrapper object
  fSelf = partWrapper;
}
void MyPartInitPartFromStorage(MyPart* somSelf,
                               Environment* ev,
                               ODStorageUnit* storageUnit,
                               ODPartWrapper* partWrapper)
{
  // Initialize the parent
  parent_InitPartFromStorage(somSelf, ev, storageUnit, partWrapper);

  // Focus to the value where the part Data is stored
  storageUnit->Focus(ev,
                     kODPropContents,
                     kODPosUndefined,
                     kMyPartKind,
                     0,
                     kODPosUndefined);

  // Get the size of the string
  fPartData[0] = storageUnit->GetSize(ev);

  // Get the string itself
  storageUnit->GetValue(ev, fPartData[0],&(fPartData[1]));

  // Save off the part-wrapper object
  fSelf = partWrapper;

  // Note the draft permissions, in order to avoid
  // writing to a read-only draft
  fPermissions = storageUnit->GetDraft(ev)->GetPermissions(ev);

  // Set the dirty flag to false
  fDirty = kODFalse;
}

Sample Code for Externalization


void MyPartExternalize(MyPart* somSelf,
Environment* ev)
{
  // Only externalize when the part has been updated.
  // Externalize the parent.
  parent_Externalize(somSelf, ev);

  if (fDirty)
  {
    // Get the part's storage unit
    ODStorageUnit* storageUnit = somSelf->GetStorageUnit(ev);
    if (storageUnit->Exists(ev, kODPropContents, kMyPartKind, 0))
    {
      // Focus to the desired value
      storageUnit->Focus(ev,
                         kODPropContents,
                         kODPosUndefined,
                         kMyPartKind,
                         0,
                         kODPosUndefined);
    }
    else
    {
      storageUnit->AddProperty(ev, kODPropContents);
      storageUnit->AddValue(ev, kMyPartKind);

      // Adding a property and value has the side effect of focusing
      // the storage unit to that value.
    }
    // Write out the part data
    storageUnit->SetValue(ev, fPartData[0],&(fPartData[1]));

    // Set the dirty flag to kODFalse to signify that the part is clean
    fDirty = kODFalse;
  }
}

Part Info 'Ternalization

Parts can attach part info data to their display frames. This data can be used to hold any information the part wants to associate with the display frame, and is stored persistently in the frame's storage unit. While the data is stored with the frame, the part editor is responsible for its 'ternalization, as the editor is the only entity that understands the structure and format of the part info.

Persistent Format

A frame's part info is stored in the frame's storage unit in the part info property. The standard name for this property is found in the constant kODPropPartInfo. Just as a part editor stores multiple representations of a part's contents in multiple values in the contents property, the editor should also store multiple representations of the part info which correspond to the representations of the contents. Some content formats may not have corresponding part info, especially interchange formats like JPEG or RTF. However, many editors will define formats with corresponding part info, and if your editor supports another editor's native format directly, it should also support the part info for that format.

The names of part info formats which are linked to a content format should be the same as the name of the content format, with the segment PartInfo appended to the end of the ISO name. For example, if your part content format was SurfSoft:SurfText 1.0 the part info should be SurfSoft:SurfText1.0:PartInfo.

For a part which externalized representations for the formats SurfText 1.0, RTF, ClarisWorks 3.0, and ASCII, its display frames should have part info with formats SurfSoft:SurfText1.0:PartInfo and Claris:ClarisWorks 3.0:PartInfo (assuming the interchange formats have no associated part info).

'Ternalizing Part Info

There are three calls in the part function that deal with 'ternalization of part info:

In all of these calls, the frame passes a StorageUnitView which is focused to the frame's part info property, but not to any value within that property. Your part editor must get the view's storage unit, and focus it to value(s) in that property as necessary to read or write the formats it needs to.

When to Write

When externalizing a property of a persistent object, it is only necessary to write the data if it is the first time writing it, or if it has changed from the last time the object was externalized. As an optimization, you can keep an isDirty flag in a frame's part info, indicating whether it has changed from when it was internalized or last externalized. In WritePartInfo, if the flag is false, skip externalizing; if the flag is true, externalize the data and set the flag to false. Note that you must always externalize part info when cloning (in ClonePartInfo), as you are making a new copy.

Just as a part must mark the draft as dirty when its content needs to be externalized, it must also mark the draft dirty when part info needs to be externalized. The part should call draft->SetChangedFromPrev() when part info has changed and needs to be externalized. Note that changes to part info in a non-persistent frame should not mark the draft dirty.

Sample Code

This code 'ternalizes a simple part info structure. The only value in it is an RGB color describing the background color of the frame. The forDebuggingRGBColor intermediate value makes debugging easier.

#include <ISOStr.h>
#include <StorageU.xh>
#include <SUView.xh>
#include <StorUtil.h>
  ...
ODInfoType CMyPart::ReadPartInfo(Environment* ev,
                                 ODFrame* frame,
                                 ODStorageUnitView* storageUnitView)
{
  ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
  ODPropertyName propName = storageUnitView->GetProperty(ev);
  PartInfoRec* partInfo = kODNULL;

  if (ODSUExistsThenFocus(ev, su, propName, kKindSamplePartInfo))
  {
    partInfo = new PartInfoRec;

    RGBColor forDebuggingRGBColor ;
    StorageUnitGetValue(su,
                        ev,
                        sizeof(RGBColor),
                        (ODValue)&forDebuggingRGBColor);

    partInfo->bgColor = forDebuggingRGBColor;
    partInfo->isDirty = kODFalse;
  }
  SOMFree(propName);
  return (ODInfoType)partInfo;
}
void CMyPart::WritePartInfo(Environment *ev,
                            ODInfoType partInfo,
                            ODStorageUnitView* storageUnitView)
{
  if (partInfo && partInfo->isDirty)
  {
    ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
    ODPropertyName propName = storageUnitView->GetProperty(ev);
    ODSUForceFocus(ev, su, propName, kKindTestDraw);

    RGBColor forDebuggingRGBColor ;
    forDebuggingRGBColor = ((PartInfoRec*)partInfo)->bgColor;
    StorageUnitSetValue(su,
                        ev,
                        sizeof(RGBColor),
                        (ODValue)&forDebuggingRGBColor);

    partInfo->isDirty = kODFalse;
    SOMFree(propName);
  }
}
void CMyPart::ClonePartInfo(Environment *ev,
ODInfoType partInfo,
ODStorageUnitView* storageUnitView)
{
  if (partInfo)
  {
    ODStorageUnit* su = storageUnitView->GetStorageUnit(ev);
    ODPropertyName propName = storageUnitView->GetProperty(ev);
    ODSUForceFocus(ev, su, propName, kKindTestDraw);

    RGBColor forDebuggingRGBColor ;
    forDebuggingRGBColor = ((PartInfoRec*)partInfo)->bgColor;
    StorageUnitSetValue(su,
                        ev,
                        sizeof(RGBColor),
                        (ODValue)&forDebuggingRGBColor);

    SOMFree(propName);
  }
}

Display Frame 'Ternalization

A well behaved part editor needs to keep track of its display frames, and store and retrieve them from an annotation property on its storage unit. This document contains the list of methods affected, and a brief description of their responsibilities. The sample code documents step by step how to implement this functionality in those methods.

Caveats

The following sample code:

Methods Affected

Constructor

Clear fDisplayFrames.

Destructor

Delete fDisplayFrames if it is not null, and set it to null.

InitPart

Create a new, empty collection (fDisplayFrames) in which to store a list of your display frames.

DisplayFrameAdded

Add the frame passed in to fDisplayFrames.

DisplayFrameConnected

Add the frame passed in to fDisplayFrames if it is not already in the list.

DisplayFrameRemoved

Remove the frame passed in from fDisplayFrames.

DisplayFrameClosed

Remove the frame passed in from fDisplayFrames.

Externalize

If necessary create a DisplayFrames annotation property and StorageUnitRefs value on your storage unit. Otherwise just focus to your DisplayFrames annotation property and its StorageUnitRefs value. Write out a list of references to your display frames from fDisplayFrames.

InitPartFromStorage

Focus to your DisplayFrames annotation property and its StorageUnitRefs value. Read in the list of your display frames into fDisplayFrames. See the "Lazy Frame Internalization" recipe for detail.

ReleaseAll

Assert that you fDisplayFrames collection is empty.

Sample Code


void CMyPart::CMyPart()
{
  ...
  fDisplayFrames = kODNULL;
  ...

}
void CMyPart::~CMyPart()
{
  ...
  if (fDisplayFrames != kODNULL)
  {
    delete fDisplayFrames;
    fDisplayFrames = kODNULL;
  }
  ...
}
void CMyPart::InitPart(Environment* ev,
                       ODStorageUnit* storageUnit,
                       ODPart* partWrapper)
{
  ...
  // Create a list to keep track of the frames we are being
  // displayed in
  fDisplayFrames = new CLinkedList;
  ...
}
void CMyPart::InitPartFromStorage(Environment* ev,
                                  ODStorageUnit* storageUnit,
                                  ODPart* partWrapper)
{
  ...
  // Create a list to keep track of the frames we are being
  // displayed in
  fDisplayFrames = new CLinkedList;

  // Read in our list of display frames
  storageUnit->Focus(ev,
                     kODPropDisplayFrames,
                     kODPosUndefined,
                     kODWeakStorageUnitRefs,
                     0,
                     kODPosUndefined);
  ODULong size = storageUnit->GetSize(ev);
  storageUnit->SetOffset(ev, 0);

  for (ODULong offset = 0; offset < size; offset += sizeof(ODStorageUnitRef))
  {
    StorageUnitGetValue(storageUnit,
                        ev,
                        sizeof(ODStorageUnitRef),
                        weakRef);

    if (storageUnit->IsValidStorageUnitRef(ev, weakRef))
    {
      ODFrame *frame = storageUnit->
                       GetDraft(ev)->
                       AcquireFrame(ev,   // Calls my DisplayFrameConnected
                       storageUnit->
                       GetIDFromStorageUnitRef(ev, weakRef));
      frame->Release(ev);
    }
  }
  ...
}
void CMyPart::DisplayFrameAdded(Environment* ev,
                                ODFrame* frame)
{
  ...
  frame->IncrementRefCount(ev);
  fDisplayFrames->Add((ODPtr)frame);
  ...
}
void CMyPart::DisplayFrameConnected(Environment* ev,
                                    ODFrame* frame)
{
  ...
  if (!fDisplayFrames->Contains((ODPtr)frame))
  {
    frame->IncrementRefCount(ev);
    fDisplayFrames->Add((ODPtr)frame);
  }
  ...
}
void CMyPart::DisplayFrameRemoved(Environment* ev,
                                  ODFrame* frame)
{
  ...
  fDisplayFrames->Remove((ODPtr)frame);
  frame->Release(ev);
  ...
}
void CMyPart::DisplayFrameClosed(Environment* ev,
                                 ODFrame* frame)
{
  ...
  fDisplayFrames->Remove((ODPtr)frame);
  frame->Release(ev);
  ...
}
void CMyPart::Externalize(Environment* ev)
{
  ...
  ODStorageUnitRef weakRef;
  ODStorageUnit* su = this->GetStorageUnit(ev);
  ODSUForceFocus(su, kODPropDisplayFrames, kODWeakStorageUnitRefs);

  // Persistent object references are stored in a side table, rather than
  // in the property/value stream.
  // Thus, deleting the contents of a value will not delete the references
  // previously written to that value.
  // To completely delete all references written to the value, we must
  // remove the value and add it back.
  su->Remove(ev);
  su->AddValue(ev, kODWeakStorageUnitRefs);

  for (CLink* link = fDisplayFrames->First();
       link->Content();
       link = link->Next())
  {
    frame = (ODFrame*) link->Content();
    frameID = frame->GetID(ev);

    // Write out weak references to each of the part's display frames
    su->GetWeakStorageUnitRef(ev, frameID, weakRef);
    StorageUnitSetValue(su, ev, sizeof(ODStorageUnitRef), weakRef);
  }
  ...
}
void CMyPart::ReleaseAll(Environment* ev)
{
  ...
  ASSERT(fDisplayFrames->First() == kODNULL);
  ...
}

Lazy Frame Internalization

Winning through laziness:

There are situations in OpenDoc where eagerly doing work as soon as possible is not the best thing to do, and the work should be deferred until it really needs to be done. Internalization is usually a good candidate for being lazy. In general, do not internalize data until it is needed, or you can predict it will be needed soon (for example you might pre-fetch the next page so scrolling will be fast). A particular case of lazy internalization involves your part's frames, both display frames and embedded frames.

Since OpenDoc places very few requirements on a part's internal representation, your part is free to organize its data however, it pleases. The only requirements relevant to frames are that your part must be able to supply an EmbeddedFramesIterator for its embedded frames, and it should externalize a list of its display frames in a standard property of its storage unit.

For very simple containing parts, there will not be much advantage in lazily internalizing embedded frames. But for large containing parts, the advantages can be quite significant. For example, a containing part which embeds five thousand frames (think CD-ROM here) would not want to internalize all of those frames at the same time. There would be no way to display all the frames simultaneously, and a large amount of memory would effectively be wasted on keeping those persistent objects resident. All parts should consider lazy internalization of display frames, since your part has no control over the number of display frames that may be added to it.

How To Be Lazy

The main trick to lazy frame internalization is stopping before you go too far. In InitPartFromStorage, you should stop short of calling draft->GetFrame(). Instead, just hang on to the frame ID that you got from the storageUnitRef. (Remember that display frame references should be weak, so they may not actually have a frame or ID at the other end). The minimal way to deal with this ID is to create a structure that can hold a frame pointer and a frame ID. Anywhere in your code you would have stored a frame reference, store one of these structures instead. If the structure does not have a frame yet, use the ID to get it, then store it in the structure for next time (Remember to increment the frame's reference count.)

For slightly more effort up-front, you can have a much easier time of it later. You can create a class that stores a frame reference and a frame ID, and does the GetFrame for you when necessary. The class Futon below is an example of such. (Do not try to read any significance into the name. It is just a random word that was easy to type).

Getting Laziness to Work for You

Now that your part is more relaxed about internalizing its frames, here is how it can take advantage of being lazy.

Sample Code


#define  INCL_ODAPI
#define  INCL_ODFRAME
#define  INCL_ODDRAFT
#include <ODos2.h>
#include <ODUtils.h>
class Futon
{
public:
  Futon(ODDraft* draft, ODID frameID);
  ~Futon();

  ODFrame* GetFrame(Environment* ev);
  void Purge(Environment* ev);

  ODDraft* fDraft;
  ODID fFrameID;
  ODFrame* fFrame;
};
Futon::Futon(ODDraft* draft, ODID frameID)
{
  // Note:
  // I am not reference counting the draft, because the draft is
  // referenced from the part's storage unit, so it is
  // not going away while the Futon (and thus the part)
  // is still around.
  fDraft = draft;
  fFrameID = frameID;
  fFrame = kODNULL;
}
Futon::~Futon()
{
  ODSafeReleaseObject(fFrame);
}
ODFrame*
Futon::GetFrame(Environment* ev)
{
  if (fFrame == kODNULL)
    fFrame = fDraft->GetFrame(ev, fFrameID);
    return fFrame;
}
void
Futon::Purge(Environment* ev)
{
  ODReleaseObject(ev, fFrame);
}


[ Top | Previous | Next | Contents | Index | Documentation Homepage ]