basic buzzer working with websocket comms
This commit is contained in:
parent
0fd244f3d4
commit
3bb58b5719
@ -23,6 +23,9 @@ mist = ">= 4.0.7 and < 5.0.0"
|
|||||||
argv = ">= 1.0.2 and < 2.0.0"
|
argv = ">= 1.0.2 and < 2.0.0"
|
||||||
gleam_erlang = ">= 0.34.0 and < 1.0.0"
|
gleam_erlang = ">= 0.34.0 and < 1.0.0"
|
||||||
gleam_http = ">= 4.0.0 and < 5.0.0"
|
gleam_http = ">= 4.0.0 and < 5.0.0"
|
||||||
|
gleam_otp = ">= 0.16.1 and < 1.0.0"
|
||||||
|
gleam_crypto = ">= 1.5.0 and < 2.0.0"
|
||||||
|
gleam_json = ">= 2.3.0 and < 3.0.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
gleeunit = ">= 1.0.0 and < 2.0.0"
|
||||||
|
@ -34,8 +34,11 @@ packages = [
|
|||||||
|
|
||||||
[requirements]
|
[requirements]
|
||||||
argv = { version = ">= 1.0.2 and < 2.0.0" }
|
argv = { version = ">= 1.0.2 and < 2.0.0" }
|
||||||
|
gleam_crypto = { version = ">= 1.5.0 and < 2.0.0" }
|
||||||
gleam_erlang = { version = ">= 0.34.0 and < 1.0.0" }
|
gleam_erlang = { version = ">= 0.34.0 and < 1.0.0" }
|
||||||
gleam_http = { version = ">= 4.0.0 and < 5.0.0" }
|
gleam_http = { version = ">= 4.0.0 and < 5.0.0" }
|
||||||
|
gleam_json = { version = ">= 2.3.0 and < 3.0.0" }
|
||||||
|
gleam_otp = { version = ">= 0.16.1 and < 1.0.0" }
|
||||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
||||||
glint = { version = ">= 1.2.1 and < 2.0.0" }
|
glint = { version = ">= 1.2.1 and < 2.0.0" }
|
||||||
|
@ -1,67 +1,47 @@
|
|||||||
import app/web
|
import app/web
|
||||||
|
import gleam/bit_array
|
||||||
import gleam/bytes_tree
|
import gleam/bytes_tree
|
||||||
|
import gleam/crypto
|
||||||
|
import gleam/dynamic/decode
|
||||||
|
import gleam/erlang/process
|
||||||
|
import gleam/float
|
||||||
import gleam/http/request.{type Request}
|
import gleam/http/request.{type Request}
|
||||||
import gleam/http/response.{type Response}
|
import gleam/http/response.{type Response}
|
||||||
import gleam/option.{None}
|
import gleam/io
|
||||||
|
import gleam/json
|
||||||
|
import gleam/list
|
||||||
|
import gleam/option.{None, Some}
|
||||||
|
import gleam/otp/actor
|
||||||
import gleam/result
|
import gleam/result
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import mist.{type Connection, type ResponseData}
|
import mist.{type Connection, type ResponseData}
|
||||||
|
|
||||||
pub fn handle_request(
|
pub fn handle_request(
|
||||||
request: Request(Connection),
|
req: Request(Connection),
|
||||||
ctx: web.Context,
|
ctx: web.Context,
|
||||||
) -> Response(ResponseData) {
|
) -> Response(ResponseData) {
|
||||||
case request.path_segments(request) {
|
io.println("Got request: " <> req.path)
|
||||||
[] -> serve_frontend()
|
case request.path_segments(req) {
|
||||||
["static", "frontend.wasm"] -> serve_wasm(ctx)
|
[] -> serve_wasm(ctx, "index.html", "text/html")
|
||||||
|
["static", "frontend.js"] ->
|
||||||
|
serve_wasm(ctx, "frontend.js", "text/javascript")
|
||||||
|
["static", "frontend_bg.wasm"] ->
|
||||||
|
serve_wasm(ctx, "frontend_bg.wasm", "application/wasm")
|
||||||
|
["ws"] -> serve_websocket(ctx, req)
|
||||||
_ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new()))
|
_ -> response.set_body(response.new(404), mist.Bytes(bytes_tree.new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const game_body = "
|
fn serve_wasm(
|
||||||
<html lang=\"en\">
|
ctx: web.Context,
|
||||||
|
file: String,
|
||||||
<head>
|
mimetype: String,
|
||||||
<meta charset=\"utf-8\">
|
) -> Response(ResponseData) {
|
||||||
<title>TITLE</title>
|
let wasm_path = string.join([ctx.config.static_path, file], "/")
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
canvas {
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
position: absolute;
|
|
||||||
background: black;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<canvas id=\"glcanvas\" tabindex='1'></canvas>
|
|
||||||
<!-- Minified and statically hosted version of https://github.com/not-fl3/macroquad/blob/master/js/mq_js_bundle.js -->
|
|
||||||
<script src=\"https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js\"></script>
|
|
||||||
<script>load(\"static/frontend.wasm\");</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
"
|
|
||||||
|
|
||||||
fn serve_frontend() -> Response(ResponseData) {
|
|
||||||
response.new(200)
|
|
||||||
|> response.set_body(mist.Bytes(bytes_tree.from_string(game_body)))
|
|
||||||
|> response.set_header("content-type", "text/html")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serve_wasm(ctx: web.Context) -> Response(ResponseData) {
|
|
||||||
let wasm_path = string.join([ctx.config.static_path, "frontend.wasm"], "/")
|
|
||||||
mist.send_file(wasm_path, offset: 0, limit: None)
|
mist.send_file(wasm_path, offset: 0, limit: None)
|
||||||
|> result.map(fn(file) {
|
|> result.map(fn(file) {
|
||||||
response.new(200)
|
response.new(200)
|
||||||
|> response.prepend_header("content-type", "application/wasm")
|
|> response.prepend_header("content-type", mimetype)
|
||||||
|> response.set_body(file)
|
|> response.set_body(file)
|
||||||
})
|
})
|
||||||
|> result.lazy_unwrap(fn() {
|
|> result.lazy_unwrap(fn() {
|
||||||
@ -69,3 +49,107 @@ fn serve_wasm(ctx: web.Context) -> Response(ResponseData) {
|
|||||||
|> response.set_body(mist.Bytes(bytes_tree.new()))
|
|> response.set_body(mist.Bytes(bytes_tree.new()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn serve_websocket(
|
||||||
|
ctx: web.Context,
|
||||||
|
req: Request(Connection),
|
||||||
|
) -> Response(ResponseData) {
|
||||||
|
mist.websocket(
|
||||||
|
request: req,
|
||||||
|
on_init: fn(_conn) { #(ctx, Some(ctx.selector)) },
|
||||||
|
on_close: fn(_state) { io.println("goodbye!") },
|
||||||
|
handler: handle_ws_message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_error_to_string(decode_err: decode.DecodeError) -> String {
|
||||||
|
"Path: "
|
||||||
|
<> string.join(decode_err.path, ".")
|
||||||
|
<> " Expected: "
|
||||||
|
<> decode_err.expected
|
||||||
|
<> " Got: "
|
||||||
|
<> decode_err.found
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_errors_to_string(decode_errs: List(decode.DecodeError)) -> String {
|
||||||
|
string.join(list.map(decode_errs, decode_error_to_string), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_ws_message(state, conn, message) {
|
||||||
|
io.println("Got WS message")
|
||||||
|
case message {
|
||||||
|
mist.Text("ping") -> {
|
||||||
|
io.println("Got Ping")
|
||||||
|
let assert Ok(_) = mist.send_text_frame(conn, "pong")
|
||||||
|
actor.continue(state)
|
||||||
|
}
|
||||||
|
mist.Text(msg) -> {
|
||||||
|
echo msg
|
||||||
|
case json.parse(msg, web.client_request_decoder()) {
|
||||||
|
Error(err) -> {
|
||||||
|
let err_msg = case err {
|
||||||
|
json.UnableToDecode(decode_err) ->
|
||||||
|
"Decode Error: " <> decode_errors_to_string(decode_err)
|
||||||
|
json.UnexpectedByte(bad_byte) -> "Unexpeted byte: " <> bad_byte
|
||||||
|
json.UnexpectedEndOfInput -> "Reached EoF"
|
||||||
|
json.UnexpectedFormat(_) -> "Unexpected format"
|
||||||
|
json.UnexpectedSequence(seq_msg) ->
|
||||||
|
"Unexpected Sequence: " <> seq_msg
|
||||||
|
}
|
||||||
|
io.println("Could not parse client message: " <> err_msg)
|
||||||
|
}
|
||||||
|
Ok(req) -> {
|
||||||
|
let resp = handle_client_msg(state, req)
|
||||||
|
echo resp
|
||||||
|
let resp_str =
|
||||||
|
web.encode_game_response(resp)
|
||||||
|
|> json.to_string
|
||||||
|
|
||||||
|
let assert Ok(_) = mist.send_text_frame(conn, resp_str)
|
||||||
|
Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actor.continue(state)
|
||||||
|
}
|
||||||
|
mist.Binary(_) -> {
|
||||||
|
actor.continue(state)
|
||||||
|
}
|
||||||
|
mist.Custom(_) -> {
|
||||||
|
actor.continue(state)
|
||||||
|
}
|
||||||
|
mist.Closed | mist.Shutdown -> actor.Stop(process.Normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_client_msg(
|
||||||
|
_ctx: web.Context,
|
||||||
|
req: web.ClientRequest,
|
||||||
|
) -> web.GameResponse {
|
||||||
|
case req.msg {
|
||||||
|
web.BuzzIn(time) -> {
|
||||||
|
io.println("Got buzz in @ " <> float.to_string(time))
|
||||||
|
web.AckBuzzer
|
||||||
|
}
|
||||||
|
web.Register(username, gamecode) -> {
|
||||||
|
let secret = crypto.strong_random_bytes(16)
|
||||||
|
let token =
|
||||||
|
crypto.new_hasher(crypto.Sha512)
|
||||||
|
|> crypto.hash_chunk(secret)
|
||||||
|
|> crypto.hash_chunk(bit_array.from_string(username))
|
||||||
|
|> crypto.hash_chunk(bit_array.from_string(gamecode))
|
||||||
|
|> crypto.digest
|
||||||
|
|
||||||
|
let token_hash =
|
||||||
|
crypto.new_hasher(crypto.Sha512)
|
||||||
|
|> crypto.hash_chunk(token)
|
||||||
|
|> crypto.digest
|
||||||
|
|
||||||
|
io.println("New token: " <> bit_array.base64_encode(token_hash, True))
|
||||||
|
|
||||||
|
let token = bit_array.base64_encode(token, True)
|
||||||
|
|
||||||
|
web.JoinResponse(token, username)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,70 @@
|
|||||||
import config
|
import config
|
||||||
|
import gleam/dynamic/decode
|
||||||
|
import gleam/erlang/process
|
||||||
|
import gleam/json
|
||||||
import session
|
import session
|
||||||
import storail
|
import storail
|
||||||
|
|
||||||
pub type Context {
|
pub type ClientMessage {
|
||||||
Context(config: config.Config, sessions: storail.Collection(session.Session))
|
BuzzIn(time: Float)
|
||||||
|
Register(game_code: String, username: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_message_decoder() -> decode.Decoder(ClientMessage) {
|
||||||
|
use variant <- decode.field("type", decode.string)
|
||||||
|
case variant {
|
||||||
|
"BuzzIn" -> {
|
||||||
|
use time <- decode.field("time", decode.float)
|
||||||
|
decode.success(BuzzIn(time:))
|
||||||
|
}
|
||||||
|
"Register" -> {
|
||||||
|
use game_code <- decode.field("game_code", decode.string)
|
||||||
|
use username <- decode.field("username", decode.string)
|
||||||
|
decode.success(Register(game_code:, username:))
|
||||||
|
}
|
||||||
|
_ -> decode.failure(BuzzIn(time: 0.0), "ClientMessage")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ClientRequest {
|
||||||
|
ClientRequest(token: String, msg: ClientMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_request_decoder() -> decode.Decoder(ClientRequest) {
|
||||||
|
use token <- decode.field("token", decode.string)
|
||||||
|
use msg <- decode.field("msg", client_message_decoder())
|
||||||
|
decode.success(ClientRequest(token:, msg:))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type GameResponse {
|
||||||
|
JoinResponse(username: String, token: String)
|
||||||
|
AckBuzzer
|
||||||
|
ResetBuzzer
|
||||||
|
UpdatePointTotal(score: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_game_response(game_response: GameResponse) -> json.Json {
|
||||||
|
case game_response {
|
||||||
|
JoinResponse(..) ->
|
||||||
|
json.object([
|
||||||
|
#("type", json.string("JoinResponse")),
|
||||||
|
#("username", json.string(game_response.username)),
|
||||||
|
#("token", json.string(game_response.token)),
|
||||||
|
])
|
||||||
|
AckBuzzer -> json.object([#("type", json.string("AckBuzzer"))])
|
||||||
|
ResetBuzzer -> json.object([#("type", json.string("ResetBuzzer"))])
|
||||||
|
UpdatePointTotal(..) ->
|
||||||
|
json.object([
|
||||||
|
#("type", json.string("UpdatePointTotal")),
|
||||||
|
#("score", json.int(game_response.score)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Context {
|
||||||
|
Context(
|
||||||
|
config: config.Config,
|
||||||
|
sessions: storail.Collection(session.Session),
|
||||||
|
selector: process.Selector(ClientRequest),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,11 @@ pub fn run_server() -> glint.Command(Result(Nil, Error)) {
|
|||||||
let storail_cfg = storail.Config(cfg.storage_path)
|
let storail_cfg = storail.Config(cfg.storage_path)
|
||||||
|
|
||||||
let ctx =
|
let ctx =
|
||||||
web.Context(config: cfg, sessions: setup_session_collection(storail_cfg))
|
web.Context(
|
||||||
|
config: cfg,
|
||||||
|
sessions: setup_session_collection(storail_cfg),
|
||||||
|
selector: process.new_selector(),
|
||||||
|
)
|
||||||
|
|
||||||
let handler = router.handle_request(_, ctx)
|
let handler = router.handle_request(_, ctx)
|
||||||
|
|
||||||
|
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
/dist
|
||||||
|
470
frontend/Cargo.lock
generated
470
frontend/Cargo.lock
generated
@ -26,6 +26,21 @@ version = "1.3.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.23.0"
|
version = "1.23.0"
|
||||||
@ -38,6 +53,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -50,6 +71,15 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@ -59,12 +89,62 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "document-features"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||||
|
dependencies = [
|
||||||
|
"litrs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ewebsock"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "679247b4a005c82218a5f13b713239b0b6d484ec25347a719f5b7066152a748a"
|
||||||
|
dependencies = [
|
||||||
|
"document-features",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"tungstenite",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fdeflate"
|
name = "fdeflate"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
@ -84,6 +164,12 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@ -104,7 +190,32 @@ dependencies = [
|
|||||||
name = "frontend"
|
name = "frontend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ewebsock",
|
||||||
"macroquad",
|
"macroquad",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -124,6 +235,23 @@ dependencies = [
|
|||||||
"foldhash",
|
"foldhash",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httparse"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.24.9"
|
version = "0.24.9"
|
||||||
@ -137,12 +265,40 @@ dependencies = [
|
|||||||
"png",
|
"png",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.172"
|
version = "0.2.172"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litrs"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "macroquad"
|
name = "macroquad"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
@ -172,6 +328,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniquad"
|
name = "miniquad"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
@ -218,6 +380,12 @@ dependencies = [
|
|||||||
"malloc_buf",
|
"malloc_buf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.17.16"
|
version = "0.17.16"
|
||||||
@ -231,24 +399,306 @@ dependencies = [
|
|||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.95"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quad-rand"
|
name = "quad-rand"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40"
|
checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.219"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.140"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-adler32"
|
name = "simd-adler32"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.101"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.21.1"
|
version = "0.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
|
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bytes",
|
||||||
|
"data-encoding",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"sha1",
|
||||||
|
"thiserror",
|
||||||
|
"utf-8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf-8"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.100"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@ -270,3 +720,23 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.8.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.8.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
@ -4,4 +4,8 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
ewebsock = "0.8.0"
|
||||||
macroquad = "0.4.14"
|
macroquad = "0.4.14"
|
||||||
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
serde_json = "1.0.140"
|
||||||
|
web-time = "1.1.0"
|
||||||
|
132
frontend/build_wasm.sh
Executable file
132
frontend/build_wasm.sh
Executable file
@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
HELP_STRING=$(
|
||||||
|
cat <<-END
|
||||||
|
usage: build_wasm.sh PROJECT_NAME [--release]
|
||||||
|
Build script for combining a Macroquad project with wasm-bindgen,
|
||||||
|
allowing integration with the greater wasm-ecosystem.
|
||||||
|
example: ./build_wasm.sh flappy-bird
|
||||||
|
This'll go through the following steps:
|
||||||
|
1. Build as target 'wasm32-unknown-unknown'.
|
||||||
|
2. Create the directory 'dist' if it doesn't already exist.
|
||||||
|
3. Run wasm-bindgen with output into the 'dist' directory.
|
||||||
|
- If the '--release' flag is provided, the build will be optimized for release.
|
||||||
|
4. Apply patches to the output js file (detailed here: https://github.com/not-fl3/macroquad/issues/212#issuecomment-835276147).
|
||||||
|
5. Generate coresponding 'index.html' file.
|
||||||
|
Author: Tom Solberg <me@sbg.dev>
|
||||||
|
Edit: Nik codes <nik.code.things@gmail.com>
|
||||||
|
Edit: Nobbele <realnobbele@gmail.com>
|
||||||
|
Edit: profan <robinhubner@gmail.com>
|
||||||
|
Edit: Nik codes <nik.code.things@gmail.com>
|
||||||
|
Version: 0.4
|
||||||
|
END
|
||||||
|
)
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo >&2 "$HELP_STRING"
|
||||||
|
echo >&2
|
||||||
|
echo >&2 "Error: $*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse primary commands
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
key="$1"
|
||||||
|
case $key in
|
||||||
|
--release)
|
||||||
|
RELEASE=yes
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
-h | --help)
|
||||||
|
echo "$HELP_STRING"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
POSITIONAL+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Restore positionals
|
||||||
|
set -- "${POSITIONAL[@]}"
|
||||||
|
[ $# -ne 1 ] && die "too many arguments provided"
|
||||||
|
|
||||||
|
PROJECT_NAME=$1
|
||||||
|
|
||||||
|
HTML=$(
|
||||||
|
cat <<-END
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>${PROJECT_NAME}</title>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
canvas {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin: 0; padding: 0; height: 100vh; width: 100vw;">
|
||||||
|
<canvas id="glcanvas" tabindex='1' hidden></canvas>
|
||||||
|
<script src="https://not-fl3.github.io/miniquad-samples/mq_js_bundle.js"></script>
|
||||||
|
<script type="module">
|
||||||
|
import init, { set_wasm } from "./static/${PROJECT_NAME}.js";
|
||||||
|
async function impl_run() {
|
||||||
|
let wbg = await init();
|
||||||
|
miniquad_add_plugin({
|
||||||
|
register_plugin: (a) => (a.wbg = wbg),
|
||||||
|
on_init: () => set_wasm(wasm_exports),
|
||||||
|
version: "0.0.1",
|
||||||
|
name: "wbg",
|
||||||
|
});
|
||||||
|
load("./static/${PROJECT_NAME}_bg.wasm");
|
||||||
|
}
|
||||||
|
window.run = function() {
|
||||||
|
document.getElementById("run-container").remove();
|
||||||
|
document.getElementById("glcanvas").removeAttribute("hidden");
|
||||||
|
document.getElementById("glcanvas").focus();
|
||||||
|
impl_run();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<div id="run-container" style="display: flex; justify-content: center; align-items: center; height: 100%; flex-direction: column;">
|
||||||
|
<button onclick="run()">Run Game</button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
END
|
||||||
|
)
|
||||||
|
|
||||||
|
TARGET_DIR="target/wasm32-unknown-unknown"
|
||||||
|
# Build
|
||||||
|
if [ -n "$RELEASE" ]; then
|
||||||
|
cargo build --release --target wasm32-unknown-unknown
|
||||||
|
TARGET_DIR="$TARGET_DIR/release"
|
||||||
|
else
|
||||||
|
cargo build --target wasm32-unknown-unknown
|
||||||
|
TARGET_DIR="$TARGET_DIR/debug"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate bindgen outputs
|
||||||
|
mkdir -p dist
|
||||||
|
wasm-bindgen $TARGET_DIR/"$PROJECT_NAME".wasm --out-dir dist --target web --no-typescript
|
||||||
|
|
||||||
|
# Shim to tie the thing together
|
||||||
|
sed -i "s/import \* as __wbg_star0 from 'env';//" dist/"$PROJECT_NAME".js
|
||||||
|
sed -i "s/let wasm;/let wasm; export const set_wasm = (w) => wasm = w;/" dist/"$PROJECT_NAME".js
|
||||||
|
sed -i "s/imports\['env'\] = __wbg_star0;/return imports.wbg\;/" dist/"$PROJECT_NAME".js
|
||||||
|
sed -i "s/const imports = __wbg_get_imports();/return __wbg_get_imports();/" dist/"$PROJECT_NAME".js
|
||||||
|
|
||||||
|
# Create index from the HTML variable
|
||||||
|
echo "$HTML" >dist/index.html
|
66
frontend/src/context.rs
Normal file
66
frontend/src/context.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use ewebsock::WsEvent;
|
||||||
|
use macroquad::prelude::{error, info};
|
||||||
|
|
||||||
|
use crate::model::{ClientMessage, ClientRequest, GameResponse};
|
||||||
|
|
||||||
|
pub struct Context {
|
||||||
|
pub sender: ewebsock::WsSender,
|
||||||
|
pub reciever: ewebsock::WsReceiver,
|
||||||
|
pub token: String,
|
||||||
|
pub username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn new(url: &str) -> Context {
|
||||||
|
let (sender, reciever) = ewebsock::connect(url, ewebsock::Options::default()).unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
sender,
|
||||||
|
reciever,
|
||||||
|
token: String::new(),
|
||||||
|
username: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_msg(&mut self, msg: &ClientMessage) {
|
||||||
|
let request = ClientRequest {
|
||||||
|
token: self.token.clone(),
|
||||||
|
msg: msg.clone(),
|
||||||
|
};
|
||||||
|
let msg = serde_json::to_string(&request).unwrap();
|
||||||
|
self.sender.send(ewebsock::WsMessage::Text(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recv_msg(&mut self) -> Option<GameResponse> {
|
||||||
|
if let Some(msg) = self.reciever.try_recv() {
|
||||||
|
match msg {
|
||||||
|
WsEvent::Opened => {
|
||||||
|
info!("Web socket opened!");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
WsEvent::Message(ws_message) => match ws_message {
|
||||||
|
ewebsock::WsMessage::Text(msg) => {
|
||||||
|
let game_resp: GameResponse = serde_json::from_str(&msg).unwrap();
|
||||||
|
|
||||||
|
if let GameResponse::JoinResponse { username, token } = &game_resp {
|
||||||
|
self.username = username.clone();
|
||||||
|
self.token = token.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(game_resp)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
WsEvent::Error(err) => {
|
||||||
|
error!("Something hecky going on: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
WsEvent::Closed => {
|
||||||
|
panic!("Socket closed, something up...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
frontend/src/login_screen.rs
Normal file
87
frontend/src/login_screen.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use crate::StateTransition;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::model::register::Register;
|
||||||
|
use crate::model::{ClientMessage, GameResponse};
|
||||||
|
use crate::screen::Screen;
|
||||||
|
use macroquad::hash;
|
||||||
|
use macroquad::{prelude::*, ui::root_ui};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct LoginScreen {
|
||||||
|
new_username: String,
|
||||||
|
new_room_code: String,
|
||||||
|
sent_join: bool,
|
||||||
|
retry_count: usize,
|
||||||
|
error_msg: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen for LoginScreen {
|
||||||
|
fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
||||||
|
clear_background(WHITE);
|
||||||
|
|
||||||
|
let group_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.05);
|
||||||
|
let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25);
|
||||||
|
|
||||||
|
let mut pressed_join = false;
|
||||||
|
root_ui().window(
|
||||||
|
hash!("Window"),
|
||||||
|
Vec2::new(
|
||||||
|
screen_width() * 0.5 - window_size.x * 0.5,
|
||||||
|
screen_height() * 0.5 - window_size.x * 0.5,
|
||||||
|
),
|
||||||
|
Vec2::new(screen_width() * 0.25, screen_height() * 0.25),
|
||||||
|
|ui| {
|
||||||
|
ui.group(hash!("User Group"), group_size, |ui| {
|
||||||
|
ui.input_text(hash!("Username In"), "Username", &mut self.new_username);
|
||||||
|
});
|
||||||
|
ui.group(hash!("Room Group"), group_size, |ui| {
|
||||||
|
ui.input_text(hash!("Room Code In"), "Room Code", &mut self.new_room_code);
|
||||||
|
});
|
||||||
|
ui.group(hash!("Join Group"), group_size, |ui| {
|
||||||
|
if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Join") {
|
||||||
|
info!(
|
||||||
|
"User pressed joined with name={} room_code={}",
|
||||||
|
self.new_username, self.new_room_code
|
||||||
|
);
|
||||||
|
|
||||||
|
pressed_join = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.error_msg {
|
||||||
|
ui.group(hash!("Error Group"), group_size, |ui| {
|
||||||
|
ui.label(Vec2::new(0.0, 0.0), "Failed to join game, try again...");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if pressed_join {
|
||||||
|
ctx.send_msg(&ClientMessage::Register(Register {
|
||||||
|
game_code: self.new_room_code.clone(),
|
||||||
|
username: self.new_username.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.sent_join = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.sent_join {
|
||||||
|
let msg = ctx.recv_msg();
|
||||||
|
|
||||||
|
if let Some(GameResponse::JoinResponse { username, token }) = msg {
|
||||||
|
return Some(StateTransition::JoinAsPlayer { username, token });
|
||||||
|
}
|
||||||
|
|
||||||
|
self.retry_count += 1;
|
||||||
|
|
||||||
|
if self.retry_count > get_fps() as usize * 5 {
|
||||||
|
warn!("Join failed, try again...");
|
||||||
|
self.retry_count = 0;
|
||||||
|
self.sent_join = false;
|
||||||
|
self.error_msg = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +1,59 @@
|
|||||||
use macroquad::hash;
|
use context::Context;
|
||||||
use macroquad::{prelude::*, ui::root_ui};
|
use login_screen::LoginScreen;
|
||||||
|
use macroquad::prelude::*;
|
||||||
|
use player_screen::PlayerScreen;
|
||||||
|
use screen::Screen;
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
mod login_screen;
|
||||||
|
mod model;
|
||||||
|
mod player_screen;
|
||||||
|
mod screen;
|
||||||
|
|
||||||
|
pub enum State {
|
||||||
|
Init,
|
||||||
|
Login(LoginScreen),
|
||||||
|
PlayerScreen(PlayerScreen),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn transition_state(&self, transition: StateTransition) -> Self {
|
||||||
|
match transition {
|
||||||
|
StateTransition::Init | StateTransition::LeaveGame => {
|
||||||
|
Self::Login(LoginScreen::default())
|
||||||
|
}
|
||||||
|
StateTransition::JoinAsPlayer { username, token } => {
|
||||||
|
Self::PlayerScreen(PlayerScreen::new(&username, &token))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
||||||
|
match self {
|
||||||
|
Self::Init => Some(StateTransition::Init),
|
||||||
|
Self::Login(login_screen) => login_screen.handle_frame(ctx),
|
||||||
|
Self::PlayerScreen(player_screen) => player_screen.handle_frame(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum StateTransition {
|
||||||
|
Init,
|
||||||
|
JoinAsPlayer { username: String, token: String },
|
||||||
|
LeaveGame,
|
||||||
|
}
|
||||||
|
|
||||||
#[macroquad::main("Play of the Game")]
|
#[macroquad::main("Play of the Game")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let mut username = String::new();
|
let mut context = Context::new("ws://127.0.0.1:8080/ws");
|
||||||
let mut rooom_code = String::new();
|
|
||||||
|
let mut state = State::Init;
|
||||||
|
state.transition_state(StateTransition::Init);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
clear_background(WHITE);
|
if let Some(transition) = state.handle_frame(&mut context) {
|
||||||
|
state = state.transition_state(transition);
|
||||||
let group_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.05);
|
}
|
||||||
let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25);
|
|
||||||
|
|
||||||
root_ui().window(
|
|
||||||
hash!("Window"),
|
|
||||||
Vec2::new(
|
|
||||||
screen_width() * 0.5 - window_size.x * 0.5,
|
|
||||||
screen_height() * 0.5 - window_size.x * 0.5,
|
|
||||||
),
|
|
||||||
Vec2::new(screen_width() * 0.25, screen_height() * 0.25),
|
|
||||||
|ui| {
|
|
||||||
ui.group(hash!("User Group"), group_size, |ui| {
|
|
||||||
ui.input_text(hash!("Username In"), "Username", &mut username);
|
|
||||||
});
|
|
||||||
ui.group(hash!("Room Group"), group_size, |ui| {
|
|
||||||
ui.input_text(hash!("Room Code In"), "Room Code", &mut rooom_code);
|
|
||||||
});
|
|
||||||
ui.group(hash!("Join Group"), group_size, |ui| {
|
|
||||||
if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Join") {
|
|
||||||
info!(
|
|
||||||
"User pressed joined with name={} room_code={}",
|
|
||||||
username, rooom_code
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
next_frame().await
|
next_frame().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
frontend/src/model/buzz_in.rs
Normal file
6
frontend/src/model/buzz_in.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct BuzzIn {
|
||||||
|
pub time: f64,
|
||||||
|
}
|
28
frontend/src/model/mod.rs
Normal file
28
frontend/src/model/mod.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use buzz_in::BuzzIn;
|
||||||
|
use register::Register;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod buzz_in;
|
||||||
|
pub mod register;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum ClientMessage {
|
||||||
|
BuzzIn(BuzzIn),
|
||||||
|
Register(Register),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct ClientRequest {
|
||||||
|
pub token: String,
|
||||||
|
pub msg: ClientMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum GameResponse {
|
||||||
|
JoinResponse { username: String, token: String },
|
||||||
|
AckBuzzer,
|
||||||
|
ResetBuzzer,
|
||||||
|
UpdatePountTotal { score: i32 },
|
||||||
|
}
|
7
frontend/src/model/register.rs
Normal file
7
frontend/src/model/register.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
pub struct Register {
|
||||||
|
pub game_code: String,
|
||||||
|
pub username: String,
|
||||||
|
}
|
72
frontend/src/player_screen.rs
Normal file
72
frontend/src/player_screen.rs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
use web_time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use crate::StateTransition;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::model::ClientMessage;
|
||||||
|
use crate::model::buzz_in::BuzzIn;
|
||||||
|
use crate::screen::Screen;
|
||||||
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
|
pub struct PlayerScreen {
|
||||||
|
token: String,
|
||||||
|
username: String,
|
||||||
|
score: i32,
|
||||||
|
button_pressed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlayerScreen {
|
||||||
|
pub fn new(token: &str, username: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
token: token.to_string(),
|
||||||
|
username: username.to_string(),
|
||||||
|
score: 0,
|
||||||
|
button_pressed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Screen for PlayerScreen {
|
||||||
|
fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
||||||
|
clear_background(BEIGE);
|
||||||
|
|
||||||
|
let button_color = if self.button_pressed {
|
||||||
|
Color::from_hex(0xaa0000)
|
||||||
|
} else {
|
||||||
|
RED
|
||||||
|
};
|
||||||
|
|
||||||
|
let button_size = (screen_width() * 0.33).min(screen_height() * 0.20);
|
||||||
|
draw_circle(
|
||||||
|
screen_width() * 0.5,
|
||||||
|
screen_height() * 0.5,
|
||||||
|
button_size * 1.1,
|
||||||
|
DARKBROWN,
|
||||||
|
);
|
||||||
|
draw_circle(
|
||||||
|
screen_width() * 0.5,
|
||||||
|
screen_height() * 0.5,
|
||||||
|
button_size,
|
||||||
|
button_color,
|
||||||
|
);
|
||||||
|
|
||||||
|
if is_mouse_button_pressed(MouseButton::Left) {
|
||||||
|
let loc = Vec2::from(mouse_position());
|
||||||
|
let normalize = loc - Vec2::new(screen_width() * 0.5, screen_height() * 0.5);
|
||||||
|
|
||||||
|
if normalize.length() < button_size {
|
||||||
|
let timestamp = SystemTime::now();
|
||||||
|
let unix_timestamp = timestamp.duration_since(UNIX_EPOCH).expect("Time invalid");
|
||||||
|
|
||||||
|
let time = unix_timestamp.as_secs_f64();
|
||||||
|
|
||||||
|
info!("Button pressed @ {}", time);
|
||||||
|
self.button_pressed = true;
|
||||||
|
let buzz_in = BuzzIn { time };
|
||||||
|
|
||||||
|
ctx.send_msg(&ClientMessage::BuzzIn(buzz_in));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
6
frontend/src/screen.rs
Normal file
6
frontend/src/screen.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use crate::{StateTransition, context::Context};
|
||||||
|
|
||||||
|
pub trait Screen {
|
||||||
|
fn init_frame(&mut self) {}
|
||||||
|
fn handle_frame(&mut self, ctx: &mut Context) -> Option<StateTransition>;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user