Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLAY_STRING should only be used for constant strings #70

Open
mfbulut opened this issue Dec 21, 2024 · 8 comments
Open

CLAY_STRING should only be used for constant strings #70

mfbulut opened this issue Dec 21, 2024 · 8 comments
Assignees
Labels
enhancement New feature or request

Comments

@mfbulut
Copy link

mfbulut commented Dec 21, 2024

Environment:

  • OS: Windows 11
  • Renderer: raylib
  • GPU: NVIDIA RTX 3060

I was writing a small demo to test if I can use Clay to create a leaderboard (source code below). However, I noticed that Clay behaves incorrectly approximately 20% of the time, while it works as expected the other 80%. Every variablein my code is constant so I thought it was an issue with clay

Expected behavior:

image

Same executable 20% of the time

image
image
image
image

#define CLAY_IMPLEMENTATION
#include "clay.h"
#include "clay_renderer_raylib.c"

const uint32_t FONT_ID_BODY_24 = 0;
const uint32_t FONT_ID_BODY_16 = 1;
#define COLOR_ORANGE (Clay_Color) {225, 138, 50, 255}
#define COLOR_BLUE (Clay_Color) {111, 173, 162, 255}

#define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = vector.x, .y = vector.y }

typedef struct {
    char rank[20];
    char name[20];
    char money[20];
} LeaderboardEntry;

LeaderboardEntry leaderboard2[] = {
    (LeaderboardEntry){"1", "richman","1000003"},
    (LeaderboardEntry){"2", "ali","3100"},
    (LeaderboardEntry){"3", "ahmed","1600"},
    (LeaderboardEntry){"4", "brokefurk","3"},
    (LeaderboardEntry){"5", "richman","1000003"},
    (LeaderboardEntry){"6", "ali","3100"},
    (LeaderboardEntry){"7", "ahmed","1600"},
    (LeaderboardEntry){"8", "brokefurk","3"},
    (LeaderboardEntry){"9", "brokefurk","3"},
    (LeaderboardEntry){"10", "brokefurk","3"},
    (LeaderboardEntry){"1", "richman","1000003"},
    (LeaderboardEntry){"2", "ali","3100"},
    (LeaderboardEntry){"3", "ahmed","1600"},
    (LeaderboardEntry){"4", "brokefurk","3"},
    (LeaderboardEntry){"5", "richman","1000003"},
    (LeaderboardEntry){"6", "ali","3100"},
    (LeaderboardEntry){"7", "ahmed","1600"},
    (LeaderboardEntry){"8", "brokefurk","3"},
    (LeaderboardEntry){"9", "brokefurk","3"},
    (LeaderboardEntry){"10","brokefurk","3"},
    (LeaderboardEntry){"1", "richman","1000003"},
    (LeaderboardEntry){"2", "ali","3100"},
    (LeaderboardEntry){"3", "ahmed","1600"},
    (LeaderboardEntry){"4", "brokefurk","3"},
    (LeaderboardEntry){"5", "richman","1000003"},
    (LeaderboardEntry){"6", "ali","3100"},
    (LeaderboardEntry){"7", "ahmed","1600"},
    (LeaderboardEntry){"8", "brokefurk","3"},
    (LeaderboardEntry){"9", "brokefurk","3"},
    (LeaderboardEntry){"10", "brokefurk","3"},
    (LeaderboardEntry){"1", "richman","1000003"},
    (LeaderboardEntry){"2", "ali","3100"},
    (LeaderboardEntry){"3", "ahmed","1600"},
    (LeaderboardEntry){"4", "brokefurk","3"},
    (LeaderboardEntry){"5", "richman","1000003"},
    (LeaderboardEntry){"6", "ali","3100"},
    (LeaderboardEntry){"7", "ahmed","1600"},
    (LeaderboardEntry){"8", "brokefurk","3"},
    (LeaderboardEntry){"9", "brokefurk","3"},
    (LeaderboardEntry){"10", "brokefurk","3"},
    (LeaderboardEntry){"1", "richman","1000003"},
    (LeaderboardEntry){"2", "ali","3100"},
    (LeaderboardEntry){"3", "ahmed","1600"},
    (LeaderboardEntry){"4", "brokefurk","3"},
    (LeaderboardEntry){"5", "richman","1000003"},
    (LeaderboardEntry){"6", "ali","3100"},
    (LeaderboardEntry){"7", "ahmed","1600"},
    (LeaderboardEntry){"8", "brokefurk","3"},
    (LeaderboardEntry){"9", "brokefurk","3"},
    (LeaderboardEntry){"10","brokefurk","3"},

};

