Home

Resume

Blog

Teikitu


/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/*  »Project«   Teikitu Gaming System (TgS) (∂)
    »File«      TgS Common - String MGR.c
    »Author«    Andrew Aye (EMail: mailto:andrew.aye@gmail.com, Web: http://www.andrewaye.com)
    »Version«   4.51 / »GUID« A9981407-3EC9-42AF-8B6F-8BE6DD919615                                                                                                        */
/*   -------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
/*  Copyright: © 2002-2017, Andrew Aye.  All Rights Reserved.
    This software is free for non-commercial use.  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
      following conditions are met:
        Redistribution of source code must retain this copyright notice, this list of conditions and the following disclaimers.
        Redistribution in binary form must reproduce this copyright notice, this list of conditions and the following disclaimers in the documentation and other materials
          provided with the distribution.
    The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
    The intellectual property rights of the algorithms used reside with Andrew Aye.
    You may not use this software, in whole or in part, in support of any commercial product without the express written consent of the author.
    There is no warranty or other guarantee of fitness of this software for any purpose. It is provided solely "as is".                                                   */
/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/* == Common ============================================================================================================================================================ */

/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  File Local Constants */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

#define STRING_VALIDATE_ON_TABLE_LOAD       0

enum { KTgSM_GLOBAL_SIZE_MAX                = 16*1024*1024 };
enum { KTgSM_MAX_HASH_ENTRIES               = 512*1024 };
enum { KTgSM_GLOBAL_USED_FLAG               = 1 << 15 };

#if TgS_STAT_COMMON || defined(TgCOMPILER_MSVC)
static C_TgSINT64 KTgSM_TABLE_USED_FLAG     = ((TgSINT64)KTgSM_GLOBAL_USED_FLAG << 16) - 1;
#endif
static C_TgSINT64 KTgSM_TABLE_SIZE_MASK     = ( 1ULL << 16 ) - 1;

TgCOMPILER_ASSERT( KTgSM_GLOBAL_SIZE_MAX < KTgMAX_S32, 0 );
TgCOMPILER_ASSERT( KTgSM_MAX_HASH_ENTRIES < KTgMAX_S32, 0 );



/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  File Local Types */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

/* #PERF: For the static string allocations, we could use the first 16 bytes as the string dict id since they are guaranteed to be unique - but does not account for aging */
TgTYPE_STRUCT(STg2_SM_String_Entry,)
{
    TgSINT32                                    m_iOffset; /*« Relative offset from start of virtual memory pool */
    TgUINT16                                    m_uiFlags;
    TgUINT16                                    m_uiSize;
    TgSTRING_DICT_ID                            m_tiString;
};

/* Tables use a single 64bit integer in a look aside buffer to store offset (32) | flags (16) | size (16) */


/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  File Local Functions and Data                                                                                                                                         */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

static ETgMODULE_STATE                      s_enString_MGR_State = ETgMODULE_STATE__FREED;
static CP_TgCHAR                            s_apszNative_String[KTgSM_MAX_NATIVE_ENTRIES];
static TgATOMIC_SINT32                      s_aiGlobal_Dict_Hash[KTgSM_MAX_HASH_ENTRIES];
static volatile TgATOMIC_SINT32             s_iGlobal_Dict_Data_Offset;
static TgATOMIC_SINT32                      s_niGlobal_Dict_Data;
static P_TgUINT08                           s_pGlobal_Dict_Data;
static TgSTRING_TABLE_ID                    s_atiString_Tables[KTgSM_NUM_STRING_TABLE_MAX];
static STg2_String_Table                    s_asString_Tables[KTgSM_NUM_STRING_TABLE_MAX];




/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  Public Functions                                                                                                                                                      */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

/* ---- tgSM_Init_MGR --------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgSM_Init_MGR( TgVOID )
{
    TgSINT32                            iIndex;

    /* Verify the state of the system */
    TgERROR(ETgMODULE_STATE__FREED == s_enString_MGR_State);
    s_enString_MGR_State = ETgMODULE_STATE__INITIALIZING;

    /* Initialize the string map for native strings */
    memset( (void*)s_apszNative_String, 0x00, sizeof( s_apszNative_String ) );

    /* Initialize the data members for the global dictionary */
    memset( s_aiGlobal_Dict_Hash, 0xFF, sizeof( s_aiGlobal_Dict_Hash ) );
    s_iGlobal_Dict_Data_Offset = 0;
    s_niGlobal_Dict_Data = 0;
    s_pGlobal_Dict_Data = (P_TgUINT08)TgRESERVE_VIRTUAL( KTgSM_GLOBAL_SIZE_MAX );

    /* Initialize data entries for the string tables */
    for (iIndex = 0; iIndex < KTgSM_NUM_STRING_TABLE_MAX; ++iIndex)
    {
        s_atiString_Tables[iIndex] = KTgSTRING_TABLE_ID__INVALID;
    };
    memset( (void*)s_asString_Tables, 0, sizeof( s_asString_Tables ) );

    s_enString_MGR_State = ETgMODULE_STATE__INITIALIZED;
    return (KTgS_OK);
}


