|
diff --git a/main.c b/main.c
|
|
|
1 |
// Traditional OpenGL "Immediate Mode" (glBegin/glEnd) is slow for many objects |
|
|
2 |
// because every function call creates CPU overhead. Instead, we: |
|
|
3 |
// 1. Calculate all vertex data (Position, Color, UV) on the CPU. |
|
|
4 |
// 2. Store them in a contiguous memory buffer (Batch). |
|
|
5 |
// 3. Send the entire buffer to the GPU in a single call (glDrawArrays). |
|
|
6 |
|
| 1 |
#include <stdio.h> |
7 |
#include <stdio.h> |
| 2 |
#include <stdlib.h> |
8 |
#include <stdlib.h> |
| 3 |
#include <getopt.h> |
9 |
#include <getopt.h> |
|
|
10 |
#include <math.h> |
|
|
11 |
|
|
|
12 |
#define NONSTD_IMPLEMENTATION |
|
|
13 |
#include "nonstd.h" |
| 4 |
|
14 |
|
| 5 |
#define GL_SILENCE_DEPRECATION |
15 |
#define GL_SILENCE_DEPRECATION |
|
|
16 |
|
|
|
17 |
#ifndef M_PI |
|
|
18 |
#define M_PI 3.14159265358979323846 |
|
|
19 |
#endif |
| 6 |
|
20 |
|
| 7 |
#ifdef __APPLE__ |
21 |
#ifdef __APPLE__ |
| 8 |
#include <OpenGL/gl.h> |
22 |
#include <OpenGL/gl.h> |
| ... |
| 24 |
float x, y, z, w; |
38 |
float x, y, z, w; |
| 25 |
} Vec4; |
39 |
} Vec4; |
| 26 |
|
40 |
|
|
|
41 |
// A packed structure representing a single vertex. |
|
|
42 |
// The GPU needs to know the "stride" (size) of this struct to jump between vertices. |
|
|
43 |
typedef struct { |
|
|
44 |
float x, y, z; // Position |
|
|
45 |
float r, g, b; // Color |
|
|
46 |
float u, v; // Texture Coordinates (UV) |
|
|
47 |
} Vertex; |
|
|
48 |
|
|
|
49 |
// Batching using nonstd.h dynamic arrays. |
|
|
50 |
// This allows us to push an arbitrary number of vertices without manual reallocs. |
|
|
51 |
typedef array(Vertex) Batch; |
|
|
52 |
|
|
|
53 |
Batch textured_batch = {0}; // Grouping textured faces to minimize state changes |
|
|
54 |
Batch colored_batch = {0}; // Grouping solid color faces |
|
|
55 |
|
|
|
56 |
// Rodrigues' rotation formula. |
|
|
57 |
// Since we are batching, we can't use glRotatef (which modifies the global matrix). |
|
|
58 |
// We must rotate every vertex manually on the CPU before pushing it to the buffer. |
|
|
59 |
Vec3 vec3_rotate(Vec3 v, float angle, Vec3 axis) { |
|
|
60 |
float rad = angle * M_PI / 180.0f; |
|
|
61 |
float c = cosf(rad); |
|
|
62 |
float s = sinf(rad); |
|
|
63 |
|
|
|
64 |
float len = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); |
|
|
65 |
if (len > 0) { axis.x /= len; axis.y /= len; axis.z /= len; } |
|
|
66 |
|
|
|
67 |
float x = axis.x, y = axis.y, z = axis.z; |
|
|
68 |
float dot = v.x*x + v.y*y + v.z*z; |
|
|
69 |
Vec3 cross = { |
|
|
70 |
y*v.z - z*v.y, |
|
|
71 |
z*v.x - x*v.z, |
|
|
72 |
x*v.y - y*v.x |
|
|
73 |
}; |
|
|
74 |
|
|
|
75 |
return (Vec3){ |
|
|
76 |
v.x*c + cross.x*s + x*dot*(1-c), |
|
|
77 |
v.y*c + cross.y*s + y*dot*(1-c), |
|
|
78 |
v.z*c + cross.z*s + z*dot*(1-c) |
|
|
79 |
}; |
|
|
80 |
} |
|
|
81 |
|
| 27 |
#define COLOR_RED (Vec3){ .x = 1.0f, .y = 0.0f, .z = 0.0f } |
82 |
#define COLOR_RED (Vec3){ .x = 1.0f, .y = 0.0f, .z = 0.0f } |
| 28 |
#define COLOR_GREEN (Vec3){ .x = 0.0f, .y = 1.0f, .z = 0.0f } |
83 |
#define COLOR_GREEN (Vec3){ .x = 0.0f, .y = 1.0f, .z = 0.0f } |
| 29 |
#define COLOR_BLUE (Vec3){ .x = 0.0f, .y = 0.0f, .z = 1.0f } |
84 |
#define COLOR_BLUE (Vec3){ .x = 0.0f, .y = 0.0f, .z = 1.0f } |
| ... |
| 86 |
|
141 |
|
| 87 |
while (getc(fp) != '\n'); // skip single whitespace |
142 |
while (getc(fp) != '\n'); // skip single whitespace |
| 88 |
|
143 |
|
| 89 |
unsigned char* data = (unsigned char*)malloc((*width) * (*height) * 3); |
144 |
unsigned char* data = ALLOC(unsigned char, (*width) * (*height) * 3); |
| 90 |
if (!data) { |
145 |
if (!data) { |
| 91 |
fprintf(stderr, "Memory allocation failed\n"); |
146 |
fprintf(stderr, "Memory allocation failed\n"); |
| 92 |
fclose(fp); |
147 |
fclose(fp); |
| ... |
| 95 |
|
150 |
|
| 96 |
if (fread(data, 1, (*width) * (*height) * 3, fp) != (size_t)((*width) * (*height) * 3)) { |
151 |
if (fread(data, 1, (*width) * (*height) * 3, fp) != (size_t)((*width) * (*height) * 3)) { |
| 97 |
fprintf(stderr, "Error reading PPM data\n"); |
152 |
fprintf(stderr, "Error reading PPM data\n"); |
| 98 |
free(data); |
153 |
FREE(data); |
| 99 |
fclose(fp); |
154 |
fclose(fp); |
| 100 |
return NULL; |
155 |
return NULL; |
| 101 |
} |
156 |
} |
| ... |
| 117 |
|
172 |
|
| 118 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); |
173 |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); |
| 119 |
|
174 |
|
| 120 |
free(data); |
175 |
FREE(data); |
| 121 |
} |
176 |
} |
| 122 |
|
177 |
|
| 123 |
void draw_text(float x, float y, void* font, const char* string, Vec3 color) { |
178 |
void draw_text(float x, float y, void* font, const char* string, Vec3 color) { |
| ... |
| 143 |
glMatrixMode(GL_MODELVIEW); |
198 |
glMatrixMode(GL_MODELVIEW); |
| 144 |
} |
199 |
} |
| 145 |
|
200 |
|
| 146 |
void draw_cube(void) { |
201 |
void batch_push_vertex(Batch* batch, Vertex v) { |
| 147 |
glEnable(GL_TEXTURE_2D); |
202 |
array_push(*batch, v); |
| 148 |
glBindTexture(GL_TEXTURE_2D, game.texture_id); |
203 |
} |
| 149 |
glBegin(GL_QUADS); |
|
|
| 150 |
// Front Face (Textured) |
|
|
| 151 |
glColor3f(1.0f, 1.0f, 1.0f); |
|
|
| 152 |
|
204 |
|
| 153 |
// Backface |
205 |
// Takes object-space coordinates and transforms them into world-space. |
| 154 |
glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.1f, -0.1f, 0.1f); |
206 |
// Vertices are then sorted into either the textured or colored batch. |
| 155 |
glTexCoord2f(1.0f, 0.0f); glVertex3f( 0.1f, -0.1f, 0.1f); |
207 |
void batch_push_cube(Vec3 pos, float size, float angle, Vec3 axis) { |
| 156 |
glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.1f, 0.1f, 0.1f); |
208 |
float h = size / 2.0f; |
| 157 |
glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.1f, 0.1f, 0.1f); |
209 |
Vec3 corners[8] = { |
|
|
210 |
{-h, -h, h}, { h, -h, h}, { h, h, h}, {-h, h, h}, // Front |
|
|
211 |
{-h, -h, -h}, { h, -h, -h}, { h, h, -h}, {-h, h, -h} // Back |
|
|
212 |
}; |
| 158 |
|
213 |
|
| 159 |
// Top face |
214 |
// Transform vertices (Rotation -> Translation) |
| 160 |
glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.1f, 0.1f, -0.1f); |
215 |
for (int i = 0; i < 8; i++) { |
| 161 |
glTexCoord2f(1.0f, 0.0f); glVertex3f(-0.1f, 0.1f, 0.1f); |
216 |
if (angle != 0.0f) corners[i] = vec3_rotate(corners[i], angle, axis); |
| 162 |
glTexCoord2f(1.0f, 1.0f); glVertex3f( 0.1f, 0.1f, 0.1f); |
217 |
corners[i].x += pos.x; |
| 163 |
glTexCoord2f(0.0f, 1.0f); glVertex3f( 0.1f, 0.1f, -0.1f); |
218 |
corners[i].y += pos.y; |
| 164 |
glEnd(); |
219 |
corners[i].z += pos.z; |
| 165 |
glDisable(GL_TEXTURE_2D); |
220 |
} |
|
|
221 |
|
|
|
222 |
// Pushing Front and Top faces to the TEXTURED batch |
|
|
223 |
Vec3 white = {1,1,1}; |
|
|
224 |
batch_push_vertex(&textured_batch, (Vertex){corners[0].x, corners[0].y, corners[0].z, white.x, white.y, white.z, 0, 0}); |
|
|
225 |
batch_push_vertex(&textured_batch, (Vertex){corners[1].x, corners[1].y, corners[1].z, white.x, white.y, white.z, 1, 0}); |
|
|
226 |
batch_push_vertex(&textured_batch, (Vertex){corners[2].x, corners[2].y, corners[2].z, white.x, white.y, white.z, 1, 1}); |
|
|
227 |
batch_push_vertex(&textured_batch, (Vertex){corners[3].x, corners[3].y, corners[3].z, white.x, white.y, white.z, 0, 1}); |
| 166 |
|
228 |
|
| 167 |
glBegin(GL_QUADS); |
229 |
batch_push_vertex(&textured_batch, (Vertex){corners[7].x, corners[7].y, corners[7].z, white.x, white.y, white.z, 0, 0}); |
| 168 |
// Back Face (Yellow) |
230 |
batch_push_vertex(&textured_batch, (Vertex){corners[3].x, corners[3].y, corners[3].z, white.x, white.y, white.z, 1, 0}); |
| 169 |
glColor3f(1.0f, 1.0f, 0.0f); |
231 |
batch_push_vertex(&textured_batch, (Vertex){corners[2].x, corners[2].y, corners[2].z, white.x, white.y, white.z, 1, 1}); |
| 170 |
glVertex3f(-0.1f, -0.1f, -0.1f); |
232 |
batch_push_vertex(&textured_batch, (Vertex){corners[6].x, corners[6].y, corners[6].z, white.x, white.y, white.z, 0, 1}); |
| 171 |
glVertex3f(-0.1f, 0.1f, -0.1f); |
|
|
| 172 |
glVertex3f( 0.1f, 0.1f, -0.1f); |
|
|
| 173 |
glVertex3f( 0.1f, -0.1f, -0.1f); |
|
|
| 174 |
|
233 |
|
| 175 |
// Top Face (Blue) |
234 |
// Pushing remaining faces to the COLORED batch |
| 176 |
glColor3f(0.0f, 0.0f, 1.0f); |
235 |
Vec3 yellow = {1,1,0}; |
| 177 |
glVertex3f(-0.1f, 0.1f, -0.1f); |
236 |
batch_push_vertex(&colored_batch, (Vertex){corners[4].x, corners[4].y, corners[4].z, yellow.x, yellow.y, yellow.z, 0, 0}); |
| 178 |
glVertex3f(-0.1f, 0.1f, 0.1f); |
237 |
batch_push_vertex(&colored_batch, (Vertex){corners[7].x, corners[7].y, corners[7].z, yellow.x, yellow.y, yellow.z, 0, 0}); |
| 179 |
glVertex3f( 0.1f, 0.1f, 0.1f); |
238 |
batch_push_vertex(&colored_batch, (Vertex){corners[6].x, corners[6].y, corners[6].z, yellow.x, yellow.y, yellow.z, 0, 0}); |
| 180 |
glVertex3f( 0.1f, 0.1f, -0.1f); |
239 |
batch_push_vertex(&colored_batch, (Vertex){corners[5].x, corners[5].y, corners[5].z, yellow.x, yellow.y, yellow.z, 0, 0}); |
| 181 |
|
240 |
|
| 182 |
// Bottom Face (Red) |
241 |
Vec3 magenta = {1,0,1}; |
| 183 |
glColor3f(1.0f, 0.0f, 1.0f); |
242 |
batch_push_vertex(&colored_batch, (Vertex){corners[4].x, corners[4].y, corners[4].z, magenta.x, magenta.y, magenta.z, 0, 0}); |
| 184 |
glVertex3f(-0.1f, -0.1f, -0.1f); |
243 |
batch_push_vertex(&colored_batch, (Vertex){corners[5].x, corners[5].y, corners[5].z, magenta.x, magenta.y, magenta.z, 0, 0}); |
| 185 |
glVertex3f( 0.1f, -0.1f, -0.1f); |
244 |
batch_push_vertex(&colored_batch, (Vertex){corners[1].x, corners[1].y, corners[1].z, magenta.x, magenta.y, magenta.z, 0, 0}); |
| 186 |
glVertex3f( 0.1f, -0.1f, 0.1f); |
245 |
batch_push_vertex(&colored_batch, (Vertex){corners[0].x, corners[0].y, corners[0].z, magenta.x, magenta.y, magenta.z, 0, 0}); |
| 187 |
glVertex3f(-0.1f, -0.1f, 0.1f); |
|
|
| 188 |
|
246 |
|
| 189 |
// Right Face (Magenta) |
247 |
Vec3 red = {1,0,0}; |
| 190 |
glColor3f(1.0f, 0.0f, 1.0f); |
248 |
batch_push_vertex(&colored_batch, (Vertex){corners[1].x, corners[1].y, corners[1].z, red.x, red.y, red.z, 0, 0}); |
| 191 |
glVertex3f( 0.1f, -0.1f, -0.1f); |
249 |
batch_push_vertex(&colored_batch, (Vertex){corners[5].x, corners[5].y, corners[5].z, red.x, red.y, red.z, 0, 0}); |
| 192 |
glVertex3f( 0.1f, 0.1f, -0.1f); |
250 |
batch_push_vertex(&colored_batch, (Vertex){corners[6].x, corners[6].y, corners[6].z, red.x, red.y, red.z, 0, 0}); |
| 193 |
glVertex3f( 0.1f, 0.1f, 0.1f); |
251 |
batch_push_vertex(&colored_batch, (Vertex){corners[2].x, corners[2].y, corners[2].z, red.x, red.y, red.z, 0, 0}); |
| 194 |
glVertex3f( 0.1f, -0.1f, 0.1f); |
|
|
| 195 |
|
252 |
|
| 196 |
// Left Face (Cyan) |
253 |
Vec3 cyan = {0,1,1}; |
| 197 |
glColor3f(0.0f, 1.0f, 1.0f); |
254 |
batch_push_vertex(&colored_batch, (Vertex){corners[4].x, corners[4].y, corners[4].z, cyan.x, cyan.y, cyan.z, 0, 0}); |
| 198 |
glVertex3f(-0.1f, -0.1f, -0.1f); |
255 |
batch_push_vertex(&colored_batch, (Vertex){corners[0].x, corners[0].y, corners[0].z, cyan.x, cyan.y, cyan.z, 0, 0}); |
| 199 |
glVertex3f(-0.1f, -0.1f, 0.1f); |
256 |
batch_push_vertex(&colored_batch, (Vertex){corners[3].x, corners[3].y, corners[3].z, cyan.x, cyan.y, cyan.z, 0, 0}); |
| 200 |
glVertex3f(-0.1f, 0.1f, 0.1f); |
257 |
batch_push_vertex(&colored_batch, (Vertex){corners[7].x, corners[7].y, corners[7].z, cyan.x, cyan.y, cyan.z, 0, 0}); |
| 201 |
glVertex3f(-0.1f, 0.1f, -0.1f); |
|
|
| 202 |
glEnd(); |
|
|
| 203 |
} |
258 |
} |
| 204 |
|
259 |
|
| 205 |
void draw_quad() { |
260 |
// The main render call that talks to the GPU. |
| 206 |
glBegin(GL_POLYGON); |
261 |
void batch_render() { |
| 207 |
glVertex3f (0.25, 0.25, 0.0); |
262 |
// Enable client-side arrays (tells OpenGL we are passing memory pointers) |
| 208 |
glVertex3f (0.75, 0.25, 0.0); |
263 |
glEnableClientState(GL_VERTEX_ARRAY); |
| 209 |
glVertex3f (0.75, 0.75, 0.0); |
264 |
glEnableClientState(GL_COLOR_ARRAY); |
| 210 |
glVertex3f (0.25, 0.75, 0.0); |
265 |
glEnableClientState(GL_TEXTURE_COORD_ARRAY); |
| 211 |
glEnd(); |
266 |
|
|
|
267 |
// 1. Render all TEXTURED geometry |
|
|
268 |
if (textured_batch.length > 0) { |
|
|
269 |
glEnable(GL_TEXTURE_2D); |
|
|
270 |
glBindTexture(GL_TEXTURE_2D, game.texture_id); |
|
|
271 |
|
|
|
272 |
Vertex* v = textured_batch.data; |
|
|
273 |
// Pointer math: specify (size, type, stride, offset) |
|
|
274 |
glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &v[0].x); |
|
|
275 |
glColorPointer(3, GL_FLOAT, sizeof(Vertex), &v[0].r); |
|
|
276 |
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &v[0].u); |
|
|
277 |
|
|
|
278 |
glDrawArrays(GL_QUADS, 0, textured_batch.length); |
|
|
279 |
glDisable(GL_TEXTURE_2D); |
|
|
280 |
} |
|
|
281 |
|
|
|
282 |
// 2. Render all COLORED geometry |
|
|
283 |
if (colored_batch.length > 0) { |
|
|
284 |
Vertex* v = colored_batch.data; |
|
|
285 |
glVertexPointer(3, GL_FLOAT, sizeof(Vertex), &v[0].x); |
|
|
286 |
glColorPointer(3, GL_FLOAT, sizeof(Vertex), &v[0].r); |
|
|
287 |
glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), &v[0].u); |
|
|
288 |
|
|
|
289 |
glDrawArrays(GL_QUADS, 0, colored_batch.length); |
|
|
290 |
} |
|
|
291 |
|
|
|
292 |
glDisableClientState(GL_VERTEX_ARRAY); |
|
|
293 |
glDisableClientState(GL_COLOR_ARRAY); |
|
|
294 |
glDisableClientState(GL_TEXTURE_COORD_ARRAY); |
|
|
295 |
|
|
|
296 |
// Clear length (re-use capacity) for the next frame |
|
|
297 |
array_clear(textured_batch); |
|
|
298 |
array_clear(colored_batch); |
| 212 |
} |
299 |
} |
|
|
300 |
|
| 213 |
|
301 |
|
| 214 |
void render_display(void) { |
302 |
void render_display(void) { |
| 215 |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
303 |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| ... |
| 217 |
glMatrixMode(GL_MODELVIEW); |
305 |
glMatrixMode(GL_MODELVIEW); |
| 218 |
glLoadIdentity(); |
306 |
glLoadIdentity(); |
| 219 |
|
307 |
|
| 220 |
// Render Moving Square |
308 |
// Render Batch of Cubes |
| 221 |
/* glPushMatrix(); */ |
309 |
for (int i = 0; i < 10; i++) { |
| 222 |
/* glTranslatef(x_offset, y_offset, 0.0f); */ |
310 |
for (int j = 0; j < 10; j++) { |
| 223 |
/* glColor3f(1.0, 1.0, 1.0); */ |
311 |
float x = 0.1f + i * 0.1f; |
| 224 |
/* draw_quad(); */ |
312 |
float y = 0.1f + j * 0.1f; |
| 225 |
/* glPopMatrix(); */ |
313 |
float angle = game.cube_angle + (i + j) * 10.0f; |
| 226 |
|
314 |
batch_push_cube((Vec3){x, y, 0.0f}, 0.05f, angle, (Vec3){1.0f, 1.0f, 1.0f}); |
| 227 |
// Render Spinning Cube |
315 |
} |
| 228 |
glPushMatrix(); |
316 |
} |
| 229 |
glTranslatef(0.5f, 0.5f, 0.0f); |
317 |
batch_render(); |
| 230 |
glRotatef(game.cube_angle, 1.0f, 1.0f, 1.0f); |
|
|
| 231 |
draw_cube(); |
|
|
| 232 |
glPopMatrix(); |
|
|
| 233 |
|
318 |
|
| 234 |
// Render UI Text |
319 |
// Render UI Text |
| 235 |
char fps_text[32]; |
320 |
char fps_text[32]; |
| ... |
|
diff --git a/nonstd.h b/nonstd.h
|
|
|
1 |
// nonstd.h |
|
|
2 |
// A collection of useful functions and macros. |
|
|
3 |
// This library is licensed under the BSD 2-Clause License. |
|
|
4 |
// |
|
|
5 |
// This file provides both the interface and the implementation. |
|
|
6 |
// To instantiate the implementation, |
|
|
7 |
// #define NONSTD_IMPLEMENTATION |
|
|
8 |
// before including this file. |
|
|
9 |
|
|
|
10 |
#ifdef NONSTD_IMPLEMENTATION |
|
|
11 |
#ifndef _POSIX_C_SOURCE |
|
|
12 |
#define _POSIX_C_SOURCE 200809L |
|
|
13 |
#endif |
|
|
14 |
#endif |
|
|
15 |
|
|
|
16 |
#ifndef NONSTD_H |
|
|
17 |
#define NONSTD_H |
|
|
18 |
|
|
|
19 |
#include <stdarg.h> |
|
|
20 |
#include <stddef.h> |
|
|
21 |
#include <stdint.h> |
|
|
22 |
#include <stdio.h> |
|
|
23 |
#include <stdlib.h> |
|
|
24 |
#include <string.h> |
|
|
25 |
#include <sys/time.h> |
|
|
26 |
#include <time.h> |
|
|
27 |
#include <unistd.h> |
|
|
28 |
|
|
|
29 |
#ifndef NONSTD_DEF |
|
|
30 |
#ifdef NONSTD_STATIC |
|
|
31 |
#define NONSTD_DEF static |
|
|
32 |
#else |
|
|
33 |
#define NONSTD_DEF extern |
|
|
34 |
#endif |
|
|
35 |
#endif |
|
|
36 |
|
|
|
37 |
typedef int8_t i8; |
|
|
38 |
typedef uint8_t u8; |
|
|
39 |
typedef int16_t i16; |
|
|
40 |
typedef uint16_t u16; |
|
|
41 |
typedef int32_t i32; |
|
|
42 |
typedef uint32_t u32; |
|
|
43 |
typedef int64_t i64; |
|
|
44 |
typedef uint64_t u64; |
|
|
45 |
typedef unsigned int uint; |
|
|
46 |
typedef unsigned long ulong; |
|
|
47 |
typedef intptr_t isize; |
|
|
48 |
typedef uintptr_t usize; |
|
|
49 |
typedef char c8; |
|
|
50 |
|
|
|
51 |
#define countof(a) (sizeof(a) / sizeof((a)[0])) |
|
|
52 |
|
|
|
53 |
#define static_foreach(type, var, array) \ |
|
|
54 |
for (size_t _i_##var = 0, _n_##var = countof(array); \ |
|
|
55 |
_i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var) |
|
|
56 |
|
|
|
57 |
#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n))) |
|
|
58 |
#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n))) |
|
|
59 |
#define FREE(ptr) \ |
|
|
60 |
do { \ |
|
|
61 |
free(ptr); \ |
|
|
62 |
ptr = NULL; \ |
|
|
63 |
} while (0) |
|
|
64 |
|
|
|
65 |
NONSTD_DEF void *safe_malloc(size_t item_size, size_t count); |
|
|
66 |
NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count); |
|
|
67 |
|
|
|
68 |
#ifndef MIN |
|
|
69 |
#define MIN(a, b) ((a) < (b) ? (a) : (b)) |
|
|
70 |
#endif |
|
|
71 |
#ifndef MAX |
|
|
72 |
#define MAX(a, b) ((a) > (b) ? (a) : (b)) |
|
|
73 |
#endif |
|
|
74 |
#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x)))) |
|
|
75 |
|
|
|
76 |
// From https://github.com/tsoding/nob.h/blob/e2c9a46f01d052ab740140e74453665dc3334832/nob.h#L205-L206. |
|
|
77 |
#define UNUSED(value) (void)(value) |
|
|
78 |
#define TODO(message) \ |
|
|
79 |
do { \ |
|
|
80 |
fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \ |
|
|
81 |
abort(); \ |
|
|
82 |
} while (0) |
|
|
83 |
#define UNREACHABLE(message) \ |
|
|
84 |
do { \ |
|
|
85 |
fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \ |
|
|
86 |
abort(); \ |
|
|
87 |
} while (0) |
|
|
88 |
|
|
|
89 |
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L |
|
|
90 |
#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg) |
|
|
91 |
#else |
|
|
92 |
#define STATIC_ASSERT(expr, msg) \ |
|
|
93 |
typedef char static_assertion_##msg[(expr) ? 1 : -1] |
|
|
94 |
#endif |
|
|
95 |
|
|
|
96 |
// String view - read-only, non-owning reference to a string |
|
|
97 |
typedef struct { |
|
|
98 |
const char *data; |
|
|
99 |
size_t length; |
|
|
100 |
} stringv; |
|
|
101 |
|
|
|
102 |
NONSTD_DEF stringv sv_from_cstr(const char *s); |
|
|
103 |
NONSTD_DEF stringv sv_from_parts(const char *data, size_t length); |
|
|
104 |
NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end); |
|
|
105 |
NONSTD_DEF int sv_equals(stringv a, stringv b); |
|
|
106 |
NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix); |
|
|
107 |
NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix); |
|
|
108 |
|
|
|
109 |
// String builder - owning, mutable, dynamically growing string buffer |
|
|
110 |
typedef struct { |
|
|
111 |
char *data; |
|
|
112 |
size_t length; |
|
|
113 |
size_t capacity; |
|
|
114 |
} stringb; |
|
|
115 |
|
|
|
116 |
NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap); |
|
|
117 |
NONSTD_DEF void sb_free(stringb *sb); |
|
|
118 |
NONSTD_DEF void sb_ensure(stringb *sb, size_t additional); |
|
|
119 |
NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s); |
|
|
120 |
NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv); |
|
|
121 |
NONSTD_DEF void sb_append_char(stringb *sb, char c); |
|
|
122 |
NONSTD_DEF stringv sb_as_sv(const stringb *sb); |
|
|
123 |
|
|
|
124 |
// Slice - generic non-owning view into an array |
|
|
125 |
// Usage: SLICE_DEF(int); slice(int) view = ...; |
|
|
126 |
#define SLICE_DEF(T) \ |
|
|
127 |
typedef struct { \ |
|
|
128 |
T *data; \ |
|
|
129 |
size_t length; \ |
|
|
130 |
} slice_##T |
|
|
131 |
|
|
|
132 |
#define slice(T) slice_##T |
|
|
133 |
|
|
|
134 |
#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)}) |
|
|
135 |
|
|
|
136 |
#define array_as_slice(T, arr) \ |
|
|
137 |
((slice(T)){.data = (arr).data, .length = (arr).length}) |
|
|
138 |
|
|
|
139 |
// Dynamic array - generic type-safe growable array using macros |
|
|
140 |
// Usage: array(int) numbers; array_init(numbers); |
|
|
141 |
#define array(T) \ |
|
|
142 |
struct { \ |
|
|
143 |
T *data; \ |
|
|
144 |
size_t length; \ |
|
|
145 |
size_t capacity; \ |
|
|
146 |
} |
|
|
147 |
|
|
|
148 |
#define array_init(arr) \ |
|
|
149 |
do { \ |
|
|
150 |
(arr).capacity = 0; \ |
|
|
151 |
(arr).data = NULL; \ |
|
|
152 |
(arr).length = 0; \ |
|
|
153 |
} while (0) |
|
|
154 |
|
|
|
155 |
#define array_init_cap(arr, initial_cap) \ |
|
|
156 |
do { \ |
|
|
157 |
(arr).capacity = (initial_cap) ? (initial_cap) : 16; \ |
|
|
158 |
(arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \ |
|
|
159 |
(arr).length = 0; \ |
|
|
160 |
} while (0) |
|
|
161 |
|
|
|
162 |
#define array_free(arr) \ |
|
|
163 |
do { \ |
|
|
164 |
FREE((arr).data); \ |
|
|
165 |
(arr).length = 0; \ |
|
|
166 |
(arr).capacity = 0; \ |
|
|
167 |
} while (0) |
|
|
168 |
|
|
|
169 |
#define array_ensure(arr, additional) \ |
|
|
170 |
do { \ |
|
|
171 |
size_t _needed = (arr).length + (additional); \ |
|
|
172 |
if (_needed > (arr).capacity) { \ |
|
|
173 |
size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \ |
|
|
174 |
while (_new_cap < _needed) { \ |
|
|
175 |
if (_new_cap > SIZE_MAX / 2) { \ |
|
|
176 |
_new_cap = SIZE_MAX; \ |
|
|
177 |
break; \ |
|
|
178 |
} \ |
|
|
179 |
_new_cap *= 2; \ |
|
|
180 |
} \ |
|
|
181 |
if (_new_cap < _needed) { /* Overflow or OOM */ \ |
|
|
182 |
break; \ |
|
|
183 |
} \ |
|
|
184 |
void *_new_data = \ |
|
|
185 |
safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \ |
|
|
186 |
if (_new_data) { \ |
|
|
187 |
(arr).data = _new_data; \ |
|
|
188 |
(arr).capacity = _new_cap; \ |
|
|
189 |
} \ |
|
|
190 |
} \ |
|
|
191 |
} while (0) |
|
|
192 |
|
|
|
193 |
#define array_push(arr, value) \ |
|
|
194 |
do { \ |
|
|
195 |
array_ensure((arr), 1); \ |
|
|
196 |
if ((arr).length < (arr).capacity) { \ |
|
|
197 |
(arr).data[(arr).length++] = (value); \ |
|
|
198 |
} \ |
|
|
199 |
} while (0) |
|
|
200 |
|
|
|
201 |
#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0) |
|
|
202 |
|
|
|
203 |
#define array_get(arr, index) ((arr).data[index]) |
|
|
204 |
|
|
|
205 |
#define array_set(arr, index, value) \ |
|
|
206 |
do { \ |
|
|
207 |
if ((index) < (arr).length) { \ |
|
|
208 |
(arr).data[index] = (value); \ |
|
|
209 |
} \ |
|
|
210 |
} while (0) |
|
|
211 |
|
|
|
212 |
#define array_insert(arr, index, value) \ |
|
|
213 |
do { \ |
|
|
214 |
if ((index) <= (arr).length) { \ |
|
|
215 |
array_ensure((arr), 1); \ |
|
|
216 |
if ((arr).length < (arr).capacity) { \ |
|
|
217 |
for (size_t _i = (arr).length; _i > (index); --_i) { \ |
|
|
218 |
(arr).data[_i] = (arr).data[_i - 1]; \ |
|
|
219 |
} \ |
|
|
220 |
(arr).data[index] = (value); \ |
|
|
221 |
(arr).length++; \ |
|
|
222 |
} \ |
|
|
223 |
} \ |
|
|
224 |
} while (0) |
|
|
225 |
|
|
|
226 |
#define array_remove(arr, index) \ |
|
|
227 |
do { \ |
|
|
228 |
if ((index) < (arr).length) { \ |
|
|
229 |
for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \ |
|
|
230 |
(arr).data[_i] = (arr).data[_i + 1]; \ |
|
|
231 |
} \ |
|
|
232 |
(arr).length--; \ |
|
|
233 |
} \ |
|
|
234 |
} while (0) |
|
|
235 |
|
|
|
236 |
#define array_clear(arr) \ |
|
|
237 |
do { \ |
|
|
238 |
(arr).length = 0; \ |
|
|
239 |
} while (0) |
|
|
240 |
|
|
|
241 |
#define array_reserve(arr, new_capacity) \ |
|
|
242 |
do { \ |
|
|
243 |
if ((new_capacity) > (arr).capacity) { \ |
|
|
244 |
void *_new_data = \ |
|
|
245 |
safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \ |
|
|
246 |
if (_new_data) { \ |
|
|
247 |
(arr).data = _new_data; \ |
|
|
248 |
(arr).capacity = (new_capacity); \ |
|
|
249 |
} \ |
|
|
250 |
} \ |
|
|
251 |
} while (0) |
|
|
252 |
|
|
|
253 |
#define array_foreach(arr, var) \ |
|
|
254 |
for (size_t _i_##var = 0; \ |
|
|
255 |
_i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \ |
|
|
256 |
++_i_##var) |
|
|
257 |
|
|
|
258 |
#define array_foreach_idx(arr, var, index) \ |
|
|
259 |
for (size_t index = 0; \ |
|
|
260 |
index < (arr).length && ((var) = (arr).data[index], 1); ++index) |
|
|
261 |
|
|
|
262 |
// Arena - block-based memory allocator |
|
|
263 |
typedef struct { |
|
|
264 |
char *ptr; |
|
|
265 |
char *end; |
|
|
266 |
array(char *) blocks; |
|
|
267 |
} Arena; |
|
|
268 |
|
|
|
269 |
#define ARENA_DEFAULT_BLOCK_SIZE (4096) |
|
|
270 |
|
|
|
271 |
NONSTD_DEF Arena arena_make(void); |
|
|
272 |
NONSTD_DEF void arena_grow(Arena *a, size_t min_size); |
|
|
273 |
NONSTD_DEF void *arena_alloc(Arena *a, size_t size); |
|
|
274 |
NONSTD_DEF void arena_free(Arena *a); |
|
|
275 |
|
|
|
276 |
// File I/O helpers |
|
|
277 |
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size); |
|
|
278 |
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size); |
|
|
279 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath); |
|
|
280 |
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv); |
|
|
281 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb); |
|
|
282 |
|
|
|
283 |
#ifdef NONSTD_IMPLEMENTATION |
|
|
284 |
|
|
|
285 |
NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) { |
|
|
286 |
if (count != 0 && item_size > SIZE_MAX / count) { |
|
|
287 |
return NULL; |
|
|
288 |
} |
|
|
289 |
return malloc(item_size * count); |
|
|
290 |
} |
|
|
291 |
|
|
|
292 |
NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) { |
|
|
293 |
if (count != 0 && item_size > SIZE_MAX / count) { |
|
|
294 |
return NULL; |
|
|
295 |
} |
|
|
296 |
return realloc(ptr, item_size * count); |
|
|
297 |
} |
|
|
298 |
|
|
|
299 |
// String View Implementation |
|
|
300 |
|
|
|
301 |
NONSTD_DEF stringv sv_from_cstr(const char *s) { |
|
|
302 |
return (stringv){.data = s, .length = s ? strlen(s) : 0}; |
|
|
303 |
} |
|
|
304 |
|
|
|
305 |
NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) { |
|
|
306 |
return (stringv){.data = data, .length = length}; |
|
|
307 |
} |
|
|
308 |
|
|
|
309 |
NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) { |
|
|
310 |
if (start > sv.length) { |
|
|
311 |
start = sv.length; |
|
|
312 |
} |
|
|
313 |
if (end > sv.length) { |
|
|
314 |
end = sv.length; |
|
|
315 |
} |
|
|
316 |
if (start > end) { |
|
|
317 |
start = end; |
|
|
318 |
} |
|
|
319 |
return (stringv){.data = sv.data + start, .length = end - start}; |
|
|
320 |
} |
|
|
321 |
|
|
|
322 |
NONSTD_DEF int sv_equals(stringv a, stringv b) { |
|
|
323 |
return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0); |
|
|
324 |
} |
|
|
325 |
|
|
|
326 |
NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) { |
|
|
327 |
return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0; |
|
|
328 |
} |
|
|
329 |
|
|
|
330 |
NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) { |
|
|
331 |
return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0; |
|
|
332 |
} |
|
|
333 |
|
|
|
334 |
// String Builder Implementation |
|
|
335 |
|
|
|
336 |
NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) { |
|
|
337 |
sb->capacity = initial_cap ? initial_cap : 16; |
|
|
338 |
sb->data = ALLOC(char, sb->capacity); |
|
|
339 |
sb->length = 0; |
|
|
340 |
if (sb->data) { |
|
|
341 |
sb->data[0] = '\0'; |
|
|
342 |
} |
|
|
343 |
} |
|
|
344 |
|
|
|
345 |
NONSTD_DEF void sb_free(stringb *sb) { |
|
|
346 |
FREE(sb->data); |
|
|
347 |
sb->length = 0; |
|
|
348 |
sb->capacity = 0; |
|
|
349 |
} |
|
|
350 |
|
|
|
351 |
NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) { |
|
|
352 |
size_t needed = sb->length + additional + 1; |
|
|
353 |
size_t new_cap = sb->capacity; |
|
|
354 |
|
|
|
355 |
if (needed > new_cap) { |
|
|
356 |
while (new_cap < needed) { |
|
|
357 |
if (new_cap > SIZE_MAX / 2) { |
|
|
358 |
new_cap = SIZE_MAX; |
|
|
359 |
break; |
|
|
360 |
} |
|
|
361 |
new_cap *= 2; |
|
|
362 |
} |
|
|
363 |
if (new_cap < needed) |
|
|
364 |
return; // Overflow |
|
|
365 |
|
|
|
366 |
char *new_data = safe_realloc(sb->data, sizeof(char), new_cap); |
|
|
367 |
if (new_data) { |
|
|
368 |
sb->data = new_data; |
|
|
369 |
sb->capacity = new_cap; |
|
|
370 |
} |
|
|
371 |
} |
|
|
372 |
} |
|
|
373 |
|
|
|
374 |
NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) { |
|
|
375 |
if (!s) { |
|
|
376 |
return; |
|
|
377 |
} |
|
|
378 |
size_t slength = strlen(s); |
|
|
379 |
sb_ensure(sb, slength); |
|
|
380 |
if (sb->length + slength + 1 <= sb->capacity) { |
|
|
381 |
memcpy(sb->data + sb->length, s, slength); |
|
|
382 |
sb->length += slength; |
|
|
383 |
sb->data[sb->length] = '\0'; |
|
|
384 |
} |
|
|
385 |
} |
|
|
386 |
|
|
|
387 |
NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) { |
|
|
388 |
if (!sv.data || sv.length == 0) { |
|
|
389 |
return; |
|
|
390 |
} |
|
|
391 |
sb_ensure(sb, sv.length); |
|
|
392 |
if (sb->length + sv.length + 1 <= sb->capacity) { |
|
|
393 |
memcpy(sb->data + sb->length, sv.data, sv.length); |
|
|
394 |
sb->length += sv.length; |
|
|
395 |
sb->data[sb->length] = '\0'; |
|
|
396 |
} |
|
|
397 |
} |
|
|
398 |
|
|
|
399 |
NONSTD_DEF void sb_append_char(stringb *sb, char c) { |
|
|
400 |
sb_ensure(sb, 1); |
|
|
401 |
if (sb->length + 2 <= sb->capacity) { |
|
|
402 |
sb->data[sb->length++] = c; |
|
|
403 |
sb->data[sb->length] = '\0'; |
|
|
404 |
} |
|
|
405 |
} |
|
|
406 |
|
|
|
407 |
NONSTD_DEF stringv sb_as_sv(const stringb *sb) { |
|
|
408 |
return (stringv){.data = sb->data, .length = sb->length}; |
|
|
409 |
} |
|
|
410 |
|
|
|
411 |
NONSTD_DEF Arena arena_make(void) { |
|
|
412 |
Arena a = {0}; |
|
|
413 |
array_init(a.blocks); |
|
|
414 |
return a; |
|
|
415 |
} |
|
|
416 |
|
|
|
417 |
// Arena Implementation |
|
|
418 |
|
|
|
419 |
NONSTD_DEF void arena_grow(Arena *a, size_t min_size) { |
|
|
420 |
size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size); |
|
|
421 |
char *block = ALLOC(char, size); |
|
|
422 |
a->ptr = block; |
|
|
423 |
a->end = block + size; |
|
|
424 |
array_push(a->blocks, block); |
|
|
425 |
} |
|
|
426 |
|
|
|
427 |
NONSTD_DEF void *arena_alloc(Arena *a, size_t size) { |
|
|
428 |
// Align to 8 bytes basically |
|
|
429 |
size_t align = sizeof(void *); |
|
|
430 |
uintptr_t current = (uintptr_t)a->ptr; |
|
|
431 |
uintptr_t aligned = (current + align - 1) & ~(align - 1); |
|
|
432 |
uintptr_t end = (uintptr_t)a->end; |
|
|
433 |
|
|
|
434 |
// Check for overflow (aligned wrapped around) or out of bounds (aligned >= end) |
|
|
435 |
// or not enough space ((end - aligned) < size) |
|
|
436 |
if (aligned < current || aligned >= end || (end - aligned) < size) { |
|
|
437 |
arena_grow(a, size); |
|
|
438 |
current = (uintptr_t)a->ptr; |
|
|
439 |
aligned = (current + align - 1) & ~(align - 1); |
|
|
440 |
end = (uintptr_t)a->end; |
|
|
441 |
} |
|
|
442 |
|
|
|
443 |
// Double check after grow (in case grow failed or size is just too huge) |
|
|
444 |
if (aligned < current || aligned >= end || (end - aligned) < size) { |
|
|
445 |
return NULL; |
|
|
446 |
} |
|
|
447 |
|
|
|
448 |
a->ptr = (char *)(aligned + size); |
|
|
449 |
return (void *)aligned; |
|
|
450 |
} |
|
|
451 |
|
|
|
452 |
NONSTD_DEF void arena_free(Arena *a) { |
|
|
453 |
char *block; |
|
|
454 |
array_foreach(a->blocks, block) { FREE(block); } |
|
|
455 |
array_free(a->blocks); |
|
|
456 |
a->ptr = NULL; |
|
|
457 |
a->end = NULL; |
|
|
458 |
} |
|
|
459 |
|
|
|
460 |
// File I/O Implementation |
|
|
461 |
|
|
|
462 |
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) { |
|
|
463 |
FILE *f = fopen(filepath, "rb"); |
|
|
464 |
if (!f) { |
|
|
465 |
return NULL; |
|
|
466 |
} |
|
|
467 |
|
|
|
468 |
fseek(f, 0, SEEK_END); |
|
|
469 |
long length = ftell(f); |
|
|
470 |
fseek(f, 0, SEEK_SET); |
|
|
471 |
|
|
|
472 |
if (length < 0) { |
|
|
473 |
fclose(f); |
|
|
474 |
return NULL; |
|
|
475 |
} |
|
|
476 |
|
|
|
477 |
size_t size = (size_t)length; |
|
|
478 |
char *buffer = ALLOC(char, size + 1); |
|
|
479 |
if (!buffer) { |
|
|
480 |
fclose(f); |
|
|
481 |
return NULL; |
|
|
482 |
} |
|
|
483 |
|
|
|
484 |
size_t read = fread(buffer, 1, size, f); |
|
|
485 |
fclose(f); |
|
|
486 |
|
|
|
487 |
if (read != size) { |
|
|
488 |
FREE(buffer); |
|
|
489 |
return NULL; |
|
|
490 |
} |
|
|
491 |
|
|
|
492 |
buffer[size] = '\0'; |
|
|
493 |
if (out_size) { |
|
|
494 |
*out_size = size; |
|
|
495 |
} |
|
|
496 |
|
|
|
497 |
return buffer; |
|
|
498 |
} |
|
|
499 |
|
|
|
500 |
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) { |
|
|
501 |
FILE *f = fopen(filepath, "wb"); |
|
|
502 |
if (!f) { |
|
|
503 |
return 0; |
|
|
504 |
} |
|
|
505 |
|
|
|
506 |
size_t written = fwrite(data, 1, size, f); |
|
|
507 |
fclose(f); |
|
|
508 |
|
|
|
509 |
return written == size; |
|
|
510 |
} |
|
|
511 |
|
|
|
512 |
NONSTD_DEF stringb read_entire_file_sb(const char *filepath) { |
|
|
513 |
size_t size = 0; |
|
|
514 |
char *data = read_entire_file(filepath, &size); |
|
|
515 |
stringb sb = {0}; |
|
|
516 |
if (data) { |
|
|
517 |
sb.data = data; |
|
|
518 |
sb.length = size; |
|
|
519 |
sb.capacity = size + 1; |
|
|
520 |
} |
|
|
521 |
return sb; |
|
|
522 |
} |
|
|
523 |
|
|
|
524 |
NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) { |
|
|
525 |
return write_entire_file(filepath, sv.data, sv.length); |
|
|
526 |
} |
|
|
527 |
|
|
|
528 |
NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) { |
|
|
529 |
return write_entire_file(filepath, sb->data, sb->length); |
|
|
530 |
} |
|
|
531 |
|
|
|
532 |
#endif // NONSTD_IMPLEMENTATION |
|
|
533 |
|
|
|
534 |
#endif // NONSTD_H |
|
|
535 |
|
|
|
536 |
/* |
|
|
537 |
BSD 2-Clause License |
|
|
538 |
|
|
|
539 |
Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com> |
|
|
540 |
|
|
|
541 |
Redistribution and use in source and binary forms, with or without |
|
|
542 |
modification, are permitted provided that the following conditions are met: |
|
|
543 |
|
|
|
544 |
1. Redistributions of source code must retain the above copyright notice, this |
|
|
545 |
list of conditions and the following disclaimer. |
|
|
546 |
|
|
|
547 |
2. Redistributions in binary form must reproduce the above copyright notice, |
|
|
548 |
this list of conditions and the following disclaimer in the documentation |
|
|
549 |
and/or other materials provided with the distribution. |
|
|
550 |
|
|
|
551 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
|
552 |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
|
553 |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
554 |
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
|
|
555 |
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
|
556 |
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|
|
557 |
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
|
558 |
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
|
559 |
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
|
560 |
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
561 |
*/ |