Dev Week 8

This week we’ll talk about the current health and maturity of Bevy!

Last week we introduced Bevy as “A refreshingly simple data-driven game engine built in Rust” created by Carter Anderson. The more I’ve looked into it and spent time with it, the more it feels like the right engine for me.

Bevy was released to the world approximately August 10, 2020. At version 0.1.0, it was already a formidable engine with a lot of potential and love, gaining over 2000 stars on Github within the first week. Anderson learned a lot from watching the Amethyst Rust-based game engine through its trails. Amethyst seems to have struggled from being too open, too early, without developing a strong enough design goal and singular focus. Anderson began in stealth by himself, working for about 5 months before unleashing it into the world. In order to drive Bevy’s success, he chose to be the BDFL “as long as he can”; note that such a position requires a lot of time and dedication, or otherwise that person can eventually become a serious bottleneck and prevent the rate of growth expected by the community and users of a project. Although there were a lot of smart people working on Amethyst, it sounds like they often struggled to come to a consensus which led to a lack of cohesion in the engine overall.

Anderson had previously been working at Microsoft. Although it’s not entirely clear what he worked on at Microsoft, he claimed both on Github and in an interview Podcast episode that he was moved by his experience at Microsoft to develop a wide-open piece of software (as opposed to a corporate-controlled closed-source project such as Unity or Unreal Engine). Although he said in the interview that he wasn’t getting paid quite enough yet to be comfortable (the interview was from June 2022, so hopefully that’s changed), he was hopeful to get more funding to be able to continue working on the engine full-time more comfortably including paying additional project contributors.

Although the website https://amethyst.rs is gone and Archive.org’s Wayback Machine isn’t responding to queries right now, Amethyst essentially gave their blessing to Bevy, as Anderson explicitly called out Amethyst as a backbone and its source of success, as Anderson had the foresight of Amethyst’s struggles. Amethyst tells its contributors, Bevy’s success is your success! They kept the Github Amethyst repository open for a while to address bugs of existing users, but it was archived in April 2022.

Bevy was given a good base, a dedicated and motivated creator and BDFL, and a large community of users and contributors who many I would guess were emotionally invested in being a part of Amethyst. The community and Github are extremely active, with chats in the Discord continuously throughout the day, including some of the core contributors/leaders responding to messages. They’ve even responded to my questions directly!

The documentation is not complete or exhaustive but it’s been very well done for everything I’ve needed; they have examples and unit tests that need to be passed when merging PRs, so there’s almost always a working example of some piece of code you might need to accomplish a common task! They also have automation through a Github bot which, for example, tells code contributors when they have added a “feature” to Bevy but haven’t updated corresponding documentation to match! Their use of development tools, frameworks, and workflows shows a high level of maturity for the project, even though they haven’t reached version 1.0.0 yet.

As of today, their main branch is tracking version 0.15.0-dev while their most recent release version (recommended for most users) is 0.14.2. Bevy (and many projects these days) follow Semantic Versioning, which means for example that code written for 0.14.0 should continue to work even on version 0.14.8 and so on, mostly adding new features or changing the underlying implementations while still remaining backwards-compatible within 0.14, while 0.15.0 can make breaking changes that aren’t compatible with 0.14.*. This means that once they make an official release of 0.15.0 they will recommend for most users to migrate, which can be a challenging task and has led plenty developers to abandon Bevy, but they also write comprehensive migration guides that aim to ease the pain of transfering from one minor version to the next.

We have chosen to track the main branch, i.e. currently 0.15.0-dev. This means that as soon as some functionality we’re using in Bevy introduces breaking changes, our code will break, which will require that we continuously update our code to continue working with the latest-and-greatest of Bevy. As Bevy has just in the past few days made some breaking changes to remove Bundles in favor of following more closely with the more generic System/Querying capabilities of “entities that contain this combination of components” (whereas a Bundle would wrap multiple components into one), Tera had to (and was able to) update her code to render text on the screen properly.

It may be a while before they hit 1.0, but everything so far has been extremely promising. It’s very fortunate that Anderson had a vision and the resources necessary to create this powerful tool, open and free for everyone. I haven’t even talked about the core technologies behind it that I’m most excited about such as the System graphs (constructing a graph of system dependencies to be executed in order at runtime), Query and QueryData! (And of course all of the gamedev must-haves such as graphical rendering and sound!)

Thank you for joining us this week!

Unfortunately, The Wayback Machine isn’t responding when I try to visit at the moment, but maybe it will be responding again by the time you read this. Here are the relevant links:

Posted in Dev

Dev Week 7

Last week's post was a bit of nonsense about abstractions while I researched and got a little more acquainted with Rust. This week we actually got some working code! Most of the features referenced here are available on Github in violet_bevy_plugins. I intend to keep as much of my code as I can modular and widely usable in that plugin as I build my own game on top of it.

