Chapter 12. Database Paging

Many scene graphs you use are too large to fit into system memory. Consequently, you need to load data dynamically at run time. Because loading data from a hard drive is relatively slow, to prevent breaking the frame rate, you should:

This chapter describes how to page the database efficiently in the following sections:

Anticipating Paging

Because many scene graphs are too large to hold into system memory, your application must anticipate which pages of memory to load and which to delete. Pages of memory are often associated with nodes in the scene graph: a node encapsulates a part of the scene which occupies a page of memory.

Figure 12-1 shows pages of memory represented as squares; each page corresponds to a node in the scene graph. The triangle represents the position and direction of the motion of the eyepoint.

Figure 12-1. Memory Pages

Memory Pages

In Figure 12-1, pages 6, 7, 10, 11, 12, 15, and 16 are currently in memory, pages 1, 2, and 5 are good candidates for loading, and pages 12, 15, and 16 are good candidates for removal.

Database Process

Because loading data from disk is relatively slow, loading deserves its own process so that it can run continuously and asynchronously in the background.

To use a database paging process, you must:

  • Fork off a DBASE process.

  • Call a database function of your creation, which handles memory allocation and deallocation, and the loading of the data.

The following code performs those tasks:

pfInit();
pfMultiprocess( PFMP_FORK_DRAW | PFMP_FORK_DBASE );
pfConfig();
 
pfDBaseFunc( myDBaseFunc );

myDBaseFunc, of type pfDBaseFuncType, needs to handle data loading and memory allocation and deallocation.

Handling Memory for the DBASE Process

The APP and DBASE processes need to share data. They reside, however, in separate virtual memory spaces. To share data, they must allocate memory in the shared memory arena, as the following code shows:

typedef struct (
    pfScene *Scene;
) SharedData;
 
SharedData *shared;
void *arena;
 
arena = pfGetsharedArena();
shared = (SharedData *)pfMalloc( sizeof(SharedData), arena );
 
shared->scene = pfNewScene();

These lines of code, except for the last, must be placed between pfInit and pfConfig. To deallocate the memory malloc'd, use pfFree.

The final line of code makes the scene node, the root node of the scene graph, accessible to the DBASE process.

Changing the Scene Graph

Because of user interaction, such as moving through a scene, the scene graph in memory often changes: nodes representing pages of memory are deleted or added to the scene graph according to the motion of the eyepoint. The DBASE process should not change the scene directly because it should anticipate where the eyepoint will go. If the process were to change the scene graph immediately, the anticipated page of memory would likely display too soon. Instead, the DBASE process should:

  1. Cache scene graph changes in a pfBuffer.

  2. Add and remove nodes from the scene graph in the buffer.

  3. Delete nodes removed from the scene graph in the buffer.

  4. Merge the changes from the buffer into the scene graph when the APP process calls pfSync.

  5. Carry out the deletion request.

The following sections explain how to perform these tasks.

Caching Scene Graph Changes

Instead of changing the scene graph directly, you should:

  1. Create a buffer.

  2. Make it active.

  3. Create the necessary scene graph changes in the buffer.

The following lines of code complete these tasks.

pfBuffer *buf;
node *d, *e;
 
buf = pfNewBuffer();
pfSelectBuffer( buf );
d = pfNewGroup();
e = pfNewGeode();
pfAddChild( d, e );

Figure 12-2 shows how a buffer is created and how nodes are created and grouped.

Figure 12-2. Creating the Buffer and Changes

Creating the Buffer and Changes

Linking Buffer Changes to the Scene Graph

Once the scene graph in the buffer is complete, you must connect the changes to the main scene graph. To remove node C and connect the scene graph in the buffer to node A, use the following lines of code:

pfBufferRemoveChild( a, c );
pfBufferAddChild( a, d );

These lines of code request but do not cause the actions to be performed. The actions are performed with the next call to pfSync.

Deleting Old Data

Once you request a node to be removed, you should request that it be deleted as well. The following line of code removes node C:

pfAsyncDelete( c );

This code requests the deletion, but the deletion is not performed until the next call to pfSync.

Figure 12-3 shows the linking and deletion of nodes.

Figure 12-3. Linking and Deleting Nodes

Linking and Deleting Nodes

Figure 12-3 shows that although node C was removed and deleted, its data remains in the cache.

Merging Changes

To make the changes to the main scene graph take effect when pfSync() is called, you must merge the changes, as follows:

int success;
 
success = pfMergeBuffer();

success is non-zero if the merge is successful.

When you merge the buffers, the following occurs:

  • Nodes D and E are placed in the scope of the main scene graph buffer.

  •  The buff buffer is cleared.

Figure 12-4 shows these changes.

Figure 12-4. Merging Scene Graph Changes

Merging Scene Graph Changes

The merge is not performed until the next call to pfSync.

Cleaning Up the Cache

To completely delete removed nodes from system memory, but not the hard drive, call the following method from the DBASE process:

void pfDBase(void);

This method carries out the deletion specified in pfAsyncDelete(). Be sure to call it after pfMergeBuffer().