quantum.
30
Then we call BigPointerBlock::SetMainObjectNumber to
set the entry in the big pointer block that indicates which main object it belongs to.
This is needed when we are updating the free space list, because each free space
entry includes the number of the main object for that quantum. This is relevant
because we do not share a quantum among several main objects, to simplify the
task of programs that recover as much data as possible from a quantum file that has
become corrupted.
The next step is to set up the header for the big pointer array. This contains the
current element count, the maximum element count, and the last quantum that
we've added an element to; of course, the latter variable is initialized to
NoQuantum, as we have not yet added any data to this new object. Once we have
set the header up, we call QuantumBlock::AddItem to add it to the big
pointer block, so that it will be accessible the next time we use the big pointer
array.
Now it's time to initialize the little pointer array for this object and the big pointer
array that allows us to access the little pointer array. To start this process, we
calculate the number of little pointer blocks that we will need, which is dependent
on the number of elements in the object and the number of item references that fit
in one block. Once we have determined that number, we can create a big pointer
array containing that number of quantum numbers, and a temporary little pointer
array that we will use to initialize each block in the little pointer array.
We're up to the loop that initializes all of the blocks in the little pointer array. It
begins by calling QuantumFile::FindEmptyBlock to find an empty block
that we can use for the little pointer array. Then we call
QuantumFile::SetFreeSpace to reserve this quantum for the current main
object and mark it as full.
31
Next, we set the current element of the big pointer
array to the quantum number of the quantum where we are storing the current little
pointer array block.
Now we're ready to initialize the little pointer block. We start by calling
QuantumFile::MakeLittlePointerBlockPtr to allow us to treat the
current quantum as a little pointer block. Once we have done that, we call
LittlePointerBlock::Clear to initialize the quantum to an unused state,
LittlePointerBlock::SetQuantumType to set the quantum type to
"little pointer array" (for consistency checking while retrieving data),
LittlePointerBlock::SetMainObjectNumber to allow us to update
the free space list when adding data to this quantum,
QuantumBlock::AddItem to add a section of the little pointer array to this
quantum, and LittlePointerBlock::ClearLittleArray to initialize
that section of the little pointer array to null item references.
One detail that might not be immediately obvious is why we have to call
QuantumFile::SetFreeSpace again at the end of the loop. The reason is
that the QuantumBlock::AddItem function recalculates the free space for the
block to which it adds the item. Since we want to prevent any further data from
being added to this block, we have to reset the free space manually (to "none
available") to indicate that this block is full.
Once we have initialized all of the little pointer array sections, we're ready to finish
setting up the big pointer array for use. The first step in that process is to add the
big pointer array data we have just filled in to the big pointer quantum, via
QuantumBlock::AddItem. Then we use QuantumFile::SetFreeSpace
to reserve the big pointer quantum for our new main object and mark it full. Then
we use the member function Set to set the big pointer array quantum number for
our new main object. Finally, we call the member function PutElement to set up
the directory entry for the new main object.
The MainObjectArray::FindObjectByName Function
The next function we'll cover, MainObjectArray::FindObjectByName
(Figure newquant.20), is used to look up a main object in the "directory".
The MainObjectArray::FindObjectByName function (from
quantum\newquant.cpp) (Figure newquant.20)
codelist/newquant.20
This one is fairly simple. It steps through the elements of the main object directory,
looking for an element whose contents are equal to the argument to the function. If
it finds such an element, it returns the index of that element, which is the object
number of the object with that name. On the other hand, if it gets to the end of the
main object directory without finding a match, it returns the value NoObject.
The MainObjectArray::GrowMainObject Function
The next function, MainObjectArray::GrowMainObject (Figure
newquant.21), is used to increase the size of a main object when needed to
accommodate more elements.
The MainObjectArray::GrowMainObject function (from
quantum\newquant.cpp) (Figure newquant.21)
codelist/newquant.21
This is about as complicated as CreateMainObject. However, because it is
quite similar to that function, we'll be able to shorten the explanation considerably.
We start by retrieving the existing big pointer block for the object. Then we copy
the big pointer array from that block to a new big pointer array. Next, we calculate
the size of the new little pointer array needed to hold references to all the elements
in the new, enlarged, object.
The next few lines of code might be a little mysterious if I don't explain a few
details of the size calculation. To simplify the accounting needed to keep track of
the sizes of all of the little pointer arrays, I've decided always to allocate a little
pointer array to the largest size that will fit into a quantum. However, setting the
accessible size of an array to the actual number of elements allocated for its little
pointer arrays has the drawback that the user wouldn't be able to find out when the
program accessed an element that is within the allocated size but outside the
number of elements specified when creating the array. Therefore, I've crafted a
compromise: when an array is created, I set the number of accessible elements to
the number the user specifies
32
; however, if the array is increased in size, the new
size is always equal to the total allocation for all little pointer arrays. This means
that if an array is expanded by a reference to an element that was already allocated
but was outside the specified array size, GrowMainObject simply resets the size
to the number of elements contained in all little pointer arrays already extant and
returns that value. Otherwise, a new little pointer array is allocated, and the big
pointer array is updated to reflect the change.
Assuming that we actually do need to allocate some new little pointer blocks, we
continue by increasing the size of the big pointer array to accommodate the new
number of little pointer blocks. Then we allocate and initialize each of the new
little pointer blocks in exactly the same way as we allocated and initialized the
original little pointer blocks when we created the object. Then we delete the old big
pointer array from the big pointer block, add the new big pointer array to the big
pointer block, set the free space entry for the big pointer block to indicate that it is
full, and return the new element count to the caller.
Now that we have reached the end of the discussion of the MainObjectArray
class, we'll return to the QuantumFile class. This time, we're going to get
into the member functions that are used in the implementation, since we've already
dealt with those that are used by outside programs.
The QuantumFile class
We'll skip over the QuantumFile member functions that fall in three categories:
1. Those that merely compute the number of blocks occupied by a segment of the
quantum file (i.e., GetMainObjectBlockCount and
QGetFreeSpaceListBlockCount).
2. Those that merely return a block of the appropriate type to be accessed as a little
pointer block, big pointer block, and the like (i.e., MakeFreeSpaceBlockPtr,
MakeMainObjectBlockPtr, MakeLittlePointerBlockPtr,
MakeBigPointerBlockPtr, MakeLeafBlockPtr).
3. Those that merely call member functions of the FreeSpaceArray class
(i.e., SetFreeSpace, QGetFreeSpace, FindEmptyBlock,
FindSpaceForItem).
We'll see how the functions in the third category work when we examine the
underlying functions that they call.
The QuantumFile::SetModified Function
However, that still leaves us with several functions to discuss in the
QuantumFile class. We'll start with QuantumFile::SetModified,
which is shown in Figure block.06.
The QuantumFile::SetModified function (from quantum\block.cpp) (Figure
block.06)
codelist/block.06
This is actually a fairly simple function. It calls a global function called
FindBuffer to look up the specified quantum number in the list of quantum
buffers.
33
If the quantum with that quantum number is in fact in one of the quantum
buffers, then the "buffer modified" flag for that buffer is set, and the function
returns. The only complication is a consistency check in the form of an "assert"
that the quantum in question is actually in one of the buffers. If that is not the case,
then we have a serious problem, because the program should not be trying to set
the modified flag for a quantum that isn't in memory: that indicates that the buffer
that contained the quantum has been reused too soon.
The QuantumFile::Position Function
The next function we will examine is QuantumFile::Position, which is
shown in Figure block.07.
The QuantumFile::Position function (from quantum\block.cpp) (Figure
block.07)
codelist/block.07
This function is used by the Read and Write functions to position the file pointer
to the correct place for reading or writing a quantum. This is quite simple because
every block is the same size. However, it is important to remember that the
quantum file contains data other than the quanta themselves, so the block number
of a quantum is not the same as its quantum number. To convert between these two
numbers is the responsibility of the function that computes the block number
supplied to Position.
The QuantumFile::Read Function
The next function we will examine is QuantumFile::Read, which is shown in
Figure block.08.
The QuantumFile::Read function (from quantum\block.cpp) (Figure block.08)
codelist/block.08
After positioning the file pointer to the location in the file where this block is
stored, we read it into the buffer. Then we set the modified flag for the buffer to
FALSE, indicating that the disk and memory versions of the block are the same.
Finally, we increment the read count, which is used for performance analysis.
The QuantumFile::Write Function
The next function we will examine is QuantumFile::Write, which is shown
in Figure block.09. This is identical to the Read function, except of course that it
writes the data to the file rather than reading it.
The QuantumFile::Write function (from quantum\block.cpp) (Figure block.09)
codelist/block.09
The QuantumFile::MakeBlockResident Function
The last function in this class is QuantumFile::MakeBlockResident. This is where
we rejoin the thread of discussion about operator->. You see, MakeBlockResident