Home

Resume

Blog

Teikitu


/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */
/*  »Project«   Teikitu Gaming System (TgS) (∂)
    »File«      TgS Common - Job 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 ============================================================================================================================================================ */

/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  Private Data                                                                                                                                                          */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

TgCN_VAR_ID                                 g_tiCVAR_Job_MGR_Enabled;
TgJOB_QUEUE_ID                              g_tiJob_Queue__OS;




/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  File Local Type */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

TgTYPE_STRUCT_ALIGN(STg2_Job_Thread,16,)
{
    TgJOB_THREAD_ID                             m_tiThread;
    TgTHREAD_ID                                 m_tiOS_Thread;
    volatile TgATOMIC_SINT32                    m_iEnabled;
    TgSINT32                                    m_iPad0;
    TgJOB_QUEUE_ID                              m_atiQueue[KTgMAX_NUM_QUEUE+1];
};


TgTYPE_STRUCT_ALIGN(STg2_Job_Queue,16,)
{
    STg2_UTM_AM_ST_ISO                          m_sFree;
    STg2_UTM_SN_ISO                             m_sLock_Queue_Execute;
    STg2_UTM_SN_ISO                             m_sLock_Queue_Add;

    STg2_Job                                    m_asJob[KTgMAX_NUM_JOB];
    STg2_UTS_QU                                 m_sQueue_Add;
    STg2_UTS_QU                                 m_sQueue_Execute;
    TgJOB_QUEUE_ID                              m_tiQueue;
    volatile TgATOMIC_SINT64                    m_niQueued;
#if 0 != (65584 % TgCOMPILE_CACHE_LINE_SIZE)
    TgUINT08                                    m_uiPad0[TgCOMPILE_CACHE_LINE_SIZE - (65584 % TgCOMPILE_CACHE_LINE_SIZE)];
#endif
};

#define DEBUG_STg2_Job_Queue 1




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

static TgBOOL                               tgJM_Is_Enabled( TgVOID );
static P_STg2_Job                           tgJM_Execute_Next_Job( TgJOB_QUEUE_ID );
static TgRESULT                             tgJM_Execute_Job( P_STg2_Job );

static ETgMODULE_STATE                      s_enJob_MGR_State = ETgMODULE_STATE__FREED;
static STg2_UTM_SN_ISO                      s_asLock_Job_Queue;
static STg2_Job_Queue                       s_asJob_Queue[KTgMAX_NUM_QUEUE];
static volatile TgATOMIC_SINT32             s_niJob_Queue;

#if TgCOMPILE_THREAD
static TgUINT32                             tgJM_Run_Job_Scheduler( C_TgUINTPTR );
static STg2_UTM_SN_ISO                      s_asLock_Job_Thread;
static STg2_Job_Thread                      s_asJob_Thread[KTgMAX_NUM_THREAD];
static volatile TgATOMIC_SINT32             s_niJob_Thread;
/*# TgCOMPILE_THREAD */
#endif




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

/* ---- tgJM_Init_MGR --------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgJM_Init_MGR( TgVOID )
{
    TgSINT32                            iIndex;
    TgJOB_QUEUE_ID                      tiTest;

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

    tgInit_JOB_QUEUE_ID( &tiTest, 0 ); /* Warm the ID system specifically to invalidate 0, 0 */

    tgCM_UTM_SN_Init( &s_asLock_Job_Queue.m_sLock );
    memset( s_asJob_Queue, 0, sizeof( STg2_Job_Queue ) * KTgMAX_NUM_QUEUE );
    s_niJob_Queue = 0;
    TgCOMPILER_ASSERT( sizeof( STg2_Job_Queue ) * KTgMAX_NUM_QUEUE == sizeof( s_asJob_Queue ), 0 );
    for (iIndex = 0; iIndex < KTgMAX_NUM_THREAD; ++iIndex)
    {
        s_asJob_Queue[iIndex].m_tiQueue= KTgJOB_QUEUE_ID__INVALID;
    };

