entirely rewrite all (carcinization)
This commit is contained in:
parent
eaca2e59bd
commit
78b5ccc163
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,2 +1,6 @@
|
|||||||
node_modules
|
node_modules
|
||||||
db.level
|
db.level
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
1822
Cargo.lock
generated
Normal file
1822
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "incdec"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["osmarks <osmarks@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lazy_static = "1.4"
|
||||||
|
tide = { version = "0.16.0", default-features = false, features = ["h1-server"] }
|
||||||
|
async-std = { version = "1.9.0", features = ["attributes", "unstable"], default-features = false }
|
||||||
|
tide-websockets = "0.2"
|
||||||
|
anyhow = "1.0"
|
||||||
|
prometheus = { version = "0.11.0", features = ["process"] }
|
||||||
|
memchr = "2"
|
10
incdec.js
10
incdec.js
@ -35,10 +35,7 @@ app.get("/", (req, res) => {
|
|||||||
res.sendFile(__dirname + "/index.html")
|
res.sendFile(__dirname + "/index.html")
|
||||||
})
|
})
|
||||||
|
|
||||||
const sendCounter = (ws, x) => ws.send(JSON.stringify({
|
const sendCounter = (ws, x) => ws.send(x.toString())
|
||||||
type: "value",
|
|
||||||
value: x
|
|
||||||
}))
|
|
||||||
|
|
||||||
const transmitCounterUpdate = _.throttle(newValue => {
|
const transmitCounterUpdate = _.throttle(newValue => {
|
||||||
wss.getWss().clients.forEach(ws => sendCounter(ws, newValue))
|
wss.getWss().clients.forEach(ws => sendCounter(ws, newValue))
|
||||||
@ -48,11 +45,10 @@ app.ws("/api", (ws, req) => {
|
|||||||
console.log("Connected")
|
console.log("Connected")
|
||||||
sendCounter(ws, counter)
|
sendCounter(ws, counter)
|
||||||
ws.on("message", async data => {
|
ws.on("message", async data => {
|
||||||
const msg = JSON.parse(data)
|
if (data.includes("i")) {
|
||||||
if (msg.type === "increment") {
|
|
||||||
counter++
|
counter++
|
||||||
transmitCounterUpdate(counter)
|
transmitCounterUpdate(counter)
|
||||||
} else if (msg.type === "decrement") {
|
} else if (data.includes("d")) {
|
||||||
counter--
|
counter--
|
||||||
transmitCounterUpdate(counter)
|
transmitCounterUpdate(counter)
|
||||||
}
|
}
|
||||||
|
1
incdec.txt
Normal file
1
incdec.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
30
|
28
index.html
28
index.html
@ -1,3 +1,6 @@
|
|||||||
|
<title>IncDec</title>
|
||||||
|
<meta name="description" content="IncDec - the thrilling game of incrementing and decrementing.">
|
||||||
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="user-scalable=0">
|
<meta name="viewport" content="user-scalable=0">
|
||||||
<noscript>JavaScript is required for this.</noscript>
|
<noscript>JavaScript is required for this.</noscript>
|
||||||
<style>
|
<style>
|
||||||
@ -35,9 +38,9 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<button id="up" onclick="increment()">+</button>
|
|
||||||
<div id="value">Loading...</div>
|
|
||||||
<button id="down" onclick="decrement()">-</button>
|
<button id="down" onclick="decrement()">-</button>
|
||||||
|
<div id="value">Loading...</div>
|
||||||
|
<button id="up" onclick="increment()">+</button>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
var output = document.querySelector("#value")
|
var output = document.querySelector("#value")
|
||||||
@ -54,26 +57,23 @@
|
|||||||
output.innerText = text
|
output.innerText = text
|
||||||
}
|
}
|
||||||
|
|
||||||
function send(obj) {
|
function send(s) {
|
||||||
socket.send(JSON.stringify(obj))
|
socket.send(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.onmessage = function(ev) {
|
socket.onmessage = function(ev) {
|
||||||
var msg = JSON.parse(ev.data)
|
display(ev.data)
|
||||||
if (msg.type === "value") {
|
|
||||||
display(msg.value.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function increment() {
|
function increment() {
|
||||||
send({
|
send("inc")
|
||||||
type: "increment"
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function decrement() {
|
function decrement() {
|
||||||
send({
|
send("dec")
|
||||||
type: "decrement"
|
}
|
||||||
})
|
|
||||||
|
socket.onclose = function(ev) {
|
||||||
|
display("Disconnected!")
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
3
runner.js
Normal file
3
runner.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
const ws = require("ws")
|
||||||
|
const a = new ws("https://osmarks.net/incdec/api")
|
||||||
|
a.onopen = () => setInterval(() => a.send("i"), 1)
|
124
src/main.rs
Normal file
124
src/main.rs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
use std::env;
|
||||||
|
use tide_websockets::{WebSocket, Message};
|
||||||
|
use prometheus::{TextEncoder, Encoder, register_int_counter, IntCounter, register_int_gauge, IntGauge};
|
||||||
|
use anyhow::Result;
|
||||||
|
use tide::Body;
|
||||||
|
use std::sync::{atomic::{AtomicI64, Ordering}, Arc};
|
||||||
|
use tide_websockets::WebSocketConnection;
|
||||||
|
use async_std::prelude::*;
|
||||||
|
use memchr::memchr;
|
||||||
|
use std::time::Duration;
|
||||||
|
use async_std::task;
|
||||||
|
use async_std::fs::{read_to_string, write};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref OPS_COUNTER: IntCounter = register_int_counter!("incdec_ops", "IncDec operations executed").unwrap();
|
||||||
|
static ref COUNTER_GAUGE: IntGauge = register_int_gauge!("incdec_counter", "Current counter value").unwrap();
|
||||||
|
static ref CONNECTIONS_COUNTER: IntCounter = register_int_counter!("incdec_total_conns", "Total client connections made").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_mime_str(s: &'static str, mime: &'static str) -> Body {
|
||||||
|
let mut b: Body = s.into();
|
||||||
|
b.set_mime(mime);
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
type Counter = Arc<AtomicI64>;
|
||||||
|
|
||||||
|
fn update_counter(ctr: &Counter, by: i64) -> i64 {
|
||||||
|
let new_val = ctr.fetch_add(by, Ordering::Relaxed);
|
||||||
|
OPS_COUNTER.inc();
|
||||||
|
COUNTER_GAUGE.set(new_val);
|
||||||
|
new_val
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_connection(ws: WebSocketConnection, ctr: Counter) -> Result<()> {
|
||||||
|
CONNECTIONS_COUNTER.inc();
|
||||||
|
let mut counter_val = ctr.load(Ordering::Relaxed);
|
||||||
|
ws.send_string(format!("{}", counter_val)).await?;
|
||||||
|
let ctr_ = ctr.clone();
|
||||||
|
let mut ws_ = ws.clone();
|
||||||
|
let ws_link = async move {
|
||||||
|
while let Some(Ok(Message::Text(txt))) = ws_.next().await {
|
||||||
|
if let Some(_) = memchr(b'i', txt.as_bytes()) {
|
||||||
|
// increment
|
||||||
|
update_counter(&ctr_, 1);
|
||||||
|
}
|
||||||
|
else if let Some(_) = memchr(b'd', txt.as_bytes()) {
|
||||||
|
// decrement
|
||||||
|
update_counter(&ctr_, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let r: Result<()> = Ok(());
|
||||||
|
r
|
||||||
|
};
|
||||||
|
let poll_value = async move {
|
||||||
|
loop {
|
||||||
|
task::sleep(Duration::from_millis(50)).await;
|
||||||
|
let new = ctr.load(Ordering::Relaxed);
|
||||||
|
if counter_val != new {
|
||||||
|
ws.send_string(format!("{}", new)).await?;
|
||||||
|
counter_val = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ws_link.race(poll_value).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
let args: Vec<String> = env::args().collect();
|
||||||
|
let save_path = args[1].clone();
|
||||||
|
let port = args[2].parse::<u16>()?;
|
||||||
|
|
||||||
|
let counter = Arc::new(AtomicI64::new({
|
||||||
|
match read_to_string(&save_path).await {
|
||||||
|
Ok(s) => s.parse::<i64>()?,
|
||||||
|
Err(_) => 0
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
let counter_ = counter.clone();
|
||||||
|
|
||||||
|
let mut app = tide::with_state(counter);
|
||||||
|
app.at("/api").get(WebSocket::new(|req: tide::Request<Counter>, stream| {
|
||||||
|
let state = req.state().clone();
|
||||||
|
async move {
|
||||||
|
handle_connection(stream, state).await.map_err(|_| tide::Error::from_str(tide::StatusCode::InternalServerError, "should not occur"))
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
app.at("/metrics").get(|_req| async {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
let encoder = TextEncoder::new();
|
||||||
|
let metric_families = prometheus::gather();
|
||||||
|
encoder.encode(&metric_families, &mut buffer)?;
|
||||||
|
let mut b = Body::from_bytes(buffer);
|
||||||
|
b.set_mime("text/plain");
|
||||||
|
Ok(b)
|
||||||
|
});
|
||||||
|
app.at("/inc").post(|req: tide::Request<Counter>| async move {
|
||||||
|
let mut b: Body = update_counter(req.state(), 1).to_string().into();
|
||||||
|
b.set_mime("text/plain");
|
||||||
|
Ok(b)
|
||||||
|
});
|
||||||
|
app.at("/dec").post(|req: tide::Request<Counter>| async move {
|
||||||
|
let mut b: Body = update_counter(req.state(), -1).to_string().into();
|
||||||
|
b.set_mime("text/plain");
|
||||||
|
Ok(b)
|
||||||
|
});
|
||||||
|
app.at("/").get(|_req| async { Ok(set_mime_str(include_str!("../index.html"), "text/html")) });
|
||||||
|
|
||||||
|
println!("Running on port {}", port);
|
||||||
|
|
||||||
|
task::spawn(async move {
|
||||||
|
loop {
|
||||||
|
write(&save_path, format!("{}", counter_.load(Ordering::Relaxed))).await.unwrap();
|
||||||
|
task::sleep(Duration::from_secs(10)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(("0.0.0.0", port)).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user