update issues

This commit is contained in:
liquidex 2024-07-22 20:34:42 +02:00
parent 04a346d851
commit 2a68cfbf63
44 changed files with 1201 additions and 318 deletions

7
Cargo.lock generated
View file

@ -813,6 +813,12 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jotdown"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b87ebc8a1c0fdfdedb65abbffb023e45ebbbfe089625f93f6f5acc56685c0abf"
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
@ -1544,6 +1550,7 @@ dependencies = [
"http-body",
"image",
"indexmap",
"jotdown",
"log",
"pulldown-cmark",
"rand",

20
content/README.md Normal file
View file

@ -0,0 +1,20 @@
# treehouse content
The treehouse is built on its own custom data format called `.tree`.
I'll spare the explanation, because it's an _extremely_ simplistic format, but know that it nests:
- TOML metadata
- Djot markup
inside of itself.
TOML metadata is defined by `Attributes` from the `treehouse` crate.
Djot on the other hand has support for generic attributes.
Keys are not documented (read the generator if you want to know about them,) but generally `:`-prefixed keys are
reserved for the treehouse's own purposes.
You probably don't want to read the content if you're not me.
Browse the treehouse yourself, experience it fully, and then when you're _sure_ you don't want to get spoiled, come back
here and look for weird stuff.

View file

@ -48,17 +48,17 @@
+ like really, where does it end?
% id = "01H89RFHCQCCWWMZCXEXF704Q3"
+ *where?*
+ _where?_
% id = "01H89RFHCQW4NN6PW6V6RFCVH3"
content.link = "about-treehouse/the-end-is-never"
+ ***WHERE???***
+ _*WHERE???*_
% id = "01H89RFHCQPAMNSN81SRVH2TZ5"
- always evolving and shaping and shifting
% id = "01H89RFHCQEJZQFXKVVQ92816R"
- so that I never need to *finish* anything
- so that I never need to _finish_ anything
% id = "01H89RFHCQ3Q6W2Q0QKRHRVFVW"
- I can just sit down, type something out
@ -85,7 +85,7 @@
+ it is designed to be elegant and clutter-free
% id = "01H89RFHCQ4N6GMFF0Q2N4PCM6"
- the treehouse contains no ads to annoy you, no sidebars to interrupt
- the treehouse contains no ads to annoy you, no [sidebars][page:design/sidebars] to interrupt
% id = "01H89RFHCQDMWX7WNSMBDWJC9G"
- it doesn't even have a navigation bar, because it doesn't need one
@ -121,7 +121,7 @@
- but you may not find the experience favorable
% id = "01H89RFHCQ7HTZSP6P2RZR8JHE"
+ but most important of all, it is *weird*.
+ but most important of all, it is _weird_.
% id = "01H89RFHCQTRVPZ0AJ0DGJHXKX"
- weird as me
@ -207,7 +207,7 @@
- it also saves your progress as you read. if you refresh the page, you'll notice you end up exactly where you left off!
% id = "01H8V55APDNSXZMZJ2K1KW11KN"
- but, there is one very crucial piece of JavaScript that makes this website tick, and your experience **will be degraded** if you disable it. that feature is linking to branches.
- but, there is one very crucial piece of JavaScript that makes this website tick, and your experience *will be degraded* if you disable it. that feature is linking to branches.
% id = "01H8V55APDBG6AH2W3RNMX724X"
- by default, if you link to an element by its id and it's contained within a `<details>`, the `<details>` will not expand. :ralsei_dead:
@ -270,11 +270,14 @@
- other than that, `.tree` assumes nothing about what format the branch attributes or content are encoded in.
% id = "01H8V55APDCXPW8JPRYZHY9NXV"
- I chose TOML and Markdown for their ease of use and flexibility, but the parser couldn't care less.
- I chose TOML and [Djot](https://djot.net) for their ease of use and flexibility, but the parser couldn't care less.
% id = "01H8V55APDWDPSHNZX6QJFM8ZA"
- …actually, that's a lie. see that code example above? Markdown code fences \`\`\` are handled specially to let embed `.tree` source code blocks within `.tree` files. that's all.
% id = "01J3GNWRFBPK8VSRV1J61Q2V8F"
- I actually used to use Markdown, but then I saw Djot and progressively warmed up to it until I convinced myself fully that "yes, this is a better markup format."
% id = "01H8V55APDVQB6AD23Y6PZPPB8"
- every branch in the treehouse has a globally unique ID.
@ -306,7 +309,10 @@
- as well as highlight branches which were added since your last visit
% id = "01H8V55APDPZK4C8H5TTHTHH7J"
- not that either of these features are implemented as of writing this (2023-08-20)
- not that either of these features are implemented as of writing this
% id = "01J3GNWRFBK2W3X8BZHD2AXQXZ"
- actually they're implemented now so yeah
% id = "01H8V55APDXB9AXTAGAE9TNQRB"
- they're much more compact than [uuids](https://en.wikipedia.org/wiki/Uuid)
@ -370,7 +376,7 @@
- not everyone runs Windows or macOS, so just assuming the reader has Comic Sans installed isn't ideal.
% id = "01H8V55APD2BPGQXEK7JARYJ9H"
+ ~~in particular the variant on my website is 100% casual (`CASL`), and -2.0 slanted (`slnt`).~~
+ {-in particular the variant on my website is 100% casual (`CASL`), and -2.0 slanted (`slnt`).-}
% id = "01H8V55APDXZJE3HHH5AQ8ZQHF"
+ that little bit of slant makes it look just a little more like handwriting.

View file

@ -76,7 +76,7 @@
- but I don't wanna replace it because it's just too good
% id = "emoji/nap"
- :nap: - <sub>z</sub>zZ
- :nap: - ~z~zZ
% id = "01HQ3YQCYQ995V33NY6QFW6TVJ"
- my little guy is asleep. please do not wake (he is actually kind of sleep deprived and needs the rest)
@ -121,7 +121,7 @@
- ### [Noto Color Emoji](https://github.com/googlefonts/noto-emoji/)
% id = "01HA4HJKQ77H0X87VZ4AYWTSFG"
- ~~most~~ some Unicode emojis on this website use an old version of Noto Color Emoji, aka [blob emoji](https://en.wikipedia.org/wiki/Blob_emoji).
- {-most-} some Unicode emojis on this website use an old version of Noto Color Emoji, aka [blob emoji](https://en.wikipedia.org/wiki/Blob_emoji).
% id = "01HA4HJKQ7A9F5P200PDDVNBQD"
- commit `914c9ecb`
@ -142,10 +142,10 @@
- so I imported a couple (20) of them into the treehouse
% id = "01H8W7VEVP9FHW7QKZFXDRPRGD"
+ ### ~~[Twemoji](https://github.com/twitter/twemoji)~~
+ ### {-[Twemoji](https://github.com/twitter/twemoji)-}
% id = "01HA4HJKQ7JFAQBMFMD55EESFD"
- ~~sometimes used as placeholders for blobs I couldn't find in the original Noto emojis. someday I might design my own placeholders.~~
- {-sometimes used as placeholders for blobs I couldn't find in the original Noto emojis. someday I might design my own placeholders.-}
% id = "01HQ99RTH2YB5BZV7WDMQC4BT3"
- update: no longer the case because I now use [blobmoji][branch:01HQ99RTH2FQ92SB7T2EKDEFHJ]

View file

@ -1,33 +1,33 @@
%% title = "who that! (about me)"
% id = "01J09B2BZXJ989S2SGWBNZ397C"
+ hello! I am **liquidex**.
+ hello! I am *liquidex*.
% id = "01J09B2BZX54T2GSP8H3D62S6J"
+ also known as **liquidex**
+ also known as *liquidex*
% id = "01J09B2BZXQ31FV1M3PMM6Q0GX"
- this is [the identity function](https://en.wikipedia.org/wiki/Identity_function) applied to my nickname
% id = "01J09B2BZX0R0AX5QHND6EBRFE"
- also known as **liquidev** on [various programming platforms][def:social/github]
- also known as *liquidev* on [various programming platforms][def:social/github]
% id = "01J09B2BZXT6AAJNPEQ044KCDR"
- also known as **lqdev**, before I realized that name was taken.
- also known as *lqdev*, before I realized that name was taken.
you may notice this older name in some places.
% id = "01J09B2BZXBS2K6MRQYXFH43PW"
- also known as **daknus** [on SoundCloud][def:social/soundcloud].
- also known as *daknus* [on SoundCloud][def:social/soundcloud].
I use this as my music making alias.
% id = "01J09B2BZXAD196RA16JTY3K3T"
+ also known as **limiter**?
+ also known as *limiter*?
% id = "01J09B2BZXNRYX758J5S00EZRG"
- or **limitre**, which is British for limiter.
- or *limitre*, which is British for limiter.
% id = "01J09B2BZX25TN31D0F18YQPB2"
+ I'm a *he/him*-type cat person doing various stuff with computers
+ I'm a _he/him_-type cat person doing various stuff with computers
% id = "01J09B2BZXWB695M9XYF7CZ2ZF"
- > you said cat person, does that mean you don't like dogs?
@ -36,13 +36,13 @@
- I prefer cats myself. though foxes are canids and I consider them pretty cute :smile:
% id = "01J09E55G0MZ0WFQ3WC98YDDGT"
- > unless by *cat person* you mean…
- > unless by _cat person_ you mean…
% id = "01J09E55G0YWYMAP62Q7AM18BE"
+ what? :clueless:
% id = "01J09E55G09K80E6YE1T4E3KJJ"
- > y'know… like a *furry*?
- > y'know… like a _furry_?
% id = "01J09E55G0F5TVAK2MT80H598M"
+ :clueless:
@ -64,7 +64,7 @@
+ I do various developer productivity-related stuff at CD PROJEKT RED, mostly focusing on the DX of delivering breathtaking games using the Unreal Engine.
% id = "01J09B2BZX00X6A8Z6H4R16BDS"
- technically I don't really love *playing* big and serious games, but making one is a whole other challenge - a challenge of *scale*.
- technically I don't really love _playing_ big and serious games, but making one is a whole other challenge - a challenge of _scale_.
which is what really gets me going!
% id = "01J09B2BZXQKWYHC51D7QCJ6PF"
@ -77,13 +77,13 @@
+ [audio][def:dawd3/repo],
% id = "01J09B2BZXY51P9D4BM8AADGYW"
- my favorite part of that is blowing out my own eardrums with my math skill, which is rather *underdeveloped* so to speak,
- my favorite part of that is blowing out my own eardrums with my math skill, which is rather _underdeveloped_ so to speak,
% id = "01J09B2BZX2P7XDH81BJ079PCP"
+ [websites](/),
% id = "01J09B2BZXN6GWK2JPC7YGT0E1"
+ after all the treehouse is *my* website, with *my* own backend, static generator, *my* own design, [an only partially stolen color scheme](https://github.com/ayu-theme/ayu-colors), and most important, lots of fun stuff hidden underneath the surface.
+ after all the treehouse is _my_ website, with _my_ own backend, static generator, _my_ own design, [an only partially stolen color scheme](https://github.com/ayu-theme/ayu-colors), and most important, lots of fun stuff hidden underneath the surface.
% id = "01J09B2BZXZAEPD36XG495WYFH"
+ like have you wondered just how deep the rabbit hole goes?
@ -91,13 +91,13 @@
who knows what you may find?
% id = "01J09B2BZX0M925RBXR023XM6X"
- <!-- what I mean is if you haven't seen anyone else here, you haven't looked hard enough. -->
- `<!-- what I mean is if you haven't seen anyone else here, you haven't looked hard enough. -->`{=html}
% id = "01J09B2BZXX2R1TH10GGZC25KG"
+ you name it. as long as it poses a cool and fun challenge, I'll be on it :verified:
% id = "01J09B2BZX5WED9XE2RAADFW8M"
- there's probably only one thing I *don't* like and it's backend web development. where did all the simplicity go?
- there's probably only one thing I _don't_ like and it's backend web development. where did all the simplicity go?
% id = "01J09B2BZXQQMPDYQJ052131GT"
- obviously treehouse is a very simple website with a very simple backend, but. seriously.
@ -123,13 +123,13 @@
% id = "01J09B2BZX39FGP634BM5HJ0FD"
+ did I mention [music][page:music]?
I also ~~aspire to~~ [make ][def:social/soundcloud][music][page:music][ sometimes][def:social/soundcloud].
I also {-aspire to-} [make ][def:social/soundcloud][music][page:music][ sometimes][def:social/soundcloud].
% id = "01J09B2BZXGG5HYVYDNPVFRKY1"
- I say aspire because I'd really like to be better at it, but *time*, man…
- I say aspire because I'd really like to be better at it, but _time_, man…
% id = "01J09B2BZXY6AN3242DYECK74Z"
+ from time to time I also try to draw things, but *time*, it's killing me, man…
+ from time to time I also try to draw things, but _time_, it's killing me, man…
% id = "01J09B2BZXN3263NEHCKQ931TS"
- I'm sure one day you'll see the results of my practice, (unless you've already seen them :ralsei_wave:,) but for now I keep them largely to myself.

View file

@ -1,48 +1,51 @@
%% title = "on digital textures"
% id = "01HQ8JHZ5NP1K7PHW4MJQS65ND"
- this is not about textures in the graphics programming sort of way. this is about textures in the *you can feel it under your fingers* way
- this is not about textures in the graphics programming sort of way. this is about textures in the _you can feel it under your fingers_ way
% id = "01HQ8JHZ5NM6CNJ1PCGCWC6DQV"
- I don't know if it's just some weird synaesthesia thing or not, but one interesting observation I've noticed is that various cursors seem to give off different *textures*.
- I don't know if it's just some weird synaesthesia thing or not, but one interesting observation I've noticed is that various cursors seem to give off different _textures_.
like you hover over something and you can digitally feel it under your fingers
% id = "01HQ8JHZ5NEHACKDTHJSJNB3QD"
- these textures just generally cause different pieces of software to give off different *vibes*;
- these textures just generally cause different pieces of software to give off different _vibes_;
for example, based on texture alone it is possible to determine whether something runs on web technologies.
% id = "01HQ8JHZ5N68ZG29CSAKYW3CYE"
- you can probably tell this blog is a website just by *feeling* how it behaves.
- you can probably tell this blog is a website just by _feeling_ how it behaves.
% id = "01HQ8JHZ5NZAVMCYFGS94W6Z8N"
- the cursors, where <span style="cursor: pointer; text-decoration: dotted underline;">pointer</span> is used for elements that are clickable,
- the cursors, where [pointer]{style="cursor: pointer; text-decoration: dotted underline;"} is used for elements that are clickable,
and a text cursor is used for text.
``` =html
<style>
.digital-textures-pointer-cursor * { cursor: default }
</style>
```
% classes.branch = "digital-textures-pointer-cursor"
id = "01HQ8JHZ5NYDJD0YKVX55AR3ZQ"
- <span style="cursor: default;">
when you have an interactive element and it <em>doesn't</em> have a <span style="cursor: pointer; text-decoration: dotted underline;">pointer</span> cursor on it, it feels almost <em>wrong.</em>
eerie in a way. out of place.<br>
- {style="cursor: default;"}
:::
when you have an interactive element and it _doesn't_ have a [pointer]{style="cursor: pointer; text-decoration: dotted underline;"} cursor on it, it feels almost _wrong._
eerie in a way. out of place.\
like have you noticed this branch is kind of really fucking weird with the default cursor?
</span>
:::
% id = "01HQ8JHZ5NT1P5R3EM9GEKZ3DK"
- <span style="cursor: default; user-select: none;">and this one is *even* weirder, with unselectable text! (sorry for this. I'm trying to make a point.)</span>
- [and this one is _even_ weirder, with unselectable text! (sorry for this. I'm trying to make a point.)]{style="cursor: default; user-select: none;"}
% id = "01HQ8JHZ5N21GM1FWEXAKPWDPA"
- the fact you can press <kbd>Ctrl</kbd> + <kbd>A</kbd> and it selects all text also gives off a texture typical of websites.
- the fact you can press `<kbd>Ctrl</kbd>`{=html} + `<kbd>A</kbd>`{=html} and it selects all text also gives off a texture typical of websites.
% id = "01HQ8JHZ5NMA2HT3M075G9AV1A"
- the common usage of [hyperlinks][def:word/hyperlink] over buttons is also typical of websites.
% id = "01HQ8JHZ5N2WA7G6QN7YCE8PN0"
- Git for Windows asks whether you want to use stock Windows `cmd.exe` or mintty.
for as long as I can remember, mintty always felt *wrong* to me. like the experience was not *Windowsy.*
simply because the terminal window is so tiny, the cursor is an I-beam, and the default font used is <span style="font-family: 'Lucida Console', RecVar;">Lucida Console</span> rather than <span style="font-family: 'Consolas', RecVar;">Consolas</span>.
for as long as I can remember, mintty always felt _wrong_ to me. like the experience was not _Windowsy._
simply because the terminal window is so tiny, the cursor is an I-beam, and the default font used is [Lucida Console]{style="font-family: 'Lucida Console', RecVar;"} rather than [Consolas]{style="font-family: 'Consolas', RecVar;"}.
% id = "01HQ8JHZ5NHEW18R8JQWQCAB6Y"
- synaesthetically, it feels to me as if `cmd.exe` is smooth, and mintty is rough. like with `cmd.exe` you're touching matte plastic, an LCD display, and with mintty you're touching a piece of paper or tree bark.

View file

@ -4,7 +4,7 @@
- imagine if this page had a distracting bunch of text to the right of regular content. or even worse, advertisements.
% id = "01HR9ZTS6P67VGVY6S460Q49HV"
- fortunately I'm not cruel enough to do that (*and* also I'm lazy *and* also I'm at work at the moment so I haven't got time to do something wicked like that.)
- fortunately I'm not cruel enough to do that (_and_ also I'm lazy _and_ also I'm at work at the moment so I haven't got time to do something wicked like that.)
% id = "01HR9ZTS6P7AB97D6T177XMNKW"
- these are sidebars in a nutshell. distracting, annoying, detracting from what you actually have to say.
@ -26,10 +26,10 @@
- If my blog piqued your interest, feel free to also check out these articles:
% id = "01HR9ZTS6P1JJ2103E4QHATH3G"
- [*on digital textures*][page:design/digital-textures], where I explore synaesthetic feelings in user interfaces
- [_on digital textures_][page:design/digital-textures], where I explore synaesthetic feelings in user interfaces
% id = "01HR9ZTS6P73HSTZK6CP1RWQWG"
- *[...so on and so forth.]*
- _[...so on and so forth.]_
% id = "01HR9ZTS6PBFR78Y54HGGKXMF6"
- note how I haven't even used pictures! I care about what you have to say, not what pretty pictures you managed to snap on your last field trip.
@ -40,16 +40,16 @@
% id = "01HR9ZTS6PKYGVTFF0F78XVXH2"
- (I'm not saying this structure is any better than a conventional blog though.
it has its own set of challenges you need to take into account while writing, like *when to split*, and *when to nest*.
it has its own set of challenges you need to take into account while writing, like _when to split_, and _when to nest_.
arguably, I still haven't learned how to write the treehouse well, but I think I'm getting better at it; compare [about (version 1)][page:about/v1] to this page and you'll see what I mean.)
% id = "01HR9ZTS6P484GD6Y9QR4TEGWD"
- I'm glad most individuals' blogs are foregoing sidebars, but unfortunately I still regularly see them on various news sites and older blogs
% id = "01HR9ZTS6PCAWGEJNYXETFVDFT"
- if you're designing a modern blog, consider structuring your page *vertically* rather than *horizontally*.
- if you're designing a modern blog, consider structuring your page _vertically_ rather than _horizontally_.
limit distractions you show on the sides of the screen.
they *really* are quite annoying when you're just trying to focus on the body.
they _really_ are quite annoying when you're just trying to focus on the body.
% id = "01HR9ZTS6PKZVXQE0VFVTNNQ6F"
- not to mention mobile devices, which do not have the horizontal real estate to fit your sidebars!

View file

@ -3,17 +3,18 @@
% id = "treehouse"
- # liquidex's treehouse
<span class="oops-you-seem-to-have-gotten-stuck">
<strong>achievement unlocked: <i>I don't want anything to do with this bullshit</i><br></strong>
…either that, or you folded the entire tree by accident.<br>
feel free to go back any time by clicking here.
</span>
::: oops-you-seem-to-have-gotten-stuck
*achievement unlocked:* _I don't want anything to do with this bullshit_\
...either that, or you folded the entire tree by accident.\
feel free to go back at any time by clicking here.
:::
% id = "01H8V556P1PND8DQ73XBTZZJH7"
- welcome! make yourself at home :ralsei_wave:
% id = "01H8VWEHX501SNYQTE61WX7YJC"
- <a class="secret" href="/kuroneko">_"owo, what's this?"_</a>
- [_"owo, what's this?"_][page:kuroneko]{.secret}
% id = "about"
content.link = "about"
@ -30,16 +31,19 @@
% id = "programming"
content.link = "programming"
+ ## programming
``` =html
<svg class="background-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8.58579 11L5.29289 7.70711L6.70711 6.29289L11.4142 11L6.70711 15.7071L5.29289 14.2929L8.58579 11Z"
fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 18H12V16H18V18Z" fill="currentColor" />
</svg>
```
% id = "design"
content.link = "design"
+ ## design
``` =html
<svg class="background-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 18V16H20V18H22V20H20V22H18V20H16V18H18Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 4H8V6H6V8H4V4Z" fill="currentColor" />
@ -50,20 +54,23 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 20H10V18H14V20Z" fill="currentColor" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M18 14V10H20V14H18Z" fill="currentColor" />
</svg>
```
% id = "music"
content.link = "music"
+ ## music
``` =html
<svg class="background-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M9 7L18 5V5.5V8.20002V15.5C18 16.6046 16.8807 17.5 15.5 17.5C14.1193 17.5 13 16.6046 13 15.5C13 14.3954 14.1193 13.5 15.5 13.5C15.6712 13.5 15.8384 13.5138 16 13.54V8.64446L11 9.75558V17.5C11 18.6046 9.88071 19.5 8.5 19.5C7.11929 19.5 6 18.6046 6 17.5C6 16.3954 7.11929 15.5 8.5 15.5C8.67123 15.5 8.83844 15.5138 9 15.54V10.2V7.5V7Z"
fill="currentColor" />
</svg>
```
% id = "games"
content.link = "games"
+ ## games
``` =html
<svg class="background-icon" width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M4.76469 9.3788C4.11751 11.8531 3.95139 14.9468 3.93837 17.1997C3.93607 17.5979 4.26468 18 4.81836 18H5.55848L5.87471 17.0513C6.28305 15.8263 7.42947 15 8.72076 15H15.2792C16.5705 15 17.717 15.8263 18.1253 17.0513L18.4415 18H19.1816C19.7353 18 20.0639 17.5978 20.0616 17.1997C20.0486 14.9468 19.8825 11.8531 19.2353 9.3788C18.9094 8.13264 18.5025 7.21029 18.0604 6.63842C17.6618 6.12281 17.335 6 17 6H15.8901C15.7929 6.10179 15.6861 6.20981 15.5815 6.30722C15.5085 6.37519 15.3966 6.47582 15.2656 6.57159C15.2019 6.61812 15.0904 6.69566 14.9457 6.76963C14.8536 6.81671 14.4947 7 14 7H10C9.5053 7 9.14639 6.81671 9.05429 6.76963C8.90956 6.69566 8.79809 6.61812 8.73445 6.57159C8.60345 6.47582 8.49146 6.37519 8.4185 6.30722C8.31391 6.20981 8.20711 6.10179 8.10995 6H7C6.66496 6 6.33822 6.12281 5.93959 6.63842C5.49747 7.21029 5.09064 8.13264 4.76469 9.3788ZM15 4H17C21.3983 4 22.0351 12.5978 22.0616 17.1882C22.0708 18.7723 20.7658 20 19.1816 20H17L16.2279 17.6838C16.0918 17.2754 15.7097 17 15.2792 17H8.72076C8.29033 17 7.90819 17.2754 7.77208 17.6838L7 20H4.81836C3.23417 20 1.92925 18.7723 1.9384 17.1882C1.96494 12.5978 2.6017 4 7 4H9C9 4 9.85285 5 10 5H14C14.1472 5 15 4 15 4Z"
@ -75,6 +82,7 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 8.5H7V10H5.5V12H7V13.5H9V12H10.5V10H9V8.5Z"
fill="currentColor" />
</svg>
```
% id = "01HFYZKREWE2AM61ZRW3R501H6"
- various thoughts

View file

@ -5,4 +5,6 @@
% template = true
cast = "chat js"
id = "01HSS5TMDD4WSZSXW17ANQN7VK"
- {% include_static chat/kuroneko.json %}
- ``` =html
{% include_static chat/kuroneko.json %}
```

View file

@ -4,19 +4,19 @@
- this section is dedicated to all those business pinheads working at music distribution companies who think DRM is good in any way shape or form.
% id = "01HPECQ3Z1EZEQS2J0CY4A0CMA"
- I'm aware they know *very well* what they're doing. just, y'know, *fuck them.*
- I'm aware they know _very well_ what they're doing. just, y'know, _fuck them._
% id = "01HPWEESFV7TPXA6H5BCZVZKD6"
+ :page: exhibit 2: ODDTAXI soundtrack
% id = "01HPWEESFVY5WGP5NY94QXFQ2A"
- I watched this anime recently and I really liked the soundtrack, so I thought to myself "cool, gotta buy it and enjoy it in glorious FLAC while I'm driving my ~~taxi~~ car"
- I watched this anime recently and I really liked the soundtrack, so I thought to myself "cool, gotta buy it and enjoy it in glorious FLAC while I'm driving my {-taxi-} car"
% id = "01HPWEESFV1W7Y0713F5P74EPQ"
- naturally I was absolutely unprepared for the shitshow that is purchasing music from Japan
% id = "01HPWEESFVC3ZX091BN3ZFT4YB"
- for some reason some business pinheads thought it would be an *incredible and wise idea* to introduce *region locks* to music!
- for some reason some business pinheads thought it would be an _incredible and wise idea_ to introduce _region locks_ to music!
% id = "01HPWEESFV2AF3V1GMFZYKECHF"
- fuck the fact that you can legitimately buy this album as a pack of FLAC files in Japan, if you're anywhere else - eat shit!
@ -50,6 +50,7 @@
+ so I look up his Twitter profile and
<https://twitter.com/bennjordan/status/1756870458358546722>
> An "internal investigation" has confirmed "evidence" of streaming fraud on Spotify.
>
> Therefore, The Flashbulb and my other music has been permanently removed from all platforms.

View file

@ -14,7 +14,7 @@
- Aphex Twin is known for track names that are complete gibberish (hello drukQs, except drukQs names are appearently Cornish) but Syro takes it to the next level
% id = "01H9DQNG9A2TB82KF5H5MM0N25"
+ even *I* with my insanely good memory for weird ass names do not remember the full name of my favorite track
+ even _I_ with my insanely good memory for weird ass names do not remember the full name of my favorite track
% id = "01H9DQNG9A6HTJD0ZWMJV5V8VW"
- that track being _something something_ [141.98] [piezoluminescence mix]
@ -80,7 +80,7 @@
+ 141.98 BPM
% id = "01H9DQNG9A0DPETJ7653SGN85A"
- there are two tracks on Syro whose tempo is 141.98 BPM: *CIRCLONT6A [141.98][syrobonkus mix]*, and *syro u473t8+e [141.98][piezoluminescence mix]*
- there are two tracks on Syro whose tempo is 141.98 BPM: _CIRCLONT6A [141.98][syrobonkus mix]_, and _syro u473t8+e [141.98][piezoluminescence mix]_
% id = "01H9DQNG9AD28Z2ZX556E45RYS"
- if you listen closely, there is a common leitmotif across these tracks:
@ -101,16 +101,16 @@
+ I think I managed to nail down how Syro track names came to be.
% id = "01H9DQNG9AB0Y7GYT6F0HB41CM"
- I can't find the exact video that made me realize this, but in *some* video on YouTube *someone* was using a step sequencer, and it prompted them to enter a track name.
- I can't find the exact video that made me realize this, but in _some_ video on YouTube _someone_ was using a step sequencer, and it prompted them to enter a track name.
% id = "01H9DQNG9AB5XPEHP39CJB8256"
- given how these sequencer interfaces are pretty cumbersome to type on (given they're mostly controlled with knobs and buttons whose layout *distinctly* does not resemble a computer keyboard), they just decided to just change out a number at the end
- given how these sequencer interfaces are pretty cumbersome to type on (given they're mostly controlled with knobs and buttons whose layout _distinctly_ does not resemble a computer keyboard), they just decided to just change out a number at the end
% id = "01H9DQNG9AGWYR75TV9NYFP5PB"
- and that's my theory: that's what happened with Syro
% id = "01H9DQNG9ANF71V4DMQ30ZA1G2"
- you can kind of confirm this by looking at `CIRCLONT6A` and `CIRCLONT14`, as well as Aphex Twin's Cheetah EP which features `CHEETAHT2`, `CHEETAHT7b`, `CHEETA1b`, `CHEETA2`, `CIRKLON3`, and `CIRKLON 1`. following this *exact* this naming scheme.
- you can kind of confirm this by looking at `CIRCLONT6A` and `CIRCLONT14`, as well as Aphex Twin's Cheetah EP which features `CHEETAHT2`, `CHEETAHT7b`, `CHEETA1b`, `CHEETA2`, `CIRKLON3`, and `CIRKLON 1`. following this _exact_ this naming scheme.
% id = "01H9DQNG9AKXC5Q5Z76YPWDAY7"
- but that's just a theory. A MUSIC THEORY

View file

@ -17,11 +17,11 @@
+ #### Present Tense
% id = "01H9JB094CYBC0QRFV8D0H8CGB"
- *this dance, this dance*\
*is like a weapon, is like a weapon*\
*of self-defense, of self-defense*\
*against the present*\
*the present tense*
- _this dance, this dance_\
_is like a weapon, is like a weapon_\
_of self-defense, of self-defense_\
_against the present_\
_the present tense_
% id = "01H9JB094CPV3N30272TKSMQNC"
- going crazy, going nuts. a dance of self-defense. against the present... the present tense.
@ -46,7 +46,7 @@
+ why not just stop giving a shit about what other people think for a moment. they probably don't give a shit either.
% id = "01H9JB094CH7JFBKQF6QZ6KPEB"
- or if they do, tell them what *you* think. tell them why you're freaking out like that. and be honest :)
- or if they do, tell them what _you_ think. tell them why you're freaking out like that. and be honest :)
% id = "music/track/radiohead/daydreaming"
+ #### Daydreaming

View file

@ -5,7 +5,7 @@
- this is an album I didn't really enjoy until very recently
% id = "01H969NN1A5B2QEKWY6B9KQFVP"
- what I came for in Radiohead was the more tender bits where they mix rock with electronica, and this album is *very* coarse and rough compared to some of the later ones
- what I came for in Radiohead was the more tender bits where they mix rock with electronica, and this album is _very_ coarse and rough compared to some of the later ones
% id = "01H969NN1A24V0J35FP07Z8Q6A"
- but this album has some pearls behind the generally rocky facade
@ -20,7 +20,7 @@
- yeah, man. slow down.
% id = "01H969NN1AGVKM992WV7B01P95"
- an ode to not appreciating the little details in life. an ode to going forward and never pausing to think. *have I been doing this all wrong?*
- an ode to not appreciating the little details in life. an ode to going forward and never pausing to think. _have I been doing this all wrong?_
% id = "01H969NN1AB8A3KMHPQ7017MS2"
- I really like the slow tempo and Pink Floyd-esque style. it really sticks in my head.

View file

@ -29,7 +29,7 @@
- as you approach it, you decide to flick the switch on the front, and it begins humming.
% id = "01HA4HJKRJEBARWW0E6BR1M86M"
+ the humming intensifies, you see various blinkenlights shining on its faceplate, and soon you become surrounded by yyyYyyYYY<kbd>y</kbd>`y`Y`Y`<kbd>Y</kbd>
+ the humming intensifies, you see various blinkenlights shining on its faceplate, and soon you become surrounded by yyyYyyYYY`<kbd>y</kbd>`{=html}`y`Y`Y``<kbd>Y</kbd>`{=html}
% id = "01HA4HJKRJZTC9V7BZVCFFBAXR"
- ...you passed out.

View file

@ -8,7 +8,7 @@
what a poor, small company they are to be having these problems :(
% id = "01HVNX7FMSV3DT95W29TDXG8R2"
- "well then why are you paying them, liquidex?" unfortunately lots of music is only available (*and* UNAVAILABLE!) on Spotify, as seen on [_fuck DRM_][page:music/fuck-drm].
- "well then why are you paying them, liquidex?" unfortunately lots of music is only available (_and_ UNAVAILABLE!) on Spotify, as seen on [_fuck DRM_][page:music/fuck-drm].
from a consumer standpoint it's also really nice for discovering new music, because you don't have to pay a large amount just to give a listen to an album you may not end up liking.
% id = "01HVNX7FMSR6TFJK3A9VVQ667K"
@ -28,7 +28,7 @@
![screenshot of a fragment of the queue panel, with a track I hovered over displaying a play button][pic:01HVNW6AQQN9345X3HXY9NN5RR]
% id = "01HVNX7FMS8JN72XSMN7EV2K0C"
- if you hover over _any_ part of the row, you get the <span style="cursor: pointer; text-decoration: dotted underline;" title="yeah, *this* cursor you're seeing right now">pointer</span> rather than default cursor, which suggests that clicking is gonna result in an action being taken immediately - except that's not what happens.
- if you hover over _any_ part of the row, you get the <span style="cursor: pointer; text-decoration: dotted underline;" title="yeah, _this_ cursor you're seeing right now">pointer</span> rather than default cursor, which suggests that clicking is gonna result in an action being taken immediately - except that's not what happens.
instead, if you click on the blank part, the element is only gonna get selected - as it used to in the old queue, which is _good_.
but if that's what happens, that's definitely not the cursor to use.
@ -36,7 +36,7 @@
- in addition to this, the play button that appears on the album art (pictured on the screenshot) further suggests that clicking anywhere on the row will result in playback skipping to the track you click.
% id = "01HVNX7FMS4J4JXYQQ8D0RNQJT"
+ this threw me off the first time I saw the new panel, because I thought it was a *downgrade* in terms of functionality.
+ this threw me off the first time I saw the new panel, because I thought it was a _downgrade_ in terms of functionality.
"what, I can't select many items in the queue now to delete them?", I thought, but no.
that is still possible; the mouse cursor is just confusing.

View file

@ -5,11 +5,11 @@
+ but if you've been wondering what makes my brain tick, you've come to the right place
% id = "01HFYZKREVXY44MS45P5J0BRBE"
- perhaps my thoughts here will be more than *just* pure philosophy and worldview, who knows!
- perhaps my thoughts here will be more than _just_ pure philosophy and worldview, who knows!
% id = "01HFYZKREVSW8KTGVKN4R8GPKK"
- **perhaps this will be the most controversial branch of this tree, so maybe if you don't like to
get all fired up... stop reading here.**
- *perhaps this will be the most controversial branch of this tree, so maybe if you don't like to
get all fired up... stop reading here.*
% id = "01HFYZKREV1CYFWBF4ZM4R3Y15"
- but if you decide to stay please approach these thoughtfully and respectfully, with more
@ -46,14 +46,14 @@
% id = "01HREVZNAHRC9BJZJPH9RE21PE"
- this is an unfortunate side effect of the tree-based structure of Lobsters.
as much as I like tree-based forums, as they let you focus on many different (off-)topics without cluttering the main thread,
it's damn near impossible to keep the broader context of *what the discussion is about* when the thread nests more than 3 levels deep.
it's damn near impossible to keep the broader context of _what the discussion is about_ when the thread nests more than 3 levels deep.
% id = "01HREVZNAH96KPDQAW1YW1FGC5"
- therefore, if you ever find yourself writing an essay about just how wrong someone is, think to yourself: am I *really* understanding what's going on in this discussion?
- therefore, if you ever find yourself writing an essay about just how wrong someone is, think to yourself: am I _really_ understanding what's going on in this discussion?
and if not - maybe just shut up and let the rest of the idiots fan the flames instead.
% id = "01J1Q8SBGFVPDBKKZPWDSV1D5G"
- :page: in wisdom you become old
+ :page: in wisdom you become old
% id = "01J1Q8SBGFNS3ZSSA1QXN83SMH"
+ this is kind of weird, but recently I have noticed I no longer keep up with slang

View file

@ -9,7 +9,7 @@
% content.link = "programming/blog/tairu"
id = "01HPD4XQQ5WM0APCAX014HM43V"
+ :page: <span class="badge blue">featured</span> tairu - an interactive exploration of 2D autotiling techniques
+ :page: [featured]{.badge .blue} tairu - an interactive exploration of 2D autotiling techniques
% content.link = "programming/blog/lvalues"
id = "01HY5R1ZW0M0Y5KQ1E8F0Q73ZT"

View file

@ -5,6 +5,7 @@
% id = "01HY5R1ZV9G92SVA0XP7CG1X6K"
- as in this case:
```c
void example(int *x) {
int y = *x; // (1)
@ -19,10 +20,10 @@
- doesn't `*x` mean "read the value pointed to by `x`?"
% id = "01HY5R1ZV9WJM9DMW5QGK04DCR"
- TL;DR: the deal with this example is that `*x` *does not mean* "read from the location pointed to by `x`", but _just_ "the location pointed to by `x`".
- TL;DR: the deal with this example is that `*x` _does not mean_ "read from the location pointed to by `x`", but _just_ "the location pointed to by `x`".
% id = "01HY5R1ZV9JN9GZ8BECJ0KV1FC"
- **`*x` is not a _value_, it's a _memory location_, or _place_ in Rust parlance**
- *`*x` is not a _value_, it's a _memory location_, or _place_ in Rust parlance*
% id = "01HY5R1ZV9QVH3W5BRS16V6EPG"
- same thing with `x`
@ -154,6 +155,7 @@ for starters, it is impossible to write the type down in C code, so we're always
% id = "01HY5R1ZV9VZFHRKY4QWBDTFJ7"
- example:
```c.types
void example(void) {
struct S { int x; } s;
@ -168,6 +170,7 @@ for starters, it is impossible to write the type down in C code, so we're always
the function signature is therefore `T* -> ptrdiff_t -> place(T)`.
example:
```c.types
void example(int* array) {
int* p = &((array /*: int* */)[123] /*: place(int) */);
@ -184,7 +187,7 @@ for starters, it is impossible to write the type down in C code, so we're always
% id = "01HY5R1ZV9MQWK5VQZQG8JJ03J"
- I do wonder though why it doesn't produce a warning.
I'm no standards lawyer, but I *believe* this may have something to do with implicit type conversions - the `0` gets promoted to a
I'm no standards lawyer, but I _believe_ this may have something to do with implicit type conversions - the `0` gets promoted to a
pointer as part of the desugared addition.
I really need to read up about C's type promotion rules.
@ -193,8 +196,8 @@ for starters, it is impossible to write the type down in C code, so we're always
there are no places in C.
% id = "01HY5R1ZV9PAD1XRS29YV64GV2"
- the C standard actually calls this concept "lvalues", which comes from the fact that they are **values** which are valid
**l**eft-hand sides of assignment.
- the C standard actually calls this concept "lvalues", which comes from the fact that they are *values* which are valid
*l*eft-hand sides of assignment.
% id = "01HY5R1ZV9BNZM45RK2AW8BF5N"
+ however, I don't like that name since it's quite esoteric - if you tell a beginner "`x` is not an lvalue," they will look at you confused.
@ -223,7 +226,8 @@ there are no places in C.
in layman's terms, C++ makes it impossible to rebind references to something else.
you can't make this variable point to `y`:
<!-- NOTE: using `c` syntax here instead of `cpp` because I don't have a working C++ syntax at the moment! -->
{% NOTE: using `c` syntax here instead of `cpp` because I don't have a working C++ syntax at the moment! %}
```c
int x = 0;
int y = 1;
@ -253,10 +257,11 @@ there are no places in C.
- I actually kind of wish references were more like they are in Rust - basically just pointers but non-null and guaranteed to be aligne
% id = "01HY5R1ZV9QWHZRJ5V53CVYG5V"
- anyways, as a final ~~boss~~ bonus of this blog post, I'd like to introduce you to the `x->y` operator (the C one)
- anyways, as a final {-boss-} bonus of this blog post, I'd like to introduce you to the `x->y` operator (the C one)
% id = "01HY5R1ZV9AYFWV96FC07WX68G"
- if you've been programmming C or C++ for a while, you'll know that it's pretty dangerous to just go pointer-[walkin'](https://www.youtube.com/watch?v=d_dLIy2gQGU) with the `->` operator
```c
int* third(struct list* first) {
return &list->next->next->value;
@ -278,6 +283,7 @@ there are no places in C.
% id = "01HY5R1ZV9X8M5T64BD0MZ2WN2"
- let's start by dismantling the entire pointer access chain into separate expressions:
```c
int* third(struct list* first) {
struct list* second = first->next;
@ -288,6 +294,7 @@ there are no places in C.
% id = "01HY5R1ZV9PGQMS2A6H5XTWYZX"
- now let's desugar the `->` operator:
```c
int* third(struct list* first) {
struct list* second = (*first).next;
@ -298,6 +305,7 @@ there are no places in C.
% id = "01HY5R1ZV92CZAV13P4KFABTR1"
- and add some type annotations:
```c.types
int* third(struct list* first) {
struct list* second = (*first).next /*: place(struct list*) */;

View file

@ -1,21 +1,20 @@
%% title = "OR-types"
scripts = ["components/literate-programming.js"]
% id = "01HTWN4XAD7C41X8XKRBFZMHJ8"
- last night I couldn't fall asleep because I was thinking how sites like [Anilist](https://anilist.co) implement their tag-based search systems.
% id = "01HTWN4XAD4CPDR36P4XNYZAE3"
- my mind was racing, and with enough thinking about scores and weights and sums and products… I arrived at the topic of *sum types* and *product types.*
- my mind was racing, and with enough thinking about scores and weights and sums and products… I arrived at the topic of _sum types_ and _product types._
% id = "01HTWN4XAD4NK5TRFVPCC4EHJ2"
- I was thinking, "*this is actually really interesting - why do we call them sum types and product types?*" -
- I was thinking, "_this is actually really interesting - why do we call them sum types and product types?_" -
I had some intuitive understanding as to where these names come from, but I wanted to write down my thoughts for future me, and future generations.
% id = "01HTWN4XADR3SARMFCF6PB8T7D"
- so I set down an alarm for today, and went to sleep.
% id = "01HTWN4XADJ9NAWKY8G4BDS9E0"
- recall that I was faced with the question of "why do we call product types *product types*, and why do we call sum types *sum types*?"
- recall that I was faced with the question of "why do we call product types _product types_, and why do we call sum types _sum types_?"
my intuitive understanding was this:
% id = "01HTWN4XADVCMHQCGDM4RM6YBN"
@ -168,7 +167,7 @@ my intuitive understanding was this:
- `U` is a subtype of `T | U`, because `T` is not present (`0`) and `U` is (`1`), therefore `has(T) XOR has(U)` is true.
% id = "01HTWN4XAD15CWASNFEQRTP0H7"
- *but* unlike our previous hypothetical `T + U`, `T * U` is not a subtype of `T | U`, because if `T` is present (`1`) and `U` is also present (`1`), `has(T) XOR has(U)` will be false -
- _but_ unlike our previous hypothetical `T + U`, `T * U` is not a subtype of `T | U`, because if `T` is present (`1`) and `U` is also present (`1`), `has(T) XOR has(U)` will be false -
which matches more typical sum types.
% id = "01HTWN4XADJC8ZHBHNTSDW8EQP"
@ -181,7 +180,7 @@ my intuitive understanding was this:
- this confused me a lot because, from my limited understanding of them, set-theoretic types represents types as sets of possible values - and mathematically any set `S` can be represented by a function `S(x)`, which is `true` if `x` is present in the set
% id = "01HTWN4XADSSASE388BBS79X7J"
- but again, it is *not* the same, because the type system I described here is defined based on subtype relations rather than sets of values.
- but again, it is _not_ the same, because the type system I described here is defined based on subtype relations rather than sets of values.
% id = "01HTWN4XADQ3F5CYBEN44J10D5"
- in the world of set-theoretic types, we get sum types for free, because the type `T | U` represents the union of the set of all possible `T` values, and the set of all possible `U` values.
@ -229,10 +228,10 @@ I'll add a note to this post summarizing what's wrong with my reasoning.
- seem familiar? it's exactly the same as the truth table for `AND`ing two numbers!
% id = "01HTWN4XADB7K02JDWMJ5T3KN9"
- except because we're working with _numbers_ here, we can take it a step further and multiply *any* two numbers, not just zero and one.
- except because we're working with _numbers_ here, we can take it a step further and multiply _any_ two numbers, not just zero and one.
% id = "01HTWN4XAD9ESATEWPNC6Y72S3"
- therefore we can search and rank our anime about crimes in space based on a *score* produced by multiplying the tag values.
- therefore we can search and rank our anime about crimes in space based on a _score_ produced by multiplying the tag values.
let's see [what Anilist tells us about crimes in space](https://anilist.co/search/anime?genres=Space&genres=Crime).
I wrote down the data of the first 10 anime it displayed to me on the day I'm writing this, so this data may become out of date with time. ignoring spoiler tags because noone likes those.

View file

@ -35,9 +35,9 @@ or even where to go to customize it to my liking?
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,
- 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.*
_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.
@ -87,13 +87,13 @@ or even where to go to customize it to my liking?
- 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.
+ _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"
- _and_ before you go at me and say "bah you should be reviewing all changes that end up in your codebase"
sigh.
@ -123,17 +123,17 @@ or even where to go to customize it to my liking?
it's all just code-generating syntax sugar!
% id = "01HV1DGFGNVRX5C3TST00V58DK"
- **everything is code! *there is no magic*!**
- *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.
- 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.*
- 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.)

View file

@ -29,14 +29,18 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAMCPC5M88QAXHX4BT"
- so to help us learn, I made a little tile editor so that we can experiment with rendering tiles! have a look:
<noscript>(…though you will need to enable JavaScript to try it out.
{% this could probably be written with some clever generator magic, but I'm too lazy for that %}
`<noscript>`{=html}
(…though you will need to enable JavaScript to try it out.
seriously, pinky promise I won't ever track you!
inspect the source code if you wanna.
if not, you will have to deal with static pictures.
but just keep in mind this was supposed to be an <strong><em>interactive</em></strong> exploration of autotiling techniques.
cheers!)</noscript>
but just keep in mind this was supposed to be an _*interactive*_ exploration of autotiling techniques.
cheers!)
`</noscript>`{=html}
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
import { TileEditor } from "tairu/editor.js";
@ -54,22 +58,26 @@ styles = ["page/tairu.css"]
});
```
```output tairu 01HQ47ZX7520PJNPJ75M793R5G
{:program=tairu :placeholder=01HQ47ZX7520PJNPJ75M793R5G}
```output
```
% id = "01HQ162WWAC3FN565QE3JAB87D"
- `Tilemap` is a class wrapping a flat [`Uint8Array`] with a `width` and a `height`, so that we can index it using (x, y) coordinates.
- `Tilemap` is a class wrapping a flat [`Uint8Array`][Uint8Array] with a `width` and a `height`, so that we can index it using (x, y) coordinates.
```javascript tairu
{:program=tairu}
```javascript
console.log(tilemapSquare.at(0, 0));
console.log(tilemapSquare.at(3, 1));
```
```output tairu
{:program=tairu}
```output
0
1
```
[`Uint8Array`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
[Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
% id = "01HQ162WWA090YW5BR1XW68XJN"
- `at` has a `setAt` counterpart which sets tiles instead of getting them.
@ -90,34 +98,35 @@ styles = ["page/tairu.css"]
- let's break this down into smaller steps. drawing a border around the rectangle will involve:
% id = "01HQ162WWATV30HXGBQVWERP2M"
- determining *on which tiles* to draw it,
- determining _on which tiles_ to draw it,
% id = "01HQ162WWAA0V0SS0D1Y38BDS1"
- determining *where in these tiles* to draw it,
- determining _where in these tiles_ to draw it,
% id = "01HQ162WWAGBCBDYF4VH26MX1B"
- and actually drawing it!
% id = "01HQ162WWA2PNGVV075HR3WMER"
- so let's zoom in a bit and look at the tiles one by one. in particular, let's focus on *these* two tiles:
- so let's zoom in a bit and look at the tiles one by one. in particular, let's focus on _these_ two tiles:
![the same red rectangle, now with a focus on the northern tile at its center][pic:01HPYWPJB1P0GK53BSJFJFRAGR]
% id = "01HQ162WWAYDS6CSD3T102NA9X"
- notice how the two highlighted tiles are *different.* therefore, we can infer we should probably connect together any tiles that are *the same*.
- notice how the two highlighted tiles are _different._ therefore, we can infer we should probably connect together any tiles that are _the same_.
% id = "01HQ162WWATDD86D4GY7RMT0BZ"
- knowing that, we can extract the logic to a function:
```javascript tairu
{:program=tairu}
```javascript
export function shouldConnect(a, b) {
return a == b;
}
```
% id = "01HQ162WWA9M6801Q0RNRSF09H"
+ now, also note that the border around this particular tile is only drawn on its *northern* edge -
therefore we can infer that borders should only be drawn on edges for whom `shouldConnect(thisTile, adjacentTile)` is **`false`** (not `true`!).
+ now, also note that the border around this particular tile is only drawn on its _northern_ edge -
therefore we can infer that borders should only be drawn on edges for whom `shouldConnect(thisTile, adjacentTile)` is *`false`* (not `true`!).
a tile generally has four edges - east, south, west, north - so we need to perform this check for all of them, and draw our border accordingly.
% id = "01HQ162WWAM5YYQCEXH791T0E9"
@ -135,7 +144,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWA5W8NXSXVZY3BBQ0H"
- to do that, I'm gonna override the tile editor's `drawTilemap` function - as this is where the actual tilemap rendering happens!
```javascript tairu
{:program=tairu}
```javascript
import { TileEditor } from "tairu/editor.js";
export class TileEditorWithBorders extends TileEditor {
@ -160,7 +170,7 @@ styles = ["page/tairu.css"]
continue;
}
// Check which of this tile's neighbors should *not* connect to it.
// Check which of this tile's neighbors should _not_ connect to it.
let disjointWithEast = !shouldConnect(tile, this.tilemap.at(x + 1, y));
let disjointWithSouth = !shouldConnect(tile, this.tilemap.at(x, y + 1));
let disjointWithWest = !shouldConnect(tile, this.tilemap.at(x - 1, y));
@ -191,14 +201,17 @@ styles = ["page/tairu.css"]
and here's the result:
```javascript tairu
{:program=tairu}
```javascript
new TileEditorWithBorders({
tilemap: tilemapSquare,
tileSize: 40,
borderWidth: 4,
});
```
```output tairu 01HQ49TJZFMK719KSE16SG3F7B
{:program=tairu :placeholder=01HQ49TJZFMK719KSE16SG3F7B}
```output
```
% id = "01HQ162WWAAEKW1ECV5G3ZEY47"
@ -221,58 +234,58 @@ styles = ["page/tairu.css"]
id = "01HQ162WWAS502000K8QZWVBDW"
- we can split this tileset up into 16 individual tiles, each one 8 × 8 pixels; people choose various resolutions, I chose a fairly low one to hide my lack of artistic skill.
<div class="horizontal-tile-strip">
<span class="metal x-0 y-0"></span>
<span class="metal x-1 y-0"></span>
<span class="metal x-2 y-0"></span>
<span class="metal x-3 y-0"></span>
<span class="metal x-0 y-1"></span>
<span class="metal x-1 y-1"></span>
<span class="metal x-2 y-1"></span>
<span class="metal x-3 y-1"></span>
<span class="metal x-0 y-2"></span>
<span class="metal x-1 y-2"></span>
<span class="metal x-2 y-2"></span>
<span class="metal x-3 y-2"></span>
<span class="metal x-0 y-3"></span>
<span class="metal x-1 y-3"></span>
<span class="metal x-2 y-3"></span>
<span class="metal x-3 y-3"></span>
</div>
::: horizontal-tile-strip
[]{.metal .x-0 .y-0}
[]{.metal .x-1 .y-0}
[]{.metal .x-2 .y-0}
[]{.metal .x-3 .y-0}
[]{.metal .x-0 .y-1}
[]{.metal .x-1 .y-1}
[]{.metal .x-2 .y-1}
[]{.metal .x-3 .y-1}
[]{.metal .x-0 .y-2}
[]{.metal .x-1 .y-2}
[]{.metal .x-2 .y-2}
[]{.metal .x-3 .y-2}
[]{.metal .x-0 .y-3}
[]{.metal .x-1 .y-3}
[]{.metal .x-2 .y-3}
[]{.metal .x-3 .y-3}
:::
% classes.branch = "tileset-cardinal-directions-demo"
id = "01HQ162WWANBTYH1JJWCTZYYVN"
- the keen eyed among you have probably noticed that this is very similar to the case we had before with drawing procedural borders -
except that instead of determining which borders to draw based on a tile's neighbors, this time we'll determine which *whole tile* to draw based on its neighbors!
except that instead of determining which borders to draw based on a tile's neighbors, this time we'll determine which _whole tile_ to draw based on its neighbors!
<div class="horizontal-tile-strip">
<span class="metal x-0 y-0"><span class="east">E</span><span class="south">S</span></span>
<span class="metal x-1 y-0"><span class="east">E</span><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-2 y-0"><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-3 y-0"><span class="south">S</span></span>
<span class="metal x-0 y-1"><span class="east">E</span><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-1 y-1"><span class="east">E</span><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-2 y-1"><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-3 y-1"><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-0 y-2"><span class="east">E</span><span class="north">N</span></span>
<span class="metal x-1 y-2"><span class="east">E</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-2 y-2"><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-3 y-2"><span class="north">N</span></span>
<span class="metal x-0 y-3"><span class="east">E</span></span>
<span class="metal x-1 y-3"><span class="east">E</span><span class="west">W</span></span>
<span class="metal x-2 y-3"><span class="west">W</span></span>
<span class="metal x-3 y-3"></span>
</div>
::: horizontal-tile-strip
[[E]{.east} [S]{.south}]{.metal .x-0 .y-0}
[[E]{.east} [S]{.south} [W]{.west}]{.metal .x-1 .y-0}
[[S]{.south} [W]{.west}]{.metal .x-2 .y-0}
[[S]{.south}]{.metal .x-3 .y-0}
[[E]{.east} [S]{.south} [N]{.north}]{.metal .x-0 .y-1}
[[E]{.east} [S]{.south} [W]{.west} [N]{.north}]{.metal .x-1 .y-1}
[[S]{.south} [W]{.west} [N]{.north}]{.metal .x-2 .y-1}
[[S]{.south} [N]{.north}]{.metal .x-3 .y-1}
[[E]{.east} [N]{.north}]{.metal .x-0 .y-2}
[[E]{.east} [W]{.west} [N]{.north}]{.metal .x-1 .y-2}
[[W]{.west} [N]{.north}]{.metal .x-2 .y-2}
[[N]{.north}]{.metal .x-3 .y-2}
[[E]{.east}]{.metal .x-0 .y-3}
[[E]{.east} [W]{.west}]{.metal .x-1 .y-3}
[[W]{.west}]{.metal .x-2 .y-3}
[]{.metal .x-3 .y-3}
:::
% id = "01HQ162WWA4Z6KKWFV59BR4WD3"
- previously we represented which single border to draw with a single boolean.
now we will represent which single tile to draw with *four* booleans, because each tile can connect to four different directions.
now we will represent which single tile to draw with _four_ booleans, because each tile can connect to four different directions.
% id = "01HQ162WWAQ9GZ6JD8KESW4N53"
- four booleans like this can easily be packed into a single integer using some bitwise operations, hence we get ***bitwise autotiling*** - autotiling using bitwise operations!
- four booleans like this can easily be packed into a single integer using some bitwise operations, hence we get _*bitwise autotiling*_ - autotiling using bitwise operations!
% id = "01HQ162WWAMBM8RXKQTN3D0XR2"
- now the clever part of bitwise autotiling is that we can use this packed integer *as an array index* - therefore selecting which tile to draw can be determined using just a single lookup table! neat, huh?
- now the clever part of bitwise autotiling is that we can use this packed integer _as an array index_ - therefore selecting which tile to draw can be determined using just a single lookup table! neat, huh?
% id = "01HQ162WWA0ZGZ97JZZBFS41TF"
- but because I'm lazy, and CPU time is valuable, instead of using an array I'll just rearrange the tileset texture a bit to be able to slice it in place using this index.
@ -280,7 +293,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAQQ99TRBDY5DCSW3Z"
- say we arrange our bits like this:
```javascript tairu
{:program=tairu}
```javascript
export const E = 0b0001;
export const S = 0b0010;
export const W = 0b0100;
@ -291,32 +305,32 @@ styles = ["page/tairu.css"]
id = "01HQ162WWABANND0WGT933TBMV"
- that means we'll need to arrange our tiles like so, where the leftmost tile is at index 0 (`0b0000`) and the rightmost tile is at index 15 (`0b1111`):
<div class="horizontal-tile-strip">
<span class="metal x-3 y-3"></span>
<span class="metal x-0 y-3"><span class="east">E</span></span>
<span class="metal x-3 y-0"><span class="south">S</span></span>
<span class="metal x-0 y-0"><span class="east">E</span><span class="south">S</span></span>
<span class="metal x-2 y-3"><span class="west">W</span></span>
<span class="metal x-1 y-3"><span class="east">E</span><span class="west">W</span></span>
<span class="metal x-2 y-0"><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-1 y-0"><span class="east">E</span><span class="south">S</span><span class="west">W</span></span>
<span class="metal x-3 y-2"><span class="north">N</span></span>
<span class="metal x-0 y-2"><span class="east">E</span><span class="north">N</span></span>
<span class="metal x-3 y-1"><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-0 y-1"><span class="east">E</span><span class="south">S</span><span class="north">N</span></span>
<span class="metal x-2 y-2"><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-1 y-2"><span class="east">E</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-2 y-1"><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
<span class="metal x-1 y-1"><span class="east">E</span><span class="south">S</span><span class="west">W</span><span class="north">N</span></span>
</div>
::: horizontal-tile-strip
[]{.metal .x-3 .y-3}
[[E]{.east}]{.metal .x-0 .y-3}
[[S]{.south}]{.metal .x-3 .y-0}
[[E]{.east} [S]{.south}]{.metal .x-0 .y-0}
[[W]{.west}]{.metal .x-2 .y-3}
[[W]{.west} [E]{.east}]{.metal .x-1 .y-3}
[[W]{.west} [S]{.south}]{.metal .x-2 .y-0}
[[W]{.west} [E]{.east} [S]{.south}]{.metal .x-1 .y-0}
[[N]{.north}]{.metal .x-3 .y-2}
[[E]{.east} [N]{.north}]{.metal .x-0 .y-2}
[[S]{.south} [N]{.north}]{.metal .x-3 .y-1}
[[E]{.east} [S]{.south} [N]{.north}]{.metal .x-0 .y-1}
[[W]{.west} [N]{.north}]{.metal .x-2 .y-2}
[[E]{.east} [W]{.west} [N]{.north}]{.metal .x-1 .y-2}
[[S]{.south} [W]{.west} [N]{.north}]{.metal .x-2 .y-1}
[[E]{.east} [S]{.south} [W]{.west} [N]{.north}]{.metal .x-1 .y-1}
:::
% id = "01HQ162WWAJPW00XA25N0K6KS7"
- packing that into a single tileset, or rather this time, a *tile strip*, we get this image:
- packing that into a single tileset, or rather this time, a _tile strip_, we get this image:
![horizontal tile strip of 16 8x8 pixel metal tiles][pic:01HPMMR6DGKYTPZ9CK0WQWKNX5]
% id = "01HQ162WWAT2ZC7T2P9ATD6WG2"
- now it's time to actually implement it as code! I'll start by defining a *tile index* function as a general way of looking up tiles in a tileset.
- now it's time to actually implement it as code! I'll start by defining a _tile index_ function as a general way of looking up tiles in a tileset.
% id = "01HQ162WWA0NRHBB6HP2RERNBK"
- I want to make the tile renderer a bit more general, so being able to attach a different tile lookup function to each tileset sounds like a great feature.
@ -327,7 +341,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAYJ4JCG3Z24SJR8S9"
- …but anyways, here's the basic bitwise magic function:
```javascript tairu
{:program=tairu}
```javascript
export function tileIndexInBitwiseTileset(tilemap, x, y) {
let tile = tilemap.at(x, y);
@ -345,7 +360,8 @@ styles = ["page/tairu.css"]
id = "01HQ162WWAS813ANMBG1PWDZHC"
- we'll define our tilesets by their texture, tile size, and a tile indexing function. so let's create an object that will hold our tileset data:
```javascript tairu
{:program=tairu}
```javascript
// You'll probably want to host the assets on your own website rather than
// hotlinking to others. It helps longevity!
let tilesetImage = new Image();
@ -361,7 +377,8 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWA0SC2GA7Y3KJE0W5F"
- with all that, we should now be able to write a tile renderer which can handle textures! so let's try it:
```javascript tairu
{:program=tairu}
```javascript
import { TileEditor } from "tairu/editor.js";
export class TilesetTileEditor extends TileEditor {
@ -409,20 +426,24 @@ styles = ["page/tairu.css"]
% id = "01HQ162WWAS2HYF41MZNJ18BXC"
- drum roll please…
```javascript tairu
{:program=tairu}
```javascript
new TilesetTileEditor({
tilemap: tilemapSquare,
tileSize: 40,
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ49X8Z57FNMN3E79FYF8CMG
{:program=tairu :placeholder=01HQ49X8Z57FNMN3E79FYF8CMG}
```output
```
% id = "01HQ162WWA03JAGJYCT0DRZP24"
- it works! buuuut if you play around with it you'll quickly start noticing some problems:
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
export const tilemapEdgeCase = Tilemap.parse(" x", [
@ -438,7 +459,9 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ49YDPQXYSAT5N6P241DG3C
{:program=tairu :placeholder=01HQ49YDPQXYSAT5N6P241DG3C}
```output
```
% id = "01HQ162WWAB0AYSPGB4AEVT03Z"
@ -448,9 +471,10 @@ styles = ["page/tairu.css"]
- ### thing is, it was never good in the first place
% id = "01HQ162WWARSVDRNHZE13ZF6W6"
- I'll be blunt: we don't have enough tiles to represent *corners*! like in this case:
- I'll be blunt: we don't have enough tiles to represent _corners_! like in this case:
```javacript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -464,7 +488,9 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ49Z8JWR75D85DGHCB34K8E
{:program=tairu :placeholder=01HQ49Z8JWR75D85DGHCB34K8E}
```output
```
% id = "01HQ1K39AS4VDW7DVTAGQ03WFM"
@ -476,50 +502,60 @@ styles = ["page/tairu.css"]
- it should kind of _"bend"_ to fit in with the tiles to the north and the south, but it doesn't :kamien:
% id = "01HQ1K39ASQQNF7B881SYJWRC7"
- so what if we made the tile look like *this* instead:
- so what if we made the tile look like _this_ instead:
![mockup showing that previous L-shape but with a real corner][pic:01HQ17GYEZSZCVRBFHP4HXAJV8]
% id = "01HQ1K39ASMKRMTXFV93FRHZTG"
- that sure as heck looks a lot nicer! but there's a problem: that tile, let's zoom in on it…
![that bent tile, and just *it* alone][pic:01HQ183RANGH4S7VZSG1ZGH0S5]
![that bent tile, and just _it_ alone][pic:01HQ183RANGH4S7VZSG1ZGH0S5]
% classes.branch = "tileset-four-to-eight-demo"
id = "01HQ1K39ASR81NWMW8Q0MF8QMP"
- enhance!
{% NOTE djot: I don't there's a way to achieve this in Djot alone %}
``` =html
<ul class="directions-square bend">
<li class="east">E</li>
<li class="south">S</li>
</ul>
```
% classes.branch = "tileset-four-to-eight-demo"
id = "01HQ1K39ASC5WTR2A2AJN85JK2"
- huh. interesting. it connects to the east and the south. so what about this tile -
``` =html
<ul class="directions-square e-s">
<li class="east">E</li>
<li class="south">S</li>
</ul>
```
% id = "01HQ1K39ASXYBH9QJH5Q0C45JZ"
- because it *also* connects to the east and the south :thinking:
- because it _also_ connects to the east and the south :thinking:
% id = "01HQ1K39ASW5PWS52NGA2X3M0P"
- seems like we'll need something to disambiguate the two cases - and what better thing to disambiguate with than *more bits*!
- seems like we'll need something to disambiguate the two cases - and what better thing to disambiguate with than _more bits_!
% classes.branch = "tileset-four-to-eight-demo"
id = "01HPQCCV4R5N97FJ1GS36HZJZ7"
- to represent the corners, we'll turn our four cardinal directions…
``` =html
<ul class="directions-square">
<li class="east">E</li>
<li class="south">S</li>
<li class="west">W</li>
<li class="north">N</li>
</ul>
```
into eight *ordinal* directions:
into eight _ordinal_ directions:
``` =html
<ul class="directions-square">
<li class="east">E</li>
<li class="south-east">SE</li>
@ -530,13 +566,14 @@ styles = ["page/tairu.css"]
<li class="north">N</li>
<li class="north-east"><a href="https://github.com/NoiseStudio/NoiseEngine/" title="NoiseEngine????">NE</a></li>
</ul>
```
% id = "01HQ1K39ASFN94YDY1RWQYS12K"
- at this point with the four extra corners we'll need 8 bits to represent our tiles, and that would make…
***256 tiles!?***
_*256 tiles!?*_
nobody in their right mind would actually draw 256 separate tiles, right? ***RIGHT???***
nobody in their right mind would actually draw 256 separate tiles, right? _*RIGHT???*_
% template = true
id = "01HQ1K39AS11M1M4GQQ60NXTY6"
@ -544,7 +581,8 @@ styles = ["page/tairu.css"]
if we arrange the tiles in a diagnonal cross like this, notice how the tile in the center would have the bits `SE | SW | NW | NE` set, which upon first glance would suggest us needing a different tile -
but it looks correct!
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -559,29 +597,35 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset],
});
```
```output tairu 01HQ4A01MPE6JT5ZZFEN9S635W
{:program=tairu :placeholder=01HQ4A01MPE6JT5ZZFEN9S635W}
```output
```
% id = "01HQ1K39AS7CRBZ67N1VVHCVME"
- therefore there must be *some* bit combinations that are redundant to others. let's find them!
- therefore there must be _some_ bit combinations that are redundant to others. let's find them!
% classes.branch = "tileset-four-to-eight-demo"
id = "01HQ1K39ASZPJ4E23EZ1XJ5J7K"
- let's pick one corner first, then generalize to all the other ones. I pick southeast!
``` =html
<ul class="directions-square e-s">
<li class="east">E</li>
<li class="south-east">SE</li>
<li class="south">S</li>
</ul>
```
% id = "01HQ1K39ASQTR054W0VWEAV2FS"
- in this case, if we remove the tile to the southeast, we get that bent tile from before:
``` =html
<ul class="directions-square bend">
<li class="east">E</li>
<li class="south">S</li>
</ul>
```
% id = "01HQ1K39AS6RGE6Z83T8MH1R0M"
- what we can learn from this is that for `E | S`, `ES` affects the result!
@ -589,6 +633,7 @@ styles = ["page/tairu.css"]
% id = "01HQ1K39ASVSAQ6F8ANEZE1WQ4"
- but if we add any other corner, nothing changes. heck, let's add all of them:
``` =html
<ul class="directions-square e-s">
<li class="east">E</li>
<li class="south-east">SE</li>
@ -597,6 +642,7 @@ styles = ["page/tairu.css"]
<li class="north-west">NW</li>
<li class="north-east">NE</li>
</ul>
```
% id = "01HQ1K39AST8RQTVSCDV7FSH62"
- this combination is definitely redundant!
@ -612,7 +658,8 @@ styles = ["page/tairu.css"]
+ we'll start off by redefining our bits to be ordinal directions instead. I still want to keep the [nice orderliness][branch:01HQ162WWAM5YYQCEXH791T0E9] that comes with
arranging the bits clockwise starting from east, so if we want that we can't just extend the indices with an extra four bits at the top.
```javascript tairu
{:program=tairu}
```javascript
export const E = 0b0000_0001;
export const SE = 0b0000_0010;
export const S = 0b0000_0100;
@ -626,7 +673,8 @@ styles = ["page/tairu.css"]
% id = "01HPSY4Y19HPNXC54VP6TFFHXN"
- I don't know about you, but I find the usual C-style way of checking whether a bit is set extremely hard to read, so let's take care of that:
```javascript tairu
{:program=tairu}
```javascript
export function isSet(integer, bit) {
return (integer & bit) == bit;
}
@ -636,7 +684,8 @@ styles = ["page/tairu.css"]
- now we can write a function that will remove the aforementioned redundancies.
the logic is quite simple - for southeast, we only allow it to be set if both south and east are also set, and so on and so forth.
```javascript tairu
{:program=tairu}
```javascript
// t is an existing tile index; variable name is short for brevity
export function removeRedundancies(t) {
if (isSet(t, SE) && (!isSet(t, S) || !isSet(t, E))) {
@ -658,7 +707,8 @@ styles = ["page/tairu.css"]
% id = "01HPSY4Y19HWQQ9XBW1DDGW68T"
- with that, we can find a set of all unique non-redundant combinations:
```javascript tairu
{:program=tairu}
```javascript
export function ordinalDirections() {
let unique = new Set();
for (let i = 0; i <= 0b1111_1111; ++i) {
@ -669,20 +719,22 @@ styles = ["page/tairu.css"]
```
% id = "01HPSY4Y19KG8DC4SYXR1DJJ5F"
- by the way, I find it quite funny how JavaScript's [`Array.prototype.sort`] defaults to ASCII ordering *for all types.*
- by the way, I find it quite funny how JavaScript's [`Array.prototype.sort`][sort] defaults to ASCII ordering _for all types._
even numbers! ain't that silly?
[`Array.prototype.sort`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
[sort]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
% id = "01HPSY4Y19V62YKTGK3TTKEB38"
- and with all the ingredients in the pot, we now _Let It Cook™_:
```javascript tairu
{:program=tairu}
```javascript
let dirs = ordinalDirections();
console.log(dirs.length);
```
```output tairu
{:program=tairu}
```output
47
```
@ -690,7 +742,7 @@ styles = ["page/tairu.css"]
- forty seven! that's how many unique tiles we actually need.
% id = "01HPSY4Y19C303Z595KNVXYYVS"
- you may find pixel art tutorials saying you need forty *eight* and not forty *seven*, but that is not quite correct -
- you may find pixel art tutorials saying you need forty _eight_ and not forty _seven_, but that is not quite correct -
the forty eighth tile is actually just the empty tile! saying it's part of the tileset is quite misleading IMO.
% id = "01HPSY4Y19TM2K2WN06HHEM3D0"
@ -707,13 +759,13 @@ styles = ["page/tairu.css"]
- so we only need to draw 47 tiles, but to actually display them in a game we still need to pack them into an image.
% id = "01HPWJB4Y0QX6YR6TQKZ7T1C2E"
- we *could* use a similar approach to the 16 tile version, but that would leave us with lots of wasted space!
- we _could_ use a similar approach to the 16 tile version, but that would leave us with lots of wasted space!
% id = "01HPWJB4Y0HKGSDABB56CNFP9H"
- think that with this redundancy elimination approach most of the tiles will never even be looked up by the renderer, because the bit combinations will be collapsed into a more canonical form before the lookup.
% id = "01HQ1K39ASM53P1E74HKRZ1T24"
- so instead of wasting space, we can compress the tiles into a compact strip, and use a lookup table from sparse tile indices to dense tile *positions* within the strip.
- so instead of wasting space, we can compress the tiles into a compact strip, and use a lookup table from sparse tile indices to dense tile _positions_ within the strip.
% id = "01HPWJB4Y0F9JGXQDAAVC3ERG1"
- I don't want to write the lookup table by hand, so let's generate it!
@ -721,7 +773,8 @@ styles = ["page/tairu.css"]
% id = "01HPWJB4Y0HTV32T4WMKCKWTVA"
- we'll start by obtaining our ordinal directions array again:
```javascript tairu
{:program=tairu}
```javascript
export let xToConnectionBitSet = ordinalDirections();
```
@ -730,7 +783,8 @@ styles = ["page/tairu.css"]
remember that our array has only 256 values, so it should be pretty cheap to represent using a [`Uint8Array`]:
```javascript tairu
{:program=tairu}
```javascript
export let connectionBitSetToX = new Uint8Array(256);
for (let i = 0; i < xToConnectionBitSet.length; ++i) {
connectionBitSetToX[xToConnectionBitSet[i]] = i;
@ -742,18 +796,22 @@ styles = ["page/tairu.css"]
% id = "01HPWJB4Y0CWQB9EZG6C91A0H0"
- and there we go! we now have a mapping from our bitset to positions within the tile strip. try to play around with the code example to see which bitsets correspond to which position!
```javascript tairu
{:program=tairu}
```javascript
console.log(connectionBitSetToX[E | SE | S]);
```
```output tairu
{:program=tairu}
```output
4
```
% id = "01HPWJB4Y09P9Q3NGN59XWX2X9"
+ for my own (and your) convenience, here's a complete list of *all* the possible combinations in order.
+ for my own (and your) convenience, here's a complete list of _all_ the possible combinations in order.
% id = "01HPWJB4Y01VJFMHYEC1WZ353W"
- ```javascript tairu
- {:program=tairu}
```javascript
function toString(bitset) {
if (bitset == 0) return "0";
@ -773,7 +831,9 @@ styles = ["page/tairu.css"]
console.log(`${x} => ${toString(xToConnectionBitSet[x])}`);
}
```
```output tairu
{:program=tairu}
```output
0 => 0
1 => E
2 => S
@ -836,7 +896,8 @@ styles = ["page/tairu.css"]
% id = "01HQ1M84GS09M7PMXFYHDPRTMT"
- since we already prepared the bulk of the framework before, it should be as simple as writing a new `tileIndex` function:
```javascript tairu
{:program=tairu}
```javascript
export function tileIndexInBitwiseTileset47(tilemap, x, y) {
let tile = tilemap.at(x, y);
@ -858,7 +919,8 @@ styles = ["page/tairu.css"]
id = "01HQ1M84GS4C99VQZC4150CMDS"
- now we can write a new tileset descriptor that uses this indexing function and the larger tile strip:
```javascript tairu
{:program=tairu}
```javascript
// Once again, use your own link here!
let tilesetImage = new Image();
tilesetImage.src = "{% pic 01HPW47SHMSVAH7C0JR9HWXWCM %}";
@ -873,7 +935,8 @@ styles = ["page/tairu.css"]
% id = "01HQ1M84GS9CC8VR1BVDC15W50"
- and Drum Roll 2: Return of the Snare please…
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -888,14 +951,16 @@ styles = ["page/tairu.css"]
tilesets: [heavyMetalTileset47],
});
```
```output tairu 01HQ4A11RRXEQ850598GFBJN0B
{:program=tairu :placeholder=01HQ4A11RRXEQ850598GFBJN0B}
```output
```
% id = "01HQ1M84GSCXTPGVPXY840WCQ6"
- it works perfectly!
% id = "01HQ1M84GSVBG9T94ZN9XTXX58"
- but honestly, this is a bit *boring* if we're gonna build a game with procedural worlds.
- but honestly, this is a bit _boring_ if we're gonna build a game with procedural worlds.
% id = "01HQ1M84GSH0KTFFZET6GZZ4V2"
- heck, it's even boring for a level designer to have to lay out all the tiles manually -
@ -907,7 +972,8 @@ styles = ["page/tairu.css"]
% id = "01HQ1M84GS0KJ9NA6GPS62RC95"
- for now, have a big editor to play around with. it's a lot of fun arranging the tiles in various shapes!
```javascript tairu
{:program=tairu}
```javascript
import { Tilemap } from "tairu/tilemap.js";
new TilesetTileEditor({
@ -916,7 +982,9 @@ new TilesetTileEditor({
tilesets: [heavyMetalTileset47],
});
```
```output tairu 01HQ4A45WNAEJGCT2WDMQJHK14
{:program=tairu :placeholder=01HQ4A45WNAEJGCT2WDMQJHK14}
```output
```
:nap: <!--
@ -947,7 +1015,7 @@ You have been warned.
- after a while I switched to a fork - [Lite XL](https://github.com/lite-xl/lite-xl), which had better font rendering and more features
% id = "01HPD4XQPWB11TZSX5VAAJ6TCD"
- I stopped using it because VS Code was just more feature packed and usable; no need to reinvent the wheel, rust-analyzer *just works.*
- I stopped using it because VS Code was just more feature packed and usable; no need to reinvent the wheel, rust-analyzer _just works._
% id = "01HPD4XQPW3G7BXTBBTD05MB8V"
- the LSP plugin for Lite XL had some issues around autocompletions not filling in properly :pensive:
@ -956,11 +1024,11 @@ You have been warned.
while tinkering with your editor is something really cool, in my experience it's only cool up to a point.
% id = "01HPD4XQPWV1BAPA27SNDFR93B"
- the cool thing with Tilekit is that it's *more* than just your average bitwise autotiling - of course it *can* do basic autotiling, but it can also do so much more
- the cool thing with Tilekit is that it's _more_ than just your average bitwise autotiling - of course it _can_ do basic autotiling, but it can also do so much more
% id = "01HPD4XQPWM1JSAPXVT6NBHKYY"
classes.branch_children = "branch-quote"
- if I had to describe it, it's basically something of a *shader langauge for tilesets.* this makes it really powerful, as you can do little programs like
- if I had to describe it, it's basically something of a _shader langauge for tilesets._ this makes it really powerful, as you can do little programs like
% id = "01HPD4XQPWE7ZVR0SS67DHTGHQ"
- autotile using this base tileset

View file

@ -12,6 +12,7 @@
% id = "01H9R1KJESJ0G0VQAW994ZHR0S"
- take the following example:
```cpp
class ComfyZone
{
@ -41,16 +42,16 @@
- this can be _very_ hard to spot if you have a big class with lots of declarations inside.
% id = "01H9R1KJESCJ3VC8ATPYFDCPSP"
- this can be worked around by banning access modifiers from appearing in `#ifdef`s, but you have to *realize* that this might happen
- this can be worked around by banning access modifiers from appearing in `#ifdef`s, but you have to _realize_ that this might happen
% id = "01H9R1KJES4ZYHVADDF80WAXH6"
- and I've seen instances of this exact thing occurring in the Unreal Engine codebase, which is *full* of long lists of declarations (made even longer by the prevalence of `UPROPERTY()` specifiers)
- and I've seen instances of this exact thing occurring in the Unreal Engine codebase, which is _full_ of long lists of declarations (made even longer by the prevalence of `UPROPERTY()` specifiers)
% id = "01H9R1KJES182MCV2V0A4VHKKX"
- even if we didn't have the preprocessor, that access modifier is state _you_ have to keep track of
% id = "01H9R1KJESH7PWNKCKW3H0WJHW"
- I very often find myself needing to scroll upward after <kbd>Ctrl</kbd>-clicking on a field or function declaration, just to find out if I can use it
- I very often find myself needing to scroll upward after `<kbd>Ctrl</kbd>`{=html}-clicking on a field or function declaration, just to find out if I can use it
% id = "01H9R1KJESFE6F1D4J5PA5Q381"
- (thankfully IDEs are helpful here and Rider shows you a symbol's visibility in the tooltip on hover, but I don't have Rider on code reviews)

View file

@ -5,6 +5,7 @@
% id = "01J0VN48B2Z5BFFEZCEYG63662"
- obviously the simplest way would be to just use the C library.
```cpp
int main(void)
{
@ -35,7 +36,7 @@
- this approach has the nice advantage of being really simple, but it doesn't work well if you build your codebase on RAII.
% id = "01J0VN48B2CT2DVHEB1HGK8KB7"
- and as much as I disagree with using it *everywhere* and injecting object-oriented design into everything, RAII is actually really useful for OS resources such as an `SDL_Window*`.
- and as much as I disagree with using it _everywhere_ and injecting object-oriented design into everything, RAII is actually really useful for OS resources such as an `SDL_Window*`.
% id = "01J0VN48B2SX6GX0B3AKDVHGFX"
- to make use of RAII you might be tempted to wrap your `SDL_Window*` in a class with a destructor…
@ -81,6 +82,7 @@ struct window
% id = "01J0VN48B2E1X2G415P1TNG4CJ"
- copying windows doesn't really make sense, so we can delete the copy constructor and copy assignment operator…
```cpp
struct window
{
@ -99,6 +101,7 @@ struct window
% id = "01J0VN48B2AAD3SKFWNDMYV4FV"
- so we'll also want an explicit move constructor and a move assignment operator:
```cpp
struct window
{
@ -129,6 +132,7 @@ struct window
% id = "01J0VN48B2TFMXQRPPKJXEEX2E"
- with all of this combined, our final `window` class looks like this:
```cpp
struct window
{
@ -205,10 +209,12 @@ struct window
% id = "01J0VN48B2WZ9PAX0W5W2ZMPPN"
- albeit I'll admit that writing
```cpp
int width;
SDL_GetWindowSize(&window, &width, nullptr);
```
just to obtain the window width does _not_ spark joy.
% id = "01J0VN48B2DCN9PPHHC818NPMD"
@ -246,6 +252,7 @@ this is what _smart pointers_ are for after all - our good friends `std::shared_
% id = "01J0VN48B2NBTZ62YDNKMDN1CC"
- to set a custom deleter for an `std::shared_ptr`, we provide it as the 2nd argument of the constructor.
so to automatically free our `SDL_Window` pointer, we would do this:
```cpp
int main(void)
{
@ -272,6 +279,7 @@ this is what _smart pointers_ are for after all - our good friends `std::shared_
}
}
```
and that's all there is to it!
% id = "01J0VN48B2WHNDKVBSDJRTYG4T"

View file

@ -5,7 +5,7 @@ scripts = [
]
% id = "01J291S06DS12DCFTNKJ27BNSQ"
- *ooh I'm sure this one is gonna be really controversial but here I go*
- _ooh I'm sure this one is gonna be really controversial but here I go_
% id = "01J291S06DRH9SP8K1QE03JVK8"
- time and time again I've heard from people just how horrible JavaScript is, but I beg to differ.
@ -31,15 +31,19 @@ you already have it on your computer.
% id = "01J2931RRHFPEH3D5MWEKGXGAE"
- don't believe me? let me show you:
```javascript i-am-showing-you-right-now
{:program=i-am-showing-you-right-now}
```javascript
console.log("see?") // and if you have JavaScript enabled, you can edit this!
```
```output i-am-showing-you-right-now
{:program=i-am-showing-you-right-now}
```output
see?
```
% id = "01J2931RRHDZ979VTF2SN3J9CB"
- and this goes for pretty much every modern personal computing device on the planet nowadays - *you can already run JavaScript on it.*
- and this goes for pretty much every modern personal computing device on the planet nowadays - _you can already run JavaScript on it._
% id = "01J2931RRHEMZY9M5W3EG666YK"
- sure it may not be the cleanest language in terms of design, but JavaScript running anywhere and everywhere is its superpower.
@ -48,7 +52,7 @@ you already have it on your computer.
- JavaScript being available on everyone's computers makes it a super-accessible programming language to learn: all you have to do is open your web browser and go to the console in its Developer Tools.
% id = "01J2931RRH51K35C80Z2NHSVRW"
- moreso, if you are building a website and would like to embed a language interpreter (like me,) *you already have JavaScript for that.*
- moreso, if you are building a website and would like to embed a language interpreter (like me,) _you already have JavaScript for that._
% id = "01J2931RRH65HHY6BQ7N0P9MX9"
- yes, you could in theory embed [Lua][page:programming/languages/lua], but for most cases JavaScript is Already There and there is nothing wrong with it.

View file

@ -44,7 +44,7 @@
but the designers of Lua had the restraint to just have One.
% id = "01HRG2RJC13YK030EPGMKM9H8H"
- tables are extremely powerful in what they can do, because they're more than just a way of structuring data - they also allow for interfacing with the language *syntax* through operator overloading
- tables are extremely powerful in what they can do, because they're more than just a way of structuring data - they also allow for interfacing with the language _syntax_ through operator overloading
% id = "01HRG2RJC1PDZFB2WBW7D827KF"
+ in fact object oriented programming in Lua is typically done by overloading the `[]` indexing operator.
@ -56,18 +56,18 @@
local fallback = { b = 2 }
local base = { a = 1 }
-- The __index field can be both a function *and* a table.
-- The __index field can be both a function _and_ a table.
-- { __index = the_table } is a shorthand for { __index = function (t, k) return the_table[k] end }
setmetatable(base, { __index = fallback })
assert(base.b == 2)
```
% id = "01HRG2RJC23XH1053A69MQJD4N"
- I'll be honest that I don't like the standard library of Lua from a usability standpoint, but maybe it *doesn't need to be bigger*.
- I'll be honest that I don't like the standard library of Lua from a usability standpoint, but maybe it _doesn't need to be bigger_.
it's similar to the principles of [Go](https://go.dev/), where the language encourages using dumb constructs rather than super clever code with lots of abstraction.
% id = "01HRG2RJC2P3832KTQMANBHGE6"
- though unlike Go, Lua has the goal of being *small* because it needs to be *embeddable*, especially given it's used in very constrained environments in the real world. (microcontrollers!)
- though unlike Go, Lua has the goal of being _small_ because it needs to be _embeddable_, especially given it's used in very constrained environments in the real world. (microcontrollers!)
% id = "01HRG2RJC2S3V38FM6DB0481WK"
- therefore there are technical, not just ideological reasons to keep the library small.
@ -76,10 +76,10 @@
- and I really like that from an embedder's standpoint, it's possible to completely disable certain standard library modules for sandboxing!
% id = "01HRG2RJC2W4MK96FMWRTS8QCJ"
- Lua also knows *very* well how much syntax sugar to have to make writing code pleasant, but not to overdose it so much as to give you instant diabetes.
- Lua also knows _very_ well how much syntax sugar to have to make writing code pleasant, but not to overdose it so much as to give you instant diabetes.
% id = "01HRG2RJC28DT0TZT47WPABD65"
+ as an example, there's function call syntax: you can pass it a string or table *literal*, which is just enough to enable some really nice DSLs without making the grammar too complex.
+ as an example, there's function call syntax: you can pass it a string or table _literal_, which is just enough to enable some really nice DSLs without making the grammar too complex.
% id = "01HRG2RJC2CVKS9CFVQ9HJSBH4"
- once upon a time I dreamed up a DSL for building GUIs using this sugar.
@ -97,7 +97,7 @@
```
% id = "01HRG2RJC2T7BF6XS3T7Q8AXW2"
- ***JUST LOOK AT HOW CLEAN IT IS!*** with no need to [invent magic syntax](https://www.typescriptlang.org/docs/handbook/jsx.html) or anything!
- _*JUST LOOK AT HOW CLEAN IT IS!*_ with no need to [invent magic syntax](https://www.typescriptlang.org/docs/handbook/jsx.html) or anything!
% id = "01HRG2RJC2D69JYCWQXSF2FQNY"
- the only missing thing then would be list comprehensions to be able to transform data into GUI elements, but even that can be ironed over using function literals:
@ -122,7 +122,7 @@
}
```
interpret this code however you want, but *damn* it looks clean. again with no magic syntax!
interpret this code however you want, but _damn_ it looks clean. again with no magic syntax!
% id = "01HRG2RJC2PA5KE0DH0RRFGW9E"
- there is also the incredibly useful sugar for indexing tables by string literals: instead of `table["x"]` you can write down `table.x`
@ -136,10 +136,10 @@
the parameter is explicit, there is just sugar for passing it into functions and declaring functions with it.
% id = "01HRG2RJC2FF05JWQ6KHS4Y5WF"
- I really wish Lua had at least *a* form of static typing though, since knowing about errors you make early is _really_ helpful during development.
- I really wish Lua had at least _a_ form of static typing though, since knowing about errors you make early is _really_ helpful during development.
% id = "01HRG2RJC2JP3HRTVMAQ22HDVE"
+ it regularly happened to me that a type error I made only occured at *some point* later during runtime; and then you have to track down a reproduction case and make a fix at the source. not fun.
+ it regularly happened to me that a type error I made only occured at _some point_ later during runtime; and then you have to track down a reproduction case and make a fix at the source. not fun.
% id = "01HRG3MJ0KGZ8T4KHMV6KZXDK4"
- there's also the ugly case I had with a division by zero in the last rewrite of [Planet Overgamma][def:planet_overgamma/repo], which caused a NaN to propagate through physics and into rendering, causing a crash.

View file

@ -21,7 +21,7 @@
- you say `info` sounds wrong for debug info? well,
% id = "01HBTSXTTATA4GW13QBC8YMWP1"
- guess what, it's debug ***info***
- guess what, it's debug _*info*_
% id = "01HBTSXTTANNMHXDGG1M4W70XN"
- the very concept of logs is to dump a load of (debug) info for later analysis,
@ -172,10 +172,10 @@
- I'd rather have the program crash and burn at the point a `NaN` is produced rather than have to sift through all the math to find that one division by zero I didn't account for
% id = "01HPEMVAH9XG3RK62RFXD29RWV"
- this does influence performance negatively, but it saves *so much* debugging pain and finding out which non deterministic scenario causes a NaN to propagate through the system
- this does influence performance negatively, but it saves _so much_ debugging pain and finding out which non deterministic scenario causes a NaN to propagate through the system
% id = "01HPEMVAH9CKAEQBMC8S6MR0GQ"
- worst case scenario you pull a Rust and disable those checks on release mode. that *does* work, but I don't like the idea of disabling numeric safety checks on release mode either.
- worst case scenario you pull a Rust and disable those checks on release mode. that _does_ work, but I don't like the idea of disabling numeric safety checks on release mode either.
% id = "01HPEQ01JRMM17Y30BP7ZFKZRJ"
+ :page: operator overloading is good, but getters and setters are not
@ -184,10 +184,10 @@
- this one stems from an argument I had today, so I'll write my thoughts for future generations' enjoyment here
% id = "01HPEQ01JR4YWC9Q6VYS82J0E3"
- I'll start by prefacing that I think operator overloading is good [*iff*][def:word/iff] it's implemented in a way that a single operator has only one, well-defined meaning
- I'll start by prefacing that I think operator overloading is good [_iff_][def:word/iff] it's implemented in a way that a single operator has only one, well-defined meaning
% id = "01HPEQ01JRBB8Z3P0KFJSR0SJN"
- this means `+` really means *addition* and nothing else.
- this means `+` really means _addition_ and nothing else.
% id = "01HPEQ01JRJJBP9C701B36ZR4N"
- this is practically impossible to enforce at a language level - what prevents the standard library authors from overloading `+` to mean string concatenation after all?
@ -196,13 +196,14 @@
- however we can at least do our best by writing good defaults and coding standards that gently suggest what to do and what not to do
% id = "01HPEQ01JR4ZC0M68818EDVDBF"
- for example, allow users to define their own arbitrary operators that are explicitly *not* addition, to incentivize inventing new syntax for these things
- for example, allow users to define their own arbitrary operators that are explicitly _not_ addition, to incentivize inventing new syntax for these things
% id = "01HPEQ01JRTWHH6PVNTFBDXPVT"
- the way I'd like to do it in [my dream language][def:rokugo/repo] is by a few means
% id = "01HPEQ01JRAAK5MQCZ7CFZ75FA"
- `(+)` is defined to be a polymorphic operator which calls into a module implementing the `AddSub` interface, which means you have to implement both addition *and* subtraction for `(+)` to work on your type
- `(+)` is defined to be a polymorphic operator which calls into a module implementing the `AddSub` interface, which means you have to implement both addition _and_ subtraction for `(+)` to work on your type
```rokugo
let AddSub = interface {
type T
@ -216,7 +217,7 @@
```
% id = "01HPEQ01JR71RV53NNSFFDV6XN"
- note how this operator *does not* have any effects declared on it - this means addition and subtraction must not have any side effects such as I/O
- note how this operator _does not_ have any effects declared on it - this means addition and subtraction must not have any side effects such as I/O
% id = "01HPEQ01JRJR3ZAY24BP8TF5HH"
+ the `(add AND subtract)` rule enforces types like strings to take a different operator, because `(-)` does not have a well-defined meaning on strings
@ -234,7 +235,7 @@
- so now getters and setters: what's so bad about them?
% id = "01HPEQ01JRQPZJEDDXV4BJN1GP"
- the problem is that given the rule above - *one operator means one thing* - getters and setters completely destroy your assumptions about what `=` might do
- the problem is that given the rule above - _one operator means one thing_ - getters and setters completely destroy your assumptions about what `=` might do
% id = "01HPEQ01JR0E8C0VJZ1D9TJRAG"
- what's that? you didn't expect `camera.angle_z = 420` to throw because 420 is out of the `[-π/2, π/2]` range? oops!
@ -250,13 +251,16 @@
% id = "01HPEQ01JRQFSFVPQA41MFZ91T"
- this is less of a problem in languages that feature automatic generation of getters and setters - such as Kotlin
```kotlin
var someVariable: String
get
private set
// no infinite recursion to be seen here!
```
but it's still an issue in eg. JavaScript, where one mistake can send your call stack down the spiral:
but it's still an issue in e.g. JavaScript, where one mistake can send your call stack down the spiral:
```javascript
class Example {
#someVariable = "";
@ -265,6 +269,7 @@
set someVariable(value) { this.someVariable = value; } // typo again!!!!!!!!!! dammit!
}
```
and the error is not caught until runtime.
% id = "01HPEQ01JRMMS1B400DP6DV5M9"

View file

@ -1,19 +1,23 @@
% id = "programming/projects/stitchkit"
content.link = "programming/projects/stitchkit"
+ ### Stitchkit
A Hat in Time mod stitching toolkit
% id = "programming/projects/muscript"
content.link = "programming/projects/muscript"
+ ### MuScript
UnrealScript compiler, part of stitchkit
% id = "programming/projects/yarnbox"
content.link = "programming/projects/yarnbox"
+ ### Yarnbox
A Hat in Time bytecode injection mod loader
% id = "programming/projects/shelter"
content.link = "programming/projects/shelter"
+ ### shelter
ideas for an operating system; not actually implemented

View file

@ -78,7 +78,7 @@
+ parallelization with an event loop
% id = "01HA0GPJ8B48K60BWQ2XZZ0PB5"
+ the thing with a pass-based architecture is that with enough locking, it *may* be easy to parallelize.
+ the thing with a pass-based architecture is that with enough locking, it _may_ be easy to parallelize.
% id = "01HA0GPJ8B5QB6DF8YVTYC2HJY"
+ I can imagine parallelization existing on many levels here.
@ -131,7 +131,7 @@
- because we're using a set, the computation is never duplicated; remember that if an answer has already been memoized, it does not spawn a task and instead returns the answer immediately
% id = "01HA0GPJ8B0V2VJMAV1YCQ19Q8"
- though this may be hard to do with Rust because, as far as I know, there is no way to suspend a function conditionally? **(needs research.)**
- though this may be hard to do with Rust because, as far as I know, there is no way to suspend a function conditionally? *(needs research.)*
% id = "01HA0GPJ8BBREEJCJRWPJJNR3N"
- once there are no more tasks in the queue, we're done compiling

View file

@ -14,7 +14,7 @@ legacy of today's systems
the project
% id = "01HFP3E77CVPJMQ69305QHRXDH"
+ the design goal is to build a **secure operating system you can trust**
+ the design goal is to build a *secure operating system you can trust*
% id = "01HFP3E77CFCF260WBQWTBNPP7"
- gone will be the days where you download an executable from the Internet and have no idea
@ -25,7 +25,7 @@ legacy of today's systems
you have to inspect, the better
% id = "01HFP3E77CGBD4ZQ2JN4Z761VE"
+ **NOTE:** at this point shelter is nothing more than an incomplete design. OS development is
+ *NOTE:* at this point shelter is nothing more than an incomplete design. OS development is
something I wanna get into but haven't enough time to research everything as of now, therefore
I'm jotting down my ideas here

View file

@ -77,14 +77,14 @@
- from my analyses when building [Yarnbox][branch:programming/projects/yarnbox], most chunks of bytecode don't exceed 4096 bytes
% id = "01HA4KNTTKW393XV8CPZSW2J7H"
+ the engine loads your bytecode *mostly* verbatim, so we could include a recognizable signature at the end of every script
+ the engine loads your bytecode _mostly_ verbatim, so we could include a recognizable signature at the end of every script
% id = "01HA4NZ9DADP5E842D1630NH19"
- I imagine we could use a short string of bytes that's unlikely to collide with anything yet fast to search for. probably 16 bytes (128 bits) would be enough but we can experiment
with less
% id = "01HA4NZ9DA4HADG7SFDTPKKJ11"
- I say it loads your code *mostly* verbatim because it actually parses the bytecode to translate archive object indices to
- I say it loads your code _mostly_ verbatim because it actually parses the bytecode to translate archive object indices to
% id = "01HA4NZ9DAW2KEDX62E810PA03"
- since we can't reallocate memory, we'll have to always preallocate all 65536 bytes - but 64 KiB isn't that much in the first place,

View file

@ -7,7 +7,7 @@
- I don't have UE installed on my home computer yet, so you'll have to take my word for a lot of these things
% id = "01H8Y0CKD1VENHWERX7CTGCE6M"
- anyways back to your regularly scheduled bullet points, *\*ahem\**
- anyways back to your regularly scheduled bullet points, _\*ahem\*_
% id = "01H8Y0CKD1QHMTGFCK2RC3NPDV"
+ Blueprint, _my arch nemesis!_
@ -171,7 +171,7 @@ flow and introduces control flow into the mix
+ which is a portmanteau of Blueprint and spaghetti.
% id = "01H8Y427B0PJN75GA33S9CZJYH"
- I can't believe I remembered the spelling of that word. *portmanteau*.
- I can't believe I remembered the spelling of that word. _portmanteau_.
% id = "01H8YT7R15B3Z3T486DB53TR51"
- there are plugins on the marketplace that solve this, but I refuse to believe Epic Games doesn't have this problem themselves
@ -199,7 +199,7 @@ flow and introduces control flow into the mix
- the biggest offender here being that hard references are the default
% id = "01H8Y427B17K7219TPH8VRFZ96"
- thus you can add a Mesh Component to your Blueprint and it'll load *the entire mesh with all the textures and skeleton and everything* when you wanna tweak a variable or some logic in the event graph
- thus you can add a Mesh Component to your Blueprint and it'll load _the entire mesh with all the textures and skeleton and everything_ when you wanna tweak a variable or some logic in the event graph
% id = "01H8Y427B1QNZHJ5Z8W0QNFYPE"
- and not asynchronously in the background, you will have to _wait_ for it to finish loading. which is pretty annoying

View file

@ -21,6 +21,7 @@
% id = "01HP1FESY38JWXNSJKPZNHP33Q"
+ the interface you have to implement is:
```cpp
struct IFixer
{
@ -64,6 +65,7 @@
% id = "01HP1FESY30HDA6JNR323GXD3D"
- minimal example:
```cpp
auto Message = FTokenizedMessage::Create(
EMessageVerbosity::Error,

View file

@ -23,11 +23,13 @@
% 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"
@ -44,13 +46,17 @@ let's disassemble it piece by piece.
% 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"

View file

@ -8,14 +8,14 @@
% id = "treehouse/changelog"
content.link = "treehouse/changelog"
+ what's going on inside the house?
+ #### :folder: what's going on inside the house?
% id = "treehouse/issues"
content.link = "treehouse/issues"
+ issues
+ #### :folder: issues
% id = "treehouse/credits"
+ credits
+ #### :page: credits
% id = "01HPSX8W0WQF2BXQ9RMME20628"
- **liquidex's treehouse** is brought to you by…
@ -33,7 +33,7 @@
+ [my girlfriend][def:person/vixenka], who's been supporting me in making this website more friendly towards stubborn people
% id = "01HPSX8W0WMX4YXY9WMAM40QKH"
- she says every part of this sentence apart from "*my girlfriend*" is untrue. I think I know better, so deal with it!
- she says every part of this sentence apart from "_my girlfriend_" is untrue. I think I know better, so deal with it!
% id = "01HPW0KZ90S1SV6ZPSDYM42DG8"
- special thanks to...

View file

@ -62,7 +62,8 @@
- **revision 7:** the main page is now a lot more colorful :rainbow:
% id = "01HRF1YCJ6S5YBSFC13QPWKF3X"
- <style>
- ``` =html
<style>
span.treehouse\/changelog\:liquidex-brand-color {
display: inline-block;
width: 0.8em;
@ -77,8 +78,10 @@
&.blue { background-color: var(--liquidex-brand-blue); }
}
</style>
each major content category now has an icon and a *liquidex brand color™*
<span class="treehouse/changelog:liquidex-brand-color red"></span><span class="treehouse/changelog:liquidex-brand-color yellow"></span><span class="treehouse/changelog:liquidex-brand-color green"></span><span class="treehouse/changelog:liquidex-brand-color blue"></span>
```
each major content category now has an icon and a _liquidex brand color™_
[]{class="treehouse/changelog:liquidex-brand-color red"}[]{class="treehouse/changelog:liquidex-brand-color yellow"}[]{class="treehouse/changelog:liquidex-brand-color green"}[]{class="treehouse/changelog:liquidex-brand-color blue"}
assigned to it
% id = "01HRF1YCJ602RS1C4E7W7DVHZB"
@ -98,14 +101,14 @@
% id = "01HR300JYTPHPKD0VCDGWT01HE"
- even as I'm writing this, I'm trembling over how half my website is probably gonna break after this refactor, due to me having it written in pure JavaScript rather than TypeScript, but eh, fuck it.
*reality is not always what you want it to be.*
_reality is not always what you want it to be._
% id = "01HR30AHZMS63DA3W3293HZ6WN"
- just know that I'm writing this not because I think the standards are good, but because one vendor not following them just results in extra work on our part - the developers' part -
doing more work than is actually really necessary to accomplish a certain task.
% id = "01HR30AHZME866NBYNXZ15CR09"
- using the browser's built-in capabilities is *good.*
- using the browser's built-in capabilities is _good._
you're just making me (and hundreds of thousands of other developers) write a shitty, slow version of what the browser could do by default, had it been following Web standards.
% id = "01HR30XM8432QV9NHA26FJQX18"
@ -124,7 +127,7 @@
- this page will show you all the updates that have been happening since your last visit
% id = "01HQ94FDZKXFRMCH5NXXAB146E"
+ it will also lightly nag you whenever there are new posts with a <span class="badge red">1</span> badge
+ it will also lightly nag you whenever there are new posts with a [1]{.badge .red} badge
% id = "01HQ94FDZK5TJDM3CMNKQKES6Z"
- if that's too annoying for you, it's easy to disable - scroll down on the [news page][page:treehouse/new] and there's a (collapsed by default) settings section for the page
@ -178,7 +181,7 @@
+ fixed "hey expand me" chevrons sometimes shifting layout when a branch is expanded
% id = "01HP20D2R4W85SKCJX2R10RHHC"
- yeah, *these* ones - like the one that just disappeared when you clicked on the branch above
- yeah, _these_ ones - like the one that just disappeared when you clicked on the branch above
% id = "01HP20RMB3RX6HSBHKM6FVDS44"
+ [`/b` endpoint](/b) is now used for shorter links. it also generates OpenGraph metadata so that Your Favorite Messaging Platform can display the linked branch's content

View file

@ -29,7 +29,7 @@ I told him there's no reason to do that in modern C++, because `std::unique_ptr`
[read][page:programming/languages/cxx/shared-unique-ptr-deleter]
% id = "01J0KRPMV7SS48B64BFCJZK7VQ"
- it's updatin' time! I took some time to clean up old pages and update my *about me*.\
- it's updatin' time! I took some time to clean up old pages and update my _about me_.\
over time I've been learning how to write content on the treehouse effectively, and the new about me reflects that.
### about me (version 2)
@ -51,7 +51,7 @@ after all, aren't we just dealing with a bunch of code running on the computer?
### 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]
[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.
@ -68,7 +68,7 @@ after all, aren't we just dealing with a bunch of code running on the computer?
[read why I like it so much][page:programming/languages/lua]
% id = "01HR9ZTS8RS4VJNJYSNRQYSKHZ"
- sidebars! also known as, *"enjoying the main content? how about I distract you from it so that you can't focus!"*\
- sidebars! also known as, _"enjoying the main content? how about I distract you from it so that you can't focus!"_\
seriously though. I don't like them.
### design: sidebars
@ -78,8 +78,8 @@ seriously though. I don't like them.
% id = "01HQ8KV8T8GRCVFDJ3EP6QE163"
- I started a branch on user interface and user experience design, because I was working with mintty at work and had some thoughts about it.
"why does mintty always feel so *out of place* compared to `cmd.exe`?"
"why does mintty always feel so _out of place_ compared to `cmd.exe`?"
### liquidex's treehouse: design
[read: *on digital textures*][page:design/digital-textures] [go to branch][page:design]
[read: _on digital textures_][page:design/digital-textures] [go to branch][page:design]

View file

@ -9,7 +9,9 @@ treehouse-format = { workspace = true }
anyhow = "1.0.75"
axum = "0.7.4"
base64 = "0.21.7"
blake3 = "1.5.3"
chrono = "0.4.35"
clap = { version = "4.3.22", features = ["derive"] }
codespan-reporting = "0.11.1"
copy_dir = "0.1.3"
@ -18,9 +20,10 @@ handlebars = "4.3.7"
http-body = "1.0.0"
image = "0.24.8"
indexmap = { version = "2.2.6", features = ["serde"] }
jotdown = { version = "0.4.1", default-features = false }
log = { workspace = true }
pulldown-cmark = { version = "0.9.3", default-features = false }
rand = "0.8.5"
regex = "1.10.3"
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.105"
tokio = { version = "1.32.0", features = ["full"] }
@ -29,6 +32,6 @@ tower-livereload = "0.9.2"
walkdir = "2.3.3"
ulid = "1.0.0"
url = "2.5.0"
base64 = "0.21.7"
chrono = "0.4.35"
regex = "1.10.3"
# TODO djot: To remove once migration to Djot is complete.
pulldown-cmark = { version = "0.9.3", default-features = false }

View file

@ -22,6 +22,10 @@ pub struct Config {
/// preferred way of setting this in production, so as not to clobber treehouse.toml.)
pub site: String,
/// Which markup to use when generating trees.
/// TODO djot: Remove this once we transition to Djot fully.
pub markup: Markup,
/// User-defined keys.
pub user: HashMap<String, String>,
@ -90,6 +94,12 @@ pub struct JavaScript {
pub import_roots: Vec<ImportRoot>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub enum Markup {
Markdown,
Djot,
}
impl Config {
pub fn load(path: &Path) -> anyhow::Result<Self> {
let string = std::fs::read_to_string(path).context("cannot read config file")?;

View file

@ -1,6 +1,7 @@
use std::fmt::{self, Display, Write};
pub mod breadcrumbs;
mod djot;
pub mod highlight;
mod markdown;
pub mod navmap;

View file

@ -0,0 +1,681 @@
//! Djot -> HTML renderer adapted from the one in jotdown.
//! Made concrete to avoid generic hell, with added treehouse-specific features.
use std::fmt::Write;
use std::ops::Range;
use codespan_reporting::diagnostic::Diagnostic;
use codespan_reporting::diagnostic::Label;
use codespan_reporting::diagnostic::LabelStyle;
use codespan_reporting::diagnostic::Severity;
use jotdown::Alignment;
use jotdown::Container;
use jotdown::Event;
use jotdown::LinkType;
use jotdown::ListKind;
use jotdown::OrderedListNumbering::*;
use jotdown::SpanLinkType;
use crate::config::Config;
use crate::config::ConfigDerivedData;
use crate::state::FileId;
use crate::state::Treehouse;
use super::highlight::highlight;
/// [`Render`] implementor that writes HTML output.
pub struct Renderer<'a> {
pub config: &'a Config,
pub config_derived_data: &'a mut ConfigDerivedData,
pub treehouse: &'a mut Treehouse,
pub file_id: FileId,
pub page_id: String,
}
impl<'a> Renderer<'a> {
pub fn render(self, events: &[(Event, Range<usize>)], out: &mut String) {
let mut writer = Writer {
renderer: self,
raw: Raw::None,
code_block: None,
img_alt_text: 0,
list_tightness: vec![],
not_first_line: false,
ignore_next_event: false,
};
for (event, range) in events {
writer
.render_event(event, range.clone(), out)
.expect("formatting event into string should not fail");
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
enum Raw {
#[default]
None,
Html,
Other,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum CodeBlockKind {
PlainText,
SyntaxHighlight,
LiterateProgram {
program_name: String,
placeholder_pic_id: Option<String>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct CodeBlock<'a> {
kind: CodeBlockKind,
language: &'a str,
}
struct Writer<'a> {
renderer: Renderer<'a>,
raw: Raw,
code_block: Option<CodeBlock<'a>>,
img_alt_text: usize,
list_tightness: Vec<bool>,
not_first_line: bool,
ignore_next_event: bool,
}
impl<'a> Writer<'a> {
fn render_event(
&mut self,
e: &Event<'a>,
range: Range<usize>,
out: &mut String,
) -> std::fmt::Result {
if let Event::Start(Container::Footnote { label: _ }, ..) = e {
self.renderer.treehouse.diagnostics.push(Diagnostic {
severity: Severity::Error,
code: Some("djot".into()),
message: "Djot footnotes are not supported".into(),
labels: vec![Label {
style: LabelStyle::Primary,
file_id: self.renderer.file_id,
range: range.clone(),
message: "".into(),
}],
notes: vec![],
})
}
if matches!(&e, Event::Start(Container::LinkDefinition { .. }, ..)) {
self.ignore_next_event = true;
return Ok(());
}
if matches!(&e, Event::End(Container::LinkDefinition { .. })) {
self.ignore_next_event = false;
return Ok(());
}
// Completely omit section events. The treehouse's structure contains linkable ids in
// branches instead.
if matches!(
&e,
Event::Start(Container::Section { .. }, _) | Event::End(Container::Section { .. })
) {
return Ok(());
}
if self.ignore_next_event {
return Ok(());
}
match e {
Event::Start(c, attrs) => {
if c.is_block() && self.not_first_line {
out.push('\n');
}
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
return Ok(());
}
match &c {
Container::Blockquote => out.push_str("<blockquote"),
Container::List { kind, tight } => {
self.list_tightness.push(*tight);
match kind {
ListKind::Unordered | ListKind::Task => out.push_str("<ul"),
ListKind::Ordered {
numbering, start, ..
} => {
out.push_str("<ol");
if *start > 1 {
write!(out, r#" start="{}""#, start)?;
}
if let Some(ty) = match numbering {
Decimal => None,
AlphaLower => Some('a'),
AlphaUpper => Some('A'),
RomanLower => Some('i'),
RomanUpper => Some('I'),
} {
write!(out, r#" type="{}""#, ty)?;
}
}
}
}
Container::ListItem | Container::TaskListItem { .. } => {
out.push_str("<li");
}
Container::DescriptionList => out.push_str("<dl"),
Container::DescriptionDetails => out.push_str("<dd"),
Container::Footnote { .. } => unreachable!(),
Container::Table => out.push_str("<table"),
Container::TableRow { .. } => out.push_str("<tr"),
Container::Section { .. } => {}
Container::Div { .. } => out.push_str("<div"),
Container::Paragraph => {
if matches!(self.list_tightness.last(), Some(true)) {
return Ok(());
}
out.push_str("<p");
}
Container::Heading { level, .. } => write!(out, "<h{}", level)?,
Container::TableCell { head: false, .. } => out.push_str("<td"),
Container::TableCell { head: true, .. } => out.push_str("<th"),
Container::Caption => out.push_str("<caption"),
Container::DescriptionTerm => out.push_str("<dt"),
Container::CodeBlock { language } => {
if let Some(program) = attrs.get(":program") {
self.code_block = Some(CodeBlock {
kind: CodeBlockKind::LiterateProgram {
program_name: program.parts().collect(),
placeholder_pic_id: attrs
.get(":placeholder")
.map(|value| value.parts().collect()),
},
language,
});
out.push_str("<th-literate-program");
} else {
self.code_block = Some(CodeBlock {
kind: match self.renderer.config.syntaxes.contains_key(*language) {
true => CodeBlockKind::SyntaxHighlight,
false => CodeBlockKind::PlainText,
},
language,
});
out.push_str("<pre");
}
}
Container::Span | Container::Math { .. } => out.push_str("<span"),
Container::Link(dst, ty) => {
if matches!(ty, LinkType::Span(SpanLinkType::Unresolved)) {
out.push_str("<a");
if let Some(resolved) = self.resolve_link(dst) {
out.push_str(r#" href=""#);
write_attr(&resolved, out);
out.push('"');
}
} else {
out.push_str(r#"<a href=""#);
if matches!(ty, LinkType::Email) {
out.push_str("mailto:");
}
write_attr(dst, out);
out.push('"');
}
}
Container::Image(..) => {
self.img_alt_text += 1;
if self.img_alt_text == 1 {
out.push_str(r#"<img class="pic""#);
} else {
return Ok(());
}
}
Container::Verbatim => out.push_str("<code"),
Container::RawBlock { format } | Container::RawInline { format } => {
self.raw = if format == &"html" {
Raw::Html
} else {
Raw::Other
};
return Ok(());
}
Container::Subscript => out.push_str("<sub"),
Container::Superscript => out.push_str("<sup"),
Container::Insert => out.push_str("<ins"),
Container::Delete => out.push_str("<del"),
Container::Strong => out.push_str("<strong"),
Container::Emphasis => out.push_str("<em"),
Container::Mark => out.push_str("<mark"),
Container::LinkDefinition { .. } => return Ok(()),
}
for (key, value) in attrs
.into_iter()
.filter(|(a, _)| !(*a == "class" || a.starts_with(':')))
{
write!(out, r#" {}=""#, key)?;
value.parts().for_each(|part| write_attr(part, out));
out.push('"');
}
if attrs.into_iter().any(|(a, _)| a == "class")
|| matches!(
c,
Container::Div { class } if !class.is_empty())
|| matches!(c, |Container::Math { .. }| Container::List {
kind: ListKind::Task,
..
} | Container::TaskListItem { .. })
{
out.push_str(r#" class=""#);
let mut first_written = false;
if let Some(cls) = match c {
Container::List {
kind: ListKind::Task,
..
} => Some("task-list"),
Container::TaskListItem { checked: false } => Some("unchecked"),
Container::TaskListItem { checked: true } => Some("checked"),
Container::Math { display: false } => Some("math inline"),
Container::Math { display: true } => Some("math display"),
_ => None,
} {
first_written = true;
out.push_str(cls);
}
for class in attrs
.into_iter()
.filter(|(a, _)| a == &"class")
.map(|(_, cls)| cls)
{
if first_written {
out.push(' ');
}
first_written = true;
class.parts().for_each(|part| write_attr(part, out));
}
// div class goes after classes from attrs
if let Container::Div { class } = c {
if !class.is_empty() {
if first_written {
out.push(' ');
}
out.push_str(class);
}
}
out.push('"');
}
match c {
Container::TableCell { alignment, .. }
if !matches!(alignment, Alignment::Unspecified) =>
{
let a = match alignment {
Alignment::Unspecified => unreachable!(),
Alignment::Left => "left",
Alignment::Center => "center",
Alignment::Right => "right",
};
write!(out, r#" style="text-align: {};">"#, a)?;
}
Container::CodeBlock { language } => {
if language.is_empty() {
out.push_str("><code>");
} else {
let code_block = self.code_block.as_ref().unwrap();
if let CodeBlockKind::LiterateProgram { program_name, .. } =
&code_block.kind
{
out.push_str(r#" data-program=""#);
write_attr(&self.renderer.page_id, out);
out.push(':');
write_attr(program_name, out);
out.push('"');
if *language == "output" {
out.push_str(r#" data-mode="output""#);
} else {
out.push_str(r#" data-mode="input""#);
}
}
out.push('>');
if let CodeBlockKind::LiterateProgram {
placeholder_pic_id: Some(placeholder_pic_id),
..
} = &code_block.kind
{
out.push_str(
r#"<img class="placeholder-image" loading="lazy" src=""#,
);
let filename = self.renderer.config.pics.get(placeholder_pic_id);
let pic_url = filename
.and_then(|filename| {
self.renderer
.config_derived_data
.static_urls
.get(&format!("pic/{filename}"))
.ok()
})
.unwrap_or_default();
write_attr(&pic_url, out);
out.push('"');
let image_size = filename.and_then(|filename| {
self.renderer
.config_derived_data
.image_size(&format!("static/pic/{filename}"))
});
if let Some(image_size) = image_size {
write!(
out,
r#" width="{}" height="{}""#,
image_size.width, image_size.height
)?;
}
out.push('>');
}
if let (CodeBlockKind::LiterateProgram { .. }, "output") =
(&code_block.kind, *language)
{
out.push_str(r#"<pre class="placeholder-console">"#);
} else {
out.push_str(r#"<code class="language-"#);
write_attr(language, out);
if self.renderer.config.syntaxes.contains_key(*language) {
out.push_str(" th-syntax-highlighting");
}
out.push_str(r#"">"#);
}
}
}
Container::Image(..) => {
if self.img_alt_text == 1 {
out.push_str(r#" alt=""#);
}
}
Container::Math { display } => {
out.push_str(if *display { r#">\["# } else { r#">\("# });
}
_ => out.push('>'),
}
}
Event::End(c) => {
if c.is_block_container() {
out.push('\n');
}
if self.img_alt_text > 0 && !matches!(c, Container::Image(..)) {
return Ok(());
}
match c {
Container::Blockquote => out.push_str("</blockquote>"),
Container::List { kind, .. } => {
self.list_tightness.pop();
match kind {
ListKind::Unordered | ListKind::Task => out.push_str("</ul>"),
ListKind::Ordered { .. } => out.push_str("</ol>"),
}
}
Container::ListItem | Container::TaskListItem { .. } => {
out.push_str("</li>");
}
Container::DescriptionList => out.push_str("</dl>"),
Container::DescriptionDetails => out.push_str("</dd>"),
Container::Footnote { .. } => unreachable!(),
Container::Table => out.push_str("</table>"),
Container::TableRow { .. } => out.push_str("</tr>"),
Container::Section { .. } => {}
Container::Div { .. } => out.push_str("</div>"),
Container::Paragraph => {
if matches!(self.list_tightness.last(), Some(true)) {
return Ok(());
}
out.push_str("</p>");
}
Container::Heading { level, .. } => write!(out, "</h{}>", level)?,
Container::TableCell { head: false, .. } => out.push_str("</td>"),
Container::TableCell { head: true, .. } => out.push_str("</th>"),
Container::Caption => out.push_str("</caption>"),
Container::DescriptionTerm => out.push_str("</dt>"),
Container::CodeBlock { language } => {
let code_block = self.code_block.take().unwrap();
out.push_str(match &code_block.kind {
CodeBlockKind::PlainText | CodeBlockKind::SyntaxHighlight => {
"</code></pre>"
}
CodeBlockKind::LiterateProgram { .. } if *language == "output" => {
"</pre></th-literate-program>"
}
CodeBlockKind::LiterateProgram { .. } => {
"</code></th-literate-program>"
}
});
}
Container::Span => out.push_str("</span>"),
Container::Link(..) => out.push_str("</a>"),
Container::Image(src, link_type) => {
if self.img_alt_text == 1 {
if !src.is_empty() {
out.push_str(r#"" src=""#);
if let SpanLinkType::Unresolved = link_type {
if let Some(resolved) = self.resolve_link(src) {
write_attr(&resolved, out);
} else {
write_attr(src, out);
}
} else {
write_attr(src, out);
}
}
out.push_str(r#"">"#);
}
self.img_alt_text -= 1;
}
Container::Verbatim => out.push_str("</code>"),
Container::Math { display } => {
out.push_str(if *display {
r#"\]</span>"#
} else {
r#"\)</span>"#
});
}
Container::RawBlock { .. } | Container::RawInline { .. } => {
self.raw = Raw::None;
}
Container::Subscript => out.push_str("</sub>"),
Container::Superscript => out.push_str("</sup>"),
Container::Insert => out.push_str("</ins>"),
Container::Delete => out.push_str("</del>"),
Container::Strong => out.push_str("</strong>"),
Container::Emphasis => out.push_str("</em>"),
Container::Mark => out.push_str("</mark>"),
Container::LinkDefinition { .. } => unreachable!(),
}
}
Event::Str(s) => match self.raw {
Raw::None if self.img_alt_text > 0 => write_attr(s, out),
Raw::None => {
let syntax = self.code_block.as_ref().and_then(|code_block| {
self.renderer.config.syntaxes.get(code_block.language)
});
if let Some(syntax) = syntax {
// TODO djot: make highlight infallible
highlight(out, syntax, s).map_err(|_| std::fmt::Error)?;
} else {
write_text(s, out);
}
}
Raw::Html => out.push_str(s),
Raw::Other => {}
},
Event::FootnoteReference(_label) => {
self.renderer.treehouse.diagnostics.push(Diagnostic {
severity: Severity::Error,
code: Some("djot".into()),
message: "Djot footnotes are unsupported".into(),
labels: vec![Label {
style: LabelStyle::Primary,
file_id: self.renderer.file_id,
range,
message: "".into(),
}],
notes: vec![],
});
}
Event::Symbol(sym) => {
if let Some(filename) = self.renderer.config.emoji.get(sym.as_ref()) {
let branch_id = self
.renderer
.treehouse
.branches_by_named_id
.get(&format!("emoji/{sym}"))
.copied();
if let Some(branch) =
branch_id.map(|id| self.renderer.treehouse.tree.branch(id))
{
out.push_str(r#"<a href=""#);
write_attr(&self.renderer.config.site, out);
out.push_str("/b?");
write_attr(&branch.attributes.id, out);
out.push_str(r#"">"#)
}
let url = self
.renderer
.config_derived_data
.static_urls
.get(&format!("emoji/{filename}"))
.unwrap_or_default();
// TODO: this could do with better alt text
write!(
out,
r#"<img data-cast="emoji" title=":{sym}:" alt="{sym}" src=""#
)?;
write_attr(&url, out);
out.push('"');
if let Some(image_size) = self
.renderer
.config_derived_data
.image_size(&format!("static/emoji/{filename}"))
{
write!(
out,
r#" width="{}" height="{}""#,
image_size.width, image_size.height
)?;
}
out.push('>');
if branch_id.is_some() {
out.push_str("</a>");
}
} else {
write!(
out,
r#"<span class="th-emoji-unknown" title="this emoji does not exist… yet!">:{sym}:</span>"#,
)?
}
}
Event::LeftSingleQuote => out.push(''),
Event::RightSingleQuote => out.push(''),
Event::LeftDoubleQuote => out.push('“'),
Event::RightDoubleQuote => out.push('”'),
Event::Ellipsis => out.push('…'),
Event::EnDash => out.push(''),
Event::EmDash => out.push('—'),
Event::NonBreakingSpace => out.push_str("&nbsp;"),
Event::Hardbreak => out.push_str("<br>\n"),
Event::Softbreak => out.push('\n'),
Event::Escape | Event::Blankline => {}
Event::ThematicBreak(attrs) => {
if self.not_first_line {
out.push('\n');
}
out.push_str("<hr");
for (a, v) in attrs {
write!(out, r#" {}=""#, a)?;
v.parts().for_each(|part| write_attr(part, out));
out.push('"');
}
out.push('>');
}
}
self.not_first_line = true;
Ok(())
}
fn resolve_link(&self, link: &str) -> Option<String> {
let Renderer {
config,
config_derived_data,
treehouse,
..
} = &self.renderer;
link.split_once(':').and_then(|(kind, linked)| match kind {
"def" => config.defs.get(linked).cloned(),
"branch" => treehouse
.branches_by_named_id
.get(linked)
.map(|&branch_id| {
format!(
"{}/b?{}",
config.site,
treehouse.tree.branch(branch_id).attributes.id
)
}),
"page" => Some(config.page_url(linked)),
"pic" => config.pics.get(linked).and_then(|filename| {
config_derived_data
.static_urls
.get(&format!("pic/{filename}"))
.ok()
}),
_ => None,
})
}
}
fn write_text(s: &str, out: &mut String) {
write_escape(s, false, out)
}
fn write_attr(s: &str, out: &mut String) {
write_escape(s, true, out)
}
fn write_escape(mut s: &str, escape_quotes: bool, out: &mut String) {
let mut ent = "";
while let Some(i) = s.find(|c| {
match c {
'<' => Some("&lt;"),
'>' => Some("&gt;"),
'&' => Some("&amp;"),
'"' if escape_quotes => Some("&quot;"),
_ => None,
}
.map_or(false, |s| {
ent = s;
true
})
}) {
out.push_str(&s[..i]);
out.push_str(ent);
s = &s[i + 1..];
}
out.push_str(s);
}

View file

@ -1,11 +1,12 @@
use std::{borrow::Cow, fmt::Write};
use jotdown::Render;
use pulldown_cmark::{BrokenLink, LinkType};
use treehouse_format::pull::BranchKind;
use crate::{
cli::Paths,
config::{Config, ConfigDerivedData},
config::{Config, ConfigDerivedData, Markup},
html::EscapeAttribute,
state::{FileId, Treehouse},
tree::{
@ -14,7 +15,7 @@ use crate::{
},
};
use super::{markdown, EscapeHtml};
use super::{djot, markdown, EscapeHtml};
pub fn branch_to_html(
s: &mut String,
@ -99,7 +100,7 @@ pub fn branch_to_html(
s.push_str("<th-bp></th-bp>");
let raw_block_content = &source.input()[branch.content.clone()];
let mut final_markdown = String::with_capacity(raw_block_content.len());
let mut final_markup = String::with_capacity(raw_block_content.len());
for line in raw_block_content.lines() {
// Bit of a jank way to remove at most branch.indent_level spaces from the front.
let mut space_count = 0;
@ -111,8 +112,8 @@ pub fn branch_to_html(
}
}
final_markdown.push_str(&line[space_count..]);
final_markdown.push('\n');
final_markup.push_str(&line[space_count..]);
final_markup.push('\n');
}
let broken_link_callback = &mut |broken_link: BrokenLink<'_>| {
@ -156,25 +157,48 @@ pub fn branch_to_html(
}
};
if branch.attributes.template {
final_markdown = mini_template::render(config, treehouse, paths, &final_markdown);
final_markup = mini_template::render(config, treehouse, paths, &final_markup);
}
let markdown_parser = pulldown_cmark::Parser::new_with_broken_link_callback(
&final_markdown,
{
use pulldown_cmark::Options;
Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES
},
Some(broken_link_callback),
);
s.push_str("<th-bc>");
markdown::push_html(
s,
treehouse,
config,
config_derived_data,
treehouse.tree_path(file_id).expect(".tree file expected"),
markdown_parser,
);
match config.markup {
Markup::Markdown => {
let markdown_parser = pulldown_cmark::Parser::new_with_broken_link_callback(
&final_markup,
{
use pulldown_cmark::Options;
Options::ENABLE_STRIKETHROUGH | Options::ENABLE_TABLES
},
Some(broken_link_callback),
);
markdown::push_html(
s,
treehouse,
config,
config_derived_data,
treehouse.tree_path(file_id).expect(".tree file expected"),
markdown_parser,
)
}
Markup::Djot => {
let events: Vec<_> = jotdown::Parser::new(&final_markup)
.into_offset_iter()
.collect();
djot::Renderer {
page_id: treehouse
.tree_path(file_id)
.expect(".tree file expected")
.to_owned(),
config,
config_derived_data,
treehouse,
file_id,
}
.render(&events, s);
}
};
let branch = treehouse.tree.branch(branch_id);
if let Content::Link(link) = &branch.attributes.content {
write!(
s,

View file

@ -603,6 +603,11 @@ th-emoji-tooltip p {
line-height: 1;
}
.th-emoji-unknown {
text-decoration: 1px underline var(--error-color);
cursor: help;
}
/* Funny joke */
@keyframes hello-there {

View file

@ -1,5 +1,6 @@
.tileset-cardinal-directions-demo th-bc {
& .horizontal-tile-strip {
/* TODO djot: remove the first rule */
& .horizontal-tile-strip>p {
display: flex;
flex-direction: row;
justify-content: center;

View file

@ -5,6 +5,9 @@
# This variable can also be set using the TREEHOUSE_SITE environment variable.
site = ""
# TODO djot: Remove once transition is over.
markup = "Djot"
[user]
title = "liquidex's treehouse"
author = "liquidex"