#if TgCOMPILE_THREAD
    tgCM_UTM_SN_Init( &s_asLock_Job_Thread.m_sLock );
    memset( s_asJob_Thread, 0, sizeof( STg2_Job_Thread ) * KTgMAX_NUM_THREAD );
    s_niJob_Thread = 0;
    TgCOMPILER_ASSERT( sizeof( STg2_Job_Thread ) * KTgMAX_NUM_THREAD == sizeof( s_asJob_Thread ), 0 );
    for (iIndex = 0; iIndex < KTgMAX_NUM_THREAD; ++iIndex)
    {
        s_asJob_Thread[iIndex].m_tiThread = KTgJOB_THREAD_ID__INVALID;
    };
/*# TgCOMPILE_THREAD */
#endif

    g_tiCVAR_Job_MGR_Enabled = tgCN_Var_Init_Bool( TgT("Job_MGR_Enabled"), TgT("When disabled, all jobs are executed immediately (no queuing)"), 0, TgTRUE );

    tgAM_FULL_FENCE();

    s_enJob_MGR_State = ETgMODULE_STATE__INITIALIZED;
    return (KTgS_OK);
}


/* ---- tgJM_Boot_MGR --------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgJM_Boot_MGR( TgVOID )
{
    /* Verify the state of the system */
    TgERROR(ETgMODULE_STATE__INITIALIZED == s_enJob_MGR_State);
    s_enJob_MGR_State = ETgMODULE_STATE__BOOTING;

    /* Test for number of cores available to determine if job system should be shut down */

    /* Create the default OS queue used by low level systems */
    g_tiJob_Queue__OS = tgJM_Init_Job_Queue();
    if (TgFALSE != tgEQ_JOB_QUEUE_ID( g_tiJob_Queue__OS, KTgJOB_QUEUE_ID__INVALID ))
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Failed to init OS Job Queue.\n"), TgT("Job MGR"), TgT("tgJM_Boot_MGR") );
        s_enJob_MGR_State = ETgMODULE_STATE__INITIALIZED;
        return (KTgE_FAIL);
    }

    s_enJob_MGR_State = ETgMODULE_STATE__BOOTED;
    return (KTgS_OK);
}


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

    if ((ETgMODULE_STATE__FREED == s_enJob_MGR_State) || (ETgMODULE_STATE__INITIALIZED == s_enJob_MGR_State))
    {
        return (KTgS_OK);
    };

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

    tgAM_FULL_FENCE();

    tgCM_UTM_SN_Lock_Spin( &s_asLock_Job_Queue.m_sLock );
    for (iIndex = 0; iIndex < KTgMAX_NUM_QUEUE; ++iIndex)
    {
        if (TgFALSE != tgEQ_JOB_QUEUE_ID( s_asJob_Queue[iIndex].m_tiQueue, KTgJOB_QUEUE_ID__INVALID ))
            continue;
        tgJM_Stop_Job_Queue( s_asJob_Queue[iIndex].m_tiQueue, TgTRUE );
    };
    tgCM_UTM_SN_Signal( &s_asLock_Job_Queue.m_sLock );

    tgAM_FULL_FENCE();

    s_enJob_MGR_State = ETgMODULE_STATE__STOPPED;
    return (KTgS_OK);
}


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

    if (ETgMODULE_STATE__FREED == s_enJob_MGR_State)
    {
        return (KTgS_OK);
    };

    /* Verify the state of the system */
    TgERROR((ETgMODULE_STATE__STOPPED == s_enJob_MGR_State) || (ETgMODULE_STATE__INITIALIZED == s_enJob_MGR_State));
    s_enJob_MGR_State = ETgMODULE_STATE__FREEING;

    tgAM_FULL_FENCE();

    tgCM_UTM_SN_Lock_Spin( &s_asLock_Job_Queue.m_sLock );
    for (iIndex = 0; iIndex < KTgMAX_NUM_QUEUE; ++iIndex)
    {
        if (TgFALSE != tgEQ_JOB_QUEUE_ID( s_asJob_Queue[iIndex].m_tiQueue, KTgJOB_QUEUE_ID__INVALID ))
            continue;
        tgJM_Free_Job_Queue( s_asJob_Queue[iIndex].m_tiQueue );
    };
    tgCM_UTM_SN_Signal( &s_asLock_Job_Queue.m_sLock );

    TgERROR(0 == s_niJob_Queue);