/* ---- tgSM_Boot_MGR --------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgSM_Boot_MGR( TgVOID )
{
    /* Verify the state of the system */
    TgERROR(ETgMODULE_STATE__INITIALIZED == s_enString_MGR_State);
    s_enString_MGR_State = ETgMODULE_STATE__BOOTED;

    #define NATIVE_LITERAL( iIndex, szLiteral ) tgSM_UID_Init_String( TgT(#szLiteral), iIndex );
    #include "TgS COMMON/TgS Literals.TgS"

    return (KTgS_OK);
}


/* ---- tgSM_Stop_MGR --------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgSM_Stop_MGR( TgVOID )
{
    TgSINT32                            iIndex;

    /* Verify the state of the system */
    TgERROR(ETgMODULE_STATE__BOOTED == s_enString_MGR_State);

    /* Confirm that all string tables (except for the global table) have been unloaded */
    for (iIndex = KTgSM_NUM_STRING_TABLE_MAX - 1; iIndex >= 0; --iIndex)
    {
        if (TgTRUE != tgSTRING_TABLE_ID_Is_Valid( s_atiString_Tables[iIndex] ))
        {
            continue;
        };
        TgWARNING_MSGF( 0, TgT("%-16.16s(%-32.32s): String Table (%d) was not removed.\n"), TgT("String MGR"), TgT("tgSM_Stop_MGR"), iIndex );
        tgSM_Table_Free( s_atiString_Tables[iIndex] );
    };

    /* "Clear" the global strings */
    TgFREE_VIRTUAL( s_pGlobal_Dict_Data );
    memset( s_aiGlobal_Dict_Hash, 0xFF, sizeof( s_aiGlobal_Dict_Hash ) );
    s_iGlobal_Dict_Data_Offset = 0;
    s_niGlobal_Dict_Data = 0;
    s_pGlobal_Dict_Data = (P_TgUINT08)TgRESERVE_VIRTUAL( KTgSM_GLOBAL_SIZE_MAX );

    /* Since native strings are just direct mapped literals we can "free" them by clearing the map list */
    memset( (void*)s_apszNative_String, 0x00, sizeof( s_apszNative_String ) );

    s_enString_MGR_State = ETgMODULE_STATE__STOPPED;
    return (KTgS_OK);
}


/* ---- tgSM_Free_MGR --------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgSM_Free_MGR( TgVOID )
{
    /* Verify the state of the system */
    TgERROR(ETgMODULE_STATE__STOPPED == s_enString_MGR_State);
    s_enString_MGR_State = ETgMODULE_STATE__FREEING;

    TgFREE_VIRTUAL( s_pGlobal_Dict_Data );
    s_pGlobal_Dict_Data = nullptr;
    s_niGlobal_Dict_Data = 0;

    s_enString_MGR_State = ETgMODULE_STATE__FREED;
    return (KTgS_OK);
}


