#include "platform.h"
#include "core.h"
#include "scheduler.h"
#include "box3d/base.h"
#include "box3d/constants.h"
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
enum b3SchedulerTaskStatus
{
b3_schedulerFree = 0,
b3_schedulerPending = 1,
b3_schedulerClaimed = 2,
b3_schedulerComplete = 3,
};
typedef struct b3SchedulerTask
{
b3TaskCallback* callback;
void* taskContext;
b3AtomicInt status;
} b3SchedulerTask;
typedef struct b3SchedulerWorkerContext
{
struct b3Scheduler* scheduler;
int threadIndex;
} b3SchedulerWorkerContext;
typedef struct b3Scheduler
{
b3Thread* threads[B3_MAX_WORKERS];
b3SchedulerWorkerContext workerContexts[B3_MAX_WORKERS];
int workerCount;
int threadCount;
b3SchedulerTask tasks[B3_MAX_TASKS];
b3AtomicInt nextSlot;
b3Semaphore* taskSemaphore;
b3AtomicInt shutdown;
} b3Scheduler;
static bool b3SchedulerExecuteOne( b3Scheduler* scheduler )
{
int taskCount = b3AtomicLoadInt( &scheduler->nextSlot );
for ( int t = 0; t < taskCount; ++t )
{
b3SchedulerTask* task = scheduler->tasks + t;
if ( b3AtomicLoadInt( &task->status ) != b3_schedulerPending )
{
continue;
}
if ( b3AtomicCompareExchangeInt( &task->status, b3_schedulerPending, b3_schedulerClaimed ) == false )
{
continue;
}
task->callback( task->taskContext );
b3AtomicStoreInt( &task->status, b3_schedulerComplete );
return true;
}
return false;
}
static void b3SchedulerWorkerMain( void* context )
{
b3SchedulerWorkerContext* workerContext = context;
b3Scheduler* scheduler = workerContext->scheduler;
while ( true )
{
b3WaitSemaphore( scheduler->taskSemaphore );
if ( b3AtomicLoadInt( &scheduler->shutdown ) != 0 )
{
break;
}
while ( b3SchedulerExecuteOne( scheduler ) )
{
}
}
}
b3Scheduler* b3CreateScheduler( int workerCount )
{
B3_ASSERT( 0 < workerCount && workerCount <= B3_MAX_WORKERS );
b3Scheduler* scheduler = b3Alloc( sizeof( b3Scheduler ) );
memset( scheduler, 0, sizeof( b3Scheduler ) );
scheduler->workerCount = workerCount;
int threadCount = workerCount - 1;
scheduler->threadCount = threadCount;
scheduler->taskSemaphore = b3CreateSemaphore( 0 );
b3AtomicStoreInt( &scheduler->shutdown, 0 );
b3AtomicStoreInt( &scheduler->nextSlot, 0 );
for ( int i = 0; i < threadCount; ++i )
{
scheduler->workerContexts[i].scheduler = scheduler;
scheduler->workerContexts[i].threadIndex = i + 1;
char name[16];
snprintf( name, sizeof( name ), "box2d_worker_%02d", i + 1 );
scheduler->threads[i] = b3CreateThread( b3SchedulerWorkerMain, scheduler->workerContexts + i, name );
}
return scheduler;
}
void b3DestroyScheduler( b3Scheduler* scheduler )
{
b3AtomicStoreInt( &scheduler->shutdown, 1 );
for ( int i = 0; i < scheduler->threadCount; ++i )
{
b3SignalSemaphore( scheduler->taskSemaphore );
}
for ( int i = 0; i < scheduler->threadCount; ++i )
{
b3JoinThread( scheduler->threads[i] );
scheduler->threads[i] = NULL;
}
b3DestroySemaphore( scheduler->taskSemaphore );
b3Free( scheduler, sizeof( b3Scheduler ) );
}
void b3ResetScheduler( b3Scheduler* scheduler )
{
b3AtomicStoreInt( &scheduler->nextSlot, 0 );
}
void* b3SchedulerEnqueueTask( b3TaskCallback* task, void* taskContext, void* userContext, const char* name )
{
B3_UNUSED( name );
b3Scheduler* scheduler = userContext;
int slot = b3AtomicFetchAddInt( &scheduler->nextSlot, 1 );
B3_ASSERT( slot < B3_MAX_TASKS );
b3SchedulerTask* schedulerTask = scheduler->tasks + slot;
schedulerTask->callback = task;
schedulerTask->taskContext = taskContext;
b3AtomicStoreInt( &schedulerTask->status, b3_schedulerPending );
b3SignalSemaphore( scheduler->taskSemaphore );
return schedulerTask;
}
void b3SchedulerFinishTask( void* userTask, void* userContext )
{
if ( userTask == NULL )
{
return;
}
b3Scheduler* scheduler = userContext;
b3SchedulerTask* waitTask = userTask;
while ( b3AtomicLoadInt( &waitTask->status ) != b3_schedulerComplete )
{
if ( b3SchedulerExecuteOne( scheduler ) == false )
{
b3Yield();
}
}
}