- concurrency
- dumb I knowingly (for no real reason) fucked around with the thread-queue code and introduced a bug. Don't play with fire kids.
- concurrency Getting some weird flashing/flickering when doing CopySet, but not when doing the regular mt copy job.. very confusing.
Turned out that the main thread was only waiting till the thread queue was empty, not until all jobs were finished. This caused the workers to overwrite random stuff from the next frame while they finished. Only presented when the jobs were long-running enough to interfere with the next frame.
- copy-pasta I had a typo that took me quite a while to find. It presented as if I had a memory bug, so that's what I was looking for, but it was actually that I copy-pasta'd a yIndex when it should have been zIndex
- va_args
- format_string I ended up printing out an error message that contains format chars and it was breaking in a weird way due to how FormatCountedString_ works (va_args). It was trying to pull a bunch of arguments out of registers (off the stack?) that weren't there and corrupting stuff. Not really sure how to prevent this error in the future other than using a metaprogramming system to comptime check args to variadic functions .. though in this case even that wouldn't have caught the error.
- allocation
- memory
- reallocate When initializing the debug system I was using the same arena for everything, including allocating debug_profile_scope structs. The bug I hit was when an allocation was made on the thread-local arena, then passed to a timed function, then re-allocated, the reallocation would fail. This would actually only happen if we allocated a new debug_profile_scope struct, which was during initialization, so we did. Anyhow, adding an arena specifically for debug_profile_scope structs resolved the issue, and sees like a prudent strategy to get good memory packing anyway.
- stream-state
- parsing This is the second time I've had this bug, which I recognized right away this time, however the first it took me quite a while to find. The problem arises when calling Eat(Line|Until|Between) when the At pointer of the parser is in an unexpected place. This happens primarily when doing lookahead, validating the input text, then deciding that it's fine to ignore it. At this point it's a good idea to require the input text that's been validated, then the cursor is in a known-good configuration. It's difficult to imagine how to prevent this sort of error. One solution might be to remember on the parser if the last operation was a Peek(), and warn in debug mode when calling one of the above functions in that case.. ? If I hit this bug again I'll do something about it.
- stream-state
- parsing I had a function that assumed we were at the end of the stream of a metaprogramming directive that's responsible for closing the meta directive stream and doing the required output. Calling it twice resulted in calling RequireToken(CloseParen) twice, which confused me for a bit.
- iteration
- confusing I had a difficult time digesting how exactly to best support looking ahead in the token stream. It was complicated because Peek is slightly different semantically from PeekRaw. PeekRaw uses the lookahead verbatem, but Peek needs scan and count how many tokens it hits, excluding whitespace and comments.
- allocation
- memory
- sentinel
- linked-list
- d-list I had a constructor returning a sentinel by value, and initializaing the sentinel to point to itself ends up initializing it to point to some random place on the stack. Should probably not use sentinels.. ? Maybe?
- opengl
- gpu-mapped-element-buffer
- nsight I had an incomplete understanding of what exactly glEnableVertexAttribArray did and as such failed to call it when unmapping vertex buffers for a new render to texture path. The other path happened to work because no other render calls fall between the start and end of the buffer map-unmap pair. Used nsight to finally figure out what the issue was.
- inverse-projection
- matrix
- math
- opengl Opengl matrices are column-major and the Bonsai application matrices are row-major. Confused by this when doing a row-major tranform on a point did not produce the correct result. Ended up remembering that opengl matrices are column-major, implemented the opposite transform, and everything worked.
- collision detection The GetCollision call had some old/crappy code that was calculating the wrong bounds for the collision check. Changing to a more sane inclusive min exclusive max scenario fixed the issue.
- memory
- arenas
- allocation Memory arena Start pointers were recently changed to be the first byte of the entire system allocation, which when reset to, caused the actual arena to be overwritten. Adding a secondary pointer to the first byte used for the allocations fixed the issue;
- off-by-one
- allocation I noticed that geometry buffers were actually setting their End pointer to one past the end of the actual memory. I assume this never got written to (they're memprotected) because we operate on triangles, thus access elements three at a time, and one was not enough to cause an overwrite crash.
- memory
- arenas
- allocator This manifested when trying to initially allocate an arena with a very small size (32 bytes). Probably should not have been trying to allocate that little of an arena in the first place, but it caught a bug.
- memory
- allocator On linux, memprotecting pages adds an entry to the virtual page table, which eventually overflows with many small allocations. Turning off memprotect on the transient arena fixes the issue for now.
- opengl
- memory
- macro Had an issue when sending data to the card. Added an emission property to the color data, which increased the stride from 3 floats to 4. The stride value was hard-coded, which resulted in a corruput-looking draw.
- network
- init Server and client sockets are created differently, which caused my assumption that they were non-blocking to be incorrect. I thought the server was hanging on a infinite recv loop when in fact it was blocking on recv
- lifecycle
- concurrency I was setting the chunk flag to build a mesh when a chunk was actually un-initialized, and the thread queue was occationally getting to it before the voxels were initialized. This caused a bogus mesh to be built.
- clock I was dividing by an integer instead of an r64, which caused the clock to get trucated on second boundaries. Whups
- lifecycle
- init CleanupScopeTree reinitialized the CurrentScope param to 0 when it should have been &DebugState->RootScope
- texture I was passing a void ptr to the MakeTexture_RGB to initialize the tex data, which bit me in the ass when I went from passing the address of a stack-allocated array to the address of a heap allocated array, which was v3** instead of a stack allocated array degrading to v3*
- opengl
- init
- lifecycle
- texture I had an old depth texture which was not needed in the shader but was being bound to its default uniform value of 0. Very bad
- opengl
- texture
- shadow mapping I was not setting the shadowMap uniform in the lighting shader and it was sampling the wrong texture. FML. I should probably make more type-safe wrapped opengl calls
- opengl
- texture I was setting the gbuffer texture size at startup to the width/height of the screen, but then when drawing into it, reset the viewport size to the current size of the screen, which could have changed if the user resized it. No bueno.
- opengl
- shadow mapping Somehow on my Lenovo laptop the scene depth texture was linear, however on other hardware it was not. This is a complete mystery and somewhat alarming. I still have yet to investigate the root cause.
- debug system In order to properly time functions, the entire debug system needed to exist outside of the game code (specifically, any timed functions). The only place where it could live is the platform layer, however it also needs to draw to the screen, so the rendering code had to be lofted up into the platform layer. 3h of refactoring and a while figuring out what was going wrong.
- entity state
- lifecycle
- player
- entity system
- collision detection Player update and the rest of entity update code was not exactly the same, thus there was a chance for entities to tunnel through the Player
- frame queue Had some bad logic to step through a linked list I clearly didn't test
- opengl
- shadow mapping Epic quest to fix the shadow mapping turned out to be mostly a wild goose chase and a couple hours tuning it at the end. The wild goose chase part was trying to translate the world geometry using a transform matrix - which I still think should have worked - instead of repositioning the projection matrix with an offset. The other part was changing the shader to use sampler2d instead of sampler2dShadow, which I'm not sure was a good idea, but I felt like doing it.
- particle system Parent entities sometimes got to the end of the world before the whole effect had time to complete, causing it to disappear in a jarring fashon.
- physics
- frame rate Introduced during refactor with variable frame rate
- globals
- hot reload
- lifecycle
- hot reload
- thread queue Problem introduced going from checking the thread queue for work -> semaphore only for flow control. Whenever AtomicCompareExchange failed, ThreadSleep would be called again before ACE was retried, thus leaving the Semaphore out of sync with the number of entries on the queue.
- frame queue
- particle system
- particle system
- frame rate