Merge eed9948383
into 877d278785
This commit is contained in:
commit
22f9f15252
1
.gitignore
vendored
1
.gitignore
vendored
@ -22,6 +22,7 @@ Cargo.lock
|
|||||||
assets/**/*.meta
|
assets/**/*.meta
|
||||||
crates/bevy_asset/imported_assets
|
crates/bevy_asset/imported_assets
|
||||||
imported_assets
|
imported_assets
|
||||||
|
.http-asset-cache
|
||||||
|
|
||||||
# Bevy Examples
|
# Bevy Examples
|
||||||
example_showcase_config.ron
|
example_showcase_config.ron
|
||||||
|
25
Cargo.toml
25
Cargo.toml
@ -526,6 +526,15 @@ file_watcher = ["bevy_internal/file_watcher"]
|
|||||||
# Enables watching in memory asset providers for Bevy Asset hot-reloading
|
# Enables watching in memory asset providers for Bevy Asset hot-reloading
|
||||||
embedded_watcher = ["bevy_internal/embedded_watcher"]
|
embedded_watcher = ["bevy_internal/embedded_watcher"]
|
||||||
|
|
||||||
|
# Enables downloading assets from HTTP sources
|
||||||
|
http = ["bevy_internal/http"]
|
||||||
|
|
||||||
|
# Enables downloading assets from HTTPS sources
|
||||||
|
https = ["bevy_internal/https"]
|
||||||
|
|
||||||
|
# Enable caching downloaded assets on the filesystem. NOTE: this cache currently never invalidates entries!
|
||||||
|
http_source_cache = ["bevy_internal/http_source_cache"]
|
||||||
|
|
||||||
# Enable stepping-based debugging of Bevy systems
|
# Enable stepping-based debugging of Bevy systems
|
||||||
bevy_debug_stepping = [
|
bevy_debug_stepping = [
|
||||||
"bevy_internal/bevy_debug_stepping",
|
"bevy_internal/bevy_debug_stepping",
|
||||||
@ -624,7 +633,7 @@ nonmax = "0.5"
|
|||||||
smol = "2"
|
smol = "2"
|
||||||
smol-macros = "0.1"
|
smol-macros = "0.1"
|
||||||
smol-hyper = "0.1"
|
smol-hyper = "0.1"
|
||||||
ureq = { version = "3.0.8", features = ["json"] }
|
ureq = { version = "3", features = ["json"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
wasm-bindgen = { version = "0.2" }
|
wasm-bindgen = { version = "0.2" }
|
||||||
@ -1913,12 +1922,24 @@ path = "examples/asset/extra_source.rs"
|
|||||||
doc-scrape-examples = true
|
doc-scrape-examples = true
|
||||||
|
|
||||||
[package.metadata.example.extra_asset_source]
|
[package.metadata.example.extra_asset_source]
|
||||||
name = "Extra asset source"
|
name = "Extra Asset Source"
|
||||||
description = "Load an asset from a non-standard asset source"
|
description = "Load an asset from a non-standard asset source"
|
||||||
category = "Assets"
|
category = "Assets"
|
||||||
# Uses non-standard asset path
|
# Uses non-standard asset path
|
||||||
wasm = false
|
wasm = false
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "http_source"
|
||||||
|
path = "examples/asset/http_source.rs"
|
||||||
|
doc-scrape-examples = true
|
||||||
|
required-features = ["https"]
|
||||||
|
|
||||||
|
[package.metadata.example.http_source]
|
||||||
|
name = "HTTP Asset Source"
|
||||||
|
description = "Load an asset from a http source"
|
||||||
|
category = "Assets"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "hot_asset_reloading"
|
name = "hot_asset_reloading"
|
||||||
path = "examples/asset/hot_asset_reloading.rs"
|
path = "examples/asset/hot_asset_reloading.rs"
|
||||||
|
@ -14,6 +14,9 @@ keywords = ["bevy"]
|
|||||||
file_watcher = ["notify-debouncer-full", "watch", "multi_threaded"]
|
file_watcher = ["notify-debouncer-full", "watch", "multi_threaded"]
|
||||||
embedded_watcher = ["file_watcher"]
|
embedded_watcher = ["file_watcher"]
|
||||||
multi_threaded = ["bevy_tasks/multi_threaded"]
|
multi_threaded = ["bevy_tasks/multi_threaded"]
|
||||||
|
http = ["ureq"]
|
||||||
|
https = ["ureq", "ureq/rustls"]
|
||||||
|
http_source_cache = []
|
||||||
asset_processor = []
|
asset_processor = []
|
||||||
watch = []
|
watch = []
|
||||||
trace = []
|
trace = []
|
||||||
@ -90,6 +93,8 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-featu
|
|||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
notify-debouncer-full = { version = "0.5.0", default-features = false, optional = true }
|
notify-debouncer-full = { version = "0.5.0", default-features = false, optional = true }
|
||||||
|
# updating ureq: while ureq is semver stable, it depends on rustls which is not, meaning unlikely but possible breaking changes on minor releases. https://github.com/bevyengine/bevy/pull/16366#issuecomment-2572890794
|
||||||
|
ureq = { version = "3", optional = true, default-features = false }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
271
crates/bevy_asset/src/http_source.rs
Normal file
271
crates/bevy_asset/src/http_source.rs
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
use crate::io::{AssetReader, AssetReaderError, Reader};
|
||||||
|
use crate::io::{AssetSource, PathStream};
|
||||||
|
use crate::AssetApp;
|
||||||
|
use alloc::boxed::Box;
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_tasks::ConditionalSendFuture;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
/// Adds the `http` and `https` asset sources to the app.
|
||||||
|
///
|
||||||
|
/// NOTE: Make sure to add this plugin *before* `AssetPlugin` to properly register http asset sources.
|
||||||
|
///
|
||||||
|
/// Any asset path that begins with `http` (when the `http` feature is enabled) or `https` (when the
|
||||||
|
/// `https` feature is enabled) will be loaded from the web via `fetch`(wasm) or `ureq`(native).
|
||||||
|
///
|
||||||
|
/// By default, `ureq`'s HTTP compression is disabled. To enable gzip and brotli decompression, add
|
||||||
|
/// the following dependency and features to your Cargo.toml. This will improve bandwidth
|
||||||
|
/// utilization when its supported by the server.
|
||||||
|
///
|
||||||
|
/// ```toml
|
||||||
|
/// [target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||||
|
/// ureq = { version = "3", default-features = false, features = ["gzip", "brotli"] }
|
||||||
|
/// ```
|
||||||
|
pub struct HttpSourcePlugin;
|
||||||
|
|
||||||
|
impl Plugin for HttpSourcePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
#[cfg(feature = "http")]
|
||||||
|
app.register_asset_source(
|
||||||
|
"http",
|
||||||
|
AssetSource::build()
|
||||||
|
.with_reader(|| Box::new(HttpSourceAssetReader::Http))
|
||||||
|
.with_processed_reader(|| Box::new(HttpSourceAssetReader::Http)),
|
||||||
|
);
|
||||||
|
|
||||||
|
#[cfg(feature = "https")]
|
||||||
|
app.register_asset_source(
|
||||||
|
"https",
|
||||||
|
AssetSource::build()
|
||||||
|
.with_reader(|| Box::new(HttpSourceAssetReader::Https))
|
||||||
|
.with_processed_reader(|| Box::new(HttpSourceAssetReader::Https)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HttpSourcePlugin {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asset reader that treats paths as urls to load assets from.
|
||||||
|
pub enum HttpSourceAssetReader {
|
||||||
|
/// Unencrypted connections.
|
||||||
|
Http,
|
||||||
|
/// Use TLS for setting up connections.
|
||||||
|
Https,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpSourceAssetReader {
|
||||||
|
fn make_uri(&self, path: &Path) -> PathBuf {
|
||||||
|
PathBuf::from(match self {
|
||||||
|
Self::Http => "http://",
|
||||||
|
Self::Https => "https://",
|
||||||
|
})
|
||||||
|
.join(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [`crate::io::get_meta_path`]
|
||||||
|
fn make_meta_uri(&self, path: &Path) -> PathBuf {
|
||||||
|
let meta_path = crate::io::get_meta_path(path);
|
||||||
|
self.make_uri(&meta_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
async fn get<'a>(path: PathBuf) -> Result<Box<dyn Reader>, AssetReaderError> {
|
||||||
|
use crate::io::wasm::HttpWasmAssetReader;
|
||||||
|
|
||||||
|
HttpWasmAssetReader::new("")
|
||||||
|
.fetch_bytes(path)
|
||||||
|
.await
|
||||||
|
.map(|r| Box::new(r) as Box<dyn Reader>)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
async fn get(path: PathBuf) -> Result<Box<dyn Reader>, AssetReaderError> {
|
||||||
|
use crate::io::VecReader;
|
||||||
|
use alloc::{boxed::Box, vec::Vec};
|
||||||
|
use bevy_platform::sync::LazyLock;
|
||||||
|
use std::io::{self, BufReader, Read};
|
||||||
|
|
||||||
|
let str_path = path.to_str().ok_or_else(|| {
|
||||||
|
AssetReaderError::Io(
|
||||||
|
io::Error::other(std::format!("non-utf8 path: {}", path.display())).into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), feature = "http_source_cache"))]
|
||||||
|
if let Some(data) = http_asset_cache::try_load_from_cache(str_path).await? {
|
||||||
|
return Ok(Box::new(VecReader::new(data)));
|
||||||
|
}
|
||||||
|
use ureq::Agent;
|
||||||
|
|
||||||
|
static AGENT: LazyLock<Agent> = LazyLock::new(|| Agent::config_builder().build().new_agent());
|
||||||
|
|
||||||
|
match AGENT.get(str_path).call() {
|
||||||
|
Ok(mut response) => {
|
||||||
|
let mut reader = BufReader::new(response.body_mut().with_config().reader());
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
reader.read_to_end(&mut buffer)?;
|
||||||
|
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), feature = "http_source_cache"))]
|
||||||
|
http_asset_cache::save_to_cache(str_path, &buffer).await?;
|
||||||
|
|
||||||
|
Ok(Box::new(VecReader::new(buffer)))
|
||||||
|
}
|
||||||
|
// ureq considers all >=400 status codes as errors
|
||||||
|
Err(ureq::Error::StatusCode(code)) => {
|
||||||
|
if code == 404 {
|
||||||
|
Err(AssetReaderError::NotFound(path))
|
||||||
|
} else {
|
||||||
|
Err(AssetReaderError::HttpError(code))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(AssetReaderError::Io(
|
||||||
|
io::Error::other(std::format!(
|
||||||
|
"unexpected error while loading asset {}: {}",
|
||||||
|
path.display(),
|
||||||
|
err
|
||||||
|
))
|
||||||
|
.into(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetReader for HttpSourceAssetReader {
|
||||||
|
fn read<'a>(
|
||||||
|
&'a self,
|
||||||
|
path: &'a Path,
|
||||||
|
) -> impl ConditionalSendFuture<Output = Result<Box<dyn Reader>, AssetReaderError>> {
|
||||||
|
get(self.make_uri(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<dyn Reader>, AssetReaderError> {
|
||||||
|
let uri = self.make_meta_uri(path);
|
||||||
|
get(uri).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn is_directory<'a>(&'a self, _path: &'a Path) -> Result<bool, AssetReaderError> {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_directory<'a>(
|
||||||
|
&'a self,
|
||||||
|
path: &'a Path,
|
||||||
|
) -> Result<Box<PathStream>, AssetReaderError> {
|
||||||
|
Err(AssetReaderError::NotFound(self.make_uri(path)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A naive implementation of an HTTP asset cache that never invalidates.
|
||||||
|
/// `ureq` currently does not support caching, so this is a simple workaround.
|
||||||
|
/// It should eventually be replaced by `http-cache` or similar, see [tracking issue](https://github.com/06chaynes/http-cache/issues/91)
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), feature = "http_source_cache"))]
|
||||||
|
mod http_asset_cache {
|
||||||
|
use alloc::string::String;
|
||||||
|
use alloc::vec::Vec;
|
||||||
|
use core::hash::{Hash, Hasher};
|
||||||
|
use futures_lite::AsyncWriteExt;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::io;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::io::Reader;
|
||||||
|
|
||||||
|
const CACHE_DIR: &str = ".http-asset-cache";
|
||||||
|
|
||||||
|
fn url_to_hash(url: &str) -> String {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
url.hash(&mut hasher);
|
||||||
|
std::format!("{:x}", hasher.finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn try_load_from_cache(url: &str) -> Result<Option<Vec<u8>>, io::Error> {
|
||||||
|
let filename = url_to_hash(url);
|
||||||
|
let cache_path = PathBuf::from(CACHE_DIR).join(&filename);
|
||||||
|
|
||||||
|
if cache_path.exists() {
|
||||||
|
let mut file = async_fs::File::open(&cache_path).await?;
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
file.read_to_end(&mut buffer).await?;
|
||||||
|
Ok(Some(buffer))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn save_to_cache(url: &str, data: &[u8]) -> Result<(), io::Error> {
|
||||||
|
let filename = url_to_hash(url);
|
||||||
|
let cache_path = PathBuf::from(CACHE_DIR).join(&filename);
|
||||||
|
|
||||||
|
async_fs::create_dir_all(CACHE_DIR).await.ok();
|
||||||
|
|
||||||
|
let mut cache_file = async_fs::File::create(&cache_path).await?;
|
||||||
|
cache_file.write_all(data).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_http_uri() {
|
||||||
|
assert_eq!(
|
||||||
|
HttpSourceAssetReader::Http
|
||||||
|
.make_uri(Path::new("example.com/favicon.png"))
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"http://example.com/favicon.png"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_https_uri() {
|
||||||
|
assert_eq!(
|
||||||
|
HttpSourceAssetReader::Https
|
||||||
|
.make_uri(Path::new("example.com/favicon.png"))
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"https://example.com/favicon.png"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_http_meta_uri() {
|
||||||
|
assert_eq!(
|
||||||
|
HttpSourceAssetReader::Http
|
||||||
|
.make_meta_uri(Path::new("example.com/favicon.png"))
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"http://example.com/favicon.png.meta"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_https_meta_uri() {
|
||||||
|
assert_eq!(
|
||||||
|
HttpSourceAssetReader::Https
|
||||||
|
.make_meta_uri(Path::new("example.com/favicon.png"))
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"https://example.com/favicon.png.meta"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn make_https_without_extension_meta_uri() {
|
||||||
|
assert_eq!(
|
||||||
|
HttpSourceAssetReader::Https
|
||||||
|
.make_meta_uri(Path::new("example.com/favicon"))
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"https://example.com/favicon.meta"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,8 @@ pub enum AssetReaderError {
|
|||||||
Io(Arc<std::io::Error>),
|
Io(Arc<std::io::Error>),
|
||||||
|
|
||||||
/// The HTTP request completed but returned an unhandled [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status).
|
/// The HTTP request completed but returned an unhandled [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status).
|
||||||
/// If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`].
|
/// - If the request returns a 404 error, expect [`AssetReaderError::NotFound`].
|
||||||
|
/// - If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`].
|
||||||
#[error("Encountered HTTP status {0:?} when loading asset")]
|
#[error("Encountered HTTP status {0:?} when loading asset")]
|
||||||
HttpError(u16),
|
HttpError(u16),
|
||||||
}
|
}
|
||||||
@ -762,11 +763,16 @@ impl Reader for SliceReader<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Appends `.meta` to the given path.
|
/// Appends `.meta` to the given path:
|
||||||
|
/// - `foo` becomes `foo.meta`
|
||||||
|
/// - `foo.bar` becomes `foo.bar.meta`
|
||||||
pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
|
pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
|
||||||
let mut meta_path = path.to_path_buf();
|
let mut meta_path = path.to_path_buf();
|
||||||
let mut extension = path.extension().unwrap_or_default().to_os_string();
|
let mut extension = path.extension().unwrap_or_default().to_os_string();
|
||||||
extension.push(".meta");
|
if !extension.is_empty() {
|
||||||
|
extension.push(".");
|
||||||
|
}
|
||||||
|
extension.push("meta");
|
||||||
meta_path.set_extension(extension);
|
meta_path.set_extension(extension);
|
||||||
meta_path
|
meta_path
|
||||||
}
|
}
|
||||||
@ -783,3 +789,24 @@ impl Stream for EmptyPathStream {
|
|||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_meta_path_no_extension() {
|
||||||
|
assert_eq!(
|
||||||
|
get_meta_path(Path::new("foo")).to_str().unwrap(),
|
||||||
|
"foo.meta"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_meta_path_with_extension() {
|
||||||
|
assert_eq!(
|
||||||
|
get_meta_path(Path::new("foo.bar")).to_str().unwrap(),
|
||||||
|
"foo.bar.meta"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -52,7 +52,8 @@ fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HttpWasmAssetReader {
|
impl HttpWasmAssetReader {
|
||||||
async fn fetch_bytes(&self, path: PathBuf) -> Result<impl Reader, AssetReaderError> {
|
// Also used by [`HttpSourceAssetReader`](crate::HttpSourceAssetReader)
|
||||||
|
pub(crate) async fn fetch_bytes(&self, path: PathBuf) -> Result<impl Reader, AssetReaderError> {
|
||||||
// The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available.
|
// The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available.
|
||||||
let global: Global = js_sys::global().unchecked_into();
|
let global: Global = js_sys::global().unchecked_into();
|
||||||
let promise = if !global.window().is_undefined() {
|
let promise = if !global.window().is_undefined() {
|
||||||
|
@ -186,6 +186,9 @@ mod reflect;
|
|||||||
mod render_asset;
|
mod render_asset;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
|
#[cfg(any(feature = "http", feature = "https"))]
|
||||||
|
pub mod http_source;
|
||||||
|
|
||||||
pub use assets::*;
|
pub use assets::*;
|
||||||
pub use bevy_asset_macros::Asset;
|
pub use bevy_asset_macros::Asset;
|
||||||
pub use direct_access_ext::DirectAssetAccessExt;
|
pub use direct_access_ext::DirectAssetAccessExt;
|
||||||
|
@ -232,6 +232,15 @@ debug_glam_assert = ["bevy_math/debug_glam_assert"]
|
|||||||
|
|
||||||
default_font = ["bevy_text?/default_font"]
|
default_font = ["bevy_text?/default_font"]
|
||||||
|
|
||||||
|
# Enables downloading assets from HTTP sources
|
||||||
|
http = ["bevy_asset?/http"]
|
||||||
|
|
||||||
|
# Enables downloading assets from HTTPS sources
|
||||||
|
https = ["bevy_asset?/https"]
|
||||||
|
|
||||||
|
# Enable caching downloaded assets on the filesystem. NOTE: this cache currently never invalidates entries!
|
||||||
|
http_source_cache = ["bevy_asset?/http_source_cache"]
|
||||||
|
|
||||||
# Enables the built-in asset processor for processed assets.
|
# Enables the built-in asset processor for processed assets.
|
||||||
asset_processor = ["bevy_asset?/asset_processor"]
|
asset_processor = ["bevy_asset?/asset_processor"]
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ plugin_group! {
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
#[custom(cfg(any(all(unix, not(target_os = "horizon")), windows)))]
|
#[custom(cfg(any(all(unix, not(target_os = "horizon")), windows)))]
|
||||||
bevy_app:::TerminalCtrlCHandlerPlugin,
|
bevy_app:::TerminalCtrlCHandlerPlugin,
|
||||||
|
// NOTE: Load this before AssetPlugin to properly register http asset sources.
|
||||||
|
#[cfg(feature = "bevy_asset")]
|
||||||
|
#[custom(cfg(any(feature = "http", feature = "https")))]
|
||||||
|
bevy_asset::http_source:::HttpSourcePlugin,
|
||||||
#[cfg(feature = "bevy_asset")]
|
#[cfg(feature = "bevy_asset")]
|
||||||
bevy_asset:::AssetPlugin,
|
bevy_asset:::AssetPlugin,
|
||||||
#[cfg(feature = "bevy_scene")]
|
#[cfg(feature = "bevy_scene")]
|
||||||
|
@ -23,6 +23,7 @@ allow = [
|
|||||||
"BSD-3-Clause",
|
"BSD-3-Clause",
|
||||||
"BSL-1.0",
|
"BSL-1.0",
|
||||||
"CC0-1.0",
|
"CC0-1.0",
|
||||||
|
"CDLA-Permissive-2.0",
|
||||||
"ISC",
|
"ISC",
|
||||||
"MIT",
|
"MIT",
|
||||||
"MIT-0",
|
"MIT-0",
|
||||||
|
@ -93,6 +93,9 @@ The default feature set enables most of the expected features of a game engine,
|
|||||||
|glam_assert|Enable assertions to check the validity of parameters passed to glam|
|
|glam_assert|Enable assertions to check the validity of parameters passed to glam|
|
||||||
|gltf_convert_coordinates_default|Enable converting glTF coordinates to Bevy's coordinate system by default. This will be Bevy's default behavior starting in 0.18.|
|
|gltf_convert_coordinates_default|Enable converting glTF coordinates to Bevy's coordinate system by default. This will be Bevy's default behavior starting in 0.18.|
|
||||||
|hotpatching|Enable hotpatching of Bevy systems|
|
|hotpatching|Enable hotpatching of Bevy systems|
|
||||||
|
|http|Enables downloading assets from HTTP sources|
|
||||||
|
|http_source_cache|Enable caching downloaded assets on the filesystem. NOTE: this cache currently never invalidates entries!|
|
||||||
|
|https|Enables downloading assets from HTTPS sources|
|
||||||
|ico|ICO image format support|
|
|ico|ICO image format support|
|
||||||
|jpeg|JPEG image format support|
|
|jpeg|JPEG image format support|
|
||||||
|libm|Uses the `libm` maths library instead of the one provided in `std` and `core`.|
|
|libm|Uses the `libm` maths library instead of the one provided in `std` and `core`.|
|
||||||
|
@ -256,7 +256,8 @@ Example | Description
|
|||||||
[Custom Asset](../examples/asset/custom_asset.rs) | Implements a custom asset loader
|
[Custom Asset](../examples/asset/custom_asset.rs) | Implements a custom asset loader
|
||||||
[Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader
|
[Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader
|
||||||
[Embedded Asset](../examples/asset/embedded_asset.rs) | Embed an asset in the application binary and load it
|
[Embedded Asset](../examples/asset/embedded_asset.rs) | Embed an asset in the application binary and load it
|
||||||
[Extra asset source](../examples/asset/extra_source.rs) | Load an asset from a non-standard asset source
|
[Extra Asset Source](../examples/asset/extra_source.rs) | Load an asset from a non-standard asset source
|
||||||
|
[HTTP Asset Source](../examples/asset/http_source.rs) | Load an asset from a http source
|
||||||
[Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk
|
[Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk
|
||||||
[Multi-asset synchronization](../examples/asset/multi_asset_sync.rs) | Demonstrates how to wait for multiple assets to be loaded.
|
[Multi-asset synchronization](../examples/asset/multi_asset_sync.rs) | Demonstrates how to wait for multiple assets to be loaded.
|
||||||
[Repeated texture configuration](../examples/asset/repeated_texture.rs) | How to configure the texture to repeat instead of the default clamp to edges
|
[Repeated texture configuration](../examples/asset/repeated_texture.rs) | How to configure the texture to repeat instead of the default clamp to edges
|
||||||
|
20
examples/asset/http_source.rs
Normal file
20
examples/asset/http_source.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//! Example usage of the `https` asset source to load assets from the web.
|
||||||
|
//!
|
||||||
|
//! Run with the feature `https`, and optionally `http_source_cache`
|
||||||
|
//! for a simple caching mechanism that never invalidates.
|
||||||
|
//!
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
|
commands.spawn(Camera2d);
|
||||||
|
let url = "https://raw.githubusercontent.com/bevyengine/bevy/refs/heads/main/assets/branding/bevy_bird_dark.png";
|
||||||
|
// Simply use a url where you would normally use an asset folder relative path
|
||||||
|
commands.spawn(Sprite::from_image(asset_server.load(url)));
|
||||||
|
}
|
19
release-content/release-notes/http_source.md
Normal file
19
release-content/release-notes/http_source.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: HTTP Assets
|
||||||
|
authors: ["@johanhelsing", "@mrchantey", "@jf908"]
|
||||||
|
pull_requests: [17889]
|
||||||
|
---
|
||||||
|
|
||||||
|
Bevy now supports downloading assets over http and https.
|
||||||
|
Use the new `http` and `https` features to enable `http://` and `https://` URLs as asset paths.
|
||||||
|
This functionality is powered by the [`ureq`](https://github.com/algesten/ureq) crate on native platforms and the fetch API on wasm.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let image = asset_server.load("https://example.com/image.png");
|
||||||
|
commands.spawn(Sprite::from_image(image));
|
||||||
|
```
|
||||||
|
|
||||||
|
By default these assets aren’t saved anywhere but you can enable the `http_source` feature to cache assets on your file system.
|
||||||
|
|
||||||
|
The implementation has changed quite a bit but this feature originally started out as an upstreaming of the [`bevy_web_asset`](https://github.com/johanhelsing/bevy_web_asset) crate.
|
||||||
|
Special thanks to @johanhelsing and bevy_web_asset's contributors!
|
Loading…
Reference in New Issue
Block a user