With Rust+Bevy we managed to knock out several base features in one week!

  • Grid-based everything
  • Scrollwheel zoom
  • Click-drag (you're going to need an easy way to navigate around the world of machines!)
  • Perlin noise terrain generation
  • Generation of ores at random locations
  • Some basic diagnostics information as an optional feature using cargo run --features diagnostics - thanks to a very friendly person on the Bevy Discord!

So far, we're using Bevy and enjoying the heck out of it (Github). Bevy is "A refreshingly simple data-driven game engine built in Rust" created by Carter Anderson. It's built from the ground up as an open source library. More specifically, it's an ECS: Entity Component System. If we borrow this image from Wikipedia:

We can think of the ECS as being a spreadsheet, where each column is an entity, rows are components, and an entity will have an entry in the given row if it has the given component. The "Systems" part of ECS is a series of functions that will act on our entities and components. Systems are written using "Queries"; for example, we can write a function called render_shapes which makes a query for any object that has both a position and a shape; then the function has enough information to render the shape in the given location. It iterates over all objects that match the query and renders all of them.

Then for any functionality we want, such as collision detection and barriers that prevent player movement (such as walls or buildings), all we need to do is ensure that every entity (players, npcs..) that can't walk through a wall or building has the needed components (probably something like a Rigid Body and a Position).

The great thing about the entity-component system is that it is parallelizable. As long as you organize your entities, components, and systems properly, each system will only have to do so much work per frame while many of the systems work at the same time, resulting in a minimum amount of time required to perform all the work each frame.

While I haven't actually dug down into how Bevy's deep internals work, I can imagine it's a very interesting bit of technology that somehow calculates dependencies and creates some of computational graph to determine what can run in what order. Just by using base Bevy we already get parallelization benefits, while in C++ we would have had to learn a lot of deeply technical stuff just to perform simple multithreading that wouldn't lead to deadlocks. This will help us focus on the logic of our game machinery rather than focusing on "getting the code to work".

We have a pair of animations showing click-drag to move around the map and zooming in and out. We'll sandwich the animations between static images so hopefully anyone who's prone to dizziness can skip relatively easily.

First, the entire game is grid-based. There might be characters that can move independent of the grid, but machinery will be placed on the grid. This will make programming much easier.

Here we show ore generation at random locations on the grid. We mostly just needed to slap some stuff on the map so we could test out our scrolling and zooming, and as simple examples of how to draw sprites and give entities positions on the map, particularly ensuring that the sprites are scaled to the correct size to fit in their expected grid.

Below, we have our zoom. I got help from an MIT-licensed game called Magus Parvus on Github. Their code uses a part of the Bevy API that was removed only recently (it might have even been in the past week or two; we're running off the main branch, which is now 0.15.0-dev, while Magus Parvus was written on Bevy 0.14.0 and current stable is 0.14.2), but as a Rust beginner, it has been reasonably easy to figure out how to navigate around and use the code.

Above, we have a demonstration of click-dragging on the world to navigate. The player may have a character body to control in the future, but right now the focus is towards building machines and interactions between them, so making a character and enabling it to walk around would just slow us down, both from development time and from being able to navigate and interact with the world.

Next, we have some nice little diagnostics along with an actual grid made of lines. The diagnostics helped in aligning the grid and making sure all our code everywhere was correct. Tera wrote the original grid and we iterated on it. Eventually the grid will be toggleable and fill the whole screen instead of a hard-coded area of the map. I also borrowed a cute little wizard from Tera for demo purposes.

Last, for the moment, is perlin noise generation. There's a Perlin noise crate that we pulled in by simply cargo add noise in our project, and used their little code snippet to get us up and running.

Last week I was so excited about getting into Rust that I ended up spending too much time researching! This week I really focused on implementing game functionality. While learning a new programming language is challenging, the main challenge has been about how to organize the code, both in the context of Rust and of Bevy. Bevy offers the ability to develop and use functionality as logical units called Plugins, which we've used since day one. While there is some level of dependencies for the low-level stuff, such as the MainCamera that is needed in multiple locations to perform calculations and move the camera around correctly, plugins have kept our code nice and modular.

The only (or biggest?) downside of using Bevy is the instability of their API. They are upfront about the fact that it's not stable and users can expect breaking changes approximately every 3 months. However, I think it has really good bones and so far seems well maintained as Carter has scaled it beyond his own capacities by delegating to trusted parties.

The Bevy examples are kept updated and there's lots of automation for keeping everything updated together. For example, just skimming some of the recent CI activity, I end up on an issue where a feature was added in Cargo.toml and the bot responded: You added a new feature but didn't update the readme. Please run cargo run -p build-templated-pages -- update features to update it, and commit the file change. That's so cool!! This gives me a lot of confidence that Bevy is growing healthily.

My goals for next week are to start implementing basic machinery. We have these "ore" patches which supposedly have an associated amount of data/resource (haven't actually confirmed that it worked properly), and we want to simulate the data being passed and processed from one machine to another.

I'm really happy with how much progress we've made in one week, and Rust has been a joy to work with!!! Thank you for joining us this week!

P.S. I spent way too much time researching for last week's post, wasn't even sure what I was going to write it about, and then didn't feel great when it didn't meet my expected level of quality. This week I focused on development first, and this post only took 2 hours to write. I think focusing on the project first and having the blog post come after is much better and ends in a much better blog post anyways!

Posted in Dev

Dev Week 6

Welcome to another dev week! Last week we realized that the Rust programming language will help us scale our codebase with confidence compared to the wishy washy landscape of C++. But how?

If you wish to make software from scratch, you must first invent the CPU.

~My parody of a beloved quote by Carl Sagan

Finding the right level of abstraction

Abstractions enable us to understand or use complex concepts with less mental overhead and burden. Abstractions are one of the most valuable tools in software programming for developing high-level functionality with less mental burden. However, finding the correct level of abstraction is a constant challenge with each step we take with software.

An example in computer networking

Internet

Most people have a fuzzy idea of what the "Internet" is. It's a place where we can "transfer data", including text, audio, and video across this magical wire (or airwaves) to someone anywhere else in the world. Put simply, it's a communication medium similar to making a phone call, but a phone call only allows audio, and early days of the telephone only allowed very few privileged parties to access and use it at once (no packet-switched networks then). And most people don't need to understand the internet at a much deeper level because websites, web services, our devices (phones, computers, operating systems), and web browsers abstract it all away for us. All a user needs to know is to open their web browser app and type a url or search into their address bar. The next thing they know, they "visited a website" and now they have information on their screen from that website. "Apps" abstract this away even further, because all you have to do is click on the app and you are presented with various functionalities backed by that organization's services/servers, so all you have to do after logging in is hit "new post" and start typing.

Packet

Most new computer networking students have a fuzzy idea of what a "packet" is. As mentioned, the telephone network only allowed a few conversations at once because of the limited amount of wires, and each wire could only transmit one conversation at a time (this is not entirely accurate, but again, the whole point of this conversas whtion is abstractions). The packet (and internet protocol, IP) is what allowed us to reuse these few wires more effectively to allow many communication streams at once, providing the backbone that led to the internet we know and love today. A packet is simply an abstraction around turning that large video file on your device into many smaller pieces suitable for transfer over the internet to the intended destination while simultaneously sharing those wires with millions of others. Without packets, the internet would be relegated to a few who could afford the rights to time slices over those network cables, and we wouldn't have the wide-open internet as we know it.

Packet Internals

Some enthusiastic computer networking students will understand deeper still what a packet is and how it works. It has various bits of information (literally bits) which specify what type of data it holds, protocol versions, source and destination IP addresses, and all kinds of information needed to get it across the internet to the intended destination. Out of everyone today who has learned or will learn what a packet is, most of them will never write a single line of code destined to create or transfer a packet over the internet. This is because we have FOSS web server software with permissive licenses such as Apache and Nginx which abstract this away for us, so we can focus on concepts such as "websites", "services", and "HTTP APIs" instead of packets.

Even the SFML game development library for C++ has a Packet class, but again, developers need not know how the packet works; they only need to know to put data inside of one and receive data out of one on the other end. The documentation even states, "Packets provide a safe and easy way to serialize data, in order to send it over the network using sockets (sf::TcpSocket, sf::UdpSocket)." For those who know what a packet is, this sf::Packet is not even a real "packet", as "you can consider there's no limit" (Laurent Gomila, sfml forums). In that way, the sf::Packet is abstracting away a block of arbitrary length data into packets for you, so you don't have to. It would be more accurately called a "PacketReadyDataStream", but that doesn't sound as nice.

Why abstractions matter

One of the main purposes of abstractions is to reduce mental burden and enable ourselves (and others) to move forward with less effort. When it comes to doors in our physical world, a doorknob provides an affordance which enables us to understand how it is meant to be used. A doorknob provides a public interface that most people will understand: "twist this to unlatch and therefore open the door". We don't need to understand how the twisting of the knob moves mechanical gears and springs in order to recede the latch bolt away from the strike plate, or even such jargon, because engineers have abstracted it away for us into a simple and understandable "doorknob" interface. There may be 10 different unique types of mechanisms used to make the doorknob work, but we don't need to concern ourselves with any of them to use it. The same goes for engines in cars for drivers and software libraries for developers.

How is this relevant to us?

I've made plenty of GitHub repositories that I "hoped would be useful to someone". When I originally got a Stream Deck, I failed to find any (linux-compatible) user-friendly GUI software that could allow it to be configured and used as nicely as Elgato's software. Instead, I found the low-level Python library python-elgato-streamdeck which has very little abstraction (sending image data and receiving button press events). I built my own abstractions on top of it, encapsulating Boards/Pages and Buttons. While these were good for my specific use case, they required a programmer who understood the code directly to use them, and I could quickly see that my abstractions were falling apart (or simply weren't nice enough) as I tried to build something more complex. I knew this meant I needed to redesign the abstractions and refactor (or start from scratch based on what I had learned), but it ultimately never happened. I'm not too heartbroken considering I eventually found an excellent software called StreamController. StreamController has very nice abstractions for pages, actions, labels, board layouts, and just about anything else you might want to use to conceptually build your Stream Deck GUI. As a result, it's easy to configure and doesn't require deep understanding to set up and use. The abstractions provide our clean interface for interaction.

Another abstraction mistake of mine was the Camera I implemented in the Python version of my gamedev adventure. Although it appeared to work as expected, I wasn't confident in the implementation details and therefore lacked confidence in my usage of the abstraction. Since I was the only user of my Camera abstraction, (also I didn't write any automated tests..yet..) I didn't have testers to reveal logical issues with the Camera's translation from world or pixel coordinates to drawing coordinates. This hindered progress of my development as I started using the camera to translate everything before drawing, not knowing for certain whether a machine or character was standing on the block that it appeared to be. It probably was, but I couldn't prove it to you; and that's what we need! Proof that our abstractions aren't deceiving us or misrepresenting their internals!

How is this relevant to switching languages?

Whenever a new piece of software is being built, the designers must decide on the abstractions and levels of abstractions. C++ aimed to provide abstractions to coders to control system resources (memory) with minimal (runtime) overhead. However, C++ didn't also implement strict rules and magical algebraic stuff to disallow various types of memory-related flaws. Rust has the concepts (abstractions!) ownership, borrowing, and lifetimes, which the compiler can then use in an automated fashion to understand and reason about your code in a way that's not possible with C++. There are plenty of tools to try to help you do just that in C++, but they still don't offer strict guarantees. Rust is your friend, but in C++, the onus is on the developer and their decades of experience to avoid making those bugs.

I'm almost certain that highly advanced C++ developers are silently citing rules in their head every time they interact with pointers:

  • "Treat your pointers like a cat in a new home: make sure they have a safe place to start" (always initialize your pointers).
  • "A null pointer is like a pizza box in the fridge: always check it's not empty before you get your hopes up or risk crashing the party" (always check pointers for null before dereferencing).
  • "Think of dynamic memory like a subscription: someone has to pay for it, and you better cancel it when you're done or it'll keep draining your resources" (manage ownership and lifetime resources meticulously).

After looking into SFML, I really liked the general abstractions and level of control it still offered its users. But as I dug in and understood the difficulties it faced in the past and today, I recognized that SFML itself (and many C++ libraries) struggle with abstractions of the underlying language. For example, C++ has too many error handling mechanisms, which each have their own distinct camps of thought throughout the developer community all supported by decades of individual experience, which leads to heated disagreements of how error states should be thrown or handled by a library. "Even boost filesystem has support for both codes and throwing. Exceptions are too oppressive to be used everywhere. :(" https://en.sfml-dev.org/forums/index.php?topic=13240.0 And even explicitly bleeds into breaking the software design choice fourth wall (from 2014): "maybe in 20 years from now we will look back at these ideas [of how to handle errors] and wonder how we could be so crazy ;)" https://en.sfml-dev.org/forums/index.php?topic=13240.msg93032#msg93032

By providing more opinionated abstractions over resource usage and error handling, with the foresight of C++'s struggles, Rust has had time and space to develop a robust solution, giving developers safety by default, and control when they need it. Rust isn't perfect; it can still have memory leak issues such as reference counting cycles or in some cases with collections. But Rust's goal is to disallow undefined behavior, which significantly reduces the risk of "what if?"s littering your codebase.

Game Development

I haven't picked a game development framework for Rust yet, but Bevy Engine looks extremely promising. Bevy is the primary successor to Amethyst which was archived in April 2022. Carter Anderson's GitHub intro line is: "Creator of Bevy Engine | GameDev | Programmer | Artist | Previously Senior Software Engineer at Microsoft." He got to learn from the challenges of Amethyst to build "Amethyst Engine 2.0". From reading about Amethyst's history, it seems that although it was beloved and built by many, it struggled from too many cooks in the kitchen, while Carter had time to build Bevy from the ground up and learn from Amethyst's codebase and history.

There is another game engine in Rust called Macroquad, but it struggles from the abstraction level problem. It is trying too hard to support WASM (building to a game/binary that can be used directly in a web browser page), and since WASM didn't (doesn't?) support FPS limiting (frames per second), Macroquad hasn't been enhanced to handle FPS limiting on any platform. While the oldest open ticket I could find relevant to the issue was opened in October 2020, there are many tickets since then that all come to that same conclsuion. This is a failure to provide the right balance of control and abstraction to the user. Although Bevy may not directly support this capability either, there's a plugin, and there's still an open issue to have well-thought-out support for it someday (and not tacked on as a hack). That's not to say you can't do your own workaround in Macroquad, but when focusing on desktop-first and web-second, it's not promising that the library intentionally doesn't give support for frame limiting just because it can't be supported natively in WASM (also the core/original problem originates in WASM, but bleeds into Macroquad..).

Rust Progress

I spent quite a bit of time learning Rust this week. I progressed most quickly and happily when I focused on completing the Rustlings challenges, then when getting stuck, referencing Rust by Example for the most relevant code, and then if I was still stuck, referencing the Experimental Rust Programming Language Book, and then the Primary Rust Programming Language Book.

While the most important concepts of Rust (ownership, borrowing, lifetimes) may be challenging for those who have ingrained habits or otherwise have a very strong understanding of how their language should work, the basics aren't too difficult to grasp, and I consistently feel welcomed into Rust with open arms, both as a developer and as part of one or more marginalized groups.

Although there was some tumult in Rust in the 2020-2022 time range, they've since developed the Rust Foundation which provides better oversight and transparency into organizational decisions.

Regarding security, Rust has plenty of CVEs that are being tracked. There is also research, but some suggest, for example, that the Heartbleed bug couldn't have happened in Rust. David Svoboda et al show that Rust still has security limitations and potential for misuse, but is overall certainly "safer" than C++.

Side note: I've been making Anki flashcards for learning Rust; I wrote about Anki back in 2018, and as I look now, Anki's codebase is 45% Rust!

Now

As a result of beginning with a new language and wanting to get acquainted with the community and history, I spent a lot more time researching and digging around than I would have liked. I really enjoyed it, though I ended up on a lot of deep side-paths (for example learning more about compilers) that I realized might never make it into this blog series. As a result, I found my attention was too far split, and with the inundation of knowledge I've accumulated all week, I actually struggled to focus and decide a specific blog post topic. I wanted to talk about so many things. Maybe this just means that I'll never run out of topics, and maybe it means I can make better-structured versions in the future of my currently fuzzy ideas. There were several topics I did want to mention but am running out of time for (and several topics I should probably not waste any more time on, since I don't find social drama particularly interesting or fun).

Next

My intention this week will be on getting Rust to become second nature as I focus on coding first, and a blog post flowing out of that effort next. I also hope to have a game/media framework/engine chosen by next Friday. The goal with Rust and whatever engine we use is for them to reduce mental burden and get out of the way so we can focus on the goal at hand: developing some awesome software!

EDIT: Finished the Rustlings within a few hours after this post.

Posted in Dev

Dev Week 5

Safety, guarantees

Last week: Out of Python

Our goal last week was to get more into the C++ and CMake world because we wanted stronger guarantees to reduce risk of programming bugs. Python has dynamic typing, duck typing, and overall lack of first-class support for strict typing. But C++ has strict typing, and you certainly can't make a variable without defining its type, so that should help us prevent lots of would-be bugs.

Developing a game will get complex very quickly, and I believe that managing complexity to maintain target growth is one of the largest barriers to success in a software project. Many games that fail to scale while keeping bugs at a minimum end up with poor reviews on Steam. The goal is to reduce mental burden at every step of the way. In terms of project management, this includes:

  • Reduce branching factor of directories (smaller number of files and folders within any given folder) so that you can quickly navigate and find what you're looking for. In contrast, Cataclysm has 819 files in a single folder (this has absolutely nothing to do with the fact that it's C++; I was simply looking for inspiration in an open-source C++-based project; C++ is certainly capable of having different organizational structure than this)
  • Smaller scope for variables so it's easier to reason about what they do. In C++, when you make protected data within a class, you're implicitly making a kind of "global" scope since any subclass can modify that data, potentially leading to bugs
  • Minimize or eliminate side-effects (if you have to make a side-effect as a hack to fix something, you're already beyond screwed and it's time to rearchitect and refactor major portions of your codebase)
  • None of those pesky old pointer bugs or memory leaks
  • Provable safety through mathematically oriented checks

This week: Into C++

Searching for Safety in C++

Past exposure

Aside from the promises of high performance and a lot of support in the game dev industry, my exposure to C and C++ has been through a cybersecurity lens about the horrendous security vulnerabilities due to dangling pointers, use-after-free, and all kinds of exciting pointer- and memory-related issues. Let's make sure we follow best practices so we don't fall prey to those same issues. Let's turn our safety and security scanners up to 11 so we can focus on developing a game; not chasing bugs.

Expectation

Considering that C++ has been out for 39 years, they must have figured out most of the problems by now. My compiler and toolchain will protect me from bugs, right? And considering that I've been on a FOSS binge lately, there must be a community-driven, single-source-of-truth resource out there that tells us how to code C++ correctly today.

Search

I looked for a solution in the open source community. In particular, something like an online book that says "This is the correct way to solve this common programming problem today, given the features now available in C++, and how to enforce it in your compiler." Something that is actively maintained. The more contributors and activity, the better. Hopefully 100+ contributors, 5,000 commits, 10,000 stars; something like that.

But the only resources I could find that seemed trustworthy and updated were books from publishers, which are cost prohibitive for me right now, and https://www.learncpp.com/ which is excellent and teaches you how to write safer code, but still certainly can't offer you any guarantees. With a language as big, seemingly healthy, and beloved as C++, shouldn't there be a big red button somewhere that's easy for newcomers to find and use and says "enable safe C++ only"? There are plenty of resources, but it seems overly complicated for someone who's just beginning; I'm hoping to start writing safe code sooner rather than later. I'm experienced and can learn all that stuff, but my goal is to reduce mental burden so we can focus on building a game.

I found myself on what seemed to be a side path reading a very academic discussion spanning multiple Github repositories about the language used to describe ""-based includes and <>​-based includes, a fundamental concept in software development that allows us to split our code into smaller, more understandable chunks and share our code with others. Different compilers have different behavior for "" and <>​ in terms of how and what you can do with them and the order in which they are searched for within your machine. The result is watery, imprecise language surrounding these topics that leads to somewhat comical side-effects such as naming it "the thing manipulated with -I" or "the <> search" or "relative file versus angle-bracket include search path". If we can't even figure out what something as simple as this is called, how can we expect to navigate the ocean filled with unclear language?

Someone there mentioned something called the C++ Core Guidelines.. written by the man himself, Bjarne Stroustrup!

C++ Core Guielines

C++ Core Guidelines

It seems we may have found the answer; the secret to getting our safe code! Let's begin reading.

Following the rules will lead to code that is statically type safe, has no resource leaks, and catches many more programming logic errors than is common in code today.

Excellent! We're off to a great start.

The rules emphasize static type safety and resource safety. For that reason, they emphasize possibilities for range checking, for avoiding dereferencing nullptr​, for avoiding dangling pointers, and the systematic use of exceptions (via RAII). Partly to achieve that and partly to minimize obscure code as a source of errors, the rules also emphasize simplicity and the hiding of necessary complexity behind well-specified interfaces.

These are precisely all the things we want! Where do we sign up?

We are uncomfortable with rules that simply state “don’t do that!” without offering an alternative. One consequence of that is that some rules can be supported only by heuristics, rather than precise and mechanically verifiable checks.

No mechanically verifiable checks.. only heuristics.. Okay. So clearly, there's going to be some burden on the developer to ensure they follow these rules.. Just make sure you learn and follow the rules?? Am I getting this right?

Some rules aim to increase various forms of safety while others aim to reduce the likelihood of accidents, many do both. The guidelines aimed at preventing accidents often ban perfectly legal C++.

Yes, I'm perfectly happy to ban legal C++ if it improves safety. I want to get started with writing C++ already! But.. "aim to reduce the likelihood"?.. This language is a bit too open for me..

However, when there are two ways of expressing an idea and one has shown itself a common source of errors and the other has not, we try to guide programmers towards the latter.

Okay.. we try to guide the programmers.. as in those little toddler fences? Why can't I just disable those features with the click of a button or the installation of a single "The Correct Compiler" version 2024?

These rules are not meant to be read serially, like a book. You can browse through them using the links. However, their main intended use is to be targets for tools.

Okay.. these guidelines are meant to be implemented as tools.. and yet they can't verifiably provide safety.. and this guide isn't meant to be read by mere mortals.

What did we find?

C++ is a behemoth of backwards compatibility. It was designed from the ground up to be backwards compatible with C. This is part of its core philosophy from the beginning (so says Wikipedia).

When you first start using compilers to compile and run your code, you probably don't realize that what you're getting by default is a monster. It will let you use features from C++98 (as in 1998). As a newbie navigating various online resources, it's not immediately clear that the advice you're following might be using those same features. The same features that lead to memory bugs and pointer bugs and all those "fun" things we're trying to avoid. If you're fortunate enough to gain awareness of these issues, and are willing to do something about it, you must begin a personal journey to figure out how to disable them. Searching around the internet, you can find safer compiler settings to use, but it's not exactly reassuring that you have to search for this resource and explicitly tell your compiler every which way to not allow unsafe code (literally "your compiler", since every compiler will have a different set of safety settings).

One of the major purposes of getting off of Python was to get safer code; why do we need to tune and churn the compiler into submission? Why can't I just type gcc --safe main.cpp​? Better yet, why is the default not simply gcc main.cpp​ (safe by default) and when you really need it, gcc --unsafe main.cpp​?

C++ has made significant strides towards safety each version since and including C++11 (2011). But compilers don't enforce it by default and it's not easy to find a recent up-to-date resource that isn't currently cost prohibitive which clearly guides you on setting all the flags and then how to use the safe features. Most resources explicitly state they will not prevent you from using old constructs. You have to piecemeal your own learning guide, while all the other newbies piecemeal theirs.

Failure to satisfy the constraints

So, although I found this great effort by the designer of C++ himself, there's no guarantees, there's no direct ability to turn off unsafe features, and you're not even supposed to read the guidelines directly. Although some brave souls might read this document and try to abide by it.. I even started reading a bit of it and learned quite a bit.. You can still make plenty of critical bugs because that's the way it's always been.

As a side note, the White House (of Washington D.C., US) issued a statement earlier this year essentially begging developers (or their employers) to stop using C++ in favor of memory-safe languages such as Rust. I'm not automatically saying that I agree with them; it's just an interesting side point that highlights how far up the food chain these problems have reached that the President of the United States has to say something about it. There's also frequently quoted research by Microsoft and Google that 70% of all security bugs are related to memory safety issues, particularly in C and C++. If Microsoft and Google are still using toddler fences to try to "guide" their $100,000-per-year developers to avoid those types of bugs, I don't trust that there's any safe configuration in C++.

And into Rust

My girlfriend has already started learning Rust; multiple friends of mine have shown interest in Rust with some of them actively learning it; it's been out for 9 years; it's provably safer than C and C++ and there are whole classes of common bugs in C++ that you can't even make in Rust if you tried. You don't have to tell the compiler not to let those bugs happen; it simply won't let you compile that code!

  • How to start a new project? cargo new my_project; cd my_project
  • How to run the code? cargo run
  • How to add a dependency? cargo add rand
  • How to add a dependency directly from Github if it's not on https://crates.io? cargo add --git https://github.com/not-fl3/macroquad.git macroquad​ # this is one possible media-/game-oriented library
  • How to compile and run with the new dependencies? cargo run
  • How to build a release-ready compiled binary? cargo build --release

The main concern raised by newcomers to Rust is the steep learning curve of the borrowing system. However, they have an amazing free online book with quizzes that carefully introduces how to use it, with visualizations of the stack and heap, variables, and pointers. It's still challenging, but isn't any new programming concept challenging? After all, once you figure this out, you'll never make another dangling pointer bug...

After spending about 1.5 weeks down the C++ rabbit hole and finding the associated communities, it was a tough decision to switch off, but not as tough of a decision if you consider the tens, hundreds, or even thousands of hours of my life that could be wasted chasing down memory issues in C++. I'm excited to grow something big and complex, fun and engaging, without worrying about pointer problems and unnecessary memory issues. (I'm excited to tackle genuinely interesting memory-related challenges such as optimizing memory usage and minimizing defragmentation with strategies such as Object Pool.)

I only started working through the Rust book today, but I'm already just past the first section of ownership, 4.1 What is Ownership? Tl;dr:

  • An object on the heap can only be owned by one variable at a time
  • The lifetime of the object on the heap is tied to the lifetime of the variable that owns it
  • When the owning variable goes out of scope, so does the object on the heap, and it gets cleaned up and freed back to the memory pool

There's also references and borrowing, which I'll most likely talk about (at least briefly) next week. I expect to be able to get through most or all of the 20 chapters by the end of next Friday.

I haven't completely written off C++ forever. The world is built on C and C++. The majority of my tech stack from the Linux kernel to the windowing system are mostly in C and C++. There's also an initiative to make C++ safer, which is aiming to do something like I was previously searching for: a compiler that simply won't compile a variety of unsafe constructs. However, despite C++ approaching 40 years of age, it's only an initiative and it's not out yet (it's dated this month, 2024-09-11).

The Future

I'm excited to begin with a memory-safe, non-garbage-collected, high performance language that will scale with my game. I think there's a lot of bright years ahead of us!

Thank you for tuning in this week!

Side Notes

This is extra junk I wanted to talk about but didn't want to clutter up the main content too much.

I wrote an entire section on CMake that didn't make it into this post. Tl;dr: CMake isn't just a text-based configuration; it's an entire LANGUAGE. You don't just have to learn C++, you have to learn CMake too. And the CMake world is as confusing and messy as C++.

C++ians seem to believe that struggling through the challenges with memory bugs and learning how to prevent them is a rite of passage, not something to be completely avoided through early training, rigorous practices, and compiler settings. But I don't want to be unknowingly planting landmines all over my codebase that I have to struggle with later while I wade through the piles of resources just to learn the basics that I wish the community would present at the forefront. 40 years is a long time and the community is fragmented; any conversation over ""- versus <>-style includes is likely to spark debate that never concludes satisfactorily as there are many compilers with different behaviors.

A lot of the work-work available to me is in C++. I haven't seen a single Rust-related task yet. As a result, I'll probably still be learning and using C++; I just won't be trying to scale a codebase with it.

One thing that I found that I was really excited to share this week had I continued down the C++ path is Cling. Cling is essentially a REPL, which allows you to quickly type C++ code on the command line and test it out, reducing the time required to see the results of your code as you try, for example, static casts of data types to see the results. For fun, try:

#include <cmath>

// fits uint, sets every bit to 1
static_cast<uint>(pow(2, 8*sizeof(uint))-1)

// overflows uint, result is every bit set to 0
static_cast<uint>(pow(2, 8*sizeof(uint)))

Another super cool thing you can do is make a C++ script similar to how you can make a Shell, Bash, or Python script. If you have Cling installed, you can save the following to a file, such as unique_ptr.cling, enable the executable bit using chmod u+x unique_ptr.cling, and then run it from the command line using ./unique_ptr.cling. This makes it easy to, for example, create lots of little samples and demos of C++ functionality.

#!/usr/bin/cling -std=c++17
// when you use "./unique_ptr.cling" on the command line, this line
// tells your shell to start this script with the /usr/bin/cling interpreter
// using the C++17 standard features

#include <memory>
#include <iostream>

class Texture {
public:
    Texture(int val) : value(val) { /* Load texture here */ }
    ~Texture() { /* Free texture resources here */ }
    int value = 5;
};

int main() {
    std::unique_ptr<Texture> texture = std::make_unique<Texture>(5);
    std::cout << "our texture has a value of: " << texture->value <<std::endl;

    // No need to delete texture, it will be automatically destroyed when out of scope
    return 0;
}
main();

You can still have memory issues in Rust, but they will more likely be associated with memory fragmentation as you request and release large portions of memory without carefully managing how they are laid out on the heap. If you're not careful about how you design your software, a user/attacker may be able to trigger it to use up memory more quickly than anticipated and result in a crash, but at least it will only lead to a crash. This is still an attack vector on availability that can lead to an opening for DoS attacks, but at least it won't be opening a hole that allows attackers to run arbitrary code and steal information from your computer such as banking credentials. In C++, triggering a crash is the first hint to an attacker that they can probably exploit the vulnerability into something much scarier; in Rust, I hope (and will research) that a crash typically leads to just that: only a crash.