int count = 50;

Clay_RenderCommandArray CreateLayout() {
    Clay_BeginLayout();
    CLAY(CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }),
    CLAY_RECTANGLE({ .color = {200, 200, 200, 255} })) {
        CLAY(CLAY_ID("MainContent"),
            CLAY_SCROLL({ .vertical = true }),
            CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_FIXED(600), CLAY_SIZING_FIXED(300) }  }),
            CLAY_RECTANGLE({ .color = {200, 200, 255, 255} }))
        {
            for (int i = 0; i < count; i++) {
                int rowColor = (i % 2) ? 40 : 60;
                CLAY(
                    CLAY_LAYOUT({ .childGap = 4, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(40) }  }),
                    CLAY_RECTANGLE({ .color = {0, 0, 0, 255} }))
                {

                    CLAY(
                        CLAY_LAYOUT({.sizing = { CLAY_SIZING_PERCENT(0.1f), CLAY_SIZING_GROW() }, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}}),
                        CLAY_RECTANGLE({ .color = {rowColor, rowColor, rowColor, 255} }))
                    {
                         CLAY_TEXT(CLAY_STRING(leaderboard2[i].rank), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {255,255,255,255} }));
                    }

                    CLAY(
                        CLAY_LAYOUT({.sizing = { CLAY_SIZING_PERCENT(0.45f), CLAY_SIZING_GROW() }, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }),
                        CLAY_RECTANGLE({ .color = {rowColor, rowColor, rowColor, 255} }))
                    {
                         CLAY_TEXT(CLAY_STRING(leaderboard2[i].name), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {255,255,255,255} }));
                    }
                    CLAY(
                        CLAY_LAYOUT({.sizing = { CLAY_SIZING_PERCENT(0.45f), CLAY_SIZING_GROW() }, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} }),
                        CLAY_RECTANGLE({ .color = {rowColor, rowColor, rowColor, 255} }))
                    {
                         CLAY_TEXT(CLAY_STRING(leaderboard2[i].money), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {255,255,255,255} }));
                    }

                }
            }

        }

        Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent")));
        if (scrollData.found) {
            CLAY(CLAY_ID("ScrollBar"),
                CLAY_FLOATING({
                    .offset = { .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height },
                    .zIndex = 1,
                    .parentId = Clay_GetElementId(CLAY_STRING("MainContent")).id,
                    .attachment = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP}
                })
            ) {
                CLAY(CLAY_ID("ScrollBarButton"),
                    CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(10), CLAY_SIZING_FIXED((scrollData.scrollContainerDimensions.height / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height) }}),
                    CLAY_RECTANGLE({ .cornerRadius = {6}, .color = Clay_PointerOver(Clay__HashString(CLAY_STRING("ScrollBar"), 0, 0)) ? (Clay_Color){255, 255, 255, 180} : (Clay_Color){255, 255, 255, 120} })
                ) {}
            }
        }
    }
    return Clay_EndLayout();
}

typedef struct
{
    Clay_Vector2 clickOrigin;
    Clay_Vector2 positionOrigin;
    bool mouseDown;
} ScrollbarData;

ScrollbarData scrollbarData = (ScrollbarData) {};

bool debugEnabled = false;