#if TgCOMPILE_THREAD
    tgCM_UTM_SN_Lock_Yield( &s_asLock_Job_Thread.m_sLock );
    for (iIndex = 0; iIndex < KTgMAX_NUM_THREAD; ++iIndex)
    {
        if (TgFALSE != tgEQ_JOB_THREAD_ID( s_asJob_Thread[iIndex].m_tiThread, KTgJOB_THREAD_ID__INVALID ))
            continue;
        tgAM32_WRITE( &s_asJob_Thread[iIndex].m_iEnabled, 0 );
        tgTR_Close( s_asJob_Thread[iIndex].m_tiOS_Thread );
    };
    tgCM_UTM_SN_Free( &s_asLock_Job_Thread.m_sLock );
    TgERROR(0 == s_niJob_Thread);
/*# TgCOMPILE_THREAD */
#endif
    
    tgCM_UTM_SN_Free( &s_asLock_Job_Queue.m_sLock );

    s_enJob_MGR_State = ETgMODULE_STATE__FREED;
    return (KTgS_OK);
}


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


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


/* ---- tgJM_Query_Fixed_Memory ----------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgSIZE tgJM_Query_Fixed_Memory( TgVOID )
{
    return (0
             + sizeof( s_enJob_MGR_State )
             + sizeof( s_asLock_Job_Queue )
             + sizeof( s_asJob_Queue )
             + sizeof( s_niJob_Queue )
#if TgCOMPILE_THREAD
             + sizeof( s_asLock_Job_Thread )
             + sizeof( s_asJob_Thread )
             + sizeof( s_niJob_Thread )
/*# TgCOMPILE_THREAD */
#endif
    );
}


/* ---- tgJM_Init_Job_Queue --------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgJOB_QUEUE_ID tgJM_Init_Job_Queue( TgVOID )
{
    TgSINT32                            iIndex_Queue;
    TgSINT32                            iIndex_Job;

    tgCM_UTM_SN_Lock_Spin( &s_asLock_Job_Queue.m_sLock );

    if ((ETgMODULE_STATE__BOOTING != s_enJob_MGR_State) && (ETgMODULE_STATE__BOOTED != s_enJob_MGR_State))
    {
        tgCM_UTM_SN_Signal( &s_asLock_Job_Queue.m_sLock );
        return (KTgJOB_QUEUE_ID__INVALID);
    };

    /* Find an available slot for a new job thread */
    for (iIndex_Queue = 0; iIndex_Queue < KTgMAX_NUM_QUEUE; ++iIndex_Queue)
    {
        if (TgFALSE != tgEQ_JOB_QUEUE_ID( s_asJob_Queue[iIndex_Queue].m_tiQueue, KTgJOB_QUEUE_ID__INVALID ))
            break;
    };

    if (KTgMAX_NUM_QUEUE == iIndex_Queue)
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Exceeded number of allowable job queues.\n"), TgT("Job MGR"), TgT("tgJM_Init_Job_Queue") );
        tgCM_UTM_SN_Signal( &s_asLock_Job_Queue.m_sLock );
        return (KTgJOB_QUEUE_ID__INVALID);
    };

    tgCM_UTM_AM_ST_Init( &s_asJob_Queue[iIndex_Queue].m_sFree.m_sStack );
    tgCM_UTM_SN_Init( &s_asJob_Queue[iIndex_Queue].m_sLock_Queue_Execute.m_sLock );
    tgCM_UTM_SN_Init( &s_asJob_Queue[iIndex_Queue].m_sLock_Queue_Add.m_sLock );
    tgCM_UTS_QU_Init( &s_asJob_Queue[iIndex_Queue].m_sQueue_Add );
    tgCM_UTS_QU_Init( &s_asJob_Queue[iIndex_Queue].m_sQueue_Execute );

    for (iIndex_Job = KTgMAX_NUM_JOB - 1; iIndex_Job >= 0; --iIndex_Job)
    {
        tgCM_UTM_AM_ST_Push( &s_asJob_Queue[iIndex_Queue].m_sFree.m_sStack, &s_asJob_Queue[iIndex_Queue].m_asJob[iIndex_Job].m_sNode.m_sStack );
    };

    tgInit_JOB_QUEUE_ID( &s_asJob_Queue[iIndex_Queue].m_tiQueue, iIndex_Queue );

    tgAM32_INC( &s_niJob_Queue );
    tgAM_WRITE_FENCE();
    tgCM_UTM_SN_Signal( &s_asLock_Job_Queue.m_sLock );
    return (s_asJob_Queue[iIndex_Queue].m_tiQueue);
}