/* ---- tgSM_Update_MGR ------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgSM_Update_MGR( C_TgFLOAT32 UNUSED_PARAM fDT )
{
    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );
    return (KTgS_OK);
}


/* ---- tgSM_Query_Init ------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgBOOL tgSM_Query_Init( TgVOID )
{
    return (ETgMODULE_STATE__INITIALIZED <= s_enString_MGR_State && s_enString_MGR_State <= ETgMODULE_STATE__STOPPED ? TgTRUE : TgFALSE);
}


/* ---- tgSM_Query_Boot ------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgBOOL tgSM_Query_Boot( TgVOID )
{
    return (ETgMODULE_STATE__BOOTED == s_enString_MGR_State ? TgTRUE : TgFALSE);
}


/* ---- tgSM_Query_Fixed_Memory ----------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSIZE tgSM_Query_Fixed_Memory( TgVOID )
{
    return (0
             + sizeof( s_enString_MGR_State )
             + sizeof( s_apszNative_String )
             + sizeof( s_aiGlobal_Dict_Hash )
             + sizeof( s_iGlobal_Dict_Data_Offset )
             + sizeof( s_niGlobal_Dict_Data )
             + sizeof( s_pGlobal_Dict_Data )
             + sizeof( s_atiString_Tables )
             + sizeof( s_asString_Tables )
    );
}


/* ---- tgSM_Stats ------------------------------------------------------------------------------------------------------------------------------------------------------ */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
#if TgS_STAT_COMMON
TgVOID tgSM_Stats( P_STg2_Output psOUT )
{
    TgSINT32                            iIndex;
    TgSINT32                            iOffset;

    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );
    TgERROR(nullptr != psOUT);

    for (iIndex = 0; iIndex < KTgSM_MAX_NATIVE_ENTRIES; ++iIndex)
    {
        CP_TgCHAR                           pszNativeString;

        pszNativeString = s_apszNative_String[iIndex];
        if (nullptr == pszNativeString)
            continue;

        tgIO_PrintF( psOUT, TgT("%-16.16s: Native String (%d, %s).\n"), TgT("String MGR"), iIndex, pszNativeString );
    };

    for (iOffset = 0; iOffset < s_iGlobal_Dict_Data_Offset;)
    {
        TgSIZE                              uiEntry_Size;
        union
        {
            P_STg2_SM_String_Entry              ps;
            P_TgUINT08                          pui;
        }                                   psEntry;

        psEntry.pui = s_pGlobal_Dict_Data + iOffset;
        uiEntry_Size = tgCM_CEL_ALGN_PW2_UXX( psEntry.ps->m_uiSize + sizeof( STg2_SM_String_Entry ), 8 );

        tgIO_PrintF( psOUT, TgT("%-16.16s: Global String (%d, %s).\n"), TgT("String MGR"), iIndex, (P_TgCHAR)(psEntry.ps + 1) );

        iOffset += (TgSINT32)uiEntry_Size;
    };

    for (iIndex = 0; iIndex < KTgSM_NUM_STRING_TABLE_MAX; ++iIndex)
    {
        TgSINT32                            niEntryMax;
        TgSINT32                            iIndex1;

        if (TgTRUE != tgSTRING_TABLE_ID_Is_Valid( s_atiString_Tables[iIndex] ))
        {
            continue;
        };

        niEntryMax = s_asString_Tables[iIndex].m_niEntry_ID;
        tgIO_PrintF( psOUT, TgT("%-16.16s: Table (%d) with (%d) entries.\n"), TgT("String MGR"), iIndex, niEntryMax );

        for (iIndex1 = 0; iIndex1 < niEntryMax; ++iIndex1)
        {
            TgSINT64                            iEntry64;

            iEntry64 = s_asString_Tables[iIndex].m_aiEntry_ID[iIndex1];
            if (0 == (KTgSM_TABLE_USED_FLAG & iEntry64))
            {
                continue; /* Entry is not in use */
            };

            tgIO_PrintF( psOUT, TgT("%-16.16s:  - String (%d, %s)\n"), TgT("String MGR"), iIndex1, (P_TgCHAR)(s_asString_Tables[iIndex].m_pData + (iEntry64 >> 32)) );
        };
    };
}
/*# TgS_STAT_COMMON */
#endif


