Last active
November 18, 2023 22:49
-
-
Save vurtun/a563c0136b9cd314ada25f10c5982f9e to your computer and use it in GitHub Desktop.
Revisions
-
vurtun revised this gist
Oct 29, 2020 . 1 changed file with 10 additions and 9 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -20,9 +20,10 @@ the functionality should further process at all. Builder APIs in contrast are placed on the data conversion/packing end of things. They take in a specific input format, process it and finally output the same data in a different format. Lets look at a relativ well know example of an API taking in scaled vector drawing commands and convert it directly into vertex, element and draw commands for rendering inside a GPU. This kind of API is for example is used inside [nuklear](https://github.com/vurtun/nuklear) to convert an internal primitive buffer (rects, lines, text,...) into an graphics API accessable vertex format: ```c enum svg_vtx_lay_attr { @@ -257,7 +258,7 @@ int gui_tbl_hdr_end_mem(struct gui_tbl_hdr **res, struct gui_tbl_builder*, void int gui_tbl_hdr_end(struct gui_tbl_hdr **res, struct gui_tbl_builder*, struct arena *); ``` In this API `struct gui_tbl_hdr` is our output which is build up inside a `struct gui_tbl_builder` object. The API allows adding new table columns either by specifing all column data as parameter or directly as our builder internal temporary column format `gui_tbl_col`. The reason here specifically for this problem @@ -408,10 +409,10 @@ personally prefer having my `struct` public as much as possible in some circumstances it is useful to abstract away the implementation details. Hopefully I could provide a small overview over immediate mode builder APIs. They are probably the most useful data conversion tool I use and are in my experience very intuitive both in implementation and usage. They work especially well starting any solution by setting up your data in a format the perfectly fits the algorithm to solve your problem which often is not necessarily the data format you get. It almost always makes sense to transform your input data into your prefered processing data format instead of trying to fit your solution around a non-fitting data format. -
vurtun revised this gist
Oct 29, 2020 . 1 changed file with 4 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -289,18 +289,18 @@ enum gui_tbl_col_flags { GUI_TBL_COL_DATE = (1 << 3), GUI_TBL_COL_ALL = 0xffffffffu }; static const struct gui_col default_columns[] = { { .title = "Name", .type = GUI_TBL_HDR_COL_FIX, .size = 180, .con = { .min = 100, .max = 400 } }, { .title = "Path", .type = GUI_TBL_HDR_COL_DYN, .size = 1.0f, .con = { .min = 150, .max = 400 } } }; static const struct gui_col optional_columns[] = { { .title = "Type", .type = GUI_TBL_HDR_COL_FIX, .size = 50, .con = { .min = 50, .max = 400 } }, { .title = "Size", .type = GUI_TBL_HDR_COL_FIX, .size = 60, .con = { .min = 50, .max = 400 } } { .title = "Permission", .type = GUI_TBL_HDR_COL_FIX, .size = 80, .con = { .min = 50, .max = 400 } } { .title = "Date Modified", .type = GUI_TBL_HDR_COL_FIX, .size = 100, .con = { .min = 150, .max = 400 } } }; #define MAX_COLS 32 struct gui_col cols[MAX_COLS]; static unsigned col_bitmask = GUI_TBL_COL_ALL; struct gui_tbl_hdr *hdr = 0; @@ -328,7 +328,7 @@ Since the adding column based on bitset is quite useful we now know that it coul be useful in general and can add another API function: ```c void gui_tbl_hdr_add_mask(struct gui_tbl_builder *b, const struct gui_col *cols, const unsigned *mask, int cnt) ``` Which simplifies our code further by moving the loop and the bit check inside an API code: -
vurtun revised this gist
Oct 29, 2020 . 1 changed file with 148 additions and 45 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -156,10 +156,21 @@ data flow for builder APIs. The reason for using a svg type renderer as the firs case no temporary format and resources are required instead primitives are directly converted into vertexes, elements and draw commands further simplifying the API. Builders are the prototypical immediate mode APIs going so far as not keeping state between each run of `svg_begin` to `svg_end` and the having the caller only directly push new state and therefore never mutates any buffer state. Most often builder have an initilizing functions call, `svg_begin` in this case and a final processing call at the end like `svg_end`. In between data is pushed by one or more function calls. Internally builders either directly convert the passed input data into the desired output format like in this case or stored into a temporary format that is later, often at the commiting `end` call, converted to the final output format. Now lets move away from the introductory `svg` builder example and lets look at a piece of code I decided to extend from static compile time data tables to also be able to choose table columns in runtime and in general abstract over some internal implementation details: ```c static const char *tbl_titles[] = {"Name", "Path", "Type", @@ -178,15 +189,43 @@ int tbl_cols[cntof(tbl_panes)]; gui_tbl_hdr(ctx, &tbl, tbl_titles, tbl_cols, tbl_panes, tbl_sep, tbl_con, cntof(tbl_panes)); ``` The code snippet defines a graphical user interface table header columns seen here:  The `tbl_titles` array describes the string title for each column while the size of each column is defined inside the `tbl_panes` array. It also includes some oddities. Not only column but also separator size is specified. Also pixel sizes are defined as negative while dynamic ratios are defined as positive float weights. Final compile time constant is array `tbl_con` of bounding constraints for each column including each separator. The reason for the odd inclusion of separator sizes is that internally the table header uses a splitter which in term uses a general layouting functions. Layouting however requires the size of each element including the separator for both size and constraints. In other words implementation details spilling out into the API. In reality it is not that bad but still something to take care of in combination with compile time limitations. Now we get to our only actual runtime state array `tbl_sep`. Since each table column can be resized we need to store the position of each separator that is converted each run into local state `tbl_cols` which stores the size of each column and separator. Overall a lot of information not really interesting for the user of the API. From user perspective we just want to define each column with title, size and constraint and not bother with all these details. So lets look at a potential builder API to construct our required internal table header column format from a more user centric input data subset: ```c enum gui_col_type { GUI_COL_FIX, GUI_COL_DYN, }; struct gui_col { const char *title; int type; float size; struct gui_lay_con con; }; @@ -196,7 +235,7 @@ struct gui_tbl_hdr_builder { int out_size; int col_cnt; // internal struct gui_col *cols; int buf_cnt; }; struct gui_tbl_hdr { @@ -209,15 +248,39 @@ struct gui_tbl_hdr { float *panes; int *sep; }; void gui_tbl_hdr_begin(struct gui_tbl_builder*, struct arena *a, int col_cnt); void gui_tbl_hdr_begin_fix(struct gui_tbl_builder*, struct gui_col *buf, int cnt); void gui_tbl_hdr_add_fix(struct gui_tbl_builder*, const char *title, int size, struct gui_lay_con con); void gui_tbl_hdr_add_dyn(struct gui_tbl_builder*, const char *title, struct gui_lay_con con); void gui_tbl_hdr_add(struct gui_tbl_builder*, const struct gui_col *cols, int cnt); int gui_tbl_hdr_end_mem(struct gui_tbl_hdr **res, struct gui_tbl_builder*, void *memory); int gui_tbl_hdr_end(struct gui_tbl_hdr **res, struct gui_tbl_builder*, struct arena *); ``` In this API `struct gui_tbl_hdr` is our output, set up inside a `struct gui_tbl_builder` object. The API allows adding new table columns either by specifing all column data as parameter or directly as our builder internal temporary column format `gui_tbl_col`. The reason here specifically for this problem is that the number and existence of all table columns is almost always known at compile time and therefore it makes sense to directly just define them in compile time data. For storing our temporary table header columns an linear allocator arena can be specified that only allows to allocate and frees everything, after the `end` call. As an alternativ based on the fact we often know the maximum number of column a static array can be passed as a buffer to be filled as well. Finally since our resulting `gui_tbl_hdr` object contains a number of dynamic arrays the `end` call needs some dynamic memory. Lucky enough our builder object tracks the required memory in variable `out_size`. So it is possible to allocate the required amout of memory and pass it into `gui_tbl_hdr_end_mem`. Alternatively it is also possible to just call `gui_tbl_hdr_end` and pass a memory arena. Now lets look at an example for how the API could used. By the way this code only needs to be called once at the beginning and once for each time the table columns change. So this probably be inside a functions with `col_bitmask` as input and a `gui_tbl_hdr` object as output: ```c enum gui_tbl_col_flags { GUI_TBL_COL_TYPE = (1 << 0), @@ -242,61 +305,86 @@ static unsigned col_bitmask = GUI_TBL_COL_ALL; struct gui_tbl_hdr *hdr = 0; struct gui_tbl_builder b = {0}; gui_tbl_hdr_begin_fix(&b, cols, MAX_COLS); { gui_tbl_hdr_add(&b, default_columns, cntof(default_columns)); for (int i = 0; i < cntof(optional_columns); ++i) { if (col_bitmask & (1 << i)) { gui_tbl_hdr_add(b, &optional_columns[i], 1); } } } gui_tbl_hdr_end(&hdr, &b, calloc(1, b.out_size)); ``` I divided our previous columns into two separate camps. The first one are columns that cannot be hidden and most be shown and the second are optional columns. In addition I limit the number of total possible columns to `32` and just use a static array as internal buffer for the `gui_tbl_builder`. Our not optional columns are directly added while each optional column is added based on a bitset, by walking over each bit and only adding the column if the corrosponding bit is set. Since the adding column based on bitset is quite useful we now know that it could be useful in general and can add another API function: ```c void gui_tbl_hdr_add_mask(struct gui_tbl_builder *b, const struct gui_tbl_hdr_builder_col *cols, const unsigned *mask, int cnt) ``` Which simplifies our code further by moving the loop and the bit check inside an API code: ```c struct gui_tbl_hdr *hdr = 0; struct gui_tbl_builder b = {0}; gui_tbl_hdr_begin_fix(&b, cols, MAX_COLS); { gui_tbl_hdr_add(&b, default_columns, cntof(default_columns)); gui_tbl_hdr_add_mask(&b, optional_columns, col_bitmask, cntof(optional_columns)); } gui_tbl_hdr_end(&hdr, &b, calloc(1, b.out_size)); ``` Now as soon as our `gui_tbl_hdr` object was created it can be called each frame by passing it directly further simplifying the code: ```c // ... gui_tbl_hdr(ctx, &tbl, hdr); // ... ``` Now what I left out for this example is column resorting which would be another array filled with the column sorting order. I will leave it out for now since it brings no further depth for the builder API explanation. Now a last concept I want to address even if it doesn't directly make a lot of sense for this particular example is the fact that it is also possible to serialize the final output data into compile time tables: ```c void gui_tbl_hdr_print(FILE *fp, const struct gui_tbl_hdr *hdr, const char *prefix); ``` which is called by: ```c gui_tbl_hdr_print(stdout, hdr, "tbl"); ``` and produces output: ```c static const char *tbl_titles[] = {"Name", "Path", "Type", "Size", "Permission", "Date Modified"}; static const float tbl_panes[] = {-180.f, -6.0f, -225.0f, -6.0f, -50.0f, -6.0f, -60.0f, -6.0f, -80.0f, -6.0f, 1.0f, -6.0f}; static const struct gui_lay_con tbl_con[cntof(tbl_panes)] = { {.min = 100, .max = 400}, {.min = 6, .max = 6}, {.min = 150, .max = 400}, {.min = 6, .max = 6}, {.min = 50, .max = 400}, {.min = 6, .max = 6}, {.min = 50, .max = 400}, {.min = 6, .max = 6}, {.min = 50, .max = 400}, {.min = 6, .max = 6}, {.min = 100, .max = 400}}; static int tbl_sep[cntof(tbl_panes)]; int tbl_cols[cntof(tbl_panes)]; ``` which brings us full circle again. Like I said not overly useful in this example but I had a case where it proved to be a vital addition. The final example I want to show is a skeleton builder useful for building a animation skeleton: ```c struct skeleton; @@ -312,3 +400,18 @@ struct skeleton* skel_end(struct skel_builder *b, struct arena *a); void skel_end_mem(struct skel_builder *b, void *memory); ``` We have our usual `begin` and `end` call including a functions to compute the final skeleton output memory size, temporary allocators and a functions to add a bone into the skeleton. The actually functionality is not so important here. I rather want to focus on the fact the the `skel_builder` can be kept private as well. While I personally prefer having my `struct` public as much as possible in some circumstances it is useful to abstract away the implementation details. Hopefully I could provide a small overview over immediate mode builder APIs. They are probably the most useful data conversion tools I use and are in my experience very intuitive both in implementation and usage side. The work especially well if you similar to me start any solution by setting up your data in a format the perfectly fits the algorithm to solve your problem that often is not necessarily the data format you are given. It almost always makes sense to transform your input data into your prefered processing data format instead of trying to fit your solution around a non-fitting data format. -
vurtun revised this gist
Oct 29, 2020 . 1 changed file with 43 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -191,18 +191,18 @@ struct gui_tbl_hdr_builder_col { struct gui_lay_con con; }; struct gui_tbl_hdr_builder { // out int err; int out_size; int col_cnt; // internal struct gui_tbl_hdr_builder_col *cols; int buf_cnt; }; struct gui_tbl_hdr { // out int col_cnt; int *cols; // internal const char **titles; struct gui_lay_con *cons; @@ -259,7 +259,6 @@ gui_tbl_hdr_builder_end(&hdr, &b, calloc(1, b.out_size)); void gui_tbl_hdr_builder_add_mask(struct gui_tbl_builder *b, const struct gui_tbl_hdr_builder_col *cols, const unsigned *mask, int cnt) ``` ```c struct gui_tbl_hdr *hdr = 0; struct gui_tbl_builder b = {0}; @@ -272,4 +271,44 @@ gui_tbl_hdr_builder_end(&hdr, &b, calloc(1, b.out_size)); // ... gui_tbl_hdr(ctx, &tbl, hdr); ``` ```c void gui_tbl_hdr_builder_print(FILE *fp, const struct gui_tbl_hdr *hdr, const char *prefix); ``` ```c gui_tbl_hdr_builder_print(stdout, hdr, "tbl"); // Output: // ---------------------------------------------------------------- // static const char *tbl_titles[] = {"Name", "Path", "Type", // "Size", "Permission", "Date Modified"}; // static const float tbl_panes[] = {-180.f, -6.0f, -225.0f, -6.0f, -50.0f, -6.0f, // -60.0f, -6.0f, -80.0f, -6.0f, 1.0f, -6.0f}; // static const struct gui_lay_con tbl_con[cntof(tbl_panes)] = { // {.min = 100, .max = 400}, {.min = 6, .max = 6}, // {.min = 150, .max = 400}, {.min = 6, .max = 6}, // {.min = 50, .max = 400}, {.min = 6, .max = 6}, // {.min = 50, .max = 400}, {.min = 6, .max = 6}, // {.min = 50, .max = 400}, {.min = 6, .max = 6}, // {.min = 100, .max = 400}}; // static int tbl_sep[cntof(tbl_panes)]; // int tbl_cols[cntof(tbl_panes)]; // ---------------------------------------------------------------- ``` ```c struct skeleton; struct skel_builder; struct skel_builder* skel_begin(struct arena *tmp, int bone_cnt); int skel_get_size(const struct skel_builder * b); void skel_add_bone(struct skel_builder *b, int parent_idx, float *local_pos3, float *local_orient4, float *world_pos3, float *world_orient4); void skel_set_parent(struct skel_builder *b, int bone_idx, int parent_idx); struct skeleton* skel_end(struct skel_builder *b, struct arena *a); void skel_end_mem(struct skel_builder *b, void *memory); ``` -
vurtun revised this gist
Oct 29, 2020 . 1 changed file with 17 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -238,15 +238,15 @@ static const struct gui_tbl_hdr_builder_col optional_columns[] = { }; #define MAX_COLS 32 struct gui_tbl_hdr_builder_col cols[MAX_COLS]; static unsigned col_bitmask = GUI_TBL_COL_ALL; struct gui_tbl_hdr *hdr = 0; struct gui_tbl_builder b = {0}; gui_tbl_hdr_builder_begin_fix(&b, cols, MAX_COLS); { gui_tbl_hdr_builder_add(&b, default_columns, cntof(default_columns)); for (int i = 0; i < cntof(optional_columns); ++i) { if (col_bitmask & (1 << i)) { gui_tbl_hdr_builder_add(b, &optional_columns[i], 1); } } @@ -256,5 +256,20 @@ gui_tbl_hdr_builder_end(&hdr, &b, calloc(1, b.out_size)); ``` ```c void gui_tbl_hdr_builder_add_mask(struct gui_tbl_builder *b, const struct gui_tbl_hdr_builder_col *cols, const unsigned *mask, int cnt) ``` ```c struct gui_tbl_hdr *hdr = 0; struct gui_tbl_builder b = {0}; gui_tbl_hdr_builder_begin_fix(&b, cols, MAX_COLS); { gui_tbl_hdr_builder_add(&b, default_columns, cntof(default_columns)); gui_tbl_hdr_builder_add_mask(&b, optional_columns, col_bitmask, cntof(optional_columns)); } gui_tbl_hdr_builder_end(&hdr, &b, calloc(1, b.out_size)); // ... gui_tbl_hdr(ctx, &tbl, hdr); ``` -
vurtun revised this gist
Oct 28, 2020 . 1 changed file with 5 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -164,8 +164,8 @@ any buffer state. ```c static const char *tbl_titles[] = {"Name", "Path", "Type", "Size", "Permission", "Date Modified"}; static const float tbl_panes[] = {-180.f, -6.0f, -225.0f, -6.0f, -50.0f, -6.0f, -60.0f, -6.0f, -80.0f, -6.0f, 1.0f, -6.0f}; static const struct gui_lay_con tbl_con[cntof(tbl_panes)] = { {.min = 100, .max = 400}, {.min = 6, .max = 6}, {.min = 150, .max = 400}, {.min = 6, .max = 6}, @@ -201,11 +201,13 @@ struct gui_tbl_hdr_builder { }; struct gui_tbl_hdr { int col_cnt; int *cols; // internal const char **titles; struct gui_lay_con *cons; float *panes; int *sep; }; void gui_tbl_hdr_builder_begin(struct gui_tbl_builder *b, struct arena *a, int col_cnt); void gui_tbl_hdr_builder_begin_fix(struct gui_tbl_builder *b, struct gui_tbl_hdr_builder_col *buf, int cnt); -
vurtun revised this gist
Oct 28, 2020 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -204,7 +204,8 @@ struct gui_tbl_hdr { const char **titles; struct gui_lay_con *cons; float *panes; int *sep; int *cols; }; void gui_tbl_hdr_builder_begin(struct gui_tbl_builder *b, struct arena *a, int col_cnt); void gui_tbl_hdr_builder_begin_fix(struct gui_tbl_builder *b, struct gui_tbl_hdr_builder_col *buf, int cnt); -
vurtun revised this gist
Oct 28, 2020 . 1 changed file with 93 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,5 @@ API Design: Builder APIs (October-2020) --------------------------------------- Some time has past (three years!) since I last [wrote](https://gist.github.com/vurtun/192cac1f1818417d7b4067d60e4fe921) about API specifically about coroutines style APIs so I thought why not write another one about a different API type I encounter relatively often. The builder API. @@ -136,6 +136,7 @@ svg_begin(&svg, &cfg, &buf[0], &buf[1], &buf[2]); svg_end(&svg, 0); // draw to screen int offset = 0; svg_draw_foreach(cmd, buf[0]) { if (!cmd->elem_cnt) { continue; @@ -161,5 +162,96 @@ any buffer state. ```c static const char *tbl_titles[] = {"Name", "Path", "Type", "Size", "Permission", "Date Modified"}; static float tbl_panes[] = {-180.f, -6.0f, -225.0f, -6.0f, -50.0f, -6.0f, -60.0f, -6.0f, -80.0f, -6.0f, 1.0f, -6.0f}; static const struct gui_lay_con tbl_con[cntof(tbl_panes)] = { {.min = 100, .max = 400}, {.min = 6, .max = 6}, {.min = 150, .max = 400}, {.min = 6, .max = 6}, {.min = 50, .max = 400}, {.min = 6, .max = 6}, {.min = 50, .max = 400}, {.min = 6, .max = 6}, {.min = 50, .max = 400}, {.min = 6, .max = 6}, {.min = 100, .max = 400}}; static int tbl_sep[cntof(tbl_panes)]; int tbl_cols[cntof(tbl_panes)]; gui_tbl_hdr(ctx, &tbl, tbl_titles, tbl_cols, tbl_panes, tbl_sep, tbl_con, cntof(tbl_panes)); ``` ```c enum gui_tbl_hdr_builder_col_type { GUI_TBL_HDR_COL_FIX, GUI_TBL_HDR_COL_DYN, }; struct gui_tbl_hdr_builder_col { const char *title; enum gui_tbl_hdr_builder_col_type type; float size; struct gui_lay_con con; }; struct gui_tbl_hdr_builder { int err; int out_size; int col_cnt; // internal struct gui_tbl_hdr_builder_col *cols; int buf_cnt; }; struct gui_tbl_hdr { int col_cnt; const char **titles; struct gui_lay_con *cons; float *panes; int *state; }; void gui_tbl_hdr_builder_begin(struct gui_tbl_builder *b, struct arena *a, int col_cnt); void gui_tbl_hdr_builder_begin_fix(struct gui_tbl_builder *b, struct gui_tbl_hdr_builder_col *buf, int cnt); void gui_tbl_hdr_builder_add_fix(struct gui_tbl_builder *b, const char *title, int size, struct gui_lay_con con); void gui_tbl_hdr_builder_add_dyn(struct gui_tbl_builder *b, const char *title, struct gui_lay_con con); void gui_tbl_hdr_builder_add(struct gui_tbl_builder *b, const struct gui_tbl_hdr_builder_col *cols, int cnt); int gui_tbl_hdr_builder_end_inplace(struct gui_tbl_hdr **res, struct gui_tbl_builder *b, void *memory); int gui_tbl_hdr_builder_end(struct gui_tbl_hdr **res, struct gui_tbl_builder *b, struct arena *a); ``` ```c enum gui_tbl_col_flags { GUI_TBL_COL_TYPE = (1 << 0), GUI_TBL_COL_SIZE = (1 << 1), GUI_TBL_COL_PERMISSION = (1 << 2), GUI_TBL_COL_DATE = (1 << 3), GUI_TBL_COL_ALL = 0xffffffffu }; static const struct gui_tbl_hdr_builder_col default_columns[] = { { .title = "Name", .type = GUI_TBL_HDR_COL_FIX, .size = 180, .con = { .min = 100, .max = 400 } }, { .title = "Path", .type = GUI_TBL_HDR_COL_DYN, .size = 1.0f, .con = { .min = 150, .max = 400 } } }; static const struct gui_tbl_hdr_builder_col optional_columns[] = { { .title = "Type", .type = GUI_TBL_HDR_COL_FIX, .size = 50, .con = { .min = 50, .max = 400 } }, { .title = "Size", .type = GUI_TBL_HDR_COL_FIX, .size = 60, .con = { .min = 50, .max = 400 } } { .title = "Permission", .type = GUI_TBL_HDR_COL_FIX, .size = 80, .con = { .min = 50, .max = 400 } } { .title = "Date Modified", .type = GUI_TBL_HDR_COL_FIX, .size = 100, .con = { .min = 150, .max = 400 } } }; #define MAX_COLS 32 struct gui_tbl_hdr_builder_col cols[MAX_COLS]; static unsigned col_flags = GUI_TBL_COL_ALL; struct gui_tbl_hdr *hdr = 0; struct gui_tbl_builder b = {0}; gui_tbl_hdr_builder_begin_fix(&b, cols, MAX_COLS); { gui_tbl_hdr_builder_add(&b, default_columns, cntof(default_columns)); for (int i = 0; i < cntof(optional_columns); ++i) { if (col_flags & (1 << i)) { gui_tbl_hdr_builder_add(b, &optional_columns[i], 1); } } } gui_tbl_hdr_builder_end(&hdr, &b, calloc(1, b.out_size)); ``` ```c gui_tbl_hdr(ctx, &tbl, hdr); ``` -
vurtun revised this gist
Oct 28, 2020 . 1 changed file with 22 additions and 20 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -11,20 +11,16 @@ am writing this in and finally rendered and displayed on the screen as output. O view but still applys to all functionallity in need of an API as well. Another maybe more appplicable example is a collision API that takes in a number of collision primitives and their transformations, finds collisions between these primitives and outputs these collisions with additional information. My previous post about coroutine style APIs showed a specific practical example on how a processing API could be written with focus on granularity and resource management. Ranging from diagonal API that handles all system resource management like memory or file access to orthogonal coroutine APIs with full API user control of resources including control over if the the functionality should further process at all. Builder APIs in contrast are placed on the data conversion/packing end of things. They take in a specific input format, process it and finally output the same data in a different format. Lets look at a relativ well know example of an API taking in scaled vector drawing commands and convert it directly into vertex, element and draw commands for rendering inside a GPU. This kind of API is for example is used inside [nuklear](https://github.com/vurtun/nuklear) to convert an internal primitive buffer (rects, lines, text,...) into an graphics API accessable vertex format: @@ -79,7 +75,6 @@ void svg_line_thickness(struct svg*, float thickness); // path void svg_path_clear(struct svg*); void svg_path_line_to(struct svg*, float x, float y); void svg_path_arc_to(struct svg*, float x, float y, float radius, float a_min, float a_max, unsigned int num_seg); void svg_path_rect_to(struct svg*, float ax, float ay, float bx, float by); void svg_path_curve_to(struct svg*, const float *p2, const float *p3, const float *p4, unsigned int num_seg); @@ -105,7 +100,6 @@ Most functions and structs should be relativ straightforward to understand. A nu output vertex layout with elements like position, texture coordinates and color and their type. A number of functions for path drawing and finally a number of functions for drawing primitives. So lets look at the usage code: ```c // specify vertex layout struct your_vertex { @@ -142,22 +136,30 @@ svg_begin(&svg, &cfg, &buf[0], &buf[1], &buf[2]); svg_end(&svg, 0); // draw to screen svg_draw_foreach(cmd, buf[0]) { if (!cmd->elem_cnt) { continue; } glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); glScissor((GLint)(cmd->clip[0]), (GLint)((win_height - (GLint)(cmd->clip[1] + cmd->clip[3]))), (GLint)(cmd->clip[2]), (GLint)(cmd->clip[3]); glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); offset += cmd->elem_count; } ``` First a vertex layout is specified and memory is reserved until finally we can specify primitives to be drawn. Finally the generated output is pushed to be rendered and output on the screen via graphics API. Specifically in this example I will skip resource handling for now and instead try to focus on the basic data flow for builder APIs. The reason for using a svg type renderer as the first example is that in this case no temporary format and resources are required instead primitives are directly converted into vertexes, elements and draw commands further simplifying the API. Builder are the prototypical immediate mode APIs going so far as not keeping state between each run of `svg_begin` to `svg_end` and the having the caller only directly push new state and therefore never mutates any buffer state. ```c ``` -
vurtun revised this gist
Oct 27, 2020 . 1 changed file with 130 additions and 43 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,75 +2,162 @@ API Design: Builder APIs (October-2020) ----------------------------- Some time has past (three years!) since I last [wrote](https://gist.github.com/vurtun/192cac1f1818417d7b4067d60e4fe921) about API specifically about coroutines style APIs so I thought why not write another one about a different API type I encounter relatively often. The builder API. Now first let me take a step back and put this into 20,000 feet view on where builder APIs are located in the grant scheme. In general everything in computing is separated into input, processing and finally output. In its most basic form I am currently typing on my keyboard. All pressed keys are processed from the OS up to the browser I am writing this in and finally rendered and displayed on the screen as output. Of course this example is very user centric view but still applys to all functionallity in need of an API as well. Another maybe more appplicable example is a collision API that takes in a number of collision primitives and their transformations, finds collisions between these primitives and outputs these collisions with additional information. I went a little bit deeper into this [here](https://gist.github.com/vurtun/9782db089430167453cff6785b37bb46) (Beware the post is a little bit abstract and very verbose). My previous post about coroutine style APIs showed a specific practical example on how a processing API could be written with focus on granularity and resource management. Ranging from diagonal API that handles all system resource management like memory or file access to orthogonal coroutine APIs with full API user control of resources including control over if the the functionality should further process at all. Builder APIs in contrast are placed on the data conversion/packing side so either on input or output. They take in a specific input format, process it and finally output the same data in a different format. Lets look at a relativ well know example of an API taking in scaled vector drawing commands and convert it directly into vertex, element und draw commands for rendering inside a GPU. This kind of API is for example is used inside [nuklear](https://github.com/vurtun/nuklear) to convert an internal primitive buffer (rects, lines, text,...) into an graphics API accessable vertex format: ```c enum svg_vtx_lay_attr { SVG_VTX_POS, // ... }; enum svg_vtx_lay_fmt { SVG_FMT_SCHAR, SVG_FMT_R8G8B8, // ... }; enum svg_stroke { SVG_STROKE_OPEN, SVG_STROKE_CLOSED }; struct svg_vtx_lay_elm { enum svg_vtx_lay_attr attr; enum svg_vtx_lay_fmt fmt; int off; }; struct svg_cmd { unsigned int elem_cnt; float clip[4]; handle tex; }; struct svg_param { const struct svg_vtx_lay_elm *layout; int size; int align; }; struct svg_info { int vtx_cnt; int elm_cnt; int cmd_cnt; }; struct svg { int error; // ... }; // init void svg_begin(struct svg *b, const struct svg_param *param, struct storage *cmds, struct storage *vtx, struct storage *elm); void svg_end(struct svg *b, struct svg_info *info); // state void svg_color(struct svg*, unsigned col); void svg_rounding(struct svg*, float rounding); void svg_line_thickness(struct svg*, float thickness); // path void svg_path_clear(struct svg*); void svg_path_line_to(struct svg*, float x, float y); void svg_path_arc_to_fast(struct svg*, float x, float y, float radius, int a_min, int a_max); void svg_path_arc_to(struct svg*, float x, float y, float radius, float a_min, float a_max, unsigned int num_seg); void svg_path_rect_to(struct svg*, float ax, float ay, float bx, float by); void svg_path_curve_to(struct svg*, const float *p2, const float *p3, const float *p4, unsigned int num_seg); void svg_path_fill(struct svg*); void svg_path_stroke(struct svg*, enum svg_stroke closed); // stroke void svg_stroke_line(struct svg*, float ax, float ay, float bx, float by); void svg_stroke_rect(struct svg*, float x, float y, float w, float h, unsiged col, float rounding); void svg_stroke_triangle(struct svg*, const float *a, const float *b, const float *c); void svg_stroke_circle(struct svg*, float x, float y, float radius, unsigned int segs); void svg_stroke_curve(struct svg*, const float *p0, const float *cp0, const float *cp1, const float *p1, unsigned int seg); void svg_stroke_poly_line(struct svg*, const float *pnts, unsigned cnt, enum svg_draw_list_stroke); // fill void svg_fill_rect(struct svg*, float x, float y, float w, float h); void svg_fill_triangle(struct svg*, const float *a, const float *b, const float *c); void svg_fill_circle(struct svg*, float x, float y, float radius, unsigned segs); void svg_fill_convex(struct svg*, const float *pnts, unsigned cnt); ``` Most functions and structs should be relativ straightforward to understand. A number of structs to specify a custom output vertex layout with elements like position, texture coordinates and color and their type. A number of functions for path drawing and finally a number of functions for drawing primitives. So lets look at the usage code: ```c // specify vertex layout struct your_vertex { float pos[2]; float uv[2]; unsigned char color[4]; }; static const struct svg_vtx_lay_elm vtx_lay[] = { {SVG_VTX_POS, SVG_FMT_FLT, offsetof(struct your_vertex, pos)}, {SVG_VTX_UV, SVG_FMT_FLT, offsetof(struct your_vertex, uv)}, {SVG_VTX_COL, SVG_FMT_R8G8B8A8, offsetof(struct your_vertex, color)}, {SVG_VTX_LAY_END} }; struct svg_param cfg = {0}; cfg.size = sizeof(struct your_vertex); cfg.align = alignof(struct your_vertex); cfg.layout = vtx_lay; // setup output memory struct storage buf[3]; storage_init_fixed(&buf[0], calloc(2*1024*1024), 2*1024*1024); storage_init_fixed(&buf[1], calloc(2*1024*1024), 2*1024*1024); storage_init_fixed(&buf[2], calloc(2*1024*1024), 2*1024*1024); // draw stuff struct svg svg = {0}; svg_begin(&svg, &cfg, &buf[0], &buf[1], &buf[2]); { // ... svg_color(&svg, svg_red); svg_fill_rect(&svg, 50, 50, 120, 30); // ... } svg_end(&svg, 0); // draw to screen svg_draw_foreach(cmd, buf[0]) { if (!cmd->elem_count) continue; glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id); glScissor( (GLint)(cmd->clip[0]), (GLint)((win_height - (GLint)(cmd->clip[1] + cmd->clip[3]))), (GLint)(cmd->clip[2]), (GLint)(cmd->clip[3]); glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset); offset += cmd->elem_count; } ``` First a vertex layout is specified and memory is reserved until finally we can specify primitives to be drawn. Finally the generated output is pushed to be rendered and output on the screen via graphics API. Specifically in this example I will skip resource handling for now and instead try to focus on the basic data flow for builder APIs. The reason for using a svg type renderer as the first example is that in this case no temporary format and resources are required instead primitives are directly converted into vertexes, elements and draw commands further simplifying the API. -
vurtun revised this gist
Oct 26, 2020 . 1 changed file with 49 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -20,9 +20,57 @@ with focus on granularity and resource management. Ranging from diagonal API tha like memory or file access to orthogonal coroutine APIs with full API user control of resources including control over if the the functionality should further process at all. Builder APIs in contrast are placed on the data conversion/repacking side so either on input or output. Now this sounds weird at first since data just is and shouldn't really need any functionality and at best should already be in a format that allows some functionality to process or output. However in reality input data needs to be gathered or preprocessed. For these cases a builder API is perfect tool. Interestingly the builder itself is split into input, process and output but more on that later. So for now enough theoretical digestion and lets look at an example: ```c enum vertex_format { // ... }; enum element_format { // ... }; struct mesh_builder_parameter { void *memory; // in int required_memory; // out // internal int vertex_cnt; int element_cnt; }; struct mesh_builder { int error; // sticky error // ... }; // init extern void mesh_builder_required_memory(struct mesh_build_proc* result, int vertex_cnt, int element_cnt); extern void mesh_builder_begin_block(struct mesh_builder *b, const struct mesh_builder_parameter *param); extern void mesh_builder_begin_fixed(struct mesh_builder *b, struct memory_arena* arena, int vertex_cnt, int element_cnt); extern void mesh_builder_begin(struct mesh_builder *b); // input extern void mesh_builder_push(struct mesh_builder *b); extern void mesh_builder_posf(struct mesh_builder *b, float x, float y, float z); extern void mesh_builder_posfv(struct mesh_builder *b, float *v3); extern void mesh_builder_posd(struct mesh_builder *b, double x, double y, double z); extern void mesh_builder_posdv(struct mesh_builder *b, double *v3); extern void mesh_builder_normalf(struct mesh_builder *b, float x, float y, float z); extern void mesh_builder_normalfv(struct mesh_builder *b, float *v3); extern void mesh_builder_normald(struct mesh_builder *b, double x, double y, double z); extern void mesh_builder_normaldv(struct mesh_builder *b, double *v3); extern void mesh_builder_tex_coordf(struct mesh_builder *b, float u, float v); extern void mesh_builder_tex_coordfv(struct mesh_builder *b, float *uv); extern void mesh_builder_tex_coordd(struct mesh_builder *b, double u, double v); extern void mesh_builder_tex_coorddv(struct mesh_builder *b, double *uv); // ... // procesing/output ``` -
vurtun revised this gist
Oct 26, 2020 . 1 changed file with 5 additions and 4 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -20,8 +20,9 @@ with focus on granularity and resource management. Ranging from diagonal API tha like memory or file access to orthogonal coroutine APIs with full API user control of resources including control over if the the functionality should further process at all. Builder APIs in contrast are placed on the data conversion or repacking side so either on input or output side. Now this sounds weird at first since data just is and shouldn't really need any functionality and at best should already be in a format that allows some functionality to process or output. However in reality input data needs to be gathered or preprocessed. For these cases a builder API is perfect tool. Interestingly the builder itself is split into input, process and output but more on that later. -
vurtun revised this gist
Oct 26, 2020 . 1 changed file with 15 additions and 7 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -5,15 +5,23 @@ API specifically about coroutines style APIs so I thought why not write another relatively often. The builder API. Now first let me take a step back and put this into 20,000 feet view on where builder APIs are located in the grant scheme. In general everything in computing is separated into input, processing and finally output. In its most basic form I am currently typing on my keyboard. All pressed keys are processed from the OS up to the browser I am writing this in and finally rendered and displayed on the screen as output. Of course this example is very user centric view but still applys to all functionallity in need of an API as well. Another maybe more appplicable example is a collision API that takes in a number of collision primitives and their transformations, finds collisions between these primitives and outputs these collisions with additional information. I went a little bit deeper into this [here](https://gist.github.com/vurtun/9782db089430167453cff6785b37bb46) (Beware the post is a little bit abstract and very verbose). My previous post about coroutine style APIs showed a specific practical example on how processing APIs could be written with focus on granularity and resource management. Ranging from diagonal API that handles all system resource management like memory or file access to orthogonal coroutine APIs with full API user control of resources including control over if the the functionality should further process at all. Builder APIs in contrast are placed on the input side. Now this sounds weird at first since data just is and shouldn't really need any functionality and at best should already be in a format that allows some functionality to process it. However in reality input data needs to be gathered or preprocessed. For these cases a builder API is perfect tool. So interestingly the builder itself creating input for processing is itself split into input, process and output but more on that later. -
vurtun revised this gist
Oct 26, 2020 . 1 changed file with 17 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,2 +1,19 @@ API Design: Builder APIs (October-2020) ----------------------------- Some time has past (three years!) since I last [wrote](https://gist.github.com/vurtun/192cac1f1818417d7b4067d60e4fe921) about API specifically about coroutines style APIs so I thought why not write another one about a different API type I encounter relatively often. The builder API. Now first let me take a step back and put this into 20,000 feet view on where builder APIs are located in the grant scheme. In general everything in computing is separated into input, processing and finally output. In its most basic form I am currently typing on my keyboard. All pressed keys are processed from the OS up to the browser I am writing this in and finally rendered and displayed on the screen as output. Of course this example is very user centric view but still applys to all functionallity in need of an API as well. For example a render API has mesh, textures, shader script,... as input, does a a lot of its processing inside shaders or hardware and pixels displayed on screen as output. My previous post about coroutine style APIs focused on a specific practical example on how a processing APIs could be written with focus on granularity and resource management. Ranging from diagonal API that handles all system resource management like memory or file access to orthogonal coroutine APIs with full API user control of resources including control over if the the functionality should further process at all. Builder APIs in contrast are placed on the input end. -
vurtun created this gist
Oct 26, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,2 @@ API Design: Builder APIs (October-2020) -----------------------------