diff --git a/.cargo/config.toml b/.cargo/config.toml index cdef3ae2573ed9bca9b2280d17e897773283c6b0..f86bcbdf913aca0dbb998d34cd0ef3706c9cef7b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,7 +5,7 @@ [target.x86_64-unknown-linux-gnu] linker = "clang" -rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"] +rustflags = ["-Clink-arg=-fuse-ld=mold", "-Zshare-generics=y"] # NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager: # `brew install michaeleisel/zld/zld` diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c13b95f1d7f3f6d8943ee16ace1873e696c558c9..0867f1bd693312f180c44299939675f09317500c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,30 +52,30 @@ build-linux: only: - trunk -build-arm64: - image: "r.lcr.gr/microhacks/bevy-builder:arm64" - tags: - - arm64 - stage: build - before_script: - - export CARGO_HOME="${CI_PROJECT_DIR}/.cargo" - - export PATH="${CI_PROJECT_DIR}/.cargo/bin:$PATH" - cache: - key: build-cache-arm64 - paths: - - .cargo/registry/cache - - .cargo/registry/index - - .cargo/git/db - - .cargo/bin/ - - target/ - script: - - cargo build --release -p ${BINARY_FOLDER} --target aarch64-unknown-linux-gnu - artifacts: - expire_in: 1 day - paths: - - target/aarch64-unknown-linux-gnu/release/game_core - only: - - trunk +#build-arm64: +# image: "r.lcr.gr/microhacks/bevy-builder:arm64" +# tags: +# - arm64 +# stage: build +# before_script: +# - export CARGO_HOME="${CI_PROJECT_DIR}/.cargo" +# - export PATH="${CI_PROJECT_DIR}/.cargo/bin:$PATH" +# cache: +# key: build-cache-arm64 +# paths: +# - .cargo/registry/cache +# - .cargo/registry/index +# - .cargo/git/db +# - .cargo/bin/ +# - target/ +# script: +# - cargo build --release -p ${BINARY_FOLDER} --target aarch64-unknown-linux-gnu +# artifacts: +# expire_in: 1 day +# paths: +# - target/aarch64-unknown-linux-gnu/release/game_core +# only: +# - trunk build-web: stage: build @@ -112,16 +112,16 @@ package-all: - cp -r assets dist/assets - cp target/x86_64-unknown-linux-gnu/release/${BINARY_NAME} "dist/${BINARY_NAME}" - cp target/x86_64-pc-windows-gnu/release/${BINARY_NAME}.exe "dist/${BINARY_NAME}.exe" - - cp target/aarch64-unknown-linux-gnu/release/${BINARY_NAME} "dist/${BINARY_NAME}.arm64" +# - cp target/aarch64-unknown-linux-gnu/release/${BINARY_NAME} "dist/${BINARY_NAME}.arm64" - cd "${CI_PROJECT_DIR}/dist" && zip -r "windows.zip" "./${BINARY_NAME}.exe" ./assets - cd "${CI_PROJECT_DIR}/dist" && zip -r "linux.x86.zip" "./${BINARY_NAME}" ./assets - - cd "${CI_PROJECT_DIR}/dist" && zip -r "linux.arm64.zip" "./${BINARY_NAME}.arm64" ./assets +# - cd "${CI_PROJECT_DIR}/dist" && zip -r "linux.arm64.zip" "./${BINARY_NAME}.arm64" ./assets - cd "${CI_PROJECT_DIR}/${BINARY_FOLDER}/dist" && zip -r "web.zip" ./* - cd "${CI_PROJECT_DIR}" && mv "${CI_PROJECT_DIR}/game_core/dist/web.zip" "${CI_PROJECT_DIR}/dist/web.zip" dependencies: - build-windows - build-linux - - build-arm64 +# - build-arm64 - build-web artifacts: expire_in: 7 days @@ -129,7 +129,7 @@ package-all: - dist/web.zip - dist/windows.zip - dist/linux.x86.zip - - dist/linux.arm64.zip +# - dist/linux.arm64.zip only: - trunk diff --git a/Cargo.toml b/Cargo.toml index a1651997580825f71a14895ee1f3d613f49b352c..976981cdc200b08933939cc652d70dcbded14310 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ members = [ "game_core", ] +[profile.dev.package."*"] +opt-level = 3 + [workspace.dependencies] fastrand = "1.8.0" anyhow = "1.0.66" @@ -19,7 +22,7 @@ micro_musicbox = { version = "0.5.0", features = ["mp3"] } micro_banimate = "0.2.1" [workspace.dependencies.bevy] -version = "0.9.0" +version = "0.9.1" default-features = false features = [ "bevy_asset", @@ -32,7 +35,3 @@ features = [ "serialize", "filesystem_watcher" ] - -[profile.release] -debug = 0 -opt-level = 3 diff --git a/Makefile b/Makefile index 2dc4e111f5fabb10dc0a0a26fea2a3e955504ff9..34f9ed63bc73664e0d9baa27b18723e7ab099823 100644 --- a/Makefile +++ b/Makefile @@ -22,10 +22,10 @@ assets: run: RUSTFLAGS="-Awarnings" \ - cargo run --release --features "bevy/dynamic" -p game_core + cargo run -p game_core run-web: - cd game_core && trunk serve --release + cd game_core && trunk serve check: cargo check --release --features "bevy/dynamic" -p game_core diff --git a/build/web/autofocus.js b/build/web/autofocus.js index bd59c5069600d882aa67626d3d1d4c2cb5c8b8c1..e84019d587ad1a69a18886291d8c1db531e54786 100644 --- a/build/web/autofocus.js +++ b/build/web/autofocus.js @@ -12,6 +12,11 @@ document.addEventListener('DOMContentLoaded', function() { if (mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if (node.nodeName.toLowerCase() === 'canvas') { + let viewport = document.querySelector(`meta[name="viewport"]`) + if (viewport) { + viewport.content = 'height=720, width=1280, initial-scale=1, user-scalable=no' + } + node.focus(); if (window.focusHandler) { window.focusHandler.disconnect() diff --git a/build/web/intercept.js b/build/web/intercept.js new file mode 100644 index 0000000000000000000000000000000000000000..eb2ab9ec8d9ae54f0ed64fde8c61ad0ec2c78a68 --- /dev/null +++ b/build/web/intercept.js @@ -0,0 +1,152 @@ +window.originalFetch = window.fetch +window.sessionFetchCache = {} +window.sessionProgressState = {} + +function fetchAndReportProgress(url) { + const outbound = new XMLHttpRequest() + + let defer = {} + let promise = new Promise((resolve, reject) => { + defer = { resolve, reject } + }) + + outbound.onprogress = function(event) { + window.sessionProgressState[url] = event.lengthComputable ? { + indeterminate: false, + loaded: event.loaded, + total: event.total, + complete: event.loaded === event.total, + error: null, + } : { + indeterminate: true, + loaded: 0, + total: 0, + complete: false, + error: null + } + } + outbound.onerror = function(event) { + window.sessionProgressState[url].error = true + defer.reject(new Error("Failed to get")) + } + outbound.onloadstart = function(event) { + window.sessionProgressState[url] = { + indeterminate: !event.lengthComputable, + loaded: event.loaded, + total: event.total, + complete: false, + error: null, + } + } + outbound.onload = function(event) { + window.sessionProgressState[url] = { + indeterminate: !event.lengthComputable, + loaded: event.loaded, + total: event.total, + complete: true, + error: null, + } + window.sessionFetchCache[url] = new Uint8Array(outbound.response) + defer.resolve(window.sessionFetchCache[url]) + } + + outbound.responseType = "arraybuffer" + outbound.open("GET", url, true) + + outbound.send() + + return promise +} + +function checkLoop(url) { + let defer = {} + let promise = new Promise((resolve, reject) => { + defer = { resolve, reject } + }) + + function inner() { + if (window.sessionFetchCache[url]) { + defer.resolve(window.sessionFetchCache[url]) + } else if (window.sessionProgressState[url]) { + const val = window.sessionProgressState[url] + if (val.error) { + defer.reject(new Error("Failed to get")) + } else { + requestAnimationFrame(inner) + } + } else { + defer.reject(new Error("Missing entry")) + } + + } + + requestAnimationFrame(inner) + + return promise +} + +window.fetch = async function interceptedFetch(...params) { + if (params.length === 1 && typeof params[0] === "string") { + const path = params[0] + if (path.endsWith("wasm")) { + try { + const value = await checkLoop(path) + return new Response(value, { + headers: {}, + status: 200, + statusText: 'ok' + }) + } catch(_) { + return window.originalFetch(...params) + } + } + } + return window.originalFetch(...params) +} + +window.interceptPreloads = function() { + let elems = document.querySelectorAll('link[rel="preload"][as="fetch"]') + const checks = [] + elems.forEach(item => { + checks.push(item.href) + fetchAndReportProgress(item.href) + }) + + const start_container = document.getElementById('start_container') + for (const child of Array.from(start_container.children)) { + start_container.removeChild(child) + } + + start_container.innerHTML = `<div style="width: 85%; height: 2.5rem; margin: auto; background-color: lightgray"> + <div id="progressbar" style="width: 0%; height: 2.5rem; background-color: cornflowerblue"></div> +</div>` + + function check_progress() { + let total_progress = 0 + let total_total = 0 + + let is_complete = checks.every(url => window.sessionFetchCache.hasOwnProperty(url)) + + if (is_complete) { + document.getElementById('progressbar').style.width = `100%` + start_container.innerHTML = document.getElementById('content_loaded').innerHTML + window.attachStartListener() + } else { + for (const url of checks) { + let value = window.sessionProgressState[url] + console.log(value) + + if (value) { + total_progress += value.loaded + total_total += value.total + } + } + + const percent = Math.floor((total_progress / total_total) * 100) + document.getElementById('progressbar').style.width = `${percent}%` + requestAnimationFrame(check_progress) + } + } + + check_progress() +} diff --git a/game_core/flit.toml b/game_core/flit.toml index 38de045c1ae3b5ab35209b797ffae883c0893c6d..5a735d6939706c0caa5f08ee8962df26b834a1e2 100644 --- a/game_core/flit.toml +++ b/game_core/flit.toml @@ -2,7 +2,7 @@ action = "wrap" template =''' <script> -document.addEventListener('DOMContentLoaded', () => { +window.attachStartListener = function() { document.getElementById('start_button').addEventListener('click', function() { let element = document.createElement('{{{TAG}}}'); let attr_string = `{{{ATTR}}}` @@ -12,11 +12,15 @@ document.addEventListener('DOMContentLoaded', () => { }); element.innerHTML = `{{{CONTENT}}}`; document.body.appendChild(element); - const remove = ['start_container', 'init-styles'] + const remove = ['start_container', 'init-styles', 'content_loaded'] remove.forEach(function(id) { let container = document.getElementById(id); container.parentNode.removeChild(container); }); }); -}); +}; + +document.addEventListener('DOMContentLoaded', () => { + window.interceptPreloads() +}) </script>''' \ No newline at end of file diff --git a/game_core/index.html b/game_core/index.html index 25f6496a796c87273c5b6173605b2fdbf59f524d..c7e0adedc0fb3bec6f08632dd84364a4cb29658a 100644 --- a/game_core/index.html +++ b/game_core/index.html @@ -4,10 +4,21 @@ <meta charset="utf-8"/> <title>Bevy 2D Template</title> <!-- TODO: Set game name --> <link data-trunk rel="copy-dir" href="../assets"/> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <style> - html, body { + body, + html { margin: 0; padding: 0; + width: 100vw; + height: 100vh; + } + canvas { + display: block; + } + body { + overflow: hidden; } </style> <style id="init-styles"> @@ -16,9 +27,8 @@ } #start_container { - width: auto; + width: 100vw; height: 100vh; - aspect-ratio: 1; display: flex; justify-content: center; align-items: center; @@ -47,8 +57,9 @@ } #start_button { - width: 25%; + width: auto; height: 25%; + aspect-ratio: 1; background-color: black; border-radius: 50%; cursor: pointer; @@ -83,26 +94,27 @@ <body> <main id="start_container"> +</main> + +<div style="display: none" id="content_loaded"> <svg - width="512" - height="512" - viewBox="0 0 512 512" - id="start_button" - xmlns="http://www.w3.org/2000/svg"> + viewBox="0 0 512 512" + id="start_button" + xmlns="http://www.w3.org/2000/svg"> <path - class="play-button-component swircle" - d="M 482.86983,256 A 226.86983,226.86983 0 0 1 256,482.86983 226.86983,226.86983 0 0 1 29.130173,256 226.86983,226.86983 0 0 1 256,29.130173 226.86983,226.86983 0 0 1 482.86983,256 Z"/> + class="play-button-component swircle" + d="M 482.86983,256 A 226.86983,226.86983 0 0 1 256,482.86983 226.86983,226.86983 0 0 1 29.130173,256 226.86983,226.86983 0 0 1 256,29.130173 226.86983,226.86983 0 0 1 482.86983,256 Z"/> <path - class="play-button-component swircle" - d="M 447.06812,256 A 191.06812,191.06812 0 0 1 256,447.06812 191.06812,191.06812 0 0 1 64.931885,256 191.06812,191.06812 0 0 1 256,64.931885 191.06812,191.06812 0 0 1 447.06812,256 Z"/> + class="play-button-component swircle" + d="M 447.06812,256 A 191.06812,191.06812 0 0 1 256,447.06812 191.06812,191.06812 0 0 1 64.931885,256 191.06812,191.06812 0 0 1 256,64.931885 191.06812,191.06812 0 0 1 447.06812,256 Z"/> <path - class="play-button-component" - d="M 194.45831,348.47991 V 163.5198 l 159.08378,91.84706 -126.14795,72.83155 V 232.4989" /> + class="play-button-component" + d="M 194.45831,348.47991 V 163.5198 l 159.08378,91.84706 -126.14795,72.83155 V 232.4989" /> </svg> - <h1>Press Start To Play</h1> -</main> +</div> +<link data-trunk rel="inline" href="../build/web/intercept.js"/> <link data-trunk rel="inline" href="../build/web/audio.js"/> <link data-trunk rel="inline" href="../build/web/autofocus.js"/> diff --git a/game_core/src/system/mod.rs b/game_core/src/system/mod.rs index 309ab329d7595034fdc2a5fcbbe46f67f4859192..5913484f098a9e8e3c2fd63d16c5253e0fad790f 100644 --- a/game_core/src/system/mod.rs +++ b/game_core/src/system/mod.rs @@ -4,3 +4,4 @@ pub mod load_config; pub mod resources; pub mod utilities; pub mod window; +pub mod web; \ No newline at end of file diff --git a/game_core/src/system/resources.rs b/game_core/src/system/resources.rs index fbab91f797256313c1e7b8bbd7b34c6c44854a58..f0741d0d88d4075920c7135c08cbb07e976a41b5 100644 --- a/game_core/src/system/resources.rs +++ b/game_core/src/system/resources.rs @@ -43,6 +43,6 @@ pub fn configure_default_plugins() -> PluginGroupBuilder { pub struct InitAppPlugins; impl PluginGroup for InitAppPlugins { fn build(self) -> PluginGroupBuilder { - configure_default_plugins().add(DefaultResourcesPlugin) + configure_default_plugins().add(DefaultResourcesPlugin).add(super::web::WebPlugin) } } diff --git a/game_core/src/system/web.rs b/game_core/src/system/web.rs new file mode 100644 index 0000000000000000000000000000000000000000..23a311c5a765e42f6a7b103bc128a67d9efc044f --- /dev/null +++ b/game_core/src/system/web.rs @@ -0,0 +1,33 @@ +#[cfg(target_arch = "wasm32")] +mod __plugin { + use bevy::app::{App, Plugin, CoreStage}; + pub fn handle_startup_fullscreen() { + if micro_bevy_web_utils::bindings::is_touch_device() { + micro_bevy_web_utils::bindings::make_selector_fullscreen("canvas".to_string()); + micro_bevy_web_utils::bindings::bind_selector_touch_events("canvas".to_string()); + micro_bevy_web_utils::bindings::orientation_lock("landscape".to_string()); + } + } + + pub struct WebPlugin; + impl Plugin for WebPlugin { + fn build(&self, app: &mut App) { + app.add_startup_system(inner::handle_startup_fullscreen) + .add_system_to_stage( + CoreStage::First, + micro_bevy_web_utils::bevy::emit_touch_events, + ); + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +mod __plugin { + use bevy::app::{App, Plugin}; + pub struct WebPlugin; + impl Plugin for WebPlugin { + fn build(&self, app: &mut App) {} + } +} + +pub use __plugin::WebPlugin; \ No newline at end of file