Merge branch 'master' of https://bitbucket.org/4coder/4coder
This commit is contained in:
commit
993d962a8b
398
linux_4ed.cpp
398
linux_4ed.cpp
|
@ -103,6 +103,8 @@
|
|||
#define SUPPORT_DPI 1
|
||||
#define LINUX_FONTS 1
|
||||
|
||||
#define InterlockedCompareExchange(dest, ex, comp) __sync_val_compare_and_swap((dest), (comp), (ex))
|
||||
|
||||
//
|
||||
// Linux structs / enums
|
||||
//
|
||||
|
@ -148,6 +150,8 @@ struct Thread_Group{
|
|||
Thread_Context *threads;
|
||||
i32 count;
|
||||
|
||||
Unbounded_Work_Queue queue;
|
||||
|
||||
i32 cancel_lock0;
|
||||
i32 cancel_cv0;
|
||||
};
|
||||
|
@ -252,6 +256,9 @@ internal void LinuxStringDup(String*, void*, size_t);
|
|||
internal Sys_Acquire_Lock_Sig(system_acquire_lock);
|
||||
internal Sys_Release_Lock_Sig(system_release_lock);
|
||||
|
||||
internal void system_wait_cv(i32, i32);
|
||||
internal void system_signal_cv(i32, i32);
|
||||
|
||||
//
|
||||
// Linux static assertions
|
||||
//
|
||||
|
@ -983,8 +990,11 @@ Sys_CLI_End_Update_Sig(system_cli_end_update){
|
|||
// Threads
|
||||
//
|
||||
|
||||
//#define OLD_JOB_QUEUE
|
||||
|
||||
#ifdef OLD_JOB_QUEUE
|
||||
internal void*
|
||||
ThreadProc(void* arg){
|
||||
JobThreadProc(void* arg){
|
||||
Thread_Context *thread = (Thread_Context*)arg;
|
||||
Work_Queue *queue = linuxvars.queues + thread->group_id;
|
||||
Thread_Group *group = linuxvars.groups + thread->group_id;
|
||||
|
@ -1005,27 +1015,27 @@ ThreadProc(void* arg){
|
|||
for (;;){
|
||||
u32 read_index = queue->read_position;
|
||||
u32 write_index = queue->write_position;
|
||||
|
||||
|
||||
if (read_index != write_index){
|
||||
u32 next_read_index = (read_index + 1) % JOB_ID_WRAP;
|
||||
u32 next_read_index = (read_index + 1) % QUEUE_WRAP;
|
||||
u32 safe_read_index =
|
||||
__sync_val_compare_and_swap(&queue->read_position,
|
||||
read_index, next_read_index);
|
||||
|
||||
__sync_val_compare_and_swap(&queue->read_position,
|
||||
read_index, next_read_index);
|
||||
|
||||
if (safe_read_index == read_index){
|
||||
Full_Job_Data *full_job = queue->jobs + (safe_read_index % QUEUE_WRAP);
|
||||
Full_Job_Data *full_job = queue->jobs + safe_read_index;
|
||||
// NOTE(allen): This is interlocked so that it plays nice
|
||||
// with the cancel job routine, which may try to cancel this job
|
||||
// at the same time that we try to run it
|
||||
|
||||
|
||||
i32 safe_running_thread =
|
||||
__sync_val_compare_and_swap(&full_job->running_thread,
|
||||
THREAD_NOT_ASSIGNED, thread->id);
|
||||
|
||||
__sync_val_compare_and_swap(&full_job->running_thread,
|
||||
THREAD_NOT_ASSIGNED, thread->id);
|
||||
|
||||
if (safe_running_thread == THREAD_NOT_ASSIGNED){
|
||||
thread->job_id = full_job->id;
|
||||
thread->running = 1;
|
||||
|
||||
|
||||
full_job->job.callback(&linuxvars.system, thread, thread_memory, full_job->job.data);
|
||||
full_job->running_thread = 0;
|
||||
thread->running = 0;
|
||||
|
@ -1050,17 +1060,17 @@ ThreadProc(void* arg){
|
|||
internal
|
||||
Sys_Post_Job_Sig(system_post_job){
|
||||
Work_Queue *queue = linuxvars.queues + group_id;
|
||||
|
||||
|
||||
Assert((queue->write_position + 1) % QUEUE_WRAP != queue->read_position % QUEUE_WRAP);
|
||||
|
||||
|
||||
b32 success = 0;
|
||||
u32 result = 0;
|
||||
while (!success){
|
||||
u32 write_index = queue->write_position;
|
||||
u32 next_write_index = (write_index + 1) % JOB_ID_WRAP;
|
||||
u32 next_write_index = (write_index + 1) % QUEUE_WRAP;
|
||||
u32 safe_write_index =
|
||||
__sync_val_compare_and_swap(&queue->write_position,
|
||||
write_index, next_write_index);
|
||||
__sync_val_compare_and_swap(&queue->write_position,
|
||||
write_index, next_write_index);
|
||||
if (safe_write_index == write_index){
|
||||
result = write_index;
|
||||
write_index = write_index % QUEUE_WRAP;
|
||||
|
@ -1070,7 +1080,7 @@ Sys_Post_Job_Sig(system_post_job){
|
|||
success = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sem_post(LinuxHandleToSem(queue->semaphore));
|
||||
|
||||
return result;
|
||||
|
@ -1080,19 +1090,15 @@ internal
|
|||
Sys_Cancel_Job_Sig(system_cancel_job){
|
||||
Work_Queue *queue = linuxvars.queues + group_id;
|
||||
Thread_Group *group = linuxvars.groups + group_id;
|
||||
|
||||
u32 job_index;
|
||||
u32 thread_id;
|
||||
Full_Job_Data *full_job;
|
||||
|
||||
job_index = job_id % QUEUE_WRAP;
|
||||
full_job = queue->jobs + job_index;
|
||||
|
||||
|
||||
u32 job_index = job_id % QUEUE_WRAP;
|
||||
Full_Job_Data *full_job = queue->jobs + job_index;
|
||||
|
||||
Assert(full_job->id == job_id);
|
||||
thread_id =
|
||||
__sync_val_compare_and_swap(&full_job->running_thread,
|
||||
THREAD_NOT_ASSIGNED, 0);
|
||||
|
||||
u32 thread_id =
|
||||
__sync_val_compare_and_swap(&full_job->running_thread,
|
||||
THREAD_NOT_ASSIGNED, 0);
|
||||
|
||||
if (thread_id != THREAD_NOT_ASSIGNED && thread_id != 0){
|
||||
i32 thread_index = thread_id - 1;
|
||||
|
||||
|
@ -1137,7 +1143,7 @@ internal
|
|||
Sys_Grow_Thread_Memory_Sig(system_grow_thread_memory){
|
||||
void *old_data;
|
||||
i32 old_size, new_size;
|
||||
|
||||
|
||||
system_acquire_lock(CANCEL_LOCK0 + memory->id - 1);
|
||||
old_data = memory->data;
|
||||
old_size = memory->size;
|
||||
|
@ -1151,6 +1157,299 @@ Sys_Grow_Thread_Memory_Sig(system_grow_thread_memory){
|
|||
system_release_lock(CANCEL_LOCK0 + memory->id - 1);
|
||||
}
|
||||
|
||||
#else // new job queue
|
||||
|
||||
internal void*
|
||||
JobThreadProc(void* lpParameter){
|
||||
Thread_Context *thread = (Thread_Context*)lpParameter;
|
||||
Work_Queue *queue = linuxvars.queues + thread->group_id;
|
||||
Thread_Group *group = linuxvars.groups + thread->group_id;
|
||||
|
||||
i32 thread_index = thread->id - 1;
|
||||
|
||||
i32 cancel_lock = group->cancel_lock0 + thread_index;
|
||||
i32 cancel_cv = group->cancel_cv0 + thread_index;
|
||||
|
||||
Thread_Memory *thread_memory = linuxvars.thread_memory + thread_index;
|
||||
|
||||
if (thread_memory->size == 0){
|
||||
i32 new_size = Kbytes(64);
|
||||
thread_memory->data = LinuxGetMemory(new_size);
|
||||
thread_memory->size = new_size;
|
||||
}
|
||||
|
||||
for (;;){
|
||||
u32 read_index = queue->read_position;
|
||||
u32 write_index = queue->write_position;
|
||||
|
||||
if (read_index != write_index){
|
||||
// NOTE(allen): Previously I was wrapping by the job wrap then
|
||||
// wrapping by the queue wrap. That was super stupid what was that?
|
||||
// Now it just wraps by the queue wrap.
|
||||
u32 next_read_index = (read_index + 1) % QUEUE_WRAP;
|
||||
u32 safe_read_index =
|
||||
InterlockedCompareExchange(&queue->read_position,
|
||||
next_read_index, read_index);
|
||||
|
||||
if (safe_read_index == read_index){
|
||||
Full_Job_Data *full_job = queue->jobs + safe_read_index;
|
||||
// NOTE(allen): This is interlocked so that it plays nice
|
||||
// with the cancel job routine, which may try to cancel this job
|
||||
// at the same time that we try to run it
|
||||
|
||||
i32 safe_running_thread =
|
||||
InterlockedCompareExchange(&full_job->running_thread,
|
||||
thread->id, THREAD_NOT_ASSIGNED);
|
||||
|
||||
if (safe_running_thread == THREAD_NOT_ASSIGNED){
|
||||
thread->job_id = full_job->id;
|
||||
thread->running = 1;
|
||||
|
||||
full_job->job.callback(&linuxvars.system,
|
||||
thread, thread_memory, full_job->job.data);
|
||||
LinuxScheduleStep();
|
||||
//full_job->running_thread = 0;
|
||||
thread->running = 0;
|
||||
|
||||
system_acquire_lock(cancel_lock);
|
||||
if (thread->cancel){
|
||||
thread->cancel = 0;
|
||||
system_signal_cv(cancel_lock, cancel_cv);
|
||||
}
|
||||
system_release_lock(cancel_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
sem_wait(LinuxHandleToSem(queue->semaphore));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void
|
||||
initialize_unbounded_queue(Unbounded_Work_Queue *source_queue){
|
||||
i32 max = 512;
|
||||
source_queue->jobs = (Full_Job_Data*)system_get_memory(max*sizeof(Full_Job_Data));
|
||||
source_queue->count = 0;
|
||||
source_queue->max = max;
|
||||
source_queue->skip = 0;
|
||||
}
|
||||
|
||||
inline i32
|
||||
get_work_queue_available_space(i32 write, i32 read){
|
||||
// NOTE(allen): The only time that queue->write_position == queue->read_position
|
||||
// is allowed is when the queue is empty. Thus if
|
||||
// queue->write_position+1 == queue->read_position the available space is zero.
|
||||
// So these computations both end up leaving one slot unused. The only way I can
|
||||
// think to easily eliminate this is to have read and write wrap at twice the size
|
||||
// of the underlying array but modulo their values into the array then if write
|
||||
// has caught up with read it still will not be equal... but lots of modulos... ehh.
|
||||
|
||||
i32 available_space = 0;
|
||||
if (write >= read){
|
||||
available_space = QUEUE_WRAP - (write - read) - 1;
|
||||
}
|
||||
else{
|
||||
available_space = (read - write) - 1;
|
||||
}
|
||||
|
||||
return(available_space);
|
||||
}
|
||||
|
||||
#define UNBOUNDED_SKIP_MAX 128
|
||||
|
||||
internal void
|
||||
flush_to_direct_queue(Unbounded_Work_Queue *source_queue, Work_Queue *queue, i32 thread_count){
|
||||
// NOTE(allen): It is understood that read_position may be changed by other
|
||||
// threads but it will only make more space in the queue if it is changed.
|
||||
// Meanwhile write_position should not ever be changed by anything but the
|
||||
// main thread in this system, so it will not be interlocked.
|
||||
u32 read_position = queue->read_position;
|
||||
u32 write_position = queue->write_position;
|
||||
u32 available_space = get_work_queue_available_space(write_position, read_position);
|
||||
u32 available_jobs = source_queue->count - source_queue->skip;
|
||||
|
||||
u32 writable_count = Min(available_space, available_jobs);
|
||||
|
||||
if (writable_count > 0){
|
||||
u32 count1 = writable_count;
|
||||
|
||||
if (count1+write_position > QUEUE_WRAP){
|
||||
count1 = QUEUE_WRAP - write_position;
|
||||
}
|
||||
|
||||
u32 count2 = writable_count - count1;
|
||||
|
||||
Full_Job_Data *job_src1 = source_queue->jobs + source_queue->skip;
|
||||
Full_Job_Data *job_src2 = job_src1 + count1;
|
||||
|
||||
Full_Job_Data *job_dst1 = queue->jobs + write_position;
|
||||
Full_Job_Data *job_dst2 = queue->jobs;
|
||||
|
||||
Assert((job_src1->id % QUEUE_WRAP) == write_position);
|
||||
|
||||
memcpy(job_dst1, job_src1, sizeof(Full_Job_Data)*count1);
|
||||
memcpy(job_dst2, job_src2, sizeof(Full_Job_Data)*count2);
|
||||
queue->write_position = (write_position + writable_count) % QUEUE_WRAP;
|
||||
|
||||
source_queue->skip += writable_count;
|
||||
|
||||
if (source_queue->skip == source_queue->count){
|
||||
source_queue->skip = source_queue->count = 0;
|
||||
}
|
||||
else if (source_queue->skip > UNBOUNDED_SKIP_MAX){
|
||||
u32 left_over = source_queue->count - source_queue->skip;
|
||||
memmove(source_queue->jobs, source_queue->jobs + source_queue->skip,
|
||||
sizeof(Full_Job_Data)*left_over);
|
||||
source_queue->count = left_over;
|
||||
source_queue->skip = 0;
|
||||
}
|
||||
}
|
||||
|
||||
i32 semaphore_release_count = writable_count;
|
||||
if (semaphore_release_count > thread_count){
|
||||
semaphore_release_count = thread_count;
|
||||
}
|
||||
|
||||
// NOTE(allen): platform dependent portion...
|
||||
// TODO(allen): pull out the duplicated part once I see
|
||||
// that this is pretty much the same on linux.
|
||||
for (i32 i = 0; i < semaphore_release_count; ++i){
|
||||
sem_post(LinuxHandleToSem(queue->semaphore));
|
||||
}
|
||||
}
|
||||
|
||||
internal void
|
||||
flush_thread_group(i32 group_id){
|
||||
Thread_Group *group = linuxvars.groups + group_id;
|
||||
Work_Queue *queue = linuxvars.queues + group_id;
|
||||
Unbounded_Work_Queue *source_queue = &group->queue;
|
||||
flush_to_direct_queue(source_queue, queue, group->count);
|
||||
}
|
||||
|
||||
// Note(allen): post_job puts the job on the unbounded queue.
|
||||
// The unbounded queue is entirely managed by the main thread.
|
||||
// The thread safe queue is bounded in size so the unbounded
|
||||
// queue is periodically flushed into the direct work queue.
|
||||
internal
|
||||
Sys_Post_Job_Sig(system_post_job){
|
||||
Thread_Group *group = linuxvars.groups + group_id;
|
||||
Unbounded_Work_Queue *queue = &group->queue;
|
||||
|
||||
u32 result = queue->next_job_id++;
|
||||
|
||||
while (queue->count >= queue->max){
|
||||
i32 new_max = queue->max*2;
|
||||
Full_Job_Data *new_jobs = (Full_Job_Data*)
|
||||
system_get_memory(new_max*sizeof(Full_Job_Data));
|
||||
|
||||
memcpy(new_jobs, queue->jobs, queue->count);
|
||||
|
||||
system_free_memory(queue->jobs);
|
||||
|
||||
queue->jobs = new_jobs;
|
||||
queue->max = new_max;
|
||||
}
|
||||
|
||||
Full_Job_Data full_job;
|
||||
|
||||
full_job.job = job;
|
||||
full_job.running_thread = THREAD_NOT_ASSIGNED;
|
||||
full_job.id = result;
|
||||
|
||||
queue->jobs[queue->count++] = full_job;
|
||||
|
||||
Work_Queue *direct_queue = linuxvars.queues + group_id;
|
||||
flush_to_direct_queue(queue, direct_queue, group->count);
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal
|
||||
Sys_Cancel_Job_Sig(system_cancel_job){
|
||||
Thread_Group *group = linuxvars.groups + group_id;
|
||||
Unbounded_Work_Queue *source_queue = &group->queue;
|
||||
|
||||
b32 handled_in_unbounded = false;
|
||||
if (source_queue->skip < source_queue->count){
|
||||
Full_Job_Data *first_job = source_queue->jobs + source_queue->skip;
|
||||
if (first_job->id <= job_id){
|
||||
u32 index = source_queue->skip + (job_id - first_job->id);
|
||||
Full_Job_Data *job = source_queue->jobs + index;
|
||||
job->running_thread = 0;
|
||||
handled_in_unbounded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled_in_unbounded){
|
||||
Work_Queue *queue = linuxvars.queues + group_id;
|
||||
Full_Job_Data *job = queue->jobs + (job_id % QUEUE_WRAP);
|
||||
Assert(job->id == job_id);
|
||||
|
||||
u32 thread_id =
|
||||
InterlockedCompareExchange(&job->running_thread,
|
||||
0, THREAD_NOT_ASSIGNED);
|
||||
|
||||
if (thread_id != THREAD_NOT_ASSIGNED && thread_id != 0){
|
||||
i32 thread_index = thread_id - 1;
|
||||
|
||||
i32 cancel_lock = group->cancel_lock0 + thread_index;
|
||||
i32 cancel_cv = group->cancel_cv0 + thread_index;
|
||||
Thread_Context *thread = group->threads + thread_index;
|
||||
|
||||
|
||||
system_acquire_lock(cancel_lock);
|
||||
|
||||
thread->cancel = 1;
|
||||
|
||||
system_release_lock(FRAME_LOCK);
|
||||
do{
|
||||
system_wait_cv(cancel_lock, cancel_cv);
|
||||
}while (thread->cancel == 1);
|
||||
system_acquire_lock(FRAME_LOCK);
|
||||
|
||||
system_release_lock(cancel_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal
|
||||
Sys_Check_Cancel_Sig(system_check_cancel){
|
||||
b32 result = 0;
|
||||
|
||||
Thread_Group *group = linuxvars.groups + thread->group_id;
|
||||
i32 thread_index = thread->id - 1;
|
||||
i32 cancel_lock = group->cancel_lock0 + thread_index;
|
||||
|
||||
system_acquire_lock(cancel_lock);
|
||||
if (thread->cancel){
|
||||
result = 1;
|
||||
}
|
||||
system_release_lock(cancel_lock);
|
||||
|
||||
return(result);
|
||||
}
|
||||
|
||||
internal
|
||||
Sys_Grow_Thread_Memory_Sig(system_grow_thread_memory){
|
||||
void *old_data;
|
||||
i32 old_size, new_size;
|
||||
|
||||
system_acquire_lock(CANCEL_LOCK0 + memory->id - 1);
|
||||
old_data = memory->data;
|
||||
old_size = memory->size;
|
||||
new_size = LargeRoundUp(memory->size*2, Kbytes(4));
|
||||
memory->data = system_get_memory(new_size);
|
||||
memory->size = new_size;
|
||||
if (old_data){
|
||||
memcpy(memory->data, old_data, old_size);
|
||||
system_free_memory(old_data);
|
||||
}
|
||||
system_release_lock(CANCEL_LOCK0 + memory->id - 1);
|
||||
}
|
||||
|
||||
#endif // OLD_JOB_QUEUE
|
||||
|
||||
internal
|
||||
Sys_Acquire_Lock_Sig(system_acquire_lock){
|
||||
pthread_mutex_lock(linuxvars.locks + id);
|
||||
|
@ -1161,6 +1460,16 @@ Sys_Release_Lock_Sig(system_release_lock){
|
|||
pthread_mutex_unlock(linuxvars.locks + id);
|
||||
}
|
||||
|
||||
internal void
|
||||
system_wait_cv(i32 lock_id, i32 cv_id){
|
||||
pthread_cond_wait(linuxvars.conds + cv_id, linuxvars.locks + lock_id);
|
||||
}
|
||||
|
||||
internal void
|
||||
system_signal_cv(i32 lock_id, i32 cv_id){
|
||||
pthread_cond_signal(linuxvars.conds + cv_id);
|
||||
}
|
||||
|
||||
//
|
||||
// Debug
|
||||
//
|
||||
|
@ -1172,19 +1481,36 @@ INTERNAL_Sys_Sentinel_Sig(internal_sentinel){
|
|||
return (&linuxvars.internal_bubble);
|
||||
}
|
||||
|
||||
#ifdef OLD_JOB_QUEUE
|
||||
internal
|
||||
INTERNAL_Sys_Get_Thread_States_Sig(internal_get_thread_states){
|
||||
Work_Queue *queue = linuxvars.queues + id;
|
||||
u32 write = queue->write_position;
|
||||
u32 read = queue->read_position;
|
||||
if (write < read) write += JOB_ID_WRAP;
|
||||
if (write < read) write += QUEUE_WRAP;
|
||||
*pending = (i32)(write - read);
|
||||
|
||||
|
||||
Thread_Group *group = linuxvars.groups + id;
|
||||
for (i32 i = 0; i < group->count; ++i){
|
||||
running[i] = (group->threads[i].running != 0);
|
||||
}
|
||||
}
|
||||
#else
|
||||
internal
|
||||
INTERNAL_Sys_Get_Thread_States_Sig(internal_get_thread_states){
|
||||
Thread_Group *group = linuxvars.groups + id;
|
||||
Unbounded_Work_Queue *source_queue = &group->queue;
|
||||
Work_Queue *queue = linuxvars.queues + id;
|
||||
u32 write = queue->write_position;
|
||||
u32 read = queue->read_position;
|
||||
if (write < read) write += QUEUE_WRAP;
|
||||
*pending = (i32)(write - read) + source_queue->count - source_queue->skip;
|
||||
|
||||
for (i32 i = 0; i < group->count; ++i){
|
||||
running[i] = (group->threads[i].running != 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
internal
|
||||
INTERNAL_Sys_Debug_Message_Sig(internal_debug_message){
|
||||
|
@ -2798,9 +3124,11 @@ main(int argc, char **argv)
|
|||
memory->id = thread->id;
|
||||
|
||||
thread->queue = &linuxvars.queues[BACKGROUND_THREADS];
|
||||
pthread_create(&thread->handle, NULL, &ThreadProc, thread);
|
||||
pthread_create(&thread->handle, NULL, &JobThreadProc, thread);
|
||||
}
|
||||
|
||||
initialize_unbounded_queue(&linuxvars.groups[BACKGROUND_THREADS].queue);
|
||||
|
||||
for(i32 i = 0; i < LOCK_COUNT; ++i){
|
||||
pthread_mutex_init(linuxvars.locks + i, NULL);
|
||||
}
|
||||
|
@ -3082,6 +3410,8 @@ main(int argc, char **argv)
|
|||
linuxvars.cursor = result.mouse_cursor_type;
|
||||
}
|
||||
|
||||
flush_thread_group(BACKGROUND_THREADS);
|
||||
|
||||
linuxvars.input.first_step = 0;
|
||||
linuxvars.input.keys = key_input_data_zero();
|
||||
linuxvars.input.mouse.press_l = 0;
|
||||
|
|
Loading…
Reference in New Issue