replace VG_STATIC -> static
[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 {
106 vg_assert_thread(k_thread_purpose_loader);
107 vg_info( "async_stall: %d\n", SDL_SemValue( vg_async.sem_wait_for_flush ) );
108 SDL_SemWait( vg_async.sem_wait_for_flush );
109 }
110
111 /*
112 * Mark the call as being filled and ready to go
113 */
114 static void vg_async_dispatch( vg_async_item *item,
115 void (*runner)( void *payload, u32 size ) )
116 {
117 vg_assert_thread(k_thread_purpose_loader);
118 if( SDL_SemValue(vg_async.sem_wait_for_flush) )
119 SDL_SemWait(vg_async.sem_wait_for_flush);
120
121 SDL_AtomicLock( &vg_async.sl_index );
122 item->fn_runner = runner;
123 SDL_AtomicUnlock( &vg_async.sl_index );
124 }
125
126 /*
127 * Make a simple async call without allocating extra.
128 */
129 static void vg_async_call( void (*runner)( void *payload, u32 size ),
130 void *payload, u32 size )
131 {
132 vg_assert_thread(k_thread_purpose_loader);
133 vg_async_item *call = vg_async_alloc(0);
134 call->payload = payload;
135 call->size = size;
136 vg_async_dispatch( call, runner );
137 }
138
139 /*
140 * Run as much of the async buffer as possible
141 */
142 static void vg_run_async_checked(void)
143 {
144 SDL_AtomicLock( &vg_async.sl_index );
145
146 while( vg_async.start ){
147 vg_async_item *entry = vg_async.start;
148
149 if( entry->fn_runner ){
150 entry->fn_runner( entry->payload, entry->size );
151 vg_async.start = entry->next;
152
153 if( vg_async.start == NULL ){
154 vg_async.end = NULL;
155
156 vg_linear_clear( vg_async.buffer );
157
158 if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){
159 SDL_SemPost( vg_async.sem_wait_for_flush );
160 }
161 }
162 }
163 else{
164 SDL_AtomicUnlock( &vg_async.sl_index );
165 return;
166 }
167
168 /* TODO: if exceed max frametime.... */
169 }
170
171 if( !SDL_SemValue( vg_async.sem_wait_for_flush ) ){
172 SDL_SemPost( vg_async.sem_wait_for_flush );
173 }
174
175 SDL_AtomicUnlock( &vg_async.sl_index );
176 }
177
178 static void vg_async_init(void)
179 {
180 vg_async.sem_wait_for_flush = SDL_CreateSemaphore(0);
181 vg_async.buffer = vg_create_linear_allocator( NULL, 50*1024*1024,
182 VG_MEMORY_SYSTEM );
183 }
184
185 #endif /* VG_ASYNC_H */