toc wip
This commit is contained in:
parent
23a51562a1
commit
b036acd3f4
|
@ -8,6 +8,10 @@ code {
|
|||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
ul.posts>li {
|
||||
margin-top: var(--margin);
|
||||
}
|
||||
|
||||
pre.codeblock {
|
||||
background: var(--background-text);
|
||||
color: var(--text);
|
||||
|
|
|
@ -20,11 +20,12 @@
|
|||
<li>Vim motions</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content" style="grid-area: content2">
|
||||
<a href="/posts"><h2>Projects I've finished</h2></a>
|
||||
<div class="content posts" style="grid-area: content2">
|
||||
<h2><a href="posts">Projects I've finished</a></h2>
|
||||
<ul>
|
||||
<li><a href="/posts?post=openbirch">Openbirch</a><br>(made it to an alpha release)</li>
|
||||
<li><a href="/posts?post=minecraft-proxy">Minecraft server
|
||||
<li><a href="/posts?post=How I made a programming language 700x slower than python">Openbirch</a><br>(made it to
|
||||
an alpha release)</li>
|
||||
<li><a href="/posts?post=Putting hungry minecraft servers to sleep">Minecraft server
|
||||
hibernator</a></li>
|
||||
<li><a href="https://gitlab.com/SpoodyTheOne/the-wheel">Wheel of names drinking game</a><br>(scraping the bottom
|
||||
of the barrel for finished projects)</li>
|
||||
|
@ -32,7 +33,9 @@
|
|||
</div>
|
||||
<!-- <div class="content" style="grid-area: content3">content</div> -->
|
||||
|
||||
<div class="footer" style="grid-area: footer1"><marquee>Footer</marquee></div>
|
||||
<div class="footer" style="grid-area: footer1">
|
||||
<marquee>Footer</marquee>
|
||||
</div>
|
||||
<div class="footer" style="grid-area: footer2; text-align: center">
|
||||
<!-- <a href="https://nixwebr.ing/next/<name>">← prev</a> <a href="https://nixwebr.ing">nixwebr.ing</a> -->
|
||||
<!-- <a href="https://nixwebr.ing/prev/<name>">next →</a> -->
|
||||
|
@ -49,6 +52,22 @@
|
|||
<script src="/js/main.js"></script>
|
||||
<script>
|
||||
generate_background();
|
||||
|
||||
(async () => {
|
||||
let response = await fetch(`/posts/index`);
|
||||
if (response.status != 200)
|
||||
return;
|
||||
|
||||
let posts = (await response.text()).split("\n");
|
||||
|
||||
let p = document.querySelector("#content>.content.posts>h2");
|
||||
p.innerText = "Recent posts";
|
||||
|
||||
let elem = document.querySelector("#content>.content.posts>ul");
|
||||
elem.innerHTML = posts.slice(0,3).map(x => `<li><a href="/posts?post=${x}">${x}</a></li>`).join("\n");
|
||||
elem.style.maxWidth = "40ch";
|
||||
elem.classList.add("posts");
|
||||
})()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -61,14 +61,23 @@ const md2tokens = (markdown, newline) => {
|
|||
switch (markdown[i]) {
|
||||
case '\\':
|
||||
i++;
|
||||
|
||||
if (peek(0) == 't' && peek(1) == 'o' && peek(2) == 'c') {
|
||||
advance();
|
||||
advance();
|
||||
advance();
|
||||
tokens.push({ type: "toc" });
|
||||
break;
|
||||
}
|
||||
|
||||
current_string += markdown[i];
|
||||
break;
|
||||
|
||||
case '*':
|
||||
finish();
|
||||
advance();
|
||||
newline=false;
|
||||
newline_count=0;
|
||||
newline = false;
|
||||
newline_count = 0;
|
||||
let depth_em = 1;
|
||||
let type = "em";
|
||||
if (match("*")) {
|
||||
|
@ -82,8 +91,8 @@ const md2tokens = (markdown, newline) => {
|
|||
}
|
||||
|
||||
current_string = capture_until_predicate((i) => { let found = true; for (let j = 0; j < depth_em; j++) { if (markdown[i + j] != "*") { found = false; break; } } return found; })
|
||||
|
||||
for (let j=0;j<depth_em-1;j++) {
|
||||
|
||||
for (let j = 0; j < depth_em - 1; j++) {
|
||||
// console.log(markdown[i] == "\n");
|
||||
advance();
|
||||
}
|
||||
|
@ -103,6 +112,8 @@ const md2tokens = (markdown, newline) => {
|
|||
depth_he++;
|
||||
} while (match("#"));
|
||||
|
||||
advance();
|
||||
|
||||
while (markdown[i] != "\n" && markdown.length > i) {
|
||||
current_string += markdown[i];
|
||||
i++
|
||||
|
@ -110,6 +121,7 @@ const md2tokens = (markdown, newline) => {
|
|||
|
||||
tokens.push({ type: "header", level: depth_he, content: current_string });
|
||||
current_string = "";
|
||||
newline_count = 2;
|
||||
break;
|
||||
|
||||
case '!':
|
||||
|
@ -144,23 +156,37 @@ const md2tokens = (markdown, newline) => {
|
|||
tokens.push({ type: "image", alt: alt_text, url: url, title: title })
|
||||
break;
|
||||
|
||||
case '>':
|
||||
case '-':
|
||||
if (!newline) {
|
||||
def();
|
||||
break;
|
||||
}
|
||||
|
||||
if (peek(0) == "-" && peek(1) == "-" && peek(2) == "-" && peek(3) == "\n") {
|
||||
advance();
|
||||
advance();
|
||||
advance();
|
||||
|
||||
tokens.push({ type: "hline" });
|
||||
break;
|
||||
}
|
||||
|
||||
let type_l = peek(0) == '-' ? "ul" : "quote";
|
||||
let ch = peek(0);
|
||||
|
||||
let items = [];
|
||||
|
||||
advance();
|
||||
|
||||
do {
|
||||
match(" ");
|
||||
let text = capture_until("\n");
|
||||
advance();
|
||||
items.push(md2tokens(text, false));
|
||||
} while (match("-"));
|
||||
} while (match(ch));
|
||||
|
||||
tokens.push({ type: "ul", items: items });
|
||||
tokens.push({ type: type_l, items: items });
|
||||
|
||||
break;
|
||||
|
||||
|
@ -177,8 +203,8 @@ const md2tokens = (markdown, newline) => {
|
|||
case '[':
|
||||
finish();
|
||||
advance();
|
||||
newline=false;
|
||||
newline_count=0;
|
||||
newline = false;
|
||||
newline_count = 0;
|
||||
let text = capture_until("]");
|
||||
advance();
|
||||
|
||||
|
@ -196,8 +222,8 @@ const md2tokens = (markdown, newline) => {
|
|||
case '`':
|
||||
finish();
|
||||
advance();
|
||||
newline=false;
|
||||
newline_count=0;
|
||||
newline = false;
|
||||
newline_count = 0;
|
||||
let stop = (i) => markdown[i] == "`";
|
||||
let type_c = "inlinecode";
|
||||
let language = "";
|
||||
|
@ -211,7 +237,7 @@ const md2tokens = (markdown, newline) => {
|
|||
current_string = capture_until_predicate(stop);
|
||||
tokens.push({ type: type_c, content: current_string, language: language });
|
||||
current_string = "";
|
||||
if (type_c == "codeblock") { advance(); advance(); newline_count=2; } // remove trailing `
|
||||
if (type_c == "codeblock") { advance(); advance(); newline_count = 2; } // remove trailing `
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -240,6 +266,32 @@ const tokens2html = (tokens) => {
|
|||
|
||||
for (let token of tokens) {
|
||||
switch (token.type) {
|
||||
case "toc":
|
||||
let htok = tokens.filter(x => x.type == "header");
|
||||
console.log(htok);
|
||||
const createList = (tokens, depth = 2) => {
|
||||
let list = '';
|
||||
let listItems = '';
|
||||
|
||||
|
||||
while (tokens.length > 0 && tokens[0].level <= depth) {
|
||||
const token = tokens.shift();
|
||||
listItems += `<li><a href="#${token.content}">${token.content}</a></li>`;
|
||||
|
||||
if (tokens.length > 0 && tokens[0].level > token.depth) {
|
||||
listItems += createList(tokens, token.depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (listItems) {
|
||||
list = `<ol>${listItems}</ol>`;
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
output += createList(htok);
|
||||
break;
|
||||
case "span":
|
||||
output += `<span>${token.content}</span>`;
|
||||
break
|
||||
|
@ -254,6 +306,10 @@ const tokens2html = (tokens) => {
|
|||
break;
|
||||
case "header":
|
||||
output += `<h${token.level}>${token.content}</h${token.level}>`
|
||||
if (token.level == 2) output += "<hr>";
|
||||
break;
|
||||
case "hline":
|
||||
output += "<hr>";
|
||||
break;
|
||||
case "newline":
|
||||
output += "<br>"
|
||||
|
@ -269,6 +325,13 @@ const tokens2html = (tokens) => {
|
|||
}
|
||||
output += "</ul>";
|
||||
break;
|
||||
case "quote":
|
||||
output += "<pre class=\"quote\">\n";
|
||||
for (let item of token.items) {
|
||||
output += `${tokens2html(item)}\n`;
|
||||
}
|
||||
output += "</pre>";
|
||||
break;
|
||||
case "inlinecode":
|
||||
output += `<code>${token.content}</code>`;
|
||||
break;
|
||||
|
|
|
@ -1,34 +1,71 @@
|
|||
2
|
||||
Minecraft Rust Async Networking Proxy CRIU
|
||||
# Minecraft servers are HUNGRY
|
||||
|
||||
They hunger for your ram and your cpu. This makes it either expensive or laggy to try and host multiple servers at once.
|
||||
This was something I encountered when my friend built a homeserver out of spare computer parts that was barely powerful enough to run a minecraft server.
|
||||
The problem was that soon multiple people wanted a minecraft server hosted by him, including ourselves with modded experiences.
|
||||
The problem was that soon multiple people wanted a minecraft server hosted by him (which included us wanting to play modded minecraft).
|
||||
|
||||
It was a hassle to ssh into the server and start and stop the various servers depending on who wanted to play,
|
||||
especially since a lot of people only played very rarily.
|
||||
|
||||
I remembered that I'd seen [a project](https://github.com/gekware/minecraft-server-hibernation) that claimed to be able to hibernate a minecraft server if nobody was playing on it.
|
||||
This worked by starting and stopping the server, leading to noticeable join delays when joining modded servers.
|
||||
The only issue was that it worked for a single server, and did so by starting and stopping the server process.
|
||||
|
||||
Another nice-to-have was being able to join through a subdomain. After having messed around with nginx to help my friend set up various services like nextcloud
|
||||
on subdomains we wanted to do the same for the minecraft servers.
|
||||
This meant that if we wanted to join a modded server the hundreds of mods could make us wait for several minutes before we could play.
|
||||
|
||||
Being able to join the vanilla server through `vanilla.domain.com` and the modded through `modded.domain.com` would simply be really cool.
|
||||
Another issue was the fact that it could only host a single server. This meant that we would have to run multiple intances of the watcher,
|
||||
and that each server would be assigned to an arbitrary port that would be needed when connecting.
|
||||
|
||||
# Minecraft Server Hibernation Proxy Manager Rs (Name WIP)
|
||||
# Building a reverse proxy for minecraft
|
||||
|
||||
I ended up writing [a Rust program](https://gitlab.com/SpoodyTheOne/minecraft-server-hibernation-proxy-rust) that could do this and more. It listens on the default minecraft server port of `25565` and parses any incoming data as minecraft packets.
|
||||
If the data contains a url that matches `*.domain.com` then it uses the subdomain to look up a configured server, and if found redirects all future traffic to that port.
|
||||
Since my friends server was accessible through a domain we thought it would be cool if instead of supplying a port
|
||||
you could connect to a subdomain and be sent to a specific server.
|
||||
|
||||

|
||||
The simplest way to do this would be to create a dhcp record for each subdomain to point to a server,
|
||||
but that would be slow and tedious to set up for every server.
|
||||
|
||||
If the server is stopped or hibernating then the command to start it is automatically run. Likewise if all players leave a configurable countdown is started.
|
||||
Once it reaches 0 the server is hibenated using CRIU, which writes the entire memory and all open file descriptors of the server program to disk.
|
||||
We then tried nginx, as it seemingly could magically redirect traffic to an internal port based on the subdomain.
|
||||
I quickly found out that this did *not* work for minecraft servers (who would have guessed), but after doing some research
|
||||
I decided on creating my own reverse proxy that spoke the minecraft protocol instead of HTTP.
|
||||
|
||||

|
||||
# The Minecraft protocol
|
||||
|
||||
This means that joining a hibernated server only takes a few seconds, and any server currently hibernating will only take up disk space.
|
||||
It can also be restored even after system restarts, so it would be possible to halt systemd shutdowns until all servers are hibernated.
|
||||
Minecraft implements its own protocol consistent of packets. My first idea was to see if anybody had created a
|
||||
rust library for dealing with minecraft packets.
|
||||
|
||||
<marquee style="font-size: 20em">Marq**WEE**</marquee>
|
||||
While some did exist, most of them where unfinished or couldn't do what I wanted. [One crate](https://www.youtube.com/watch?v=E4WlUXrJgy4)
|
||||
was useful for seeing how an implementation of parsing packets was done, but ultimately I had to write my own parser.
|
||||
|
||||
*images of minecraft protocol*
|
||||
|
||||
# Detecting the subdomain
|
||||
|
||||
Luckily for me, the Minecraft procotol sends the full address it is trying to connect to during the handshake. This meant that I simply
|
||||
had to parse the handshake and extract the subdomain from the address, see if it matches any configured server, and then redirect all
|
||||
traffic to the internal port that server runs on.
|
||||
|
||||
*code explanation*
|
||||
|
||||
# Why aren't my chunks loading?
|
||||
|
||||
Just as I thought I was finally done I encountered a problem. The chunks where loading *really* slowly. It felt like we we're back to
|
||||
hosting on [aternos](aternos.org) (nothing against aternos, the **free** servers are just a little slow).
|
||||
|
||||
*image of chunks not loading*
|
||||
|
||||
I couldn't figure out why it was so slow. I thought it was because all the traffic had to flow through my program,
|
||||
but the sockets are connected on the kernel level.
|
||||
|
||||
After hours of debugging I found the solution. `stream.set_nodelay(true)`
|
||||
|
||||
## ...
|
||||
|
||||
**All this time the problem was as simple as saying ** `if (slow) { dont(); }`!
|
||||
|
||||
To say I was pissed was an understatement, but at the same time I was glad it was finally working.
|
||||
|
||||
The reason this fixed it was because it disabled [Nagle's algorithm](https://en.wikipedia.org/wiki/Nagle%27s_algorithm), which bunches packets together
|
||||
in order to (hopefully) speed up the sending of lots of smaller packets as one big packet.
|
||||
|
||||
For some reason this absolutely destroyed the chunk loading speed, and disabling it led to finally having the perfect Minecraft reverse proxy hibernation system thing.
|
||||
|
|
|
@ -21,6 +21,24 @@
|
|||
|
||||
ul,ol {
|
||||
margin-top:0;
|
||||
padding-left: calc(4*var(--margin));
|
||||
}
|
||||
|
||||
pre.quote {
|
||||
font: inherit;
|
||||
|
||||
background: var(--background-text);
|
||||
border-left: var(--margin) solid var(--border);
|
||||
border-radius: 0 var(--radius) var(--radius) 0;
|
||||
/* padding-bottom: var(--margin); */
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
background-color: var(--border);
|
||||
border-radius: var(--radius);
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
img {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
0
|
||||
Test
|
||||
\toc
|
||||
# H1
|
||||
## H2
|
||||
### H3
|
||||
|
@ -16,6 +17,8 @@ This is regular text
|
|||
|
||||
***bold italic***
|
||||
|
||||
---
|
||||
|
||||
`this is an inline code block`
|
||||
|
||||
```
|
||||
|
@ -60,6 +63,14 @@ in
|
|||
- Test
|
||||
- here
|
||||
|
||||
> this
|
||||
> is
|
||||
> a
|
||||
>
|
||||
> QUOTE
|
||||
|
||||
> **QUOTE**
|
||||
|
||||

|
||||

|
||||

|
||||
|
|
5
public/posts/toc.md
Normal file
5
public/posts/toc.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
0
|
||||
Tick Tock Cock
|
||||
# Table of Contents test
|
||||
|
||||
\toc
|
Loading…
Reference in a new issue