You are here: Elements > Decoder Data Objects > Using Decoder Data Objects for per-Frame Data

Using Decoder Data Objects for per-Frame Data

As we have indicated, you can use a data object to hold data that has any scope less than or equal to one analysis session. The most common use of data objects, however, is to keep state data that may change with each frame. This is where the PUT_DATA_INTO_A_BYTE_ARRAY, OPTIMIZE_DATA, and GET_DATA_FROM_A_BYTE_ARRAY sections come into play.

PUT_DATA_INTO_A_BYTE_ARRAY is called just before the first time a data object is called in a frame during the compilation phase. It puts the data object's data into byte array abStatic. A good way of saving the data into abStatic is with class CStaticStorage (described below). If nothing needs to be saved then abStatic should be sized to zero bytes. It is perfectly okay to save a different amount of data for different frames.

What data should you save? The answer is the exact data you will be needing to reconstruct the frame. In the PUT_DATA_INTO_A_BYTE_ARRAY section you do not know precisely what data that will be because it is called before the frame is compiled. If you include an OPTIMIZE_DATA section, it is called when the frame has finished compiling. At that point you can then figure out which data is really necessary, and you can change abStatic to make it shorter. Here is an example:

@OPTIMIZE_DATA

/*

This should not save the items that don't need to be saved. Create a copy of the data so we can manipulate it without changing this instance. We then initialize it with the data that would be saved if this routine didn't exist.

*/

CDecoderStaticMyStatic old("");

old._ReadDataFromAByteArray(abStatic);

/*

We now figure out which values were actually used. In this example, let's assume that the data consists of an array, only one of which is actually used for any particular frame. We then only have to save that one entry. We set a boolean when we process a particular entry.

*/

bool bChanged = false;

for (int i = 0; i < 255; i++)

{

if (!old.m_abJustUsed[i])

{

old.m_aBigDataItem[i].RemoveAll();

bChanged = true;

}

}

if (bChanged)

old._PutDataInAByteArray(abStatic);

When a frame is being reconstructed (such as when a decode for a frame is to be displayed), then the data for the chosen frame is copied from the FRM file to abStatic and GET_DATA_FROM_A_BYTE_ARRAY is called to recreate the state of the data object as it was when that frame was compiled. If abStatic is empty that is always an explicit reset signal; GET_DATA_FROM_A_BYTE_ARRAY will not be called in cases when no data was saved. Instead, the RESET section is executed.

We suggest that when you first write your method, you use only PUT_DATA_INTO_A_BYTE_ARRAY and GET_DATA_FROM_A_BYTE_ARRAY (i.e. don't use OPTIMIZE_DATA) and save your entire object until you have your decoder debugged. You may find, though, that if you have a lot of data in your data object that the FRM file becomes much too large. If you find that to be the case, you may be able to save less data (sometimes much less) by using OPTIMIZE_DATA as described above.

OPTIMIZE_DATA is also needed when a FRAME_BEFORE_DECODING statement is being run by the decoder. A FRAME_BEFORE_DECODING statement is executed during compilation but not during reconstruction. Therefore, the data read by GET_DATA_FROM_A_BYTE_ARRAY during reconstruction must include the output of the method invoked by FRAME_BEFORE_DECODING. PUT_DATA_INTO_A_BYTE_ARRAY doesn't help because it runs before FRAME_BEFORE_DECODING. OPTIMIZE_DATA does the trick because it runs after the frame is compiled. Here's an example (the method invoked by FRAME_BEFORE_DECODING is not shown):

DECODER_STATIC pima_superframe

public:

bool m_bSuperframing;

int m_iBytesLeft;

Abyte m_abFrameSoFar;

 

@RESET

m_bSuperframing = false;

m_iBytesLeft = 0;

m_abFrameSoFar.RemoveAll();

 

@PUT_DATA_INTO_A_BYTE_ARRAY

abStatic; // get rid of the "unreferenced" warning

 

@OPTIMIZE_DATA

CStaticStorage ss(m_abFrameSoFar.GetSize()+8);

ss.Store(m_bSuperframing);

ss.Store(m_iBytesLeft);

if (m_abFrameSoFar.GetSize() < 32000)

ss.Store(m_abFrameSoFar);

else

{

Abyte abTruncatedFrame;

abTruncatedFrame.Append(m_abFrameSoFar);

abTruncatedFrame.SetSize(32000);

ss.Store(abTruncatedFrame);

}

ss.RetrieveStream(abStatic);

 

@GET_DATA_FROM_A_BYTE_ARRAY

CStaticStorage ss(abStatic);

ss.Get(m_bSuperframing);

ss.Get(m_iBytesLeft);

ss.Get(m_abFrameSoFar);

 

@END_STATIC

Exactly when are these sections called? The short answer is only when necessary. They are not called unless the data is actually used in the current frame. That means that a FIELD must be executed that contains a method that is defined with the USES_STATIC line in it for that particular data object. Normally you won't have to worry about that, but that distinction can be confusing as you are debugging.

Frames are preprocessed as they come in (either from a live source or a capture file) to create the FRM file of per-frame data object snapshots. This file is normally saved between runs so this time-consuming preprocessing need be done only once. We must note one caveat about this process: When you load a capture file, a background thread is launched to perform the preprocessing. Frames that have been captured but not yet preprocessed are marked with green circles in the left margin of the Summary Pane. Such frames are decoded when they are displayed, but since the context provided by preprocessing is absent the decode might be wrong. Once the frame is preprocessed, it is marked with a green dot if it contains the layer in the Summary dropdown, otherwise it's not marked at all (the left margin is blank).