/* ---- tgJM_Stop_Job_Queue --------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgVOID tgJM_Stop_Job_Queue( C_TgJOB_QUEUE_ID tiQueue, C_TgBOOL bAbort )
{
    /* Validate that we have a working queue */
    if ((TgFALSE != tgEQ_JOB_QUEUE_ID( tiQueue, KTgJOB_QUEUE_ID__INVALID )) || (TgTRUE != tgEQ_JOB_QUEUE_ID( s_asJob_Queue[tiQueue.m.iI].m_tiQueue, tiQueue )))
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Invalid Queue.\n"), TgT("Job MGR"), TgT("tgJM_Queue_Job") );
        return;
    };

    tgCM_UTM_SN_Lock_Spin( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Execute.m_sLock );

    tgCM_UTM_SN_Lock_Spin( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Add.m_sLock );
    tgInit_JOB_QUEUE_ID( &s_asJob_Queue[tiQueue.m.iI].m_tiQueue, tiQueue.m.iI ); /* Invalidate the index */
    tgCM_UTS_QU_Merge( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute, &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Add );
    tgCM_UTM_SN_Signal( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Add.m_sLock );

    while (1)
    {
        union
        {
            P_STg2_UTS_QU_Node                  psNode;
            P_STg2_Job                          psJob;
        }                                   sUnion;

        sUnion.psNode = tgCM_UTS_QU_Dequeue( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute );
        if (nullptr == sUnion.psNode)
            break;

        if ((TgFALSE != bAbort) && sUnion.psJob->m_pfnAbort)
        {
            sUnion.psJob->m_pfnAbort( sUnion.psJob );
        };

        if (sUnion.psJob->m_pxiFinish)
        {
            tgAM32_DEC( sUnion.psJob->m_pxiFinish );
        };

        tgCM_UTM_AM_ST_Push( &s_asJob_Queue[tiQueue.m.iI].m_sFree.m_sStack, &sUnion.psJob->m_sNode.m_sStack );
    };

    tgAM_FULL_FENCE();

    tgCM_UTM_SN_Signal( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Execute.m_sLock );
}


/* ---- tgJM_Free_Job_Queue --------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgVOID tgJM_Free_Job_Queue( C_TgJOB_QUEUE_ID tiQueue )
{
    TgERROR(TgTRUE != tgEQ_JOB_QUEUE_ID( tiQueue, KTgJOB_QUEUE_ID__INVALID ));

    TgERROR(TgFALSE != tgCM_UTS_QU_Is_Empty( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute ));
    TgERROR(TgFALSE != tgCM_UTS_QU_Is_Empty( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Add ));

    tgCM_UTM_AM_ST_Free( &s_asJob_Queue[tiQueue.m.iI].m_sFree.m_sStack );
    tgCM_UTM_SN_Free( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Execute.m_sLock );
    tgCM_UTM_SN_Free( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Add.m_sLock );
    tgCM_UTS_QU_Free( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Add );
    tgCM_UTS_QU_Free( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute );

    memset( &s_asJob_Queue[tiQueue.m.iI], 0, sizeof( STg2_Job_Queue ) );

    tgAM32_DEC( &s_niJob_Queue );
    tgAM_WRITE_FENCE();
    s_asJob_Queue[tiQueue.m.iI].m_tiQueue = KTgJOB_QUEUE_ID__INVALID;
}


/* ---- tgJM_Queue_Job ------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgRESULT tgJM_Queue_Job( C_TgJOB_QUEUE_ID tiQueue, P_STg2_Job psJobSrc )
{
    union
    {
        P_STg2_UTM_Node                     psNode;
        P_STg2_Job                          psJob;
    }                                   sUnion;

    TgPARAM_CHECK(psJobSrc);

    /* Allow for runtime disable of the job system - this will force all queues to execute immediately */
    if (TgTRUE != tgJM_Is_Enabled())
    {
        return (TgSUCCEEDED(tgJM_Execute_Job( psJobSrc )) ? KTgS_JOB_EXECUTED: KTgE_FAIL);
    };

    /* Validate that we have a working queue */
    if ((TgFALSE != tgEQ_JOB_QUEUE_ID( tiQueue, KTgJOB_QUEUE_ID__INVALID )) || (TgTRUE != tgEQ_JOB_QUEUE_ID( s_asJob_Queue[tiQueue.m.iI].m_tiQueue, tiQueue )))
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Invalid Queue.\n"), TgT("Job MGR"), TgT("tgJM_Queue_Job") );
        return (KTgE_FAIL);
    };

    /* Get an available job to add to the queue.  If no job is free, then immediately execute the next waiting job, and re-use the job structure from the queue afterwards. */
    do
    {
        sUnion.psNode = tgCM_UTM_AM_ST_Pop( &s_asJob_Queue[tiQueue.m.iI].m_sFree.m_sStack );
        if (nullptr == sUnion.psJob)
        {
            tgCM_UTM_SN_Lock_Spin( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Execute.m_sLock );
            sUnion.psJob = tgJM_Execute_Next_Job( tiQueue );
        };
    }
    while (nullptr == sUnion.psJob);
    sUnion.psJob->m_sNode.m_sStack.m_pNext_Node = nullptr;

    TgMEMCPY( sUnion.psJob, sizeof( STg2_Job ), psJobSrc, sizeof( STg2_Job ) );

    /* Add job to queue */
    tgCM_UTM_SN_Lock_Spin( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Add.m_sLock );
    tgCM_UTS_QU_Enqueue( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Add, &sUnion.psJob->m_sNode.m_sQueue );
    tgCM_UTM_SN_Signal( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Add.m_sLock );

