Skip to content

Commit

Permalink
extra text formatting options (#132)
Browse files Browse the repository at this point in the history
features
- support `<s>`, `<u>` and `<mark>` in UiText and TextShape
- add spawn/kill portable commands

bugfixes
- pointer events are no longer blocked by CL_PHYSICS colliders
- missing audios are removed to avoid warning spam
- animations now replay if playing=true and they are playing but finished
- match foundation client UV calculations for MeshRenderer plane, box, sphere and cylinder shapes
- fix missing snapshots for guest profiles

misc
- add social service tests
- increased default fov (todo - make configurable)
- bump dcl-rpc and async-tungestenite to remove DoS vuln
  • Loading branch information
robtfm authored Oct 18, 2024
1 parent 6e2b0cb commit 2ffaa41
Show file tree
Hide file tree
Showing 23 changed files with 897 additions and 278 deletions.
193 changes: 81 additions & 112 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ bevy_simple_text_input = { git = "https://github.com/robtfm/bevy_simple_text_inp
directories = "5"
uuid = { version = "1.7", features = ["v4"] }
build-time = "0.1.3"
async-tungstenite = { version = "0.22.0", features = ["async-std-runtime", "async-tls"] }
async-tungstenite = { version = "0.28.0", features = ["async-std-runtime", "async-tls"] }
dcl-rpc = { version = "2.3.5", default-features = false, features=["client", "websockets", "codegen", "server", "tungstenite"] }
async-trait = "0.1.68"
fastrand = "2"
futures-util = "0.3.28"
async-tls = "0.13.0"

[dependencies]
analytics = { workspace = true }
Expand Down Expand Up @@ -163,6 +164,7 @@ bevy_simple_text_input = { workspace = true }
prost-build = "0.11.8"

[patch.crates-io]
dcl-rpc = { git = "https://github.com/decentraland/rpc-rust", branch = "chore/bump-tokio-tungstenite" }
bevy = { git = "https://github.com/robtfm/bevy", branch = "release-0.14-dcl-cosmic-noimage" }
# bevy = { path="../bevy" }
ffmpeg-next = { git = "https://github.com/robtfm/rust-ffmpeg", branch = "audio-linesize-0-6.1" }
Expand Down
Binary file added assets/images/screenshots/montage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 7 additions & 6 deletions crates/av/src/audio_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,11 @@ fn play_system_audio(

#[allow(clippy::too_many_arguments, clippy::type_complexity)]
fn update_source_volume(
query: Query<(
mut query: Query<(
Entity,
Option<&SceneEntity>,
Option<&AudioSource>,
&AudioEmitter,
&mut AudioEmitter,
&GlobalTransform,
)>,
mut audio_instances: ResMut<Assets<AudioInstance>>,
Expand All @@ -255,7 +255,7 @@ fn update_source_volume(

let mut prev_instances = std::mem::take(&mut *all_instances);

for (ent, maybe_scene, maybe_source, emitter, transform) in query.iter() {
for (ent, maybe_scene, maybe_source, mut emitter, transform) in query.iter_mut() {
if maybe_scene.map_or(true, |scene| current_scenes.contains(&scene.root)) {
let (volume, panning) = if maybe_source.map_or(false, |source| source.0.global()) {
(
Expand All @@ -276,14 +276,15 @@ fn update_source_volume(
(volume * volume_adjust, panning)
};

for h_instance in &emitter.instances {
emitter.instances.retain_mut(|h_instance| {
if let Some(instance) = audio_instances.get_mut(h_instance) {
instance.set_volume(volume as f64, AudioTween::linear(Duration::ZERO));
instance.set_panning(panning as f64, AudioTween::default());
true
} else {
warn!("missing audio instance");
false
}
}
});
} else if maybe_scene.map_or(false, |scene| prev_scenes.contains(&scene.root)) {
debug!("stop [{:?}]", ent);
for h_instance in &emitter.instances {
Expand Down
4 changes: 4 additions & 0 deletions crates/common/src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ impl Default for SerializedProfile {
{\"slot\": 8, \"urn\": \"headexplode\" },
{\"slot\": 9, \"urn\": \"shrug\" }
],
\"snapshots\": {
\"face256\":\"\",
\"body\":\"\"
},
\"eyes\":{
\"color\":{\"r\":0.3,\"g\":0.2235294133424759,\"b\":0.99,\"a\":1}
},
Expand Down
2 changes: 1 addition & 1 deletion crates/comms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ kira = { workspace = true }
async-trait = { workspace = true }
async-tungstenite = { workspace = true }
futures-util = { workspace = true }
async-tls = { workspace = true }

async-tls = "0.12.0"
livekit = { git = "https://github.com/robtfm/client-sdk-rust", branch="0.6-h264-false-2", features=["rustls-tls-webpki-roots"], optional = true }
rand = "0.8.5"
multihash-codetable = { version = "0.1.1", features = ["digest", "sha2"] }
Expand Down
20 changes: 15 additions & 5 deletions crates/comms/src/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,21 @@ impl<'w, 's> ProfileManager<'w, 's> {
let Some(profile) = profile else {
return Ok(None);
};
let Some(path) = profile.content.avatar.snapshots.as_ref().map(|snapshots| {
let url = format!("{}{}", profile.base_url, snapshots.face256);
let ipfs_path = IpfsPath::new_from_url(&url, "png");
PathBuf::from(&ipfs_path)
}) else {
let Some(path) = profile
.content
.avatar
.snapshots
.as_ref()
.and_then(|snapshots| {
if snapshots.face256.is_empty() {
None
} else {
let url = format!("{}{}", profile.base_url, snapshots.face256);
let ipfs_path = IpfsPath::new_from_url(&url, "png");
Some(PathBuf::from(&ipfs_path))
}
})
else {
return Err(ProfileMissingError);
};
Ok(Some(self.ipfs.asset_server().load(path)))
Expand Down
7 changes: 3 additions & 4 deletions crates/comms/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

use std::{collections::HashMap, sync::Arc};

use async_tungstenite::tungstenite::{
client::IntoClientRequest,
http::{HeaderValue, Uri},
};
use async_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue};
#[cfg(feature = "livekit")]
use livekit::{
options::TrackPublishOptions,
Expand All @@ -26,6 +23,8 @@ fn test_tls() {
#[cfg(feature = "livekit")]
#[test]
fn test_livekit() {
use isahc::http::Uri;

let mut wallet = Wallet::default();
wallet.finalize_as_guest();

Expand Down
3 changes: 3 additions & 0 deletions crates/restricted_actions/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ wallet = { workspace = true }
dcl_component = { workspace = true }
nft = { workspace = true }
system_ui = { workspace = true }
console = { workspace = true }

bevy = { workspace = true }
bevy_dui = { workspace = true }
bevy_console = { workspace = true }
tokio = { workspace = true }
isahc = { workspace = true }
serde_json = { workspace = true }
ethers-core = { workspace = true }
urlencoding = { workspace = true }
opener = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true }
184 changes: 141 additions & 43 deletions crates/restricted_actions/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bevy::{
tasks::{IoTaskPool, Task},
utils::{HashMap, HashSet},
};
use bevy_console::{ConsoleCommand, PrintConsoleLine};
use bevy_dui::{DuiCommandsExt, DuiProps, DuiRegistry};
use common::{
profile::SerializedProfile,
Expand All @@ -26,6 +27,7 @@ use comms::{
profile::{CurrentUserProfile, UserProfile},
NetworkMessage, Transport,
};
use console::DoAddConsoleCommand;
use dcl_component::proto_components::kernel::comms::rfc4;
use ethers_core::types::Address;
use ipfs::{ipfs_path::IpfsPath, ChangeRealmEvent, EntityDefinition, IpfsAssetServer, ServerAbout};
Expand Down Expand Up @@ -78,10 +80,14 @@ impl Plugin for RestrictedActionsPlugin {
handle_eth_async,
handle_texture_size,
handle_generic_perm,
handle_spawned_command,
),
)
.in_set(SceneSets::RestrictedActions),
);
app.init_resource::<PendingPortableCommands>();
app.add_console_command::<SpawnPortableCommand, _>(spawn_portable_command);
app.add_console_command::<KillPortableCommand, _>(kill_portable_command);
}
}

Expand Down Expand Up @@ -247,6 +253,52 @@ fn external_url(
}
}

async fn lookup_ens(
parent_scene: Option<String>,
ens: String,
) -> Result<(String, PortableSource), String> {
let mut about = isahc::get_async(format!(
"https://worlds-content-server.decentraland.org/world/{ens}/about"
))
.await
.map_err(|e| e.to_string())?;
if about.status() != StatusCode::OK {
return Err(format!("status: {}", about.status()));
}

let about = about
.json::<ServerAbout>()
.await
.map_err(|e| e.to_string())?;
let Some(config) = about.configurations else {
return Err("No configurations on server/about".to_owned());
};
let Some(scenes) = config.scenes_urn else {
return Err("No scenesUrn on server/about/configurations".to_owned());
};
let Some(urn) = scenes.first() else {
return Err("Empty scenesUrn on server/about/configurations".to_owned());
};
let hacked_urn = urn.replace('?', "?=&");

let Ok(path) = IpfsPath::new_from_urn::<EntityDefinition>(&hacked_urn) else {
return Err("failed to parse urn".to_owned());
};

let Ok(Some(hash)) = path.context_free_hash() else {
return Err("failed to resolve content hash from urn".to_owned());
};

Ok((
hash,
PortableSource {
pid: hacked_urn,
parent_scene,
ens: Some(ens),
},
))
}

type SpawnResponseChannel = Option<tokio::sync::oneshot::Sender<Result<SpawnResponse, String>>>;

#[allow(clippy::type_complexity, clippy::too_many_arguments)]
Expand Down Expand Up @@ -324,49 +376,7 @@ fn spawn_portable(
PortableLocation::Ens(ens) => {
let ens = ens.clone();
pending_lookups.push((
IoTaskPool::get().spawn(async move {
let mut about = isahc::get_async(format!(
"https://worlds-content-server.decentraland.org/world/{ens}/about"
))
.await
.map_err(|e| e.to_string())?;
if about.status() != StatusCode::OK {
return Err(format!("status: {}", about.status()));
}

let about = about
.json::<ServerAbout>()
.await
.map_err(|e| e.to_string())?;
let Some(config) = about.configurations else {
return Err("No configurations on server/about".to_owned());
};
let Some(scenes) = config.scenes_urn else {
return Err("No scenesUrn on server/about/configurations".to_owned());
};
let Some(urn) = scenes.first() else {
return Err("Empty scenesUrn on server/about/configurations".to_owned());
};
let hacked_urn = urn.replace('?', "?=&");

let Ok(path) = IpfsPath::new_from_urn::<EntityDefinition>(&hacked_urn)
else {
return Err("failed to parse urn".to_owned());
};

let Ok(Some(hash)) = path.context_free_hash() else {
return Err("failed to resolve content hash from urn".to_owned());
};

Ok((
hash,
PortableSource {
pid: hacked_urn,
parent_scene: Some(parent_hash),
ens: Some(ens),
},
))
}),
IoTaskPool::get().spawn(lookup_ens(Some(parent_hash), ens)),
Some(response.take()),
));
}
Expand Down Expand Up @@ -1141,3 +1151,91 @@ pub fn handle_generic_perm(
}
}
}

enum PortableAction {
Spawn,
Kill,
}

#[allow(clippy::type_complexity)]
#[derive(Resource, Default)]
struct PendingPortableCommands(
Vec<(
Task<Result<(String, PortableSource), String>>,
PortableAction,
)>,
);

/// manually spawn a portable
#[derive(clap::Parser, ConsoleCommand)]
#[command(name = "/spawn")]
struct SpawnPortableCommand {
ens: String,
}

fn spawn_portable_command(
mut input: ConsoleCommand<SpawnPortableCommand>,
mut pending: ResMut<PendingPortableCommands>,
) {
if let Some(Ok(command)) = input.take() {
pending.0.push((
IoTaskPool::get().spawn(lookup_ens(None, command.ens)),
PortableAction::Spawn,
));
}
}

/// manually kill a portable
#[derive(clap::Parser, ConsoleCommand)]
#[command(name = "/kill")]
struct KillPortableCommand {
ens: String,
}

fn kill_portable_command(
mut input: ConsoleCommand<KillPortableCommand>,
mut pending: ResMut<PendingPortableCommands>,
) {
if let Some(Ok(command)) = input.take() {
pending.0.push((
IoTaskPool::get().spawn(lookup_ens(None, command.ens)),
PortableAction::Kill,
));
}
}

fn handle_spawned_command(
mut pending: ResMut<PendingPortableCommands>,
mut portables: ResMut<PortableScenes>,
mut reply: EventWriter<PrintConsoleLine>,
) {
pending.0.retain_mut(|(task, action)| {
if let Some(result) = task.complete() {
match result {
Ok((hash, source)) => match action {
PortableAction::Spawn => {
portables.0.insert(hash.clone(), source);
reply.send(PrintConsoleLine::new("[ok]".into()));
}
PortableAction::Kill => {
if portables.0.remove(&hash).is_some() {
reply.send(PrintConsoleLine::new("[ok]".into()));
} else {
reply.send(PrintConsoleLine::new("portable not running".into()));
reply.send(PrintConsoleLine::new("[failed]".into()));
}
}
},
Err(e) => {
reply.send(PrintConsoleLine::new(
format!("failed to lookup ens: {e}").into(),
));
reply.send(PrintConsoleLine::new("[failed]".into()));
}
}
false
} else {
true
}
})
}
2 changes: 1 addition & 1 deletion crates/scene_runner/src/update_scene/pointer_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ fn update_pointer_target(
ray.origin,
ray.direction.into(),
f32::MAX,
ColliderLayer::ClPointer as u32 | ColliderLayer::ClPhysics as u32,
ColliderLayer::ClPointer as u32,
true,
);

Expand Down
5 changes: 4 additions & 1 deletion crates/scene_runner/src/update_world/animation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,10 @@ fn update_animations(
.collect(),
};

let mut prev_anims: HashSet<_> = player.playing_animations().map(|(ix, _)| *ix).collect();
let mut prev_anims: HashSet<_> = player
.playing_animations()
.filter_map(|(ix, anim)| (!anim.is_finished()).then_some(*ix))
.collect();

for (ix, (duration, state)) in targets.into_iter() {
let playing = state.playing.unwrap_or(true);
Expand Down
Loading

0 comments on commit 2ffaa41

Please sign in to comment.