[In this reprinted #altdevblogaday technical piece, WhatIf Productions' Jake Kolb discusses Darwinian Coding and the different assumption patterns that coders often fall into.] Welcome: Have you ever had (or made) enough time to thoroughly re-evaluate past project coding choices? It can be a humbling although entertaining activity. After nearly 30 years of video game programming, I realize that time-scales really affect how you see things, whether it's an 'immediate moment' choice of a variable name, a 'short-term' decision on a comment's level of detail, medium-term class diagramming/system planning, long-term design document, or the after-shipping post-mortem analysis. Since reviewing past projects revealed how much I couldn't see at those shorter time scales, I'm targeting this blog on the patterns that are easily missed. The goal being to caution your design-considerations against my mistakes and share some pros-and-cons of past solutions. While veterans may find little newness, I hope novices or those currently developing similar systems might at least spark some dialogue on their choices. Hopefully the phrase 'Darwinian Coding' evokes the notion that the most successful coding choices, particularly for long-term large-projects, are not always the most powerful/optimal but instead the most adaptable. In my journey, the most important metric of code has been how it survives change. Whether drifting design docs, shifting QA feedback, blame-ridden profiling results, OS/Bios/Driver/SDK/Compiler updates, or even new hardware/net-services, change can cost time and innovation. Code-survival means less time re-writing, debugging, and integrating to adapt to those changes. The bulk of common programming advice, such as Knuth's 'premature optimization is the root of evil' or 'profile-early/profile-often', or 'metrics-metrics-metrics' seems to boil down to the childhood warning against relying on 'assumptions.' Ironically, as coders we are often forced to make assumptions in the interest schedule-realities, needing to delegate tasks to others, relying on trusted advice of experts, ease of cut'n'paste code, or sometimes lack of other options. Code survival relies on identifying those assumptions and finding the patterns in them that will pop-up elsewhere. Each of my blog-articles will cover these 'assumption-patterns' given a problem's context (where we use this code), goals (what we need from the code), solutions (how we tried to solve it in the past), and a survivor (who has proven best over time). The biggest survival sagas (aka changes in coding approaches) in the past involved:
App_Services (Providing asynchronous processing capabilities) —entry below—
Reversible_Timelines (How to build a rich-AI & physics world with rewind/fast-forward amd network propagation)
RPG_Improvising_Rules (Using Pen and Paper RPG flexibility in rigid Digital Simulations)
Knowledge_Representation ( Signs, symbols, relationships, inferences, remembering, forgetting, etc)
Energy_Propagator (Propagating energy through matter, managing error metrics and logarithmic scales, suited for invisible RF/sound/thermal or imaginary 'energies' since we are too sensitive to aliasing/roughness/inaccuracies in the visible spectrum)
Behavior_Creation (Using feedback loops to adjust behavior trees and mixing actions to generate complex behavior recipes)
Security_Islands (Methods to protect Application-services, User-choices, and Simulation-events/thinking means to secure/limit data changes)
Shape_Synthesis ( Constructing destructible and animatable shapes for rendering, physics, and general AI queries)
Possibility_Mapping (How to synthesize new animations through constrained mixing of local animation areas)
Bit_Shipping (Methods to combine transforming, compressing, encrypting, and transferring raw bits of known data stream types)
Cultural_Text (How we manage dynamic paragraph layout, resolutions of mobile vs many screens, cache rendered words/sentences, rich font formatting, aliasing, writing orientations)
Coding_Productivity (What choices/habits have universally been of benefit, code-generators using scripts, including naming conventions, file/make organization)
Project_Productivity (What hurt/helped projects to get finished on time, auto-generated documentation & in-game bug-tracking, expectations vs humility vs drive)
Self_Balancing_Metrics (How to dynamically adjust analog sensory subsystems (graphics, audio) and discrete subsystems (physics, AI simulating) to balance quality vs interactivity (frame-rate))
A visualizer that relied on App_Services to balance network streams (as I can't find a thread-specific image)
Application Services Context: where we use this Application services is a name for how your software engine provide services, such as rendering an image or loading a file, to the actual product. App-services are usually library or system calls, often directly spawning threads, or adding a task to a job-stealing pool. Sometimes they use networking to contact another computer or network domain to make requests. In all cases, this is how we harness all available CPUs and other processors as well as network access to deliver the best performance (at least best under a given a battery-budget/power-settings). App Services provide background tasks such as filling or mixing audio buffers, file I/O, monitoring network messages, and decompressing assets. In this context, they can also provide immediate foreground services such balancing rendering loads, where there would be app services to mix geometry, search a list of text, count valid elements in an array, or walk a scene graph. Goals: what we need