#if defined(DEBUG_STg2_Job_Queue)
    tgAM64_INC( &s_asJob_Queue[tiQueue.m.iI].m_niQueued );
#endif

    /* #REVIEW: Do I need to Wake job threads */
    return (KTgS_OK);
}


/* ---- tgJM_Spawn_Job_Thread ------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgJOB_THREAD_ID tgJM_Spawn_Job_Thread( P_TgJOB_QUEUE_ID atiQueueList, TgUINT32 niQueue, C_ETgTHREAD_PRIORITY enPriority, CPC_TgCHAR szName )
{
#if TgCOMPILE_THREAD
    TgSINT32                            iIndex;

    tgCM_UTM_SN_Lock_Spin( &s_asLock_Job_Thread.m_sLock );

    if (ETgMODULE_STATE__BOOTED != s_enJob_MGR_State)
    {
        tgCM_UTM_SN_Signal( &s_asLock_Job_Thread.m_sLock );
        return (KTgJOB_THREAD_ID__INVALID);
    };

    /* Find an available slot for a new job thread */
    for (iIndex = 0; iIndex < KTgMAX_NUM_THREAD; ++iIndex)
    {
        if (TgFALSE != tgEQ_JOB_THREAD_ID( s_asJob_Thread[iIndex].m_tiThread, KTgJOB_THREAD_ID__INVALID ))
            break;
    };

    if (KTgMAX_NUM_THREAD == iIndex)
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Exceeded number of allowable job threads.\n"), TgT("Job MGR"), TgT("tgJM_Spawn_Job_Thread") );
        tgCM_UTM_SN_Signal( &s_asLock_Job_Thread.m_sLock );
        return (KTgJOB_THREAD_ID__INVALID);
    };

    /* Initialize the thread data structure */
    memset( s_asJob_Thread + iIndex, 0, sizeof( STg2_Job_Thread ) );
    TgMEMCPY( &s_asJob_Thread[iIndex].m_atiQueue, sizeof( s_asJob_Thread[iIndex].m_atiQueue ), atiQueueList, niQueue*sizeof( TgJOB_QUEUE_ID ) );
    s_asJob_Thread[iIndex].m_iEnabled = 1;
    tgInit_JOB_THREAD_ID( &s_asJob_Thread[iIndex].m_tiThread, iIndex );

    tgAM_WRITE_FENCE();
    tgCM_UTM_SN_Signal( &s_asLock_Job_Thread.m_sLock );

    /* Create the OS thread */
    s_asJob_Thread[iIndex].m_tiOS_Thread = tgTR_Create( tgJM_Run_Job_Scheduler, (TgUINTPTR)s_asJob_Thread[iIndex].m_tiThread.m_iKI, 0, enPriority, szName );
    if (TgFALSE == tgTHREAD_ID_Is_Valid( s_asJob_Thread[iIndex].m_tiOS_Thread ))
    {
        /* On fail - clean it all up and return the slot reservation */
        s_asJob_Thread[iIndex].m_tiThread = KTgJOB_THREAD_ID__INVALID;
        s_asJob_Thread[iIndex].m_iEnabled = 0;
        s_asJob_Thread[iIndex].m_tiOS_Thread = KTgTHREAD_ID__INVALID;

        tgAM_WRITE_FENCE();
        return (KTgJOB_THREAD_ID__INVALID);
    };

    tgAM32_INC( &s_niJob_Thread );
    return s_asJob_Thread[iIndex].m_tiThread;
