From 2f52c018cd2a2d48e0c3567d399a223d8e7c94f1 Mon Sep 17 00:00:00 2001 From: Krzosa Karol Date: Thu, 4 Jul 2024 14:30:20 +0200 Subject: [PATCH] Add spall profiler and stop generating layout every frame --- src/basic/win32.cpp | 22 ++ src/text_editor/layout.cpp | 29 ++- src/text_editor/main.cpp | 72 +++--- src/text_editor/profiler.cpp | 50 ++++ src/text_editor/spall.h | 443 +++++++++++++++++++++++++++++++++++ 5 files changed, 587 insertions(+), 29 deletions(-) create mode 100644 src/text_editor/profiler.cpp create mode 100644 src/text_editor/spall.h diff --git a/src/basic/win32.cpp b/src/basic/win32.cpp index dd5af69..1ae75c4 100644 --- a/src/basic/win32.cpp +++ b/src/basic/win32.cpp @@ -204,3 +204,25 @@ FileIter IterateFiles(Allocator alo, String path) { Advance(&it); return it; } + +#if _WIN32 + #include +double get_time_in_micros(void) { + static double invfreq; + if (!invfreq) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + invfreq = 1000000.0 / frequency.QuadPart; + } + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return counter.QuadPart * invfreq; +} +#else + #include +double get_time_in_micros(void) { + struct timespec spec; + clock_gettime(CLOCK_MONOTONIC, &spec); + return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000); +} +#endif \ No newline at end of file diff --git a/src/text_editor/layout.cpp b/src/text_editor/layout.cpp index 285c195..b9c7100 100644 --- a/src/text_editor/layout.cpp +++ b/src/text_editor/layout.cpp @@ -56,6 +56,7 @@ struct Window { Buffer buffer; History history; + bool not_regen_layout; Array colored_strings; Arena layout_arena; Layout layout; @@ -68,6 +69,7 @@ struct Tuple { }; void DrawString(Font font, String text, Vector2 position, float fontSize, float spacing, Color tint) { + ProfileFunction(); if (font.texture.id == 0) font = GetFontDefault(); // Security check in case of not valid font float textOffsetX = 0.0f; // Offset X to next character to draw @@ -92,6 +94,7 @@ void DrawString(Font font, String text, Vector2 position, float fontSize, float } Vector2 MeasureString(Font font, String text, float fontSize, float spacing) { + ProfileFunction(); Vector2 textSize = {0}; if ((font.texture.id == 0) || (text.data == NULL)) return textSize; // Security check @@ -132,6 +135,7 @@ Vector2 MeasureString(Font font, String text, float fontSize, float spacing) { } void RegenLayout(Window *window) { + ProfileFunction(); Clear(&window->layout_arena); window->layout = {}; @@ -228,15 +232,20 @@ void RegenLayout(Window *window) { } layout.buffer_world_pixel_size.y = text_offset_y; + window->not_regen_layout = !window->not_regen_layout; } +// @todo: this is not taking line wrapping into account int64_t YPosWorldToLineNumber(Window &window, float ypos_window_buffer_world_units) { + ProfileFunction(); float line_spacing = window.font_size; int64_t line = (int64_t)floorf(ypos_window_buffer_world_units / line_spacing); return line; } +// @todo: this is not taking line wrapping into account LayoutRow *GetLayoutRow(Window &window, float ypos_window_buffer_world_units) { + ProfileFunction(); int64_t line = YPosWorldToLineNumber(window, ypos_window_buffer_world_units); if (line >= 0 && line < window.layout.rows.len) { return window.layout.rows.data + line; @@ -245,6 +254,7 @@ LayoutRow *GetLayoutRow(Window &window, float ypos_window_buffer_world_units) { } LayoutColumn *GetLayoutColumn(LayoutRow *row, float xpos_window_buffer_world_units) { + ProfileFunction(); if (!row) return NULL; For(row->columns) { if (xpos_window_buffer_world_units >= it.rect.min.x && xpos_window_buffer_world_units <= it.rect.max.x) { @@ -255,6 +265,7 @@ LayoutColumn *GetLayoutColumn(LayoutRow *row, float xpos_window_buffer_world_uni } Tuple GetRowCol(Window &window, Vec2 pos_buffer_world_units) { + ProfileFunction(); Tuple result = {}; result.a = GetLayoutRow(window, pos_buffer_world_units.y); result.b = GetLayoutColumn(result.a, pos_buffer_world_units.x); @@ -262,6 +273,7 @@ Tuple GetRowCol(Window &window, Vec2 pos_buffer_wor } Tuple GetRowCol(Window &window, int64_t pos) { + ProfileFunction(); Tuple result = {}; For(window.layout.rows) { if (window.layout.rows.len == 0) continue; @@ -286,6 +298,7 @@ Tuple GetRowCol(Window &window, int64_t pos) { } Range CalculateVisibleLineRange(Window &window) { + ProfileFunction(); Rect2 visible_rect = {window.scroll, window.scroll + GetSize(window.rect)}; Range result = {-1, -1}; For(window.layout.rows) { @@ -302,6 +315,7 @@ Range CalculateVisibleLineRange(Window &window) { } Array CalculateVisibleColumns(Arena *arena, Window &window) { + ProfileFunction(); Range visible_line_range = CalculateVisibleLineRange(window); Array r = {*arena}; for (int64_t line = visible_line_range.min; line < visible_line_range.max && line >= 0 && line < window.layout.rows.len; line += 1) { @@ -316,6 +330,7 @@ Array CalculateVisibleColumns(Arena *arena, Window &window) { } void MergeCursors(Window *window) { + ProfileFunction(); Scratch scratch; // Merge cursors that overlap, this needs to be handled before any edits to // make sure overlapping edits won't happen. @@ -351,12 +366,14 @@ void MergeCursors(Window *window) { } void SaveHistoryBeforeMergeCursor(Window *window, Array *stack) { + ProfileFunction(); HistoryEntry *entry = stack->alloc(); Allocator sys_allocator = GetSystemAllocator(); entry->cursors = window->cursors.tight_copy(sys_allocator); } void SaveHistoryBeforeApplyEdits(Window *window, Array *stack, Array edits) { + ProfileFunction(); HistoryEntry *entry = stack->last(); Allocator sys_allocator = GetSystemAllocator(); entry->edits = edits.tight_copy(sys_allocator); @@ -390,6 +407,7 @@ void SaveHistoryBeforeApplyEdits(Window *window, Array *stack, Arr } void RedoEdit(Window *window) { + ProfileFunction(); if (window->history.redo_stack.len <= 0) return; HistoryEntry entry = window->history.redo_stack.pop(); @@ -399,7 +417,8 @@ void RedoEdit(Window *window) { _ApplyEdits(&window->buffer, entry.edits); window->cursors.dealloc(); - window->cursors = entry.cursors; + window->cursors = entry.cursors; + window->not_regen_layout = false; Allocator sys_allocator = GetSystemAllocator(); For(entry.edits) Dealloc(sys_allocator, &it.string.data); @@ -407,6 +426,7 @@ void RedoEdit(Window *window) { } void UndoEdit(Window *window) { + ProfileFunction(); if (window->history.undo_stack.len <= 0) return; HistoryEntry entry = window->history.undo_stack.pop(); @@ -416,7 +436,8 @@ void UndoEdit(Window *window) { _ApplyEdits(&window->buffer, entry.edits); window->cursors.dealloc(); - window->cursors = entry.cursors; + window->cursors = entry.cursors; + window->not_regen_layout = false; Allocator sys_allocator = GetSystemAllocator(); For(entry.edits) Dealloc(sys_allocator, &it.string.data); @@ -442,6 +463,7 @@ void BeforeEdit(Window *window) { } void ApplyEdits(Window *window, Array edits) { + ProfileFunction(); Assert(window->history.debug_edit_phase == 1); window->history.debug_edit_phase += 1; SaveHistoryBeforeApplyEdits(window, &window->history.undo_stack, edits); @@ -449,6 +471,7 @@ void ApplyEdits(Window *window, Array edits) { } void AfterEdit(Window *window, Array edits) { + ProfileFunction(); Assert(window->history.debug_edit_phase == 2); window->history.debug_edit_phase -= 2; @@ -484,4 +507,6 @@ void AfterEdit(Window *window, Array edits) { // Make sure all cursors are in range For(window->cursors) it.range = Clamp(window->buffer, it.range); + + window->not_regen_layout = false; } diff --git a/src/text_editor/main.cpp b/src/text_editor/main.cpp index 440375a..83ca16b 100644 --- a/src/text_editor/main.cpp +++ b/src/text_editor/main.cpp @@ -2,9 +2,9 @@ #include "../basic/basic.h" #include "raylib.h" #include "raymath.h" +#include "profiler.cpp" // @todo: highlight all occurences of selected word -// @todo: systematic way of coloring words // @todo: search for word // @todo: context menu // @todo: toy around with acme ideas @@ -63,6 +63,7 @@ bool IsDoubleClick() { } int main() { + BeginProfiler(); InitScratch(); InitWindow(800, 600, "Hello"); // @todo: dpi @@ -123,6 +124,7 @@ int main() { UpdateEventRecording(); { + ProfileScope(focused_window_input_update); Window *focused_window = &windows[0]; focused_window->start_rect = GetScreenRect(); @@ -428,28 +430,31 @@ int main() { } { - String seek = "number"; - int64_t index = 0; - int64_t base_index = 0; - String s = GetString(focused_window->buffer); - while (Seek(s, seek, &index)) { - Range range = {base_index + index, base_index + index + seek.len}; + Cursor cursor = focused_window->cursors[0]; + if (GetRangeSize(cursor.range)) { + String seek = GetString(focused_window->buffer, cursor.range); + int64_t index = 0; + int64_t base_index = 0; + String s = GetString(focused_window->buffer); + while (Seek(s, seek, &index)) { + Range range = {base_index + index, base_index + index + seek.len}; - ColoredString colored = {}; - colored.range = range; - colored.use_text_color = true; - colored.use_underline = true; - colored.text_color = RED; - focused_window->colored_strings.add(colored); + // ColoredString colored = {}; + // colored.range = range; + // colored.use_text_color = true; + // colored.use_underline = true; + // colored.text_color = RED; + // focused_window->colored_strings.add(colored); - colored = {}; - colored.range = range; - colored.use_highlight_background_color = true; - colored.highlight_background_color = {0, 50, 150, 50}; - focused_window->colored_strings.add(colored); + ColoredString colored = {}; + colored.range = range; + colored.use_highlight_background_color = true; + colored.highlight_background_color = {0, 0, 0, 25}; + focused_window->colored_strings.add(colored); - base_index += index + seek.len; - s = s.skip(index + seek.len); + base_index += index + seek.len; + s = s.skip(index + seek.len); + } } } @@ -460,15 +465,17 @@ int main() { ClearBackground(RAYWHITE); ForItem(window, windows) { + ProfileScope(window_render_loop); window.rect = window.start_rect; Rect2 horizontal_bar_rect = CutBottom(&window.rect, 10); Rect2 vertical_bar_rect = CutRight(&window.rect, 10); Rect2 line_number_rect = CutLeft(&window.rect, MeasureString(window.font, "1234", window.font_size, window.font_spacing).x); - RegenLayout(&window); + if (!window.not_regen_layout) RegenLayout(&window); // Draw and layout window overlay Vec2 mouse = GetMousePosition(); { + ProfileScope(draw_rectangles); DrawRectangleRec(ToRectangle(window.rect), WHITE); DrawRectangleRec(ToRectangle(line_number_rect), WHITE); @@ -659,11 +666,16 @@ int main() { Array visible_columns = CalculateVisibleColumns(&FrameArena, window); Range visible_line_range = CalculateVisibleLineRange(window); + BeginProfileScope("draw_glyphs"); ForItem(col, visible_columns) { Rect2 rect = {col.rect.min + window.rect.min - window.scroll, col.rect.max + window.rect.min - window.scroll}; if (col.codepoint == '\n') { - DrawTextEx(font, "\\n", rect.min, font_size, font_spacing, GRAY); + // DrawTextEx(font, "\\n", rect.min, font_size, font_spacing, GRAY); + DrawTextCodepoint(font, 'n', rect.min, font_size, GRAY); + } else if (col.codepoint == '\r') { + // DrawTextEx(font, "\\r", rect.min, font_size, font_spacing, GRAY); + DrawTextCodepoint(font, 'r', rect.min, font_size, GRAY); } else if (col.codepoint == '\0') { DrawTextEx(font, "\\0", rect.min, font_size, font_spacing, {255, 0, 0, 150}); } else if ((col.codepoint != ' ') && (col.codepoint != '\t')) { @@ -693,8 +705,10 @@ int main() { } } } + EndProfileScope(); // Draw cursor stuff + BeginProfileScope("draw_cursor"); Array lines_to_highlight = {FrameArena}; ForItem(cursor, window.cursors) { Tuple front = GetRowCol(window, GetFront(cursor)); @@ -711,27 +725,29 @@ int main() { For(visible_columns) { if (it.pos >= cursor.range.min && it.pos < cursor.range.max) { Rect2 rect = it.rect + window.rect.min - window.scroll; - DrawRectangleRec(ToRectangle(rect), {0, 50, 150, 50}); + DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 75}); } } // Cursor blocks if (front.b) { Rect2 rect = front.b->rect + window.rect.min - window.scroll; - rect = CutLeft(&rect, 4); - DrawRectangleRec(ToRectangle(rect), RED); + rect = CutLeft(&rect, 2); + DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 255}); } if (back.b) { Rect2 rect = back.b->rect + window.rect.min - window.scroll; - rect = CutLeft(&rect, 2); - DrawRectangleRec(ToRectangle(rect), GREEN); + rect = CutLeft(&rect, 1); + DrawRectangleRec(ToRectangle(rect), {0, 0, 0, 255}); } } + EndProfileScope(); EndScissorMode(); // Draw line numbers { + ProfileScope(draw_line_numbers); BeginScissorMode((int)line_number_rect.min.x, (int)line_number_rect.min.y, (int)(line_number_rect.max.x - line_number_rect.min.x), (int)(line_number_rect.max.y - line_number_rect.min.y)); for (int64_t line = visible_line_range.min; line < visible_line_range.max; line += 1) { LayoutRow &row = window.layout.rows[line]; @@ -765,4 +781,6 @@ int main() { EndDrawing(); } + CloseWindow(); + EndProfiler(); } \ No newline at end of file diff --git a/src/text_editor/profiler.cpp b/src/text_editor/profiler.cpp new file mode 100644 index 0000000..6ac7518 --- /dev/null +++ b/src/text_editor/profiler.cpp @@ -0,0 +1,50 @@ +#if 1 + #include "spall.h" + +static SpallProfile spall_ctx; +static SpallBuffer spall_buffer; +double get_time_in_micros(void); + +void BeginProfiler() { + spall_ctx = spall_init_file("hello_world.spall", 1); + + int buffer_size = 1 * 1024 * 1024; + unsigned char *buffer = (unsigned char *)malloc(buffer_size); + spall_buffer = {buffer, (size_t)buffer_size}; + + spall_buffer_init(&spall_ctx, &spall_buffer); +} + +void EndProfiler() { + spall_buffer_quit(&spall_ctx, &spall_buffer); + free(spall_buffer.data); + spall_quit(&spall_ctx); +} + +void BeginProfileScope(const char *name) { + spall_buffer_begin(&spall_ctx, &spall_buffer, + name, // name of your name + sizeof(name) - 1, // name len minus the null terminator + get_time_in_micros() // timestamp in microseconds -- start of your timing block + ); +} + +void EndProfileScope() { + spall_buffer_end(&spall_ctx, &spall_buffer, + get_time_in_micros() // timestamp in microseconds -- end of your timing block + ); +} + #define ProfileScope(name) ProfileScope_ PROFILE_SCOPE_VAR_##name(#name) + #define ProfileFunction() ProfileScope_ PROFILE_SCOPE_FUNCTION(__FUNCTION__) +struct ProfileScope_ { + ProfileScope_(const char *name) { BeginProfileScope(name); } + ~ProfileScope_() { EndProfileScope(); } +}; +#else + #define ProfileScope(name) + #define ProfileFunction() + #define BeginProfiler() + #define EndProfiler() + #define BeginProfileScope(name) + #define EndProfileScope() +#endif \ No newline at end of file diff --git a/src/text_editor/spall.h b/src/text_editor/spall.h new file mode 100644 index 0000000..02b32f3 --- /dev/null +++ b/src/text_editor/spall.h @@ -0,0 +1,443 @@ +// SPDX-FileCopyrightText: © 2023 Phillip Trudeau-Tavara +// SPDX-License-Identifier: MIT + +/* + +TODO: Optional Helper APIs: + + - Compression API: would require a mutexed lockable context (yuck...) + - Either using a ZIP library, a name cache + TIDPID cache, or both (but ZIP is likely more than enough!!!) + - begin()/end() writes compressed chunks to a caller-determined destination + - The destination can be the buffered-writing API or a custom user destination + - Ultimately need to take a lock with some granularity... can that be the caller's responsibility? + + - Counter Event: should allow tracking arbitrary named values with a single event, for memory and frame profiling + + - Ring-buffer API + spall_ring_init + spall_ring_emit_begin + spall_ring_emit_end + spall_ring_flush +*/ + +#ifndef SPALL_H +#define SPALL_H + +#if !defined(_MSC_VER) || defined(__clang__) + #define SPALL_NOINSTRUMENT __attribute__((no_instrument_function)) + #define SPALL_FORCEINLINE __attribute__((always_inline)) +#else + #define SPALL_NOINSTRUMENT // Can't noinstrument on MSVC! + #define SPALL_FORCEINLINE __forceinline +#endif + +#include +#include +#include +#include + +#define SPALL_FN static inline SPALL_NOINSTRUMENT + +#define SPALL_MIN(a, b) (((a) < (b)) ? (a) : (b)) + +#pragma pack(push, 1) + +typedef struct SpallHeader { + uint64_t magic_header; // = 0x0BADF00D + uint64_t version; // = 1 + double timestamp_unit; + uint64_t must_be_0; +} SpallHeader; + +enum { + SpallEventType_Invalid = 0, + SpallEventType_Custom_Data = 1, // Basic readers can skip this. + SpallEventType_StreamOver = 2, + + SpallEventType_Begin = 3, + SpallEventType_End = 4, + SpallEventType_Instant = 5, + + SpallEventType_Overwrite_Timestamp = 6, // Retroactively change timestamp units - useful for incrementally improving RDTSC frequency. + SpallEventType_Pad_Skip = 7, +}; + +typedef struct SpallBeginEvent { + uint8_t type; // = SpallEventType_Begin + uint8_t category; + + uint32_t pid; + uint32_t tid; + double when; + + uint8_t name_length; + uint8_t args_length; +} SpallBeginEvent; + +typedef struct SpallBeginEventMax { + SpallBeginEvent event; + char name_bytes[255]; + char args_bytes[255]; +} SpallBeginEventMax; + +typedef struct SpallEndEvent { + uint8_t type; // = SpallEventType_End + uint32_t pid; + uint32_t tid; + double when; +} SpallEndEvent; + +typedef struct SpallPadSkipEvent { + uint8_t type; // = SpallEventType_Pad_Skip + uint32_t size; +} SpallPadSkipEvent; + +#pragma pack(pop) + +typedef struct SpallProfile SpallProfile; + +// Important!: If you define your own callbacks, mark them SPALL_NOINSTRUMENT! +typedef bool (*SpallWriteCallback)(SpallProfile *self, const void *data, size_t length); +typedef bool (*SpallFlushCallback)(SpallProfile *self); +typedef void (*SpallCloseCallback)(SpallProfile *self); + +struct SpallProfile { + double timestamp_unit; + bool is_json; + SpallWriteCallback write; + SpallFlushCallback flush; + SpallCloseCallback close; + void *data; +}; + +// Important!: If you are writing Begin/End events, then do NOT write +// events for the same PID + TID pair on different buffers!!! +typedef struct SpallBuffer { + void *data; + size_t length; + + // Internal data - don't assign this + size_t head; + SpallProfile *ctx; +} SpallBuffer; + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(SPALL_BUFFER_PROFILING) && !defined(SPALL_BUFFER_PROFILING_GET_TIME) + #error "You must #define SPALL_BUFFER_PROFILING_GET_TIME() to profile buffer flushes." +#endif + +SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len); +#ifdef SPALL_BUFFER_PROFILING + #define SPALL_BUFFER_PROFILE_BEGIN() double spall_time_begin = (SPALL_BUFFER_PROFILING_GET_TIME()) + // Don't call this with anything other than a string literal + #define SPALL_BUFFER_PROFILE_END(name) spall__buffer_profile(ctx, wb, spall_time_begin, (SPALL_BUFFER_PROFILING_GET_TIME()), "" name "", sizeof("" name "") - 1) +#else + #define SPALL_BUFFER_PROFILE_BEGIN() + #define SPALL_BUFFER_PROFILE_END(name) +#endif + +SPALL_FN SPALL_FORCEINLINE bool spall__file_write(SpallProfile *ctx, const void *p, size_t n) { + if (!ctx->data) return false; +#ifdef SPALL_DEBUG + if (feof((FILE *)ctx->data)) return false; + if (ferror((FILE *)ctx->data)) return false; +#endif + + if (fwrite(p, n, 1, (FILE *)ctx->data) != 1) return false; + return true; +} +SPALL_FN bool spall__file_flush(SpallProfile *ctx) { + if (!ctx->data) return false; + if (fflush((FILE *)ctx->data)) return false; + return true; +} +SPALL_FN void spall__file_close(SpallProfile *ctx) { + if (!ctx->data) return; + + if (ctx->is_json) { +#ifdef SPALL_DEBUG + if (!feof((FILE *)ctx->data) && !ferror((FILE *)ctx->data)) +#endif + { + fseek((FILE *)ctx->data, -2, SEEK_CUR); // seek back to overwrite trailing comma + fwrite("\n]}\n", sizeof("\n]}\n") - 1, 1, (FILE *)ctx->data); + } + } + fflush((FILE *)ctx->data); + fclose((FILE *)ctx->data); + ctx->data = NULL; +} + +SPALL_FN SPALL_FORCEINLINE bool spall__buffer_flush(SpallProfile *ctx, SpallBuffer *wb) { + // precon: wb + // precon: wb->data + // precon: wb->head <= wb->length + // precon: !ctx || ctx->write +#ifdef SPALL_DEBUG + if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL) +#endif + + if (wb->head && ctx) { + SPALL_BUFFER_PROFILE_BEGIN(); + if (!ctx->write) return false; + if (ctx->write == spall__file_write) { + if (!spall__file_write(ctx, wb->data, wb->head)) return false; + } else { + if (!ctx->write(ctx, wb->data, wb->head)) return false; + } + SPALL_BUFFER_PROFILE_END("Buffer Flush"); + } + wb->head = 0; + return true; +} + +SPALL_FN SPALL_FORCEINLINE bool spall__buffer_write(SpallProfile *ctx, SpallBuffer *wb, void *p, size_t n) { + // precon: !wb || wb->head < wb->length + // precon: !ctx || ctx->write + if (!wb) return ctx->write && ctx->write(ctx, p, n); +#ifdef SPALL_DEBUG + if (wb->ctx != ctx) return false; // Buffer must be bound to this context (or to NULL) +#endif + if (wb->head + n > wb->length && !spall__buffer_flush(ctx, wb)) return false; + if (n > wb->length) { + SPALL_BUFFER_PROFILE_BEGIN(); + if (!ctx->write || !ctx->write(ctx, p, n)) return false; + SPALL_BUFFER_PROFILE_END("Unbuffered Write"); + return true; + } + memcpy((char *)wb->data + wb->head, p, n); + wb->head += n; + return true; +} + +SPALL_FN bool spall_buffer_flush(SpallProfile *ctx, SpallBuffer *wb) { +#ifdef SPALL_DEBUG + if (!wb) return false; + if (!wb->data) return false; +#endif + + if (!spall__buffer_flush(ctx, wb)) return false; + return true; +} + +SPALL_FN bool spall_buffer_init(SpallProfile *ctx, SpallBuffer *wb) { + if (!spall_buffer_flush(NULL, wb)) return false; + wb->ctx = ctx; + return true; +} +SPALL_FN bool spall_buffer_quit(SpallProfile *ctx, SpallBuffer *wb) { + if (!spall_buffer_flush(ctx, wb)) return false; + wb->ctx = NULL; + return true; +} + +SPALL_FN bool spall_buffer_abort(SpallBuffer *wb) { + if (!wb) return false; + wb->ctx = NULL; + if (!spall__buffer_flush(NULL, wb)) return false; + return true; +} + +SPALL_FN size_t spall_build_header(void *buffer, size_t rem_size, double timestamp_unit) { + size_t header_size = sizeof(SpallHeader); + if (header_size > rem_size) { + return 0; + } + + SpallHeader *header = (SpallHeader *)buffer; + header->magic_header = 0x0BADF00D; + header->version = 1; + header->timestamp_unit = timestamp_unit; + header->must_be_0 = 0; + return header_size; +} +SPALL_FN SPALL_FORCEINLINE size_t spall_build_begin(void *buffer, size_t rem_size, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) { + SpallBeginEventMax *ev = (SpallBeginEventMax *)buffer; + uint8_t trunc_name_len = (uint8_t)SPALL_MIN(name_len, 255); // will be interpreted as truncated in the app (?) + uint8_t trunc_args_len = (uint8_t)SPALL_MIN(args_len, 255); // will be interpreted as truncated in the app (?) + + size_t ev_size = sizeof(SpallBeginEvent) + trunc_name_len + trunc_args_len; + if (ev_size > rem_size) { + return 0; + } + + ev->event.type = SpallEventType_Begin; + ev->event.category = 0; + ev->event.pid = pid; + ev->event.tid = tid; + ev->event.when = when; + ev->event.name_length = trunc_name_len; + ev->event.args_length = trunc_args_len; + memcpy(ev->name_bytes, name, trunc_name_len); + memcpy(ev->name_bytes + trunc_name_len, args, trunc_args_len); + + return ev_size; +} +SPALL_FN SPALL_FORCEINLINE size_t spall_build_end(void *buffer, size_t rem_size, double when, uint32_t tid, uint32_t pid) { + size_t ev_size = sizeof(SpallEndEvent); + if (ev_size > rem_size) { + return 0; + } + + SpallEndEvent *ev = (SpallEndEvent *)buffer; + ev->type = SpallEventType_End; + ev->pid = pid; + ev->tid = tid; + ev->when = when; + + return ev_size; +} + +SPALL_FN void spall_quit(SpallProfile *ctx) { + if (!ctx) return; + if (ctx->close) ctx->close(ctx); + + memset(ctx, 0, sizeof(*ctx)); +} + +SPALL_FN SpallProfile spall_init_callbacks(double timestamp_unit, + SpallWriteCallback write, + SpallFlushCallback flush, + SpallCloseCallback close, + void *userdata, + bool is_json) { + SpallProfile ctx; + memset(&ctx, 0, sizeof(ctx)); + if (timestamp_unit < 0) return ctx; + ctx.timestamp_unit = timestamp_unit; + ctx.is_json = is_json; + ctx.data = userdata; + ctx.write = write; + ctx.flush = flush; + ctx.close = close; + + if (ctx.is_json) { + if (!ctx.write(&ctx, "{\"traceEvents\":[\n", sizeof("{\"traceEvents\":[\n") - 1)) { + spall_quit(&ctx); + return ctx; + } + } else { + SpallHeader header; + size_t len = spall_build_header(&header, sizeof(header), timestamp_unit); + if (!ctx.write(&ctx, &header, len)) { + spall_quit(&ctx); + return ctx; + } + } + + return ctx; +} + +SPALL_FN SpallProfile spall_init_file_ex(const char *filename, double timestamp_unit, bool is_json) { + SpallProfile ctx; + memset(&ctx, 0, sizeof(ctx)); + if (!filename) return ctx; + ctx.data = fopen(filename, "wb"); // TODO: handle utf8 and long paths on windows + if (ctx.data) { // basically freopen() but we don't want to force users to lug along another macro define + fclose((FILE *)ctx.data); + ctx.data = fopen(filename, "ab"); + } + if (!ctx.data) { + spall_quit(&ctx); + return ctx; + } + ctx = spall_init_callbacks(timestamp_unit, spall__file_write, spall__file_flush, spall__file_close, ctx.data, is_json); + return ctx; +} + +SPALL_FN SpallProfile spall_init_file(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, false); } +SPALL_FN SpallProfile spall_init_file_json(const char *filename, double timestamp_unit) { return spall_init_file_ex(filename, timestamp_unit, true); } + +SPALL_FN bool spall_flush(SpallProfile *ctx) { +#ifdef SPALL_DEBUG + if (!ctx) return false; +#endif + + if (!ctx->flush || !ctx->flush(ctx)) return false; + return true; +} + +SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_args(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, const char *args, signed long args_len, double when, uint32_t tid, uint32_t pid) { +#ifdef SPALL_DEBUG + if (!ctx) return false; + if (!name) return false; + if (name_len <= 0) return false; + if (!wb) return false; +#endif + + if (ctx->is_json) { + char buf[1024]; + int buf_len = snprintf(buf, sizeof(buf), + "{\"ph\":\"B\",\"ts\":%f,\"pid\":%u,\"tid\":%u,\"name\":\"%.*s\",\"args\":\"%.*s\"},\n", + when * ctx->timestamp_unit, pid, tid, (int)(uint8_t)name_len, name, (int)(uint8_t)args_len, args); + if (buf_len <= 0) return false; + if (buf_len >= sizeof(buf)) return false; + if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false; + } else { + if ((wb->head + sizeof(SpallBeginEventMax)) > wb->length) { + if (!spall__buffer_flush(ctx, wb)) { + return false; + } + } + + wb->head += spall_build_begin((char *)wb->data + wb->head, wb->length - wb->head, name, name_len, args, args_len, when, tid, pid); + } + + return true; +} + +SPALL_FN SPALL_FORCEINLINE bool spall_buffer_begin_ex(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when, uint32_t tid, uint32_t pid) { + return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, tid, pid); +} + +SPALL_FN bool spall_buffer_begin(SpallProfile *ctx, SpallBuffer *wb, const char *name, signed long name_len, double when) { + return spall_buffer_begin_args(ctx, wb, name, name_len, "", 0, when, 0, 0); +} + +SPALL_FN SPALL_FORCEINLINE bool spall_buffer_end_ex(SpallProfile *ctx, SpallBuffer *wb, double when, uint32_t tid, uint32_t pid) { +#ifdef SPALL_DEBUG + if (!ctx) return false; + if (!wb) return false; +#endif + + if (ctx->is_json) { + char buf[512]; + int buf_len = snprintf(buf, sizeof(buf), + "{\"ph\":\"E\",\"ts\":%f,\"pid\":%u,\"tid\":%u},\n", + when * ctx->timestamp_unit, pid, tid); + if (buf_len <= 0) return false; + if (buf_len >= sizeof(buf)) return false; + if (!spall__buffer_write(ctx, wb, buf, buf_len)) return false; + } else { + if ((wb->head + sizeof(SpallEndEvent)) > wb->length) { + if (!spall__buffer_flush(ctx, wb)) { + return false; + } + } + + wb->head += spall_build_end((char *)wb->data + wb->head, wb->length - wb->head, when, tid, pid); + } + + return true; +} + +SPALL_FN bool spall_buffer_end(SpallProfile *ctx, SpallBuffer *wb, double when) { return spall_buffer_end_ex(ctx, wb, when, 0, 0); } + +SPALL_FN SPALL_FORCEINLINE void spall__buffer_profile(SpallProfile *ctx, SpallBuffer *wb, double spall_time_begin, double spall_time_end, const char *name, int name_len) { + // precon: ctx + // precon: ctx->write + char temp_buffer_data[2048]; + SpallBuffer temp_buffer = {temp_buffer_data, sizeof(temp_buffer_data)}; + if (!spall_buffer_begin_ex(ctx, &temp_buffer, name, name_len, spall_time_begin, (uint32_t)(uintptr_t)wb->data, 4222222222)) return; + if (!spall_buffer_end_ex(ctx, &temp_buffer, spall_time_end, (uint32_t)(uintptr_t)wb->data, 4222222222)) return; + if (ctx->write) ctx->write(ctx, temp_buffer_data, temp_buffer.head); +} + +#ifdef __cplusplus +} +#endif + +#endif // SPALL_H \ No newline at end of file