eno writer

021 - the tortoise and the hare - building faster with low level programming

I am deriving a lot of personal satisfaction from writing eno from scratch using a low level programming language. At the same time, I can't ignore the reality that I spend a lot of time writing code to do things that are trivially accomplished in higher level programming environments.

My last company's software was written to run in a web browser. This meant it could leverage languages that are interpreted by the browser like HTML, CSS and Javascript to accomplish things very quickly. Displaying a word on a web browser is as simple as typing it into an HTML file and loading it. With eno, I spent several weeks writing low level code to load fonts, rasterize glyphs, manage a font atlas and draw rectangles on the GPU before I could display a single letter on the screen. I could have accomplished a lot with those weeks if I had been building in a web browser instead.

Obviously, I am building slower with a low level language than I would with a high level one. However, there's an argument to be made that it might be faster in the long term: it could be like the tortoise and the hare.

Take for example the notorious problem of "how to vertically center text in a web browser". On StackOverflow, this question has over 2700 upvotes. More notably, there are 37 answers proposing different solutions including using tables, absolute positioning, flexbox, CSS grid and most ridiculously, setting the "line-height" property to the height of the div. Each of these involves learning a completely separate part of the CSS spec and each has various tradeoffs that makes it non-viable in certain situations.

On the other hand, when I control my own rendering code, I can just put words wherever I want. The only job is to figure out where the vertical center of the container is. This is done with the simple math equation: <container top> + (<container height> / 2). I don't need to know the entire CSS spec as it has evolved over twenty years. I just need to know how to divide by two.

I recently had an opportunity to test this phenomenon on a more significant scale when I implemented scrolling. In HTML, to get a scrollbar to appear, you just put a lot of text in a box and when there is too much the web browser displays a scroll bar next to it automatically. Very simple. This also works well with more complicated UI elements, like items in a list or a post on a social media feed.

The issue is that some lists need to be really, really long. HTML requires you to provide all the content so it can make the scrollbar the right size. If you are loading all of this content from a web server, you might only want to load a few items to begin with and then load more later, as needed.

To solve this, web developers use a concept called "occlusion culling" where they cut things off the top of the list as they scroll out and put new things onto the bottom as they scroll in. Implementing this is a little bit tricky and, unfortunately, the web browser makes it extra difficult every step of the way.

First of all, in a browser, removing HTML elements and recreating them is a relatively expensive operation. To avoid this, you need to reuse the HTML elements from the top of the list by moving them to the bottom and modifying them to have the contents of the next item scrolling in. In a naive implementation, this can lead to bugs where you leak state from the list item getting cut off the top of the screen to the list item entering at the bottom.

The next issue is the scroll bar. While it was handy that the web browser put it there automatically, it is now automatically sizing based on what is actually present in the list - meaning that the scrollbar won't let you scroll very far up or down. To fix this, you need to insert big empty HTML elements at the top and bottom of the list and constantly resize them to simulate the space taken up by the culled list items.

If all your elements are the same height then you can figure out how big to make this box by multiplying the number of culled items by the per-item-height. If the elements vary in height, things get complicated. In order to know how much height an element will take up, you have to actually render it to the screen; but, the whole point of occlusion culling is to not render it to the screen!

After navigating through all these tradeoffs you eventually reach something that kind of works well enough. You are forced to settle with this because, on a fundamentally technical level, you are actually not able to make it any better. The browser doesn't give you the tools you need. Unfortunately, after investing time to learn about all these high level language eccentricities, you can't reach an optimal solution in the end.

How did the low level version of the story work out? In my conservative estimate, I was able to implement occlusion culling in about half the time it took in the web browser environment. Moreover, it was a much more pleasant experience. My time was spent thinking about how occlusion culling should work on a fundamental level, whereas in the browser world I was reading through documentation trying figure out some eccentric behaviour of the browser. What's most important though, is that I was able to get my scrolling to behave exactly how I wanted. No trade-offs. To the extent eno has a bad scrolling experience, the fault lies entirely with me.

So, can low level be the tortoise to the hare of high level programming? Well, if you can never get to the finish line with the high level approach, then the answer is "yes".


If you enjoyed this post, subscribe below to get notifications for the next one.

Powered by Buttondown.

We also have an RSS feed

#programming #software