#else
    (TgVOID)atiQueueList;
    (TgVOID)niQueue;
    (TgVOID)enPriority;
    (TgVOID)szName;
    return (KTgJOB_THREAD_ID__INVALID);
    
/*# TgCOMPILE_THREAD */
#endif
}


/* ---- tgJM_Stop_Job_Thread -------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgVOID tgJM_Stop_Job_Thread( C_TgJOB_THREAD_ID tiJob_Thread )
{
#if TgCOMPILE_THREAD
    tgCM_UTM_SN_Lock_Yield( &s_asLock_Job_Thread.m_sLock );

    TgERROR(TgTRUE != tgEQ_JOB_THREAD_ID( tiJob_Thread, KTgJOB_THREAD_ID__INVALID ));

    if (TgTRUE != tgEQ_JOB_THREAD_ID( tiJob_Thread, s_asJob_Thread[tiJob_Thread.m.iI].m_tiThread ))
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Job Thread ID Mismatch.\n"), TgT("Job MGR"), TgT("tgJM_Run_Job_Scheduler") );
        tgCM_UTM_SN_Signal( &s_asLock_Job_Thread.m_sLock );
        return;
    };

    tgAM32_WRITE( &s_asJob_Thread[tiJob_Thread.m.iI].m_iEnabled, 0 );
    tgTR_Close( s_asJob_Thread[tiJob_Thread.m.iI].m_tiOS_Thread );

    s_asJob_Thread[tiJob_Thread.m.iI].m_tiOS_Thread = KTgTHREAD_ID__INVALID;
    s_asJob_Thread[tiJob_Thread.m.iI].m_tiThread = KTgJOB_THREAD_ID__INVALID;

    tgAM_WRITE_FENCE();
    tgCM_UTM_SN_Signal( &s_asLock_Job_Thread.m_sLock );
#else
    (void)tiJob_Thread;
/*# TgCOMPILE_THREAD */
#endif
}


/* ---- tgJM_Stop_Job_Thread -------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
TgTHREAD_ID tgJM_Query_Thread_Id( C_TgJOB_THREAD_ID tiJob_Thread )
{
    TgTHREAD_ID                         tiThread;

    tiThread.m_iKI = KTgJOB_THREAD_ID__INVALID.m_iKI;

#if TgCOMPILE_THREAD
    tgCM_UTM_SN_Lock_Yield( &s_asLock_Job_Thread.m_sLock );
    if (TgFALSE != tgEQ_JOB_THREAD_ID( tiJob_Thread, s_asJob_Thread[tiJob_Thread.m.iI].m_tiThread ))
    {
        tiThread = s_asJob_Thread[tiJob_Thread.m.iI].m_tiOS_Thread;
    };
    tgCM_UTM_SN_Signal( &s_asLock_Job_Thread.m_sLock );
#else
    (void)tiJob_Thread;
/*# TgCOMPILE_THREAD */
#endif
    
    return (tiThread);
}




/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */
/*  File Local Functions                                                                                                                                                  */
/* -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. */

/* ---- tgJM_Is_Enabled ------------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
static TgBOOL tgJM_Is_Enabled( TgVOID )
{
#if TgCOMPILE_JOB_THREAD
    TgBOOL                              bValue;

    if (TgTRUE != tgCN_Var_Query_Bool( &bValue, g_tiCVAR_Job_MGR_Enabled ))
    {
        return (TgFALSE);
    };

    return (((bValue == TgTRUE) && (s_niJob_Thread > 0))? TgTRUE : TgFALSE);
/*# TgCOMPILE_JOB_THREAD */
#else
    return (TgFALSE);
/*# TgCOMPILE_JOB_THREAD */
#endif
}


