Jim ↗

blog

coding as self-care


i've tried a lot of ways of keeping track of my TODO's and so far only one has even sort of worked

first, here's some approaches that didn't work for me
a) scattering the TODO's throughout the code -- they accumulate and become meaningless and overwhelming; plus they distract from explanatory comments
b) curating the TODO's in GitHub issues -- way too much friction; requires me to be connected to the internet; end up piling up and becoming meaningless
c) no TODO list (follow your heart 🥺) -- unfortunately, my heart is a dumb dumb (boring ideas never gets done, fun/stupid ideas always get started but rarely get finished)

and here's an approach that did work for me
- i have one file called todo.x and another file called done.x
- if i have an idea for something to do, i add it to todo.x, for example...

// TODO make a less exciting test_eso that doesn't require transforms, etc. (just draw the primitives yo)
- if i end up doing it, i replace TODO with DONE and move it to done.x
- if i end up realizing it was NOT worth doing, i replace TODO with SKIP and move it to done.x

that's pretty much it 🤷

in practice, there are some additional details
- i group the TODO's by app version, and try to only work on ones for the current version
- within a version, i group the TODO's by feature or whatever
- the first and most import important feature is TechnicalDebt -- this is where bugs, missing pieces of features, proposals to rename variables and functions, etc. go

// // v0.0 vertical slice (render lines, create 3D geometry) /////////////////////////////////////////////////////////////////////////////             

// TechnicalDebt                                                                                                                                                
// |Naming
// ||TODO active is not quite the right word for active_toggle_buttons, active_momentary_button
// |GUIComm
// ||TODO can we just store this on the box?--that would be siiiick
// |APP_MOD_*
// ||TODO #define APP_MOD_??? to be CONTROL on Windows and SUPER on Mac (NOTE web browser is going to pose a challenge)
// |WebDev                                                                                                                                                      
// ||TODO  and  in chrome                                                                                                                             
// ||TODO  and  in safari                                                                                                                             
// ||TODO resizing window vertically does weird clippy stuff to gui text                                                                                        
// |Vim                                                                                                                                                         
// ||TODO grepping before ever calling build and run errors out related to g:have_not_ignored_first_fire_yet                                                    
// ||TODO grepping should open the qf window if it's not yet open                                                                                               
// |Ring                                                                                                                                                        
// ||TODO add to test.c                                                                                                                                         
...

// TopRibbon                                                                                                                                                   
// |TODO could throw dropdowns in a horizontal rack with invisible spacer ACTUAL LOCAL POSITION? local/global?
// |TODO purple top ribbon
// |TODO epsilon spacer in top menu
// |TODO View -> Hide Cancel Buttons

// Popup                                                                                                                                               
// |TODO buffer                                                                                                                                         
// |TODO blue popup cursor                                                                                                                              
// |TODO route hotkey (Escape is a good test case -- have line end and extrude-add on) to the active pane                                               

// Vim                                                                                                                                                 
// |TODO MacroSlam()                                                                                                                                   
// ||TODO use on tree.c, etc.                                                                                                                          

...


// // v0.1 juiced vertical slice ////////////////////////////////////////////////////////////////////////////////////////////////////////////

// TechnicalDebt                                                                                                                                                                                                                           
// |TODO curr_pane->string being passed around as a prefix... (should we just assume that's what the caller would use)
// |TODO consider switching order of lerp args -> lerp(a, t, b)
// |TODO BUG in WASM, mouse moving through toolbox from Select to Deselct hits gap                                                                                                                                                         
// |TODO revisit gui_pane_solve_separators -- the normalization routine is not accounting for separator thickness (i think this is maybe fine...but we need to think about it and document it)                                             
// |TODO revisit middle mouse scroll treatment in app and gui (either fold it into LR or don't, but be consistent across gui and app)                                                                                                      
// |TODO revisit gui_mouse_button_state release logic -- its scary if the button goes away while we're holding it (we'll have a stale key_string)                                                                                          
// |TODO revisit gui_pane_insert to take a pointer to trev_prev_pane or something                                                                                                                                                          

// MultiCursor
// |CursorMirrorX

...

done.x is (for me) purely a motivational thing; i don't worry about its formatting and make no effort to maintain the organizational structure from todo.x honestly, i've never gone back to read it (except to say wow, look at all that stuff i got done) it's really just a Goggins-style cookie jar

// |DONE proof of concept File -> Open
// ||DONE? calling make () moves cursor to 0 -- i think this is caused by tpope Make (need to mess with the g:have_not_ignored_first_fire_yet flag)        
// ||DONE (combine stb_easy_font_vprintf and text_vprintf) we can make this function emit quads eso style (progressively collapse the in-between functions away)
// ||DONE pen_new_pen, pen->flags ...                                                                                                                           
// |DONE still not quite right, see tmp.x
// |DONE just use this as a global, it's not going to change mid-frame yo (just make sure it doesn't leak into the history system)
// |DONE clamp_to_window_extents
// DONE |fps trace                                                                                                                                                                                                                         
// DONE BUG Check mode build fails                                                                                                                            
// |DONE AppEvent, app_event -> AppEvent, app_event
// |SKIP (NOTE had a fundamental misunderstanding of how ## works -- only allowed in macro defs) use List ## R32 syntax everywhere  instead of List_R32 (this way everything gets hit by find/replace and naming stays in sync)
// |DONE enqueue to the front 
// ||DONE ? use glfw time instead
// |SKIP remove -Wno-missing-braces from build_and_run.bat
// |DONE console should pane should display global messages                                                                                             
// DONE |fps 
// ||DONE get rid of pen_print and just support printf for now (can use the silly ("%S", string) trick)                                          
// ||DONE get compiling again -- port to STBEasyFontResult, brining travel into that function
// ||DONE public facing API should just be vprintf returning STBEasyFontResult (helper functions like draw_segs are fine)
// ||DONE fix emscripten_set_main_loop bug
// ||DONE restore test_run_tests and check
// ||DONE make a less exciting test_eso that doesn't require transforms, etc. (just draw the primitives yo)
// ||TODO rename POE_WASM to POE_WASM
...

