b3c8a0af09fecd21c28f505c7bdc4d46e05c0426
[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 static void vg_assert_thread( enum vg_thread_purpose required );
13
14 typedef struct vg_async_item vg_async_item;
15
16 struct vg_async_item{
17 vg_async_item *next;
18
19 void *payload;
20 u32 size;
21
22 void (*fn_runner)( void *payload, u32 size );
23 };
24
25 struct vg_async{
26 void *buffer;
27
28 vg_async_item *start, *end;
29
30 SDL_sem *sem_wait_for_flush;
31 SDL_SpinLock sl_index;
32 }
33 static vg_async;
34
35 static enum vg_thread_purpose vg_thread_purpose(void);
36 static enum engine_status _vg_engine_status(void);
37
38 /*
39 * Allocate an asynchronous call with a bit of memory
40 */
41 static vg_async_item *vg_async_alloc( u32 size )
42 {
43 /* ditch out here if engine crashed. this serves as the 'quit checking' */
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 static void vg_async_stall(void){
105 vg_assert_thread(k_thread_purpose_loader);
106 #if 0
107 vg_info( "async_stall: %d\n", SDL_SemValue( vg_async.sem_wait_for_flush ) );
108 #endif
109 SDL_SemWait( vg_async.sem_wait_for_flush );
110 }
111
112 /*
113 * Mark the call as being filled and ready to go
114 */
115 static void vg_async_dispatch( vg_async_item *item,
116 void (*runner)( void *payload, u32 size ) )
117 {
118 vg_assert_thread(k_thread_purpose_loader);
119 if( SDL_SemValue(vg_async.sem_wait_for_flush) )
120 SDL_SemWait(vg_async.sem_wait_for_flush);
121
122 SDL_AtomicLock( &vg_async.sl_index );
123 item->fn_runner = runner;
124 SDL_AtomicUnlock( &vg_async.sl_index );
125 }
126
127 /*
128 * Make a simple async call without allocating extra.
129 */
130 static void vg_async_call( void (*runner)( void *payload, u32 size ),
131 void *payload, u32 size )
132 {
133 vg_assert_thread(k_thread_purpose_loader);
134 vg_async_item *call = vg_async_alloc(0);
135 call->payload = payload;
136 call->size = size;
137 vg_async_dispatch( call, runner );
138 }
139
140 /*
141 * Run as much of the async buffer as possible
142 */
143 static void vg_run_async_checked(void)
144 {
145 SDL_AtomicLock( &vg_async.sl_index );
146
147 while( vg_async.start ){
148 vg_async_item *entry = vg_async.start;
149
150 if( entry->fn_runner ){
151 entry->fn_runner( entry->payload, entry->size );
152 vg_async.start = entry->next;
153
154 if( vg_async.start == NULL ){
155 vg_async.end = NULL;
156
157 vg_linear_clear( vg_async.buffer );
158
159 if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){
160 SDL_SemPost( vg_async.sem_wait_for_flush );
161 }
162 }
163 }
164 else{
165 SDL_AtomicUnlock( &vg_async.sl_index );
166 return;
167 }
168
169 /* TODO: if exceed max frametime.... */
170 }
171
172 if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){
173 SDL_SemPost( vg_async.sem_wait_for_flush );
174 }
175
176 SDL_AtomicUnlock( &vg_async.sl_index );
177 }
178
179 static void vg_async_init(void)
180 {
181 vg_async.sem_wait_for_flush = SDL_CreateSemaphore(0);
182 vg_async.buffer = vg_create_linear_allocator( NULL, 50*1024*1024,
183 VG_MEMORY_SYSTEM );
184 }
185
186 #endif /* VG_ASYNC_H */