/* ---- tgJM_Execute_Next_Job ------------------------------------------------------------------------------------------------------------------------------------------- */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
static P_STg2_Job tgJM_Execute_Next_Job( TgJOB_QUEUE_ID tiQueue )
{
    union
    {
        P_STg2_UTS_QU_Node                  psNode;
        P_STg2_Job                          psJob;
    }                                   sUnion;
    P_STg2_UTS_QU_Node                  psPrev_Node;

    /* Merge the new jobs into the execute queue.  The empty check is not thread safe but conservative.  It will eventually post ... */
    if (TgTRUE != tgCM_UTS_QU_Is_Empty( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Add ))
    {
        tgCM_UTM_SN_Lock_Yield( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Add.m_sLock );
        tgCM_UTS_QU_Merge( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute, &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Add );
        tgCM_UTM_SN_Signal( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Add.m_sLock );
    };

    sUnion.psNode = s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute.m_sNode.m_pNext_Node;
    psPrev_Node = nullptr;

    /* Iterate through the execute queue for the first valid job */
    for (; &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute.m_sNode != sUnion.psNode; psPrev_Node = sUnion.psNode, sUnion.psNode = sUnion.psNode->m_pNext_Node)
    {
        if (sUnion.psJob->m_pxiAbort && (0 != tgAM32_XCMP( sUnion.psJob->m_pxiAbort, 0, 0 )))
        {
            break;
        };

        if (sUnion.psJob->m_pxiTrigger && (0 != tgAM32_XCMP( sUnion.psJob->m_pxiTrigger, 0, 0 )))
        {
            continue;
        };

        if (sUnion.psJob->m_pfnRunTest && (KTgS_OK != sUnion.psJob->m_pfnRunTest( sUnion.psJob )))
        {
            continue;
        };

        break;
    };

    /* Check to see if all jobs were invalid for execution (or queue was empty) */
    if (&s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute.m_sNode == sUnion.psNode)
    {
        tgCM_UTM_SN_Signal( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Execute.m_sLock );
        return (nullptr);
    }

#if defined(DEBUG_STg2_Job_Queue)
    tgAM64_DEC( &s_asJob_Queue[tiQueue.m.iI].m_niQueued );
#endif

    /* Remove the node from the queue */
    if (nullptr == psPrev_Node)
    {
        TgVERIFY(sUnion.psNode == tgCM_UTS_QU_Dequeue( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute ));
    }
    else
    {
        if (s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute.m_psTail_Node == sUnion.psNode)
        {
            TgERROR(sUnion.psNode->m_pNext_Node == &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute.m_sNode);
            s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute.m_psTail_Node = psPrev_Node;
        }
        psPrev_Node->m_pNext_Node = sUnion.psNode->m_pNext_Node;
    };

    tgAM_WRITE_FENCE();
    TgERROR( TgTRUE == tgCM_UTS_QU_Is_Valid( &s_asJob_Queue[tiQueue.m.iI].m_sQueue_Execute ) );

    tgCM_UTM_SN_Signal( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Execute.m_sLock );

    /* Execute the Job */
    sUnion.psJob->m_pxiTrigger = nullptr;
    sUnion.psJob->m_pfnRunTest = nullptr;
    tgJM_Execute_Job( sUnion.psJob );

    /* Put the node back into the free pool */
    memset( sUnion.psJob, 0, sizeof( STg2_Job ) );

    return (sUnion.psJob);
}


/* ---- tgJM_Execute_Job ------------------------------------------------------------------------------------------------------------------------------------------------ */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
static TgRESULT tgJM_Execute_Job( P_STg2_Job psJob )
{
    TgERROR(nullptr != psJob);

    if (psJob->m_pxiAbort && (0 != tgAM32_XCMP( psJob->m_pxiAbort, 0, 0 )))
    {
        if (psJob->m_pfnAbort)
        {
            psJob->m_pfnAbort( psJob );
        };
    }
    else if (psJob->m_pfnExecute)
    {
        if (psJob->m_pxiTrigger && (0 != tgAM32_XCMP( psJob->m_pxiTrigger, 0, 0 )))
        {
            return (KTgE_FAIL);
        };

        if (psJob->m_pfnRunTest && (KTgS_OK != psJob->m_pfnRunTest( psJob )))
        {
            return (KTgE_FAIL);
        };

        if (psJob->m_pxiExecute)
        {
            tgAM32_INC( psJob->m_pxiExecute );
            psJob->m_pfnExecute( psJob );
            tgAM32_DEC( psJob->m_pxiExecute );
        }
        else
        {
            psJob->m_pfnExecute( psJob );
        };
    };

    if (psJob->m_pxiFinish)
    {
        tgAM32_DEC( psJob->m_pxiFinish );
    };

    return (KTgS_OK);
}