I didn't read a ton of the C++ Core Guidelines, but my favorite part that really suck out to me was Avoid protected data and Minimize exposure of members. (Note "Avoid" protected data and "minimize exposure" of members, which continues my concerns; why can't I tell my compiler to entirely disallow protected data?..) This offers an excellent example showing the danger of protected data that implicitly becomes a sort of "global" variable that becomes modifiable by any subclass, making it difficult to reason about the scope and behavior of these variables. The guidelines have plenty to teach, whether you use C++ or not.

Posted in Dev

Dev Week 4

This week we've started with C++ and CMake. While other languages like Rust have an ecosystem for making video games, C++ still seems to be the de facto standard in the game industry. There is a rich history and community in C++ and a variety of open-source projects, and CMake has come a long way in the past 5-10 years alone. While many projects' Make and CMake files are still riddled with ancient blobs of cross-platform madness, 3.0 and several particularly important minor patches since (currently on 3.30) have made it easier to obtain cross-platform support with less effort. There's even a directive that enables you to pull a dependency directly from Github while CMake can handle the depenency seamlessly under the hood so you don't have to. As an example, let's see the FetchContent section of the SFML CMake project template:

include(FetchContent)
FetchContent_Declare(SFML
    GIT_REPOSITORY https://github.com/SFML/SFML.git
    GIT_TAG 2.6.x
    GIT_SHALLOW ON
    EXCLUDE_FROM_ALL
    SYSTEM)
FetchContent_MakeAvailable(SFML)

The tag can be changed to switch to a different version of SFML with just a few keystrokes. In particular, following their README, we've changed it from 2.6.x to the main branch of SFML which holds sfml3. In fact, 3.0.0-rc.1 was released just today! Another benefit of FetchContent is that you now have the source and can compile the debugging symbols directly so you can step-debug into your dependencies if you need to.

