Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: setup bevy_remote_asset #19

Merged
merged 2 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 6 additions & 15 deletions .github/workflows/ci-rs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ concurrency:

jobs:
ci-rs:
# revert later
# runs-on: ubuntu-latest
runs-on: macos-latest
runs-on: [ macos-latest, ubuntu-latest]
strategy:
fail-fast: false
matrix:
Expand All @@ -44,22 +42,15 @@ jobs:
components: rustfmt, clippy
enable-sccache: "true"

# - name: Install binaries
# run: sudo apt-get update && sudo apt-get install -y clang pkg-config libx11-dev libasound2-dev libudev-dev libxkbcommon-x11-0 gcc-multilib
- name: Install binaries
run: sudo apt-get update && sudo apt-get install -y clang pkg-config libx11-dev libasound2-dev libudev-dev libxkbcommon-x11-0 gcc-multilib

- name: Build
run: cargo build --release # --verbose
run: cargo build --release --features docker

- name: Test
run: cargo test --release # --verbose
run: cargo test --release

- name: Lint
run: cargo fmt --all -- --check
# && cargo clippy --verbose -- -D warnings

# - name: Bloat Check
# uses: cs50victor/cargo-bloat-action@master
# with:
# token: ${{ secrets.GITHUB_TOKEN }}
# kv_token: ${{ secrets.KV_TOKEN }}
# included_packages: "lkgpt"
# && cargo clippy --verbose -- -D warnings
10 changes: 5 additions & 5 deletions crates/bevy_headless/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ edition = "2021"
rust-version.workspace = true

[dependencies]
anyhow = { workspace = true }
base64 = { workspace = true }
bevy = { workspace = true }
anyhow.workspace = true
base64.workspace = true
bevy.workspace = true
bytemuck = "1.14.1"
futures = "0.3.30"
futures-lite = "2.2.0"
image = { version = "0.24.8", features = ["exr", "png", "jpeg", "webp"], default-features = false }
wgpu = { workspace = true }
log = { workspace = true }
wgpu.workspace = true
log.workspace = true
parking_lot = "0.12.1"
10 changes: 10 additions & 0 deletions crates/bevy_remote_asset/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "bevy_remote_asset"
version = "0.1.0"
edition = "2021"
rust-version.workspace = true

[dependencies]
bevy.workspace = true
pin-project = "1.1.3"
surf = {version = "2.3", default-features = false, features = ["h1-client-rustls"]}
7 changes: 7 additions & 0 deletions crates/bevy_remote_asset/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// derived from https://github.com/johanhelsing/bevy_web_asset

mod web_asset_plugin;
mod web_asset_source;

pub use web_asset_plugin::WebAssetPlugin;
pub use web_asset_source::WebAssetReader;
33 changes: 33 additions & 0 deletions crates/bevy_remote_asset/src/web_asset_plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use bevy::prelude::*;

use crate::web_asset_source::*;
use bevy::asset::io::AssetSource;

/// Add this plugin to bevy to support loading http and https urls.
///
/// Needs to be added before Bevy's `DefaultPlugins`.
///
/// # Example
///
/// ```no_run
/// # use bevy::prelude::*;
/// # use bevy_web_asset::WebAssetPlugin;
///
/// let mut app = App::new();
///
/// app.add_plugins((
/// WebAssetPlugin::default(),
/// DefaultPlugins
/// ));
/// ```
#[derive(Default)]
pub struct WebAssetPlugin;

impl Plugin for WebAssetPlugin {
fn build(&self, app: &mut App) {
app.register_asset_source(
"https",
AssetSource::build().with_reader(|| Box::new(WebAssetReader::Https)),
);
}
}
143 changes: 143 additions & 0 deletions crates/bevy_remote_asset/src/web_asset_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use bevy::{asset::io::PathStream, utils::BoxedFuture};
use std::path::{Path, PathBuf};

use bevy::asset::io::{AssetReader, AssetReaderError, Reader};

/// Treats paths as urls to load assets from.
pub enum WebAssetReader {
/// Use TLS for setting up connections.
Https,
}

impl WebAssetReader {
fn make_uri(&self, path: &Path) -> PathBuf {
PathBuf::from(match self {
Self::Https => "https://",
})
.join(path)
}

/// See [bevy::asset::io::get_meta_path]
fn make_meta_uri(&self, path: &Path) -> PathBuf {
let mut uri = self.make_uri(path);
let mut extension =
path.extension().expect("asset paths must have extensions").to_os_string();
extension.push(".meta");
uri.set_extension(extension);
uri
}
}

async fn get<'a>(path: PathBuf) -> Result<Box<Reader<'a>>, AssetReaderError> {
use std::{
future::Future,
io,
pin::Pin,
task::{Context, Poll},
};

use bevy::asset::io::VecReader;
use surf::StatusCode;

#[pin_project::pin_project]
struct ContinuousPoll<T>(#[pin] T);

impl<T: Future> Future for ContinuousPoll<T> {
type Output = T::Output;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Always wake - blocks on single threaded executor.
cx.waker().wake_by_ref();

self.project().0.poll(cx)
}
}

let str_path = path.to_str().ok_or_else(|| {
AssetReaderError::Io(io::Error::new(
io::ErrorKind::Other,
format!("non-utf8 path: {}", path.display()),
))
})?;
let mut response = ContinuousPoll(surf::get(str_path)).await.map_err(|err| {
AssetReaderError::Io(io::Error::new(
io::ErrorKind::Other,
format!(
"unexpected status code {} while loading {}: {}",
err.status(),
path.display(),
err.into_inner(),
),
))
})?;

match response.status() {
StatusCode::Ok => Ok(Box::new(VecReader::new(
ContinuousPoll(response.body_bytes())
.await
.map_err(|_| AssetReaderError::NotFound(path.to_path_buf()))?,
)) as _),
StatusCode::NotFound => Err(AssetReaderError::NotFound(path)),
code => Err(AssetReaderError::Io(io::Error::new(
io::ErrorKind::Other,
format!("unexpected status code {} while loading {}", code, path.display()),
))),
}
}