/* ---- tgJM_Run_Job_Scheduler ------------------------------------------------------------------------------------------------------------------------------------------ */
/* ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
#if TgCOMPILE_THREAD
static TgUINT32 tgJM_Run_Job_Scheduler( C_TgUINTPTR uiJob_Thread )
{
    TgJOB_THREAD_ID                     tiJob_Thread;
    P_STg2_Job_Thread                   psJob_Thread;

    /* Verify parameter is valid, and data passes basic integrity check */
    tiJob_Thread.m_iKI = (TgSINT64)uiJob_Thread;

    if (tiJob_Thread.m.iI < 0 || tiJob_Thread.m.iI >= KTgMAX_NUM_THREAD || TgFALSE != tgEQ_JOB_THREAD_ID( tiJob_Thread, KTgJOB_THREAD_ID__INVALID ))
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Invalid Job Thread ID as Parameter.\n"), TgT("Job MGR"), TgT("tgJM_Run_Job_Scheduler") );
        tgAM32_DEC( &s_niJob_Thread );
        return (1);
    };

    if (TgTRUE != tgEQ_JOB_THREAD_ID( tiJob_Thread, s_asJob_Thread[tiJob_Thread.m.iI].m_tiThread ))
    {
        TgCRITICAL_MSGF( 0, TgT("%-16.16s(%-32.32s): Job Thread ID Mismatch.\n"), TgT("Job MGR"), TgT("tgJM_Run_Job_Scheduler") );
        tgAM32_DEC( &s_niJob_Thread );
        return (1);
    };

    /* Primary loop */
    psJob_Thread = s_asJob_Thread + tiJob_Thread.m.iI;
    while (psJob_Thread->m_iEnabled)
    {
        TgSINT32                            iQueue;
        TgJOB_QUEUE_ID                      tiQueue;
        P_STg2_Job                          psJob;

        for (iQueue = 0; iQueue < KTgMAX_NUM_QUEUE; ++iQueue)
        {
            tiQueue = psJob_Thread->m_atiQueue[iQueue];

            if (TgFALSE != tgEQ_JOB_QUEUE_ID( tiQueue, KTgJOB_QUEUE_ID__INVALID ))
                break;

            /* Check that the queue is still active - if not, remove it from the execute list for the thread */
            if (TgTRUE != tgEQ_JOB_QUEUE_ID( tiQueue, s_asJob_Queue[tiQueue.m.iI].m_tiQueue ))
            {
                for (++iQueue; iQueue < KTgMAX_NUM_QUEUE; ++iQueue)
                {
                    tiQueue = psJob_Thread->m_atiQueue[iQueue];
                    if (TgFALSE != tgEQ_JOB_QUEUE_ID( tiQueue, KTgJOB_QUEUE_ID__INVALID ))
                        break;
                    psJob_Thread->m_atiQueue[iQueue - 1] = tiQueue;
                }
                psJob_Thread->m_atiQueue[iQueue - 1] = KTgJOB_QUEUE_ID__INVALID;
                break;
            };

            /* #REVIEW: For now, rather than deal with contention - if another thread is processing the queue, move onto the next queue */
            /* Note: This "should" work for small number of threads, but will definitely fall as that number increases */
            if (KTgS_OK != tgCM_UTM_SN_Lock_Test( &s_asJob_Queue[tiQueue.m.iI].m_sLock_Queue_Execute.m_sLock ))
                continue;

            psJob = tgJM_Execute_Next_Job( tiQueue );
            if (nullptr == psJob)
                continue;
            tgCM_UTM_AM_ST_Push( &s_asJob_Queue[tiQueue.m.iI].m_sFree.m_sStack, &psJob->m_sNode.m_sStack );

            /* We always want to restart searching for a job at the top of the queue list */
            break;
        };

        if (iQueue == KTgMAX_NUM_QUEUE)
            tgTR_Sleep( 1 );
    };

    tgAM32_DEC( &s_niJob_Thread );
    return (0);
}
/*# TgCOMPILE_THREAD */
#endif