diff --git a/.github/workflows/build-game-client.yml b/.github/workflows/build-game-client.yml new file mode 100644 index 0000000..07f8457 --- /dev/null +++ b/.github/workflows/build-game-client.yml @@ -0,0 +1,62 @@ +name: "Build CosmicKube game client" +on: + push: + branches: + - main + +env: + GODOT_VERSION: 4.2.1 + EXPORT_NAME: CosmicKube + PROJECT_PATH: game-source + +jobs: + export-web: + name: "Export for Web" + runs-on: ubuntu-20.04 + container: + image: barichello/godot-ci:4.2.1 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + - name: Setup + run: | + mkdir -v -p ~/.local/share/godot/export_templates/ + mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable + - name: Web Build + run: | + mkdir -v -p build/web + cd $PROJECT_PATH + godot --headless --verbose --export-release "Web" ../build/web/index.html 2>&1 | tee output.txt + echo Reading build logs... + if search="$(cat output.txt | grep 'ERROR: Project export')" + then + echo "Build failed!" + exit 1 + else + echo "Build succeeded!" + exit 0 + fi ; + - name: Create staticwebapp.config.json + run: | + cd build/web + echo "${{ vars.STATIC_WEB_APP_CONFIG }}" > staticwebapp.config.json + ls + - name: Upload Artifact + uses: actions/upload-artifact@v1 + with: + name: web + path: build/web + + - name: Publish to Azure Static Web Apps + id: publishto + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} + repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for GitHub integrations (i.e. PR comments) + action: "upload" + ###### Repository/Build Configurations ###### + app_location: "build/web" + skip_app_build: true + ###### End of Repository/Build Configurations ###### \ No newline at end of file diff --git a/.github/workflows/test-jenkinsfile.yml.noworky b/.github/workflows/test-jenkinsfile.yml.noworky new file mode 100644 index 0000000..31712e7 --- /dev/null +++ b/.github/workflows/test-jenkinsfile.yml.noworky @@ -0,0 +1,18 @@ +name: "Test Jenkinsfile" +on: push + +env: + GODOT_VERSION: 4.2.1 + EXPORT_NAME: CosmicKube + PROJECT_PATH: game-source + +jobs: + test-jenkins: + name: "Test Jenkinsfile" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@master + - name: jenkinsfile-runner-prepackaged + uses: jenkinsci/jenkinsfile-runner-github-actions/jenkinsfile-runner-prepackaged@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 51f57d7..5076dab 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,24 +1,38 @@ pipeline { - agent any + agent { + docker { + image 'barichello/godot-ci:4.2.1' + + } } + environment { + GODOT_VERSION = '4.2.1' + EXPORT_NAME = 'CosmicKube' + PROJECT_PATH = 'game-source' + } stages { - stage('Hello world') { + stage('Setup') { steps { - sh 'echo pee pee' + sh '''mkdir -v -p ~/.local/share/godot/export_templates/ + mv /root/.local/share/godot/export_templates/${GODOT_VERSION}.stable ~/.local/share/godot/export_templates/${GODOT_VERSION}.stable + ''' + } + } + stage('Web Build') { + steps { + sh '''mkdir -v -p build/web + cd $PROJECT_PATH + godot --headless --verbose --export-release "Web" ../build/web/index.html 2>&1 | tee output.txt + echo Reading build logs... + if search="$(cat output.txt | grep 'ERROR: Project export')" + then + echo "Build failed!" + exit 1 + else + echo "Build succeeded!" + exit 0 + fi ;''' } } - - // stage('Push image') { - // steps { - // sh 'docker push localhost:5000/ttt' - // } - // } - // - // stage('Package') { - // steps { - // sh 'helm install ttt ttt | true' - // sh 'helm upgrade ttt ttt' - // } - // } } -} +} \ No newline at end of file diff --git a/backend/Cargo.toml b/backend/Cargo.toml index da37338..7e278a8 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -11,4 +11,9 @@ diesel = { version = "2.1.0", features = ["postgres"] } dotenvy = "0.15" uuid = { version = "1.7.0", features = ["v5", "fast-rng", "macro-diagnostics"] } tokio = { version = "1", features = ["full"] } +tokio-stream = "0.1.6" async-trait = "0.1.77" +warp = "0.3" +serde = { version = "1.0", features = ["derive"]} +serde_json = "1.0" +futures = { version = "0.3", default-features=false} diff --git a/backend/src/handlers.rs b/backend/src/handlers.rs new file mode 100644 index 0000000..c2a2eb3 --- /dev/null +++ b/backend/src/handlers.rs @@ -0,0 +1,9 @@ +use crate::{ws, Clients, Result}; +use warp::Reply; + +pub async fn ws_handler(ws: warp::ws::Ws, clients: Clients) -> Result +{ + println!("ws_handler"); //debug + + Ok(ws.on_upgrade(move |socket| ws::client_connection(socket, clients))) +} \ No newline at end of file diff --git a/backend/src/kube.rs b/backend/src/kube.rs index e7554b2..5c82bcd 100644 --- a/backend/src/kube.rs +++ b/backend/src/kube.rs @@ -32,3 +32,5 @@ impl Kube { } } } + +// we should have a placeholder ''loading'' cube we can send over if api is slow diff --git a/backend/src/main.rs b/backend/src/main.rs index e7a11a9..5e9410d 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,3 +1,39 @@ -fn main() { - println!("Hello, world!"); +use std::{collections::HashMap, convert::Infallible, sync::Arc}; +use tokio::sync::{mpsc, Mutex}; +use warp::{filters::ws::Message, Filter, Rejection}; + +mod handlers; +mod ws; + +// type that represents a connecting client +#[derive(Debug, Clone)] +pub struct Client { + pub client_id: String, + pub sender: Option>>, } + +// type aliases! +type Clients = Arc>>; +type Result = std::result::Result; + +#[tokio::main] +async fn main() { + + //initialise a hashmap to store currently connected clients. We may want some more logic here if we want currently connected clients to be stored somewhere + let clients: Clients = Arc::new(Mutex::new(HashMap::new())); + + println!("configuring websocket route"); //debug + let ws_route = warp::path("ws") + .and(warp::ws()) + .and(with_clients(clients.clone())) + .and_then(handlers::ws_handler); + + let routes = ws_route.with(warp::cors().allow_any_origin()); + println!("starting server"); //debug + warp::serve(routes).run(([127, 0, 0, 1], 8000)).await; +} + +fn with_clients(clients: Clients) -> impl Filter + Clone { + warp::any().map(move || clients.clone()) +} + diff --git a/backend/src/websocketstructs.rs b/backend/src/websocketstructs.rs new file mode 100644 index 0000000..8b5017d --- /dev/null +++ b/backend/src/websocketstructs.rs @@ -0,0 +1,15 @@ + +// these are the structs intended for use when communicating via websockets + +// this is the data we expect to recieve from the player +pub struct PlayerInfo { + player: Player, //the player requesting the data + coordinates: [u64; 2], //current player coordinates + action: String, //PLACEHOLDER! we need to know what the player is doing. +} + +// this is the data we expect to send to the player +pub struct GameState { + grid: String, //PLACEHOLDER! This will be the partial grid state type +} + diff --git a/backend/src/ws.rs b/backend/src/ws.rs new file mode 100644 index 0000000..ae1c19a --- /dev/null +++ b/backend/src/ws.rs @@ -0,0 +1,81 @@ +use crate::{Client, Clients}; +use futures::{FutureExt, StreamExt}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::UnboundedReceiverStream; +use uuid::Uuid; +use warp::ws::{Message, WebSocket}; + +pub async fn client_connection(ws: WebSocket, clients: Clients) { + println!("establishing client connection... {:?}", ws); //debug + + // splitting the WebSocket stream object into separate 'Sink' and 'Stream' objects. + // This lets us split up the logic of sending and recieving tasks + // 'Stream' lets us recieve messages from the client + // 'Sink' letes us establish a connection from the unbounded channel + let (client_ws_sender, mut client_ws_rcv) = ws.split(); + // creates an unbounded channel. It is configured to send messages to the client. + let (client_sender, client_rcv) = mpsc::unbounded_channel(); + + let client_rcv = UnboundedReceiverStream::new(client_rcv); + + // 'spawns' a new task, that stays alive until the client has disconnected. + tokio::task::spawn(client_rcv.forward(client_ws_sender).map(|result| { + if let Err(e) = result { + println!("error sending websocket msg: {}", e); + } + })); + + // creating a new uuid to use as the key in the 'clients' hashmap, and a new instance of a 'client' + let uuid = Uuid::new_v4().simple().to_string(); + + let new_client = Client { + client_id: uuid.clone(), + //the client_sender object is stored within this new client instance so that we can send messages to this connected client in other parts of the code + sender: Some(client_sender), + }; + + //obtains a lock on the client list and inserts the new client into the hashmap using the uuid as the key. + clients.lock().await.insert(uuid.clone(), new_client); + + // creates a loop that handles incoming messages from the client + while let Some(result) = client_ws_rcv.next().await { + let msg = match result { + Ok(msg) => msg, + Err(e) => { + println!("error receiving message for id {}): {}", uuid.clone(), e); + break; + } + }; + client_msg(&uuid, msg, &clients).await; + } + + // as the above will keep running as long as the client is active, when we exit the loop we can safely remove this client instance from the hashmap. + clients.lock().await.remove(&uuid); + println!("{} disconnected", uuid); //debug +} + +// example function to respond to a clients message, this just responds to 'ping!' with 'pong!', but later we will replace this with; +// ->recieve client game info <- send back client game state +// wwwwwwwwwwwwwwwwwwwww i am so tired +async fn client_msg(client_id: &str, msg: Message, clients: &Clients) { + println!("received message from {}: {:?}", client_id, msg); //debug + + let message = match msg.to_str() { + Ok(v) => v, + Err(_) => return, + }; + + if message == "ping" || message == "ping\n" { + let locked = clients.lock().await; + match locked.get(client_id) { + Some(v) => { + if let Some(sender) = &v.sender { + println!("sending pong"); + let _ = sender.send(Ok(Message::text("pong"))); + } + } + None => return, + } + return; + }; +} \ No newline at end of file diff --git a/client.Dockerfile b/client.Dockerfile new file mode 100644 index 0000000..61f558e --- /dev/null +++ b/client.Dockerfile @@ -0,0 +1,58 @@ +FROM ubuntu:jammy +LABEL author="https://github.com/aBARICHELLO/godot-ci/graphs/contributors" + +USER root +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + git-lfs \ + unzip \ + wget \ + zip \ + adb \ + openjdk-17-jdk-headless \ + rsync \ + && rm -rf /var/lib/apt/lists/* + +ARG GODOT_VERSION="4.2.1" +ARG RELEASE_NAME="stable" +ARG SUBDIR="" +ARG GODOT_TEST_ARGS="" +ARG GODOT_PLATFORM="linux.x86_64" + +RUN wget https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}${SUBDIR}/Godot_v${GODOT_VERSION}-${RELEASE_NAME}_${GODOT_PLATFORM}.zip \ + && wget https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}${SUBDIR}/Godot_v${GODOT_VERSION}-${RELEASE_NAME}_export_templates.tpz \ + && mkdir ~/.cache \ + && mkdir -p ~/.config/godot \ + && mkdir -p ~/.local/share/godot/export_templates/${GODOT_VERSION}.${RELEASE_NAME} \ + && unzip Godot_v${GODOT_VERSION}-${RELEASE_NAME}_${GODOT_PLATFORM}.zip \ + && mv Godot_v${GODOT_VERSION}-${RELEASE_NAME}_${GODOT_PLATFORM} /usr/local/bin/godot \ + && unzip Godot_v${GODOT_VERSION}-${RELEASE_NAME}_export_templates.tpz \ + && mv templates/* ~/.local/share/godot/export_templates/${GODOT_VERSION}.${RELEASE_NAME} \ + && rm -f Godot_v${GODOT_VERSION}-${RELEASE_NAME}_export_templates.tpz Godot_v${GODOT_VERSION}-${RELEASE_NAME}_${GODOT_PLATFORM}.zip + +# Download and setup android-sdk +ENV ANDROID_HOME="/usr/lib/android-sdk" +RUN wget https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip \ + && unzip commandlinetools-linux-*_latest.zip -d cmdline-tools \ + && mv cmdline-tools $ANDROID_HOME/ \ + && rm -f commandlinetools-linux-*_latest.zip + +ENV PATH="${ANDROID_HOME}/cmdline-tools/cmdline-tools/bin:${PATH}" + +RUN yes | sdkmanager --licenses \ + && sdkmanager "platform-tools" "build-tools;33.0.2" "platforms;android-33" "cmdline-tools;latest" "cmake;3.22.1" "ndk;25.2.9519653" + +# Adding android keystore and settings +RUN keytool -keyalg RSA -genkeypair -alias androiddebugkey -keypass android -keystore debug.keystore -storepass android -dname "CN=Android Debug,O=Android,C=US" -validity 9999 \ + && mv debug.keystore /root/debug.keystore + +RUN godot -v -e --quit --headless ${GODOT_TEST_ARGS} +RUN echo 'export/android/android_sdk_path = "/usr/lib/android-sdk"' >> ~/.config/godot/editor_settings-4.tres +RUN echo 'export/android/debug_keystore = "/root/debug.keystore"' >> ~/.config/godot/editor_settings-4.tres +RUN echo 'export/android/debug_keystore_user = "androiddebugkey"' >> ~/.config/godot/editor_settings-4.tres +RUN echo 'export/android/debug_keystore_pass = "android"' >> ~/.config/godot/editor_settings-4.tres +RUN echo 'export/android/force_system_user = false' >> ~/.config/godot/editor_settings-4.tres +RUN echo 'export/android/timestamping_authority_url = ""' >> ~/.config/godot/editor_settings-4.tres +RUN echo 'export/android/shutdown_adb_on_exit = true' >> ~/.config/godot/editor_settings-4.tres \ No newline at end of file diff --git a/game-source/export_presets.cfg b/game-source/export_presets.cfg new file mode 100644 index 0000000..0078c93 --- /dev/null +++ b/game-source/export_presets.cfg @@ -0,0 +1,37 @@ +[preset.0] + +name="Web" +platform="Web" +runnable=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +variant/extensions_support=false +vram_texture_compression/for_desktop=true +vram_texture_compression/for_mobile=true +html/export_icon=true +html/custom_html_shell="" +html/head_include="" +html/canvas_resize_policy=2 +html/focus_canvas_on_start=true +html/experimental_virtual_keyboard=false +progressive_web_app/enabled=false +progressive_web_app/offline_page="" +progressive_web_app/display=1 +progressive_web_app/orientation=0 +progressive_web_app/icon_144x144="" +progressive_web_app/icon_180x180="" +progressive_web_app/icon_512x512="" +progressive_web_app/background_color=Color(0, 0, 0, 1) diff --git a/game-source/project.godot b/game-source/project.godot index f937ed2..d64f5ba 100644 --- a/game-source/project.godot +++ b/game-source/project.godot @@ -19,3 +19,4 @@ config/icon="res://icon.svg" renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility" +textures/vram_compression/import_etc2_astc=true diff --git a/plugins.txt b/plugins.txt new file mode 100644 index 0000000..e69de29