impl AssetReader for WebAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(get(self.make_uri(path)))
}

fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(get(self.make_meta_uri(path)))
}

fn is_directory<'a>(
&'a self,
_path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
Box::pin(async { Ok(false) })
}

fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
Box::pin(async { Err(AssetReaderError::NotFound(self.make_uri(path))) })
}
}

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

#[test]
fn make_https_uri() {
assert_eq!(
WebAssetReader::Https
.make_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png"))
.to_str()
.unwrap(),
"https://s3.johanhelsing.studio/dump/favicon.png"
);
}

#[test]
fn make_https_meta_uri() {
assert_eq!(
WebAssetReader::Https
.make_meta_uri(Path::new("s3.johanhelsing.studio/dump/favicon.png"))
.to_str()
.unwrap(),
"https://s3.johanhelsing.studio/dump/favicon.png.meta"
);
}
}
12 changes: 6 additions & 6 deletions crates/bevy_ws_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ edition = "2021"
rust-version.workspace = true

[dependencies]
anyhow = {workspace = true}
base64 = {workspace = true}
anyhow.workspace = true
base64.workspace = true
bevy = { version = "0.12.1", default-features = false, features = ["multi-threaded"] }
async-net = {workspace = true}
async-net.workspace = true
futures = "0.3.29"
async-tungstenite = "0.24.0"
log = {workspace = true}
crossbeam-channel = {workspace = true}
log.workspace = true
crossbeam-channel.workspace = true
async-channel = "2.1.0"
async-native-tls = "0.5.0"
smol = "2.0.0"
tungstenite = {workspace = true}
tungstenite.workspace = true
pollster = "0.3.0"
6 changes: 3 additions & 3 deletions crates/minimal_example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"
rust-version.workspace = true

[dependencies]
bevy = { workspace = true }
bevy.workspace = true
bevy_headless = { path = "../../crates/bevy_headless"}
log = { workspace = true }
pretty_env_logger = { workspace = true }
log.workspace = true
pretty_env_logger.workspace = true
23 changes: 12 additions & 11 deletions new_media/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,27 @@
name = "new_media"
version = "0.0.1"
edition = "2021"
rust-version.workspace = true
rust.workspace = true
default-run = "new_media"

[dependencies]
anyhow = { workspace = true }
async-net = { workspace = true }
base64 = { workspace = true }
bevy = { workspace = true }
anyhow.workspace = true
async-net.workspace = true
base64.workspace = true
bevy.workspace = true
bevy_ws_server = { path = "../crates/bevy_ws_server" }
bevy_headless = { path = "../crates/bevy_headless" }
bevy_remote_asset = { path = "../crates/bevy_remote_asset" }
bevy_gaussian_splatting = { version = "2.0.2", default-features = true }
bevy_panorbit_camera = { workspace = true }
crossbeam-channel = { workspace = true }
bevy_panorbit_camera.workspace = true
crossbeam-channel.workspace = true
dotenvy = "0.15.7"
log = { workspace = true }
pretty_env_logger = { workspace = true }
log.workspace = true
pretty_env_logger.workspace = true
serde = { version = "1.0.192", features = ["derive"] }
serde_json = "1.0.108"
tungstenite = { workspace = true }
tungstenite.workspace = true
openssl = { version = "0.10.63", features = ["vendored"], optional = true}

[features]
docker = ["dep:openssl","bevy/x11"]
docker = ["dep:openssl","bevy/x11"]
57 changes: 57 additions & 0 deletions new_media/src/asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use bevy::{
asset::{AssetServer, Assets},
core::Name,
core_pipeline::{core_3d::Camera3dBundle, tonemapping::Tonemapping},
ecs::system::{Commands, Res, ResMut},
math::Vec3,
render::{camera::Camera, texture::Image},
transform::components::Transform,
utils::default,
};
use bevy_headless::ImageExportSource;
use bevy_panorbit_camera::PanOrbitCamera;

use bevy_gaussian_splatting::{GaussianCloud, GaussianSplattingBundle};

pub fn setup_gaussian_cloud(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut gaussian_assets: ResMut<Assets<GaussianCloud>>,
mut scene_controller: ResMut<bevy_headless::SceneInfo>,
mut images: ResMut<Assets<Image>>,
export_sources: ResMut<Assets<ImageExportSource>>,
) {
// let remote_file = Some("https://huggingface.co/datasets/cs50victor/splats/resolve/main/train/point_cloud/iteration_7000/point_cloud.gcloud");
// TODO: figure out how to load remote files later
let splat_file = "splats/garden/point_cloud/iteration_7000/point_cloud.gcloud";
log::info!("loading {}", splat_file);
// let cloud = asset_server.load(splat_file.to_string());

let cloud = gaussian_assets.add(GaussianCloud::test_model());

let render_target = bevy_headless::setup_render_target(
&mut commands,
&mut images,
&mut scene_controller,
export_sources,
);

let gs = GaussianSplattingBundle { cloud, ..default() };
commands.spawn((gs, Name::new("gaussian_cloud")));

commands.spawn((
Camera3dBundle {
transform: Transform::from_translation(Vec3::new(0.0, 1.5, 5.0)),
tonemapping: Tonemapping::None,
camera: Camera { target: render_target, ..default() },
..default()
},
PanOrbitCamera {
allow_upside_down: true,
orbit_smoothness: 0.0,
pan_smoothness: 0.0,
zoom_smoothness: 0.0,
..default()
},
));
}
Loading
Loading