/* ---- tgSM_UID_Init_String -------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSINT32 tgSM_UID_Init_String( CPC_TgCHAR pszNative_String, C_TgSINT32 iIndex )
{
    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );

    if (nullptr == pszNative_String || (nullptr == pszNative_String[0]))
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Invalid String.\n"), TgT("String MGR"), TgT("tgSM_UID_Init_String") );
        return (-1);
    }

    if (iIndex < 0 || iIndex >= KTgSM_MAX_NATIVE_ENTRIES || 0 != s_apszNative_String[iIndex])
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Invalid Index.\n"), TgT("String MGR"), TgT("tgSM_UID_Init_String") );
        return (-1);
    }

    s_apszNative_String[iIndex] = pszNative_String;
    return (iIndex);
}


/* ---- tgSM_UID_Query_String ------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
CP_TgCHAR tgSM_UID_Query_String( C_TgSINT32 iIndex )
{
    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );
    return (iIndex < 0 ? nullptr : (iIndex >= KTgSM_MAX_NATIVE_ENTRIES ? nullptr : s_apszNative_String[iIndex]));
}


/* ---- tgSM_Dict_Insert_String_Hash ------------------------------------------------------------------------------------------------------------------------------------ */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSTRING_DICT_ID tgSM_Dict_Insert_String_Hash( CPC_TgCHAR pszString, C_TgSIZE nuiString, C_TgUINTXX uiHash )
{
    TgSTRING_DICT_ID                    tiRet;
    TgSIZE                              uiString_Encode_Size;
    TgSIZE                              uiEntry_Size;
    TgSINT32                            iOffset;
    TgSINT32                            iHash_Index_Start;
    TgSINT32                            iHash_Index;
    TgSINT32                            iHash_Offset;
    union
    {
        P_STg2_SM_String_Entry              ps;
        P_TgUINT08                          pui;
    }                                   psEntry;

    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );

    tiRet = tgSM_Dict_Find_Id_By_Hash( pszString, uiHash );
    if (TgFALSE != tgSTRING_DICT_ID_Is_Valid( tiRet ))
    {
        return (tiRet);
    };

    uiString_Encode_Size = nuiString + 1;
    TgCRITICAL( uiString_Encode_Size < KTgMAX_U16 - 1 );
    uiEntry_Size = tgCM_CEL_ALGN_PW2_UXX( uiString_Encode_Size + sizeof( STg2_SM_String_Entry ), 8 );

    /* Grab the memory for the entry */
    do 
    {
        iOffset = s_iGlobal_Dict_Data_Offset;
        if (iOffset + (TgSINT32)uiEntry_Size >= KTgSM_GLOBAL_SIZE_MAX)
        {
            return (KTgSTRING_DICT_ID__INVALID);
        };
    }
    while (iOffset != tgAM32_XCMP(&s_iGlobal_Dict_Data_Offset, iOffset + (TgSINT32)uiEntry_Size, iOffset ));

    TgCOMMIT_VIRTUAL( s_pGlobal_Dict_Data + iOffset, uiEntry_Size ); /* Commit the needed memory */

    /* Initialize the entry */
    psEntry.pui = s_pGlobal_Dict_Data + iOffset;
    psEntry.ps->m_iOffset = iOffset;
    psEntry.ps->m_uiFlags = KTgSM_GLOBAL_USED_FLAG;
    psEntry.ps->m_uiSize = (TgUINT16)uiString_Encode_Size;
    tgInit_STRING_DICT_ID( &psEntry.ps->m_tiString, iOffset );

    /* Copy the string data */
    TgMEMCPY( psEntry.ps + 1, uiString_Encode_Size, pszString, nuiString );
    s_pGlobal_Dict_Data[(TgUINT32)iOffset + sizeof(STg2_SM_String_Entry) + uiString_Encode_Size - 1] = 0;

    tgAM32_INC( &s_niGlobal_Dict_Data );
    tgAM_WRITE_FENCE(); /* NOTE: Valid entry is known by being both used and having non-zero size */

    /* Add the string to the hash table */
    TgERROR(s_niGlobal_Dict_Data + 1 < KTgSM_MAX_HASH_ENTRIES);
    iHash_Index_Start = uiHash % KTgSM_MAX_HASH_ENTRIES;
    iHash_Index = iHash_Index_Start;
    do
    {
        iHash_Offset = s_aiGlobal_Dict_Hash[iHash_Index];

        if (iHash_Offset >= 0)
        {
            iHash_Index = (iHash_Index + 1) % KTgSM_MAX_HASH_ENTRIES;
            continue;
        };

        if (iHash_Offset == tgAM32_XCMP( s_aiGlobal_Dict_Hash + iHash_Index, iOffset, iHash_Offset ))
        {
            return (psEntry.ps->m_tiString);
        };

        iHash_Index = (iHash_Index + 1) % KTgSM_MAX_HASH_ENTRIES;
    }
    while (iHash_Index != iHash_Index_Start);
    TgCRITICAL( iHash_Index != iHash_Index_Start );

    /* At this point we have a valid string but we have somehow been unable to find a hash position for it - hilarity will ensure. */
    return (psEntry.ps->m_tiString);
}


