Skip to content

Commit

Permalink
nft shape
Browse files Browse the repository at this point in the history
  • Loading branch information
robtfm committed Nov 17, 2023
1 parent f826e76 commit dd39476
Show file tree
Hide file tree
Showing 9 changed files with 483 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dcl = { path="crates/dcl" }
dcl_component = { path="crates/dcl_component" }
restricted_actions = { path="crates/restricted_actions" }
wallet = { path="crates/wallet" }
nft = { path="crates/nft" }

bevy = { version = "0.12", features=["jpeg"] }
bevy_console = { git = "https://github.com/msklosak/bevy-console", branch = "bevy_0.12_update" }
Expand Down Expand Up @@ -72,6 +73,7 @@ av = { workspace = true }
restricted_actions = { workspace = true }
wallet = { workspace = true }
dcl = { workspace = true }
nft = { workspace = true }

bevy = { workspace = true }
bevy_console = { workspace = true }
Expand Down
1 change: 1 addition & 0 deletions crates/dcl_component/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn main() -> Result<()> {
"video_event",
"visibility_component",
"avatar_modifier_area",
"nft_shape",
];

let mut sources = components
Expand Down
1 change: 1 addition & 0 deletions crates/dcl_component/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ impl SceneComponentId {
pub const AUDIO_STREAM: SceneComponentId = SceneComponentId(1021);

pub const TEXT_SHAPE: SceneComponentId = SceneComponentId(1030);
pub const NFT_SHAPE: SceneComponentId = SceneComponentId(1040);

pub const GLTF_CONTAINER: SceneComponentId = SceneComponentId(1041);
pub const ANIMATOR: SceneComponentId = SceneComponentId(1042);
Expand Down
1 change: 1 addition & 0 deletions crates/dcl_component/src/proto_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl DclProtoComponent for sdk::components::PbAudioStream {}
impl DclProtoComponent for sdk::components::PbVideoEvent {}
impl DclProtoComponent for sdk::components::PbVisibilityComponent {}
impl DclProtoComponent for sdk::components::PbAvatarModifierArea {}
impl DclProtoComponent for sdk::components::PbNftShape {}

// VECTOR3 conversions
impl Copy for common::Vector3 {}
Expand Down
2 changes: 1 addition & 1 deletion crates/ipfs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ clap = { workspace = true }
urn = { workspace = true }
async-std = { workspace = true }
isahc = { workspace = true }

urlencoding = { workspace = true }

url = "2.4.0"
downcast-rs = "1.2"
24 changes: 24 additions & 0 deletions crates/nft/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "nft"
version = "0.1.0"
edition = "2021"

[lib]

[dependencies]
common = { workspace = true }
console = { workspace = true }
dcl = { workspace = true }
dcl_component = { workspace = true }
scene_runner = { workspace = true }
ipfs = { workspace = true }

bevy = { workspace = true }
anyhow = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
urn = { workspace = true }
isahc = { workspace = true }
once_cell = { workspace = true }
async-std = { workspace = true }
urlencoding = { workspace = true }
209 changes: 209 additions & 0 deletions crates/nft/src/asset_source.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
use std::{io::ErrorKind, str::FromStr, time::Duration};

use async_std::io::{Cursor, ReadExt};
use bevy::{
asset::{
io::{AssetReader, AssetReaderError, AssetSourceBuilder, Reader},
AssetApp, AssetLoader,
},
prelude::*,
};
use isahc::{config::Configurable, http::StatusCode, AsyncReadResponseExt, RequestExt};
use serde::Deserialize;

pub struct NftReaderPlugin;

impl Plugin for NftReaderPlugin {
fn build(&self, app: &mut App) {
app.register_asset_source(
"nft",
AssetSourceBuilder::default().with_reader(|| Box::<NftReader>::default()),
);
}
}

#[derive(Default)]
pub struct NftReader;

impl AssetReader for NftReader {
fn read<'a>(
&'a self,
path: &'a std::path::Path,
) -> bevy::utils::BoxedFuture<
'a,
Result<Box<bevy::asset::io::Reader<'a>>, bevy::asset::io::AssetReaderError>,
> {
let path = path.to_owned();
Box::pin(async move {
println!("getting nft raw data");

let path = path.to_string_lossy();
let Some(encoded_urn) = path.split('.').next() else {
return Err(AssetReaderError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
path,
)));
};
let urn = urlencoding::decode(encoded_urn).map_err(|e| {
AssetReaderError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
})?;
let urn = urn::Urn::from_str(&urn).map_err(|e| {
AssetReaderError::Io(std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
})?;

if urn.nid() != "decentraland" {
return Err(AssetReaderError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"nid must be `decentraland`",
)));
}

let mut parts = urn.nss().split(':');
let (Some(chain), Some(_standard), Some(address), Some(token)) =
(parts.next(), parts.next(), parts.next(), parts.next())
else {
return Err(AssetReaderError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"nss must be `chain:standard:contract_address:token`",
)));
};