writing stuff from scratch is delightfully infective

i started using Vjekoslav Krajačić-style sliceable strings a while back

STRUCT {
 U8 *_; // NOTE i usually name this kind of thing `_`; you could also use `data` or `v` or whatever
 U64 length;
} String;

they're great. you can slice in O(1) and you don't need to null-terminate them

// NOTE analogous to Python's string[start:one_past_end]
String _string_slice(String string, U32 start, U32 one_past_end) {
 // NOTE we allow empty slices
 ASSERT(start <= string.length);
 ASSERT(start <= one_past_end);
 ASSERT(one_past_end <= string.length);

 String result;
 result._ = &string._[start];
 result.length = (one_past_end - start);
 return(result);
}

// NOTE analogous to Python's string[start:]
String string_slice(String string, U32 start) {
 return _string_slice(string, start, string.length);
}

the only downside i experienced using these String's was that the standard printf didn't know about them -- if you pass a non-null terminated one of these String's with %s, printf will just goes marching merrily off into the distance, looking for a 0 that may never come

i saw two ways around this (like always, there are probably more options than just these):
A) remember to add null-terminators whenever i wanted to print of sprint, or
B) bring your own printf

conventional wisdom has that option (B) is Bad and Hard, but conventional wisdom is basically always wrong, so let's go with option (B)

like many Hard things, it ended up taking about 5 minutes:
1) borrow Marco Paland's tiny printf from GitHub, and
2) add ~20 mostly copy-and-pasted lines to it

case 'S' : { // our custom String struct
 String string = va_arg(va, String);
 const char *p = string._;
 unsigned int l = (unsigned int) string.length;
 if (l) {
  // NOTE this block (mostly) copy and pasted from case 's' i did need to
  // change / add one important thing or else it did bad things beware copypasta
  ...
 }
}

that's it.

and now we can do stuff like this...

printf("[gui_tracked_box] box->key_string clash: %S\n", box->key_string);

and since snprintf and vsnprintf also know about our String's now, we can use %S format specifiers in other functions, like...

GUIBox *box_field_buffer = gui_tracked_box(
 "buffer_%S%S%S",
 string_prefix,
 command->string,
 field->name
);

:D

sidenote: while i was in the neighborhood, i made a small change to _internal_vsnprintf(...) -- i prefer my functions to just crash the program when they fail, rather than returning some number i'll forget to check for and then spend hours trying to debug the downstream effects of

int _internal_vsnprintf(...) {
 ...

 // // NOTE (copied from the printf docs)                                                                        
 // \return The number of characters that COULD have been written into the buffer, not counting the terminating  
 //         null character. A value equal or larger than count indicates truncation. Only when the returned value
 //         is non-negative and less than count, the result has been completely written.                         

 B32 success = (1
  && (result >= 0)
  && ((size_t) result < maxlen)
 );
 if (!success) {
  *((volatile S32 *) 0) = 0; // (crash)
 }

 return(result);
}

i like being able to easily delete lines and swap lines without having to think about C's syntax; one useful family of tricks is to avoid syntactic "special cases" -- basically, the first and last of something should be styled just like the things in the middle

here's how i initialize structs

{
 a,
 b,
 c, // <- i put a comma here (NOTE trailing comma in struct-initialization is valid C)
}

here's how i write an else-if ladder

if (0) { // <- i put an `if (0)` here (NOTE 0 is false)
} else if (...) {
...
} else if (...) {
...
} else if (...) {
...
}

here's how i write an AND chain

(1 // <- i put a `1` here (NOTE 1 is true)
&& ...
&& ...
&& ...
)

here's an example snippet from Conversation's source

B32 is_prinicipal_frame = (1
&& R32_is_zero(part_projection->signed_distance_from_unprojected_origin_to_feature_plane) 
&& geom3d_vector_has_unit_length(part_projection->normal) 
&& R32_are_equal(1.0F, vec3_max(part_projection->normal))
);
if (0
|| (!part_projection->is_active)
|| (!is_prinicipal_frame)
) {
...
}

and here's another

Void gui2_separator(
 Axis split_axis,
 String prev_sibling_string,
 String next_sibling_string
) {
 GUIBox *separator = gui_tracked_box(
  "%S_separator_%S",
  prev_sibling_string,
  next_sibling_string
 );
 separator->flags = (0
  | GUIBoxFlagRenderFill
  | GUIBoxFlagLeftClickable
  | GUIBoxFlagIsASeparator
 );
 ...
}

it's a shame HolyC didn't catch on, because i think it allowed you to include a trailing comma in function calls and that would have been siiick