diff --git a/Dengine.vcxproj b/Dengine.vcxproj
index 417bcf5..0275641 100644
--- a/Dengine.vcxproj
+++ b/Dengine.vcxproj
@@ -129,6 +129,7 @@
+
@@ -153,6 +154,7 @@
+
diff --git a/Dengine.vcxproj.filters b/Dengine.vcxproj.filters
index ca60c51..36ba4a9 100644
--- a/Dengine.vcxproj.filters
+++ b/Dengine.vcxproj.filters
@@ -51,6 +51,9 @@
Source Files
+
+ Source Files
+
@@ -107,5 +110,8 @@
Header Files
+
+ Header Files
+
\ No newline at end of file
diff --git a/src/Common.c b/src/Common.c
index f0e274f..08c230a 100644
--- a/src/Common.c
+++ b/src/Common.c
@@ -39,7 +39,7 @@ char *common_strncpy(char *dest, const char *src, i32 numChars)
return dest;
}
-char *common_memset(char *const ptr, const i32 value, const i32 numBytes)
+u8 *common_memset(u8 *const ptr, const i32 value, const i32 numBytes)
{
for (i32 i = 0; i < numBytes; i++)
ptr[i] = value;
diff --git a/src/String.c b/src/String.c
new file mode 100644
index 0000000..0161f5d
--- /dev/null
+++ b/src/String.c
@@ -0,0 +1,117 @@
+#include "Dengine/String.h"
+#include "Dengine/Platform.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);
+ 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 = PLATFORM_MEM_ALLOC(arena, bytesToAllocate, u8);
+ if (!chunk) return NULL;
+
+ StringHeader *header = CAST(StringHeader *) chunk;
+ header->len = len;
+ return &header->string;
+}
+
diff --git a/src/WorldTraveller.c b/src/WorldTraveller.c
index 4a06460..5a6f2da 100644
--- a/src/WorldTraveller.c
+++ b/src/WorldTraveller.c
@@ -3,6 +3,7 @@
#include "Dengine/Debug.h"
#include "Dengine/Entity.h"
#include "Dengine/Platform.h"
+#include "Dengine/String.h"
#include "Dengine/UserInterface.h"
enum State
@@ -575,6 +576,30 @@ INTERNAL void unitTest(MemoryArena *arena)
ASSERT(common_atoi("+32", common_strlen("+32")) == 32);
ASSERT(common_atoi("+ 32", common_strlen("+ 32")) == 0);
asset_unitTest(arena);
+
+ i32 memBefore = arena->bytesAllocated;
+ String *hello = string_make(arena, "hello, ");
+ String *world = string_make(arena, "world");
+ ASSERT(string_len(hello) == 7);
+ ASSERT(string_len(world) == 5);
+
+ hello = string_append(arena, hello, world, string_len(world));
+ ASSERT(string_len(hello) == 12);
+ string_free(arena, hello);
+ string_free(arena, world);
+
+ hello = string_make(arena, "");
+ world = string_make(arena, "");
+ hello = string_append(arena, hello, world, string_len(world));
+ ASSERT(string_len(hello) == 0);
+ ASSERT(string_len(world) == 0);
+
+ string_free(arena, hello);
+ string_free(arena, world);
+
+ i32 memAfter = arena->bytesAllocated;
+
+ ASSERT(memBefore == memAfter);
}
// TODO(doyle): Remove and implement own random generator!
diff --git a/src/include/Dengine/Common.h b/src/include/Dengine/Common.h
index 399d9ef..141e26c 100644
--- a/src/include/Dengine/Common.h
+++ b/src/include/Dengine/Common.h
@@ -32,7 +32,7 @@ i32 common_strlen(const char *const string);
i32 common_strcmp(const char *a, const char *b);
void common_strncat(char *dest, const char *src, i32 numChars);
char *common_strncpy(char *dest, const char *src, i32 numChars);
-char *common_memset(char *const ptr, const i32 value, const i32 numBytes);
+u8 *common_memset(u8 *const ptr, const i32 value, const i32 numBytes);
// Max buffer size should be 11 for 32 bit integers
void common_itoa(i32 value, char *buf, i32 bufSize);
diff --git a/src/include/Dengine/String.h b/src/include/Dengine/String.h
new file mode 100644
index 0000000..07f1c17
--- /dev/null
+++ b/src/include/Dengine/String.h
@@ -0,0 +1,17 @@
+#ifndef 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