void UpdateDrawFrame(void)
{
    Vector2 mouseWheelDelta = GetMouseWheelMoveV();
    float mouseWheelX = mouseWheelDelta.x;
    float mouseWheelY = mouseWheelDelta.y;

    if (IsKeyPressed(KEY_D)) {
        debugEnabled = !debugEnabled;
        Clay_SetDebugModeEnabled(debugEnabled);
    }

    Clay_Vector2 mousePosition = RAYLIB_VECTOR2_TO_CLAY_VECTOR2(GetMousePosition());
    Clay_SetPointerState(mousePosition, IsMouseButtonDown(0) && !scrollbarData.mouseDown);
    Clay_SetLayoutDimensions((Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() });
    if (!IsMouseButtonDown(0)) {
        scrollbarData.mouseDown = false;
    }

    if (IsMouseButtonDown(0) && !scrollbarData.mouseDown && Clay_PointerOver(Clay__HashString(CLAY_STRING("ScrollBar"), 0, 0))) {
        Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0, 0));
        scrollbarData.clickOrigin = mousePosition;
        scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
        scrollbarData.mouseDown = true;
    } else if (scrollbarData.mouseDown) {
        Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0, 0));
        if (scrollContainerData.contentDimensions.height > 0) {
            Clay_Vector2 ratio = (Clay_Vector2) {
                scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
                scrollContainerData.contentDimensions.height / scrollContainerData.scrollContainerDimensions.height,
            };
            if (scrollContainerData.config.vertical) {
                scrollContainerData.scrollPosition->y = scrollbarData.positionOrigin.y + (scrollbarData.clickOrigin.y - mousePosition.y) * ratio.y;
            }
            if (scrollContainerData.config.horizontal) {
                scrollContainerData.scrollPosition->x = scrollbarData.positionOrigin.x + (scrollbarData.clickOrigin.x - mousePosition.x) * ratio.x;
            }
        }
    }

    Clay_UpdateScrollContainers(true, (Clay_Vector2) {mouseWheelX, mouseWheelY}, GetFrameTime());

    double currentTime = GetTime();
    Clay_RenderCommandArray renderCommands = CreateLayout();
    printf("layout time: %f microseconds\n", (GetTime() - currentTime) * 1000 * 1000);

    BeginDrawing();
    ClearBackground(BLACK);
    Clay_Raylib_Render(renderCommands);
    EndDrawing();
}

int main() {
    uint64_t totalMemorySize = Clay_MinMemorySize();
    Clay_Arena clayMemory = (Clay_Arena) {.memory = malloc(totalMemorySize), .capacity = totalMemorySize };
    Clay_SetMeasureTextFunction(Raylib_MeasureText);
    Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() });
    Clay_Raylib_Initialize(1200, 540, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT);

    Raylib_fonts[FONT_ID_BODY_24] = (Raylib_Font) {
        .font = LoadFontEx("Poppins-Regular.ttf", 48, 0, 400),
        .fontId = FONT_ID_BODY_24,
    };
	SetTextureFilter(Raylib_fonts[FONT_ID_BODY_24].font.texture, TEXTURE_FILTER_BILINEAR);

    Raylib_fonts[FONT_ID_BODY_16] = (Raylib_Font) {
        .font = LoadFontEx("Poppins-Regular.ttf", 32, 0, 400),
        .fontId = FONT_ID_BODY_16,
    };
    SetTextureFilter(Raylib_fonts[FONT_ID_BODY_16].font.texture, TEXTURE_FILTER_BILINEAR);

    while (!WindowShouldClose())
    {
        UpdateDrawFrame();
    }
}
@nicbarker
Copy link
Owner

Ah I have seen this myself. I believe font loading in raylib is not synchronous, so there is a race condition occurring.
Clay is measuring and caching the font before it's 100% ready. I will have a read and confirm for you if there is a way to check if it's ready yet 👍

@mfbulut
Copy link
Author

