basic file open optimizations
							parent
							
								
									774c8daaff
								
							
						
					
					
						commit
						dcdec287a1
					
				|  | @ -42,8 +42,8 @@ struct Font{ | |||
|     String name; | ||||
|     bool32 loaded; | ||||
|      | ||||
| 	Glyph_Data glyphs[128]; | ||||
|     stbtt_bakedchar chardata[128]; | ||||
| 	Glyph_Data glyphs[256]; | ||||
|     stbtt_bakedchar chardata[256]; | ||||
| 	i32 height, ascent, descent, line_skip; | ||||
|     i32 advance; | ||||
|     u32 tex; | ||||
|  |  | |||
|  | @ -27,6 +27,16 @@ buffer_stringify(Buffer_Type *buffer, int start, int end, char *out){ | |||
|     } | ||||
| } | ||||
| 
 | ||||
| inline_4tech void | ||||
| buffer_backify(Buffer_Type *buffer, int start, int end, char *out){ | ||||
|     for (Buffer_Backify_Type loop = buffer_backify_loop(buffer, end, start); | ||||
|          buffer_backify_good(&loop); | ||||
|          buffer_backify_next(&loop)){ | ||||
|         memcpy_4tech(out, loop.data, loop.size); | ||||
|         out += loop.size; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| internal_4tech int | ||||
| buffer_convert_out(Buffer_Type *buffer, char *dest, int max){ | ||||
|     Buffer_Stringify_Type loop; | ||||
|  | @ -480,9 +490,11 @@ typedef struct Buffer_Measure_Starts{ | |||
|     int i; | ||||
|     int count; | ||||
|     int start; | ||||
|     float width; | ||||
| } Buffer_Measure_Starts; | ||||
| #endif | ||||
| 
 | ||||
| #if 0 | ||||
| internal_4tech int | ||||
| buffer_measure_starts(Buffer_Measure_Starts *state, Buffer_Type *buffer){ | ||||
|     Buffer_Stringify_Type loop; | ||||
|  | @ -497,7 +509,7 @@ buffer_measure_starts(Buffer_Measure_Starts *state, Buffer_Type *buffer){ | |||
|     starts = buffer->line_starts; | ||||
|     max = buffer->line_max; | ||||
|      | ||||
|     result = 0; | ||||
|     result = 1; | ||||
|      | ||||
|     i = state->i; | ||||
|     count = state->count; | ||||
|  | @ -510,10 +522,7 @@ buffer_measure_starts(Buffer_Measure_Starts *state, Buffer_Type *buffer){ | |||
|         data = loop.data - loop.absolute_pos; | ||||
|         for (; i < end; ++i){ | ||||
|             if (data[i] == '\n'){ | ||||
|                 if (count == max){ | ||||
|                     result = 1; | ||||
|                     goto buffer_measure_starts_end; | ||||
|                 } | ||||
|                 if (count == max) goto buffer_measure_starts_end; | ||||
|                  | ||||
|                 starts[count++] = start; | ||||
|                 start = i + 1; | ||||
|  | @ -521,13 +530,11 @@ buffer_measure_starts(Buffer_Measure_Starts *state, Buffer_Type *buffer){ | |||
|         } | ||||
|     } | ||||
|      | ||||
|     if (i == size){ | ||||
|         if (count == max){ | ||||
|             result = 1; | ||||
|             goto buffer_measure_starts_end; | ||||
|         } | ||||
|         starts[count++] = start; | ||||
|     } | ||||
|     assert_4tech(i == size); | ||||
|      | ||||
|     if (count == max) goto buffer_measure_starts_end; | ||||
|     starts[count++] = start; | ||||
|     result = 0; | ||||
|      | ||||
| buffer_measure_starts_end: | ||||
|     state->i = i; | ||||
|  | @ -536,6 +543,75 @@ buffer_measure_starts_end: | |||
|      | ||||
|     return(result); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| internal_4tech int | ||||
| buffer_measure_starts_widths(Buffer_Measure_Starts *state, Buffer_Type *buffer, | ||||
|                              float *advance_data){ | ||||
|     Buffer_Stringify_Type loop; | ||||
|     int *start_ptr, *start_end; | ||||
|     float *width_ptr; | ||||
|     debug_4tech(int widths_max); | ||||
|     debug_4tech(int max); | ||||
|     char *data; | ||||
|     int size, end; | ||||
|     float width; | ||||
|     int start, i; | ||||
|     int result; | ||||
|     char ch; | ||||
|      | ||||
|     size = buffer_size(buffer); | ||||
|      | ||||
|     debug_4tech(max = buffer->line_max); | ||||
|     debug_4tech(widths_max = buffer->widths_max); | ||||
|     assert_4tech(max == widths_max); | ||||
|      | ||||
|     result = 1; | ||||
|      | ||||
|     i = state->i; | ||||
|     start = state->start; | ||||
|     width = state->width; | ||||
|      | ||||
|     start_ptr = buffer->line_starts + state->count; | ||||
|     width_ptr = buffer->line_widths + state->count; | ||||
|     start_end = buffer->line_starts + buffer->line_max; | ||||
|      | ||||
|     for (loop = buffer_stringify_loop(buffer, i, size); | ||||
|          buffer_stringify_good(&loop); | ||||
|          buffer_stringify_next(&loop)){ | ||||
|         end = loop.size + loop.absolute_pos; | ||||
|         data = loop.data - loop.absolute_pos; | ||||
|         for (; i < end; ++i){ | ||||
|             ch = data[i]; | ||||
|             if (ch == '\n'){ | ||||
|                 if (start_ptr == start_end) goto buffer_measure_starts_widths_end; | ||||
| 
 | ||||
|                 *width_ptr++ = width; | ||||
|                 *start_ptr++ = start; | ||||
|                 start = i + 1; | ||||
|                 width = 0; | ||||
|             } | ||||
|             else{ | ||||
|                 width += measure_character(advance_data, ch); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     assert_4tech(i == size); | ||||
|      | ||||
|     if (start_ptr == start_end) goto buffer_measure_starts_widths_end; | ||||
|     *start_ptr++ = start; | ||||
|     *width_ptr++ = 0; | ||||
|     result = 0; | ||||
|      | ||||
| buffer_measure_starts_widths_end: | ||||
|     state->i = i; | ||||
|     state->count = (int)(start_ptr - buffer->line_starts); | ||||
|     state->start = start; | ||||
|     state->width = width; | ||||
|      | ||||
|     return(result); | ||||
| } | ||||
| 
 | ||||
| internal_4tech void | ||||
| buffer_remeasure_starts(Buffer_Type *buffer, int line_start, int line_end, int line_shift, int text_shift){ | ||||
|  | @ -600,7 +676,7 @@ buffer_remeasure_starts_end: | |||
| } | ||||
| 
 | ||||
| internal_4tech void | ||||
| buffer_remeasure_widths(Buffer_Type *buffer, void *advance_data, int stride, | ||||
| buffer_remeasure_widths(Buffer_Type *buffer, float *advance_data, | ||||
|                         int line_start, int line_end, int line_shift){ | ||||
|     Buffer_Stringify_Type loop; | ||||
|     int *starts; | ||||
|  | @ -652,17 +728,19 @@ buffer_remeasure_widths(Buffer_Type *buffer, void *advance_data, int stride, | |||
|                 width = 0; | ||||
|             } | ||||
|             else{ | ||||
|                 width += measure_character(advance_data, stride, ch); | ||||
|                 width += measure_character(advance_data, ch); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #if 0 | ||||
| inline_4tech void | ||||
| buffer_measure_widths(Buffer_Type *buffer, void *advance_data, int stride){ | ||||
| buffer_measure_widths(Buffer_Type *buffer, void *advance_data){ | ||||
|     assert_4tech(buffer->line_count >= 1); | ||||
|     buffer_remeasure_widths(buffer, advance_data, stride, 0, buffer->line_count-1, 0); | ||||
|     buffer_remeasure_widths(buffer, advance_data, 0, buffer->line_count-1, 0); | ||||
| } | ||||
| #endif | ||||
| 
 | ||||
| internal_4tech void | ||||
| buffer_measure_wrap_y(Buffer_Type *buffer, float *wraps, | ||||
|  | @ -748,8 +826,8 @@ typedef struct Seek_State{ | |||
| } Seek_State; | ||||
| 
 | ||||
| internal_4tech int | ||||
| cursor_seek_step(Seek_State *state, Buffer_Seek seek, int xy_seek, float max_width, float font_height, | ||||
|                  char *advances, int stride, int size, char ch){ | ||||
| cursor_seek_step(Seek_State *state, Buffer_Seek seek, int xy_seek, float max_width, | ||||
|                  float font_height, float *advances, int size, char ch){ | ||||
|     Full_Cursor cursor, prev_cursor; | ||||
|     float ch_width; | ||||
|     int result; | ||||
|  | @ -772,8 +850,8 @@ cursor_seek_step(Seek_State *state, Buffer_Seek seek, int xy_seek, float max_wid | |||
|          | ||||
|     default: | ||||
|         ++cursor.character; | ||||
|         if (ch == '\r') ch_width = *(float*)(advances + stride * '\\') + *(float*)(advances + stride * 'r'); | ||||
|         else ch_width = *(float*)(advances + stride * ch); | ||||
|         if (ch == '\r') ch_width = *(float*)(advances + '\\') + *(float*)(advances + 'r'); | ||||
|         else ch_width = *(float*)(advances + ch); | ||||
|              | ||||
|         if (cursor.wrapped_x + ch_width >= max_width){ | ||||
|             cursor.wrapped_y += font_height; | ||||
|  | @ -855,8 +933,8 @@ cursor_seek_step_end: | |||
| #endif | ||||
| 
 | ||||
| internal_4tech Full_Cursor | ||||
| buffer_cursor_seek(Buffer_Type *buffer, Buffer_Seek seek, float max_width, float font_height, | ||||
|                    void *advance_data, int stride, Full_Cursor cursor){ | ||||
| buffer_cursor_seek(Buffer_Type *buffer, Buffer_Seek seek, float max_width, | ||||
|                    float font_height, float *advance_data, Full_Cursor cursor){ | ||||
|     Buffer_Stringify_Type loop; | ||||
|     char *data; | ||||
|     int size, end; | ||||
|  | @ -864,11 +942,9 @@ buffer_cursor_seek(Buffer_Type *buffer, Buffer_Seek seek, float max_width, float | |||
|     int result; | ||||
|      | ||||
|     Seek_State state; | ||||
|     char *advances; | ||||
|     int xy_seek; | ||||
| 
 | ||||
|     size = buffer_size(buffer); | ||||
|     advances = (char*)advance_data; | ||||
|     xy_seek = (seek.type == buffer_seek_wrapped_xy || seek.type == buffer_seek_unwrapped_xy); | ||||
|     state.cursor = cursor; | ||||
|      | ||||
|  | @ -880,14 +956,14 @@ buffer_cursor_seek(Buffer_Type *buffer, Buffer_Seek seek, float max_width, float | |||
|         end = loop.size + loop.absolute_pos; | ||||
|         data = loop.data - loop.absolute_pos; | ||||
|         for (; i < end; ++i){ | ||||
|             result = cursor_seek_step(&state, seek, xy_seek, max_width, font_height, | ||||
|                                       advances, stride, size, data[i]); | ||||
|             result = cursor_seek_step(&state, seek, xy_seek, max_width, | ||||
|                                       font_height, advance_data, size, data[i]); | ||||
|             if (!result) goto buffer_cursor_seek_end; | ||||
|         } | ||||
|     } | ||||
|     if (result){ | ||||
|         result = cursor_seek_step(&state, seek, xy_seek, max_width, font_height, | ||||
|                                   advances, stride, size, 0); | ||||
|         result = cursor_seek_step(&state, seek, xy_seek, max_width, | ||||
|                                   font_height, advance_data, size, 0); | ||||
|         assert_4tech(result == 0); | ||||
|     } | ||||
|      | ||||
|  | @ -897,21 +973,21 @@ buffer_cursor_seek_end: | |||
| 
 | ||||
| internal_4tech Full_Cursor | ||||
| buffer_cursor_from_pos(Buffer_Type *buffer, int pos, float *wraps, | ||||
|                        float max_width, float font_height, void *advance_data, int stride){ | ||||
|                        float max_width, float font_height, float *advance_data){ | ||||
|     Full_Cursor result; | ||||
|     int line_index; | ||||
| 
 | ||||
|     line_index = buffer_get_line_index_range(buffer, pos, 0, buffer->line_count); | ||||
|     result = make_cursor_hint(line_index, buffer->line_starts, wraps, font_height); | ||||
|     result = buffer_cursor_seek(buffer, seek_pos(pos), max_width, font_height, | ||||
|                                 advance_data, stride, result); | ||||
|                                 advance_data, result); | ||||
| 
 | ||||
|     return(result); | ||||
| } | ||||
| 
 | ||||
| internal_4tech Full_Cursor | ||||
| buffer_cursor_from_unwrapped_xy(Buffer_Type *buffer, float x, float y, int round_down, float *wraps, | ||||
|                                 float max_width, float font_height, void *advance_data, int stride){ | ||||
|                                 float max_width, float font_height, float *advance_data){ | ||||
|     Full_Cursor result; | ||||
|     int line_index; | ||||
| 
 | ||||
|  | @ -921,21 +997,21 @@ buffer_cursor_from_unwrapped_xy(Buffer_Type *buffer, float x, float y, int round | |||
| 
 | ||||
|     result = make_cursor_hint(line_index, buffer->line_starts, wraps, font_height); | ||||
|     result = buffer_cursor_seek(buffer, seek_unwrapped_xy(x, y, round_down), max_width, font_height, | ||||
|                                 advance_data, stride, result); | ||||
|                                 advance_data, result); | ||||
| 
 | ||||
|     return(result); | ||||
| } | ||||
| 
 | ||||
| internal_4tech Full_Cursor | ||||
| buffer_cursor_from_wrapped_xy(Buffer_Type *buffer, float x, float y, int round_down, float *wraps, | ||||
|                               float max_width, float font_height, void *advance_data, int stride){ | ||||
|                               float max_width, float font_height, float *advance_data){ | ||||
|     Full_Cursor result; | ||||
|     int line_index; | ||||
| 
 | ||||
|     line_index = buffer_get_line_index_from_wrapped_y(wraps, y, font_height, 0, buffer->line_count); | ||||
|     result = make_cursor_hint(line_index, buffer->line_starts, wraps, font_height); | ||||
|     result = buffer_cursor_seek(buffer, seek_wrapped_xy(x, y, round_down), max_width, font_height, | ||||
|                                 advance_data, stride, result); | ||||
|                                 advance_data, result); | ||||
| 
 | ||||
|     return(result); | ||||
| } | ||||
|  | @ -1007,7 +1083,7 @@ buffer_invert_batch(Buffer_Invert_Batch *state, Buffer_Type *buffer, Buffer_Edit | |||
| internal_4tech void | ||||
| buffer_get_render_data(Buffer_Type *buffer, float *wraps, Buffer_Render_Item *items, int max, int *count, | ||||
|                        float port_x, float port_y, float scroll_x, float scroll_y, int wrapped, | ||||
|                        float width, float height, void *advance_data, int stride, float font_height){ | ||||
|                        float width, float height, float *advance_data, float font_height){ | ||||
|     Buffer_Stringify_Type loop; | ||||
|     Full_Cursor start_cursor; | ||||
|     Buffer_Render_Item *item; | ||||
|  | @ -1025,12 +1101,12 @@ buffer_get_render_data(Buffer_Type *buffer, float *wraps, Buffer_Render_Item *it | |||
|     shift_y = port_y - scroll_y; | ||||
|     if (wrapped){ | ||||
|         start_cursor = buffer_cursor_from_wrapped_xy(buffer, 0, scroll_y, 0, wraps, | ||||
|                                                      width, font_height, advance_data, stride); | ||||
|                                                      width, font_height, advance_data); | ||||
|         shift_y += start_cursor.wrapped_y; | ||||
|     } | ||||
|     else{ | ||||
|         start_cursor = buffer_cursor_from_unwrapped_xy(buffer, 0, scroll_y, 0, wraps, | ||||
|                                                        width, font_height, advance_data, stride); | ||||
|                                                        width, font_height, advance_data); | ||||
|         shift_y += start_cursor.unwrapped_y; | ||||
|     } | ||||
|      | ||||
|  | @ -1048,7 +1124,7 @@ buffer_get_render_data(Buffer_Type *buffer, float *wraps, Buffer_Render_Item *it | |||
|          | ||||
|         for (i = loop.absolute_pos; i < end; ++i){ | ||||
|             ch = data[i]; | ||||
|             ch_width = measure_character(advance_data, stride, ch); | ||||
|             ch_width = measure_character(advance_data, ch); | ||||
|              | ||||
|             if (ch_width + x > width + shift_x && wrapped){ | ||||
|                 x = shift_x; | ||||
|  | @ -1058,7 +1134,7 @@ buffer_get_render_data(Buffer_Type *buffer, float *wraps, Buffer_Render_Item *it | |||
|              | ||||
|             switch (ch){ | ||||
|             case '\n': | ||||
|                 write_render_item_inline(item, i, ' ', x, y, advance_data, stride, font_height); | ||||
|                 write_render_item_inline(item, i, ' ', x, y, advance_data, font_height); | ||||
|                 ++item_i; | ||||
|                 ++item; | ||||
|                  | ||||
|  | @ -1067,35 +1143,35 @@ buffer_get_render_data(Buffer_Type *buffer, float *wraps, Buffer_Render_Item *it | |||
|                 break; | ||||
| 
 | ||||
|             case 0: | ||||
|                 ch_width = write_render_item_inline(item, i, '\\', x, y, advance_data, stride, font_height); | ||||
|                 ch_width = write_render_item_inline(item, i, '\\', x, y, advance_data, font_height); | ||||
|                 ++item_i; | ||||
|                 ++item; | ||||
|                 x += ch_width; | ||||
| 
 | ||||
|                 ch_width = write_render_item_inline(item, i, '0', x, y, advance_data, stride, font_height); | ||||
|                 ch_width = write_render_item_inline(item, i, '0', x, y, advance_data, font_height); | ||||
|                 ++item_i; | ||||
|                 ++item; | ||||
|                 x += ch_width; | ||||
|                 break; | ||||
| 
 | ||||
|             case '\r': | ||||
|                 ch_width = write_render_item_inline(item, i, '\\', x, y, advance_data, stride, font_height); | ||||
|                 ch_width = write_render_item_inline(item, i, '\\', x, y, advance_data, font_height); | ||||
|                 ++item_i; | ||||
|                 ++item; | ||||
|                 x += ch_width; | ||||
| 
 | ||||
|                 ch_width = write_render_item_inline(item, i, 'r', x, y, advance_data, stride, font_height); | ||||
|                 ch_width = write_render_item_inline(item, i, 'r', x, y, advance_data, font_height); | ||||
|                 ++item_i; | ||||
|                 ++item; | ||||
|                 x += ch_width; | ||||
|                 break; | ||||
| 
 | ||||
|             case '\t': | ||||
|                 ch_width_sub = write_render_item_inline(item, i, '\\', x, y, advance_data, stride, font_height); | ||||
|                 ch_width_sub = write_render_item_inline(item, i, '\\', x, y, advance_data, font_height); | ||||
|                 ++item_i; | ||||
|                 ++item; | ||||
| 
 | ||||
|                 write_render_item_inline(item, i, 't', x + ch_width_sub, y, advance_data, stride, font_height); | ||||
|                 write_render_item_inline(item, i, 't', x + ch_width_sub, y, advance_data, font_height); | ||||
|                 ++item_i; | ||||
|                 ++item; | ||||
|                 x += ch_width; | ||||
|  | @ -1116,7 +1192,7 @@ buffer_get_render_data(Buffer_Type *buffer, float *wraps, Buffer_Render_Item *it | |||
| buffer_get_render_data_end: | ||||
|     if (y <= height + shift_y || item == items){ | ||||
|         ch = 0; | ||||
|         ch_width = measure_character(advance_data, stride, ' '); | ||||
|         ch_width = measure_character(advance_data, ' '); | ||||
|         write_render_item(item, size, ch, x, y, ch_width, font_height); | ||||
|         ++item_i; | ||||
|         ++item; | ||||
|  |  | |||
|  | @ -692,7 +692,7 @@ buffer_edit_provide_memory(Multi_Gap_Buffer *buffer, void *new_data, int size){ | |||
|         assert_4tech(size >= fixed_width_buffer_size); | ||||
|          | ||||
|         gap = &buffer->gaps[buffer->chunk_alloced++]; | ||||
|         *gap = {}; | ||||
|         memzero_4tech(*gap); | ||||
|         gap->data = (char*)new_data; | ||||
|         result = 0; | ||||
|     } | ||||
|  |  | |||
|  | @ -402,7 +402,7 @@ buffer_end_init(Rope_Buffer_Init *init, void *scratch, int scratch_size){ | |||
|         result = 1; | ||||
|          | ||||
|         node = buffer->nodes; | ||||
|         *node = {}; | ||||
|         memzero_4tech(*node); | ||||
|         node->weight = init->size; | ||||
|         node->left_weight = init->size; | ||||
| 
 | ||||
|  | @ -988,12 +988,14 @@ buffer_split_end: | |||
| internal_4tech int | ||||
| buffer_build_tree_floating(Rope_Buffer *buffer, char *str, int len, int *out, | ||||
|                            void *scratch, int scratch_size, int *request_amount){ | ||||
|     Rope_Node *super_root_node; | ||||
|     int result; | ||||
|     int super_root; | ||||
| 
 | ||||
|     result = 0; | ||||
|     if (buffer_alloc_rope_node(buffer, &super_root)){ | ||||
|         buffer->nodes[super_root] = {}; | ||||
|         super_root_node = buffer->nodes + super_root; | ||||
|         memset_4tech(super_root_node, 0, sizeof(*super_root_node)); | ||||
|         if (buffer_build_tree(buffer, str, len, super_root, scratch, scratch_size, request_amount)){ | ||||
|             *out = buffer->nodes[super_root].left; | ||||
|             buffer_free_rope_node(buffer, super_root); | ||||
|  | @ -1124,7 +1126,7 @@ buffer_replace_range(Rope_Buffer *buffer, int start, int end, char *str, int len | |||
|     nodes->left = state.middle; | ||||
|     nodes->weight = nodes->left_weight = nodes[state.middle].weight; | ||||
|      | ||||
|     state = {}; | ||||
|     memset_4tech(&state, 0, sizeof(state)); | ||||
|      | ||||
|     buffer_rope_check(buffer, scratch, scratch_size); | ||||
|      | ||||
|  |  | |||
|  | @ -24,6 +24,10 @@ | |||
| #define memset_4tech memset | ||||
| #endif | ||||
| 
 | ||||
| #ifndef memzero_4tech | ||||
| #define memzero_4tech(x) ((x) = {}) | ||||
| #endif | ||||
| 
 | ||||
| #ifndef memcpy_4tech | ||||
| #define memcpy_4tech memcpy | ||||
| #endif | ||||
|  | @ -70,21 +74,7 @@ lroundup_(int x, int granularity){ | |||
| #define round_pot_4tech ROUNDPOT32 | ||||
| #endif | ||||
| 
 | ||||
| inline_4tech float | ||||
| measure_character(void *advance_data, int stride, char character){ | ||||
|     char *advances; | ||||
|     float width; | ||||
|      | ||||
|     advances = (char*)advance_data; | ||||
|     switch (character){ | ||||
|     case 0: width = *(float*)(advances + stride * '\\') + *(float*)(advances + stride * '0'); break; | ||||
|     case '\n': width = 0; break; | ||||
|     case '\r': width = *(float*)(advances + stride * '\\') + *(float*)(advances + stride * '\r'); break; | ||||
|     default: width = *(float*)(advances + stride * character); | ||||
|     } | ||||
|      | ||||
|     return(width); | ||||
| } | ||||
| #define measure_character(a,c) ((a)[c]) | ||||
| 
 | ||||
| typedef struct Buffer_Edit{ | ||||
|     int str_start, len; | ||||
|  | @ -176,9 +166,9 @@ write_render_item(Buffer_Render_Item *item, int index, int glyphid, | |||
| 
 | ||||
| inline_4tech float | ||||
| write_render_item_inline(Buffer_Render_Item *item, int index, int glyphid, | ||||
|                          float x, float y, void *advance_data, int stride, float h){ | ||||
|                          float x, float y, float *advance_data, float h){ | ||||
|     float ch_width; | ||||
|     ch_width = measure_character(advance_data, stride, (char)glyphid); | ||||
|     ch_width = measure_character(advance_data, (char)glyphid); | ||||
|     write_render_item(item, index, glyphid, x, y, ch_width, h); | ||||
|     return(ch_width); | ||||
| } | ||||
|  | @ -348,12 +338,27 @@ buffer_batch_edit_update_cursors(Cursor_With_Index *sorted_positions, int count, | |||
| 
 | ||||
| internal_4tech int | ||||
| eol_convert_in(char *dest, char *src, int size){ | ||||
|     int i, j; | ||||
|     int i, j, k; | ||||
| 
 | ||||
|     i = 0; | ||||
|     k = 0; | ||||
|     j = 0; | ||||
|      | ||||
|     for (i = 0, j = 0; i < size; ++i){ | ||||
|         if (src[i] != '\r'){ | ||||
|             dest[j++] = src[i]; | ||||
|     for (; j < size && src[j] != '\r'; ++j); | ||||
|     memcpy_4tech(dest, src, j); | ||||
|      | ||||
|     if (j < size){ | ||||
|         k = 1; | ||||
|         ++j; | ||||
|         for (i = j; i < size; ++i){ | ||||
|             if (src[i] == '\r'){ | ||||
|                 memcpy_4tech(dest + j - k, src + j, i - j); | ||||
|                 ++k; | ||||
|                 j = i+1; | ||||
|             } | ||||
|         } | ||||
|         memcpy_4tech(dest + j - k, src + j, i - j); | ||||
|         j = i - k; | ||||
|     } | ||||
|      | ||||
|     return(j); | ||||
|  | @ -361,12 +366,26 @@ eol_convert_in(char *dest, char *src, int size){ | |||
| 
 | ||||
| internal_4tech int | ||||
| eol_in_place_convert_in(char *data, int size){ | ||||
|     int i, j; | ||||
|     int i, j, k; | ||||
| 
 | ||||
|     i = 0; | ||||
|     k = 0; | ||||
|     j = 0; | ||||
|      | ||||
|     for (i = 0, j = 0; i < size; ++i){ | ||||
|         if (data[i] != '\r'){ | ||||
|             data[j++] = data[i]; | ||||
|     for (; j < size && data[j] != '\r'; ++j); | ||||
|      | ||||
|     if (j < size){ | ||||
|         k = 1; | ||||
|         ++j; | ||||
|         for (i = j; i < size; ++i){ | ||||
|             if (data[i] == '\r'){ | ||||
|                 memmove_4tech(data + j - k, data + j, i - j); | ||||
|                 ++k; | ||||
|                 j = i+1; | ||||
|             } | ||||
|         } | ||||
|         memmove_4tech(data + j - k, data + j, i - j); | ||||
|         j = i - k; | ||||
|     } | ||||
|      | ||||
|     return(j); | ||||
|  |  | |||
|  | @ -0,0 +1,67 @@ | |||
| /* 
 | ||||
|  * Mr. 4th Dimention - Allen Webster | ||||
|  *  Four Tech | ||||
|  * | ||||
|  * public domain -- no warranty is offered or implied; use this code at your own risk | ||||
|  *  | ||||
|  * 08.11.2015 | ||||
|  *  | ||||
|  * Buffer experiment testing layer, abstract portion | ||||
|  *  | ||||
|  */ | ||||
| 
 | ||||
| // TOP
 | ||||
| 
 | ||||
| #define Buffer_Init_Type cat_4tech(Buffer_Type, _Init) | ||||
| #define Buffer_Stringify_Type cat_4tech(Buffer_Type, _Stringify_Loop) | ||||
| #define Buffer_Backify_Type cat_4tech(Buffer_Type, _Backify_Loop) | ||||
| 
 | ||||
| void | ||||
| init_buffer(Buffer_Type *buffer, File_Data file, void *scratch, int scratch_size){ | ||||
|     memzero_4tech(*buffer); | ||||
|     Buffer_Init_Type init; | ||||
|     for (init = buffer_begin_init(buffer, file.data, file.size); | ||||
|          buffer_init_need_more(&init);){ | ||||
|         int page_size = buffer_init_page_size(&init); | ||||
|         void *page = malloc(page_size); | ||||
|         buffer_init_provide_page(&init, page, page_size); | ||||
|     } | ||||
|     debug_4tech(int result =) | ||||
|         buffer_end_init(&init, scratch, scratch_size); | ||||
|     assert_4tech(result); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| measure_starts_widths(Buffer_Type *buffer, float *font_widths){ | ||||
|     int max = 1 << 10; | ||||
|     buffer->line_starts = (int*)malloc(max*sizeof(int)); | ||||
|     buffer->line_max = max; | ||||
|     buffer->line_widths = (float*)malloc(max*sizeof(float)); | ||||
|     buffer->widths_max = max; | ||||
| 
 | ||||
|     Buffer_Measure_Starts state; | ||||
|     memzero_4tech(state); | ||||
|     for (;buffer_measure_starts_widths(&state, buffer, font_widths);){ | ||||
|         int max = buffer->line_max; | ||||
|         int count = state.count; | ||||
|         int target = count + 1; | ||||
| 
 | ||||
|         max = target*2; | ||||
|         int *new_lines = (int*)malloc(max*sizeof(int)); | ||||
|         memcpy_4tech(new_lines, buffer->line_starts, count*sizeof(int)); | ||||
|         free(buffer->line_starts); | ||||
|         buffer->line_starts = new_lines; | ||||
|         buffer->line_max = max; | ||||
| 
 | ||||
|         float *new_widths = (float*)malloc(max*sizeof(float)); | ||||
|         memcpy_4tech(new_widths, buffer->line_widths, count*sizeof(int)); | ||||
|         free(buffer->line_widths); | ||||
|         buffer->line_widths = new_widths; | ||||
|         buffer->widths_max = max; | ||||
|     } | ||||
|     buffer->line_count = state.count; | ||||
|     buffer->widths_count = state.count; | ||||
| } | ||||
| 
 | ||||
| // BOTTOM
 | ||||
| 
 | ||||
|  | @ -52,6 +52,10 @@ ROUNDPOT32(unsigned int v){ | |||
| #endif | ||||
| #define hard_assert_4tech(x) assert(x) | ||||
| 
 | ||||
| #ifdef __linux__ | ||||
| #define memzero_4tech(x) memset_4tech(&(x), 0, sizeof(x)) | ||||
| #endif | ||||
| 
 | ||||
| #include "4coder_shared.cpp" | ||||
| #include "4coder_golden_array.cpp" | ||||
| #include "4coder_gap_buffer.cpp" | ||||
|  | @ -60,25 +64,21 @@ ROUNDPOT32(unsigned int v){ | |||
| 
 | ||||
| #define Buffer_Type Buffer | ||||
| #include "4coder_buffer_abstract.cpp" | ||||
| 
 | ||||
| #undef Buffer_Type | ||||
| 
 | ||||
| #define Buffer_Type Gap_Buffer | ||||
| #include "4coder_buffer_abstract.cpp" | ||||
| 
 | ||||
| #undef Buffer_Type | ||||
| 
 | ||||
| #define Buffer_Type Multi_Gap_Buffer | ||||
| #include "4coder_buffer_abstract.cpp" | ||||
| 
 | ||||
| #undef Buffer_Type | ||||
| 
 | ||||
| #define Buffer_Type Rope_Buffer | ||||
| #include "4coder_buffer_abstract.cpp" | ||||
| 
 | ||||
| #undef Buffer_Type | ||||
| #undef Buffer_Init_Type | ||||
| #undef Buffer_Stringify_Type | ||||
| #undef Buffer_Backify_Type | ||||
| 
 | ||||
| #ifdef _WIN32 | ||||
| #if defined(_WIN32) | ||||
| #include <Windows.h> | ||||
| 
 | ||||
| typedef unsigned long long time_int; | ||||
|  | @ -110,28 +110,47 @@ time_int get_time(){ | |||
|     return(result); | ||||
| } | ||||
| 
 | ||||
| #else | ||||
| #error Timer not supported by this platform | ||||
| #endif | ||||
| #elif defined(__linux__) | ||||
| #include <time.h> | ||||
| 
 | ||||
| void setup(){ | ||||
|     unsigned long long resolution; | ||||
|     if (!time_init(&resolution)){ | ||||
|         printf("error: could not initialize timer"); | ||||
|         exit(1); | ||||
| typedef unsigned long long time_int; | ||||
| 
 | ||||
| int time_init(unsigned long long *resolution){ | ||||
|     int result; | ||||
|     struct timespec res; | ||||
|     result = 0; | ||||
|      | ||||
|     if (!clock_getres(CLOCK_MONOTONIC, &res)){ | ||||
|         result = 1; | ||||
| 	if (res.tv_sec > 0 || res.tv_nsec == 0) *resolution = 0; | ||||
| 	else *resolution = (unsigned long long)(1000000/res.tv_nsec); | ||||
|     } | ||||
| 
 | ||||
|     if (resolution < 1000000) | ||||
|         printf("warning: timer is not actually at high enough resolution for good measurements!\n"); | ||||
| 
 | ||||
|     return(result); | ||||
| } | ||||
| 
 | ||||
| time_int get_time(){ | ||||
|     time_int result; | ||||
|     struct timespec time; | ||||
|      | ||||
|     result = 0; | ||||
|     if (!clock_gettime(CLOCK_MONOTONIC, &time)){ | ||||
|         result = (time.tv_sec * 1000000) + (time.tv_nsec / 1000); | ||||
|     } | ||||
|      | ||||
|     return(result); | ||||
| } | ||||
| 
 | ||||
| #else | ||||
| #error Timer not supported on this platform | ||||
| #endif | ||||
| 
 | ||||
| typedef struct File_Data{ | ||||
|     char *data; | ||||
|     int size; | ||||
| } File_Data; | ||||
| 
 | ||||
| File_Data get_file(char *filename){ | ||||
| File_Data get_file(const char *filename){ | ||||
|     FILE *file; | ||||
|     File_Data result; | ||||
|      | ||||
|  | @ -162,6 +181,61 @@ void free_file(File_Data file){ | |||
|     free(file.data); | ||||
| } | ||||
| 
 | ||||
| #define STB_TRUETYPE_IMPLEMENTATION | ||||
| #include "stb_truetype.h" | ||||
| 
 | ||||
| float* get_font_data(const char *font_file){ | ||||
|     float *data = 0; | ||||
|     stbtt_bakedchar *baked; | ||||
|     File_Data file = get_file(font_file); | ||||
|     int stride, offset; | ||||
| 
 | ||||
|     if (file.data){ | ||||
|         int size = sizeof(*baked)*256; | ||||
|         baked = (stbtt_bakedchar*)malloc(size); | ||||
|         memset_4tech(baked, 0, sizeof(*baked)*256); | ||||
|      | ||||
|         offset = (int)((char*)&baked->xadvance - (char*)baked); | ||||
|         stride = sizeof(*baked); | ||||
| 
 | ||||
|         int w, h; | ||||
|         w = 10*256; | ||||
|         h = 25; | ||||
|         unsigned char *pixels = (unsigned char*)malloc(w * h); | ||||
|         stbtt_BakeFontBitmap((unsigned char*)file.data, 0, 17.f, pixels, w, h, 0, 128, baked); | ||||
|         free(pixels); | ||||
|         free_file(file); | ||||
| 
 | ||||
|         data = (float*)malloc(sizeof(float)*256); | ||||
|         memset_4tech(data, 0, sizeof(float)*256); | ||||
|          | ||||
|         char *pos = (char*)baked; | ||||
|         pos += offset; | ||||
|         for (int i = 0; i < 128; ++i){ | ||||
|             data[i] = *(float*)pos; | ||||
|             pos += stride; | ||||
|         } | ||||
|         free(baked); | ||||
|     } | ||||
|     else{ | ||||
|         printf("error: cannot continue without font\n"); | ||||
|     } | ||||
|      | ||||
|     return data; | ||||
| } | ||||
| 
 | ||||
| void setup(){ | ||||
|     unsigned long long resolution; | ||||
|     if (!time_init(&resolution)){ | ||||
|         printf("error: could not initialize timer"); | ||||
|         exit(1); | ||||
|     } | ||||
| 
 | ||||
|     if (resolution < 1000000) | ||||
|         printf("warning: timer is not actually at high enough resolution for good measurements!\n"); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| typedef struct Time_Record{ | ||||
|     time_int buffer; | ||||
|     time_int gap_buffer; | ||||
|  | @ -260,20 +334,21 @@ typedef struct Buffer_Set{ | |||
|     Rope_Buffer rope_buffer; | ||||
| } Buffer_Set; | ||||
| 
 | ||||
| template<typename Buffer_Init_Type, typename Buffer_Type> void | ||||
| init_buffer(Buffer_Type *buffer, File_Data file, void *scratch, int scratch_size){ | ||||
|     *buffer = {}; | ||||
|     Buffer_Init_Type init; | ||||
|     for (init = buffer_begin_init(buffer, file.data, file.size); | ||||
|          buffer_init_need_more(&init);){ | ||||
|         int page_size = buffer_init_page_size(&init); | ||||
|         void *page = malloc(page_size); | ||||
|         buffer_init_provide_page(&init, page, page_size); | ||||
|     } | ||||
|     debug_4tech(int result =) | ||||
|         buffer_end_init(&init, scratch, scratch_size); | ||||
|     assert_4tech(result); | ||||
| } | ||||
| #define Buffer_Type Buffer | ||||
| #include "4coder_test_abstract.cpp" | ||||
| #undef Buffer_Type | ||||
| 
 | ||||
| #define Buffer_Type Gap_Buffer | ||||
| #include "4coder_test_abstract.cpp" | ||||
| #undef Buffer_Type | ||||
| 
 | ||||
| #define Buffer_Type Multi_Gap_Buffer | ||||
| #include "4coder_test_abstract.cpp" | ||||
| #undef Buffer_Type | ||||
| 
 | ||||
| #define Buffer_Type Rope_Buffer | ||||
| #include "4coder_test_abstract.cpp" | ||||
| #undef Buffer_Type | ||||
| 
 | ||||
| #define print_name() printf("%s:\n", __FUNCTION__) | ||||
| 
 | ||||
|  | @ -288,22 +363,22 @@ initialization_test(Buffer_Set *set, File_Data file, int test_repitions, | |||
|      | ||||
|     for (int i = 0; i < test_repitions; ++i){ | ||||
|         tstart = get_time(); | ||||
|         init_buffer<Buffer_Init>(&set->buffer, file, scratch, scratch_size); | ||||
|         init_buffer(&set->buffer, file, scratch, scratch_size); | ||||
|         tend = get_time(); | ||||
|         init_time[i].buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         init_buffer<Gap_Buffer_Init>(&set->gap_buffer, file, scratch, scratch_size); | ||||
|         init_buffer(&set->gap_buffer, file, scratch, scratch_size); | ||||
|         tend = get_time(); | ||||
|         init_time[i].gap_buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         init_buffer<Multi_Gap_Buffer_Init>(&set->multi_gap_buffer, file, scratch, scratch_size); | ||||
|         init_buffer(&set->multi_gap_buffer, file, scratch, scratch_size); | ||||
|         tend = get_time(); | ||||
|         init_time[i].multi_gap_buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         init_buffer<Rope_Buffer_Init>(&set->rope_buffer, file, scratch, scratch_size); | ||||
|         init_buffer(&set->rope_buffer, file, scratch, scratch_size); | ||||
|         tend = get_time(); | ||||
|         init_time[i].rope_buffer = tend - tstart; | ||||
| 
 | ||||
|  | @ -325,31 +400,10 @@ initialization_test(Buffer_Set *set, File_Data file, int test_repitions, | |||
|     test_is_silenced = 0; | ||||
| } | ||||
| 
 | ||||
| template<typename Buffer_Type> void | ||||
| measure_starts(Buffer_Type *buffer){ | ||||
|     int max = 1 << 10; | ||||
|     buffer->line_starts = (int*)malloc(max*sizeof(int)); | ||||
|     buffer->line_max = max; | ||||
| 
 | ||||
|     Buffer_Measure_Starts state = {}; | ||||
|     for (;buffer_measure_starts(&state, buffer);){ | ||||
|         int max = buffer->line_max; | ||||
|         int count = state.count; | ||||
|         int target = count + 1; | ||||
| 
 | ||||
|         max = target*2; | ||||
|         int *new_lines = (int*)malloc(max*sizeof(int)); | ||||
|         memcpy_4tech(new_lines, buffer->line_starts, count*sizeof(int)); | ||||
|         free(buffer->line_starts); | ||||
|         buffer->line_starts = new_lines; | ||||
|         buffer->line_max = max; | ||||
|     } | ||||
|     buffer->line_count = state.count; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| measure_starts_test(Buffer_Set *set, int test_repitions, | ||||
|                     void *scratch, int scratch_size, Record_Statistics *stats_out){ | ||||
| measure_starts_widths_test(Buffer_Set *set, int test_repitions, | ||||
|                            void *scratch, int scratch_size, Record_Statistics *stats_out, | ||||
|                            float *font_widths){ | ||||
|     time_int tstart, tend; | ||||
|     Time_Record *measure_time = (Time_Record*)scratch; | ||||
|     scratch = measure_time + test_repitions; | ||||
|  | @ -358,22 +412,22 @@ measure_starts_test(Buffer_Set *set, int test_repitions, | |||
|      | ||||
|     for (int i = 0; i < test_repitions; ++i){ | ||||
|         tstart = get_time(); | ||||
|         measure_starts(&set->buffer); | ||||
|         measure_starts_widths(&set->buffer, font_widths); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         measure_starts(&set->gap_buffer); | ||||
|         measure_starts_widths(&set->gap_buffer, font_widths); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].gap_buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         measure_starts(&set->multi_gap_buffer); | ||||
|         measure_starts_widths(&set->multi_gap_buffer, font_widths); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].multi_gap_buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         measure_starts(&set->rope_buffer); | ||||
|         measure_starts_widths(&set->rope_buffer, font_widths); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].rope_buffer = tend - tstart; | ||||
| 
 | ||||
|  | @ -382,59 +436,7 @@ measure_starts_test(Buffer_Set *set, int test_repitions, | |||
|             free(set->gap_buffer.line_starts); | ||||
|             free(set->multi_gap_buffer.line_starts); | ||||
|             free(set->rope_buffer.line_starts); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (!test_is_silenced) print_name(); | ||||
|     print_statistics(measure_time, test_repitions, stats_out); | ||||
|     if (!test_is_silenced) printf("\n"); | ||||
|     test_is_silenced = 0; | ||||
| } | ||||
| 
 | ||||
| template<typename Buffer_Type> void | ||||
| measure_widths(Buffer_Type *buffer){ | ||||
|     int new_max = round_up_4tech(buffer->line_count, 1 << 10); | ||||
|     if (new_max < (1 << 10)) new_max = 1 << 10; | ||||
|          | ||||
|     buffer->line_widths = (float*)malloc(new_max*sizeof(float)); | ||||
|     buffer->widths_max = new_max; | ||||
|     buffer->widths_count = 0; | ||||
|      | ||||
|     float glyph_width = 8.f; | ||||
|     buffer_measure_widths(buffer, &glyph_width, 0); | ||||
| } | ||||
| 
 | ||||
| void | ||||
| measure_widths_test(Buffer_Set *set, int test_repitions, | ||||
|                     void *scratch, int scratch_size, Record_Statistics *stats_out){ | ||||
|     time_int tstart, tend; | ||||
|     Time_Record *measure_time = (Time_Record*)scratch; | ||||
|     scratch = measure_time + test_repitions; | ||||
|     assert_4tech(test_repitions*sizeof(*measure_time) < scratch_size); | ||||
|     scratch_size -= test_repitions*sizeof(*measure_time); | ||||
|      | ||||
|     for (int i = 0; i < test_repitions; ++i){ | ||||
|         tstart = get_time(); | ||||
|         measure_widths(&set->buffer); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         measure_widths(&set->gap_buffer); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].gap_buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         measure_widths(&set->multi_gap_buffer); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].multi_gap_buffer = tend - tstart; | ||||
|      | ||||
|         tstart = get_time(); | ||||
|         measure_widths(&set->rope_buffer); | ||||
|         tend = get_time(); | ||||
|         measure_time[i].rope_buffer = tend - tstart; | ||||
| 
 | ||||
|         if (i+1 != test_repitions){ | ||||
|              | ||||
|             free(set->buffer.line_widths); | ||||
|             free(set->gap_buffer.line_widths); | ||||
|             free(set->multi_gap_buffer.line_widths); | ||||
|  | @ -494,32 +496,54 @@ stream_check_test(Buffer_Set *buffers, void *scratch, int scratch_size){ | |||
|         buffer_stringify(&buffers->rope_buffer, i, end, page_2); | ||||
|         page_compare(page_1, page_2, page_size); | ||||
|     } | ||||
| 
 | ||||
|     for (i = size-1; i > 0; i -= page_size){ | ||||
|         int end = i - page_size; | ||||
|         if (end < 0) end = 0; | ||||
|          | ||||
|         buffer_backify(&buffers->buffer, i, end, page_1); | ||||
|          | ||||
|         buffer_backify(&buffers->gap_buffer, i, end, page_2); | ||||
|         page_compare(page_1, page_2, page_size); | ||||
|          | ||||
|         buffer_backify(&buffers->multi_gap_buffer, i, end, page_2); | ||||
|         page_compare(page_1, page_2, page_size); | ||||
|          | ||||
|         buffer_backify(&buffers->rope_buffer, i, end, page_2); | ||||
|         page_compare(page_1, page_2, page_size); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| int main(){ | ||||
| int main(int argc, char **argv){ | ||||
|     Buffer_Set buffers; | ||||
|     File_Data file; | ||||
|     float *widths_data; | ||||
| 
 | ||||
|     void *scratch; | ||||
|     int scratch_size; | ||||
|      | ||||
|     if (argc < 2){ | ||||
|         printf("usage: buffer_test <filename>\n"); | ||||
|         exit(1); | ||||
|     } | ||||
|      | ||||
|     setup(); | ||||
| 
 | ||||
|     scratch_size = 1 << 20; | ||||
|     scratch = malloc(scratch_size); | ||||
|      | ||||
|     file = get_file("test_file_1.cpp"); | ||||
|     file = get_file(argv[1]); | ||||
|     widths_data = get_font_data("LiberationSans-Regular.ttf"); | ||||
|      | ||||
|     Record_Statistics init_rec, starts_rec, widths_rec; | ||||
|     Record_Statistics init_rec, starts_widths_rec; | ||||
|      | ||||
|     initialization_test(&buffers, file, 100, scratch, scratch_size, &init_rec); | ||||
|     stream_check_test(&buffers, scratch, scratch_size); | ||||
|      | ||||
|     measure_starts_test(&buffers, 100, scratch, scratch_size, &starts_rec); | ||||
|     measure_widths_test(&buffers, 100, scratch, scratch_size, &widths_rec); | ||||
|     measure_starts_widths_test(&buffers, 100, scratch, scratch_size, &starts_widths_rec, widths_data); | ||||
|      | ||||
|     Time_Record expected_file_open; | ||||
|     expected_file_open = init_rec.expected + starts_rec.expected + widths_rec.expected; | ||||
|     expected_file_open = init_rec.expected + starts_widths_rec.expected; | ||||
|      | ||||
|     printf("average file open:\n"); | ||||
|     print_record(expected_file_open); | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue