Saving and Reloading a Custom Container

Among other nice features, the .red file format is extensible. It means that users can enrich RED data by streaming their own chunks of data right inside a .red file. This tutorial demonstrates how to do that along with another helpful HOOPS Luminate mechanism: user data.

User Data

Almost every resource or data class in HOOPS Luminate implements the RED::IUserData interface (including shapes, materials, images, fonts…). This interface lets you concatenate your own data to the corresponding RED object. The data can be of any class as long as they inherit from the RED::UserData class. A default implementation of a user data class is provided with HOOPS Luminate: RED::UserRawData.

In this tutorial we’ll implement our own class inheriting from RED::UserData and we’ll add to it the capability of being saved and loaded from .red file.

The only method of RED::UserData is the UnlinkFrom method. Property of user data can be delegated to RED objects when set. In that case, the user data will be deleted when the corresponding RED object will be deleted. Otherwise, the caller keeps the ownership of the user data and get called through the UnlinkFrom callback when the corresponding RED object is destroyed. It’s up to the user to choose the management policy for each user data. As we won’t care about that point in this tutorial, we’ll give full ownership of the user data to the RED object and keep the UnlinkFrom method empty.

Here is the skeleton of our class:

class MyClass : public RED::UserData, public RED::IChunkSaver
{
    public:

    // Typing information:
    // -------------------

    // Make sure the Class ID and the chunk signature are unique. Existing Class IDs can be
    // found in REDCID.h and custom chunk signatures must be in the [0x0000ffff, 0xffffffff] range.
    SET_CID( 0x01234567 ); IMPLEMENT_AS();
    SET_CHUNK_SIGNATURE( 0xffffffff ); IMPLEMENT_CHUNKINTERPRETER();

    MyClass():
        _string( "Dummy string" )
    {
    }


    ~MyClass()
    {
    }


    // Inherited from RED::UserData.
    void UnlinkFrom( RED::Object* iObject )
    {
        // Do nothing as we'll delegate the property of the user data to the corresponding
        // RED object.
    }


    // Inherited from RED::IChunkSaver.
    RED_RC Save( RED::IStream*          iStream,
                RED::StreamingPolicy&  iPolicy,
                int                    iState = -1 ) const;


    /** Dummy string.
    */

    RED::String _string;
};

Our user data will consist of a simple string which should be saved along with a viewpoint in a .red file.

Dynamic Casting Mechanism

Support for the dynamic casting mechanism of HOOPS Luminate (As methods) is required as our class inherits from RED::IObject (through RED::IChunkSaver). Because our class inherits from several classes, the As methods should check for several input CID values and return the corresponding cast object:

void* MyClass::As( const RED::CID& iCID )
{
    // HOOPS Luminate casting operator implementation.
    if( iCID == MyClass::GetClassID() )
    {
        return this;
    }
    else if( iCID == RED::UserData::GetClassID() )
    {
        return (RED::UserData*)this;
    }
    else if( iCID == RED::IChunkSaver::GetClassID() )
    {
        return (RED::IChunkSaver*)this;
    }

    return RED::Object::As( iCID );
    }


    const void* MyClass::As( const RED::CID& iCID ) const
    {
    // HOOPS Luminate casting operator implementation.
    if( iCID == MyClass::GetClassID() )
    {
        return this;
    }
    else if( iCID == RED::UserData::GetClassID() )
    {
        return (RED::UserData*)this;
    }
    else if( iCID == RED::IChunkSaver::GetClassID() )
    {
        return (RED::IChunkSaver*)this;
    }

    return RED::Object::As( iCID );
}

Saving the User Data

The saving of the user data is accomplished through the MyClass::Save method declared by the RED::IChunkSaver interface. In our case this consists of writing the user data string to the given stream and check for errors:

RED_RC MyClass::Save( RED::IStream*          iStream,
                    RED::StreamingPolicy&  iPolicy,
                    int                    iState ) const
{
    if( iStream == NULL )
        return RED_BAD_PARAM;

    // A valid chunk always starts with a chunk signature (32bit value) and a
    // size in bytes (64bit value).

    // Store the current stream position.
    RED::uint64 strpos;
    RC_TEST( iStream->GetCurrentPosition( strpos ) );

    // Write the chunk signature.
    RC_TEST( iStream->WriteDWord( MyClass::GetChunkSignature() ) );

    // Write the chunk size (unknown at that moment).
    RC_TEST( iStream->WriteDWord( 0 ) );
    RC_TEST( iStream->WriteDWord( 0 ) );

    RC_TEST( iStream->WriteString( _string ) );

    // Now that we're done with the chunk content, we can move back to the start of the chunk in the
    // stream and write the actual size of the chunk in bytes.

    // Backup the current position in the stream.
    RED::uint64 strpos2;
    RC_TEST( iStream->GetCurrentPosition( strpos2 ) );

    // Set the stream position to the start of the chunk size field.
    RC_TEST( iStream->Move( strpos + 4 ) );

    // Write the chunk size.
    RC_TEST( iStream->WriteDDWord( strpos2 - strpos - 12 ) );

    // Move back to the end of the chunk in the stream.
    RC_TEST( iStream->Move( strpos2 ) );

    return RED_OK;
}

Here is the viewpoint creation code and how we set the user data:

RED::Object* viewpoint = RED::Factory::CreateInstance( CID_REDViewpoint );
if( viewpoint == NULL )
    return RED_ALLOC_FAILURE;

RED::IUserData* iuser_data = viewpoint->As< RED::IUserData >();
iuser_data->SetUserData( rnew MyClass(), true );

and the file saving code:

RED::Object* file = RED::Factory::CreateInstance( CID_REDFile );
if( file == NULL )
    return RED_ALLOC_FAILURE;

RED::Object* resmgr = RFK::TutorialApplication::GetResourceManager();
RED::IResourceManager* iresmgr = resmgr->As< RED::IResourceManager >();

RED::StreamingPolicy policy;
RED::IREDFile* ifile = file->As< RED::IREDFile >();
RC_TEST( ifile->Create( "./custom_container.red", true ) );
RC_TEST( ifile->Write( viewpoint, policy, resmgr ) );

RC_TEST( RED::Factory::DeleteInstance( file, iresmgr->GetState() ) );

The RED::IStream interface exposes all the needed methods to read/write from/to data streams. Here we use the RED::IStream::WriteString helper which writes a RED::String to the stream.

Loading the User Data

We need to register our class loading method to HOOPS Luminate in order to get it called automatically when loading .red file with our custom data in it. This is done by calling RED::ChunkInterpretersDictionary::RegisterChunkInterpreter with our chunk signature. Each time a chunk with the given signature is encountered in a .red stream, the corresponding callback will be called by HOOPS Luminate.

We can now load back the saved file and get access to the viewpoint and its user data:

// Load the file.
RED::Object* file = RED::Factory::CreateInstance( CID_REDFile );
if( file == NULL )
    return RED_ALLOC_FAILURE;

RED::Object* resmgr = RFK::TutorialApplication::GetResourceManager();
RED::IResourceManager* iresmgr = resmgr->As< RED::IResourceManager >();

RED::FileHeader file_header;
RED::FileInfo file_info;
RED::StreamingPolicy policy;
RED::Vector< unsigned int > ctx;

RED::IREDFile* ifile = file->As< RED::IREDFile >();
RC_TEST( ifile->Load( "./custom_container.red", iresmgr->GetState(), policy, file_header, file_info, ctx ) );

// Get back the viewpoint.
RED::Object* viewpoint;
RED::IDataManager* idatamgr = iresmgr->GetDataManager()->As< RED::IDataManager >();

unsigned int vcount;
RC_TEST( idatamgr->GetViewpointsCount( vcount, ctx[0] ) );
if( vcount != 1 )
    return RED_FAIL;

RC_TEST( idatamgr->GetViewpoint( viewpoint, ctx[0], 0, iresmgr->GetState() ) );

// Get the viewpoint user data.
RED::IUserData* iuser_data = viewpoint->As< RED::IUserData >();
RED::UserData* user_data = iuser_data->GetUserData();
if( user_data == NULL )
    return RED_FAIL;

We must now insure that the user data are of the right class before trying to access back to the string:

MyClass* mc = user_data->As< MyClass >();
if( mc == NULL )
{
    // The user data is not of MyClass type: error!
    return RED_FAIL;
}