chess work

This commit is contained in:
dr-frmr 2023-11-21 17:49:18 -05:00
parent 5a4813b8c4
commit 84a5bbbe6c
No known key found for this signature in database
10 changed files with 687 additions and 959 deletions

View File

@ -62,7 +62,7 @@ snow = { version = "0.9.3", features = ["ring-resolver"] }
thiserror = "1.0"
tokio = { version = "1.28", features = ["fs", "macros", "rt-multi-thread", "sync"] }
tokio-tungstenite = "*"
url = "*"
url = "2.4.1"
uqbar_process_lib = { git = "ssh://git@github.com/uqbar-dao/process_lib.git", rev = "e53c124" }
uuid = { version = "1.1.2", features = ["serde", "v4"] }
warp = "0.3.5"

View File

@ -50,6 +50,12 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -124,6 +130,21 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
dependencies = [
"percent-encoding",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@ -162,12 +183,33 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "http"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "idna"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "2.1.0"
@ -234,6 +276,12 @@ dependencies = [
"libc",
]
[[package]]
name = "percent-encoding"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pleco"
version = "0.5.0"
@ -532,12 +580,42 @@ dependencies = [
"syn",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "unicode-bidi"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
@ -553,16 +631,30 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "uqbar_process_lib"
version = "0.3.0"
source = "git+ssh://git@github.com/uqbar-dao/process_lib.git?rev=db26c5b#db26c5b1607ba6532bcc7687bf8902a21ebd3393"
dependencies = [
"anyhow",
"bincode",
"http",
"rand 0.8.5",
"serde",
"serde_json",
"thiserror",
"url",
"wit-bindgen",
]
[[package]]
name = "url"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@ -17,7 +17,7 @@ bincode = "1.3.3"
pleco = "0.5"
serde = {version = "1.0", features = ["derive"] }
serde_json = "1.0"
uqbar_process_lib = { path = "../../../process_lib" }
uqbar_process_lib = { git = "ssh://git@github.com/uqbar-dao/process_lib.git", rev = "db26c5b" }
wit-bindgen = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "5390bab780733f1660d14c254ec985df2816bf1d" }
[lib]

View File

@ -1,79 +0,0 @@
import os
import subprocess
import sys
import json
import glob
import shutil
def compile_process(process_dir, pkg_dir, root_dir):
# Get the path to the source code and the compiled WASM file
src_path = os.path.join(process_dir, "src")
wasm_path = os.path.join(pkg_dir, os.path.basename(process_dir) + ".wasm")
# Check if the source code or the Cargo.toml file has been modified since the last compile
src_mtime = max(os.path.getmtime(f) for f in glob.glob(os.path.join(src_path, '**'), recursive=True))
wasm_mtime = os.path.getmtime(wasm_path) if os.path.exists(wasm_path) else 0
# Change to the process directory
os.chdir(process_dir)
# Create the target/bindings/$name/ directory
bindings_dir = os.path.join(process_dir, "target", "bindings", os.path.basename(process_dir))
os.makedirs(bindings_dir, exist_ok=True)
# Create target.wasm (compiled .wit) & world
subprocess.check_call([
"wasm-tools", "component", "wit",
os.path.join(root_dir, "wit"),
"-o", os.path.join(bindings_dir, "target.wasm"),
"--wasm"
])
# Copy /wit (world is empty file currently)
shutil.copytree(os.path.join(root_dir, "wit"), os.path.join(bindings_dir, "wit"), dirs_exist_ok=True)
# shutil.copy(os.path.join(root_dir, "world"), os.path.join(bindings_dir, "world"))
# Create an empty world file
with open(os.path.join(bindings_dir, "world"), 'w') as f:
pass
# Build the module using Cargo
subprocess.check_call([
"cargo", "+nightly", "build",
"--release",
"--no-default-features",
"--target", "wasm32-wasi"
])
# Adapt the module using wasm-tools
wasm_file = os.path.join(process_dir, "target", "wasm32-wasi", "release", os.path.basename(process_dir) + ".wasm")
adapted_wasm_file = wasm_file.replace(".wasm", "_adapted.wasm")
subprocess.check_call([
"wasm-tools", "component", "new",
wasm_file,
"-o", adapted_wasm_file,
"--adapt", os.path.join(root_dir, "wasi_snapshot_preview1.wasm")
])
# Embed "wit" into the component and place it in the expected location
subprocess.check_call([
"wasm-tools", "component", "embed", os.path.join(root_dir, "wit"),
"--world", "process",
adapted_wasm_file,
"-o", wasm_path
])
if __name__ == "__main__":
root_dir = os.getcwd()
pkg_dir = os.path.join(root_dir, "pkg")
# If a specific process is provided, compile it
if len(sys.argv) > 1:
process_dir = os.path.abspath(os.path.join(root_dir, sys.argv[1]))
compile_process(process_dir, pkg_dir, root_dir)
else:
# Compile each base dir folder that has a Cargo.toml
for root, dirs, files in os.walk(root_dir):
if 'Cargo.toml' in files and "process_lib" not in root:
process_dir = os.path.abspath(root)
compile_process(process_dir, pkg_dir, root_dir)

View File

@ -1,5 +1,5 @@
{
"package": "chess2",
"package": "chess",
"publisher": "uqbar",
"version": [0, 1, 0]
"version": [0, 2, 0]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,82 @@
use crate::*;
pub fn save_chess_state(state: &ChessState) {
let stored_state = convert_state(&state);
set_state(&bincode::serialize(&stored_state).unwrap());
}
fn convert_game(game: Game) -> StoredGame {
StoredGame {
id: game.id,
turns: game.turns,
board: game.board.fen(),
white: game.white,
black: game.black,
ended: game.ended,
}
}
fn convert_state(state: &ChessState) -> StoredChessState {
StoredChessState {
games: state
.games
.iter()
.map(|(id, game)| (id.to_string(), convert_game(game.clone())))
.collect(),
records: state.records.clone(),
}
}
pub fn json_game(game: &Game) -> serde_json::Value {
serde_json::json!({
"id": game.id,
"turns": game.turns,
"board": game.board.fen(),
"white": game.white,
"black": game.black,
"ended": game.ended,
})
}
pub fn send_ws_update(our: &Address, game: &Game) -> anyhow::Result<()> {
Request::new()
.target((&our.node, "http_server", "sys", "uqbar"))
.ipc(
serde_json::json!({
"EncryptAndForward": {
"channel_id": our.process.to_string(),
"forward_to": {
"node": our.node.clone(),
"process": {
"process_name": "http_server",
"package_name": "sys",
"publisher_node": "uqbar"
}
}, // node, process
"json": Some(serde_json::json!({ // this is the JSON to forward
"WebSocketPush": {
"target": {
"node": our.node.clone(),
"id": "chess", // If the message passed in an ID then we could send to just that ID
}
}
})),
}
})
.to_string()
.as_bytes()
.to_vec(),
)
.payload(Payload {
mime: Some("application/json".to_string()),
bytes: serde_json::json!({
"kind": "game_update",
"data": json_game(game),
})
.to_string()
.as_bytes()
.to_vec(),
})
.send()
}

View File

@ -1,124 +0,0 @@
#!/usr/bin/env python3
import sys
import json
import base64
import os
import shutil
import http.client
from urllib.parse import urlparse
### helpers
def send_request(path, json_data):
conn = http.client.HTTPConnection(HOST, PORT)
headers = {'Content-Type': 'application/json'}
conn.request("POST", path, json_data, headers)
response = conn.getresponse()
conn.close()
return response
def new_package(package_name, publisher_node, zip_file):
request = {
"node": NODE,
"process": "main:app_store:uqbar",
"inherit": False,
"expects_response": None,
"ipc": json.dumps({
"NewPackage": {
"package": {"package_name": package_name, "publisher_node": publisher_node },
"mirror": True
}
}),
"metadata": None,
"context": None,
"mime": "application/zip",
"data": zip_file
}
return json.dumps(request)
def install_package(package_name, publisher_node):
request = {
"node": NODE,
"process": "main:app_store:uqbar",
"inherit": False,
"expects_response": None,
"ipc": json.dumps({
"Install": {"package_name": package_name, "publisher_node": publisher_node },
}),
"metadata": None,
"context": None,
"mime": None,
"data": None,
}
return json.dumps(request)
# zip a directory
def zip_directory(directory, zip_filename):
shutil.make_archive(zip_filename, 'zip', directory)
# encode a file with base64
def encode_file_in_base64(file_path):
with open(file_path, 'rb') as file:
return base64.b64encode(file.read()).decode('utf-8')
# check if there are enough parameters provided.
if len(sys.argv) < 3 or len(sys.argv) > 4:
print("Usage: python3 start-package.py <url> <pkg-dir> [node-id]")
sys.exit(1)
URL = sys.argv[1]
PKG_DIR = os.path.abspath(sys.argv[2])
# If NODE is provided, use it. Otherwise, set it to None.
NODE = sys.argv[3] if len(sys.argv) == 4 else None
parsed_url = urlparse(URL)
HOST = parsed_url.hostname
PORT = parsed_url.port
# parse metadata.json to get the package and publisher
with open(f"{PKG_DIR}/metadata.json", 'r') as f:
metadata = json.load(f)
PACKAGE = metadata['package']
PUBLISHER = metadata['publisher']
PKG_PUBLISHER = f"{PACKAGE}:{PUBLISHER}"
print(PKG_PUBLISHER)
# create zip and put it in /target
parent_dir = os.path.dirname(PKG_DIR)
parent_dir = os.path.abspath(parent_dir)
os.makedirs(os.path.join(parent_dir, 'target'), exist_ok=True)
zip_filename = os.path.join(parent_dir, 'target', PKG_PUBLISHER)
zip_directory(PKG_DIR, zip_filename)
encoded_zip_file = encode_file_in_base64(zip_filename + '.zip')
# create a new package
new_pkg = new_package(PACKAGE, PUBLISHER, encoded_zip_file)
res = send_request("/rpc:sys:uqbar/message", new_pkg)
if not res.status == 200:
print("Failed to send new package request, status: ", res.status)
sys.exit(1)
# install/start/reboot the package
install_pkg = install_package(PACKAGE, PUBLISHER)
resp = send_request("/rpc:sys:uqbar/message", install_pkg)
if not resp.status == 200:
print("Failed to send install package request, status: ", resp.status)
sys.exit(1)
print("Successfully installed package: ", PKG_PUBLISHER)
sys.exit(0)

View File

@ -177,6 +177,12 @@ async fn http_handler(
) -> Result<impl warp::Reply, warp::Rejection> {
// TODO this is all so dirty. Figure out what actually matters.
println!(
"http_server: got request from {:?} for {}\r",
socket_addr,
path.as_str()
);
// trim trailing "/"
let original_path = normalize_path(path.as_str());
let id: u64 = rand::random();
@ -188,6 +194,8 @@ async fn http_handler(
};
let bound_path = route.handler();
println!("here1\r");
if bound_path.authenticated {
let auth_token = serialized_headers
.get("cookie")
@ -198,6 +206,8 @@ async fn http_handler(
}
}
println!("here2\r");
let is_local = socket_addr
.map(|addr| addr.ip().is_loopback())
.unwrap_or(false);
@ -206,6 +216,8 @@ async fn http_handler(
return Ok(warp::reply::with_status(vec![], StatusCode::FORBIDDEN).into_response());
}
println!("here3\r");
// if path has static content, serve it
if let Some(static_content) = &bound_path.static_content {
return Ok(warp::http::Response::builder()
@ -221,6 +233,8 @@ async fn http_handler(
.into_response());
}
println!("here4\r");
// RPC functionality: if path is /rpc:sys:uqbar/message,
// we extract message from base64 encoded bytes in data
// and send it to the correct app.
@ -376,7 +390,7 @@ async fn handle_rpc_message(
async fn maintain_websocket(
ws: WebSocket,
our: Arc<String>,
jwt_secret_bytes: Arc<Vec<u8>>,
_jwt_secret_bytes: Arc<Vec<u8>>,
ws_senders: WebSocketSenders,
send_to_loop: MessageSender,
) {
@ -386,7 +400,7 @@ async fn maintain_websocket(
// channel and verify their identity using JWT. Then we can forward their
// messages to a specific process.
let owner_process: ProcessId = todo!();
let owner_process: ProcessId = ProcessId::new(Some("chess"), "chess2", "uqbar");
let ws_channel_id: u64 = rand::random();
let (ws_sender, mut ws_receiver) = tokio::sync::mpsc::channel(100);
@ -399,17 +413,44 @@ async fn maintain_websocket(
None => {
// stream closed, remove and exit
websocket_close(ws_channel_id, owner_process, &ws_senders, &send_to_loop).await;
return;
break;
}
Some(Err(e)) => {
// stream error, remove and exit
println!("http_server websocket channel error: {e}");
websocket_close(ws_channel_id, owner_process, &ws_senders, &send_to_loop).await;
return;
break;
}
Some(Ok(msg)) => {
// forward message to process associated with this channel
todo!();
let _ = send_to_loop
.send(KernelMessage {
id: rand::random(),
source: Address {
node: our.to_string(),
process: HTTP_SERVER_PROCESS_ID.clone(),
},
target: Address {
node: our.to_string(),
process: owner_process.clone(),
},
rsvp: None,
message: Message::Request(Request {
inherit: false,
expects_response: None,
ipc: serde_json::to_vec(&HttpServerAction::WebSocketPush {
channel_id: ws_channel_id,
message_type: WsMessageType::Binary,
}).unwrap(),
metadata: None,
}),
payload: Some(Payload {
mime: None,
bytes: msg.into_bytes(),
}),
signed_capabilities: None,
})
.await;
}
}
}
@ -421,12 +462,14 @@ async fn maintain_websocket(
// stream error, remove and exit
println!("http_server websocket channel error: {e}");
websocket_close(ws_channel_id, owner_process, &ws_senders, &send_to_loop).await;
return;
break;
}
}
}
}
}
let stream = write_stream.reunite(read_stream).unwrap();
let _ = stream.close().await;
}
async fn websocket_close(
@ -601,7 +644,6 @@ async fn handle_app_message(
} => {
let mut path_bindings = path_bindings.write().await;
if km.source.process != "homepage:homepage:uqbar" {
// TODO ???
path = if path.starts_with('/') {
format!("/{}{}", km.source.process, path)
} else {