/* ---- tgSM_Dict_Insert_String ----------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSTRING_DICT_ID tgSM_Dict_Insert_String( CPC_TgCHAR pszString, C_TgSIZE nuiString )
{
    return (tgSM_Dict_Insert_String_Hash( pszString, nuiString, tgSZ_Hash( pszString ) ));
}


/* ---- tgSM_Dict_Find_Id_By_Hash --------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSTRING_DICT_ID tgSM_Dict_Find_Id_By_Hash( CPC_TgCHAR pszString, C_TgUINTXX uiHash )
{
    TgSINT32                            iHash_Index_Start;
    TgSINT32                            iHash_Index;
    TgSINT32                            iOffset;
    union
    {
        P_STg2_SM_String_Entry              ps;
        P_TgUINT08                          pui;
    }                                   psEntry;

    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );

    if (KTgEMPTY_HASH == uiHash)
    {
        return (KTgSTRING_DICT_ID__INVALID);
    };

    iHash_Index_Start = uiHash % KTgSM_MAX_HASH_ENTRIES;
    iHash_Index = iHash_Index_Start;
    do
    {
        iOffset = s_aiGlobal_Dict_Hash[iHash_Index];

        if (iOffset < 0 || iOffset > s_iGlobal_Dict_Data_Offset)
        {
            return (KTgSTRING_DICT_ID__INVALID);
        };

        psEntry.pui = s_pGlobal_Dict_Data + iOffset;
        if (0 == tgSZ_Compare( (P_TgCHAR)(psEntry.ps + 1), pszString ))
        {
            return (psEntry.ps->m_tiString);
        };

        iHash_Index = (iHash_Index + 1) % KTgSM_MAX_HASH_ENTRIES;
    } while (iHash_Index != iHash_Index_Start);

    return (KTgSTRING_DICT_ID__INVALID);
}


/* ---- tgSM_Dict_Find_Id_By_String ------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSTRING_DICT_ID tgSM_Dict_Find_Id_By_String( CPC_TgCHAR pszString )
{
    return (tgSM_Dict_Find_Id_By_Hash( pszString, tgSZ_Hash( pszString ) ));
}


/* ---- tgSM_Dict_Query_String ------------------------------------------------------------------------------------------------------------------------------------------ */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgBOOL tgSM_Dict_Query_String( CPP_TgCHAR pszStringOut, PC_TgSIZE piLengthOut, C_TgSTRING_DICT_ID tiString )
{
    union
    {
        P_STg2_SM_String_Entry              ps;
        P_TgUINT08                          pui;
    }                                   psEntry;

    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );
    TgERROR(tiString.m.iI < s_iGlobal_Dict_Data_Offset);

    psEntry.pui = s_pGlobal_Dict_Data + tiString.m.iI;

    if (TgTRUE != tgEQ_STRING_DICT_ID( psEntry.ps->m_tiString, tiString ))
    {
        TgERROR_MSGF( 0, TgT("%-16.16s(%-32.32s): Stale String Dictionary ID.\n"), TgT("Common"), TgT("tgSM_Dict_Decrement_Reference") );
        return (TgFALSE);
    }

    TgERROR(KTgSM_GLOBAL_USED_FLAG & psEntry.ps->m_uiFlags);

    if (pszStringOut)
        *pszStringOut = (P_TgCHAR)(psEntry.ps + 1);
    if (piLengthOut)
        *piLengthOut = psEntry.ps->m_uiSize - 1;

    return (TgTRUE);
}