if chain != "ethereum" {
return Err(AssetReaderError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"unsupported chain `{chain}`",
)));
}

let remote = format!(
"https://opensea.decentraland.org/api/v1/asset/{}/{}",
address, token
);

let token = path;

let mut attempt = 0;
let data = loop {
attempt += 1;

let request = isahc::Request::get(&remote)
.connect_timeout(Duration::from_secs(5 * attempt))
.timeout(Duration::from_secs(30 * attempt))
.body(())
.map_err(|e| {
AssetReaderError::Io(std::io::Error::new(
ErrorKind::Other,
format!("[{token:?}]: {e}"),
))
})?;

let response = request.send_async().await;

debug!("[{token:?}]: attempt {attempt}: request: {remote}, response: {response:?}");

let mut response = match response {
Err(e) if e.is_timeout() && attempt <= 3 => continue,
Err(e) => {
return Err(AssetReaderError::Io(std::io::Error::new(
ErrorKind::Other,
format!("[{token:?}]: {e}"),
)))
}
Ok(response) if !matches!(response.status(), StatusCode::OK) => {
return Err(AssetReaderError::Io(std::io::Error::new(
ErrorKind::Other,
format!(
"[{token:?}]: server responded with status {} requesting `{}`",
response.status(),
remote,
),
)))
}
Ok(response) => response,
};

let data = response.bytes().await;

match data {
Ok(data) => break data,
Err(e) => {
if matches!(e.kind(), std::io::ErrorKind::TimedOut) && attempt <= 3 {
continue;
}
return Err(AssetReaderError::Io(std::io::Error::new(
ErrorKind::Other,
format!("[{token:?}] {e}"),
)));
}
}
};

println!("GOT nft RAW DATA!");

let reader: Box<Reader> = Box::new(Cursor::new(data));
Ok(reader)
})
}

fn read_meta<'a>(
&'a self,
path: &'a std::path::Path,
) -> bevy::utils::BoxedFuture<
'a,
Result<Box<bevy::asset::io::Reader<'a>>, bevy::asset::io::AssetReaderError>,
> {
Box::pin(async { Err(AssetReaderError::NotFound(path.to_owned())) })
}

fn read_directory<'a>(
&'a self,
_: &'a std::path::Path,
) -> bevy::utils::BoxedFuture<
'a,
Result<Box<bevy::asset::io::PathStream>, bevy::asset::io::AssetReaderError>,
> {
panic!()
}

fn is_directory<'a>(
&'a self,
_: &'a std::path::Path,
) -> bevy::utils::BoxedFuture<'a, Result<bool, bevy::asset::io::AssetReaderError>> {
Box::pin(async { Ok(false) })
}
}

#[derive(Asset, TypePath, Deserialize)]
pub struct Nft {
pub image_url: String,
}

pub struct NftLoader;

impl AssetLoader for NftLoader {
type Asset = Nft;
type Settings = ();
type Error = std::io::Error;

fn load<'a>(
&'a self,
reader: &'a mut Reader,
_: &'a Self::Settings,
_: &'a mut bevy::asset::LoadContext,
) -> bevy::utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
println!("loading nft");
let mut bytes = Vec::default();
reader
.read_to_end(&mut bytes)
.await
.map_err(|e| std::io::Error::new(e.kind(), e))?;
serde_json::from_reader(bytes.as_slice())
.map_err(|e| std::io::Error::new(ErrorKind::InvalidData, e))
})
}

fn extensions(&self) -> &[&str] {
&["nft"]
}
}
Loading

0 comments on commit dd39476

Please sign in to comment.