Skip to content

Commit

Permalink
feat: setup bevy_remote_asset (#19)
Browse files Browse the repository at this point in the history
* refactor: remote_asset dir and use .workspace syntax

* refactor: setup bevy_remote_asset
  • Loading branch information
cs50victor authored Feb 16, 2024
1 parent 0258a91 commit e50c550
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 94 deletions.
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

0 comments on commit e50c550

Please sign in to comment.