systems + GENERATED_BODY

This commit is contained in:
liquidex 2024-04-09 14:33:39 +02:00
parent 76b6833c44
commit 85937c65b6
4 changed files with 267 additions and 0 deletions

View file

@ -0,0 +1,150 @@
%% title = "systems are just a bunch of code"
% id = "01HV1DGFGNV3DXD8A3CW2J4RZP"
- often enough I see people scared to dive deep into the internals of their favorite technologies
% id = "01HV1DGFGN1KJRA93GWZZ3159S"
- because it looks scary, or they think it's not really gonna impact their lives too much.
% id = "01HV1DGFGNNZN0RR9DFV2G6EXM"
- and people tend to form these grandiose noun phrases like _The Object System_, or _The Borrow Checker_, making it seem like there are impenetrable walls that guard these complicated pieces of software,
and that only people worthy enough will be granted entry into them.
% id = "01HV1DGFGNHX9H39FFT4BTZ8RB"
- _but great adventurers fear no dungeons, as the deepest dungeons often hide the most precious treasure._
% id = "01HV1DGFGN729RHYNZB7AS36TD"
- over time I've been growing accustomed to _knowing my dependencies_.
after all, how can I _depend_ on some code functioning correctly, if I don't even know how it works, or what to do to repair bugs in it?
or even where to go to customize it to my liking?
% id = "01HV1DGFGNA306RSQW9NRV79YQ"
- it's largely annoying to me that compilers ship without their source code, nor do they ship with debug symbols.
it would be great if whenever `rustc` crashes on me while incremental-compiling the treehouse (and it does so pretty often),
I was able to catch the crash with a debugger and step through the code to see what happened.
% id = "01HV1DGFGN2WE5RK5X31H5AFSP"
- but alas, performance and size optimizations make that impossible.
I just end up filing bugs in the compiler repo `OR` moving on with my life.
% id = "01HV1DGFGNJ0ZA14QQH40S5FNS"
- I wonder what the world could have been, had we been compiling and optimizing software on users' computers rather than downloading ready-to-use, but opaque binaries.
% id = "01HV1DGFGN5CAV3RP4GXASQVQJ"
- I imagine a world where I can tell the computer "I'd like to debug this crash" after the compiler crashes, and it will download the relevant sources and let me single-step through the
code to see what happened.
% id = "01HV1DGFGNY3FGEKY70D6RMV22"
- and then I could edit the source code and submit the patch *right where I'm standing* rather than having to go through complicated and lengthy processes of cloning repositories,
bootstrapping, reproducing, and such.
*imagine fixing compiler bugs on real codebases rather than having to come up with isolated, reproducible examples first.*
% id = "01HV1DGFGNXJJX0BMMM3KQ42M0"
- I know how hard this is to achieve in practice, which is precisely why I don't use Gentoo.
% id = "01HV1DGFGN8WEWY1CVV4HNNBQ4"
- package managers like Cargo make using someone's code incredibly easy, and that's really cool.
we should be encouraged to share reliable, well-tested code instead of reinventing half-baked solutions every time we need a thing.
% id = "01HV1DGFGN3V7WTM1FQE8GC92D"
- but I think at the same time they make understanding someone else's code kind of an afterthought.
% id = "01HV1DGFGNVD0BPN0S9WCGQY61"
+ to integrate a library into your C++ project you usually have to browse through their CMake build files to figure out how they name their build targets.
and while doing that, through cursory glances at `CMakeLists.txt`, you gain knowledge of what knobs can be tweaked in the project, how it _builds_.
you may even stumble upon a list of source files, which can give you clues as to the underlying architecture.
while this can hardly be called _understanding_ the code, at least it gives you a _peek_ into how it's structured.
% id = "01HV1DGFGNW44T5FGJ7W4AT85M"
- (and I'm not defending C++ here, I think its dependency management has a terrible UX in the long run and makes sharing code needlessly hard - but like all tools, it has its strengths and weaknesses)
% id = "01HV1DGFGNGDB48GB1268R6DB5"
- Cargo on the other hand makes it needlessly difficult to _poke_ at someone's code.
% id = "01HV1DGFGNBJZX522X5EBMC6XM"
- when vendoring code into C++ projects, you make the build system treat your dependencies as _just another piece of code in your code base_,
which means you can insert debugging code wherever you please, and the build system will happily rebuild it.
% id = "01HV1DGFGNJFFN73F5FF49V3YS"
+ Cargo treats dependencies as immutable, which means that a given version of a package only compiles _once_ per your project.
you can't go poking at the package's files for debugging purposes - you can't insert `dbg!(x)` expressions where you need them, which is really annoying.
the only way I know of is to use [overrides](https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html), but compared to editing an already vendored dependency, those are quite cumbersome to set up...
% id = "01HV1DGFGNE4REYV9QKV3494AM"
- you need to clone the repository at the correct version (I don't even remember the Git command to clone a repository at a given tag)
% id = "01HV1DGFGNQ54Q71EB7W7F7R80"
- which means opening a separate terminal, `cd`ing into your folder with repositories
% id = "01HV1DGFGNX61NAMNGSMWSMX5W"
- also adding the repo as a project in your IDE
% id = "01HV1DGFGN41KWVQX8J9JG3E9H"
- then you need to edit `Cargo.toml` to override the dependency
% id = "01HV1DGFGNGXC4WBYQ1MZT5HMK"
- have fun typing in that full path `/home/daknus/Repositories/cool-library`
% id = "01HV1DGFGN5X57Z807GMS3F07Q"
+ *and* don't forget to revert the changes to `Cargo.toml` once you're done.
unlike editing a vendored dependency, which will appear under a very visible path in Git, it's pretty easy to forget to check your `Cargo.toml` diff before staging/committing changes.
I mean, I edit my `Cargo.toml` all the time, adding in libaries and stuff.
so why would I look at every single change?
% id = "01HV1DGFGN0DAMASGQD64XXTRK"
- *and* before you go at me and say "bah you should be reviewing all changes that end up in your codebase"
sigh.
listen. I'm not gonna review all the garbage I commit into my personal website, alright.
I value fast prototyping over having a clean Git history in this particular case.
% id = "01HV1DGFGN6P3BDXEXZEB97GY2"
- don't forget that overriding dependencies doesn't always work - if the resolver cannot resolve your override, it will not build your project.
% id = "01HV1DGFGN72Q12G1AKVGEXRXV"
- most of the time the more reliable approach ends up being editing the `workspace.dependencies` entry for your dependency, and changing it to use a `path` instead of a `version`.
% id = "01HV1DGFGNPWTRV8W582JRPEH2"
- this all sounds automatable, but it's pretty annoying nevertheless that the basic functionality of _poking into one of your dependencies_ is hidden away under layers of caching and patching and immutability and stuff.
% id = "01HV1DGFGNC1JV9ZSJAMQKZ61W"
- seriously I just wanna insert a `dbg!(x)`, how hard could it be?
% id = "01HV1DGFGNWWQ8XX2X67STEC8B"
- as an example, when I first started working with Unreal Engine, everything seemed like magic.
% id = "01HV1DGFGNJGMRWJZZ9Q7WYPFE"
- like [how in the world does the `GENERATED_BODY()` macro manage to expand to different things depending on which class or struct it's declared in?][page:programming/technologies/unreal-engine/generated-body]
% id = "01HV1DGFGN8MZB8YTGB5SFP577"
- but the more you poke at it, the more you look at definitions, the more you look at the build tools, the less magical it all seems.
it's all just code-generating syntax sugar!
% id = "01HV1DGFGNVRX5C3TST00V58DK"
- **everything is code! *there is no magic*!**
% id = "01HV1DGFGN569R7MEE9FBXRF7H"
- there are no walls blocking you from looking at the code. the grandiose noun phrases are misleading!
% id = "01HV1DGFGNE1CTAX01DE61GWC5"
- UnrealBuildTool is just a bunch of C# files. and the *Gameplay Ability System™* is likewise just a bunch of C++ sources.
why can't we come up with more inviting names?
% id = "01HV1DGFGN129VY5FHHF4GC4Z5"
- grandiose noun phrases sound so hostile. *but it's all just code.*
% id = "01HV1DGFGN96CPWYMJ14EWJJR2"
- most of the time not knowing your software is largely fine - not everyone has the time to deeply inspect a piece of software, deciphering functions, consulting manuals, asking the authors (when possible.)
% id = "01HV1DGFGNJYYN7SKYGCFDXN3M"
- what I'm saying is that we should be encouraging more engineering practices and tools that enable us to inspect and poke at our dependencies _when we need it_.
% id = "01HV1DGFGN0QE49S9VBGVYVZY9"
- _don't fear code_; respect it like it's your [Holy Mountain](https://noita.wiki.gg/wiki/Holy_Mountain).
a place to tinker with stuff, and occasionally wreak havoc, anger the fuck out of the gods, and let them kick your ass with an `EXCEPTION_ACCESS_VIOLATION` or another `Segmentation fault (core dumped)`.
and then [Steve](https://noita.wiki.gg/wiki/Stevari) kills you but you start another run anyways because this game of chasing bugs and perpetually improving software is just too damn addicting
% id = "01HV1DGFGN2EGFPD48WA8XZ34Z"
- next time you encounter a crash in some library you're using, try _stepping into it_ with your debugger. you might find some real gems in there.

View file

@ -14,6 +14,11 @@
% id = "01HP1FESY5WVJG4X80AZ4ZBX5D"
- ### random but cool things
% content.link = "programming/technologies/unreal-engine/generated-body"
id = "01HV1DGFHP6GB268MDGGDXMR12"
+ how does `GENERATED_BODY()` work exactly?
% content.link = "programming/technologies/unreal-engine/fixes"
id = "01HP1FESY5ZS6YTZXA8QTT5V1Z"
+ data validation quick fixes

View file

@ -0,0 +1,103 @@
%% title = "how does GENERATED_BODY() work?"
% id = "01HV1DGFHMD7KNNX3FWS6R11SF"
- the `UCLASS()`, `USTRUCT()`, and other reflection macros - but especially the `GENERATED_BODY()` macro might seem a bit magical the first time you see them.
% id = "01HV1DGFHM71ZD6CJY77N0PA8X"
- the thing about it is that it defies the usual rules of how C++ macros work.
let's have a look at it together.
% id = "01HV1DGFHM75BEC4B54MSBQ3P9"
- _what does it even expand to?_
% id = "01HV1DGFHM2SAR1JMV8BAGDNE6"
- try looking at what your IDE's autocomplete suggests -
from a cursory glance at the symbols available in a `UCLASS()`-declared class, you will notice there are a few that are non-standard ones, such as `StaticClass()` or `Super`.
this is what the `GENERATED_BODY()` macro ends up expanding to after all the preprocessing is done.
% id = "01HV1DGFHMZ4F2DYDDJXXJXX63"
- but the thing is that `Super` is a typedef to the parent class - how does it know the parent class without passing it in as a macro argument?
% id = "01HV1DGFHMG2FFHTC9EA5NXB78"
- and `StaticClass()` returns a different value for each class. this would require _knowing_ the class beforehand - how does it know?
% id = "01HV1DGFHMDFMWY2FWRHN3NGAX"
- the thing is - it doesn't. `GENERATED_BODY()` by itself is incredibly stupid:
```cpp
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);
```
let's disassemble it piece by piece.
% id = "01HV1DGFHM4BW1FXVX3AMJYR91"
- `BODY_MACRO_COMBINE` is just a macro combining four identifiers together. no magic here.
% id = "01HV1DGFHM29ENZ6HE36B2SP92"
- so `GENERATED_BODY` combines the identifiers `CURRENT_FILE_ID`, `_`, `__LINE__`, and `_GENERATED_BODY`.
% id = "01HV1DGFHM8JH5RMTV79P8HRDV"
+ `CURRENT_FILE_ID` is a preprocessor macro defined by the UnrealBuildTool for each file.
for simplicity's sake, let's assume it's the filename with dots replaced by underscores.
for instance, `GameplayAbility_h`.
% id = "01HV1DGFHM6XQ68XJPART8TND3"
- the actual form it seems to take is `FID_{Path}` with `{Path}` being the file path relative to the project root directory, with slashes and dots replaced with underscores.
for:
```
Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h
```
the file ID is:
```
FID_Engine_Source_Runtime_Engine_Classes_Engine_Blueprint_h
```
I haven't inspected the UnrealBuildTool/UnrealHeaderTool sources though, so there may be more to it.
% id = "01HV1DGFHMVS11W47KWXJC7TY4"
- `_` is just an underscore. nothing magical here.
% id = "01HV1DGFHMPC27J2JN30PRGQSF"
- `__LINE__` is a standard C++ macro which expands to the current line number.
% id = "01HV1DGFHMAEZV26CE77X5AXYF"
- and `_GENERATED_BODY` is just an identifier.
% id = "01HV1DGFHM2Q8EC8EMKN0HMXTB"
- therefore for a simple file, let's call it `MyClass.h`:
```cpp
#pragma once
#include "UObject/Object.h"
#include "MyClass.generated.h"
UCLASS()
class UMyClass : public UObject
{
GENERATED_BODY()
};
```
after expanding the `GENERATED_BODY()`, we'll get this:
```cpp
// -- snip --
UCLASS()
class UMyClass : public UObject
{
MyClass_h_10_GENERATED_BODY
};
```
% id = "01HV1DGFHM78BW738ENHCN0ASF"
- and this identifier is declared as a macro in the UnrealHeaderTool-generated `MyClass.generated.h` -
and expands to a bunch of declarations, including the declaration of `Super` and `StaticClass`, as well as constructors if they're not already declared.
% id = "01HV1DGFHMFZNP6S3E1YNC8QH7"
- you can even inspect the source code of `.generated.h` files yourself, by <kbd>Ctrl</kbd>+clicking on them (at least in Rider. I haven't tested Visual Studio.)
% id = "01HV1DGFHMP6ZP6N4WNWPTT04D"
- that's all there is to it.
incredibly simple, cursed as heck, yet super effective.

View file

@ -10,6 +10,15 @@
[read][page:programming/blog/tairu]
% id = "01HV1DGFHZ65GJVQRSREKR67J9"
- I've been thinking recently how cool it is to be able to single-step into Unreal Engine's source code and edit it while you're working with it, and how uncool it is that I can't do the same thing easily in the Rust world.
after all, aren't we just dealing with a bunch of code running on the computer? why not let me poke at it?
### systems are just a bunch of code
[can *you* can read other people's code?][page:programming/blog/systems] [bonus: dismantling Unreal Engine's GENERATED_BODY][page:programming/technologies/unreal-engine/generated-body]
% id = "01HTWNETT2S5NSBF3QR4HYA7HN"
- last night I couldn't sleep because of type theory. in the process of trying to write down my thoughts, I ended up discovering a class of types which, to my knowledge, no language implements.