The point of switching over to C++ was to be able to get a better developer experience, providing stronger guarantees about how our code works (otherwise we'd just use Python). For that, we need, at minimum:

  • Debugger: GDB
  • As much compiler-time checks as you can get your hands on: Clang-Tidy, AddressSanitizer..
  • Profiling runtime memory and performance: Valgrind
  • Full IDE support: intellisense, go-to-definition, and all of the above integrated to make development as bug-free and smooth as possible (inline compiler checks approximately every time you save to disk).
  • Testing framework, i.e. unit testing. We haven't researched into this one yet but I know there's GTest and CTest.

Using Valgrind+GDB used to be very difficult, but fortunately in 2023 there was official work to directly support using them together. And with a little help from a web search, and a helpful person named Ferran Pujol Camins, we know exactly how to use Valgrind and GDB together in CLion. It might be a little bit tricky to understand (I'm coming from Python, just give me a minute), but a quick overview that might help you understand it more quickly:

  1. You set up a run/debug target to use Valgrind, which you configure to start GDB mode immediately when it starts by using --vgdb-error=0. This means that it won't wait for any errors before automatically launching gdb, whereas the default is to wait until a certain number of errors occur before waiting for your gdb to attach. Then, instead of just hitting the run/play or debug/bug buttons, run one of the valgrind modes (coverage/profiler/memcheck) which starts your session and waits for a gdb client to attach.
  2. Configure another run target that starts the gdb client and attaches to the already running vgdb server. Now your code will stop at your set breakpoints!

Using this method, without even leaving CLion, we can now inspect variables, execution paths, profile, check for memory leaks, and profile overall performance.

When I was originally introduced to IDEs in uni, they were big and scary (or just bloated and unnecessary; I preferred TextMate 2) and I thought I would never understand them, but now that I understand a little bit of the C++/CMake ecosystem that the IDE is trying to abstract away, I see the value of those tools. While we still have to make the CMake file that could end up complex, it's still saving us from a lot of extra typing and messy unmaintainable undebuggable nightmare shell scripts (ugh).

Since I'm starting directly on sfml3 which uses C++17, it will be useful to check the migration guide for good examples of the new correct ways to do things. Since 3.0.0-rc1 was just released today, it appears there is no updated documentation/tutorials for 3.0, but they may be coming soon.

Getting back to CMake, while existing projects may be a mess, new CMake projects can be a lot simpler. There are lots of resources on how to do CMake correctly now. The primary resources I've been using include:

  • https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1 (lots of handy linked resources)
  • https://github.com/dev-cafe/cmake-cookbook A promising CMake cookbook, it hasn't been updated in a bit but hopefully it shows correct practices
  • Asking questions and searching how to do things the correct way. Don't just copy from somewhere; look into the ways to accomplish a given goal and figure out which one works better for ease of maintenance and remaining cross-platform. There are a lot of samples out there that are outdated; CMake has become easier, so make sure there's not a better way before using something that doesn't look nice.

I'm pleased to continue my foray into the open-source world this week, as I've managed to make an update to the Gentoo wiki (updated a reference to a portage package which moved to a different classification a while ago), and updated SFML's website tutorials to point to the correct minor version documentation when referencing classes.

Game

And finally.. the point of all this is to make a game!!!! So far we're working on the project structure, something similar to the existing Python project but more C++-like with associated namespaces.

We have a machine, and have used the component design pattern in order to separate the responsibility between client and server so the server doesn't need to render sprites and the server can be the source of truth for all clients. And in the case of single-player, the client can potentially (ideally) run the "server" code internally (each machine would run the client and server components simultaneously) and not require to double memory for every machine (and every other object).

This diagram was generated using the following Mermaid diagram code using their live editor:

classDiagram
    class Machine {
        +RAM
        +UpdateState()
        +PerformOperations()
    }
    class MachineServer {
        +Simulate()
        +HandleNetworking()
    }
    class MachineClient {
        +RenderGraphics()
        +HandleUserInput()
    }
    Machine o-- "0..1" MachineServer
    Machine o-- "0..1" MachineClient

As for the actual implementation of the machine, it may as well just be printing statements to the console! Right now the focus is on general structure that will allow the project to grow without too much pain, and to decide which of the (very few) parts will make it over from Python. Beyond that, we have all the exciting challenges that manual memory management bring in C++ (though RAII is one way to make it much easier and less error-prone), and thus carefully considering the design especially as it relates to memory usage.

Even when I don't know what I'm going to write before I begin aside from a few talking points, I end up with a whole post! Who knew this much stuff is going on in my head! (Weekly meetings in industry were never this awesome.) See you next week!

Posted in Dev

Dev Week 3

SFML logo

The past two weeks, we've faced some of Python's language typing limitations as we try to create generic classes. We're officially switching to C++/SFML. See last week's post for some details and a specific problem example in Python.

We've always had C++ on our learning wishlist, but it's finally time to do it. I took C Programming as my final elective before undergrad graduation, and I finished the entire semester's course material (all labs) within 2-3 weeks (it was at a community college, so take that with a grain of salt if you please) plus an extra "honors project" (I probably would have done another one if COVID hadn't hit right then).

C++ gives us access to deep control over memory in our program. Python is beautiful to the eyes and can be made to do quite a lot, but we ran into problems with the typing system, especially as it relates to generics. We had a composable GameNode class that has handle_input, update, and render. It can have any number of GameNode children, i.e. it's recursive. Now that it's time to work on GUI, we wanted a similar recursive composable class that has all the same handle_input/... functionality as GameNode but with a stronger guarantee: instead of children being any GameNode, GUIElements' children are also GUIElements. Then our GUIElement has recursive functions for drilling down to be able to quickly figure out exactly which element the mouse is hovering over, rather than checking overlap with every single GUI element.

Specifically, we don't want to iterate over the rectangles of every single element on the screen - we should only check a given element if the mouse is hovering over it, then check that element's children to see if the mouse is on top of any of those, and recurse until we know the exact element the mouse is hovering over. This saves a lot of unnecessary iterations, calculations, and overhead.

We had some code like this:

from typing import List, Optional, TypeVar

from ..game_node import GameNode

class GUIElement(GameNode['GUIElementType']):
    _parent: 'Optional[GUIElementType]'
    _children: List['GUIElementType']
    ...

GUIElementType = TypeVar('GUIElementType', bound=GUIElement, covariant=True, default=GUIElement)

Our original GameNode can have a parent that is also a GameNode, and children which are also GameNodes. Now we want a GameNode-like object GUIElement which is like a GameNode and can be treated the same as a GameNode, but we want to make a stronger assertion that its parent will be a GUIElement and its children will be GUIElements. We had upgraded our project from Python 3.12 to 3.13 specifically for the default argument of typing.TypeVar(..., default=...), but as mentioned in our previous post, our seemingly simple example kills Mypy server instantly.

Additionally, we had to make concessions with Python typing as we sometimes accepted typing errors. I searched high and low for a solution but eventually gave up because it was actually time to make progress on our codebase:

class GUIElement(GameNode['GUIElementType']):
    _parent: 'Optional[GUIElementType]'
    ...
    @property
    def parent(self) -> 'GUIElement|None':
        return self._parent

    @parent.setter
    def parent(self, value: 'GUIElement') -> None:
        # Unnecessary "cast" call; type is already
        # "GUIElement[GUIElement[GUIElementType@GUIElement]]"
        # Pylance reportUnnecessaryCast
        self._parent = cast(GUIElement, value)
        # Cannot assign to attribute "_parent" for class
        # "GUIElement[GUIElementType@GUIElement]*"
        #   Expression of type "GUIElement[GUIElement[GUIElementType@GUIElement]]"
        #       cannot be assigned to attribute "_parent" of
        #       class "GUIElement[GUIElementType@GUIElement]"
        self._parent = value # as hideous as this was, we had to accept it and move on.

I've been using Python for over 10 years now, but do we really want to keep fighting with the typing system? I think it may finally be time to move on..

Onto C++

So, it's time to dig up our old knowledge of C and start applying it to C++. As I was on StackOverflow trying to figure out the core issues with Python to get it working, I took a detour to Tag search, chose Python, and then sorted by highest votes. This is what we get. Now I know more about Python than I ever did before. And so, this seems like a great way to learn some really important topics for a given language, especially one so complex and contentious as C++. Here's the same search but for C++. This includes a post about The Definitive C++ Book Guide and List since C++ books are cheaper by the dozen and most of them are bad. REALLY BAD. Most of them encourage you to do bad things that would make the true gray beards of C++ rant and rave.

Now we can get more strict guarantees and can ideally ensure the generic typing that we want. It will take time to learn, but I was already pushing against my own experiential limits with Python, so why not just apply that hard work to another valuable language? We learned some generic typing back in Java, but I can't take Java seriously since it's Boilerplate: The Language and their restrictive licensing that caters to mega corporations (thanks, Oracle).

Learning C++ will go hand-in-hand with the knowledge that university dumped in my brain about operating systems. The specific class I took recommended the book Operating Systems: Three Easy Pieces by Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau (although operating systems are commonly written in C, not C++, but they still have a lot of valuable ideas and solve a lot of similar problems as we will have in game development). The challenge will be with navigating the multitude of different C++ standards and avoiding learning things incorrectly from outdated or ill-informed resources. C and C++ are well known for the horrendous bugs they can allow (buffer overflow/underflow, use after free, and the list goes on) because of various pointer misuses, and it is paramount to understand memory layout and apply standards and best practices appropriately.

Game. Duh.

And.. the whole point of this is to build a game! For that, we will be using SFML. They have a dedicated Learn page where I've navigated to download the SFML Game Development book's examples source code, where I've already learned how to keep the game loop in sync with the realtime clock even when the update loop or the screen render takes too long (maybe Pygame was doing that for me already, but now we know for sure that we are handling it properly in C++).

I originally forgot to write a blog post last week, but recognized how much work I had actually done and wanted to make sure it's clear that I'm not sitting on my hands, so I went ahead and wrote last week's and back-dated it for consistency 🙂 Thanks for joining us this week! See you next week!

Posted in Dev

Dev Week 2

a pixel art of a clothing washer machine made using Aseprite.

This week we got Gentoo installed on a Microsoft SP4 tablet!! This includes touch support so that we can make game sprites using Aseprite.

A washing machine sprite to wash all your dirty data!

We started developing a Qt6/Pyside6-like API in Python to create in-game GUIs for Pygame. It's on Github but I've already switched away from Python (see more info from next week's post) so it probably won't get any more updates.

A pygame-qt window with a single window widget containing many widgets (they would have eventually been implemented as buttons) that have configurable padding on each side individually. The colors are generated for each element one time at startup and stored in memory. The "button" widgets were added in a for-loop allowing a dynamic number to be added. The QHBoxLayout grows to the right as new widgets are appended. The handlebar would have eventually served as a way to click-drag the window to move it around.

This week as we continued down the rabbit hole of trying to add strict typing to everything in Python, we hit some hard limits, either in Python's capabilities or in my understanding. We had previously upgraded the project to Python 3.13 (from 3.12) specifically for the default argument in typing.TypeVar(..., default=...). However, as we proceeded to use the default argument, we ran into issues with Python typing itself and with Mypy (see ticket here). Here's the code:

from typing import TypeVar, Generic

class GameNode(Generic['GameNodeType']):
    pass

GameNodeType = TypeVar('GameNodeType', bound=GameNode, default=GameNode)

This short block of code is enough to insta-crash Mypy: "AssertionError: Must not defer during final iteration." We didn't dig into it any further, but it would seem that it was delaying trying to figure something out about the typing and ended up never doing it, so it gave up.

We see in this code block, our goal was to create a generic composite GameNode (which can support regular game loop behavior like update, handle input, and render), but we don't want to have to type GameNode[GameNode] or something every time we subclass it. The generic should automatically take on another GameNode so it continues to accept any GameNodes as children without having to specify the generic type every time. Maybe I'm misusing Python typing, but even so, the experience felt lacking. Next week, I'll talk about switching from Python to C++.

Posted in Dev

Dev Week 1

Game

This game is supposed to be Factorio-like but with some of the perceived "issues" "fixed". These are not real issues, but part of the challenging constraints of Factorio's gameplay. An example - when you get further into the game and use blueprints, sometimes you need to change the design of your blueprint later. But everything created out of the blueprint isn't automatically updated. Here, we consider a virtual, software-like scenario where, whenever you improve a component that you've used in multiple places, everywhere you've used that component automatically gets the benefits. This follows the same ideas as programming software.

Purpose

The purpose of this game is still not yet known. In Factorio, the primary goals are to survive the biters and launch a rocket. We certainly want a biter-like challenge (virus/malware? competitor factions trying to hack you?) and some kind of end goal, but it's not decided yet.

Artistic Themes

We want the themes to be computer-oriented. Think of backgrounds like synthwave album cover art, and PCB/motherboards.

Progression

Some thoughts on progression: unlock more RAM/CPU, and/or more core functions.

Design Challenges

As of right now, we're not sure where these magical "resources" are supposed to come from. Do we suck data out of the internet and process it? How "realistic" should it be? We still hope for it to be game-like with a wide enough audience. We also don't want to accidentally develop something that people could see as "creepy". For example, there's a game on Steam called "Learning Factory". One of the reviews said that while the game is fun and the mechanics are interesting, it reminds them too much of Amazon (and apparently the game mentions Amazon directly) and there's mechanics for optimizing the price to extract the most value out of your cat customers for the items you sell them (such as balls of yarn). We also want to balance complexity and simplicity. The goal is "easy to learn, challenging to master." A game which you can sink tens or hundreds of hours into and feel your time was well-spent on an enjoyable experience.

Mining Resources

For now, we must make progress somehow. As of this week, we can "mine resources"! All it does is iterate through all the patches it's on top of and decrement their amount. The amounts can even go negative and it doesn't remove the ore patch, but all the parts are joined together and updating properly. While mining resources may or may not fit into the final view of the game, we must begin somewhere, and this is somewhere.

Inspiration

As mentioned previously, this is inspired by Factorio, but as many concerned individuals note, there are many-a-Factorio-clone, and our goal is to make something genuinely new. But as it's easier to copy than to dream up entire worlds from scratch, we must begin somewhere. Factorio is unbelievably beautifully optimized to handle thousands, tens of thousands, of entities, machines, and items. This is heavy inspiration for this game and we hope to get to a point where lots of neat tricks are needed to keep the game moving smoothly even as the scope expands to mind-boggling proportions.

I originally wrote an Asteroids clone in middle school for my "8th grade exhibition project". It was written in Blitz Basic; I didn't know how to organize my code and didn't have any mentor or tutor, so every change or enhancement to the code was a huge struggle. Now, thanks to a BS in Computer Science (not everyone needs one of those dang things anyhow), and several books including Game Programming Patterns by our munificent Robert Nystrom and the original Design Patterns: Elements of Reusable Object-Oriented Software by our fearless Gang of Four, we have a base on which to build something maintainable, extensible, and beautiful.

Closing

Thank you for joining us for our first week! We are incredibly excited to begin this journey!!!

Posted in Dev

Reverse SSHFS

Mount a local folder on a remote machine.

This method uses ssh -R to open a reverse tunnel, and the remote machine has to have authorization to ssh back into the local machine. This may not be practical for all users, and if that is not acceptable for you, then this method may not be viable.

First, from the local machine:

ssh remote-machine -R 2222:localhost:22

2222:localhost:22 says "on the remote machine, open port 2222 to point back to 'localhost:22' from my vantage point, which is the ssh port back into the machine I'm ssh'ing from."

Then, on the remote machine within that ssh session:

sshfs -p 2222 localhost:local_folder remote_mount_folder -o uid=$(id -u),gid=$(id -g),allow_other

By default, if you try executing code files inside of the folder, you get a lot of strange "can't open file" errors, even when you try doing so as sudo. The uid=...,gid=...,allow_other sets the permissions such that your user temporarily "owns" the stuff that's mounted, and then other users are allowed to access data in the directory as well. This allows, for example, to run code out of that directory that then run by a less-privileged user.

I find this method very helpful when developing on a local codebase but executing/running on a remote server.

The Average Internet User's Guide to Being Paranoid on the Internet

This is a rant from 2021-07-16 that will develop into a more polished and thorough blog post and/or series of blog posts.

when it comes to malware, prevention is the best. once you are infected with malware, you don't know how sophisticated the attacker is, and if they are sophisticated, then you almost certainly have a backdoor or a means of persistence (surviving reboots, surviving attempts at removal, etc). if you want to get rid of it, i really don't know where i'd start other than a reputable antivirus software.


have windows defender do a full scan. it can do some amount of detection and removal. i don't know how well (if at all) it handles rootkits. malware with sophistication hides itself from tools, modifying the operating system so that attempts to detect it are difficult or impossible. once you've run a full scan with windows defender, you want to turn your computer off and boot from external media like a flash drive, probably using a linux-based operating system, and then you can scan the drives with that.


with regards to prevention, there are a lot of things i do on a regular basis to prevent getting malware. you want to harden your machine, which means making it less susceptible to attack, and the means is usually just to reduce your attack surface. user-friendly operating systems like windows and macOS try so hard to be user-friendly that they basically have their genitals hanging out in the wind asking to get attacked. they work under the assumption that their software is secure, but there's always new zero days or other previously unknown vulnerabilities, as bugs are written faster than they can be found and squashed. such is the state of the software industry, and we trust much of our lives to software and operating system producers.


in all your email clients (web-based or native), set it to not download/load/display external content. even if not for malware, there's still tracking beacons in the form of single-pixel images that simply make your computer call out to all the peeping toms and say "I'm here! I'm here! come track me!" (by this, i mean advertising companies, whether they be giants like facebook or google, or even more vicious advertising groups).


go through all your operating system settings and turn off "let my device be discovered on the network". turn off bluetooth, wifi, and other wireless technologies. again, having wireless technologies turned on is like having your genitals dangling in the wind, asking to get prodded by anyone who knows how to put kali linux on a flash drive (hint: it's not difficult). i only use wireless technologies on a regular basis on my phone. otherwise, i'm wired for everything else, including my headphones.


macOS has a feature called "power nap", where even when it's asleep it will occasionally turn on briefly to check emails, text messages, etc… turn it off.


on your home router, go through your settings and turn off uPNP, universal plug-n-play. having that on is one of the absolute best ways to get hacked. i know it's tempting to open things up for gaming or so you can reach your NAS from anywhere in the world. don't do it. if you want to connect to your home network from anywhere in the world, use a VPN. there are a VPN router hardwares you can get to achieve this.


i personally would also use ipv4 with NAT, and turn off ipv6. your refrigerator and your alexa don't need their own public IP addresses on the open internet. NAT effectively works like a firewall, not allowing any unexpected traffic in. you're most likely already using NAT. when you access stuff on the internet from your computer, your router opens up a tunnel back in from whoever you're talking to so the communications can get back in to you. otherwise, unsolicited traffic is dropped at the router and never makes it inside your network (unless you have uPNP on or port forwarding enabled on some ports).


in your browsers, install ublock origin. it blocks ads, and a lot of ads are called "malvertising" because attackers can often inject a malicious payload into ads and get them downloaded by users. so, as much as someone might want to "support websites by keeping ads enabled".. you're also letting in attackers 👍️


downloading files.. this one is a little complicated.. but it's still very important to understand if you ever plan on downloading things (most people do). only download things from sources you trust. AND, only download those things from sites that have HTTPS! (S is short for "secure", and HTTPS is "Hypertext Transfer Protocol Secure".) if you visit a website and your browser says "hey, this page isn't safe!" and then you go ahead anyways, and then you proceed to download files, you're asking to get hacked. because someone can MitM ("man in the middle"), modify the downloaded file in-route to your computer, and inject malicious stuff in there to take over your computer.


OKAY, so there is an exception. there are such things as "mirrors", and many of them don't use HTTPS. AS LONG AS YOU DO SOME VERIFICATION, this MAY be safe. see, the original source will [should] give you a hash. hashes are one-way functions that take data of arbitrary length and return a fixed-length string of characters, such as a9b07e070fa2a28976a7d460abb300d1. whenever you download a file from an http source (and preferably also from an https source), you MUST check the file hash! and make sure the hash they provide isn't md5 or sha1, because those have been cracked. attackers can inject malware and still make calculated modifications to the payload get the hash to match. with stronger hashing algorithms like sha256, sha512, there currently aren't any publicly known ways to crack them.


if you get a link from a mirror and aren't given a hash, assume it's malicious. seriously.


and even if you are given a hash, if you don't trust the source, still don't download it.


ahh, checking links before you click. because one click is all it takes to get attacked. and it's not like clicking the link will cause you to obviously and immediately get compromised. it's not like you click the link, a webpage loads, and it says "haha! i got you!" or your screen goes dark or something. if attackers have any level of sophistication, they will hide as best they can. and they will use your machine to send send emails, launch DDoS attacks, and possibly to try to infect other machines on your local network (i.e. your computers on your home network aren't even safe from each other! don't leave important data/files on an unauthenticated file server!!)


you can almost always hover over a link (in email clients, web browsers, etc) and see where they go. if you can't, then you should scream expletives as loudly as you can directed at the vendor of the software, and then never use the software again, and also never trust the company, and then publicly defame the company whenever you get a chance.


the most important part of a url is the domain name. unfortunately, lately many companies have sold their souls to advertising companies and bypassed important security guarantees of the internet, and they've basically allowed themselves to be taken over so they can continue to track their users (well, now the site A that decided to do that, with advertising company B, and now B has their hands in A's pants or wallet or wherever they want to put their hands).


okay, back to urls and domain names. in https://www.google.com/q?=something+interesting, the domain is google.com and the subdomain is www.google.com. i personally own two domains. it's about $16 a year. and there are attackers who buy look-alike domains like paypai instead of paypal, called "domain squatting", in the hopes that they can trick you into clicking links to their site and drop your credentials to them, or simply download a tasty malicious payload to your beloved machines. so be very careful when checking links to make sure that the base domain (google.com in this example) is exactly who you think it is. READ VERY CAREFULLY!


in addition to that, don't click a link that says https://evilhacker.com/innocent_webpage.html.


AND, even if the displayed url with blue and underlined LOOKS like a full url, STILL HOVER OVER IT AND READ THE URL!! DON'T LET THEM TRICK YOU!! it's so unimaginably easy to make the actual url look nearly the same while still being a malicious website, while the displayed url is actually a friendly website.