Compare commits

...

10 commits

Author SHA1 Message Date
Joe Ardent
e1f5ae35bd add random link for constellation text 2024-03-31 11:37:04 -07:00
Joe Ardent
eadbe5f4d3 Add webring links, plus add counter to home template. 2024-03-31 10:35:20 -07:00
Joe Ardent
ef9d7ac9be make the footer text a little bigger 2024-03-30 16:49:22 -07:00
Joe Ardent
732156dcd6 handle an error from the hit endpoint 2024-03-30 16:23:17 -07:00
Joe Ardent
87afa418b2 add hit counter to page footer 2024-03-30 13:15:52 -07:00
Joe Ardent
c67821256e add comment about loogle 2023-07-31 07:44:31 -07:00
Joe Ardent
7b3cbc9dad tweak 2023-07-31 07:28:17 -07:00
Joe Ardent
51ff082cbf ready to publish 2023-07-30 13:11:29 -07:00
Joe Ardent
ca1c3ea2d9 tweak code style 2023-07-30 13:02:13 -07:00
Joe Ardent
a9f020f4ed fix the styling for 'code' entities 2023-07-30 12:31:13 -07:00
6 changed files with 122 additions and 41 deletions

View file

@ -0,0 +1,16 @@
+++
title = "Hitman: another fine essential sundry service from Nebcorp Heavy Industries and Sundries"
slug = "hitman"
date = "2024-03-31"
draft = true
[taxonomies]
tags = ["software", "sundry", "proclamation", "90s", "hitman", "web"]
+++
# Hitman counts your hits, man.
## The dream
## The reality

View file

@ -1,5 +1,5 @@
+++ +++
title = "Presenting Julids, another fine sundry by Nebcorp Heavy Industries and Sundries" title = "Presenting Julids, another fine sundry from Nebcorp Heavy Industries and Sundries"
slug = "presenting-julids" slug = "presenting-julids"
date = "2023-07-31" date = "2023-07-31"
[taxonomies] [taxonomies]
@ -53,9 +53,12 @@ sqlite> select julid_counter(julid_new());
0 0
``` ```
Intrigued? Confused? Disgusted? Enraged?? Well, read on!
## Julids vs ULIDs ## Julids vs ULIDs
Julids are a drop-in replacement for ULIDs; all Julids are valid ULIDs, but not all ULIDs are valid Julids. Julids are a drop-in replacement for ULIDs: all Julids are valid ULIDs, but not all ULIDs are valid
Julids.
Given their compatibility relationship, Julids and ULIDs must have quite a bit in common, and indeed Given their compatibility relationship, Julids and ULIDs must have quite a bit in common, and indeed
they do: they do:
@ -63,7 +66,7 @@ they do:
* they are 128-bits long * they are 128-bits long
* they are lexicographically sortable * they are lexicographically sortable
* they encode their creation time as the number of milliseconds since the [UNIX * they encode their creation time as the number of milliseconds since the [UNIX
epoch](https://en.wikipedia.org/wiki/Unix_time) epoch](https://en.wikipedia.org/wiki/Unix_time) in their top 48 bits
* their string representation is a 26-character [base-32 * their string representation is a 26-character [base-32
Crockford](https://en.wikipedia.org/wiki/Base32) encoding of their big-endian bytes Crockford](https://en.wikipedia.org/wiki/Base32) encoding of their big-endian bytes
* IDs created within the same millisecond are still meant to sort in their order of creation * IDs created within the same millisecond are still meant to sort in their order of creation
@ -80,13 +83,13 @@ guess a new possibly-valid ULID simply by incrementing an already-known one. And
that sorting will need to read all the way to the end of the ULID for IDs created in the same that sorting will need to read all the way to the end of the ULID for IDs created in the same
millisecond. millisecond.
To address these shortcomings, Julids (Joe's ULIDs) have the following structure: To address these shortcomings, Julids (Joe's[^httm] ULIDs) have the following structure:
![Julid bit structure](./julid.svg) ![Julid bit structure](./julid.svg)
As with ULIDs, the 48 most-significant bits encode the time of creation. Unlike ULIDs, the next 16 As with ULIDs, the 48 most-significant bits encode the time of creation. Unlike ULIDs, the next 16
most-significant bits are not random: they're a monotonic counter for IDs created within the same most-significant bits are not random[^counter idea]: they're a monotonic counter for IDs created
millisecond[^monotonic]. Since it's only 16 bits, it will saturate after 65,536 IDs within the same millisecond[^monotonic]. Since it's only 16 bits, it will saturate after 65,536 IDs
intra-millisecond creations, after which, IDs in that same millisecond will not have an intrinsic intra-millisecond creations, after which, IDs in that same millisecond will not have an intrinsic
total order (the random bits will still be different, so you shouldn't have collisions). My PC, total order (the random bits will still be different, so you shouldn't have collisions). My PC,
which is no slouch, can only generate about 20,000 per millisecond, so hopefully this is not an which is no slouch, can only generate about 20,000 per millisecond, so hopefully this is not an
@ -95,12 +98,11 @@ you already have one.
# How to use # How to use
As noted, the Julid crate can be used in two different ways: as a regular Rust library, declared The Julid crate can be used in two different ways: as a regular Rust library, declared in your Rust
in your Rust project's `Cargo.toml` file (say, by running `cargo add julid-rs`), and used as shown project's `Cargo.toml` file (say, by running `cargo add julid-rs`), and used as shown above. There's
above. There's a rudimentary a rudimentary [benchmark](https://gitlab.com/nebkor/julid/-/blob/main/examples/benchmark.rs) example
[benchmark](https://gitlab.com/nebkor/julid/-/blob/main/examples/benchmark.rs) example in the repo, in the repo, which I'll talk more about below. But the primary use case for me was as a loadable
which I'll talk more about below. But the primary use case for me was as a loadable SQLite SQLite extension, as I [previously
extension, as I [previously
wrote](/rnd/one-part-serialized-mystery-part-2/#next-steps-with-ids). Both are covered in the wrote](/rnd/one-part-serialized-mystery-part-2/#next-steps-with-ids). Both are covered in the
[documentation](https://docs.rs/julid-rs/latest/julid/), but let's go over them here, starting with [documentation](https://docs.rs/julid-rs/latest/julid/), but let's go over them here, starting with
the extension. the extension.
@ -116,8 +118,8 @@ The extension, when loaded into SQLite, provides the following functions:
* `julid_counter(julid)`: show the value of this julid's monotonic counter * `julid_counter(julid)`: show the value of this julid's monotonic counter
* `julid_sortable(julid)`: return the 64-bit concatenation of the timestamp and counter * `julid_sortable(julid)`: return the 64-bit concatenation of the timestamp and counter
* `julid_string(julid)`: show the [base-32 Crockford](https://en.wikipedia.org/wiki/Base32) * `julid_string(julid)`: show the [base-32 Crockford](https://en.wikipedia.org/wiki/Base32)
encoding of this julid; the raw bytes won't be valid UTF-8, so use this or the built-in `hex()` encoding of this julid; the raw bytes of Julids won't be valid UTF-8, so use this or the built-in
function to `select` a human-readable representation `hex()` function to `select` a human-readable representation
### Building and loading ### Building and loading
@ -148,10 +150,9 @@ create table if not exists watches (
id blob not null primary key default (julid_new()), id blob not null primary key default (julid_new()),
kind int not null, -- enum for movie or tv show or whatev kind int not null, -- enum for movie or tv show or whatev
title text not null, title text not null,
metadata_url text, -- possible url for imdb or other metadata-esque site to show the user
length int, length int,
release_date int, release_date int,
added_by blob not null, -- ID of the user that added it added_by blob not null,
last_updated int not null default (unixepoch()), last_updated int not null default (unixepoch()),
foreign key (added_by) references users (id) foreign key (added_by) references users (id)
); );
@ -177,7 +178,8 @@ a simple benchmark in the examples folder of the repo, the important parts of wh
use julid::Julid; use julid::Julid;
fn main() { fn main() {
[....] /* snip some stuff */
let start = Instant::now(); let start = Instant::now();
for _ in 0..num { for _ in 0..num {
v.push(Julid::new()); v.push(Julid::new());
@ -213,14 +215,14 @@ timestamp as a [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime
`created_at(&self)` method to `Julid`. `created_at(&self)` method to `Julid`.
Something to note: don't enable the `plugin` feature in your Cargo.toml if you're using this crate Something to note: don't enable the `plugin` feature in your Cargo.toml if you're using this crate
inside your Rust application, *especially* if you're also loading it as an extension in SQLite in inside your Rust application, especially if you're *also* loading it as an extension in SQLite in
your application. You'll get a long and confusing runtime panic due to there being multiple your application. You'll get a long and confusing runtime panic due to there being multiple
entrypoints defined with the same name. entrypoints defined with the same name.
# Why Julids? # Why Julids?
The astute may have noticed that this is the third time I've written about globally unique The astute may have noticed that this is the third time I've written about globally unique sortable
sortable IDs ([here is part one](/rnd/one-part-serialized-mystery), and [part two is IDs ([here is part one](/rnd/one-part-serialized-mystery), and [part two is
here](/rnd/one-part-serialized-mystery-part-2)). What's, uh... what's up with that? here](/rnd/one-part-serialized-mystery-part-2)). What's, uh... what's up with that?
![marge just thinks they're neat][marge ids] ![marge just thinks they're neat][marge ids]
@ -255,17 +257,17 @@ before](/rnd/one-part-serialized-mystery-part-2/#next-steps-with-ids):
> and so my next step is to write one of those, and remove the ID generation logic from the > and so my next step is to write one of those, and remove the ID generation logic from the
> application. > application.
Now that I've accomplished all I've set out to, is this the last time I'll time I'll be writing at Now that I've accomplished all that I've set out to do, is this the last time I'll time I'll be
length about these things? It's hard to say for sure, but signs point to "yes". I hope you've found writing at length about these things? It's hard to say for sure, but signs point to "yes". I hope
them at least a little interesting! you've found them at least a little interesting!
# Thanks # Thanks
This crate wouldn't have been possible without a lot of inspiration (and a little shameless This project wouldn't have happened without a lot of inspiration (and a little shameless stealing)
stealing) from the [ulid-rs](https://github.com/dylanhart/ulid-rs) crate. For the loadable from the [ulid-rs](https://github.com/dylanhart/ulid-rs) crate. For the loadable extension, the
extension, the [sqlite-loadable-rs](https://github.com/asg017/sqlite-loadable-rs) crate made it [sqlite-loadable-rs](https://github.com/asg017/sqlite-loadable-rs) crate made it *extremely* easy to
*extremely* easy to write; what I thought would take a couple days instead took a couple write; what I thought would take a couple days instead took a couple hours. Thank you, authors of
hours. Thank you, authors of those crates! Feel free to steal from this project! those crates! Feel free to steal code from me any time!
---- ----
@ -276,13 +278,24 @@ hours. Thank you, authors of those crates! Feel free to steal from this project!
[name](https://gitlab.com/nebkor/julid/-/blob/2484d5156bde82a91dcc106410ed56ee0a5c1e07/Cargo.toml#L24) [name](https://gitlab.com/nebkor/julid/-/blob/2484d5156bde82a91dcc106410ed56ee0a5c1e07/Cargo.toml#L24)
is just "julid"; that's how you refer to it in a `use` statement in your Rust program. is just "julid"; that's how you refer to it in a `use` statement in your Rust program.
[^httm]: Remember in *Hot Tub Time Machine*, where Rob Cordry's character, "Lew", decides to stay in
the past and use his future-knowledge to amass wealth and power, and he makes his own versions
of things that were done in his past, like forming a glam rock band called "Mötley Lew", and a
search engine called "Loogle", etc.?
[^counter idea]: Putting the counter bits after the timestamp bits was stolen from
<https://github.com/ahawker/ulid/issues/306#issuecomment-451850395>, though they use only 15 bits
for the counter, due to each character in the string encoding representing five bits, and using
three whole characters for the counter. That gives them one more random bit than Julids, and
lowers the number of available unique intra-millisecond IDs in the same process to 32,678.
[^monotonic]: At least, they will still have a total order if they're all generated within the same [^monotonic]: At least, they will still have a total order if they're all generated within the same
process in the same way; the code uses a [64-bit atomic process in the same way; the code uses a [64-bit atomic
integer](https://gitlab.com/nebkor/julid/-/blob/2484d5156bde82a91dcc106410ed56ee0a5c1e07/src/julid.rs#L11-12) integer](https://gitlab.com/nebkor/julid/-/blob/2484d5156bde82a91dcc106410ed56ee0a5c1e07/src/julid.rs#L11-12)
to ensure that IDs generated within the same millisecond have incremented counters, but that to ensure that IDs generated within the same millisecond have incremented counters, but that
atomic counter is not global; calling `Julid::new()` in Rust and `select julid_new()` in SQLite atomic counter is not global; calling `Julid::new()` in Rust and `select julid_new()` in SQLite
will not be aware of each others' counters. I just make sure to only generate them inside the would be as though they were generated on different machines. I just make sure to only generate
DB. them inside the DB.
[^my computer]: According to the output of `lscpu`, my computer has an "AMD Ryzen 9 3900X 12-Core [^my computer]: According to the output of `lscpu`, my computer has an "AMD Ryzen 9 3900X 12-Core
Processor", running between 2.2 and 4.6 GHz. It's no slouch! Processor", running between 2.2 and 4.6 GHz. It's no slouch!

View file

@ -1,9 +1,10 @@
code { code {
background-color: var(--bg-1); background-color: #ddddee;
padding: 0.1em 0.2em; padding: 0em 0.1em 0em 0.1em;
border-radius: 5px; border-radius: 5px;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-style: none solid none solid;
} }
pre { pre {
@ -110,4 +111,4 @@ pre code[class*="language-rs"]::before {
content: "rust"; content: "rust";
background: #fff8f6; background: #fff8f6;
color: #ff4647; color: #ff4647;
} }

View file

@ -1,6 +1,6 @@
.hias-footer { .hias-footer {
text-align: center; text-align: center;
font-size: 0.4rem; font-size: 0.9rem;
} }
.page-header { .page-header {

View file

@ -1,9 +1,9 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block main_content %} {% block main_content %}
{% if section.extra.section_path -%} {% if section.extra.section_path -%}
{% set section = get_section(path=section.extra.section_path) %} {% set section = get_section(path=section.extra.section_path) %}
{% endif -%} {% endif -%}
{{ post_macros::page_header(title=section.title) }} {{ post_macros::page_header(title=section.title) }}
@ -12,5 +12,36 @@
{{ post_macros::list_posts(pages = term.pages) }} {{ post_macros::list_posts(pages = term.pages) }}
{% endblock main_content %} <hr>
<script src="/js/footnoter.js"></script>
<div class=hias-footer>
<p>There have been <span id="hitman-count">no</span> views of this page.</p>
<p><a href=https://webring.club/tJiARCf_atDhfvzd/previous></a> - The <a
href=https://webring.club/random>Constellation</a> Webring - <a
href=https://webring.club/tJiARCf_atDhfvzd/next>→</a> </p>
<hr>
<p>
<script async defer src="https://www.recurse-scout.com/loader.js?t=e38ac183ce767b3800a4b4587c00f3fd"></script>
<div class="rc-scout"></div>
</p>
</div>
<script defer>
const hits = document.getElementById('hitman-count');
fetch("/hit/main-landing-page").then((resp) => {
if (resp.ok) {
return resp.text();
} else {
return "I don't even know how many";
}
}).then((data) => {
hits.innerHTML = data;
});
</script>
{% endblock main_content %}

View file

@ -7,9 +7,29 @@
<hr> <hr>
<script src="/js/footnoter.js"></script> <script src="/js/footnoter.js"></script>
<div class=hias-footer> <div class=hias-footer>
<p> <p>There have been <span id="hitman-count">no</span> views of this page.</p>
<p><a href=https://webring.club/tJiARCf_atDhfvzd/previous></a> - The <a
href=https://webring.club/random>Constellation</a> Webring - <a
href=https://webring.club/tJiARCf_atDhfvzd/next>→</a> </p>
<hr>
<p>
<script async defer src="https://www.recurse-scout.com/loader.js?t=e38ac183ce767b3800a4b4587c00f3fd"></script> <script async defer src="https://www.recurse-scout.com/loader.js?t=e38ac183ce767b3800a4b4587c00f3fd"></script>
<div class="rc-scout"></div> <div class="rc-scout"></div>
</p> </p>
</div> </div>
<script defer>
const hits = document.getElementById('hitman-count');
fetch("/hit/{{ page.slug }}").then((resp) => {
if (resp.ok) {
return resp.text();
} else {
return "I don't even know how many";
}
}).then((data) => {
hits.innerHTML = data;
});
</script>
{% endblock main_content %} {% endblock main_content %}