Add resolution changer in options menu

This commit is contained in:
Doyle Thai 2016-11-29 01:41:33 +11:00
parent eb4b0e1714
commit 10891ab56e
13 changed files with 497 additions and 255 deletions

View File

@ -82,7 +82,7 @@ INTERNAL v2 *createAsteroidVertexList(MemoryArena_ *arena, i32 iterations,
for (i32 i = 0; i < iterations; i++) for (i32 i = 0; i < iterations; i++)
{ {
i32 randValue = rand(); i32 randValue = rand();
// NOTE(doyle): Sin/cos generate values from +-1, we want to create // NOTE(doyle): Sin/cos generate values from +-1, we want to create
// vertices that start from 0, 0 (i.e. strictly positive) // vertices that start from 0, 0 (i.e. strictly positive)
result[i] = V2(((math_cosf(iterationAngle * i) + 1) * asteroidRadius), result[i] = V2(((math_cosf(iterationAngle * i) + 1) * asteroidRadius),
@ -91,7 +91,7 @@ INTERNAL v2 *createAsteroidVertexList(MemoryArena_ *arena, i32 iterations,
ASSERT(result[i].x >= 0 && result[i].y >= 0); ASSERT(result[i].x >= 0 && result[i].y >= 0);
#if 1 #if 1
f32 displacementDist = 0.50f * asteroidRadius; f32 displacementDist = 0.50f * asteroidRadius;
i32 vertexDisplacement = i32 vertexDisplacement =
randValue % (i32)displacementDist + (i32)(displacementDist * 0.25f); randValue % (i32)displacementDist + (i32)(displacementDist * 0.25f);
@ -240,16 +240,15 @@ INTERNAL u32 moveEntity(GameWorldState *world, MemoryArena_ *transientArena,
ASSERT(checkEntity->vertexPoints); ASSERT(checkEntity->vertexPoints);
/* Create entity edge lists */ /* Create entity edge lists */
v2 *entityVertexListOffsetToP = entity_generateUpdatedVertexList( v2 *entityVertexListOffsetToP =
transientArena, entity); entity_generateUpdatedVertexList(transientArena, entity);
v2 *checkEntityVertexListOffsetToP = v2 *checkEntityVertexListOffsetToP =
entity_generateUpdatedVertexList(transientArena, entity_generateUpdatedVertexList(transientArena, checkEntity);
checkEntity);
v2 *entityEdgeList = createNormalEdgeList(transientArena, v2 *entityEdgeList =
entityVertexListOffsetToP, createNormalEdgeList(transientArena, entityVertexListOffsetToP,
entity->numVertexPoints); entity->numVertexPoints);
v2 *checkEntityEdgeList = createNormalEdgeList( v2 *checkEntityEdgeList = createNormalEdgeList(
transientArena, checkEntityVertexListOffsetToP, transientArena, checkEntityVertexListOffsetToP,
@ -296,7 +295,8 @@ enum AsteroidSize
asteroidsize_count, asteroidsize_count,
}; };
typedef struct { typedef struct
{
v2 pos; v2 pos;
v2 dP; v2 dP;
} AsteroidSpec; } AsteroidSpec;
@ -377,12 +377,12 @@ INTERNAL void addAsteroidWithSpec(GameWorldState *world,
{ {
ASSERT(INVALID_CODE_PATH); ASSERT(INVALID_CODE_PATH);
} }
asteroid->pos = newP; asteroid->pos = newP;
} }
else else
{ {
asteroid->pos = spec->pos; asteroid->pos = spec->pos;
asteroid->dP = spec->dP; asteroid->dP = spec->dP;
} }
asteroid->size = size; asteroid->size = size;
@ -406,7 +406,7 @@ INTERNAL void addAsteroidWithSpec(GameWorldState *world,
} }
asteroid->vertexPoints = vertexCache[cacheIndex]; asteroid->vertexPoints = vertexCache[cacheIndex];
asteroid->color = V4(1.0f, 1.0f, 1.0f, 1.0f); asteroid->color = V4(1.0f, 1.0f, 1.0f, 1.0f);
} }
INTERNAL void addAsteroid(GameWorldState *world, enum AsteroidSize asteroidSize) INTERNAL void addAsteroid(GameWorldState *world, enum AsteroidSize asteroidSize)
@ -456,7 +456,7 @@ INTERNAL AudioRenderer *getFreeAudioRenderer(GameWorldState *world,
AudioVorbis *vorbis, AudioVorbis *vorbis,
i32 maxSimultaneousPlayers) i32 maxSimultaneousPlayers)
{ {
i32 freeIndex = -1; i32 freeIndex = -1;
i32 sameAudioPlayingCount = 0; i32 sameAudioPlayingCount = 0;
AudioRenderer *result = NULL; AudioRenderer *result = NULL;
@ -512,13 +512,13 @@ INTERNAL void addPlayer(GameWorldState *world)
INTERNAL void deleteEntity(GameWorldState *world, i32 entityIndex) INTERNAL void deleteEntity(GameWorldState *world, i32 entityIndex)
{ {
ASSERT(entityIndex > 0); ASSERT(entityIndex > 0);
ASSERT(entityIndex < ARRAY_COUNT(world->entityList)); ASSERT(entityIndex < world->entityListSize);
/* Last entity replaces the entity to delete */ /* Last entity replaces the entity to delete */
world->entityList[entityIndex] = world->entityList[world->entityIndex - 1]; world->entityList[entityIndex] = world->entityList[world->entityIndex - 1];
/* Make sure the replaced entity from end of list is cleared out */ /* Make sure the replaced entity from end of list is cleared out */
Entity emptyEntity = {0}; Entity emptyEntity = {0};
world->entityList[--world->entityIndex] = emptyEntity; world->entityList[--world->entityIndex] = emptyEntity;
} }
@ -581,6 +581,10 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
world->camera.max = state->renderer.size; world->camera.max = state->renderer.size;
world->size = state->renderer.size; world->size = state->renderer.size;
world->entityListSize = 1024;
world->entityList = MEMORY_PUSH_ARRAY(&world->entityArena,
world->entityListSize, Entity);
{ // Init null entity { // Init null entity
Entity *nullEntity = &world->entityList[world->entityIndex++]; Entity *nullEntity = &world->entityList[world->entityIndex++];
nullEntity->id = world->entityIdCounter++; nullEntity->id = world->entityIdCounter++;
@ -613,7 +617,7 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
world->numStarP = 100; world->numStarP = 100;
world->starPList = world->starPList =
MEMORY_PUSH_ARRAY(&state->persistentArena, world->numStarP, v2); MEMORY_PUSH_ARRAY(&world->entityArena, world->numStarP, v2);
for (i32 i = 0; i < world->numStarP; i++) for (i32 i = 0; i < world->numStarP; i++)
{ {
@ -630,7 +634,7 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
addAsteroid(world, (rand() % asteroidsize_count)); addAsteroid(world, (rand() % asteroidsize_count));
Radians starRotation = DEGREES_TO_RADIANS(45.0f); Radians starRotation = DEGREES_TO_RADIANS(45.0f);
v2 starSize = V2(2, 2); v2 starSize = V2(2, 2);
for (i32 i = 0; i < world->numStarP; i++) for (i32 i = 0; i < world->numStarP; i++)
{ {
renderer_rect(&state->renderer, world->camera, world->starPList[i], renderer_rect(&state->renderer, world->camera, world->starPList[i],
@ -640,7 +644,7 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
} }
if (platform_queryKey(&state->input.keys[keycode_left_square_bracket], if (platform_queryKey(&state->input.keys[keycode_left_square_bracket],
readkeytype_repeat, 0.2f)) readkeytype_repeat, 0.2f))
{ {
addAsteroid(world, (rand() % asteroidsize_count)); addAsteroid(world, (rand() % asteroidsize_count));
} }
@ -673,17 +677,17 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
{ {
addBullet(world, entity); addBullet(world, entity);
AudioVorbis *fire = AudioVorbis *fire =
asset_vorbisGet(&state->assetManager, "fire"); asset_vorbisGet(&state->assetManager, "fire");
AudioRenderer *audioRenderer = AudioRenderer *audioRenderer =
getFreeAudioRenderer(world, fire, 2); getFreeAudioRenderer(world, fire, 2);
if (audioRenderer) if (audioRenderer)
{ {
// TODO(doyle): Atm transient arena is not used, this is // TODO(doyle): Atm transient arena is not used, this is
// just to fill out the arguments // just to fill out the arguments
audio_vorbisPlay(&state->transientArena, audio_vorbisPlay(&state->transientArena,
&state->audioManager, audioRenderer, &state->audioManager, audioRenderer, fire,
fire, 1); 1);
} }
} }
@ -768,11 +772,15 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
// direction by extrapolating from it's current dp // direction by extrapolating from it's current dp
else else
{ {
if (entity->dP.x >= 0) localDp.x = 1.0f; if (entity->dP.x >= 0)
else localDp.x = -1.0f; localDp.x = 1.0f;
else
localDp.x = -1.0f;
if (entity->dP.y >= 0) localDp.y = 1.0f; if (entity->dP.y >= 0)
else localDp.y = -1.0f; localDp.y = 1.0f;
else
localDp.y = -1.0f;
} }
/* /*
@ -814,10 +822,9 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
f32 divisor = f32 divisor =
MAX(entity->particleInitDp.x, entity->particleInitDp.y); MAX(entity->particleInitDp.x, entity->particleInitDp.y);
f32 maxDp = MAX(entity->dP.x, entity->dP.y); f32 maxDp = MAX(entity->dP.x, entity->dP.y);
entity->color.a = maxDp / divisor; entity->color.a = maxDp / divisor;
} }
/* Loop entity around world */ /* Loop entity around world */
@ -876,10 +883,10 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
spec.dP = v2_scale(colliderA->dP, -4.0f); spec.dP = v2_scale(colliderA->dP, -4.0f);
addAsteroidWithSpec(world, asteroidsize_medium, &spec); addAsteroidWithSpec(world, asteroidsize_medium, &spec);
spec.dP = v2_perpendicular(spec.dP); spec.dP = v2_perpendicular(spec.dP);
addAsteroidWithSpec(world, asteroidsize_small, &spec); addAsteroidWithSpec(world, asteroidsize_small, &spec);
spec.dP = v2_perpendicular(colliderA->dP); spec.dP = v2_perpendicular(colliderA->dP);
addAsteroidWithSpec(world, asteroidsize_small, &spec); addAsteroidWithSpec(world, asteroidsize_small, &spec);
numParticles = 16; numParticles = 16;
@ -892,8 +899,8 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
&world->entityList[world->entityIndex++]; &world->entityList[world->entityIndex++];
particle->id = world->entityIdCounter++; particle->id = world->entityIdCounter++;
particle->pos = colliderA->pos; particle->pos = colliderA->pos;
particle->size = V2(4.0f, 4.0f); particle->size = V2(4.0f, 4.0f);
i32 randValue = rand(); i32 randValue = rand();
Radians rotation = Radians rotation =
@ -977,11 +984,9 @@ INTERNAL void gameUpdate(GameState *state, Memory *memory, f32 dt)
RenderFlags flags = renderflag_wireframe | renderflag_no_texture; RenderFlags flags = renderflag_wireframe | renderflag_no_texture;
renderer_entity(&state->renderer, &state->transientArena, world->camera, renderer_entity(&state->renderer, &state->transientArena, world->camera,
entity, V2(0, 0), 0, entity, V2(0, 0), 0, collideColor, flags);
collideColor, flags);
} }
for (i32 i = 0; i < world->numAudioRenderers; i++) for (i32 i = 0; i < world->numAudioRenderers; i++)
{ {
AudioRenderer *audioRenderer = &world->audioRenderer[i]; AudioRenderer *audioRenderer = &world->audioRenderer[i];
@ -1000,6 +1005,45 @@ INTERNAL void startMenuUpdate(GameState *state, Memory *memory, f32 dt)
StartMenuState *menuState = StartMenuState *menuState =
GET_STATE_DATA(state, &state->persistentArena, StartMenuState); GET_STATE_DATA(state, &state->persistentArena, StartMenuState);
if (!menuState->init)
{
MemoryArena_ *persistentArena = &state->persistentArena;
OptimalArrayV2 *resolutionArray = inputBuffer->resolutionList;
v2 currRes = renderer->size;
i32 resIndex = -1;
menuState->resStrings = memory_pushBytes(
persistentArena, resolutionArray->index * sizeof(String *));
for (i32 i = 0; i < resolutionArray->index; i++)
{
v2 res = resolutionArray->ptr[i];
if (v2_equals(res, currRes)) resIndex = i;
char widthString[8] = {0};
char heightString[8] = {0};
common_itoa((i32)res.w, widthString, ARRAY_COUNT(widthString));
common_itoa((i32)res.h, heightString, ARRAY_COUNT(heightString));
String *resString = common_stringMake(transientArena, widthString);
resString = common_stringAppend(transientArena, resString, "x", 1);
resString =
common_stringAppend(transientArena, resString, heightString,
ARRAY_COUNT(heightString));
menuState->resStrings[i] = MEMORY_PUSH_ARRAY(
persistentArena, common_stringLen(resString), char);
common_strncpy(menuState->resStrings[i], resString,
common_stringLen(resString));
}
if (resIndex == -1) ASSERT(INVALID_CODE_PATH);
menuState->init = TRUE;
menuState->numResStrings = resolutionArray->index;
menuState->resStringDisplayIndex = resIndex;
}
Font *arial15 = asset_fontGetOrCreateOnDemand( Font *arial15 = asset_fontGetOrCreateOnDemand(
assetManager, &state->persistentArena, transientArena, "Arial", 15); assetManager, &state->persistentArena, transientArena, "Arial", 15);
Font *arial25 = asset_fontGetOrCreateOnDemand( Font *arial25 = asset_fontGetOrCreateOnDemand(
@ -1008,7 +1052,6 @@ INTERNAL void startMenuUpdate(GameState *state, Memory *memory, f32 dt)
v2 screenCenter = v2_scale(renderer->size, 0.5f); v2 screenCenter = v2_scale(renderer->size, 0.5f);
ui_beginState(uiState); ui_beginState(uiState);
if (menuState->optionsShow) if (menuState->optionsShow)
{ {
if (platform_queryKey(&inputBuffer->keys[keycode_o], if (platform_queryKey(&inputBuffer->keys[keycode_o],
@ -1020,39 +1063,78 @@ INTERNAL void startMenuUpdate(GameState *state, Memory *memory, f32 dt)
} }
else else
{ {
f32 textYOffset = arial25->size * 1.5f;;
const char *const title = "Options";
v2 p = v2_add(screenCenter, V2(0, textYOffset));
renderer_stringFixedCentered(renderer, transientArena, arial25,
title, p, V2(0, 0), 0, V4(1, 0, 1, 1),
0);
const char *const resolutionLabel = "Resolution";
p = v2_add(screenCenter, V2(0, 0));
renderer_stringFixedCentered(renderer, transientArena, arial25,
resolutionLabel, p, V2(0, 0), 0,
V4(1, 0, 1, 1), 0);
const char *const resSizeLabel = "< 800x600 >";
p = v2_add(screenCenter, V2(0, -textYOffset));
renderer_stringFixedCentered(renderer, transientArena, arial25,
resSizeLabel, p, V2(0, 0), 0,
V4(1, 0, 1, 1), 0);
if (platform_queryKey(&inputBuffer->keys[keycode_enter], if (platform_queryKey(&inputBuffer->keys[keycode_enter],
readkeytype_one_shot, KEY_DELAY_NONE)) readkeytype_one_shot, KEY_DELAY_NONE))
{ {
menuState->newResolutionRequest = TRUE; OptimalArrayV2 *resolutionArray = inputBuffer->resolutionList;
menuState->newResolution = V2(800, 600);
renderer->size = menuState->newResolution; menuState->newResolutionRequest = TRUE;
v2 newSize =
resolutionArray->ptr[menuState->resStringDisplayIndex];
GameWorldState *world = GET_STATE_DATA( GameWorldState *world = GET_STATE_DATA(
state, &state->persistentArena, GameWorldState); state, &state->persistentArena, GameWorldState);
world->size = menuState->newResolution;
world->camera.max = menuState->newResolution; renderer_updateSize(renderer, &state->assetManager, newSize);
// TODO(doyle): reset world arena instead of zeroing out struct
common_memset((u8 *)world, 0, sizeof(GameWorldState));
debug_init(newSize, *arial15);
}
else
{
if (platform_queryKey(&inputBuffer->keys[keycode_left],
readkeytype_one_shot, KEY_DELAY_NONE))
{
menuState->resStringDisplayIndex--;
}
else if (platform_queryKey(&inputBuffer->keys[keycode_right],
readkeytype_one_shot,
KEY_DELAY_NONE))
{
menuState->resStringDisplayIndex++;
}
if (menuState->resStringDisplayIndex < 0)
{
menuState->resStringDisplayIndex = 0;
}
else if (menuState->resStringDisplayIndex >=
menuState->numResStrings)
{
menuState->resStringDisplayIndex =
menuState->numResStrings - 1;
}
}
f32 textYOffset = arial25->size * 1.5f;
{ // Options Title String Display
const char *const title = "Options";
v2 p = v2_add(screenCenter, V2(0, textYOffset));
renderer_stringFixedCentered(renderer, transientArena, arial25,
title, p, V2(0, 0), 0,
V4(1, 0, 1, 1), 0);
}
{ // Resolution String Display
/* Draw label */
const char *const resolutionLabel = "Resolution";
v2 p = v2_add(screenCenter, V2(0, 0));
renderer_stringFixedCentered(renderer, transientArena, arial25,
resolutionLabel, p, V2(0, 0), 0,
V4(1, 0, 1, 1), 0);
/* Draw label value */
char *resStringToDisplay =
menuState->resStrings[menuState->resStringDisplayIndex];
p = v2_add(screenCenter, V2(0, -textYOffset));
renderer_stringFixedCentered(renderer, transientArena, arial25,
resStringToDisplay, p, V2(0, 0), 0,
V4(1, 0, 1, 1), 0);
} }
} }
} }
@ -1084,10 +1166,10 @@ INTERNAL void startMenuUpdate(GameState *state, Memory *memory, f32 dt)
{ // Draw show options prompt { // Draw show options prompt
const char *const optionPrompt = "Press [o] for options "; const char *const optionPrompt = "Press [o] for options ";
v2 p = v2_add(screenCenter, V2(0, -120)); v2 p = v2_add(screenCenter, V2(0, -120));
renderer_stringFixedCentered(renderer, transientArena, arial25, renderer_stringFixedCentered(renderer, transientArena, arial25,
optionPrompt, p, V2(0, 0), 0, V4(1, 1, 0, 1), optionPrompt, p, V2(0, 0), 0,
0); V4(1, 1, 0, 1), 0);
} }
if (platform_queryKey(&inputBuffer->keys[keycode_enter], if (platform_queryKey(&inputBuffer->keys[keycode_enter],
@ -1131,7 +1213,7 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
srand((u32)time(NULL)); srand((u32)time(NULL));
asset_init(&state->assetManager, &state->persistentArena); asset_init(&state->assetManager, &state->persistentArena);
audio_init(&state->audioManager); audio_init(&state->audioManager);
// NOTE(doyle): Load game assets must be before init_renderer so that // NOTE(doyle): Load game assets must be before init_renderer so that
// shaders are available for the renderer configuration // shaders are available for the renderer configuration
loadGameAssets(state); loadGameAssets(state);
@ -1139,7 +1221,7 @@ void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
&state->persistentArena, windowSize); &state->persistentArena, windowSize);
Font *arial15 = asset_fontGet(&state->assetManager, "Arial", 15); Font *arial15 = asset_fontGet(&state->assetManager, "Arial", 15);
debug_init(&state->persistentArena, windowSize, *arial15); debug_init(windowSize, *arial15);
state->currState = appstate_StartMenuState; state->currState = appstate_StartMenuState;
state->init = TRUE; state->init = TRUE;

View File

@ -1,10 +1,173 @@
#include <stdlib.h>
#include "Dengine/Common.h" #include "Dengine/Common.h"
#include "Dengine/Math.h" #include "Dengine/MemoryArena.h"
void common_optimalArrayV2Create(OptimalArrayV2 *array)
{
array->ptr = array->fastStorage;
array->size = ARRAY_COUNT(array->fastStorage);
}
i32 common_optimalArrayV2Push(OptimalArrayV2 *array, v2 data)
{
if (array->index + 1 > array->size)
{
array->size += ARRAY_COUNT(array->fastStorage);
i32 newSizeInBytes = array->size * sizeof(v2);
/* If first time expanding, we need to manually malloc and copy */
if (array->ptr == array->fastStorage)
{
array->ptr = malloc(newSizeInBytes);
for (i32 i = 0; i < ARRAY_COUNT(array->fastStorage); i++)
{
array->ptr[i] = array->fastStorage[i];
}
}
else
{
array->ptr = realloc(array->ptr, newSizeInBytes);
}
if (!array->ptr) return optimalarrayerror_out_of_memory;
}
array->ptr[array->index++] = data;
return 0;
}
void common_optimalArrayV2Destroy(OptimalArrayV2 *array)
{
if (array->ptr != array->fastStorage)
{
free(array->ptr);
}
}
/*
* +-------------------------------------+
* | Header | C-String | Null Terminator |
* +-------------------------------------+
* |
* +--> Functions return the c-string for compatibility with other
* string libraries
*
* Headers are retrieved using pointer arithmetric from the C string. These
* strings are typechecked by their own typedef char String.
*/
typedef struct StringHeader
{
i32 len;
// NOTE(doyle): A string is stored as one contiguous chunk of memory. We
// don't use a pointer for storing the string as this'd require an extra
// 4 bytes to store the pointer, which we don't need if everything is
// contiguous. The string follows on from the len, and we return the address
// of the string to simulate a pointer.
String string;
} StringHeader;
// TODO(doyle): string capacity- append if already enough space
INTERNAL StringHeader *stringGetHeader(String *const string)
{
StringHeader *result = NULL;
// NOTE(doyle): C-String must be located at end of struct type for offset to
// be correct! We cannot just subtract the string-header since we start at
// the string ptr position
if (string)
{
i32 byteOffsetToHeader = sizeof(StringHeader) - sizeof(String *);
result = CAST(StringHeader *)((CAST(u8 *) string) - byteOffsetToHeader);
}
return result;
}
i32 common_stringLen(String *const string)
{
if (!string) return -1;
StringHeader *header = stringGetHeader(string);
i32 result = header->len;
return result;
}
String *const common_stringAppend(MemoryArena_ *const arena, String *oldString,
char *appendString, i32 appendLen)
{
if (!oldString || !appendString || !arena) return oldString;
/* Calculate size of new string */
StringHeader *oldHeader = stringGetHeader(oldString);
i32 newLen = oldHeader->len + appendLen;
String *newString = common_stringMakeLen(arena, newLen);
/* Append strings together */
String *insertPtr = newString;
common_strncpy(insertPtr, oldString, oldHeader->len);
insertPtr += oldHeader->len;
common_strncpy(insertPtr, appendString, appendLen);
/* Free old string */
common_stringFree(arena, oldString);
return newString;
}
void common_stringFree(MemoryArena_ *arena, String *string)
{
if (!string || !arena) return;
StringHeader *header = stringGetHeader(string);
i32 bytesToFree = sizeof(StringHeader) + header->len;
common_memset((u8 *)header, 0, bytesToFree);
// TODO(doyle): Mem free
// PLATFORM_MEM_FREE(arena, header, bytesToFree);
string = NULL;
}
String *const common_stringMake(MemoryArena_ *const arena, char *string)
{
if (!arena) return NULL;
i32 len = common_strlen(string);
String *result = common_stringMakeLen(arena, len);
common_strncpy(result, string, len);
return result;
}
String *const common_stringMakeLen(MemoryArena_ *const arena, i32 len)
{
if (!arena) return NULL;
// NOTE(doyle): Allocate the string header size plus the len. But _note_
// that StringHeader contains a single String character. This has
// a side-effect of already preallocating a byte for the null-terminating
// character. Whilst the len of a string counts up to the last character
// _not_ including null-terminator.
i32 bytesToAllocate = sizeof(StringHeader) + len;
void *chunk = memory_pushBytes(arena, bytesToAllocate * sizeof(u8));
if (!chunk) return NULL;
StringHeader *header = CAST(StringHeader *) chunk;
header->len = len;
return &header->string;
}
i32 common_strlen(const char *const string) i32 common_strlen(const char *const string)
{ {
i32 result = 0; i32 result = 0;
while (string[result]) result++; while (string[result])
result++;
return result; return result;
} }
@ -12,8 +175,7 @@ i32 common_strcmp(const char *a, const char *b)
{ {
while (*a == *b) while (*a == *b)
{ {
if (!*a) if (!*a) return 0;
return 0;
a++; a++;
b++; b++;
} }
@ -27,13 +189,13 @@ void common_strncat(char *dest, const char *src, i32 numChars)
while (*stringPtr) while (*stringPtr)
stringPtr++; stringPtr++;
for (i32 i = 0; i < numChars; i++) for (i32 i = 0; i < numChars; i++)
*(stringPtr++) = src[i]; *(stringPtr++) = src[i];
} }
char *common_strncpy(char *dest, const char *src, i32 numChars) char *common_strncpy(char *dest, const char *src, i32 numChars)
{ {
for (i32 i = 0; i < numChars; i++) for (i32 i = 0; i < numChars; i++)
dest[i] = src[i]; dest[i] = src[i];
return dest; return dest;
@ -54,9 +216,9 @@ INTERNAL void reverseString(char *const buf, const i32 bufSize)
for (i32 i = 0; i < mid; i++) for (i32 i = 0; i < mid; i++)
{ {
char tmp = buf[i]; char tmp = buf[i];
buf[i] = buf[(bufSize-1) - i]; buf[i] = buf[(bufSize - 1) - i];
buf[(bufSize-1) - i] = tmp; buf[(bufSize - 1) - i] = tmp;
} }
} }
@ -65,9 +227,9 @@ void common_itoa(i32 value, char *buf, i32 bufSize)
if (!buf || bufSize == 0) return; if (!buf || bufSize == 0) return;
// NOTE(doyle): Max 32bit integer (+-)2147483647 // NOTE(doyle): Max 32bit integer (+-)2147483647
i32 charIndex = 0; i32 charIndex = 0;
b32 negative = FALSE; b32 negative = FALSE;
if (value < 0) negative = TRUE; if (value < 0) negative = TRUE;
if (negative) buf[charIndex++] = '-'; if (negative) buf[charIndex++] = '-';
@ -75,7 +237,7 @@ void common_itoa(i32 value, char *buf, i32 bufSize)
i32 val = ABS(value); i32 val = ABS(value);
while (val != 0 && charIndex < bufSize) while (val != 0 && charIndex < bufSize)
{ {
i32 rem = val % 10; i32 rem = val % 10;
buf[charIndex++] = rem + '0'; buf[charIndex++] = rem + '0';
val /= 10; val /= 10;
} }
@ -137,31 +299,34 @@ u32 common_murmurHash2(const void *key, i32 len, u32 seed)
// Mix 4 bytes at a time into the hash // Mix 4 bytes at a time into the hash
const unsigned char * data = (const unsigned char *)key; const unsigned char *data = (const unsigned char *)key;
while(len >= 4) while (len >= 4)
{ {
u32 k = *(u32 *)data; u32 k = *(u32 *)data;
k *= m; k *= m;
k ^= k >> r; k ^= k >> r;
k *= m; k *= m;
h *= m; h *= m;
h ^= k; h ^= k;
data += 4; data += 4;
len -= 4; len -= 4;
} }
// Handle the last few bytes of the input array // Handle the last few bytes of the input array
switch(len) switch (len)
{ {
case 3: h ^= data[2] << 16; case 3:
case 2: h ^= data[1] << 8; h ^= data[2] << 16;
case 1: h ^= data[0]; case 2:
h *= m; h ^= data[1] << 8;
case 1:
h ^= data[0];
h *= m;
}; };
// Do a few final mixes of the hash to ensure the last few // Do a few final mixes of the hash to ensure the last few
@ -172,4 +337,4 @@ u32 common_murmurHash2(const void *key, i32 len, u32 seed)
h ^= h >> 15; h ^= h >> 15;
return h; return h;
} }

View File

@ -9,7 +9,7 @@ typedef struct DebugState
{ {
b32 init; b32 init;
Font font; Font font;
i32 *callCount; i32 callCount[debugcount_num];
f32 stringLineGap; f32 stringLineGap;
/* Debug strings rendered in top left corner */ /* Debug strings rendered in top left corner */
@ -28,11 +28,12 @@ typedef struct DebugState
GLOBAL_VAR DebugState GLOBAL_debug; GLOBAL_VAR DebugState GLOBAL_debug;
void debug_init(MemoryArena_ *arena, v2 windowSize, Font font) void debug_init(v2 windowSize, Font font)
{ {
GLOBAL_debug.font = font; GLOBAL_debug.font = font;
GLOBAL_debug.callCount =
memory_pushBytes(arena, debugcount_num * sizeof(i32)); for (i32 i = 0; i < debugcount_num; i++) GLOBAL_debug.callCount[i] = 0;
GLOBAL_debug.stringLineGap = CAST(f32) font.verticalSpacing; GLOBAL_debug.stringLineGap = CAST(f32) font.verticalSpacing;
/* Init debug string stack */ /* Init debug string stack */
@ -40,18 +41,17 @@ void debug_init(MemoryArena_ *arena, v2 windowSize, Font font)
GLOBAL_debug.stringUpdateTimer = 0.0f; GLOBAL_debug.stringUpdateTimer = 0.0f;
GLOBAL_debug.stringUpdateRate = 0.15f; GLOBAL_debug.stringUpdateRate = 0.15f;
GLOBAL_debug.initialStringP = GLOBAL_debug.initialStringP = V2(0.0f, (windowSize.h - 1.8f * GLOBAL_debug.stringLineGap));
V2(0.0f, (windowSize.h - 1.8f * GLOBAL_debug.stringLineGap)); GLOBAL_debug.currStringP = GLOBAL_debug.initialStringP;
GLOBAL_debug.currStringP = GLOBAL_debug.initialStringP;
/* Init gui console */ /* Init gui console */
i32 maxConsoleStrLen = ARRAY_COUNT(GLOBAL_debug.console[0]); i32 maxConsoleStrLen = ARRAY_COUNT(GLOBAL_debug.console[0]);
GLOBAL_debug.consoleIndex = 0; GLOBAL_debug.consoleIndex = 0;
// TODO(doyle): Font max size not entirely correct? using 1 * font.maxSize.w // TODO(doyle): Font max size not entirely correct? using 1 * font.maxSize.w
// reveals around 4 characters .. // reveals around 4 characters ..
f32 consoleXPos = font.maxSize.w * 20; f32 consoleXPos = font.maxSize.w * 20;
f32 consoleYPos = windowSize.h - 1.8f * GLOBAL_debug.stringLineGap; f32 consoleYPos = windowSize.h - 1.8f * GLOBAL_debug.stringLineGap;
GLOBAL_debug.initialConsoleP = V2(consoleXPos, consoleYPos); GLOBAL_debug.initialConsoleP = V2(consoleXPos, consoleYPos);
GLOBAL_debug.init = TRUE; GLOBAL_debug.init = TRUE;

View File

@ -32,8 +32,7 @@ void shaderUniformSetVec4f(u32 shaderId, const GLchar *name,
void shaderUse(u32 shaderId) { glUseProgram(shaderId); } void shaderUse(u32 shaderId) { glUseProgram(shaderId); }
void renderer_init(Renderer *renderer, AssetManager *assetManager, void renderer_updateSize(Renderer *renderer, AssetManager *assetManager, v2 windowSize)
MemoryArena_ *persistentArena, v2 windowSize)
{ {
renderer->size = windowSize; renderer->size = windowSize;
// NOTE(doyle): Value to map a screen coordinate to NDC coordinate // NOTE(doyle): Value to map a screen coordinate to NDC coordinate
@ -43,6 +42,7 @@ void renderer_init(Renderer *renderer, AssetManager *assetManager,
const mat4 projection = const mat4 projection =
mat4_ortho(0.0f, renderer->size.w, 0.0f, renderer->size.h, 0.0f, 1.0f); mat4_ortho(0.0f, renderer->size.w, 0.0f, renderer->size.h, 0.0f, 1.0f);
for (i32 i = 0; i < shaderlist_count; i++) for (i32 i = 0; i < shaderlist_count; i++)
{ {
renderer->shaderList[i] = asset_shaderGet(assetManager, i); renderer->shaderList[i] = asset_shaderGet(assetManager, i);
@ -54,6 +54,12 @@ void renderer_init(Renderer *renderer, AssetManager *assetManager,
renderer->activeShaderId = renderer->shaderList[shaderlist_default]; renderer->activeShaderId = renderer->shaderList[shaderlist_default];
GL_CHECK_ERROR(); GL_CHECK_ERROR();
}
void renderer_init(Renderer *renderer, AssetManager *assetManager,
MemoryArena_ *persistentArena, v2 windowSize)
{
renderer_updateSize(renderer, assetManager, windowSize);
/* Create buffers */ /* Create buffers */
glGenVertexArrays(ARRAY_COUNT(renderer->vao), renderer->vao); glGenVertexArrays(ARRAY_COUNT(renderer->vao), renderer->vao);

View File

@ -1,120 +1,3 @@
#include "Dengine/String.h" #include "Dengine/String.h"
#include "Dengine/MemoryArena.h" #include "Dengine/MemoryArena.h"
/*
* +-------------------------------------+
* | Header | C-String | Null Terminator |
* +-------------------------------------+
* |
* +--> Functions return the c-string for compatibility with other
* string libraries
*
* Headers are retrieved using pointer arithmetric from the C string. These
* strings are typechecked by their own typedef char String.
*/
typedef struct StringHeader
{
i32 len;
// NOTE(doyle): A string is stored as one contiguous chunk of memory. We
// don't use a pointer for storing the string as this'd require an extra
// 4 bytes to store the pointer, which we don't need if everything is
// contiguous. The string follows on from the len, and we return the address
// of the string to simulate a pointer.
String string;
} StringHeader;
// TODO(doyle): string capacity- append if already enough space
INTERNAL StringHeader *string_getHeader(String *const string)
{
StringHeader *result = NULL;
// NOTE(doyle): C-String must be located at end of struct type for offset to
// be correct! We cannot just subtract the string-header since we start at
// the string ptr position
if (string)
{
i32 byteOffsetToHeader = sizeof(StringHeader) - sizeof(String *);
result = CAST(StringHeader *)((CAST(u8 *) string) - byteOffsetToHeader);
}
return result;
}
i32 string_len(String *const string)
{
if (!string) return -1;
StringHeader *header = string_getHeader(string);
i32 result = header->len;
return result;
}
String *const string_append(MemoryArena_ *const arena, String *oldString,
char *appendString, i32 appendLen)
{
if (!oldString || !appendString || !arena) return oldString;
/* Calculate size of new string */
StringHeader *oldHeader = string_getHeader(oldString);
i32 newLen = oldHeader->len + appendLen;
String *newString = string_makeLen(arena, newLen);
/* Append strings together */
String *insertPtr = newString;
common_strncpy(insertPtr, oldString, oldHeader->len);
insertPtr += oldHeader->len;
common_strncpy(insertPtr, appendString, appendLen);
/* Free old string */
string_free(arena, oldString);
return newString;
}
void string_free(MemoryArena_ *arena, String *string)
{
if (!string || !arena) return;
StringHeader *header = string_getHeader(string);
i32 bytesToFree = sizeof(StringHeader) + header->len;
common_memset((u8 *)header, 0, bytesToFree);
// TODO(doyle): Mem free
// PLATFORM_MEM_FREE(arena, header, bytesToFree);
string = NULL;
}
String *const string_make(MemoryArena_ *const arena, char *string)
{
if (!arena) return NULL;
i32 len = common_strlen(string);
String *result = string_makeLen(arena, len);
common_strncpy(result, string, len);
return result;
}
String *const string_makeLen(MemoryArena_ *const arena, i32 len)
{
if (!arena) return NULL;
// NOTE(doyle): Allocate the string header size plus the len. But _note_
// that StringHeader contains a single String character. This has
// a side-effect of already preallocating a byte for the null-terminating
// character. Whilst the len of a string counts up to the last character
// _not_ including null-terminator.
i32 bytesToAllocate = sizeof(StringHeader) + len;
void *chunk = memory_pushBytes(arena, bytesToAllocate * sizeof(u8));
if (!chunk) return NULL;
StringHeader *header = CAST(StringHeader *) chunk;
header->len = len;
return &header->string;
}

View File

@ -83,7 +83,7 @@ INTERNAL void mouseButtonCallback(GLFWwindow *window, int button, int action,
{ {
GameState *game = CAST(GameState *)(glfwGetWindowUserPointer(window)); GameState *game = CAST(GameState *)(glfwGetWindowUserPointer(window));
switch(button) switch (button)
{ {
case GLFW_MOUSE_BUTTON_LEFT: case GLFW_MOUSE_BUTTON_LEFT:
processKey(&game->input.keys[keycode_mouseLeft], action); processKey(&game->input.keys[keycode_mouseLeft], action);
@ -115,8 +115,73 @@ i32 main(void)
glfwInit(); glfwInit();
setGlfwWindowHints(); setGlfwWindowHints();
i32 windowWidth = 1600; OptimalArrayV2 vidList = {0};
i32 windowHeight = 900; common_optimalArrayV2Create(&vidList);
i32 windowWidth = 0;
i32 windowHeight = 0;
{ // Query Computer Video Resolutions
i32 numMonitors;
GLFWmonitor **monitors = glfwGetMonitors(&numMonitors);
GLFWmonitor *primaryMonitor = monitors[0];
i32 numModes;
const GLFWvidmode *modes = glfwGetVideoModes(primaryMonitor, &numModes);
i32 targetRefreshHz = 60;
f32 targetWindowRatio = 16.0f / 9.0f;
i32 targetPixelDensity = 1280 * 720;
i32 minPixelDensityDelta = 100000000;
printf("== Supported video modes ==\n");
for (i32 i = 0; i < numModes; i++)
{
GLFWvidmode mode = modes[i];
printf("width: %d, height: %d, rgb: %d, %d, %d, refresh: %d\n",
mode.width, mode.height, mode.redBits, mode.greenBits,
mode.blueBits, mode.refreshRate);
if (mode.refreshRate == targetRefreshHz)
{
i32 result = common_optimalArrayV2Push(
&vidList, V2i(mode.width, mode.height));
if (result)
{
printf(
"common_optimalArrayV2Push(): Failed error code %d\n",
result);
ASSERT(INVALID_CODE_PATH);
}
f32 sizeRatio = (f32)mode.width / (f32)mode.height;
f32 delta = targetWindowRatio - sizeRatio;
if (delta < 0.1f)
{
i32 pixelDensity = mode.width * mode.height;
i32 densityDelta = ABS((pixelDensity - targetPixelDensity));
if (densityDelta < minPixelDensityDelta)
{
minPixelDensityDelta = densityDelta;
windowWidth = mode.width;
windowHeight = mode.height;
}
}
}
}
printf("== ==\n");
ASSERT(vidList.index > 0);
}
if (windowWidth == 0 || windowHeight == 0)
{
// NOTE(doyle): In this case just fallback to some value we hope is safe
windowWidth = 800;
windowHeight = 600;
}
GLFWwindow *window = GLFWwindow *window =
glfwCreateWindow(windowWidth, windowHeight, "Dengine", NULL, NULL); glfwCreateWindow(windowWidth, windowHeight, "Dengine", NULL, NULL);
@ -168,7 +233,7 @@ i32 main(void)
* INITIALISE GAME * INITIALISE GAME
******************* *******************
*/ */
Memory memory = {0}; Memory memory = {0};
MemoryIndex persistentSize = MEGABYTES(32); MemoryIndex persistentSize = MEGABYTES(32);
MemoryIndex transientSize = MEGABYTES(64); MemoryIndex transientSize = MEGABYTES(64);
@ -181,12 +246,12 @@ i32 main(void)
MemoryArena_ gameArena = {0}; MemoryArena_ gameArena = {0};
memory_arenaInit(&gameArena, memory.persistent, memory.persistentSize); memory_arenaInit(&gameArena, memory.persistent, memory.persistentSize);
GameState *gameState = MEMORY_PUSH_STRUCT(&gameArena, GameState); GameState *gameState = MEMORY_PUSH_STRUCT(&gameArena, GameState);
gameState->persistentArena = gameArena; gameState->persistentArena = gameArena;
glfwSetWindowUserPointer(window, CAST(void *)(gameState)); glfwSetWindowUserPointer(window, CAST(void *)(gameState));
{ { // Load game icon
i32 width, height; i32 width, height;
char *iconPath = "data/textures/Asteroids/icon.png"; char *iconPath = "data/textures/Asteroids/icon.png";
u8 *pixels = asset_imageLoad(&width, &height, NULL, iconPath, FALSE); u8 *pixels = asset_imageLoad(&width, &height, NULL, iconPath, FALSE);
@ -198,6 +263,7 @@ i32 main(void)
asset_imageFree(pixels); asset_imageFree(pixels);
} }
} }
gameState->input.resolutionList = &vidList;
/* /*
******************* *******************
@ -209,7 +275,6 @@ i32 main(void)
#if 0 #if 0
// TODO(doyle): Get actual monitor refresh rate // TODO(doyle): Get actual monitor refresh rate
i32 monitorRefreshHz = 60;
f32 targetSecondsPerFrame = 1.0f / CAST(f32)(monitorRefreshHz); f32 targetSecondsPerFrame = 1.0f / CAST(f32)(monitorRefreshHz);
#else #else
// TODO(doyle): http://gafferongames.com/game-physics/fix-your-timestep/ // TODO(doyle): http://gafferongames.com/game-physics/fix-your-timestep/
@ -234,7 +299,7 @@ i32 main(void)
/* Swap the buffers */ /* Swap the buffers */
glfwSwapBuffers(window); glfwSwapBuffers(window);
f32 endTime = CAST(f32)glfwGetTime(); f32 endTime = CAST(f32) glfwGetTime();
secondsElapsed = endTime - startTime; secondsElapsed = endTime - startTime;
#if 0 #if 0
@ -277,8 +342,11 @@ i32 main(void)
{ {
if (menuState->newResolutionRequest) if (menuState->newResolutionRequest)
{ {
windowSize = menuState->newResolution; i32 index = menuState->resStringDisplayIndex;
windowSize = gameState->input.resolutionList->ptr[index];
glfwSetWindowSize(window, (i32)windowSize.w, (i32)windowSize.h); glfwSetWindowSize(window, (i32)windowSize.w, (i32)windowSize.h);
glViewport(0, 0, (i32)windowSize.w, (i32)windowSize.h);
menuState->newResolutionRequest = FALSE; menuState->newResolutionRequest = FALSE;
} }

View File

@ -24,7 +24,8 @@ typedef struct GameWorldState
MemoryArena_ entityArena; MemoryArena_ entityArena;
v2 *entityVertexListCache[entitytype_count]; v2 *entityVertexListCache[entitytype_count];
Entity entityList[1024]; Entity *entityList;
i32 entityListSize;
i32 entityIndex; i32 entityIndex;
u32 entityIdCounter; u32 entityIdCounter;
@ -54,19 +55,23 @@ typedef struct GameWorldState
typedef struct StartMenuState typedef struct StartMenuState
{ {
b32 init;
f32 startPromptBlinkTimer; f32 startPromptBlinkTimer;
b32 startPromptShow; b32 startPromptShow;
b32 optionsShow; char **resStrings;
i32 numResStrings;
i32 resStringDisplayIndex;
b32 newResolutionRequest; b32 newResolutionRequest;
v2 newResolution;
b32 optionsShow;
} StartMenuState; } StartMenuState;
typedef struct GameState typedef struct GameState
{ {
b32 init; b32 init;
enum AppState currState; enum AppState currState;
void *appStateData[appstate_count]; void *appStateData[appstate_count];
@ -81,8 +86,7 @@ typedef struct GameState
UiState uiState; UiState uiState;
} GameState; } GameState;
#define ASTEROID_GET_STATE_DATA(state, type) \ #define ASTEROID_GET_STATE_DATA(state, type) (type *)asteroid_getStateData_(state, appstate_##type)
(type *)asteroid_getStateData_(state, appstate_##type)
void *asteroid_getStateData_(GameState *state, enum AppState appState); void *asteroid_getStateData_(GameState *state, enum AppState appState);
void asteroid_gameUpdateAndRender(GameState *state, Memory *memory, void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,

View File

@ -15,6 +15,9 @@ typedef float f32;
typedef double f64; typedef double f64;
typedef size_t MemoryIndex; typedef size_t MemoryIndex;
typedef char String;
typedef struct MemoryArena MemoryArena_;
#define TRUE 1 #define TRUE 1
#define FALSE 0 #define FALSE 0
@ -34,6 +37,44 @@ typedef size_t MemoryIndex;
#define DENGINE_DEBUG #define DENGINE_DEBUG
#include "Dengine/Math.h"
/*
NOTE(doyle): Small sized optimised dynamic array that grows as required. The
array uses the stack first, only if it runs out of space does it rely on
memory allocated from the machine.
The array->ptr is initially set to fast storage. Once we are out of space
we allocate space on the heap for the ptr and copy over the elements in
fast storage.
The default behaviour expands the array storage by the size of fastStorage.
*/
enum OptimalArrayError
{
optimalarrayerror_out_of_memory = 1,
optimalarrayerror_count,
};
typedef struct OptimalArrayV2
{
v2 fastStorage[16];
v2 *ptr;
i32 index;
i32 size;
} OptimalArrayV2;
void common_optimalArrayV2Create(OptimalArrayV2 *array);
i32 common_optimalArrayV2Push(OptimalArrayV2 *array, v2 data);
void common_optimalArrayV2Destroy(OptimalArrayV2 *array);
i32 common_stringLen(String *const string);
String *const common_stringAppend(MemoryArena_ *const arena, String *oldString,
String *appendString, i32 appendLen);
void common_stringFree(MemoryArena_ *arena, String *string);
String *const common_stringMake(MemoryArena_ *const arena, char *string);
String *const common_stringMakeLen(MemoryArena_ *const arena, i32 len);
i32 common_strlen(const char *const string); i32 common_strlen(const char *const string);
i32 common_strcmp(const char *a, const char *b); i32 common_strcmp(const char *a, const char *b);
void common_strncat(char *dest, const char *src, i32 numChars); void common_strncat(char *dest, const char *src, i32 numChars);

View File

@ -20,7 +20,7 @@ enum DebugCount
debugcount_num, debugcount_num,
}; };
void debug_init(MemoryArena_ *arena, v2 windowSize, Font font); void debug_init(v2 windowSize, Font font);
#define DEBUG_RECURSIVE_PRINT_XML_TREE(sig) debug_recursivePrintXmlTree(sig, 1) #define DEBUG_RECURSIVE_PRINT_XML_TREE(sig) debug_recursivePrintXmlTree(sig, 1)
void debug_recursivePrintXmlTree(XmlNode *root, i32 levelsDeep); void debug_recursivePrintXmlTree(XmlNode *root, i32 levelsDeep);

View File

@ -2,6 +2,7 @@
#define DENGINE_MATH_H #define DENGINE_MATH_H
#include <math.h> #include <math.h>
#include "Dengine/Common.h" #include "Dengine/Common.h"
#define MATH_PI 3.14159265359f #define MATH_PI 3.14159265359f

View File

@ -143,6 +143,8 @@ typedef struct KeyState
typedef struct InputBuffer typedef struct InputBuffer
{ {
OptimalArrayV2 *resolutionList;
v2 mouseP; v2 mouseP;
KeyState keys[keycode_count]; KeyState keys[keycode_count];
} InputBuffer; } InputBuffer;

View File

@ -84,6 +84,7 @@ typedef struct Renderer
i32 groupCapacity; i32 groupCapacity;
} Renderer; } Renderer;
void renderer_updateSize(Renderer *renderer, AssetManager *assetManager, v2 windowSize);
void renderer_init(Renderer *renderer, AssetManager *assetManager, void renderer_init(Renderer *renderer, AssetManager *assetManager,
MemoryArena_ *persistentArena, v2 windowSize); MemoryArena_ *persistentArena, v2 windowSize);

View File

@ -1,16 +1,5 @@
#ifndef DENGINE_STRING_H #ifndef DENGINE_STRING_H
#define DENGINE_STRING_H #define DENGINE_STRING_H
#include "Dengine/Common.h"
typedef struct MemoryArena MemoryArena_;
typedef char String;
i32 string_len(String *const string);
String *const string_append(MemoryArena_ *const arena, String *oldString,
String *appendString, i32 appendLen);
void string_free(MemoryArena_ *arena, String *string);
String *const string_make(MemoryArena_ *const arena, char *string);
String *const string_makeLen(MemoryArena_ *const arena, i32 len);
#endif #endif