Skip to content

Commit

Permalink
Merge pull request #33 from pilksoc/backend
Browse files Browse the repository at this point in the history
Add client for cache server
  • Loading branch information
djpiper28 authored Mar 3, 2024
2 parents 21a9d07 + e198cbd commit 390d231
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 85 deletions.
2 changes: 2 additions & 0 deletions backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
futures = { version = "0.3", default-features=false}
serde_repr = "0.1"
reqwest = "0.11.24"
url = "2.5.0"
rand = "0.8.5"
lazy_static = "1.4.0"
118 changes: 118 additions & 0 deletions backend/src/cache_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use crate::kube::Kube;
use crate::recipe::Recipe;
use thiserror::Error;
use reqwest::Url;
use uuid::Uuid;

#[derive(Debug, Error)]
/// Errors while parsing the environment variable for the endpoint URL.
pub enum EnvVarParseError {
#[error("Error while getting environment variable. Have you included an equals sign?")]
/// Used when the environment variable hasn't been set properly (such as a missing equals sign).
EnvironmentError(#[from] std::env::VarError),
#[error("Failed to parse URL, is it valid?")]
/// Used when the URL can't be parsed, usually meaning that it's not been written correctly.
UrlParseError(#[from] url::ParseError)
}

#[derive(Debug, Error)]
/// Errors while communicating with the REST cache server.
pub enum RestError {
#[error("Error while trying to reach REST server.")]
/// Failed to establish connection to the REST server, or got another error (e.g. 404).
ReqwestError(#[from] reqwest::Error),
#[error("Could not parse JSON.")]
/// Couldn't parse the JSON, usually because something else went wrong when trying to establish a connection to the server.
SerdeError(#[from] serde_json::Error),
}

/// A client which interacts with the cache server.
#[derive(Debug)]
pub struct CacheClient {
/// The endpoint URL.
url: Url,
}
impl CacheClient {
/// Create a new client using an environment variable.
///
/// # Errors
/// Will error when the environment variable isn't defined correctly, or if the URL couldn't be parsed.
pub fn new() -> Result<CacheClient, EnvVarParseError> {
let url_string = std::env::var("ENDPOINT")?;
Ok(CacheClient {
url: Url::parse(&url_string)?,
})
}
/// Create a new client using a given endpoint URL.
///
/// # Errors
///
/// Will error if the URL couldn't be parsed.
pub fn from_url(url: &str) -> Result<CacheClient, url::ParseError> {
Ok(CacheClient {
url: Url::parse(url)?,
})
}
/// Gets all the Kubes from the cache server.
///
/// # Errors
///
/// If the REST server connection fails, or if the output is an error (like 404), then this will return an Err.
pub async fn get_kubes(&self) -> Result<Vec<Kube>, RestError> {
let res = reqwest::get(self.url.join("kubes").unwrap()).await?.text().await?;
Ok(serde_json::from_str(&res)?)
}
/// Gets all the recipes from the cache server.
///
/// # Errors
///
/// If the REST server connection fails, or if the output is an error (like 404), then this will return an Err.
pub async fn get_recipes(&self) -> Result<Vec<Recipe>, RestError> {
let res = reqwest::get(self.url.join("kubeRecipes").unwrap()).await?.text().await?;
Ok(serde_json::from_str(&res)?)
}
/// Gets a Kube by its ID from the cache server.
///
/// # Errors
///
/// If the REST server connection fails, or if the output is an error (like 404), then this will return an Err.
pub async fn get_kube_by_id(&self, id: Uuid) -> Result<Kube, RestError> {
let res = reqwest::get(self.url.join(format!("kubeById/{}", id).as_str()).unwrap()).await?.text().await?;
Ok(serde_json::from_str(&res)?)
}
/// Gets a recipe by its ID from the cache server.
///
/// # Errors
///
/// If the REST server connection fails, or if the output is an error (like 404), then this will return an Err.
pub async fn get_recipe_by_id(&self, id1: Uuid, id2: Uuid) -> Result<Recipe, RestError> {
let res = reqwest::get(self.url.join(format!("kubeRecipeByIds/{}/{}", id1, id2).as_str()).unwrap()).await?.text().await?;
Ok(serde_json::from_str(&res)?)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn try_get_kubes() {
let client = CacheClient::from_url("https://hack.djpiper28.co.uk/cache/").unwrap();
let mut kubes = client.get_kubes().await.unwrap();
let control_kubes_string = String::from(
"[{\"name\":\"hydrogen\",\"id\":\"cdede93f-d0d7-4b4a-9fde-2a909444c58d\"},{\"name\":\"oxygen\",\"id\":\"8ddcf7ad-61f6-47ff-a49c-4abcec21d6a1\"},{\"name\":\"nitrogen\",\"id\":\"59a64f5b-bcf4-4d2d-bb7f-bc4eceaf41e5\"},{\"name\":\"calcium\",\"id\":\"2b006956-063d-4ca2-b75d-f6e5d67455c9\"},{\"name\":\"iron\",\"id\":\"5e930e14-4597-49b3-95fa-3e6dcc40b1ae\"},{\"name\":\"aluminium\",\"id\":\"d076033a-c583-4d38-8094-249a7fe2972b\"},{\"name\":\"uranium\",\"id\":\"82ac0ed4-62e3-4c5e-af3e-024c38285227\"},{\"name\":\"sodium\",\"id\":\"1c7fda1b-af90-411d-8162-8fd04c4890d3\"},{\"name\":\"chlorine\",\"id\":\"061f6efd-0067-4d71-92b8-a0b58562b906\"},{\"name\":\"light\",\"id\":\"e38ba705-58c1-469d-8eff-7cc01b94fd46\"},{\"name\":\"time\",\"id\":\"6991989a-f347-48eb-8c67-ade4cdc010d0\"},{\"name\":\"silicon\",\"id\":\"72650f96-011b-404d-aba0-2c8aa2f17aeb\"},{\"name\":\"water\",\"id\":\"8cf89e77-bf8b-4cf4-9941-46b853df4480\"},{\"name\":\"tap water\",\"id\":\"5ca230bc-135e-4be8-8be3-7c7c1b3e5484\"},{\"name\":\"salt\",\"id\":\"540710d4-5d7d-42f6-b9b7-aad181affbcf\"},{\"name\":\"sea water\",\"id\":\"74102256-00b5-42aa-9665-1bc81f13c18b\"},{\"name\":\"air\",\"id\":\"88bb179c-0d91-4322-a4e5-d3862bf83a31\"},{\"name\":\"rust\",\"id\":\"ad92587b-1643-469e-8357-1d6ec5ab6380\"},{\"name\":\"feldspar\",\"id\":\"2adfa430-faa4-4ecd-95e1-c6cc8c85f3b5\"},{\"name\":\"sand\",\"id\":\"1db355de-1404-4e7c-bf8b-a6b3355b9dc4\"},{\"name\":\"dirt\",\"id\":\"649e8325-530b-4abb-8fe0-5b79b395e84f\"},{\"name\":\"beach\",\"id\":\"03f9f164-be03-4de3-b5b4-c7549c1ef9e4\"},{\"name\":\"earth\",\"id\":\"66744f80-ccec-4c8b-9025-9edbb75df0a6\"},{\"name\":\"life\",\"id\":\"abd2f9a5-34cd-4b3f-941f-8cf934f6b967\"},{\"name\":\"age\",\"id\":\"bda3c6c5-3b79-418d-8d24-cc533a509065\"},{\"name\":\"energy\",\"id\":\"b4eba917-2179-4cd4-a64e-316fe005f11e\"},{\"name\":\"rock\",\"id\":\"3c545935-1382-4c3d-9771-1fa8492f1b77\"},{\"name\":\"fire\",\"id\":\"1e2e14df-f36b-43a4-9a2a-5112f84abb52\"},{\"name\":\"glass\",\"id\":\"12ec3f8d-0986-42d7-acc2-e6af8ba1842a\"}]"
);
let mut control_kubes: Vec<Kube> = serde_json::from_str(&control_kubes_string).unwrap();
kubes.sort();
control_kubes.sort();
assert_eq!(control_kubes, kubes)
}
#[tokio::test]
async fn try_kube_by_id() {
let client = CacheClient::from_url("https://hack.djpiper28.co.uk/cache/").unwrap();
let kube = client.get_kube_by_id(Uuid::parse_str("5e930e14-4597-49b3-95fa-3e6dcc40b1ae").unwrap()).await.unwrap();
let expected_string = String::from("{\"name\":\"iron\",\"id\":\"5e930e14-4597-49b3-95fa-3e6dcc40b1ae\"}");
let expected_kube: Kube = serde_json::from_str(&expected_string).unwrap();
assert_eq!(expected_kube, kube);
}
}
36 changes: 29 additions & 7 deletions backend/src/grid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ fn distance([a, b]: Coordinate, [x, y]: Coordinate) -> u64 {
(a.abs_diff(x)).max(b.abs_diff(y))
}

#[derive(Debug, Clone, Copy)]
/// The direction in which to grow. For example, going from 3 to 9 would give a `GrowDirection::Expand`.
enum GrowDirection {
Shrink,
Expand Down Expand Up @@ -56,6 +57,10 @@ impl Grid {
pub fn insert(&mut self, space: Space) {
self.spaces.insert(space.coordinate, space);
}
/// Removes the space at the given coordinate. If there is no recorded space there, then this returns [`std::option::Option::None`].
pub fn remove(&mut self, coordinate: Coordinate) -> Option<Space> {
self.spaces.remove(&coordinate)
}
/// Checks that a coordinate is not beyond the bounds of the grid.
pub fn in_bounds(&self, coordinate: [u64; 2]) -> bool {
coordinate[0] < self.width && coordinate[1] < self.height
Expand All @@ -67,7 +72,7 @@ impl Grid {
/// ```rust
/// use cosmic_kube::grid::Grid;
/// use cosmic_kube::space::{ Space, SpaceKind };
///
///
/// let grid = Grid::from_spaces(
/// vec![
/// Space::new([0, 2], SpaceKind::EmptySpace),
Expand All @@ -83,6 +88,9 @@ impl Grid {
self.spaces.get(&coordinate)
}

/// Gets the neighbours that are *n* squares away.
///
/// In other words, this will look at all the rings which are 1, 2, …, n squares away from the coordinate. It will return any squares found in these rings.
pub fn get_neighbours_n_away(&self, coordinate: Coordinate, n: u64) -> Vec<&Space> {
let mut coords: Vec<Coordinate> = Vec::new();
let mut stack: Vec<Coordinate> = self.neighbour_coords_in_bounds(coordinate);
Expand All @@ -97,18 +105,18 @@ impl Grid {
.filter(|c|
distance(coordinate, **c) <= n && distance(coordinate, **c) > distance(coord, **c)
)
)
);
}
let mut to_return: Vec<&Space> = Vec::new();
for coord in coords {
match self.spaces.get(&coord) {
Some(space) => to_return.push(space),
None => (),
if let Some(space) = self.spaces.get(&coord) {
to_return.push(space)
}
}
to_return
}

/// Gets all the neighbours (directly adjacent squares incl. diagonally) of the coordinate if they're in bounds.
fn neighbour_coords_in_bounds(&self, coordinate: Coordinate) -> Vec<Coordinate> {
let coordinates: [[Option<u64>; 2]; 8] = [
[coordinate[0].checked_add(1), Some(coordinate[1])],
Expand All @@ -129,7 +137,7 @@ impl Grid {
.collect::<Vec<_>>()
}

/// Returns neighbours which are in the grid *and* which aren't [`crate::space::SpaceKind::EmptySpace`]s.
/// Returns neighbours (adjacent incl. diagonally) which are in the grid *and* which aren't [`crate::space::SpaceKind::EmptySpace`]s.
pub fn get_nonempty_neighbours(&self, coordinate: Coordinate) -> Vec<&Space> {
self.neighbour_coords_in_bounds(coordinate).iter()
.map(|coord| self.get_space(*coord))
Expand Down Expand Up @@ -206,13 +214,27 @@ impl Grid {
}

/// This will expand the grid size and change the coordinates of the respective kubes.
///
///
/// The change in size can be thought of as "rings of squares" to be added around the outside of the grid. So if the grid used to be a 2×2 grid, adding a ring of squares around the outside will give a 4×4 square.
///
/// <div class="warning">Warning! This function may use a lot of memory! When resizing the grid, the program needs to copy all of the grid's contents to a new `Vec`. If the grid is densely populated then take care when calling it.</div>
///
/// # Errors
///
/// Will fail if the resize request is invalid, if the new grid will be too big, (bigger than 2^64 - 1).
pub fn expand_grid(&mut self, rings_to_add: u64) -> Result<(), ResizeError> {
self.change_grid_by_rings(rings_to_add, GrowDirection::Expand)
}

/// Like [`crate::grid::Grid::expand_grid`], but instead of expanding, this shrinks.
///
/// <div class="warning">Warning! This function may use a lot of memory! When resizing the grid, the program needs to copy all of the grid's contents to a new `Vec`. If the grid is densely populated then take care when calling it.</div>
///
/// # Errors
///
/// Will fail if the resize request is invalid.
/// - If the new grid will be too small, (smaller than 1).
/// - If there is a Kube near the edge such that it would be "cut off" when the rings are removed.
pub fn shrink_grid(&mut self, rings_to_shrink_by: u64) -> Result<(), ResizeError> {
self.change_grid_by_rings(rings_to_shrink_by, GrowDirection::Shrink)
}
Expand Down
57 changes: 32 additions & 25 deletions backend/src/kube.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,48 @@
use std::str::FromStr;

use uuid::Uuid;
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
pub struct KubeId {
uuid: Uuid,
}

impl KubeId {
pub fn new(name: &str) -> Self {
let mut name = name.to_string();
name.push_str("kube");
KubeId {
uuid: Uuid::new_v5(&Uuid::NAMESPACE_DNS, name.as_bytes()),
}
}

pub fn as_u128(&self) -> u128 {
self.uuid.as_u128()
}

pub fn uuid(&self) -> Uuid {
self.uuid
}
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Kube {
pub id: KubeId,
pub id: Uuid,
pub name: String,
}
impl Kube {
pub fn new(name: String) -> Kube {
let mut name_uuid = name.clone();
name_uuid.push_str("kube");
Kube {
id: KubeId::new(name.as_str()),
id: Uuid::new_v5(&Uuid::NAMESPACE_DNS, name_uuid.as_bytes()),
name,
}
}
pub fn from_name_uuid(name: &str, uuid: &str) -> Result<Kube, <Uuid as FromStr>::Err> {
Ok(Kube {
id: Uuid::from_str(uuid)?,
name: name.to_string()
})
}
}

// we should have a placeholder ''loading'' cube we can send over if api is slow

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_kubes() {
let input_json = String::from(
"[{\"name\":\"hydrogen\",\"id\":\"153227c6-c069-4748-b5aa-aafac8abef00\"},{\"name\":\"oxygen\",\"id\":\"3ffe4c9e-5d35-42c9-a70e-1d80c544bdbb\"},{\"name\":\"nitrogen\",\"id\":\"bb155bf1-7200-45e7-b126-f2882f7aecaa\"}]"
);
let mut expected: Vec<Kube> = vec![
Kube::from_name_uuid("hydrogen", "153227c6-c069-4748-b5aa-aafac8abef00").unwrap(),
Kube::from_name_uuid("oxygen", "3ffe4c9e-5d35-42c9-a70e-1d80c544bdbb").unwrap(),
Kube::from_name_uuid("nitrogen", "bb155bf1-7200-45e7-b126-f2882f7aecaa").unwrap(),
];
let mut kubes: Vec<Kube> = serde_json::from_str(&input_json).unwrap();
expected.sort();
kubes.sort();
assert_eq!(expected, kubes);
}
}
2 changes: 2 additions & 0 deletions backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub mod kube;
pub mod player;
pub mod llm;
pub mod local_grid;
pub mod cache_client;
pub mod recipe;

#[macro_use]
extern crate lazy_static;
Expand Down
52 changes: 0 additions & 52 deletions backend/src/llm.rs

This file was deleted.

12 changes: 12 additions & 0 deletions backend/src/recipe.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::kube::Kube;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Recipe {
id: Uuid,
output_id: Uuid,
output_kube: Kube,
kube1_id: Uuid,
kube2_id: Uuid,
}
2 changes: 1 addition & 1 deletion backend/src/ws.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub async fn client_connection(ws: WebSocket, clients: Clients) {


// ->recieve client game info <- send back client game state
// wwwwwwwwwwwwwwwwwwwww i am so tired
// wwwwwwwwwwwwwwwwwwwww i am so tired
async fn client_msg(client_id: &str, msg: Message, clients: &Clients) {
println!("received message from {}: {:?}", client_id, msg); //debug

Expand Down

0 comments on commit 390d231

Please sign in to comment.