mfbulut commented Dec 21, 2024

It makes a lot of sense; that's why it only happens in text elements. But why would it keep happening after the font has loaded if you are recreating ui every frame like other immediate mode libraryes

@nicbarker
Copy link
Owner

nicbarker commented Dec 21, 2024

It makes a lot of sense; that's why it only happens in text elements. But why would it keep happening after the font has loaded if you are recreating ui every frame

Clay internally keeps a hashmap cache of text measurements because measuring text is very expensive 🙂
So if it gets measured incorrectly the first time, it will continue to retrieve the incorrect value from the cache afterwards.

Would you be able to try calling UnloadFont before you exit your program? it's mentioned down the bottom of this example in raylib: just want to rule out something happening with file handles not being released
https://www.raylib.com/examples/text/loader.html?name=text_raylib_fonts

@mfbulut mfbulut changed the title Clay is not deterministic Clay text is not deterministic Dec 21, 2024
@mfbulut
Copy link
Author

mfbulut commented Dec 21, 2024

I added UnloadFont but it still keeps happening. I will try adding sleep for 1 second to check if raylib loads fonts asynchronously

@nicbarker
Copy link
Owner

I copy pasted your code from above and caught the error in my debugger locally so looking at it now 👍

@nicbarker
Copy link
Owner

Ah, so this one is my fault. It's not well documented at all, but the CLAY_STRING macro is intended for use with constant c strings e.g. CLAY_STRING("someValue").

I modified your code and it seems to work like this:

typedef struct {
    Clay_String rank;
    Clay_String name;
    Clay_String money;
} LeaderboardEntry;
LeaderboardEntry leaderboard2[] = {
        (LeaderboardEntry){CLAY_STRING("1"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("2"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("3"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("4"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("5"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("6"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("7"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("8"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("9"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("10"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("1"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("2"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("3"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("4"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("5"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("6"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("7"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("8"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("9"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("10"),CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("1"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("2"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("3"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("4"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("5"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("6"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("7"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("8"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("9"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("10"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("1"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("2"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("3"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("4"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("5"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("6"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("7"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("8"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("9"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("10"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("1"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("2"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("3"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("4"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("5"), CLAY_STRING("richman"), CLAY_STRING("1000003")},
        (LeaderboardEntry){CLAY_STRING("6"), CLAY_STRING("ali"), CLAY_STRING("3100")},
        (LeaderboardEntry){CLAY_STRING("7"), CLAY_STRING("ahmed"), CLAY_STRING("1600")},
        (LeaderboardEntry){CLAY_STRING("8"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("9"), CLAY_STRING("brokefurk"), CLAY_STRING("3")},
        (LeaderboardEntry){CLAY_STRING("10"),CLAY_STRING("brokefurk"), CLAY_STRING("3")},
};

Rendering each as
CLAY_TEXT(leaderboard2[i].rank, // etc

Please let me know if this works for you 🙂

@mfbulut
Copy link
Author

mfbulut commented Dec 21, 2024

Thank you for finding the source of this issue, in my use case I get the data from a web server so i can't use constant variables.
But this happens way after the font has loaded so it wouldn't be an issue right?

Also can you explain why CLAY_STRING dont work well with dynamic text

@nicbarker
Copy link
Owner

nicbarker commented Dec 21, 2024

No worries, in that case you can just construct the strings dynamically using:

(Clay_String) { .length = strlen(your_c_string), .chars = your_c_string };

CLAY_STRING works nicely because the compiler knows how long a constant c string is at compile time so we can get the length with sizeof. That approach however doesn't work for dynamic strings, we need to walk through each char one by one until we find the null terminator, using something like strlen 🙂

@nicbarker nicbarker changed the title Clay text is not deterministic CLAY_STRING should only be used for constant strings Dec 21, 2024
@nicbarker nicbarker self-assigned this Dec 21, 2024
@nicbarker nicbarker added the enhancement New feature or request label Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants