diff --git a/asset/AssetManagementSystem.h b/asset/AssetManagementSystem.h index 1aa13f0..f336f34 100644 --- a/asset/AssetManagementSystem.h +++ b/asset/AssetManagementSystem.h @@ -25,6 +25,11 @@ struct AssetManagementSystem { // @question is this even necessary or could we integrate this directly into the system here? HashMap hash_map; + uint64 ram_size; + uint64 vram_size; + uint64 asset_count; + bool has_changed; + // The indices of asset_memory and asset_data_memory are always linked // General asset memory @@ -71,7 +76,7 @@ int32 ams_calculate_chunks(AssetManagementSystem* ams, int32 byte_size) inline int64 ams_get_buffer_size(int count, int chunk_size) { - return hashmap_get_buffer_size(count, sizeof(HashEntryInt64)) // hash map + return hashmap_size(count, sizeof(HashEntryInt64)) // hash map + sizeof(Asset) * count + CEIL_DIV(count, 64) * sizeof(uint64) // asset_memory + chunk_size * count + CEIL_DIV(count, 64) * sizeof(uint64); // asset_data_memory } @@ -103,14 +108,55 @@ void ams_create(AssetManagementSystem* ams, byte* buf, int chunk_size, int count } inline -uint64 ams_get_vram_usage(AssetManagementSystem* ams) +void ams_update_stats(AssetManagementSystem* ams) { - uint64 size = 0; - for (int32 i = 0; i < ams->asset_memory.count; ++i) { - size += ((Asset *) (ams->asset_memory.memory))[i].vram_size; + // @bug We should check the hash map or the memory status, we could still have old values in here + + ams->vram_size = 0; + ams->ram_size = 0; + ams->asset_count = 0; + + Asset* temp_asset = ams->first; + + while (temp_asset) { + ams->vram_size += temp_asset->vram_size; + ams->ram_size += temp_asset->ram_size; + ++ams->asset_count; + + temp_asset = temp_asset->next; } - return size; + ams->has_changed = false; +} + +inline +uint64 ams_get_asset_count(AssetManagementSystem* ams) +{ + if (ams->has_changed) { + ams_update_stats(ams); + } + + return ams->asset_count; +} + +inline +uint64 ams_get_vram_usage(AssetManagementSystem* ams) +{ + if (ams->has_changed) { + ams_update_stats(ams); + } + + return ams->vram_size; +} + +inline +uint64 ams_get_ram_usage(AssetManagementSystem* ams) +{ + if (ams->has_changed) { + ams_update_stats(ams); + } + + return ams->ram_size; } void ams_free_asset(AssetManagementSystem* ams, Asset* asset) @@ -124,6 +170,8 @@ void ams_free_asset(AssetManagementSystem* ams, Asset* asset) chunk_free_element(&ams->asset_memory, asset->internal_id + i); chunk_free_element(&ams->asset_data_memory, asset->internal_id + i); } + + ams->has_changed = true; } inline @@ -208,6 +256,8 @@ Asset* ams_reserve_asset(AssetManagementSystem* ams, const char* name, uint32 el ams->last = asset; } + ams->has_changed = true; + return asset; } diff --git a/compression/CRC.h b/compression/CRC.h index 726aa03..f4e1e61 100644 --- a/compression/CRC.h +++ b/compression/CRC.h @@ -47,7 +47,7 @@ uint32 crc_table[256] = 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, }; -uint32 calculate_crc32_checksum(uint8 *p, uint32 length) +uint32 crc32_checksum_calculate(uint8 *p, uint32 length) { uint32 crc = 0xFFFFFFFF; while (length-- != 0) { @@ -58,7 +58,7 @@ uint32 calculate_crc32_checksum(uint8 *p, uint32 length) return (crc ^ 0xFFFFFFFF); } -void fill_crc32_table(uint32 *table){ +void crc32_table_fill(uint32 *table){ uint8 index = 0,z; do { table[index] = index; diff --git a/compression/Huffman.h b/compression/Huffman.h new file mode 100644 index 0000000..0c15d6b --- /dev/null +++ b/compression/Huffman.h @@ -0,0 +1,229 @@ +/** + * Jingga + * + * @copyright Jingga + * @license OMS License 2.0 + * @version 1.0.0 + * @link https://jingga.app + */ +#ifndef TOS_COMPRESSION_HUFFMAN_H +#define TOS_COMPRESSION_HUFFMAN_H + +#include +#include + +#include "../stdlib/Types.h" +#include "../utils/BitUtils.h" +#include "../utils/EndianUtils.h" +#include "../utils/Utils.h" + +struct HuffmanNode { + HuffmanNode* left; + HuffmanNode* right; + int32 frequency; + byte character; +}; + +struct Huffman { + HuffmanNode pool[512]; + HuffmanNode priority_queue[511]; + HuffmanNode** pq; + int32 node_count; + int32 pq_end; + + // Contains the actual table data + char buffer[1024]; + char* code[256]; +}; + +HuffmanNode* huffman_node_create(Huffman* hf, int32 frequency, byte character, HuffmanNode* left, HuffmanNode* right) +{ + HuffmanNode* node = hf->pool + hf->node_count++; + if (frequency) { + node->character = character; + node->frequency = frequency; + } else { + node->left = left; + node->right = right; + node->frequency = left->frequency + right->frequency; + } + + return node; +} + +void huffman_node_insert(Huffman* hf, HuffmanNode* node) +{ + int32 child_id; + int32 parent_id = hf->pq_end++; + + while ((child_id = parent_id / 2)) { + if (hf->pq[child_id]->frequency <= node->frequency) { + break; + } + + hf->pq[parent_id] = hf->pq[child_id]; + parent_id = child_id; + } + + hf->pq[parent_id] = node; +} + +HuffmanNode* huffman_node_remove(Huffman* hf) +{ + int32 parent_id = 1; + int32 left_child_id; + HuffmanNode* min_node = hf->pq[parent_id]; + + if (hf->pq_end < 2) { + return 0; + } + + --hf->pq_end; + + while ((left_child_id = parent_id * 2) < hf->pq_end) { + // Branchless increment + left_child_id += (int32) (left_child_id + 1 < hf->pq_end + && hf->pq[left_child_id + 1]->frequency < hf->pq[left_child_id]->frequency + ); + + hf->pq[parent_id] = hf->pq[left_child_id]; + parent_id = left_child_id; + } + + hf->pq[parent_id] = hf->pq[hf->pq_end]; + + return min_node; +} + +int64 huffman_code_build(Huffman* hf, HuffmanNode* root, char* code, int32 length, char* code_buffer, int32* buffer_position) +{ + if (root->character) { + code[length] = 0; + strcpy(&code_buffer[*buffer_position], code); + hf->code[root->character] = &code_buffer[*buffer_position]; + *buffer_position += length + 1; + + return; + } + + code[length] = '0'; huffman_code_build(hf, root->left, code, length + 1, code_buffer, buffer_position); + code[length] = '1'; huffman_code_build(hf, root->right, code, length + 1, code_buffer, buffer_position); +} + +void huffman_init(Huffman* hf, const byte* in) +{ + int32 frequency[256] = {0}; + char temp_code[16]; + int32 buffer_position = 0; + + // We artificially force the root element (usually the 0 element) to have the index 1. + hf->pq = (HuffmanNode **) (hf->priority_queue - 1); + + while (*in) frequency[(byte) *in++]++; + + for (int32 i = 0; i < 256; ++i) { + if (frequency[i]) { + huffman_node_insert(hf, huffman_node_create(hf, frequency[i], i, NULL, NULL)); + } + } + + while (hf->pq_end > 2) { + huffman_node_insert(hf, huffman_node_create(hf, 0, 0, huffman_node_remove(hf), huffman_node_remove(hf))); + } + + huffman_code_build(hf, hf->pq[1], temp_code, 0, hf->buffer, &buffer_position); +} + +void huffman_dump(const Huffman* hf, byte* out) +{ + // dump the char -> code relations as relative indeces + for (int32 i = 0; i < ARRAY_COUNT(hf->code); ++i) { + if (hf->code[i]) { + *((int64 *) out) = SWAP_ENDIAN_LITTLE(hf->code[i] - hf->buffer); + } else { + *((int64 *) out) = SWAP_ENDIAN_LITTLE(-1); + } + + out += sizeof(int64); + } + + // dump the table codes + memcpy(out, hf->buffer, sizeof(char) * ARRAY_COUNT(hf->buffer)); +} + +void huffman_load(Huffman* hf, const byte* in) +{ + // load the char -> code relations and convert relative indeces to pointers + for (int32 i = 0; i < ARRAY_COUNT(hf->code); ++i) { + int64 value = SWAP_ENDIAN_LITTLE(*((int64 *) in)); + in += sizeof(value); + + if (value > -1) { + hf->code[i] = hf->buffer + value; + } + } + + // load the table codes + memcpy(hf->buffer, in, sizeof(char) * ARRAY_COUNT(hf->buffer)); +} + +int64 huffman_encode(Huffman* hf, const byte* in, byte* out) +{ + uint64 bit_length = 0; + int32 pos_bit = 0; + + while (*in) { + const char* code = hf->code[*in++]; + + while (*code) { + if (*code == '1') { + BIT_SET_L2R(*out, pos_bit, 1); + } + + ++code; + ++bit_length; + ++pos_bit; + + if (pos_bit > 7) { + ++out; + pos_bit = 0; + } + } + } + + return bit_length; +} + +int64 huffman_decode(Huffman* hf, const byte* in, byte* out, uint64 bit_length) +{ + HuffmanNode* current = hf->pq[1]; + int32 pos_bit = 0; + int64 out_length = 0; + + byte* start = out; + + while (pos_bit < bit_length) { + if (BITS_GET_8_L2R(*in, pos_bit++, 1)) { + current = current->right; + } else { + current = current->left; + } + + if (current->character) { + *out++ = current->character; + current = hf->pq[1]; + } + + if (pos_bit > 7) { + ++in; + pos_bit = 0; + } + } + + *out = '\0'; + + // -1 for the \0 character which is not part of the length + return out - start - 1; +} + +#endif \ No newline at end of file diff --git a/compression/LZP.h b/compression/LZP.h index 2034401..caf842a 100644 --- a/compression/LZP.h +++ b/compression/LZP.h @@ -14,7 +14,7 @@ #include "../stdlib/Types.h" -uint32 encode_lzp(const byte* in, size_t length, byte* out) +uint32 lzp_encode(const byte* in, size_t length, byte* out) { byte buf[9]; byte table[1 << 16] = {0}; @@ -58,7 +58,7 @@ uint32 encode_lzp(const byte* in, size_t length, byte* out) return out_pos; } -uint32 decode_lzp(const byte* in, size_t length, byte* out) +uint32 lzp_decode(const byte* in, size_t length, byte* out) { byte buf[8]; byte table[1 << 16] = {0}; @@ -126,7 +126,7 @@ int32 find_longest_match(char *window, int32 window_start, char *buffer, int32 b return best_length; } -uint32 encode_lzp3(const byte* in, size_t length, byte* out) { +uint32 lzp3_encode(const byte* in, size_t length, byte* out) { char window[4096] = {0}; int32 window_start = 0; @@ -157,7 +157,7 @@ uint32 encode_lzp3(const byte* in, size_t length, byte* out) { return out_size; } -uint32 decode_lzp3(const byte* in, size_t length, byte* out) { +uint32 lzp3_decode(const byte* in, size_t length, byte* out) { char window[4096] = {0}; int32 window_start = 0; diff --git a/compression/RLE.h b/compression/RLE.h new file mode 100644 index 0000000..1e6b3bc --- /dev/null +++ b/compression/RLE.h @@ -0,0 +1,66 @@ +/** + * Jingga + * + * @copyright Jingga + * @license OMS License 2.0 + * @version 1.0.0 + * @link https://jingga.app + */ +#ifndef TOS_COMPRESSION_RLE_H +#define TOS_COMPRESSION_RLE_H + +#include +#include + +#include "../stdlib/Types.h" +#include "../utils/StringUtils.h" + +// max out length = length * 2 + 1 +uint64 rle_encode(const char* in, size_t length, char* out) +{ + uint64 count; + uint64 j = 0; + + for (uint64 i = 0; i < length; i++) { + count = 1; + while (i + 1 < length && in[i] == in[i + 1]) { + ++count; + ++i; + } + + out[j++] = in[i]; + + j += int_to_str(count, &out[j], NULL); + } + + out[j] = '\0'; + + return j; +} + +uint64 rle_decode(const char* in, size_t length, char* out) +{ + uint64 j = 0; + + for (int64 i = 0; i < length; i++) { + char current_char = in[i]; + ++i; + + int32 count = 0; + while (i < length && in[i] >= '0' && in[i] <= '9') { + count = count * 10 + (in[i] - '0'); + ++i; + } + --i; + + for (int32 k = 0; k < count; k++) { + out[j++] = current_char; + } + } + + out[j] = '\0'; + + return j; +} + +#endif \ No newline at end of file diff --git a/font/Font.h b/font/Font.h index b188159..5376f6c 100644 --- a/font/Font.h +++ b/font/Font.h @@ -3,19 +3,8 @@ #include "../stdlib/Types.h" #include "../memory/BufferMemory.h" - -// @todo Move this somewhere else, it doesn't belong here -enum UIAlignH { - UI_ALIGN_H_LEFT, - UI_ALIGN_H_CENTER, - UI_ALIGN_H_RIGHT, -}; - -enum UIAlignV { - UI_ALIGN_V_BOTTOM, - UI_ALIGN_V_CENTER, - UI_ALIGN_V_TOP, -}; +#include "../utils/EndianUtils.h" +#include "../stdlib/simd/SIMD_I32.h" struct GlyphMetrics { f32 width; // Width of the glyph @@ -25,11 +14,9 @@ struct GlyphMetrics { f32 advance_x; // Horizontal advance after drawing the glyph }; -// @question Do we even need all this information? x2 and y2 follow from width and height, no? struct GlyphTextureCoords { f32 x1; f32 y1; - f32 x2; f32 y2; }; @@ -50,9 +37,9 @@ struct Font { }; inline -void font_init(BufferMemory* buf, Font* font, int count) +void font_init(Font* font, byte* data, int count) { - font->glyphs = (Glyph *) buffer_get_memory(buf, sizeof(Glyph) * count); + font->glyphs = (Glyph *) data; font->glyph_count = count; } @@ -68,4 +55,168 @@ Glyph* font_glyph_find(Font* font, uint32 codepoint) return NULL; } +void font_from_file_txt( + RingMemory* ring, + const char* path, + Font* font +) +{ + FileBody file; + file_read(path, &file, ring); + + char* pos = (char *) file.content; + + bool start = true; + char block_name[32]; + + int32 glyph_index = 0; + + int32 image_width = 0; + int32 image_height = 0; + + while (*pos != '\0') { + if (start) { + // Parsing general data + int32 i = 0; + while (*pos != '\0' && *pos != ' ' && *pos != '\n' && i < 31) { + block_name[i] = *pos; + ++pos; + ++i; + } + + // Go to value + while (*pos == ' ' || *pos == '\t') { + ++pos; + } + + if (strcmp(block_name, "font_size") == 0) { + font->size = strtof(pos, &pos); + } else if (strcmp(block_name, "line_height") == 0) { + font->line_height = strtoul(pos, &pos, 10); + } else if (strcmp(block_name, "image_width") == 0) { + image_width = strtoul(pos, &pos, 10); + } else if (strcmp(block_name, "image_height") == 0) { + image_height = strtoul(pos, &pos, 10); + } else if (strcmp(block_name, "glyph_count") == 0) { + // glyph_count has to be the last general element + font->glyph_count = strtoul(pos, &pos, 10); + start = false; + } + + // Go to next line + while (*pos++ != '\n') {}; + ++pos; + } else { + // Parsing glyphs + // In the text file we don't have to define width and height of the character, we calculate that here + font->glyphs[glyph_index] = { + strtoul(pos, &pos, 10), + {0.0f, 0.0f, strtof(++pos, &pos), strtof(++pos, &pos), strtof(++pos, &pos)}, + {strtof(++pos, &pos), strtof(++pos, &pos), strtof(++pos, &pos), strtof(++pos, &pos)} + }; + + font->glyphs[glyph_index].metrics.width = font->glyphs[glyph_index].coords.x2 - font->glyphs[glyph_index].coords.x1; + font->glyphs[glyph_index].metrics.height = font->glyphs[glyph_index].coords.y2 - font->glyphs[glyph_index].coords.y1; + + ++glyph_index; + + // Go to next line + while (*pos != '\n' && *pos != '\0') { ++pos; }; + ++pos; + } + } +} + +// Calculates the required size for representing a font definition in memory +inline +uint64 font_size_from_file(const byte* data) +{ + return SWAP_ENDIAN_LITTLE(*((uint32 *) data)) * sizeof(Glyph); +} + +inline +uint64 font_size(const Font* font) +{ + // We have to remove the size of the pointer which will not be stored + return sizeof(font) - sizeof(Glyph*) + + font->glyph_count * sizeof(Glyph); +} + +void font_from_file( + Font* font, + const byte* data, + int32 size = 8 +) +{ + const byte* pos = data; + + // Read count + font->glyph_count = SWAP_ENDIAN_LITTLE(*((uint32 *) pos)); + pos += sizeof(font->glyph_count); + + // Read font size + font->size = SWAP_ENDIAN_LITTLE(*((f32 *) pos)); + pos += sizeof(font->size); + + // Read line height + font->line_height = SWAP_ENDIAN_LITTLE(*((uint32 *) pos)); + pos += sizeof(font->line_height); + + memcpy(font->glyphs, pos, font->glyph_count * sizeof(Glyph)); + + SWAP_ENDIAN_LITTLE_SIMD( + (int32 *) font->glyphs, + (int32 *) font->glyphs, + font->glyph_count * sizeof(Glyph) / 4, // everything in here is 4 bytes -> super easy to swap + steps + ); +} + +inline +int64 font_size_from_font(Font* font) +{ + return font->glyph_count * sizeof(Glyph) + sizeof(Font); +} + +void font_to_file( + RingMemory* ring, + const char* path, + const Font* font, + int32 steps = 8 +) +{ + FileBody file; + file.size = font->glyph_count * sizeof(Glyph) + sizeof(Font); + file.content = ring_get_memory(ring, file.size, 64); + + byte* pos = file.content; + + // Glyph count + *((uint32 *) pos) = font->glyph_count; + pos += sizeof(font->glyph_count); + + // Font size + *((f32 *) pos) = font->size; + pos += sizeof(font->size); + + // Line height + *((uint32 *) pos) = font->line_height; + pos += sizeof(font->line_height); + + // The glyphs are naturally tightly packed -> we can just store the memory + memcpy(pos, font->glyphs, font->glyph_count * sizeof(Glyph)); + pos += font->glyph_count * sizeof(Glyph); + + file.size = pos - file.content; + + SWAP_ENDIAN_LITTLE_SIMD( + (int32 *) file.content, + (int32 *) file.content, + file.size / 4, // everything in here is 4 bytes -> super easy to swap + steps + ); + + file_write(path, &file); +} + #endif \ No newline at end of file diff --git a/gpuapi/RenderUtils.h b/gpuapi/RenderUtils.h index 4382de0..7679df6 100644 --- a/gpuapi/RenderUtils.h +++ b/gpuapi/RenderUtils.h @@ -16,6 +16,12 @@ #include "../math/matrix/MatrixFloat32.h" #include "../font/Font.h" #include "../object/Vertex.h" +#include "../ui/UITheme.h" +#include "../ui/UIElement.h" +#include "../ui/UIAlignment.h" + +// @performance Create improved vertice generation for components (input + button, chat, ...) where we don't use as many +// degenerate triangled inline void vertex_degenerate_create( @@ -370,7 +376,7 @@ void text_calculate_dimensions( *height = y; } -void vertex_text_create( +f32 vertex_text_create( Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex, f32 x, f32 y, f32 width, f32 height, int32 align_h, int32 align_v, const Font* __restrict font, const char* __restrict text, f32 size, uint32 color_index = 0 @@ -433,6 +439,189 @@ void vertex_text_create( // @todo If width or height (usually just width) > 0 we use those values for automatic wrapping // This way we can ensure no overflow easily // @todo implement line alignment, currently only total alignment is considered + + return offset_x; +} + +f32 ui_text_create( + Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex, + UITheme* theme, UIElement* element +) { + if (element->vertex_count > 0) { + memcpy(vertices + *index, element->vertices, sizeof(Vertex3DTextureColorIndex) * element->vertex_count); + return; + } + + HashEntryVoidP* entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, element->name, element->id); + UIAttributeGroup* group = (UIAttributeGroup *) entry->value; + + UIAttribute* x; + UIAttribute* y; + + UIAttribute* parent = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_PARENT); + if (parent) { + HashEntryVoidP* parent_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, parent->value_str); + UIAttributeGroup* parent_group = (UIAttributeGroup *) parent_entry->value; + + x = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_X); + y = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_Y); + + // @question Do we have more values which can be inherited from the parent? + // We don't want to inherit implicit stuff like size, background etc. These things should be defined explicitly + } else { + x = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_POSITION_X); + y = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_POSITION_Y); + } + + UIAttribute* width = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH); + UIAttribute* height = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT); + UIAttribute* align_h = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_ALIGN_H); + UIAttribute* align_v = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_ALIGN_V); + UIAttribute* text = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_CONTENT); + UIAttribute* size = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_FONT_SIZE); + UIAttribute* color_index = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_FONT_COLOR); + + int32 length = utf8_strlen(text->value_str); + float scale = size->value_float / theme->font.size; + + // If we do a different alignment we need to pre-calculate the width and height + if (align_h != NULL || align_v != NULL) { + f32 tmp_width = width->value_int; + f32 tmp_height = height->value_int; + + text_calculate_dimensions(&tmp_width, &tmp_height, &theme->font, text->value_str, scale, length); + + if (align_h->value_int == UI_ALIGN_H_RIGHT) { + x -= width->value_int; + } else if (align_h->value_int == UI_ALIGN_H_CENTER) { + x -= width->value_int / 2; + } + + if (align_v->value_int == UI_ALIGN_V_TOP) { + y -= height->value_int; + } else if (align_v->value_int == UI_ALIGN_V_CENTER) { + y -= height->value_int / 2; + } + } + + int32 start = *index; + f32 offset_x = x->value_int; + f32 offset_y = y->value_int; + for (int i = 0; i < length; ++i) { + int32 character = utf8_get_char_at(text->value_str, i); + + if (character == '\n') { + offset_y += theme->font.line_height * scale; + offset_x = x->value_int; + + continue; + } + + Glyph* glyph = NULL; + for (int j = 0; j < theme->font.glyph_count; ++j) { + if (theme->font.glyphs[j].codepoint == character) { + glyph = &theme->font.glyphs[j]; + + break; + } + } + + if (!glyph) { + continue; + } + + vertex_rect_create( + vertices, index, zindex, + offset_x, offset_y, glyph->metrics.width * scale, glyph->metrics.height * scale, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM, + color_index->value_int, glyph->coords.x1, glyph->coords.y1, glyph->coords.x2, glyph->coords.y2 + ); + + offset_x += (glyph->metrics.width + glyph->metrics.offset_x) * scale; + } + + element->vertex_count = *index - start; + memcpy(element->vertices, vertices + start, sizeof(Vertex3DTextureColorIndex) * element->vertex_count); + + // @todo See todo of vertex_text function + // @performance use elements->vertices and also cache result in there + + return offset_x; +} + +void ui_button_create( + Vertex3DTextureColorIndex* __restrict vertices, uint32* __restrict index, f32 zindex, + UITheme* theme, UIElement* element +) +{ + // @todo handle different states and ongoing animations + // We cannot return early in such cases + if (element->vertex_count > 0) { + memcpy(vertices + *index, element->vertices, sizeof(Vertex3DTextureColorIndex) * element->vertex_count); + return; + } + + HashEntryVoidP* entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, element->name, element->id); + UIAttributeGroup* group = (UIAttributeGroup *) entry->value; + + UIAttribute* x; + UIAttribute* y; + + UIAttribute* parent = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_PARENT); + if (parent) { + HashEntryVoidP* parent_entry = (HashEntryVoidP *) hashmap_get_entry(&theme->hash_map, parent->value_str); + UIAttributeGroup* parent_group = (UIAttributeGroup *) parent_entry->value; + + x = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_X); + y = ui_attribute_from_group(parent_group, UI_ATTRIBUTE_TYPE_POSITION_Y); + + // @question Do we have more values which can be inherited from the parent? + // We don't want to inherit implicit stuff like size, background etc. These things should be defined explicitly + } else { + x = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_POSITION_X); + y = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_POSITION_Y); + } + + UIAttribute* width = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH); + UIAttribute* height = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT); + UIAttribute* align_h = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_ALIGN_H); + UIAttribute* align_v = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_ALIGN_V); + UIAttribute* text = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_CONTENT); + UIAttribute* size = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_FONT_SIZE); + UIAttribute* color_index = ui_attribute_from_group(group, UI_ATTRIBUTE_TYPE_FONT_COLOR); + + // @todo Above we only handle the default values, what about state dependent values like hover, active? + // Simply check the state here and load the child_entries based on the state + // However, for that we need the current state of the button... should this be in a separate button object, + // that also holds position information for hover checks etc. Or should that state be stored in the theme data? + // Right now we could make these checks right here anyway but in the future we don't want to update the rendering data + // every frame if we don't have to. We don't want immediate mode! We only want to update the UI if there is a change. + // If a change (or state change like hover) triggers a complete update of all elements or just a sub region update + // remains TBD + + int32 start = *index; + + vertex_rect_border_create( + vertices, index, zindex, + x->value_int, y->value_int, width->value_int, height->value_int, 1, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM, + 12, 0.0f, 0.0f + ); + + vertex_rect_create( + vertices, index, zindex, + x->value_int + 1, y->value_int + 1, width->value_int - 2, height->value_int - 2, UI_ALIGN_H_LEFT, UI_ALIGN_V_BOTTOM, + 14, 0.0f, 0.0f + ); + + zindex = nextafterf(zindex, INFINITY); + + vertex_text_create( + vertices, index, zindex, + x->value_int, y->value_int, width->value_int, height->value_int, align_h->value_int, align_v->value_int, + &theme->font, text->value_str, size->value_int, color_index->value_int + ); + + element->vertex_count = *index - start; + memcpy(element->vertices, vertices + start, sizeof(Vertex3DTextureColorIndex) * element->vertex_count); } inline diff --git a/hash/GeneralHash.h b/hash/GeneralHash.h index be8c1f4..a5fc284 100644 --- a/hash/GeneralHash.h +++ b/hash/GeneralHash.h @@ -105,4 +105,107 @@ uint32 hash_ejb(const char* str) return h % PRIME2; } +// CONSTEXPR + +constexpr +uint64 hash_djb2_const(const char* key) { + uint64 hash = 5381; + int32 c; + + while ((c = *key++)) { + hash = ((hash << 5) + hash) + c; + } + + return hash; +} + +constexpr +uint64 hash_sdbm_const(const byte* key) +{ + uint64 hash = 0; + int32 c; + + while (c = *key++) { + hash = c + (hash << 6) + (hash << 16) - hash; + } + + return hash; +} + +constexpr +uint64 hash_lose_lose_const(const byte* key) +{ + uint64 hash = 0; + int32 c; + + while (c = *key++) { + hash += c; + } + + return hash; +} + +constexpr +uint64 hash_polynomial_rolling_const(const char* str) { + const int32 p = 31; + const int32 m = 1000000009; + uint64 hash = 0; + uint64 p_pow = 1; + + while (*str) { + hash = (hash + (*str - 'a' + 1) * p_pow) % m; + p_pow = (p_pow * p) % m; + str++; + } + + return hash; +} + +constexpr +uint64 hash_fnv1a_const(const char* str) { + const uint64 FNV_OFFSET_BASIS = 14695981039346656037UL; + const uint64 FNV_PRIME = 1099511628211UL; + uint64 hash = FNV_OFFSET_BASIS; + + while (*str) { + hash ^= (byte) *str; + hash *= FNV_PRIME; + str++; + } + + return hash; +} + +constexpr +uint32 hash_oat_const(const char* str) +{ + uint32 h = 0; + + while(*str) { + h += *str++; + h += (h << 10); + h ^= (h >> 6); + } + + h += (h << 3); + h ^= (h >> 11); + h += (h << 15); + + return h; +} + +constexpr +uint32 hash_ejb_const(const char* str) +{ + const uint32 PRIME1 = 37; + const uint32 PRIME2 = 1048583; + uint32 h = 0; + + while (*str) { + h = h * PRIME1 ^ (*str++ - ' '); + } + + return h % PRIME2; +} + #endif \ No newline at end of file diff --git a/ui/UIDropdown.h b/localization/Dialog.h similarity index 100% rename from ui/UIDropdown.h rename to localization/Dialog.h diff --git a/localization/Language.h b/localization/Language.h new file mode 100644 index 0000000..ccccc6f --- /dev/null +++ b/localization/Language.h @@ -0,0 +1,130 @@ +#ifndef TOS_UI_LANGUAGE_H +#define TOS_UI_LANGUAGE_H + +#include "../stdlib/Types.h" +#include "../memory/RingMemory.h" + +#if _WIN32 + #include "../platform/win32/UtilsWin32.h" +#else + #include "../platform/linux/UtilsLinux.h" +#endif + +struct Language { + // WARNING: the actual start of data is data -= sizeof(count); see file loading below + byte* data; + + int32 count; + char** lang; +}; + +void language_from_file_txt( + RingMemory* ring, + const char* path, + Language* language +) { + FileBody file; + file.size = file_size(path); + file.content = ring_get_memory(ring, file.size); + + file_read(path, &file); + + // count elements + language->count = 1; + for (int32 i = 0; i < file.size - 1; ++i) { + if (file.content[i] == '\n' && file.content[i + 1] == '\n') { + ++language->count; + file.content[i] = '\0'; + ++i; + } + } + + language->lang = (char **) language->data; + memcpy(language->data + language->count * sizeof(char *), file.content, file.size); + + // First element = 0 + *language->lang = (char *) file.content; + ++language->lang; + + for (int32 i = 1; i < file.size - 1; ++i) { + if (file.content[i] == '\0') { + // We have to move by 2 since every text element is separated by 2 \n + // 1 \n is a valid char for a single text element + // @performance This also means that we have one additional byte for + // every text element even in the binary version. + *language->lang = (char *) &file.content[i + 2]; + ++language->lang; + } + } +} + +// File layout - binary +// offsets for start of strings +// actual string data +void language_from_file( + const char* path, + Language* language +) { + FileBody file; + file.content = language->data; + + file_read(path, &file); + + byte* pos = language->data; + + // Count + language->count = SWAP_ENDIAN_LITTLE(*((int32 *) pos)); + pos += sizeof(language->count); + + language->lang = (char **) pos; + + byte* start = pos; + + // Load pointers/offsets + for (int32 i = 0; i < language->count; ++i) { + *language->lang = (char *) (start + SWAP_ENDIAN_LITTLE(*((uint64 *) pos))); + ++language->lang; + pos += sizeof(uint64); + } + + // We don't have to load the actual strings, they are already in ->data due to the file reading +} + +void language_to_file( + RingMemory* ring, + const char* path, + Language* language +) { + FileBody file; + + // Temporary file size for buffer + // @todo This is a bad placeholder, The problem is we don't know how much we actually need without stepping through the elements + // I also don't want to add a size variable to the theme as it is useless in all other cases + file.size = MEGABYTE * 32; + + file.content = ring_get_memory(ring, file.size, 64); + byte* pos = file.content; + + // Count + *((int32 *) pos) = SWAP_ENDIAN_LITTLE(language->count); + pos += sizeof(language->count); + + byte* start = pos; + + // Save pointers + for (int32 i = 0; i < language->count; ++i) { + *((uint64 *) pos) = SWAP_ENDIAN_LITTLE(pos - start); + pos += sizeof(uint64); + } + + // Save actual strings + for (int32 i = 0; i < language->count; ++i) { + strcpy((char *) pos, language->lang[i]); + pos += strlen(language->lang[i]); + } + + file.size = pos - file.content; + file_write(path, &file); +} + +#endif \ No newline at end of file diff --git a/log/Debug.cpp b/log/Debug.cpp index 57cdaeb..fbeac26 100644 --- a/log/Debug.cpp +++ b/log/Debug.cpp @@ -6,6 +6,15 @@ #include "Log.h" #include "TimingStat.h" +#if _WIN32 + #include + void setup_performance_count() { + LARGE_INTEGER perf_counter; + QueryPerformanceFrequency(&perf_counter); + debug_container->performance_count_frequency = perf_counter.QuadPart; + } +#endif + // IMPORTANT: This function should only be called when you actually use this data // e.g. log to display or file inline @@ -15,10 +24,28 @@ void update_timing_stat(uint32 stat, const char* function) debug_container->perf_stats[stat].function = function; debug_container->perf_stats[stat].delta_tick = new_tick_count - debug_container->perf_stats[stat].old_tick_count; - debug_container->perf_stats[stat].delta_time = (double) debug_container->perf_stats[stat].delta_tick / (double) performance_count_frequency; + debug_container->perf_stats[stat].delta_time = (double) debug_container->perf_stats[stat].delta_tick / (double) debug_container->performance_count_frequency; debug_container->perf_stats[stat].old_tick_count = new_tick_count; } +inline +void reset_counter(int32 id) +{ + debug_container->counter[id] = 0; +} + +inline +void log_increment(int32 id) +{ + ++debug_container->counter[id]; +} + +inline +void log_counter(int32 id, int32 value) +{ + debug_container->counter[id] = value; +} + // @todo don't use a pointer to this should be in a global together with other logging data (see Log.h) inline DebugMemory* debug_memory_find(uint64 start, uint64 size) @@ -160,9 +187,7 @@ byte* log_get_memory(uint64 size, byte aligned = 1, bool zeroed = false) if (aligned > 1) { uintptr_t address = (uintptr_t) debug_container->log_memory.memory; - int64 adjustment = (aligned - ((address + debug_container->log_memory.pos) & (aligned - 1))) % aligned; - - debug_container->log_memory.pos += adjustment; + debug_container->log_memory.pos += (aligned - ((address + debug_container->log_memory.pos) & (aligned - 1))) % aligned; } size = ROUND_TO_NEAREST(size, aligned); @@ -171,9 +196,7 @@ byte* log_get_memory(uint64 size, byte aligned = 1, bool zeroed = false) if (aligned > 1) { uintptr_t address = (uintptr_t) debug_container->log_memory.memory; - int64 adjustment = (aligned - ((address + debug_container->log_memory.pos) & (aligned - 1))) % aligned; - - debug_container->log_memory.pos += adjustment; + debug_container->log_memory.pos += (aligned - ((address + debug_container->log_memory.pos) & (aligned - 1))) % aligned; } } diff --git a/log/Debug.h b/log/Debug.h index 9752f49..5473052 100644 --- a/log/Debug.h +++ b/log/Debug.h @@ -16,9 +16,19 @@ struct DebugContainer { DebugMemoryContainer dmc; + + // Used for logging timings for different sections TimingStat* perf_stats; + + // Required to calculate the "fps" + uint64 performance_count_frequency; + + // Used to log memory access (read, write) LogMemory log_memory; + // Used to log general int values (e.g. counter for draw calls etc.) + int32* counter; + #if _WIN32 HANDLE log_fp; #endif diff --git a/log/Log.h b/log/Log.h index 44560ea..3710c48 100644 --- a/log/Log.h +++ b/log/Log.h @@ -54,16 +54,24 @@ struct LogMemory { void log_to_file(); void log(const char* str, bool should_log, bool save, const char* file, const char* function, int32 line); void log(const char* format, LogDataType data_type, void* data, bool should_log, bool save, const char* file, const char* function, int32 line); +void log_increment(int32); +void log_counter(int32, int32); #if (LOG_LEVEL == 0) // Don't perform any logging at log level 0 #define LOG(str, should_log, save) ((void) 0) #define LOG_FORMAT(format, data_type, data, should_log, save) ((void) 0) #define LOG_TO_FILE() ((void) 0) + #define LOG_INCREMENT(a) ((void) 0) + #define LOG_COUNTER(a, b) ((void) 0) + #define RESET_COUNTER(a) ((void) 0) #else #define LOG(str, should_log, save) log((str), (should_log), (save), __FILE__, __func__, __LINE__) #define LOG_FORMAT(format, data_type, data, should_log, save) log((format), (data_type), (data), (should_log), (save), __FILE__, __func__, __LINE__) #define LOG_TO_FILE() log_to_file() + #define LOG_INCREMENT(a) log_increment((a)) + #define LOG_COUNTER(a, b) log_counter((a), (b)) + #define RESET_COUNTER(a) reset_counter((a)) #endif #if DEBUG diff --git a/log/TimingStat.h b/log/TimingStat.h index 681a854..15fcbfa 100644 --- a/log/TimingStat.h +++ b/log/TimingStat.h @@ -20,8 +20,6 @@ #include #endif -global_persist uint64 performance_count_frequency; - struct TimingStat { const char* function; uint64 old_tick_count; @@ -32,6 +30,7 @@ struct TimingStat { // Sometimes we want to only do logging in debug mode. // In such cases use the following macro. #if DEBUG || INTERNAL + void update_timing_stat(uint32, const char*); #define UPDATE_TIMING_STAT(stat) update_timing_stat(stat, __func__) #else #define UPDATE_TIMING_STAT(stat) ((void) 0) diff --git a/memory/BufferMemory.h b/memory/BufferMemory.h index 0209cc8..d1c6d7a 100644 --- a/memory/BufferMemory.h +++ b/memory/BufferMemory.h @@ -12,6 +12,7 @@ #include #include "../stdlib/Types.h" #include "../utils/MathUtils.h" +#include "../utils/EndianUtils.h" #include "../utils/TestUtils.h" #include "Allocation.h" #include "../log/DebugMemory.h" @@ -35,6 +36,7 @@ void buffer_alloc(BufferMemory* buf, uint64 size, int32 alignment = 64) : (byte *) playform_alloc_aligned(size, alignment); buf->alignment = alignment; + buf->element_alignment = 0; buf->size = size; DEBUG_MEMORY_INIT((uint64) buf->memory, size); @@ -51,6 +53,20 @@ void buffer_free(BufferMemory* buf) } } +inline +void buffer_init(BufferMemory* buf, byte* data, uint64 size, int32 alignment = 64) +{ + // @bug what if an alignment is defined? + buf->memory = data; + + buf->size = size; + buf->pos = 0; + buf->alignment = alignment; + buf->element_alignment = 0; + + DEBUG_MEMORY_INIT((uint64) buf->memory, buf->size); +} + inline void buffer_reset(BufferMemory* buf) { @@ -88,4 +104,53 @@ byte* buffer_get_memory(BufferMemory* buf, uint64 size, int32 aligned = 0, bool return offset; } +inline +int64 buffer_dump(const BufferMemory* buf, byte* data) +{ + byte* start = data; + + // Size + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(buf->size); + data += sizeof(buf->size); + + // Pos + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(buf->pos); + data += sizeof(buf->pos); + + // Alignment + *((int32 *) data) = SWAP_ENDIAN_LITTLE(buf->alignment); + data += sizeof(buf->alignment); + + *((int32 *) data) = SWAP_ENDIAN_LITTLE(buf->element_alignment); + data += sizeof(buf->element_alignment); + + // All memory is handled in the buffer -> simply copy the buffer + memcpy(data, buf->memory, buf->size); + data += buf->size; + + return data - start; +} + +inline +int64 buffer_load(BufferMemory* buf, const byte* data) +{ + // Size + buf->size = SWAP_ENDIAN_LITTLE(*((uint64 *) data)); + data += sizeof(buf->size); + + // Pos + buf->pos = SWAP_ENDIAN_LITTLE(*((uint64 *) data)); + data += sizeof(buf->pos); + + // Alignment + buf->alignment = SWAP_ENDIAN_LITTLE(*((int32 *) data)); + data += sizeof(buf->alignment); + + buf->element_alignment = SWAP_ENDIAN_LITTLE(*((int32 *) data)); + data += sizeof(buf->element_alignment); + + memcpy(buf->memory, data, buf->size); + data += buf->size; +} + #endif \ No newline at end of file diff --git a/memory/ChunkMemory.h b/memory/ChunkMemory.h index 76c5640..3d49ac6 100644 --- a/memory/ChunkMemory.h +++ b/memory/ChunkMemory.h @@ -12,6 +12,7 @@ #include #include "../stdlib/Types.h" #include "../utils/MathUtils.h" +#include "../utils/EndianUtils.h" #include "Allocation.h" #include "../log/DebugMemory.h" @@ -37,10 +38,12 @@ void chunk_alloc(ChunkMemory* buf, uint64 count, uint64 chunk_size, int32 alignm : (byte *) playform_alloc_aligned(count * chunk_size + sizeof(buf->free) * CEIL_DIV(count, 64), alignment); buf->count = count; - buf->size = chunk_size + sizeof(buf->free) * CEIL_DIV(count, 64); + buf->size = count * chunk_size + sizeof(buf->free) * CEIL_DIV(count, 64); buf->chunk_size = chunk_size; buf->last_pos = -1; buf->alignment = alignment; + + // @question Could it be beneficial to have this before the element data? buf->free = (uint64 *) (buf->memory + count * chunk_size); DEBUG_MEMORY_INIT((uint64) buf->memory, buf->size); @@ -57,6 +60,24 @@ void chunk_free(ChunkMemory* buf) } } +inline +void chunk_init(ChunkMemory* buf, byte* data, uint64 count, uint64 chunk_size, int32 alignment = 64) +{ + // @bug what if an alignment is defined? + buf->memory = data; + + buf->count = count; + buf->size = chunk_size + sizeof(buf->free) * CEIL_DIV(count, 64); + buf->chunk_size = chunk_size; + buf->last_pos = -1; + buf->alignment = alignment; + + // @question Could it be beneficial to have this before the element data? + buf->free = (uint64 *) (buf->memory + count * chunk_size); + + DEBUG_MEMORY_INIT((uint64) buf->memory, buf->size); +} + inline byte* chunk_get_element(ChunkMemory* buf, uint64 element, bool zeroed = false) { @@ -228,4 +249,66 @@ void chunk_free_element(ChunkMemory* buf, uint64 element) buf->free[byte_index] &= ~(1 << bit_index); } +inline +int64 chunk_dump(const ChunkMemory* buf, byte* data) +{ + byte* start = data; + + // Count + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(buf->count); + data += sizeof(buf->count); + + // Size + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(buf->size); + data += sizeof(buf->size); + + // Chunk Size + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(buf->chunk_size); + data += sizeof(buf->chunk_size); + + // Last pos + *((int64 *) data) = SWAP_ENDIAN_LITTLE(buf->last_pos); + data += sizeof(buf->last_pos); + + // Alignment + *((int32 *) data) = SWAP_ENDIAN_LITTLE(buf->alignment); + data += sizeof(buf->alignment); + + // All memory is handled in the buffer -> simply copy the buffer + // This also includes the free array + memcpy(data, buf->memory, buf->size); + data += buf->size; + + return data - start; +} + +inline +int64 chunk_load(ChunkMemory* buf, const byte* data) +{ + // Count + buf->count = SWAP_ENDIAN_LITTLE(*((uint64 *) data)); + data += sizeof(buf->count); + + // Size + buf->size = SWAP_ENDIAN_LITTLE(*((uint64 *) data)); + data += sizeof(buf->size); + + // Chunk Size + buf->chunk_size = SWAP_ENDIAN_LITTLE(*((uint64 *) data)); + data += sizeof(buf->chunk_size); + + // Last pos + buf->last_pos = SWAP_ENDIAN_LITTLE(*((int64 *) data)); + data += sizeof(buf->last_pos); + + // Alignment + buf->alignment = SWAP_ENDIAN_LITTLE(*((int32 *) data)); + data += sizeof(buf->alignment); + + memcpy(buf->memory, data, buf->size); + data += buf->size; + + buf->free = (uint64 *) (buf->memory + buf->count * buf->chunk_size); +} + #endif \ No newline at end of file diff --git a/memory/RingMemory.h b/memory/RingMemory.h index 96479e5..34408e6 100644 --- a/memory/RingMemory.h +++ b/memory/RingMemory.h @@ -13,6 +13,7 @@ #include "../stdlib/Types.h" #include "../utils/MathUtils.h" +#include "../utils/EndianUtils.h" #include "../utils/TestUtils.h" #include "Allocation.h" @@ -49,6 +50,7 @@ void ring_alloc(RingMemory* ring, uint64 size, int32 alignment = 64) ring->size = size; ring->pos = 0; ring->alignment = alignment; + ring->element_alignment = 0; ring->start = 0; ring->end = 0; @@ -56,13 +58,30 @@ void ring_alloc(RingMemory* ring, uint64 size, int32 alignment = 64) } inline -void ring_create(RingMemory* ring, BufferMemory* buf, uint64 size, int32 alignment = 64) +void ring_init(RingMemory* ring, BufferMemory* buf, uint64 size, int32 alignment = 64) { ring->memory = buffer_get_memory(buf, size, alignment); ring->size = size; ring->pos = 0; ring->alignment = alignment; + ring->element_alignment = 0; + ring->start = 0; + ring->end = 0; + + DEBUG_MEMORY_INIT((uint64) ring->memory, ring->size); +} + +inline +void ring_init(RingMemory* ring, byte* buf, uint64 size, int32 alignment = 64) +{ + // @bug what if an alignment is defined? + ring->memory = buf; + + ring->size = size; + ring->pos = 0; + ring->alignment = alignment; + ring->element_alignment = 0; ring->start = 0; ring->end = 0; @@ -88,9 +107,7 @@ uint64 ring_calculate_position(const RingMemory* ring, uint64 pos, uint64 size, if (aligned) { uintptr_t address = (uintptr_t) ring->memory; - int64 adjustment = (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; - - pos += adjustment; + pos += (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; } size = ROUND_TO_NEAREST(size, aligned); @@ -99,9 +116,7 @@ uint64 ring_calculate_position(const RingMemory* ring, uint64 pos, uint64 size, if (aligned > 1) { uintptr_t address = (uintptr_t) ring->memory; - int64 adjustment = (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; - - pos += adjustment; + pos += (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; } } @@ -125,9 +140,7 @@ byte* ring_get_memory(RingMemory* ring, uint64 size, byte aligned = 0, bool zero if (aligned > 1) { uintptr_t address = (uintptr_t) ring->memory; - int64 adjustment = (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; - - ring->pos += adjustment; + ring->pos += (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; } size = ROUND_TO_NEAREST(size, aligned); @@ -136,9 +149,7 @@ byte* ring_get_memory(RingMemory* ring, uint64 size, byte aligned = 0, bool zero if (aligned > 1) { uintptr_t address = (uintptr_t) ring->memory; - int64 adjustment = (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; - - ring->pos += adjustment; + ring->pos += (aligned - ((address + ring->pos) & (aligned - 1))) % aligned; } } @@ -183,4 +194,38 @@ bool ring_commit_safe(const RingMemory* ring, uint64 size, byte aligned = 0) : pos < ring->start; } +inline +int64 ring_dump(const RingMemory* ring, byte* data) +{ + byte* start = data; + + // Size + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(ring->size); + data += sizeof(ring->size); + + // Pos + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(ring->pos); + data += sizeof(ring->pos); + + // Alignment + *((int32 *) data) = SWAP_ENDIAN_LITTLE(ring->alignment); + data += sizeof(ring->alignment); + + *((int32 *) data) = SWAP_ENDIAN_LITTLE(ring->element_alignment); + data += sizeof(ring->element_alignment); + + // Start/End + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(ring->start); + data += sizeof(ring->start); + + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(ring->end); + data += sizeof(ring->end); + + // All memory is handled in the buffer -> simply copy the buffer + memcpy(data, ring->memory, ring->size); + data += ring->size; + + return data - start; +} + #endif \ No newline at end of file diff --git a/models/settings/setting_types.h b/models/settings/setting_types.h index fb9a256..41f5343 100644 --- a/models/settings/setting_types.h +++ b/models/settings/setting_types.h @@ -75,5 +75,7 @@ #define SETTING_TYPE_UNLIMITED 0x00 #define SETTING_UI_VISIBILITY_FPS 1 +#define SETTING_UI_VISIBILITY_DEBUG 2 +#define SETTING_UI_VISIBILITY_WIREFRAME 4 #endif \ No newline at end of file diff --git a/object/Object.h b/object/Object.h index bd8885c..64f0d16 100644 --- a/object/Object.h +++ b/object/Object.h @@ -60,13 +60,12 @@ void object_from_file_txt( uint32 temp_color_count = 0; while (*pos != '\0') { - while (*pos == ' ') { + while (*pos == ' ' || *pos == '\t' || *pos == '\n') { ++pos; } - if (*pos == '\n') { - ++pos; - continue; + if (*pos == '\0') { + break; } // Parse type @@ -373,8 +372,8 @@ int32 object_from_file( byte* pos = file.content; // Read version - //mesh->version = *((int32 *) pos); - //pos += sizeof(mesh->version); + mesh->version = *((int32 *) pos); + pos += sizeof(mesh->version); // Read base data mesh->vertex_type = *((int32 *) pos); @@ -552,7 +551,11 @@ void object_to_file( FileBody file; // Temporary file size for buffer - file.size = sizeof(mesh) + sizeof(Vertex3D) * mesh->vertex_count + sizeof(f32) * 12 * mesh->vertex_count + 4096; + // @todo check the actual size, we are currently more or less guessing + file.size = sizeof(mesh) + + sizeof(Vertex3D) * mesh->vertex_count + + sizeof(f32) * 12 * mesh->vertex_count + + 4096; file.content = ring_get_memory(ring, file.size, 64); byte* pos = file.content; @@ -940,7 +943,7 @@ void object_to_file( SWAP_ENDIAN_LITTLE_SIMD( (int32 *) file.content, (int32 *) file.content, - (pos - file.content) / 4, // everything in here is 4 bytes -> super easy to swap + file.size / 4, // everything in here is 4 bytes -> super easy to swap steps ); diff --git a/platform/win32/Window.h b/platform/win32/Window.h index 24fdb20..49605ec 100644 --- a/platform/win32/Window.h +++ b/platform/win32/Window.h @@ -16,8 +16,8 @@ struct WindowState { uint16 width; uint16 height; - int32 x; - int32 y; + uint16 x; + uint16 y; uint64 style; }; @@ -30,8 +30,8 @@ struct Window { uint16 width; uint16 height; - int32 x; - int32 y; + uint16 x; + uint16 y; // 1. position // 2. focus diff --git a/stdlib/HashMap.h b/stdlib/HashMap.h index 28e2b48..fc7fdb0 100644 --- a/stdlib/HashMap.h +++ b/stdlib/HashMap.h @@ -74,48 +74,49 @@ struct HashMap { // WARNING: element_size = element size + remaining HashEntry data size void hashmap_create(HashMap* hm, int32 count, int32 element_size, RingMemory* ring) { - hm->table = (void **) ring_get_memory(ring, count * sizeof(void *)); + byte* data = ring_get_memory( + ring, + count * (sizeof(void *) + element_size) + + CEIL_DIV(count, 64) * sizeof(hm->buf.free) + ); - hm->buf.memory = ring_get_memory(ring, count * element_size); - hm->buf.free = (uint64 *) ring_get_memory(ring, CEIL_DIV(count, 64) * sizeof(hm->buf.free)); - hm->buf.count = count; - hm->buf.chunk_size = element_size; - hm->buf.last_pos = -1; - hm->buf.alignment = 1; + hm->table = (void **) data; + chunk_init(&hm->buf, data + sizeof(void *) * count, count, element_size, 1); } // WARNING: element_size = element size + remaining HashEntry data size void hashmap_create(HashMap* hm, int32 count, int32 element_size, BufferMemory* buf) { - hm->table = (void **) buffer_get_memory(buf, count * sizeof(void *)); + byte* data = buffer_get_memory( + buf, + count * (sizeof(void *) + element_size) + + CEIL_DIV(count, 64) * sizeof(hm->buf.free) + ); - hm->buf.memory = buffer_get_memory(buf, count * element_size); - hm->buf.free = (uint64 *) buffer_get_memory(buf, CEIL_DIV(count, 64) * sizeof(hm->buf.free)); - hm->buf.count = count; - hm->buf.chunk_size = element_size; - hm->buf.last_pos = -1; - hm->buf.alignment = 1; -} - -inline -int64 hashmap_get_buffer_size(int count, int32 element_size) -{ - return sizeof(void *) * count // table - + count * element_size // elements - + sizeof(uint64) * CEIL_DIV(count, 64); // free + hm->table = (void **) data; + chunk_init(&hm->buf, data + sizeof(void *) * count, count, element_size, 1); } // WARNING: element_size = element size + remaining HashEntry data size void hashmap_create(HashMap* hm, int32 count, int32 element_size, byte* buf) { hm->table = (void **) buf; + chunk_init(&hm->buf, buf + sizeof(void *) * count, count, element_size, 1); +} - hm->buf.memory = buf + sizeof(void *) * count; - hm->buf.free = (uint64 *) (hm->buf.memory + count * element_size); - hm->buf.count = count; - hm->buf.chunk_size = element_size; - hm->buf.last_pos = -1; - hm->buf.alignment = 1; +// Calculates how large a hashmap will be +inline +int64 hashmap_size(int count, int32 element_size) +{ + return count * sizeof(element_size) // table + + count * element_size // elements + + sizeof(uint64) * CEIL_DIV(count, 64); // free +} + +inline +int64 hashmap_size(const HashMap* hm) +{ + return hm->buf.count * sizeof(hm->table) + hm->buf.size; } void hashmap_insert(HashMap* hm, const char* key, int32 value) { @@ -243,6 +244,23 @@ HashEntry* hashmap_get_entry(HashMap* hm, const char* key) { return NULL; } +// This function only saves one step (omission of the hash function) +// The reason for this is in some cases we can use compile time hashing +HashEntry* hashmap_get_entry(HashMap* hm, const char* key, uint64 index) { + index %= hm->buf.count; + HashEntry* entry = (HashEntry *) hm->table[index]; + + while (entry != NULL) { + if (strncmp(entry->key, key, MAX_KEY_LENGTH) == 0) { + return entry; + } + + entry = (HashEntry *) entry->next; + } + + return NULL; +} + void hashmap_delete_entry(HashMap* hm, const char* key) { uint64 index = hash_djb2(key); HashEntry* entry = (HashEntry *) hm->table[index]; @@ -266,4 +284,90 @@ void hashmap_delete_entry(HashMap* hm, const char* key) { } } +// @bug We cannot know if the data needs endian swap (it coult be int/float, but also some other 4/8 byte value) +// -> if we save this to a file and load it on a different system we will have "corrupt" data +inline +int64 hashmap_dump(const HashMap* hm, byte* data) +{ + *((uint64 *) data) = SWAP_ENDIAN_LITTLE(hm->buf.count); + data += sizeof(uint64); + + uint64 next_count_total = 0; + + // Dump the table content where the elements are relative indeces/pointers + for (int32 i = 0; i < hm->buf.count; ++i) { + *((uint64 *) data) = SWAP_ENDIAN_LITTLE((uintptr_t) hm->table[i] - (uintptr_t) hm->buf.memory); + data += sizeof(uint64); + + // Also dump the next pointer + // Count how many next elements we have + HashEntry* entry = ((HashEntry *) hm->table[i])->next; + int32 next_count = 0; + while (entry) { + ++next_count; + entry = entry->next; + } + + next_count_total += next_count; + + *((int32 *) data) = SWAP_ENDIAN_LITTLE(next_count); + data += sizeof(next_count); + + if (next_count > 0) { + entry = ((HashEntry *) hm->table[i])->next; + while (entry) { + *((uint64 *) data) = SWAP_ENDIAN_LITTLE((uintptr_t) entry - (uintptr_t) hm->buf.memory); + data += sizeof(uint64); + + entry = entry->next; + } + } + } + + // @performance chunk_dump() below contains some data we already output above + // (next pointer but it is useless, since we need relative positions) + // Maybe we should manually re-create the chunk_dump here and omit the already dumped data for the next pointer? + + // How many bytes were written (+ dump the chunk memory) + return sizeof(hm->buf.count) + + hm->buf.count * sizeof(uint64) // table content + + hm->buf.count * sizeof(int32) // counter for the next pointer (one for every element) + + next_count_total * sizeof(uint64) // next pointer offset + + chunk_dump(&hm->buf, data); +} + +inline +int64 hashmap_load(HashMap* hm, const byte* data) +{ + uint64 count = SWAP_ENDIAN_LITTLE(*((uint64 *) data)); + data += sizeof(uint64); + + uint64 next_count_total = 0; + + // Load the table content, we also need to convert from relative indeces to pointers + for (int i = 0; i < count; ++i) { + hm->table[i] = hm->buf.memory + SWAP_ENDIAN_LITTLE(*((uint64 *) data)); + data += sizeof(uint64); + + // Also load the next pointer + // Count how many next elements we have + int32 next_count = SWAP_ENDIAN_LITTLE(*((int32 *) data)); + data += sizeof(next_count); + + HashEntry* entry = ((HashEntry *) hm->table[i]); + for (int32 j = 0; j < next_count; ++j) { + entry->next = (HashEntry *) (hm->buf.memory + SWAP_ENDIAN_LITTLE(*((uint64 *) data))); + data += sizeof(uint64); + entry = entry->next; + } + } + + // How many bytes was read from data + return sizeof(count) + + hm->buf.count * sizeof(uint64) // table content + + hm->buf.count * sizeof(int32) // counter for the next pointer (one for every element) + + next_count_total * sizeof(uint64) // next pointer offset + + chunk_load(&hm->buf, data); +} + #endif \ No newline at end of file diff --git a/ui/UIAnchor.h b/ui/UIAnchor.h deleted file mode 100644 index a95b41e..0000000 --- a/ui/UIAnchor.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef TOS_UI_ANCHOR_POINT_H -#define TOS_UI_ANCHOR_POINT_H - -enum UIAnchor { - UI_ANCHOR_GLOBAL, - UI_ANCHOR_PANEL, -}; - -#endif \ No newline at end of file diff --git a/ui/UIAttribute.h b/ui/UIAttribute.h index 0c9dbd1..f88c38a 100644 --- a/ui/UIAttribute.h +++ b/ui/UIAttribute.h @@ -4,7 +4,7 @@ #include "../stdlib/Types.h" struct UIAttribute { - // Attributes use ids instead of strings + // Attributes use ids (=type name) instead of strings int32 attribute_id; union { @@ -16,11 +16,19 @@ struct UIAttribute { }; struct UIAttributeGroup { - int32 attribute_count; + int32 attribute_size; UIAttribute* attributes; }; enum UIAttributeType { + UI_ATTRIBUTE_TYPE_TYPE, + UI_ATTRIBUTE_TYPE_STYLE, + + UI_ATTRIBUTE_TYPE_CONTENT, + UI_ATTRIBUTE_TYPE_CONTENT_ALIGN_H, + UI_ATTRIBUTE_TYPE_CONTENT_ALIGN_V, + + UI_ATTRIBUTE_TYPE_FONT_COLOR_INDEX, UI_ATTRIBUTE_TYPE_FONT_COLOR, UI_ATTRIBUTE_TYPE_FONT_SIZE, UI_ATTRIBUTE_TYPE_FONT_WEIGHT, @@ -31,6 +39,14 @@ enum UIAttributeType { UI_ATTRIBUTE_TYPE_ZINDEX, + UI_ATTRIBUTE_TYPE_POSITION_X, + UI_ATTRIBUTE_TYPE_POSITION_Y, + UI_ATTRIBUTE_TYPE_PARENT, + + UI_ATTRIBUTE_TYPE_DIMENSION_WIDTH, + UI_ATTRIBUTE_TYPE_DIMENSION_HEIGHT, + + UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR_INDEX, UI_ATTRIBUTE_TYPE_BACKGROUND_COLOR, UI_ATTRIBUTE_TYPE_BACKGROUND_IMG, UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_OPACITY, @@ -38,6 +54,7 @@ enum UIAttributeType { UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_POSITION_H, UI_ATTRIBUTE_TYPE_BACKGROUND_IMG_STYLE, + UI_ATTRIBUTE_TYPE_BORDER_COLOR_INDEX, UI_ATTRIBUTE_TYPE_BORDER_COLOR, UI_ATTRIBUTE_TYPE_BORDER_WIDTH, UI_ATTRIBUTE_TYPE_BORDER_TOP_COLOR, @@ -55,23 +72,43 @@ enum UIAttributeType { UI_ATTRIBUTE_TYPE_PADDING_BOTTOM, UI_ATTRIBUTE_TYPE_PADDING_LEFT, + UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR_INDEX, UI_ATTRIBUTE_TYPE_SHADOW_INNER_COLOR, UI_ATTRIBUTE_TYPE_SHADOW_INNER_ANGLE, UI_ATTRIBUTE_TYPE_SHADOW_INNER_DISTANCE, + UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR_INDEX, UI_ATTRIBUTE_TYPE_SHADOW_OUTER_COLOR, UI_ATTRIBUTE_TYPE_SHADOW_OUTER_ANGLE, UI_ATTRIBUTE_TYPE_SHADOW_OUTER_DISTANCE, + // @todo This isn't enough, we can have many animations (position, size, colors, ...) + // Maybe we need to define an animation child which overwrites the defined values + // Maybe it should use the same system as state dependent values like hover, active, ... UI_ATTRIBUTE_TYPE_TRANSITION_ANIMATION, UI_ATTRIBUTE_TYPE_TRANSITION_DURATION, UI_ATTRIBUTE_TYPE_SIZE, }; +UIAttribute* ui_attribute_from_group(UIAttributeGroup* group, UIAttributeType type) +{ + for (int i = 0; i < UI_ATTRIBUTE_TYPE_SIZE && i <= type; ++i) { + if (group->attributes[i].attribute_id == type) { + return &group->attributes[i]; + } + } + + return NULL; +} + constexpr const char* ui_attribute_type_to_string(int32 e) { switch (e) { + case UI_ATTRIBUTE_TYPE_TYPE: + return "type"; + case UI_ATTRIBUTE_TYPE_STYLE: + return "style"; case UI_ATTRIBUTE_TYPE_FONT_COLOR: return "font_color"; case UI_ATTRIBUTE_TYPE_FONT_SIZE: diff --git a/ui/UIButton.h b/ui/UIButton.h deleted file mode 100644 index 9aa1c0a..0000000 --- a/ui/UIButton.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef TOS_UI_BUTTON_H -#define TOS_UI_BUTTON_H - -#include "UIElement.h" -#include "UILayout.h" - -#include "../animation/AnimationEaseType.h" - -enum ButtonState { - BUTTON_STATE_DEFAULT, - BUTTON_STATE_HOVER, - BUTTON_STATE_CLICK -}; - -struct UIButton { - UIElement element; - - ButtonState state_old; - ButtonState state_new; - - // Is pointer becuase we probably want to have a global layout style that we re-use over and over - UILayout* layout_default; - - UILayout* layout_hover; - f32 layout_hover_anim_duration; - AnimationEaseType layout_hover_anim_style; - int hover_sound; - - UILayout* layout_click; - f32 layout_click_anim_duration; - AnimationEaseType layout_click_anim_style; - int click_sound; -}; - -#endif \ No newline at end of file diff --git a/ui/UIElement.h b/ui/UIElement.h index 4aca7a5..643c42f 100644 --- a/ui/UIElement.h +++ b/ui/UIElement.h @@ -1,10 +1,9 @@ #ifndef TOS_UI_ELEMENT_H #define TOS_UI_ELEMENT_H -#include "UIElementType.h" -#include "UIAlignment.h" -#include "UIAnchor.h" #include "../stdlib/Types.h" +#include "UIElementType.h" +#include "../object/Vertex.h" struct UIElementDimension { int16 x1; @@ -13,27 +12,37 @@ struct UIElementDimension { int16 y2; }; +#define UI_ELEMENT_STATE_VISIBLE 1 +#define UI_ELEMENT_STATE_ACTIVE 2 +#define UI_ELEMENT_STATE_FOCUSED 4 +#define UI_ELEMENT_STATE_CLICKED 8 +#define UI_ELEMENT_STATE_ANIMATION 16 + struct UIElement { - int id; + const char* name; + int32 id; UIElementType type; - int window_id; - int panel_id; + int16 window_id; + int16 panel_id; UIElementDimension dimension; - UIAlignH align_h; - UIAlignV align_v; - UIAnchor anchor; + int32 state_flag; - bool is_visible; - bool is_active; - bool is_focused; + f32 anim_elapsed; int16 scroll_x; int16 scroll_y; // @todo animation state + + // @todo cache vertex result for default, hover etc. + int32 vertex_count; + Vertex3DTextureColorIndex* vertices; // WARNING: This is not the official holder of the memory, its in UILayout + + // @todo We could even have a pointer that points into the complete ui array making it possible to simply replace this section + // This is something we wanted to do anyway when updating sub regions on the gpu memory }; #endif \ No newline at end of file diff --git a/ui/UIImage.h b/ui/UIImage.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UILayout.h b/ui/UILayout.h index 09d46ab..00657b6 100644 --- a/ui/UILayout.h +++ b/ui/UILayout.h @@ -2,22 +2,35 @@ #define TOS_UI_LAYOUT_H #include "../stdlib/Types.h" - +#include "../stdlib/HashMap.h" #include "UIElement.h" -#include "UILocation.h" +// Modified for every scene struct UILayout { - int32 ui_deadzone_count = 5; + int32 ui_deadzone_size = 5; UIElementDimension ui_deadzone[5]; - int32 element_hoverable_count; + int32 element_hoverable_size; + int32 element_hoverable_pos; UIElementDimension* elements_hoverable; - int32 element_interactible_count; + int32 element_interactible_size; + int32 element_interactible_pos; UIElementDimension* elements_interactible; - int32 element_count; - UIElement* element; + // @question Since we use a hashmap below, do we even need the size? + // Isn't the size exactly the same as the hash_map buf size + int32 element_size; + int32 element_pos; + HashMap hash_map; // Used to directly find element by name + + // @question Do we even need this or should the hashmap values be the elements directly? + // In other places (e.g. theme) we simply define a byte* data variable which actually holds the info. + UIElement* elements; + + int32 vertex_size; + int32 vertex_pos; + Vertex3DTextureColorIndex* vertices; }; #endif \ No newline at end of file diff --git a/ui/UILink.h b/ui/UILink.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UILocation.h b/ui/UILocation.h deleted file mode 100644 index 1ff9b90..0000000 --- a/ui/UILocation.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef TOS_UI_LOCATION_H -#define TOS_UI_LOCATION_H - -enum UILocation { - UI_LOCATION_LEFT, - UI_LOCATION_CENTER, - UI_LOCATION_RIGHT, - - UI_LOCATION_FLEX_ROW, - UI_LOCATION_FLEX_COL -}; - -#endif \ No newline at end of file diff --git a/ui/UIPanel.h b/ui/UIPanel.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UIPosition.h b/ui/UIPosition.h deleted file mode 100644 index c3692fa..0000000 --- a/ui/UIPosition.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef TOS_UI_POSITION_H -#define TOS_UI_POSITION_H - -enum UIPosition { - UI_POSITION_RELATIVE, - UI_POSITION_ABSOLUTE -}; - -#endif \ No newline at end of file diff --git a/ui/UISelect.h b/ui/UISelect.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UITable.h b/ui/UITable.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UIText.h b/ui/UIText.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UITextarea.h b/ui/UITextarea.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UITextfield.h b/ui/UITextfield.h deleted file mode 100644 index e69de29..0000000 diff --git a/ui/UITheme.h b/ui/UITheme.h index c663434..0f69cdd 100644 --- a/ui/UITheme.h +++ b/ui/UITheme.h @@ -18,35 +18,87 @@ #define UI_THEME_VERSION 1 -struct UITheme { +// @question Currently there is some data duplication in here and in the UIElement. +// Not sure if this is how we want this to be or if we want to change this in the future +// Modified for every scene +// WARNING: Make sure the order of this struct and UITheme is the same for the first elements +// This allows us to cast between both +struct UIThemeStyle { byte* data; int32 version; - char name[32]; - bool is_active; - - // Every element has all the style attributes - UIAttribute styles_global[UI_ELEMENT_TYPE_SIZE * UI_ATTRIBUTE_TYPE_SIZE]; // A theme may have N named styles - // @todo We should probably create a hashmap - // key = name - // value = pointer to group (in the file we store the offset when we load it into memory convert it to pointer) + // The hashmap contains the offset where the respective style can be found HashMap hash_map; +}; - int32 style_group_count; - UIAttributeGroup* style_groups; +// General theme for all scenes +struct UITheme { + // This one usually remains unchanged unless someone changes the theme + UIThemeStyle ui_general; + // This one is scene specific and is loaded with the scene + // We have a pointer that references the currently active scene + // The other two elements contain the actual data. + // This allows us to easily pre-fetch scene styles and the pointer allows us to easily switch between them + // When loading a new scene we simply use the style that is currently not pointed to by primary_scene + UIThemeStyle* primary_scene; + UIThemeStyle ui_scene1; + UIThemeStyle ui_scene2; + + // @question This basically means we only support 1 font for the UI ?! + // This is probably something to re-consider Font font; // @todo add cursor styles + // @todo what about ui audio? + + char name[32]; }; +// @performance Consider to replace HashEntryInt64 with HashEntryVoidP. +// This way we wouldn't have to do theme->data + entry->value and could just use entry->value +// Of course this means during the saving and loading we need to convert to and from offsets +// The problem is that the actual dumping and loading for that part doesn't happen in the hashmap but in the chunk_memory +// The chunk_memory doesn't know how the value look like -> cannot do the conversion +inline +UIAttributeGroup* theme_style_group(UIThemeStyle* theme, const char* group_name) +{ + HashEntryInt64* entry = (HashEntryInt64 *) hashmap_get_entry(&theme->hash_map, group_name); + return (UIAttributeGroup *) (theme->data + entry->value); +} + +inline +UIAttributeGroup* theme_style_group(UIThemeStyle* theme, const char* group_name, int32 group_id) +{ + HashEntryInt64* entry = (HashEntryInt64 *) hashmap_get_entry(&theme->hash_map, group_name, group_id); + return (UIAttributeGroup *) (theme->data + entry->value); +} + +int compare_by_attribute_id(const void* a, const void* b) { + UIAttribute* attr_a = (UIAttribute *) a; + UIAttribute* attr_b = (UIAttribute *) b; + + return attr_a->attribute_id - attr_b->attribute_id; +} + +// File layout - text +// version +// #group_name +// attributes ... +// attributes ... +// attributes ... +// #group_name +// attributes ... +// attributes ... +// attributes ... + // WARNING: theme needs to have memory already reserved and asigned to data void theme_from_file_txt( RingMemory* ring, const char* path, - UITheme* theme + UIThemeStyle* theme ) { FileBody file; file_read(path, &file, ring); @@ -59,6 +111,39 @@ void theme_from_file_txt( char attribute_name[32]; bool last_token_newline = false; + // We have to find how many groups are defined in the theme file. + // Therefore we have to do an initial iteration + int32 temp_group_count = 0; + while (*pos != '\0') { + // Skip all white spaces + while (*pos == ' ' || *pos == '\t' || *pos == '\n') { + ++pos; + } + + // Is group name + if (*pos == '#' || *pos == '.') { + ++temp_group_count; + } + + // Go to the end of the line + while (*pos != '\n' && *pos != '\0') { + ++pos; + } + + // Go to next line + if (*pos != '\0') { + ++pos; + } + } + + // @performance This is probably horrible since we are not using a perfect hashing function (1 hash -> 1 index) + // I wouldn't be surprised if we have a 50% hash overlap (2 hashes -> 1 index) + hashmap_create(&theme->hash_map, temp_group_count, sizeof(HashEntryInt64), theme->data); + int64 data_offset = hashmap_size(&theme->hash_map); + + UIAttributeGroup* temp_group = NULL; + + pos = (char *) file.content; while (*pos != '\0') { while (*pos == ' ' || *pos == '\t') { ++pos; @@ -90,9 +175,21 @@ void theme_from_file_txt( block_name[i] = '\0'; - if (*block_name == '#') { + // All blocks need to start with #. In the past this wasn't the case and may not be in the future. This is why we keep this if here. + if (*block_name == '#' || *block_name == '.') { // Named style - // @todo create new style + if (temp_group != NULL) { + // Before we insert a new group we have to sort the attributes + // since this makes searching them later on more efficient. + qsort(temp_group->attributes, temp_group->attribute_size, sizeof(UIAttribute), compare_by_attribute_id); + } + + // Insert new group + hashmap_insert(&theme->hash_map, block_name, data_offset); + + temp_group = (UIAttributeGroup *) (theme->data + data_offset); + temp_group->attribute_size = 0; + data_offset += sizeof(temp_group->attribute_size); } continue; @@ -115,8 +212,37 @@ void theme_from_file_txt( ASSERT_SIMPLE((*pos != '\0' && *pos != '\n')); // Handle different attribute types - UIAttribute attribute; - if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_FONT_COLOR), attribute_name) == 0) { + UIAttribute attribute = {}; + if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_TYPE), attribute_name) == 0) { + attribute.attribute_id = UI_ATTRIBUTE_TYPE_TYPE; + + char str[32]; + char* temp = str; + while (*pos != '\n' && *pos != '\0') { + *temp++ = *pos++; + } + + *temp = '\0'; + + for (int32 i = 0; i < UI_ELEMENT_TYPE_SIZE; ++i) { + if (strcmp(str, ui_attribute_type_to_string(i)) == 0) { + attribute.value_int = i; + break; + } + } + + ++pos; + } else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_STYLE), attribute_name) == 0) { + attribute.attribute_id = UI_ATTRIBUTE_TYPE_STYLE; + + char* temp = attribute.value_str; + while (*pos != '\n' && *pos != '\0') { + *temp++ = *pos++; + } + + *temp = '\0'; + ++pos; + } else if (strcmp(ui_attribute_type_to_string(UI_ATTRIBUTE_TYPE_FONT_COLOR), attribute_name) == 0) { ++pos; // Skip '#' attribute.attribute_id = UI_ATTRIBUTE_TYPE_FONT_COLOR; @@ -306,47 +432,131 @@ void theme_from_file_txt( continue; } + // Again, currently this if check is redundant but it wasn't in the past and we may need it again in the future. if (block_name[0] == '#') { // Named block - } else { - // Global default elements and their attributes - int32 element_type = -1; - for (int j = 0; j < UI_ELEMENT_TYPE_SIZE; ++j) { - if (strcmp(ui_element_type_to_string((UIElementType) j), block_name) == 0) { - element_type = j; - break; - } - } - - if (element_type < 0) { - continue; - } - memcpy( - theme->styles_global + element_type * UI_ATTRIBUTE_TYPE_SIZE, + temp_group->attributes + temp_group->attribute_size, &attribute, sizeof(attribute) ); + + data_offset += sizeof(attribute); + ++temp_group->attribute_size; } } + + // We still need to sort the last group + qsort(temp_group->attributes, temp_group->attribute_size, sizeof(UIAttribute), compare_by_attribute_id); +} + +// Memory layout (data) - This is not the layout of the file itself, just how we represent it in memory +// Hashmap +// UIAttributeGroup - This is where the pointers point to (or what the offset represents) +// size +// Attributes ... +// Attributes ... +// Attributes ... +// UIAttributeGroup +// size +// Attributes ... +// Attributes ... +// Attributes ... + +// The size of theme->data should be the file size. +// Yes, this means we have a little too much data but not by a lot +void theme_from_file( + UIThemeStyle* theme, + const byte* data +) { + const byte* pos = data; + + theme->version = *((int32 *) pos); + pos += sizeof(theme->version); + + // Prepare hashmap (incl. reserve memory) by initializing it the same way we originally did + // Of course we still need to populate the data using hashmap_load() + hashmap_create(&theme->hash_map, SWAP_ENDIAN_LITTLE(*((uint64 *) pos)), sizeof(HashEntryInt64), theme->data); + pos += hashmap_load(&theme->hash_map, pos); + + // theme data + // Layout: first load the size of the group, then load the individual attributes + for (int32 i = 0; i < theme->hash_map.buf.count; ++i) { + HashEntryInt64* entry = (HashEntryInt64 *) theme->hash_map.table[i]; + UIAttributeGroup* group = (UIAttributeGroup *) (theme->data + entry->value); + + group->attribute_size = SWAP_ENDIAN_LITTLE(*((int32 *) pos)); + pos += sizeof(group->attribute_size); + + // @performance The UIAttribute contains a char array which makes this WAY larger than it needs to be in 99% of the cases + memcpy(group->attributes, pos, group->attribute_size * sizeof(UIAttribute)); + pos += group->attribute_size * sizeof(UIAttribute); + } } -void theme_from_file( - RingMemory* ring, - const char* path, - UITheme* theme -) { - // version - // global definitions - // names count - // +// Calculates the maximum theme size +// Not every group has all the attributes (most likely only a small subset) +// However, an accurate calculation is probably too slow and not needed most of the time +inline +int64 theme_size(const UIThemeStyle* theme) +{ + return hashmap_size(&theme->hash_map) + + theme->hash_map.buf.count * UI_ATTRIBUTE_TYPE_SIZE * sizeof(UIAttribute); } -void theme_to_file(RingMemory* ring, - const char* path, - const UITheme* theme -) { +// File layout - binary +// version +// hashmap size +// Hashmap (includes offsets to the individual groups) +// #group_name (this line doesn't really exist in file, it's more like the pointer offset in the hashmap) +// size +// attributes ... +// attributes ... +// attributes ... +// #group_name +// size +// attributes ... +// attributes ... +// attributes ... +void theme_to_file( + RingMemory* ring, + const char* path, + const UIThemeStyle* theme +) { + FileBody file; + + // Temporary file size for buffer + // @todo This is a bad placeholder, The problem is we don't know how much we actually need without stepping through the elements + // I also don't want to add a size variable to the theme as it is useless in all other cases + file.size = theme_size(theme); + + file.content = ring_get_memory(ring, file.size, 64); + byte* pos = file.content; + + // version + *((int32 *) pos) = SWAP_ENDIAN_LITTLE(theme->version); + pos += sizeof(theme->version); + + // hashmap + pos += hashmap_dump(&theme->hash_map, pos); + + // theme data + // Layout: first save the size of the group, then save the individual attributes + for (int32 i = 0; i < theme->hash_map.buf.count; ++i) { + HashEntryInt64* entry = (HashEntryInt64 *) theme->hash_map.table[i]; + UIAttributeGroup* group = (UIAttributeGroup *) (theme->data + entry->value); + + *((int32 *) pos) = SWAP_ENDIAN_LITTLE(group->attribute_size); + pos += sizeof(group->attribute_size); + + // @performance The UIAttribute contains a char array which makes this WAY larger than it needs to be in 99% of the cases + memcpy(pos, group->attributes, group->attribute_size * sizeof(UIAttribute)); + pos += group->attribute_size * sizeof(UIAttribute); + } + + file.size = pos - file.content; + file_write(path, &file); } #endif \ No newline at end of file diff --git a/ui/UIWindow.h b/ui/UIWindow.h deleted file mode 100644 index b9400bc..0000000 --- a/ui/UIWindow.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef TOS_UI_WINDOW_H -#define TOS_UI_WINDOW_H - -#include "UIElement.h" -#include "UILayout.h" - -#include "../animation/AnimationEaseType.h" - -enum WindowState { - WINDOW_STATE_ACIVE, - WINDOW_STATE_INACTIVE, - WINDOW_STATE_FOCUS, - - WINDOW_STATE_MOVING, - WINDOW_STATE_OPENING, - WINDOW_STATE_CLOSING -}; - -struct UIWindow { - UIElement element; - - WindowState state_old; - WindowState state_new; - - bool is_minimizable; - bool is_maximizable; - bool is_movable; - bool is_resizable; - - // window is only movable when holding this area - int32 movable_area[4]; - - UILayout* layout_default; - f32 layout_open_anim_duration; - f32 layout_close_anim_duration; - f32 layout_min_anim_duration; -}; - -#endif \ No newline at end of file diff --git a/utils/StringUtils.h b/utils/StringUtils.h index b056315..ac16a76 100644 --- a/utils/StringUtils.h +++ b/utils/StringUtils.h @@ -16,7 +16,7 @@ #include "../stdlib/Types.h" constexpr -size_t strlen_compile_time(const char* str) { +size_t strlen_const(const char* str) { size_t len = 0; while (str[len] != '\0') { ++len; @@ -259,6 +259,7 @@ str_concat( *dst = '\0'; } +inline char* strtok(char* str, const char* __restrict delim, char* *key) { char* result; if (str == NULL) { @@ -283,38 +284,42 @@ char* strtok(char* str, const char* __restrict delim, char* *key) { } inline -void format_number_render(int32 length, char* buffer, const char thousands = ',') -{ - int32 count = (int32) (length / 3) - (length % 3 == 0 ? 1 : 0); +int32 int_to_str(int64 number, char *str, const char thousands = ',') { + int32 i = 0; + int64 sign = number; + int32 digit_count = 0; - int32 j = -1; - for (int32 i = length; i > 0; --i) { - ++j; - - if (j % 3 == 0 && j != 0) { - buffer[i + count] = buffer[i]; - --count; - buffer[i + count] = thousands; - } else { - buffer[i + count] = buffer[i]; - } + if (number == 0) { + str[i++] = '0'; + } else if (number < 0) { + number = -number; } -} -char* format_number(size_t number, char* buffer, const char thousands = ',') -{ - int32 length = snprintf(buffer, 32, "%zu", number); - format_number_render(length, buffer, thousands); + while (number > 0) { + if (thousands + && (digit_count == 3 || digit_count == 6 || digit_count == 9 || digit_count == 12 || digit_count == 15) + ) { + str[i++] = thousands; + } - return buffer; -} + str[i++] = number % 10 + '0'; + number /= 10; + ++digit_count; + } -char* format_number(int32 number, char* buffer, const char thousands = ',') -{ - int32 length = snprintf(buffer, 32, "%i", number); - format_number_render(length, buffer, thousands); + if (sign < 0) { + str[i++] = '-'; + } - return buffer; + str[i] = '\0'; + + for (int32 j = 0, k = i - 1; j < k; ++j, --k) { + char temp = str[j]; + str[j] = str[k]; + str[k] = temp; + } + + return i - 1; } char toupper_ascii(char c)