GeistHaus
log in · sign up

https://tednesday.wordpress.com/feed

rss
10 posts
Polling state
Status active
Last polled May 18, 2026 23:22 UTC
Next poll May 19, 2026 23:46 UTC
Poll interval 86400s
Last-Modified Thu, 06 Nov 2025 17:23:50 GMT

Posts

Why the case for Rust is not particularly compelling
Uncategorized
If you are terminally online like me (and a programmer) you’ve probably seen some form of Rust evangelism on the web. It’s a bit of meme, but one of the main arguments underpinning Rust is this: Programs that are written in C and C++ have many recorded security vunerabilities. This is because C (I’m saying […]
Show full content

If you are terminally online like me (and a programmer) you’ve probably seen some form of Rust evangelism on the web. It’s a bit of meme, but one of the main arguments underpinning Rust is this:

Programs that are written in C and C++ have many recorded security vunerabilities. This is because C (I’m saying C for ease, take it to mean C/C++) allows you to write to any arbitrary memory address (along with many cases of undefined behaviour), without much push back from the design of the language. Therefore, people are more likely to create memory bugs in C.

Memory bugs are bad, because memory bugs can be exploited. If an attacker can exploit one of these bugs they could potentially read or write to arbitrary memory.

If an attacker can do that, they effectively have control of your system. It’s kind of (I imagine) a worst case scenario. Because then they can read your plain text passwords and/or crash your server. Or launch a nuclear missile.

So, what about a language that makes memory errors unlikely? Sounds good right? That’s what Rust does. Well, that’s what the Rust compiler does. Rust is essentially a systems language that was designed with a static analyzer in mind. Same cannot be said for C or C++.

All great right? Why the hell are we writing C++ if Rust exists? We need to switch immediately! Imagine funny Rust jokes and shenanigans.

In programmer world, you really can’t beat this argument. Because programmers are hyper-rational. And hyper-rational programmer people are obsessed by what can be measured.

Just look at the stats. Look at all of these security flaws. Look at windows/firefox/chrome, 70% of their vunerabilities are related to memory errors. Heartbleed, that other one that I can’t remember etc etc. On paper it’s just a no brainer. NONE of these would have happened if it were written in Rust (in theory).

But… hang on. What about all the code that doesn’t have vunerabilities? How do you measure when a memory error DIDN’T happen? How do you measure or notice something that just works and always worked? Seemingly, then, there seems to be a massive hidden dimensionality to these questions that isn’t being measured or can’t be measured. How many unknown unknowns are there? How does that relate to the known knowns?

I hear you though. You are hyper-rationalist programmer, you are probably thinking “well who cares if there are unknown unknowns, by using Rust we’ve addressed the ones we do know about”.

Well.. no. Not really, because shouldn’t the question really be, “What the fuck are we doing in the cases where there are no vunerabilities?”.

What’s the answer to that? Do you know?

The Rust case, often positions Rust to be the only viable option for memory safety (atleast when it comes to replacing C or C++). Don’t take this from me. This is the position of many Cyber Security Agencies who think the only way is to have a language secure by design. (https://www.cisa.gov/news-events/news/cisa-us-and-international-partners-announce-updated-secure-design-principles-joint-guide)

But this is just throwing the baby out with the bath water. Clearly. Because we have tonnes of safe code, and no one knows why its safe.

Except, they probably do. But no ones is asking the people who wrote the code. Or even talking about the fact it exists. The only solution is Rust adoption or else. How you write code, how you design code, what environment you are in, what your domain is, what the complexity of your problem is. None of that matters. Only the design of the language matters and Rust is the only language that is designed this way currently (even when its not).

How much programming wisdom, when it comes to how to design a memory safe program, is being chucked out the window to pursue a “security by language design” approach? Are we really going to sacrifice all of that because its a hard argument to make?

Rust on the other hand is an amazing argument. It really is. That’s only because it makes assumptions that are hard to disagree with if you don’t know what they are. But once you do, you realise they are kinda flawed.

And unfortunately, just because you can win an argument doesn’t actually mean you are right.

Having a “secure by design” language SPECIFICALLY to the detriment of everything else is not going to solve your security problems. This is a multi-front war.

This is analagous to World War 2 bomber design (bear with me). The prevailing wisdom at the time was to pile as many guns and armour onto bombers as possible, to prevent them being shot down. As the war went on they got more armoured. They had more mounted guns. Because, obviously? We don’t want our bombers shot down by enemy fighters!

However, what if you just flew really fast so the enemy can’t catch and shoot you? Then you don’t need armour and guns. If anything all of that stuff prevents you from pursuing the optimal strategy. Which is going fast! Fast forward to today. Do you notice how many bombers look like hench World War 2 Lancasters? None.

Rust and its “be all or end all” case for secure language design is the “pile all the guns onto the bomber” strategy. It is the singular pursuit of known knowns. That is a sure fire way to get stuck in a local minima.

We need diversity in strategy. We need to actually understand what security means and how existing programs that ARE secure, are written that way. Any acknowledgement of this is sorely lacking in any of the growing movement toward legislation. This is worrying. Because we could end up in a less secure future, where nobody understands why, with red tape that prevents you from finding out what went wrong.

tednesdaygames
http://tednesday.wordpress.com/?p=310
Extensions
The classical world is OBVIOUSLY NOT locally real.
Uncategorized
What if I posed you the question: “If a tree falls in a forest does it make a sound?” If you answered “yes” or “no” what assumption are you operating under? To put it plainly, your axiom is: “You can know the state of a system without measuring it”. “No, that’s not what I assumed” […]
Show full content

What if I posed you the question:

“If a tree falls in a forest does it make a sound?”

If you answered “yes” or “no” what assumption are you operating under?

To put it plainly, your axiom is:

“You can know the state of a system without measuring it”.

“No, that’s not what I assumed” I hear you say. “I know that trees fall and make sounds, so obviously it’s going to make a sound”.

My question then is, what is the only way that we can agree that this is the case?

“I don’t know” I hear you say.

“We must observe/measure the tree falling to verify your prediction is true”.

That is the only way to provide certainty on the outcome. It is the only way to truely know the state of the tree, forest and sound. Everything else is predictive and has uncertainty.

This is THE scientific world view. And has been since whenever the scientific method was discovered. To know the state of a system you must measure it first. This is not controversial.

So why then, are classical systems intepreted as locally real? Something that completely violates this world view.

“Local realism” assumes that the properties of a system are “real” and persist. I.e. something maintains its state even when not being observed

Well you see the problem here right? How do I prove to you a system has persistent state? I can’t. I can only tell you the state of a system when I measure it. I can only make a prediction about its persistence. I can’t actually prove it is persistent.

So a scientific world view does not preclude a locally real interpretation. In fact, quite the opposite.

So why is this “locally real” universe the mainstream interpretation of our “classical” world?

It is one of the most bizarre and obviously wrong interpretation in physics that I can think of and it is often used as a justification as to why a quantum world is “strange”.

But the quantum world isn’t strange. At all. In fact the quantum interpretation of the world lines up better with your intuitive understanding of how things work (i’ll get to that later).

Okay, you still don’t believe me. Let’s do a thought experiment. I have a tennis ball. It is NOT quantum particle. It’s just a tennis ball. It lives in the classical world. I throw the ball. Now, tell me, what is the velocity of the ball at time T = 0?

Oh you don’t know? But I thought you assumed you knew the state of a system without measuring it? It has a real property so it has a “known” value. What do you mean you don’t know it? The “locally real” interpretation does not work here. Locally real assumes some global observer and there is no global observer.

It is better to say here (and more predictive) that the velocity has an uncertainty between negative infinity and positive infinity (and even then I throw my hands up and say “it’s just a prediction bro”). This sounds an awful lot like describing the state of quantum particles doesn’t it? Where a quantum particle has a super-position of possible states. Which is a mathematically fancy way of saying “I dunno exactly, but it might be these things”.

Okay, still not convinced. Another aspect of quantum mechanics is that you “collapse the wave function” or in meaningful terms “you don’t know the state of the system until you measure it, and measuring two dependent properties is not possible” (wow not so crazy when you put it like that now is it?).

You cannot know the velocity of quantum particle while you are measuring the momentum. The velocity has an uncertainty. Well what if I told you the exact same thing is true of a classical system?

When I measure the tennis balls speed, what is the momentum?
I need to work it out by observing the mass. But when I observe the mass, the velocity is now unknown. I can’t in good convscious tell you exactly what the velocity is because I don’t know! And I hear you say “but the velocity is a real property that must exist!” And that might be true, but it is still an assumption and I literally cannot tell you that is true until I measure it again.

So in conclusion. Quantum is not weird. At all. In fact YOUR interpretation of the classical world is weird. Yeah, you can’t know the state of things until you measure them. That’s really not surprising is it? So why, seemingly, when it comes to quantum mechanics is this seen as the most bizarre intepretation the world has ever seen?

We operate a lot of our lives under a quantum interpretation. Case in point, when I open a file on a computer, I don’t just assume the state of the file without opening it. That is COMPLETELY nonsensical. I have to observe the state of the file to know the state of the file (duh). So we start getting into this tautalogical arguments that really make no sense and you have these apparent paradoxes that aren’t really paradoxes because FUNDAMENTALLY you cannot KNOW the state of a system without observing it.

This also touches on the notion that observing a quantum system changes the outcome. But this is also true of a classical system. Measuring anything always changes the outcome. It’s just, it’s not as noticeble at a classical level.

When I observe the tennis ball with my eyes, it requires light to bounce off it. I’ve changed the state of the tennis ball in order to observe it. Again, this is not magical. This is self evident. So why is it weird that measurements at the quantum level change outcomes? It’s not weird. It’s only weird if you assume local realism, which is an interpretation that doesn’t even align with a scientific world view.

So yeah I really just don’t get any of this. I’m happy for a scientist to come along and tell me why I am completely wrong.

tednesdaygames
http://tednesday.wordpress.com/?p=299
Extensions
SIMD Model of software project management
Uncategorized
Current software businesses are run like a conventional multi threaded program. Ideally, a project is split into sub-tasks by a manager, where it isassigned to a series of workers (threads) based on each workers own profiency. Workers may synchronise with other workers daily to clear a block. Managers wait for all threads to finish before […]
Show full content

Current software businesses are run like a conventional multi threaded program. Ideally, a project is split into sub-tasks by a manager, where it is
assigned to a series of workers (threads) based on each workers own profiency.

Workers may synchronise with other workers daily to clear a block. Managers wait for all threads to finish before delegating more tasks.

The problem with this in practice is that it is incredibly difficult to realistically work out how to organise independent sub tasks so that worker threads do not encounter blockages.

This DOES NOT scale for size at all. The more workers you have, the more sub-tasks you have and the more hard constraints exist between those tasks. Thus
daily blockages start to add up and managers call constant meetings to resync.

My proposal is to flip this concept on its head and embrace the SIMD model of software project management methodology: SAME INSTRUCTION MULTIPLE DATA.

Simply put, take the same task and give it to more than one worker.

Seems ridiculous right? Why would I want the same task done more than once?

Firstly, synchronisation gets solved. Each worker thread is completely independent. It is a serial process within one thread (a small team or individual). This scales extremely well. Adding more workers never incurs a synchronisation cost because dependencies stay strictly within the thread.

Secondly, to get a valuable outcome, each task can be given some variance. With N teams, that’s N outcomes to choose from.

Choose the best one, reassemble the teams, rinse repeat.

To show that it scales, imagine having a business with 1000 engineers. That could be 250 teams of 4. Within the time of completion of one task you have 250 solutions to choose from.

tednesdaygames
http://tednesday.wordpress.com/?p=295
Extensions
The real irony at the heart of software engineering
Uncategorized
There is a growing and deep seated irony at the core of software engineering. It goes something like this: “The people most attracted to the discipline are the people who tend to be the worst at it.” At face value it doesn’t seem as though this can be true. How can the people with the […]
Show full content

There is a growing and deep seated irony at the core of software engineering.

It goes something like this:

“The people most attracted to the discipline are the people who tend to be the worst at it.”

At face value it doesn’t seem as though this can be true. How can the people with the most interest, be the worst practioners?

As an analogy, one may only look at film to see a clear example as to where this is possible. Whilst a film director makes films, his critic, while having the same deep interest in the art does no such thing.

The parallel does eventually fall flat. The software industry is not quite dominated by the software equivalent of a film critic. Instead, although certainly “critical” in it’s belief system, this software engineer is much worse than a critic.

Fundamentally this, engineer lacks all of the requirements to be an engineer. Not only that, it actively dislikes engineering and forthrightly adheres to a doctrine that works against it. It adopts this doctrine out of sheer necessity to esnure its own survival.

These engineers are victims of the very tool they love. The computer. They are trapped within its shadow, unsatisfied by its projection, but too scared to step outside of it. Meanwhile their brains calcifiy over time as they understand what is happening less and less.

The Two Camps

We can place software engineers into two camps.

  • The first, forms beliefs by seeking to falsify them.
  • The second has beliefs where it seeks to confirm them
  • The first wants to model the world as is,
  • The second wants to model the world as it ought to be.

Camp 1

Engineering as a disicpline requires intellectual humility. The human mind compared to the universe is tiny. Our ability to model the universe accurately is severely limited. We must assume that many of our ideas are wrong.

It is exceptionally difficult to prove something to be true. At times, it is impossible. It only takes one exception to a rule to prove an idea wrong. It is very difficult to know how many of these exceptions there are.

It logically safe to assume that any ideas are likely false. In order to find the truth one should find the single exception that falsifies it rather than finding all the ways in which it is validated. This is because the former is far easier.

This philosophy, to some extent, requires an acute form of faith. It is humility in the overwhelming face of reality. It is the belief that you might be wrong, where ideas should be improved via falsification.

Camp 2

The second camp wishes that reality ought to be a certain way. It does not seek to falsify it’s own claims. It is strongly “a priori”. It believes itself to always be right. The truth simply requires enough push, enough coercement until everyone believes it.

Sometimes this may unintentionally involve validation with reality, but it should be stressed that this is not the goal. The goal is to prove the believer right. The easiest way to do this is to convince everyone of the lie, rather confirm all the ways they may be correct.

It is not a method of seeking truth. It is a method of promulugating it’s own truth.

Similarly to camp 1, it is faith based. It’s faith points inward. Rather than an outward faith (a belief that reality will prove them wrong) they have an inward faith (belief they are right).

Camp 2 does not know where its ideas come from. It simply adopts ideas out of convenience. Either because it has been duped by other members of camp 2 or because the social cost of not accepting the ideas is very high.

In the context of software, camp 2 does not understand how computers work. This is because it does not want to. It does not believe that matters. All that matters is that we reach consensus. A consensus that prefably puts them into a position of power where their anxiety over uncertainty is eliminated.

Camp 2 And It’s Hold On The Industry

There are a number of examples where Camp 2 thinking has completely dominated large swathes of the industry.

CASE 1: Code Review

The central principal of code review is that your code is falsifiable. It is likely in a state that is incorrect, thus you and your peers should try to prove it wrong.

Unfortunately, this strategy relies on good faith. It relies on almost all of your peers being part of camp 1. A single person operating in bad faith, and being part of camp 2, will slowly erode the santicity of code review.

This is because camp 1 wants to be proven wrong, while camp 2 strongly believes they are right. Camp 2 always has strong convictions while camp 1 always has strong doubts. Camp 2 wins out if it’s not directly called out. Camp 1’s humility is exploited in order to install camp 2s hedgemony.

The social and community notion of review allows camp 2 thinkers to gang up and install their believe system into the core of your team. Thus code reviews over time become an exercise in proving camp 2 right rather than finding the truth that camp 1 seeks.

CASE 2: Formalised Testing

Since assumptions must be tested and ideas must be falsified, testing is a given. However, the philosophy of modern software testing sits squarely in camp 2.

This is because the modern assumption when it comes to testing is that a program can be “proved” to be correct with enough tests. This is an incorrect assumption. Tests can only prove your code is wrong. They cannot prove that your code is right.

So the idea that you formalise testing as a form of proof. I.e. you write unit testing for every function, is wrong. The idea that you write tests first is also wrong because you end up validating your assumptions rather than falsifying them.

We see software quality not measurably improve whilst testing effort skyrockets.

Camp 2 hijacked testing. It uses social cost to install itself into positions of power. The motivation for testing should always be to prove yourself wrong, yet often the disucssion is testing to minimise the social cost of being shamed. When shame is used as a battering ram in an engineering discussion this is usually a red flag that camp 2 is involved.

CASE 3: Modern Language design

As we speak there is plethora of new languages being designed and released seemingly every day.

From systems level languages to high level scripting langauges. Programming is seeing a new golden age of development.

One might describe this era as a cambrian explosion of languages. A rapid evolution and expansion of a wild variety of new techniques and ideas, each tackling and providing unique solutions to novel problems.

However, reality is much more sombre. The language explosion is more cancerous in nature. Each new language is a malformed confirmation of someones belief system. For instance, often they take C as a foundation and then they iterate and mutate it until their assumptions are confirmed.

The primary motivation is often, “if only they got it right this time”.

Yet many new programming ideas and paradigms serve no noticeable or practical purpose.

New languages in this era are designed to confirm that a paradigm or feature is correct. They have no notion of a user. In fact, the user is often an after thought, if it is even thought about at all.

They simply choose to ignore human psychology. They are simply designed with a “I know best” mentality. This will create a landscape of noise and vapourware. This era will not be looked back on fondly. It will be seen as an era of community lead and “a priori” design where foundational problems were never solved or even addressed.

Spirtually Dead

Camp 2 must malign uncertainty at all costs because uncertainty means they might be wrong. In some sense we see a delightful ignorance of the unknown.

We see an overwhelming admonishing of the art of questioning. In no uncertain terms we see a spirtual death at the heart of software engineering.

The outcome?

It creates an environment where experimentation is dead, yet it claims to experiment.

It creates an environment where curiosity is alluded to, yet pondering is heavily punished.

It is flimsy house of cards that participates in self-indulgent sophistry. At times and by accident, reality shines through the facade, moving things forward at a snails pace, and shaking up power in the camp 2 hegemony. However, the hegemony never really wavers.

Where Does Camp 2 Thinking Come From?

Camp 2 thinking spreads in environments where the cost of getting ideas wrong is low. In this environment, choosing convenience is not punished. The convenient choice is to simply agree that an idea is correct even when it might be wrong.

There is rarely an environment where a low cost holds for long. Often, environments have an ever growing cost the longer the cost is denied. This is why camp 2 positions become increasingly absurd over time. The philosophy is inherently unstable and often becomes unstuck and so will often collapse in a dramatic paradigm shift where suddenly everyone denied believe in the previous “zeitgeist”.

The rampant camp 2 thinking in software engineering is likely caused by the digital world enabled by computers. In the digital world, there is very little initial cost to having bad ideas, because software is so easily written as a ratio of its ability to be falisfied.

Cost can also be hidden very easily. Programming is very hard. It is understood to be very hard. There is a strong need to defer to the expertise of others. These “experts” have an incentive then, to make software seem harder than it actually is, so they maintain a sense of authority. It becomes convenient for them to lie and obscure the truth.

Most importantly, the computer is a mischevious tool that initially encourages concreteness in thinking while eventually and desperately requiring a holistic thinker.

Instructions to a computer are completely unambiguous. They have no uncertainty. They are painfully deterministic. However, any idea worth expressing on a computer is the complete opposite. It is uncertain. Non-deterministic. There is lots of ambiguity in requirements. It demands the exact opposite mode of thinking. This is dichotomy is primarily why programming is so difficult. Yet this is not even a well understood idea.

The disicipline is overwhelmingly and culturally swamped by the concrete and detail oriented thinker, where we see the malignant form of this as camp 2. A concrete thinker hates uncertainty. It has uncertainty anxiety. In some cases this is preferred, however it must be at parity with other lines of thinking to be productive.

Camp 2’s dominance is because the industry has completely ostracised the holistic thinker. Unintentionally or not.

And so truly, the irony at the heart of the software industry is that it chases away the very people that needs.

tednesdaygames
http://tednesday.wordpress.com/?p=262
Extensions
The ultimate way to handle errors – the error context
Uncategorized
Took a break from writing my game. Too stressful. Instead I decided to take my loading and saving code, and refine it. It was overdue a refactor anyway. One of the issues with the original code was error reporting. So some context (pun intended). I save and load data to a very simple text file […]
Show full content

Took a break from writing my game. Too stressful. Instead I decided to take my loading and saving code, and refine it. It was overdue a refactor anyway.

One of the issues with the original code was error reporting.

So some context (pun intended). I save and load data to a very simple text file format and parsing this file can be quite annoying. (I have some examples of all of this on github: https://github.com/temper-studios/graph-file-format)

When there is a bug, if you havent written the code well, you have to trawl through the file and find out what is causing the parser to break. If you have a file that is 30 thousand lines long…well. Needless to say it wasn’t written well.

There is another issue too. The ergonomics of saving data to and from a file is not great. Checking for errors is something you could do without. Having good robust code is nice, but having lots of defensive code is a massive pain in the arse. You don’t want to have to check that each individual variable was saved correctly. That quickly adds up and eats away at your time.

This is why, in my game engine, I had a mechanism that just safely exited when there was an error. If it could not parse, load or save data the whole thing just exited.

But ideally, you want the flexibility to do both defensive and ad hoc error handling. And you want that to be “context” dependent.

This is where I came up with the error context. (which is likely already a thing already, I’m sure)

The error context is a mix of exceptions, error codes and errnos. All in one. It gives you the freedom to be defensive if you want, but it also allows you to be lax and just exit() if you so desire. And the best thing is, you can decide that behaviour per function. It’s a fine grained form of error handling.

Okay so an example. The context is effectively a stack of errors, with a callback

struct error_context {
    error_code errorStack[16];
    u64 errorStackPtr;
    error_context_callback callback};

You can push an error onto the context and then the callback is called. The callback could be an abort/panic, but it could also just log the error in some way. It can be whatever you want. You can specify that with your own callback.

void error_context_push_error(error_context *ctx, error_code error) {
    ctx->errorStack[ctx->errorStackPtr] = error;
    ctx->errorStackPtr = (ctx->errorStackPtr + 1) % 16;
    ctx->callback(ctx, error);  
}

This is a rudimentary example, but essentially, we push an error to the stack and call our callback. Now, for functions that can potentially fail, we provide them with our error context.

void my_function_that_can_fail(error_context *ctx, int a) {
    if (a == 10) {
        error_context_push_error(ctx, ERROR_CODE_A_IS_10);
        return;
    }
}

As the implementer of the function all I have to do is handle the error cases. So in this case, if a is 10 I decide that I cannot continue and a I push an error onto the context.

Now, as the caller I can pass in an error context to this function and then check whether there was an error.

error_context *ctx;
/* set up our error context */
my_function_that_can_fail(ctx, 10);
if (error_context_check(ctx)) {
    /* there was an error */
}

my_function_that_can_fail() accepts an error context. my_function_that_can_fail() will push an error onto the context when it fails. Whether an error is pushed onto the context is up to the implementer of the function. In this case it happens because a is equal to 10.

As the caller, all we have to do is check the context. But also as the caller, we specify how we want errors handled by passing in the context we want. Pretty sweet.

We could have had the error context abort the application here. Or we could have had the error context log an error. As the caller we choose how we want to continue. As long as the implementer has done their job correctly everything is fine.

The error context also forces you to care about errors, because the function requires a valid error context. That extra step should not be understated, and it does encourage you to be rigorous about how to handle errors.

It’s kinda like a function local errno. But with a stack. Like a local stack just for errors, which is a bit like exceptions. The ergonomics are somewhat like exceptions, but more explicit.

It’s a sprinkling of many different kinds of error handling. It’s not ideal for all situations. But for things that need specific error handling in specific circumstance it is very useful. Try it and see. I’ll be using for other things in future.

Wishlist my game on Steam: https://store.steampowered.com/app/2102680/Blinded_by_Fear/

Watch progress on youtube: https://www.youtube.com/watch?v=2C5tTBfBRyM

Follow me on twitter: https://twitter.com/TEMPER42604230

tednesdaygames
http://tednesday.wordpress.com/?p=238
Extensions
IS C++ DOOMED?
Uncategorized
I was bored so wrote a contiguous queue in C++ ( https://github.com/Tednesday/cpp-contiguous-circular-queue ). These are my thoughts from that exercise. INTRO I’ve written a lot of data structures before, but I’ve never written one that is “idiomatic”. After doing it, I’m left with the question, is it actually feasible to do any of this correctly? […]
Show full content

I was bored so wrote a contiguous queue in C++ ( https://github.com/Tednesday/cpp-contiguous-circular-queue ). These are my thoughts from that exercise.

INTRO

I’ve written a lot of data structures before, but I’ve never written one that is “idiomatic”. After doing it, I’m left with the question, is it actually feasible to do any of this correctly?

Things like move semantics, hidden copy semantics, operator overloads, complex and implicit initialisation logic, exception safety etc etc, have resulted in a
combinatorial explosion of possible outcomes when it comes to the most trivial and minutae of operations, making writing basic data structures rather difficult. And the biggest problem is that you are forced to care about these things, whether you opted in or not, because everything ultimately effects everything else.

AN EXAMPLE

Simple questions often lead to complex problems.

Let’s say I want to know if a constructor failed. I have two options, one is to pass
in an in-out parameter, the other is to the throw an exception. The former is annoying
because now you have complicated initialisation due to the addition of a parameter. (lets not even touch different kinds of initialisation). So let’s throw an exception instead.

MyConstructor() {
  /* do stuff */
  if (something_bad) {
    throw exception;
  }
}

But exceptions come at a performance cost. So ideally we want to turn them off. So let’s turn them off and just accept we can’t really determine if a constructor failed or not.

Congrats you just opted out of lots of the functionality of the STL. Reluctantly you switch them back on. I want to use idomatic C++ right?

Now you have the issue that every single operation can throw an exception. That means you need to write code that wraps every possible resource in RAII logic. Even when it makes the program more complicated.

int my_function() {
  
  FILE file = open_file();
  do_stuff_a(file);
  do_stuff_b(file);
  c = d;

  close_file(file);
}

The above example is very simple. But it is not exception safe. do_stuff_a, do_stuff_b and c = d might throw an exception and so we would need to wrap our FILE so that in the event of an exception it will be correctly destroyed.

class FileWrapper {
  FILE file;
  FileWrapper() {
    file = open_file();
  }
  ~FileWrapper() {
    close_file(file);
  }
}

int my_function() {
  
  FileWrapper file_wrapper; 

  do_stuff_a(file_wrapper.file);
  do_stuff_b(file_wrapper.file);
  c = d;
}

Okay. Fine right? Not really. Instead of all the logic being contained within the scope of one function we now have an additional wrapper class with 2 extra functions. I dont think that’s easier to understand. (I’m aware that this is a somewhat trivial example)

This also introduces new challenges. Where do I jump to? What objects will get destroyed? How do I know what operations will throw? And now suddenly something that was supposed to be easy just became a lot more complicated.

Anyway the point is, you can’t do something simple, like check if a constructor failed, without using exceptions. And using exceptions means a full commitment to RAII for any custom data type. Idiomatic modern C++ isn’t a pick and mix, it’s a full course meal and you’ll eat it whether you like it or not.

Another big issue is copy and move semantics. Take something like return value optimisation (RVO).

It great for making function calls more ergonomic, but it messes with copy semantics. Now copies that result from calling a function have a different meaning to regular copies, even when they can look identical. And that’s assuming that RVO always works (which it doesn’t).

When we add in the many possibilites that can happen during a copy we suddenly need to spend a substantial time understanding exactly what is happening in some of the most simple operations of the program. Did it move? Did it copy? Was it elided? Does it need to be moved? Does it need to be copied? What else is that assignment doing? Why did it throw?

And when it comes to implementing move and copy constructors you are left feeling like you’ve definitely done something wrong. Just look at how many value categories there are https://en.cppreference.com/w/cpp/language/value_category

This is what friction in a design looks like. When two different parts of the design actively go against one another. When something that is supposed to do one thing does N number of things and the benefit is not obvious. Did we really need move semantics on top of value based copy semantics? Is copy elision actually the right choice? Is it a good thing that the initial meaning of what a copy was has now substantially changed?

THE COUNTER ARGUMENT

The argument is that all of this complexity is required. That is is useful. It allows us to solve problems more easily and reduce complexity.

In principal, I have nothing against that. But there are two issues.

The major issue is that the promise of the language is that “you do not pay for what you don’t use”. Picking and choosing features that best solve your current problem is supposed to be the whole selling point of C++. Unfortunately a lot of these features bleed into one another by design.

The second issue is that it is simply too complicated to write code that satisfy a lot of these requirements. And I really disagree with the argument that the complexity is required. It’s just not. It’s only required if you buy fully into idiomatic C++. The only reason it is as complicated as it is, is because every new features is trying to fix the mistakes of the last one, which is why there is substantial dependency between features. This doesn’t seem like a good thing to me. The complexity of the language is not helping reduce the complexity of my domain.

The solution?

There is a trend in modern programming language design that attempts to shift complexity so that it only exists behind an interface.

In C++ this manifests itself in the form of library implementers doing all the heavy lifting. Complexity is hidden behind interfaces and writing correct, idiomatic data structures is quickly becoming something that is not for mortal men.

This is a shame because writing custom data structures is very, very useful and we should be encouraging everyone to do it. But it is no longer a first class citizen in C++ because of uneeded and runaway complexity that is caused by years of adding things but never taking them away.

Try stepping into an STL implementation. Standard types should be understandable by the standard C++ programmer. It’s a failure of the language that the code is not easily understood.

I’m not particularly impressed by what the future holds. Many new proposals come off
as completely bizarre and detached from any observable reality. That is, of course, until you realise the design philosophy that is being followed. They literally just implement whatever the current fad in programming is.

Proposals seem more beholden to research funding, internet fame and ego rather than working towards some clearly defined design goals.

Professionally speaking these design decisions cost me money. Friction and complexity costs me money and my time. That’s why I’m not particularly amused
by design approaches that do not get me to my end goal.

I’m not in the business of writing the perfect program that satisfies an arbitrary standard. I’m in the business of making stuff with tools. The language is the means to the end, not the end itself and until C++ realises that, it is doomed.

tednesdaygames
http://tednesday.wordpress.com/?p=200
Extensions
Why long functions are probably a good idea.
Uncategorizedcodecodingdevelopmentengineeringfunctionsgame developmentgamesprogrammingsoftwaresoftware developmentsoftware engineering
Play Spatium Here: https://tednesday.itch.io/spatium Recently I submitted a game to the 4MB Game Jam. The rules of the jam were that the game, its binaries and all its resources, had to be less than 4 megabytes in total. And the jammies has exactly one month to pull this off. The purpose of the jam was […]
Show full content

Play Spatium Here: https://tednesday.itch.io/spatium

Recently I submitted a game to the 4MB Game Jam. The rules of the jam were that the game, its binaries and all its resources, had to be less than 4 megabytes in total. And the jammies has exactly one month to pull this off.

The purpose of the jam was to rule out a lot of existing tools like Unity or Unreal who’s DLL’s and executables already exceed the limit before you’ve even started. This allows people to use and build their own tools and test their ingenuity when it came to making a game of a small size.

Luckily I have been developing my own tools and frameworks for a long time so I wouldn’t have to do too much tedious boilerplate (like writing maths libraries, or windowing code/initialising opengl for instance).

At the outset my ambition for this project was to spend 80% of my time writing code that directly contributed to the game and not get bogged down in needless architectural boilerplate or ancillary libraries that I would need in future. This was a one and done deal so I didn’t need to care about it being “nice” I just had to care about it being “finished”.

I settled on it being a 3D first person game and given my relatively recent stuff on voxel planets I decided to set it in space with asteroids that would correctly orbit the sun and a player who could fly amongst the asteroids and land on them.

At this point, I wanted to get asteroids onto screen as quickly as possible. So, I created an asteroid type with a position and mass, dumped them in a contiguous array and updated them all inline inside my games update function in main(). This made a lot of sense because the code was simple, sequential and didn’t branch much. For each asteroid I updated it’s force on every other asteroid via its gravitational pull and then I updated each of their positions based on the force and velocity. It didn’t need to be split up and I didn’t want to waste time thinking about how it could be.

  • First asteroid prototype

Now at this point conventional wisdom says that functions should be short, snappy and readable lest you become afflicted with complexity that you cannot control. But conventional wisdom is mostly wrong in programming anyway.

I kept adding more and more code to main(). Each asteroid had to collide with one another. So I added in another step where every asteroid checks with every asteroid whether it needs to collide and behave accordingly. I added the player’s controls. If the player presses certain keys that have a certain velocity and then the player moves. More and more stuff was added inline.

You are probably thinking to yourself that this sounds absolutely horrible. But as I added more and more things a number of things started to happen.

  • It became easier to read and follow what was happening
  • It became really easy to debug
  • It became really obvious what things depend on what other things
  • It became very easy to change and frictionless to move things around

In terms of being easy to read, it is allows the logical progression of the code to be easy to see. The asteroids forces were updated, then players forces were updated, then all the asteroids were moved etc etc. Yes there is a large quantity of code to look at, but the benefit of having it inline is that you cannot get lost and the complexity is not hidden, which is what can happen with hundreds of function calls. (particularly because you literally have to jump to those functions in your IDE and lose track of what was happening).

In terms of debugging, you don’t have to jump around a call heirachy that is 12 functions deep to find the root cause of a bug. And you don’t have to worry about functions that split up the same piece of logic becoming out of sync or stale. If you change something at the top of the update loop its going to break the constraints in ways you will spot straight away.

Code does not spill out where it does not belong. Functions that may only be called once that exist in some global scope don’t need to exist when you write it inline. This touches on some other thoughts about how to reduce complexity, in that code should go from inline, to local functions to global functions as it is required and not as a matter of necessity. (which people are often pressured to do).

There is also a question of maintainability. This was a one and done deal so surely it should be an unmaintainable mess. Well, having returned to it after a few months to write this blog the code is just as easy to mess with is as it was before because it’s all right there. There is no ambiguity. It literally does what it says on the tin.

The only requirement I would place on writing these long functions is that the logic must be like a “stream”. For example, excessive branching and jumping kills the readability. Complicated operations that do branching transformations on the data are the biggest sin here and I would avoid these. Also code that is repeated ad nauseum suggests it might need to be split up.

For example, in the case of manipulating wide or deep data structures, like trees, long functions are likely not a good idea (although it depends). This is really a strategy for manipulating flattened data structures. Things like simple update loops, and sequential logic iterating through arrays or lists.

For example, late in the project I switched out my many little asteroids for a small number of large voxel planets. The voxel code was a completely seperate subsystem because it dealt with complicated data structures in which the logic was not easily “streamable”. In that instance it was more like library code and was written as such. However, gameplay code was still all inlined in main().

  • Flying in space and cool stuff

All in all my main function came to 800 lines. That included all the physics, collision and player game logic.

Do I recommend doing this for production code? It depends. My stuff clearly needs a refactor but that doesn’t mean that i’ll abandon the use of a long function entirely. If it makes sense to do it I’ll do it. I just think that long functions should get split up when and as needed and not as a tradition.

And to stop the nerds currently typing away about the elephant in the the room which is unit testing.

My answer to that is you should be testing behaviour and not units of code. Code should not be written to be easily unit testable, because you are just butchering your design and introducing hidden bugs between the spaces those units interact with one another. Think about what behaviour your code should and should not elicit and test that. Just testing the code you have written so that you know it does what you wrote is not a very good test.

It is still possible to test long functions, those tests are just larger in granularity because the function itself is larger. And with longer functions you are forced to test behaviour rather than the cardinal sin of unit tests which is testing that the code you wrote is the code you wrote.

Based on the contraints of my project, I needed robustness but I also needed flexibility because of the nebulous behaviour/requirement that is “fun-ness”. That means literally scrapping the entire code more than once. But with each iteration comes more robustness from it being a better design making it easier to debug and reason about. Making the first iteration more testable creates too much friction.

Work smart and not hard.

asteroid
tednesdaygames
http://tednesday.wordpress.com/?p=179
Extensions
Voxel Planet Gen
Uncategorized
I decided to extract a slice from my ongoing game engine. The goal with this was to have dynamic, smooth, voxel planets that fit nicely in memory. The big problem with voxels is that they tend to use too much memory. As you increase the radius of the playable area linearly the size in memory […]
Show full content

I decided to extract a slice from my ongoing game engine. The goal with this was to have dynamic, smooth, voxel planets that fit nicely in memory.

The big problem with voxels is that they tend to use too much memory. As you increase the radius of the playable area linearly the size in memory increases in an n^3 fashion. That’s a problem because we need memory to load in the rest of the game.

I explored a few ways to get around this. I settled on using a grid to represent the voxels (even though most of the time it is sparse, but more on that later) and started using marching cubes to triangulate the voxels

Marching cubes has problems because every vertex in your grid has to be represented via a floating point (in a naive approach) so that it can do it’s interpolation. This uses way too much memory.

I switched to a bit mask approach. Every voxel in the world is 1 bit, and the triangulation is done on the fly and discarded when it is offloaded to the gpu (or collision). While our memory footprint is down this creates numerous challenges.

  • We have to build our chunks anytime we want to query the mesh
  • We need to have a way to smooth/triangulate our mesh without using marching cubes.
  • We have to figure out how to remove seams in our chunk meshes when we triangulate. (we get this for free with marching cubes at the cost of memory)
  • Build times for chunks has to be fast!

The triangulation is done by going through each vertex in the chunk and finding the positions of each of it’s orthogonal neighbours. The average mean position of these neighbours is calculated and this is set as the new position of this vertex. This whole thing is done multiple times to get a smoother and smoother mesh. Data is stored in a matrix which is used to query what point has which neighbour.

This is fine, but it creates seams with neighbouring chunks and odd triangles that need to be removed (not quite sure how to fix the latter yet).

Dealing with seams involves increasing the radius of the search area, so that voxels from neighbouring chunks are included in the smooth.

For this to be playable, build times have to be fast, which is difficult because smoother terrains require more iterations. The code above may have to run 3 – 4 times to produce something that looks good! In order to address this I:

  • Performed some profiling and did some micro optimisations.
  • Reduce the number of voxels in each block.
  • Amortized the builds. Only 3-4 rebuilds are performed each frame. This is okay, but the player will see gaps in the terrain while this is happening. (this could be mitigated with fancy particle effects perhaps).

This is work in progress and currently is…fine. There are number of things that can be done to improve it though.

Along with amortizing the rebuilds I can rebuild multiple chunks on separate threads at the same time. This will require one instance of the build matrix per thread, but the memory overhead of this shouldn’t be too taxing.

I also need to address the memory layout of my working matrix. It is stored as a contiguous array but this is an issue if a point in the chunk queries its neighbour that happens to be on another row. For example, in 2D with a width of 10, if a point (0, 0) queries its row neighbour (0, 1) this memory address is 10 points away from where we currently are. For big widths you are likely thrashing your cache which will be slow. I’ve thought of ways around this. One way would be to bundle sections of the matrix together, so that neighbours are next to each other *some* of the time during computation.

Fundamentally using a grid to represent, effectively sparse information, is the elephant in the room. I know that in some global illumination and lighting implementations, sparse voxel octrees are used. However, I’m not quite sure, as of yet, how to use that to implement voxel triangulation. This is something I will have to think about it.

Anyway play it here:

https://tednesday.itch.io/voxel-planet-gen

Capture
tednesdaygames
http://tednesday.wordpress.com/?p=143
Extensions
Wayfarers
Uncategorized
Wayfarers was a return to Unity. Personally I would prefer to work with my own stuff but game jams are harder to do that way (particularly in a team). There was a team of three of us this time. The biggest I’d been in. Personally, I wanted to make something that was a good contender […]
Show full content

Wayfarers was a return to Unity. Personally I would prefer to work with my own stuff but game jams are harder to do that way (particularly in a team).

There was a team of three of us this time. The biggest I’d been in. Personally, I wanted to make something that was a good contender for the top spot at Ludum Dare.

I think it went pretty well. We got it working in the browser and lots of people played it. In the end we came 79th which is not bad out of 5000 games!

Play it here: https://tednesday.itch.io/wayfarers

wayfar
tednesdaygames
http://tednesday.wordpress.com/?p=136
Extensions
Slave: Unchained
Uncategorized
Slave: Unchained was another Ludum Dare game jam entry. This time I moved away from Unity and used my custom engine. I worked alongside my trusted Estonian artist and we got to work. This was really a test of the current engine I had. Having a 48 hour time window basically revealed to me everything […]
Show full content

Slave: Unchained was another Ludum Dare game jam entry. This time I moved away from Unity and used my custom engine. I worked alongside my trusted Estonian artist and we got to work.

This was really a test of the current engine I had. Having a 48 hour time window basically revealed to me everything that I actually had to do and had been putting off.

By the end of it we had something playable which was good, although there were some teething issues when it came to shaders. Some GLSL features will compile and run fine on one GPU and crash on the next. That’s very difficult to predict!

Play here: https://tednesday.itch.io/slave-unchained

unchained
tednesdaygames
http://tednesday.wordpress.com/?p=125
Extensions