fd65c0db77ba3484882aa066866d01e21b8c14fe
[vg.git] / vg_async.h
1 /* Copyright (C) 2021-2023 Harry Godden (hgn) - All Rights Reserved
2 *
3 * primateves that you use when you need to run something from another thread
4 * back in the main loop of vg, at the start of each frame
5 */
6
7 #ifndef VG_ASYNC_H
8 #define VG_ASYNC_H
9
10 #define VG_GAME
11 #include "vg/vg.h"
12
13 typedef struct vg_async_item vg_async_item;
14
15 struct vg_async_item{
16 vg_async_item *next;
17
18 void *payload;
19 u32 size;
20
21 void (*fn_runner)( void *payload, u32 size );
22 };
23
24 struct vg_async{
25 void *buffer;
26
27 vg_async_item *start, *end;
28
29 SDL_sem *sem_wait_for_flush;
30 SDL_SpinLock sl_index;
31 }
32 static vg_async;
33
34 VG_STATIC enum vg_thread_purpose vg_thread_purpose(void);
35 VG_STATIC enum engine_status _vg_engine_status(void);
36
37 /*
38 * Allocate an asynchronous call with a bit of memory
39 */
40 VG_STATIC vg_async_item *vg_async_alloc( u32 size )
41 {
42 /* ditch out here if engine crashed. this serves as the 'quit checking' */
43
44 if( _vg_engine_status() == k_engine_status_crashed ){
45 assert( vg_thread_purpose() == k_thread_purpose_loader );
46 longjmp( vg.env_loader_exit, 1 );
47 }
48
49 SDL_AtomicLock( &vg_async.sl_index );
50
51 u32 total_allocation = vg_align8(size) + vg_align8(sizeof(vg_async_item)),
52 remaining = vg_linear_remaining( vg_async.buffer ),
53 capacity = vg_linear_get_capacity( vg_async.buffer );
54
55 if( total_allocation > capacity ){
56 SDL_AtomicUnlock( &vg_async.sl_index );
57 vg_error( "Requested: %umb. Buffer size: %umb\n",
58 (total_allocation/1024)/1024,
59 (capacity/1024)/1024 );
60
61 vg_fatal_error( "async alloc invalid size\n" );
62 }
63
64 if( total_allocation > remaining ){
65 SDL_AtomicUnlock( &vg_async.sl_index );
66 SDL_SemWait( vg_async.sem_wait_for_flush );
67 SDL_AtomicLock( &vg_async.sl_index );
68
69 remaining = vg_linear_remaining( vg_async.buffer );
70 capacity = vg_linear_get_capacity( vg_async.buffer );
71
72 assert( remaining == capacity );
73 assert( vg_async.start == NULL );
74 assert( vg_async.end == NULL );
75 }
76
77 void *block = vg_linear_alloc( vg_async.buffer, total_allocation );
78
79 vg_async_item *entry = block;
80 entry->next = NULL;
81
82 if( size ) entry->payload = ((u8*)block) + vg_align8(sizeof(vg_async_item));
83 else entry->payload = NULL;
84
85 entry->size = size;
86 entry->fn_runner = NULL;
87
88 if( vg_async.end ){
89 vg_async.end->next = entry;
90 vg_async.end = entry;
91 }else{
92 vg_async.start = entry;
93 vg_async.end = entry;
94 }
95
96 SDL_AtomicUnlock( &vg_async.sl_index );
97
98 return entry;
99 }
100
101 /*
102 * Wait until the current stack of async calls is completely flushed out
103 */
104 VG_STATIC void vg_async_stall(void)
105 {
106 vg_info( "async_stall: %d\n", SDL_SemValue( vg_async.sem_wait_for_flush ) );
107 SDL_SemWait( vg_async.sem_wait_for_flush );
108 }
109
110 /*
111 * Mark the call as being filled and ready to go
112 */
113 VG_STATIC void vg_async_dispatch( vg_async_item *item,
114 void (*runner)( void *payload, u32 size ) )
115 {
116 if( SDL_SemValue(vg_async.sem_wait_for_flush) )
117 SDL_SemWait(vg_async.sem_wait_for_flush);
118
119 SDL_AtomicLock( &vg_async.sl_index );
120 item->fn_runner = runner;
121 SDL_AtomicUnlock( &vg_async.sl_index );
122 }
123
124 /*
125 * Make a simple async call without allocating extra.
126 */
127 VG_STATIC void vg_async_call( void (*runner)( void *payload, u32 size ),
128 void *payload, u32 size )
129 {
130 vg_async_item *call = vg_async_alloc(0);
131 call->payload = payload;
132 call->size = size;
133 vg_async_dispatch( call, runner );
134 }
135
136 /*
137 * Run as much of the async buffer as possible
138 */
139 VG_STATIC void vg_run_async_checked(void)
140 {
141 SDL_AtomicLock( &vg_async.sl_index );
142
143 while( vg_async.start ){
144 vg_async_item *entry = vg_async.start;
145
146 if( entry->fn_runner ){
147 entry->fn_runner( entry->payload, entry->size );
148 vg_async.start = entry->next;
149
150 if( vg_async.start == NULL ){
151 vg_async.end = NULL;
152
153 vg_linear_clear( vg_async.buffer );
154
155 if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){
156 SDL_SemPost( vg_async.sem_wait_for_flush );
157 }
158 }
159 }
160 else{
161 SDL_AtomicUnlock( &vg_async.sl_index );
162 return;
163 }
164
165 /* TODO: if exceed max frametime.... */
166 }
167
168 if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){
169 SDL_SemPost( vg_async.sem_wait_for_flush );
170 }
171
172 SDL_AtomicUnlock( &vg_async.sl_index );
173 }
174
175 VG_STATIC void vg_async_init(void)
176 {
177 vg_async.sem_wait_for_flush = SDL_CreateSemaphore(0);
178 vg_async.buffer = vg_create_linear_allocator( NULL, 50*1024*1024,
179 VG_MEMORY_SYSTEM );
180 }
181
182 #endif /* VG_ASYNC_H */