/* ---- tgSM_Table_Load ------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSTRING_TABLE_ID tgSM_Table_Load( PC_STg2_Input psInput, C_TgSIZE uiOffset_Start )
{
    TgSIZE                              uiOffset;
    TgSINT32                            iIndex;

    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );

    /* Confirm that all string tables (except for the global table) have been unloaded */
    TgCRITICAL( ~0LL == KTgSTRING_TABLE_ID__INVALID.m_iKI );
    uiOffset = uiOffset_Start;
    for (iIndex = 1; iIndex < KTgSM_NUM_STRING_TABLE_MAX; ++iIndex)
    {
        TgSINT32                            niEntrySize;
        TgSTRING_TABLE_ID                   tiRet;

        if (~0LL != tgAM64_XCMP( &s_atiString_Tables[iIndex].m_iKI, 0, ~0LL ))
        {
            continue; /* Reserve an invalid entry for us to use */
        };
        TgERROR(0 == s_asString_Tables[iIndex].m_aiEntry_ID);
        TgERROR(0 == s_asString_Tables[iIndex].m_pData);


        /* End of loop processing - execution is guaranteed to return from this point */

        tgInit_STRING_TABLE_ID( &tiRet, iIndex );

        uiOffset += psInput->m_pfnRead( psInput, uiOffset, (P_TgUINT08)&s_asString_Tables[iIndex].m_niEntry_ID, sizeof( TgSINT32 ) );
        niEntrySize = s_asString_Tables[iIndex].m_niEntry_ID * (TgSINT32)sizeof( TgSINT64 );
        s_asString_Tables[iIndex].m_aiEntry_ID = (P_TgSINT64)TgMALLOC_POOL( (TgSIZE)niEntrySize );
        uiOffset += psInput->m_pfnRead( psInput, uiOffset, (P_TgUINT08)s_asString_Tables[iIndex].m_aiEntry_ID, (TgSIZE)niEntrySize );
        uiOffset += psInput->m_pfnRead( psInput, uiOffset, (P_TgUINT08)&s_asString_Tables[iIndex].m_niData, sizeof( TgSINT32 ) );
        s_asString_Tables[iIndex].m_pData = TgMALLOC_POOL( (TgSIZE)s_asString_Tables[iIndex].m_niData );
        uiOffset += psInput->m_pfnRead( psInput, uiOffset, s_asString_Tables[iIndex].m_pData, (TgSIZE)s_asString_Tables[iIndex].m_niData );

    #if STRING_VALIDATE_ON_TABLE_LOAD
        {
            TgSINT32                            iIndex_Check;
            TgSIZE                              uiSize_Check;

            uiSize_Check = 0;
            for (iIndex_Check = 0; iIndex_Check < s_asString_Tables[iIndex].m_niEntry_ID; ++iIndex_Check)
            {
                uiSize_Check += (TgSIZE)(KTgTABLE_SIZE_MASK & s_asString_Tables[iIndex].m_aiEntry_ID[iIndex_Check]);
            };
            TgERROR(uiSize_Check == (TgSIZE)s_asString_Tables[iIndex].m_niData);
        }
    /*# STRING_VALIDATE_ON_TABLE_LOAD */
    #endif
        s_atiString_Tables[iIndex] = tiRet;
        return (tiRet);
    };

    return (KTgSTRING_TABLE_ID__INVALID);
}


/* ---- tgSM_Table_Free ------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgBOOL tgSM_Table_Free( C_TgSTRING_TABLE_ID tiTable )
{
    STg2_String_Table                   tgLocal_Table;

    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );

    if (TgTRUE != tgSM_Table_Is_Loaded( tiTable ))
    {
        return (TgFALSE);
    };

    tgLocal_Table = s_asString_Tables[tiTable.m.iI];

    memset( s_atiString_Tables + tiTable.m.iI, 0, sizeof( TgSTRING_DICT_ID ) );
    s_atiString_Tables[tiTable.m.iI] = KTgSTRING_TABLE_ID__INVALID;

    TgFREE_POOL( tgLocal_Table.m_aiEntry_ID );
    TgFREE_POOL( tgLocal_Table.m_pData );

    return (TgTRUE);
}


/* ---- tgSM_Table_Is_Loaded -------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgBOOL tgSM_Table_Is_Loaded( C_TgSTRING_TABLE_ID tiTable )
{
    return (tgEQ_STRING_TABLE_ID( s_atiString_Tables[tiTable.m.iI], tiTable ));
}


/* ---- tgSM_Table_Query_String ----------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgBOOL tgSM_Table_Query_String( CPP_TgCHAR pszStringOut, PC_TgSIZE piLengthOut, C_TgSTRING_TABLE_ID tiTable, C_TgSINT32 iIndex )
{
    TgSINT64                            iEntry;

    TgPARAM_CHECK(nullptr != pszStringOut);
    TgPARAM_CHECK(nullptr != piLengthOut);

    TgDIAG( ETgMODULE_STATE__BOOTED == s_enString_MGR_State );

    if (TgTRUE != tgSM_Table_Is_Loaded( tiTable ))
    {
        return (TgFALSE);
    };

    if (iIndex < 0 || iIndex >= s_asString_Tables[tiTable.m.iI].m_niEntry_ID)
    {
        return (TgFALSE);
    };

    iEntry = s_asString_Tables[tiTable.m.iI].m_aiEntry_ID[iIndex];
    TgERROR(KTgSM_TABLE_USED_FLAG & iEntry);

    *pszStringOut = (P_TgCHAR)(s_asString_Tables[tiTable.m.iI].m_pData + (iEntry >> 32));
    *piLengthOut = (KTgSM_TABLE_SIZE_MASK & iEntry) - 1;

    return (TgTRUE);
}