liquidex
7169e65244
works pretty much the same as our previous `?cache` parameter, but is implemented in a less ad-hoc way
362 lines
16 KiB
Plaintext
362 lines
16 KiB
Plaintext
%% title = "Hotland - Bad Opinion Zone"
|
||
|
||
% id = "01HBTSXTTAAAHGKD4TZZW14KFK"
|
||
template = true
|
||
- ``` =html
|
||
<style>
|
||
@font-face {
|
||
font-family: "Determination Sans";
|
||
src: url('/static/font/DTM-Sans.otf?v=b3-4fe96c14');
|
||
}
|
||
|
||
.undertale-save-box {
|
||
--icon-heart: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAQ0lEQVQY042P0QoAMARF7/X/3zzTsoW0nBRd5wHAUCs9DYQTM/rwMEssWCmrUodgwEjiPfIjSPqmEYj4chVZdmjEJGzAMxYAKMRofgAAAABJRU5ErkJggg==');
|
||
|
||
border: 6px solid white;
|
||
background: black;
|
||
color: white;
|
||
|
||
font-family: "Determination Sans";
|
||
font-size: 20pt;
|
||
line-height: 1;
|
||
|
||
width: 424px;
|
||
height: 174px;
|
||
padding: 20px 24px 17px 24px;
|
||
|
||
display: flex;
|
||
flex-direction: column;
|
||
|
||
&>ul.row {
|
||
padding-left: 0;
|
||
margin-left: 0;
|
||
|
||
display: flex;
|
||
flex-direction: row;
|
||
|
||
&.player-info { padding-bottom: 14px; }
|
||
&.buttons { margin-top: auto; }
|
||
|
||
&>li {
|
||
display: block;
|
||
|
||
&.player-name { width: 146px; }
|
||
&.love { width: 138px; }
|
||
&.button-save, &.button-return { width: 180px; }
|
||
}
|
||
|
||
&.buttons {
|
||
&>li {
|
||
padding-left: 30px;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
image-rendering: pixelated;
|
||
background-image: var(--icon-heart);
|
||
background-repeat: no-repeat;
|
||
background-size: 18px;
|
||
background-position: 2px 4px;
|
||
}
|
||
|
||
&>a {
|
||
text-decoration: none;
|
||
color: white;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
&.file-saved {
|
||
color: #ff0;
|
||
}
|
||
}
|
||
</style>
|
||
|
||
<div class="undertale-save-box">
|
||
<ul class="row player-info">
|
||
<li class="player-name">SIGSEGV</li>
|
||
<li class="love">LV 1</li>
|
||
<li class="play-time">0:00</li>
|
||
</ul>
|
||
<ul class="row">
|
||
<li class="save-point">Hotland – Bad Opinion Zone</li>
|
||
</ul>
|
||
<ul class="row buttons">
|
||
<li class="button-save">Save</li>
|
||
<li class="button-return"><a href="/">Return</a></li>
|
||
</ul>
|
||
</div>
|
||
```
|
||
|
||
% id = "01HBTSXTTAMWJ2BM6395YS72FN"
|
||
+ :page: log verbosity levels are stupid
|
||
|
||
% id = "01HBTSXTTA946PJ04NN50P393T"
|
||
- because when should you use any log level other than `error`, `warn`, or `info`?
|
||
|
||
% id = "01HBTSXTTAA873BYW2X3499W4P"
|
||
- for debug info that doesn't affect most users, there will always be lower verbosity levels
|
||
than `info`, but in my experience these are *always* extremely specific, and clumping them
|
||
all into `debug` or `trace` doesn't make much sense
|
||
|
||
% id = "01HBTSXTTAQ9S7Q1SSPN0BCG8F"
|
||
+ in that case I believe it is better to declare separate channels that can be filtered individually, and use `info` for everything
|
||
|
||
% id = "01HBTSXTTAHCQ5YH1FZT3WKNTZ"
|
||
- you say `info` sounds wrong for debug info? well,
|
||
|
||
% id = "01HBTSXTTATA4GW13QBC8YMWP1"
|
||
- guess what, it's debug _*info*_
|
||
|
||
% id = "01HBTSXTTANNMHXDGG1M4W70XN"
|
||
- the very concept of logs is to dump a load of (debug) info for later analysis,
|
||
so making some distinction between debug and non-debug info while logs are
|
||
_inherently_ for debugging seems a little silly to me
|
||
|
||
% id = "01HBTSXTTADJCTV9RTWT4NPASS"
|
||
+ use severities instead of verbosities
|
||
|
||
% id = "01HBTSXTTA3KN26DS4QMPSHEEC"
|
||
- `error` is "OH SHIT OH FUCK UHHH"
|
||
|
||
% id = "01HBTSXTTATK9W0TRGM50SKWRE"
|
||
- `warn` is "ding ding ding! something ain't right, but I'll carry on"
|
||
|
||
% id = "01HBTSXTTA8Z5JJHFKH4T88G0B"
|
||
- `info` is just that - info
|
||
|
||
% id = "01HBTSXTTANQXBN7FQQSBXTSB1"
|
||
- and there is really no need for anything lower, because guess what: [`debug` and `trace`](https://docs.rs/tracing/latest/tracing/struct.Level.html)
|
||
and [`Display` and `Verbose` and `VeryVerbose`](https://docs.unrealengine.com/4.26/en-US/API/Runtime/Core/Logging/ELogVerbosity__Type/)
|
||
and what have you - they're all just more `info` for you
|
||
|
||
% id = "01HBTSXTTAE94HEE60GZFBEAW7"
|
||
- and while writing logging code it's always hard to decide which info should be
|
||
stuffed into which verbosity, because which info will be interesting to you at the
|
||
time you read logs depends on what you're looking for at a given moment
|
||
|
||
% id = "01HBTSXTTAKZ3MKD3EJPX84J9N"
|
||
+ this is something I haven't tried yet, but one day I wanna build a logging framework one day that associates severities with whole categories rather than individual log messages
|
||
|
||
% id = "01HBTSXTTAD4TQ0A8JP71KZMXA"
|
||
- but at that point, do we even need severities at all? would a hierarchy of categories suffice?
|
||
|
||
% id = "01HBTSXTTAW160GH44JV3JYV3Y"
|
||
- I have no clue how well that will work but it would be pretty interesting
|
||
|
||
% id = "01HBTSXTTASQDZK2HMCZXR3JJ4"
|
||
+ :page: tracing is better than logging
|
||
|
||
% id = "01HBTSXTTA08SCA313HSFWB13T"
|
||
- information about time spans is very valuable for profiling
|
||
|
||
% id = "01HBTSXTTAAHQ0Q8GY78Z1RYFN"
|
||
- and you do not have to worry about "should I log before I start the operation, or after I end the operation?"
|
||
|
||
% id = "01HBTSXTTAJ6R319P2N6HT54CA"
|
||
+ additionally, traces made of spans are way easier to visualize than tonnes of logs
|
||
|
||
% id = "01HBTSXTTA3M93STVPHA898GM4"
|
||
- if you've never tried [Perfetto](https://ui.perfetto.dev/) or similar tools, I highly recommend giving it a shot
|
||
|
||
% id = "01HBTSXTTAB9WD17FNDE5RYP8Y"
|
||
+ I also imagine visualizing traces live in your CLI could yield a very nice user experience,
|
||
with a visible progress indicator as to what eg. [your compiler][branch:programming/projects/muscript]
|
||
is working on right now at a glance, reassuring you that it is not stuck on some
|
||
`while (true) {}` in constant evaluation
|
||
|
||
% id = "01HBTSXTTA3Q36Y98GSBRVCS3B"
|
||
- perhaps emitting warnings along the road for things that take alarmingly long, so that you
|
||
can keep your build times in check
|
||
|
||
% id = "01HBTSXTTAA89CXD17GBNR3FEQ"
|
||
- though printing to stdout is quite slow, so perhaps limiting the frequency or depth would
|
||
be a worthwhile thing to do
|
||
|
||
% id = "01HCD90XT3G0J8G9Y48H5QT1GJ"
|
||
+ :page: don't use [`RefCell<T>`](https://doc.rust-lang.org/std/cell/struct.RefCell.html)
|
||
|
||
% id = "01HCD90XT3X82R37WTJJABZF4Y"
|
||
+ usually if you have to resort to `RefCell<T>`, it means something is wrong with your architecture
|
||
|
||
% id = "01HCD90XT3QQA9MCB77Q04W148"
|
||
- `Cell<T>` less so but still you should avoid it whenever possible
|
||
|
||
% id = "01HCD90XT36Q0WTZNRCEWZQE1X"
|
||
- `Rc<RefCell<T>>` is an immediate red flag when I read someone's code. it means they don't
|
||
really know what they're doing and are just trying to sidestep the language
|
||
|
||
% id = "01HCD90XT3QNZ7H77Z4QMW66QJ"
|
||
- it's really not that hard to avoid, try bumping your resources up a scope and borrow them
|
||
from there
|
||
|
||
% id = "01HCD90XT32M4F61W5QRRRXG0K"
|
||
+ using `RefCell` has a few disadvantages
|
||
|
||
% id = "01HCD90XT3R214HQM03TSRZV6K"
|
||
+ first, `RefCell` is _literally_ turning off the borrow checker
|
||
|
||
% id = "01HCD90XT3V4K4RKRQXX5BCCRB"
|
||
- or really deferring it until runtime, but then what's the point. Rust's borrowing
|
||
rules are meant to prevent bugs, not cause more of them
|
||
|
||
% id = "01HCD90XT3DVTHP82Z5NAAF7RP"
|
||
+ second, your program can now panic in unexpected places, because it turns out runtime
|
||
borrows can be really hard to predict
|
||
|
||
% id = "01HCD90XT34QD8MCN0BA35AV0F"
|
||
- as evidenced by a random crash I once got while using [druid](https://lib.rs/crates/druid)
|
||
which was caused by a overlapping mutable then immutable borrows at runtime
|
||
|
||
% id = "01HCD90XT39NZ0BSY630MFNG1X"
|
||
- therefore maybe uhhh... don't use it in libraries? :pleading:
|
||
|
||
% id = "01HCD90XT3GT9G16EQDAK76WEC"
|
||
+ third, it incurs a runtime overhead for the borrow checking, which in 99% of cases is
|
||
totally unnecessary
|
||
|
||
% id = "01HCD90XT3V3B1TVPX7GS8SPDQ"
|
||
- (this is more about non-pessimization rather than premature microoptimization, so
|
||
don't yell at me with your "it'll be fast enough" arguments please)
|
||
|
||
% id = "01HCD90XT37138S38DN0V3DKHF"
|
||
+ fourth, and this one is extremely easy to spot - your code becomes really verbose!
|
||
|
||
% id = "01HCD90XT3WNGWD4GA2Y63D42H"
|
||
- yes please, `.borrow()` me everywhere!
|
||
|
||
% id = "01HCD90XT361QQB2YNFBY51N2A"
|
||
- and you end up with lots of temporaries because of the [`Ref<'b, T>`](https://doc.rust-lang.org/std/cell/struct.Ref.html)
|
||
guards you have to keep in scope.
|
||
|
||
% id = "01HCD90XT34K9BREXAKA5FFP3M"
|
||
- remember that you cannot do `&ref_cell.borrow().some_field` because that would
|
||
drop the temporary `Ref<'b, T>` guard after the expression ends
|
||
|
||
% id = "01HCG7KTGGAFS07QYJXZG6WHJJ"
|
||
+ :page: `None` or `Invalid` cases in enums are a bad idea
|
||
|
||
% id = "01HCG7KTGGHWTT1ME9GQ5VPFPR"
|
||
- by having them, you're forcing your users into an API that forces them to think about the
|
||
invalid case every time they read the value.
|
||
|
||
% id = "01HCG7KTGGBJRX5JGQTFD59P4W"
|
||
- you're repeating the million dollar mistake - `null`
|
||
|
||
% id = "01HCG7KTGGYQ8EQB97AMFJPWYK"
|
||
- instead, use an explicit `Option<T>` or `std::optional<T>` or `T?` or ... when you need
|
||
to represent a possibly-invalid case
|
||
|
||
% id = "01HPEMVAH9JZWYPVN53GVFQNQY"
|
||
+ :page: NaNs should crash the program
|
||
|
||
% id = "01HPEMVAH97PDHJJS70SKG5VMN"
|
||
- any time I see a NaN I cry inside knowing how much pain debugging it's gonna be.
|
||
|
||
% id = "01HPEMVAH9Y3W35Y6Z4QMCJ5QM"
|
||
- 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
|
||
|
||
% 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.
|
||
|
||
% id = "01HPEQ01JRMM17Y30BP7ZFKZRJ"
|
||
+ :page: operator overloading is good, but getters and setters are not
|
||
|
||
% id = "01HPEQ01JR57B057439SY90BQ9"
|
||
- 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
|
||
|
||
% id = "01HPEQ01JRBB8Z3P0KFJSR0SJN"
|
||
- 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?
|
||
|
||
% id = "01HPEQ01JRY7R5QGJ2AM762PPN"
|
||
- 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
|
||
|
||
% 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
|
||
|
||
```rokugo
|
||
let AddSub = interface {
|
||
type T
|
||
fun add (a : T) (b : T) : T
|
||
fun subtract (a : T) (b : T) : T
|
||
}
|
||
|
||
fun (+) (a : let T) (b : T) : T
|
||
use AS : AddSub with { T } =
|
||
AS.add a b
|
||
```
|
||
|
||
% 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
|
||
|
||
% 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
|
||
|
||
% id = "01HPEQ01JRGCPT2PGY5HK7HK7F"
|
||
- is `"foobar" - "bar" == "foo"`?
|
||
|
||
% id = "01HPEQ01JR3CVNNACZ6EGQ7NWM"
|
||
- by extension, is `"foofoobarbar" - "bar" == "foofoobar"` or `"foofoobarbar" - "bar" == "foofoo"`?
|
||
|
||
% id = "01HPEQ01JRK25NHG72ZX5XHEEJ"
|
||
- maybe characters are subtracted from the left string one by one? such that `"foobar" - "bar" == "\x04\x0e\xfcbar"` (wtf)
|
||
|
||
% id = "01HPEQ01JR25J5BY54J6RJ0KEC"
|
||
- 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
|
||
|
||
% 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!
|
||
|
||
% id = "01HPEQ01JR0T4C2YC7TE9ZHXHT"
|
||
- what's that? you didn't expect `camera.angle_z` to return a different value every time you access it? oh, well!
|
||
|
||
% id = "01HPEQ01JR2KWGJVP7T4SH1SXD"
|
||
- at least when it's spelled `camera.angle_z()` it suggests that it might do something weird, like access the thread RNG.
|
||
|
||
% id = "01HPEQ01JRDNA35YPYV30CJG42"
|
||
- not to mention all the infinite recursion annoyance that sometimes happens when implementing them manually
|
||
|
||
% 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 e.g. JavaScript, where one mistake can send your call stack down the spiral:
|
||
|
||
```javascript
|
||
class Example {
|
||
#someVariable = "";
|
||
|
||
get someVariable() { return this.someVariable; } // typo!!!!
|
||
set someVariable(value) { this.someVariable = value; } // typo again!!!!!!!!!! dammit!
|
||
}
|
||
```
|
||
|
||
and the error is not caught until runtime.
|
||
|
||
% id = "01HPEQ01JRMMS1B400DP6DV5M9"
|
||
- it's easy to fix but still an annoyance whenever you write a getter/setter pair.
|