diff --git a/content/programming/blog/systems.tree b/content/programming/blog/systems.tree new file mode 100644 index 0000000..eb816cb --- /dev/null +++ b/content/programming/blog/systems.tree @@ -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. diff --git a/content/programming/technologies/unreal-engine.tree b/content/programming/technologies/unreal-engine.tree index 49f445a..39e116a 100644 --- a/content/programming/technologies/unreal-engine.tree +++ b/content/programming/technologies/unreal-engine.tree @@ -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 + diff --git a/content/programming/technologies/unreal-engine/generated-body.tree b/content/programming/technologies/unreal-engine/generated-body.tree new file mode 100644 index 0000000..a486bb6 --- /dev/null +++ b/content/programming/technologies/unreal-engine/generated-body.tree @@ -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 Ctrl+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. diff --git a/content/treehouse/new.tree b/content/treehouse/new.tree index 537556f..37613ea 100644 --- a/content/treehouse/new.tree +++ b/content/treehouse/new.tree @@ -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.