diff --git a/.cargo/config.toml b/.cargo/config.toml index ae82b08f..d007a746 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,5 @@ [target.x86_64-pc-windows-msvc] -rustflags = ["-C", "target-feature=+crt-static", "-C", "link-args=/force:multiple"] +rustflags = ["-C", "target-feature=+crt-static"] [target.aarch64-pc-windows-msvc] rustflags = ["-C", "target-feature=+crt-static"] @@ -12,3 +12,4 @@ rustflags = ["-C", "link-args=-ObjC"] [env] RUST_BACKTRACE = "1" +CARGO_FEATURE_CRT_STATIC = "ohyes" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2510f0c1..9defd499 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,7 +65,7 @@ jobs: strategy: fail-fast: false matrix: - os: [self-hosted-windows, ubuntu-latest, macos-latest] + os: [bigwin, ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -99,7 +99,7 @@ jobs: run: | $VCINSTALLDIR = $(& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath) Add-Content $env:GITHUB_ENV "LIBCLANG_PATH=${VCINSTALLDIR}\VC\Tools\LLVM\x64\bin`n" - Invoke-WebRequest "https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-6.0-full_build-shared.7z" -OutFile ffmpeg-6.0-full_build-shared.7z + Invoke-WebRequest "https://github.com/GyanD/codexffmpeg/releases/download/6.0/ffmpeg-6.0-full_build-shared.7z" -OutFile ffmpeg-6.0-full_build-shared.7z 7z x ffmpeg-6.0-full_build-shared.7z mkdir ffmpeg mv ffmpeg-*/* ffmpeg/ @@ -114,7 +114,7 @@ jobs: if: runner.os == 'linux' with: command: test - args: --all --release --no-default-features --features "ffmpeg" + args: --all --release - uses: actions-rs/cargo@v1 if: runner.os == 'macos' with: diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index e81bfd2d..64958fd9 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -54,7 +54,7 @@ jobs: # os: macos-14-large - build_name: windows-x86_64 - os: self-hosted-windows + os: bigwin # target: x86_64-pc-windows-msvc env: @@ -102,7 +102,7 @@ jobs: run: | $VCINSTALLDIR = $(& "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath) Add-Content $env:GITHUB_ENV "LIBCLANG_PATH=${VCINSTALLDIR}\VC\Tools\LLVM\x64\bin`n" - Invoke-WebRequest "https://www.gyan.dev/ffmpeg/builds/packages/ffmpeg-6.0-full_build-shared.7z" -OutFile ffmpeg-6.0-full_build-shared.7z + Invoke-WebRequest "https://github.com/GyanD/codexffmpeg/releases/download/6.0/ffmpeg-6.0-full_build-shared.7z" -OutFile ffmpeg-6.0-full_build-shared.7z 7z x ffmpeg-6.0-full_build-shared.7z mkdir ffmpeg mv ffmpeg-*/* ffmpeg/ @@ -122,7 +122,7 @@ jobs: run: cargo build --release --bin decentra-bevy - name: Cargo build (linux) if: runner.os == 'linux' - run: cargo build --release --bin decentra-bevy --no-default-features --features "ffmpeg,inspect" + run: cargo build --release --bin decentra-bevy - name: Package common run: | @@ -177,13 +177,13 @@ jobs: cd deploy/linux mkdir BevyExplorer.AppDir/usr mkdir BevyExplorer.AppDir/usr/bin - cp ../target/release/decentra-bevy BevyExplorer.AppDir/usr/bin - cp ../assets BevyExplorer.AppDir/usr/bin -r + cp ../../target/release/decentra-bevy BevyExplorer.AppDir/usr/bin + cp ../../assets BevyExplorer.AppDir/usr/bin -r wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage wget https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage chmod +x ./linuxdeploy-x86_64.AppImage chmod +x ./appimagetool-x86_64.AppImage - ./linuxdeploy-x86_64.AppImage --appdir BevyExplorer.AppDir --output appimage --create-desktop-file --executable=../target/release/decentra-bevy --icon-file=decentra-bevy.png + ./linuxdeploy-x86_64.AppImage --appdir BevyExplorer.AppDir --output appimage --create-desktop-file --executable=../../target/release/decentra-bevy --icon-file=decentra-bevy.png mv ./decentra-bevy-x86_64.AppImage ${{ env.APPIMAGE_FILE }} gh release upload "${{ needs.create-release.outputs.tag_name }}" ${{ env.APPIMAGE_FILE }} env: diff --git a/Cargo.toml b/Cargo.toml index cbdc0218..d50225c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ serde = "1.0.152" serde_json = { version = "1.0.92", features = ["raw_value"] } itertools = "0.12" -tokio = { version = "1.29.1", features = ["sync"] } +tokio = { version = "1.40", features = ["sync"] } anyhow = "1.0.70" urn = "0.7.0" ethers-signers = "2.0.3" @@ -162,10 +162,16 @@ bevy = { git = "https://github.com/robtfm/bevy", branch = "release-0.14-dcl-cosm ffmpeg-next = { git = "https://github.com/robtfm/rust-ffmpeg", branch = "audio-linesize-0-6.1" } # parry3d-f64 = { git = "https://github.com/robtfm/parry", branch = "try_convex" } # rapier3d-f64 = { git = "https://github.com/robtfm/rapier", branch = "master" } -deno_core = { git = "https://github.com/robtfm/deno_core", branch = "0_280_hotfix" } -serde_v8 = { git = "https://github.com/robtfm/deno_core", branch = "0_280_hotfix" } -deno_ops = { git = "https://github.com/robtfm/deno_core", branch = "0_280_hotfix" } -# deno_ops = { path = "../deno_core/ops" } +deno_core = { git = "https://github.com/robtfm/deno_core", branch = "0_307_hotfix" } +serde_v8 = { git = "https://github.com/robtfm/deno_core", branch = "0_307_hotfix" } +deno_ops = { git = "https://github.com/robtfm/deno_core", branch = "0_307_hotfix" } +deno_console = { git = "https://github.com/robtfm/deno", branch = "1_46_hotfix" } +deno_fetch = { git = "https://github.com/robtfm/deno", branch = "1_46_hotfix" } +deno_net = { git = "https://github.com/robtfm/deno", branch = "1_46_hotfix" } +deno_url = { git = "https://github.com/robtfm/deno", branch = "1_46_hotfix" } +deno_webidl = { git = "https://github.com/robtfm/deno", branch = "1_46_hotfix" } +deno_web = { git = "https://github.com/robtfm/deno", branch = "1_46_hotfix" } +deno_websocket = { git = "https://github.com/robtfm/deno", branch = "1_46_hotfix" } # [patch."https://github.com/robtfm/bevy_dui"] # bevy_dui = { path = "../bevy_dui" } diff --git a/assets/ui/discover.dui b/assets/ui/discover.dui index 6432d694..d624f4a4 100644 --- a/assets/ui/discover.dui +++ b/assets/ui/discover.dui @@ -6,14 +6,18 @@
-
+
- +
+
+ + +
diff --git a/assets/ui/emote.dui b/assets/ui/emote.dui index bbaefec8..449cb252 100644 --- a/assets/ui/emote.dui +++ b/assets/ui/emote.dui @@ -15,11 +15,11 @@
- +
- +
diff --git a/assets/ui/wearables.dui b/assets/ui/wearables.dui index 42af6dff..03e525d3 100644 --- a/assets/ui/wearables.dui +++ b/assets/ui/wearables.dui @@ -14,16 +14,16 @@
- +
- +
- +
diff --git a/crates/av/src/audio_source.rs b/crates/av/src/audio_source.rs index 1ed36c35..2a9cde25 100644 --- a/crates/av/src/audio_source.rs +++ b/crates/av/src/audio_source.rs @@ -1,14 +1,14 @@ +use std::time::Duration; + use bevy::{ prelude::*, utils::{HashMap, HashSet}, }; -use bevy_kira_audio::{ - prelude::{AudioEmitter, AudioReceiver}, - AudioControl, AudioInstance, AudioTween, -}; +use bevy_kira_audio::{prelude::AudioEmitter, AudioControl, AudioInstance, AudioTween}; use common::{ - sets::{SceneSets, SetupSets}, + sets::SetupSets, structs::{AudioSettings, PrimaryCameraRes, PrimaryUser, SystemAudio}, + util::{AudioReceiver, VolumePanning}, }; use dcl::interface::ComponentPosition; use dcl_component::{proto_components::sdk::components::PbAudioSource, SceneComponentId}; @@ -37,8 +37,9 @@ impl Plugin for AudioSourcePlugin { ComponentPosition::EntityOnly, ); app.add_systems( - Update, - (update_audio, update_source_volume, play_system_audio).in_set(SceneSets::PostLoop), + PostUpdate, + (update_audio, update_source_volume, play_system_audio) + .after(TransformSystem::TransformPropagate), ); app.add_systems(Startup, setup_audio.in_set(SetupSets::Main)); } @@ -229,12 +230,12 @@ fn play_system_audio( }) } -#[allow(clippy::too_many_arguments)] +#[allow(clippy::too_many_arguments, clippy::type_complexity)] fn update_source_volume( query: Query<( Entity, - &SceneEntity, - &AudioSource, + Option<&SceneEntity>, + Option<&AudioSource>, &AudioEmitter, &GlobalTransform, )>, @@ -242,7 +243,7 @@ fn update_source_volume( containing_scene: ContainingScene, player: Query>, mut prev_scenes: Local>, - receiver: Query<&GlobalTransform, With>, + pan: VolumePanning, settings: Res, mut all_instances: Local>>>, ) { @@ -252,39 +253,38 @@ fn update_source_volume( .map(|p| containing_scene.get(p)) .unwrap_or_default(); - let Ok(receiver) = receiver.get_single() else { - return; - }; - let mut prev_instances = std::mem::take(&mut *all_instances); - for (ent, scene, source, emitter, transform) in query.iter() { - if current_scenes.contains(&scene.root) { - let (volume, panning) = if source.0.global() { - (source.0.volume.unwrap_or(1.0), 0.5) + for (ent, maybe_scene, maybe_source, emitter, transform) in query.iter() { + 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()) { + ( + maybe_source + .and_then(|source| source.0.volume) + .unwrap_or(1.0), + 0.5, + ) } else { - let sound_path = transform.translation() - receiver.translation(); - let volume = (1. - sound_path.length() / 125.0).clamp(0., 1.).powi(2) - * source.0.volume.unwrap_or(1.0) - * settings.scene(); - - let panning = if sound_path.length() > f32::EPSILON { - let right_ear_angle = receiver.right().angle_between(sound_path); - (right_ear_angle.cos() + 1.) / 2. + let volume_adjust = if maybe_scene.is_some() { + settings.scene() } else { - 0.5 + settings.avatar() }; - (volume, panning) + let (volume, panning) = pan.volume_and_panning(transform.translation()); + + (volume * volume_adjust, panning) }; for h_instance in &emitter.instances { if let Some(instance) = audio_instances.get_mut(h_instance) { - instance.set_volume(volume as f64, AudioTween::default()); + instance.set_volume(volume as f64, AudioTween::linear(Duration::ZERO)); instance.set_panning(panning as f64, AudioTween::default()); + } else { + warn!("missing audio instance"); } } - } else if prev_scenes.contains(&scene.root) { + } else if maybe_scene.map_or(false, |scene| prev_scenes.contains(&scene.root)) { debug!("stop [{:?}]", ent); for h_instance in &emitter.instances { if let Some(instance) = audio_instances.get_mut(h_instance) { diff --git a/crates/avatar/src/animate.rs b/crates/avatar/src/animate.rs index e26de6ea..e077a307 100644 --- a/crates/avatar/src/animate.rs +++ b/crates/avatar/src/animate.rs @@ -19,6 +19,7 @@ use common::{ rpc::{RpcCall, RpcEventSender}, sets::SceneSets, structs::{AppConfig, PrimaryUser}, + util::{TryPushChildrenEx, VolumePanning}, }; use comms::{ chat_marker_things, @@ -503,7 +504,13 @@ impl SpawnedExtras { #[allow(clippy::too_many_arguments, clippy::type_complexity)] fn play_current_emote( mut commands: Commands, - mut q: Query<(Entity, &mut ActiveEmote, &AvatarAnimPlayer, &Children)>, + mut q: Query<( + Entity, + &mut ActiveEmote, + &AvatarAnimPlayer, + &Children, + &GlobalTransform, + )>, definitions: Query<&AvatarDefinition>, mut emote_loader: CollectibleManager, mut gltfs: ResMut>, @@ -519,11 +526,12 @@ fn play_current_emote( mut cached_gltf_handles: Local>>, mut spawned_extras: Local>, mut scene_spawner: ResMut, - (audio, sounds, anim_clips, config): ( + (audio, sounds, anim_clips, config, pan): ( Res, Res>, Res>, Res, + VolumePanning, ), mut emitters: Query<&mut bevy_kira_audio::prelude::AudioEmitter>, mut audio_instances: ResMut>, @@ -532,7 +540,7 @@ fn play_current_emote( let prior_playing = std::mem::take(&mut *playing); let mut prev_spawned_extras = std::mem::take(&mut *spawned_extras); - for (entity, mut active_emote, target_entity, children) in q.iter_mut() { + for (entity, mut active_emote, target_entity, children, transform) in q.iter_mut() { debug!("emote {}", active_emote.urn); let Some(definition) = children .iter() @@ -877,6 +885,7 @@ fn play_current_emote( if elapsed >= play_time { debug!("duration {}", clip_duration); debug!("play {:?} @ {}>{}", sound.path(), elapsed, play_time); + let (volume, panning) = pan.volume_and_panning(transform.translation()); let existing = spawned_extras .get_mut(&entity) .and_then(|extras| extras.audio.as_mut()); @@ -892,14 +901,16 @@ fn play_current_emote( existing_emitter.instances.push( audio .play(sound) - .with_volume(config.audio.avatar() as f64) + .with_volume((volume * config.audio.avatar()) as f64) + .with_panning(panning as f64) .handle(), ); existing.unwrap().1 = elapsed; } else { let handle = audio .play(sound) - .with_volume(config.audio.avatar() as f64) + .with_volume((volume * config.audio.avatar()) as f64) + .with_panning(panning as f64) .handle(); let audio_entity = commands @@ -911,6 +922,10 @@ fn play_current_emote( )) .id(); + if let Some(mut commands) = commands.get_entity(ent) { + commands.try_push_children(&[audio_entity]); + } + spawned_extras .entry(entity) .or_insert_with(|| SpawnedExtras::new(active_emote.urn.clone())) diff --git a/crates/common/src/util.rs b/crates/common/src/util.rs index 02acee80..56eb95aa 100644 --- a/crates/common/src/util.rs +++ b/crates/common/src/util.rs @@ -5,13 +5,14 @@ use bevy::{ ecs::{ component::Component, event::{Event, Events}, - system::{Commands, EntityCommand, EntityCommands, Query}, + system::{Commands, EntityCommand, EntityCommands, Query, SystemParam}, world::Command, }, hierarchy::DespawnRecursiveExt, + math::Vec3, prelude::{ - despawn_with_children_recursive, BuildWorldChildren, Bundle, Entity, IntoSystemConfigs, - Plugin, World, + despawn_with_children_recursive, BuildWorldChildren, Bundle, Entity, GlobalTransform, + IntoSystemConfigs, Plugin, With, World, }, tasks::Task, }; @@ -397,3 +398,29 @@ macro_rules! anim_last_system { bevy::prelude::expire_completed_transitions }; } + +#[derive(Component)] +pub struct AudioReceiver; + +#[derive(SystemParam)] +pub struct VolumePanning<'w, 's> { + receiver: Query<'w, 's, &'static GlobalTransform, With>, +} + +impl<'w, 's> VolumePanning<'w, 's> { + pub fn volume_and_panning(&self, translation: Vec3) -> (f32, f32) { + let Ok(receiver) = self.receiver.get_single() else { + return (1.0, 0.5); + }; + let sound_path = translation - receiver.translation(); + let volume = (1. - sound_path.length() / 75.0).clamp(0., 1.).powi(2); + let panning = if sound_path.length() > f32::EPSILON { + let right_ear_angle = receiver.right().angle_between(sound_path); + (right_ear_angle.cos() + 1.) / 2. + } else { + 0.5 + }; + + (volume, panning) + } +} diff --git a/crates/comms/Cargo.toml b/crates/comms/Cargo.toml index 6a47d7e6..f4e31ebc 100644 --- a/crates/comms/Cargo.toml +++ b/crates/comms/Cargo.toml @@ -34,7 +34,7 @@ async-trait = "0.1.68" async-tungstenite = { version = "0.22.0", features = ["async-std-runtime", "async-tls"] } async-tls = "0.12.0" futures-util = "0.3.28" -livekit = { git = "https://github.com/robtfm/client-sdk-rust", branch="h264-false", features=["rustls-tls-webpki-roots"], optional = true } +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"] } cid = "0.11.0" diff --git a/crates/comms/src/livekit_room.rs b/crates/comms/src/livekit_room.rs index 469a64b4..8a4872ac 100644 --- a/crates/comms/src/livekit_room.rs +++ b/crates/comms/src/livekit_room.rs @@ -10,16 +10,20 @@ use bevy::{ }; use futures_lite::StreamExt; use livekit::{ + id::TrackSid, options::TrackPublishOptions, track::{LocalAudioTrack, LocalTrack, TrackSource}, webrtc::{ audio_source::native::NativeAudioSource, prelude::{AudioFrame, AudioSourceOptions, RtcAudioSource}, }, - DataPacketKind, RoomOptions, + RoomOptions, }; use prost::Message; -use tokio::sync::mpsc::{error::TryRecvError, Receiver, Sender}; +use tokio::sync::{ + mpsc::{error::TryRecvError, Receiver, Sender}, + Mutex, +}; use common::{structs::AudioDecoderError, util::AsH160}; use dcl_component::proto_components::kernel::comms::rfc4; @@ -132,17 +136,32 @@ async fn livekit_handler( sender: Sender, mic: tokio::sync::broadcast::Receiver, ) { - if let Err(e) = livekit_handler_inner(transport_id, remote_address, receiver, sender, mic).await - { - warn!("livekit error: {e}"); + let receiver = Arc::new(Mutex::new(receiver)); + + loop { + if let Err(e) = livekit_handler_inner( + transport_id, + &remote_address, + receiver.clone(), + sender.clone(), + mic.resubscribe(), + ) + .await + { + warn!("livekit error: {e}"); + } + if receiver.lock().await.is_closed() { + // caller closed the channel + return; + } + warn!("livekit connection dropped, reconnecting"); } - warn!("livekit thread exit"); } async fn livekit_handler_inner( transport_id: Entity, - remote_address: String, - mut app_rx: Receiver, + remote_address: &str, + app_rx: Arc>>, sender: Sender, mut mic: tokio::sync::broadcast::Receiver, ) -> Result<(), anyhow::Error> { @@ -173,27 +192,49 @@ async fn livekit_handler_inner( let rt2 = rt.clone(); let task = rt.spawn(async move { - let (room, mut network_rx) = livekit::prelude::Room::connect(&address, &token, RoomOptions{ auto_subscribe: true, adaptive_stream: false, dynacast: false }).await.unwrap(); - let native_source = NativeAudioSource::new(AudioSourceOptions{ - echo_cancellation: true, - noise_suppression: true, - auto_gain_control: true, - }); - let mic_track = LocalTrack::Audio(LocalAudioTrack::create_audio_track("mic", RtcAudioSource::Native(native_source.clone()))); - room.local_participant().publish_track(mic_track, TrackPublishOptions{ source: TrackSource::Microphone, ..Default::default() }).await.unwrap(); + let (room, mut network_rx) = livekit::prelude::Room::connect(&address, &token, RoomOptions{ auto_subscribe: true, adaptive_stream: false, dynacast: false, ..Default::default() }).await.unwrap(); + let local_participant = room.local_participant(); + + let mut native_source: Option = None; + let mut mic_sid: Option = None; rt2.spawn(async move { while let Ok(frame) = mic.recv().await { let data = frame.data.iter().map(|f| (f * i16::MAX as f32) as i16).collect(); - native_source.capture_frame(&AudioFrame { + if native_source.as_ref().map_or(true, |ns| ns.sample_rate() != frame.sample_rate || ns.num_channels() != frame.num_channels) { + // update track + if let Some(sid) = mic_sid.take() { + if let Err(e) = local_participant.unpublish_track(&sid).await { + warn!("error unpublishing previous mic track: {e}"); + } + warn!("unpub"); + } + let new_source = native_source.insert(NativeAudioSource::new( + AudioSourceOptions{ + echo_cancellation: true, + noise_suppression: true, + auto_gain_control: true, + }, + frame.sample_rate, + frame.num_channels, + None + )); + let mic_track = LocalTrack::Audio(LocalAudioTrack::create_audio_track("mic", RtcAudioSource::Native(new_source.clone()))); + mic_sid = Some(local_participant.publish_track(mic_track, TrackPublishOptions{ source: TrackSource::Microphone, ..Default::default() }).await.unwrap().sid()); + warn!("set sid"); + } + if let Err(e) = native_source.as_mut().unwrap().capture_frame(&AudioFrame { data, sample_rate: frame.sample_rate, num_channels: frame.num_channels, - samples_per_channel: frame.data.len() as u32, - }) + samples_per_channel: frame.data.len() as u32 / frame.num_channels, + }).await { + warn!("failed to capture from mic: {e}"); + }; } }); + let mut app_rx = app_rx.lock().await; 'stream: loop { tokio::select!( incoming = network_rx.recv() => { @@ -205,7 +246,7 @@ async fn livekit_handler_inner( match incoming { livekit::RoomEvent::DataReceived { payload, participant, .. } => { - if let Some(address) = participant.identity().0.as_str().as_h160() { + if let Some(address) = participant.and_then(|p| p.identity().0.as_str().as_h160()) { let packet = match rfc4::Packet::decode(payload.as_slice()) { Ok(packet) => packet, Err(e) => { @@ -234,7 +275,7 @@ async fn livekit_handler_inner( livekit::track::RemoteTrack::Audio(audio) => { let sender = sender.clone(); rt2.spawn(async move { - let mut x = livekit::webrtc::audio_stream::native::NativeAudioStream::new(audio.rtc_track()); + let mut x = livekit::webrtc::audio_stream::native::NativeAudioStream::new(audio.rtc_track(), 48_000, 1); // get first frame to set sample rate let Some(frame) = x.next().await else { @@ -249,6 +290,8 @@ async fn livekit_handler_inner( receiver: frame_receiver, }; + println!("recced with {} / {}", frame.sample_rate, frame.num_channels); + let sound_data = kira::sound::streaming::StreamingSoundData::from_decoder( bridge, kira::sound::streaming::StreamingSoundSettings::new(), @@ -289,12 +332,8 @@ async fn livekit_handler_inner( break 'stream; }; - let kind = if outgoing.unreliable { - DataPacketKind::Lossy - } else { - DataPacketKind::Reliable - }; - if let Err(_e) = room.local_participant().publish_data(outgoing.data, kind, Default::default()).await { + let packet = livekit::DataPacket { payload: outgoing.data, topic: None, reliable: !outgoing.unreliable, destination_identities: Default::default() }; + if let Err(_e) = room.local_participant().publish_data(packet).await { // debug!("outgoing failed: {_e}; not exiting loop though since it often fails at least once or twice at the start..."); break 'stream; }; @@ -311,7 +350,7 @@ async fn livekit_handler_inner( struct LivekitKiraBridge { sample_rate: u32, - receiver: tokio::sync::mpsc::Receiver, + receiver: tokio::sync::mpsc::Receiver>, } impl kira::sound::streaming::Decoder for LivekitKiraBridge { diff --git a/crates/comms/src/test.rs b/crates/comms/src/test.rs index 3587fe50..93ab21a1 100644 --- a/crates/comms/src/test.rs +++ b/crates/comms/src/test.rs @@ -62,14 +62,15 @@ fn test_livekit() { println!("{params:?}"); let token = params.get("access_token").cloned().unwrap_or_default(); - let (room, _network_rx) = livekit::prelude::Room::connect(&address, &token, RoomOptions{ auto_subscribe: true, adaptive_stream: false, dynacast: false }).await.unwrap(); + let (room, _network_rx) = livekit::prelude::Room::connect(&address, &token, RoomOptions{ auto_subscribe: true, adaptive_stream: false, dynacast: false, ..Default::default() }).await.unwrap(); let native_source = NativeAudioSource::new(AudioSourceOptions{ echo_cancellation: true, noise_suppression: true, auto_gain_control: true, - }); + }, 44_100, 1, None); let mic_track = LocalTrack::Audio(LocalAudioTrack::create_audio_track("mic", RtcAudioSource::Native(native_source.clone()))); room.local_participant().publish_track(mic_track, TrackPublishOptions{ source: TrackSource::Microphone, ..Default::default() }).await.unwrap(); + println!("ok"); }); rt.block_on(task).unwrap(); diff --git a/crates/dcl/Cargo.toml b/crates/dcl/Cargo.toml index 48e3e0e8..ddfd84f9 100644 --- a/crates/dcl/Cargo.toml +++ b/crates/dcl/Cargo.toml @@ -23,14 +23,14 @@ isahc = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -deno_core = "0.280" -deno_fetch = "0.176" -deno_webidl = "0.152" -deno_web = "0.183" -deno_url = "0.152" -deno_console = "0.152" -deno_websocket = "0.157" -deno_net = "0.144.0" +deno_core = "0.307" +deno_console = "0.168" +deno_fetch = "0.192" +deno_net = "0.160" +deno_url = "0.168" +deno_webidl = "0.168" +deno_web = "0.199" +deno_websocket = "0.173" num-derive = "0.3" num = "0.4" diff --git a/crates/dcl/src/js/engine.rs b/crates/dcl/src/js/engine.rs index aa215d2d..ffe2fd9e 100644 --- a/crates/dcl/src/js/engine.rs +++ b/crates/dcl/src/js/engine.rs @@ -5,8 +5,12 @@ use bevy::{ utils::tracing::{debug, info, info_span, warn}, }; use deno_core::{op2, OpDecl, OpState}; -use std::{cell::RefCell, rc::Rc, sync::mpsc::SyncSender}; -use tokio::sync::{broadcast::error::TryRecvError, mpsc::Receiver}; +use std::{ + cell::RefCell, + rc::Rc, + sync::{mpsc::SyncSender, Arc}, +}; +use tokio::sync::{broadcast::error::TryRecvError, mpsc::Receiver, Mutex}; use crate::{ crdt::{append_component, put_component}, @@ -68,12 +72,15 @@ pub fn crdt_send_to_renderer(op_state: Rc>, messages: &[u8]) { #[op2(async)] #[serde] async fn op_crdt_recv_from_renderer(op_state: Rc>) -> Vec> { - let mut receiver = op_state.borrow_mut().take::>(); - let span = op_state.borrow_mut().take::(); + let span = op_state.borrow_mut().try_take::(); drop(span); // don't hold it over the await point so we get a clearer view of when js is running debug!("op_crdt_recv_from_renderer"); - let response = receiver.recv().await; + let receiver = op_state + .borrow_mut() + .borrow_mut::>>>() + .clone(); + let response = receiver.lock().await.recv().await; let mut op_state = op_state.borrow_mut(); let span = info_span!("js update").entered(); diff --git a/crates/dcl/src/js/inspector.rs b/crates/dcl/src/js/inspector.rs index 918e28f8..46b6b6b5 100644 --- a/crates/dcl/src/js/inspector.rs +++ b/crates/dcl/src/js/inspector.rs @@ -257,7 +257,7 @@ async fn server( let json_version_response = json!({ "Browser": name, "Protocol-Version": "1.3", - "V8-Version": deno_core::v8_version(), + "V8-Version": deno_core::v8::VERSION_STRING, }); let make_svc = hyper::service::make_service_fn(|_| { diff --git a/crates/dcl/src/js/mod.rs b/crates/dcl/src/js/mod.rs index c275af92..104593f0 100644 --- a/crates/dcl/src/js/mod.rs +++ b/crates/dcl/src/js/mod.rs @@ -1,4 +1,9 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::mpsc::SyncSender}; +use std::{ + cell::RefCell, + collections::HashMap, + rc::Rc, + sync::{mpsc::SyncSender, Arc}, +}; use bevy::utils::tracing::{debug, error, info_span}; use deno_core::{ @@ -7,7 +12,7 @@ use deno_core::{ include_js_files, op2, v8, Extension, JsRuntime, OpDecl, OpState, PollEventLoopOptions, RuntimeOptions, }; -use tokio::sync::mpsc::Receiver; +use tokio::sync::{mpsc::Receiver, Mutex}; use ipfs::{IpfsResource, SceneJsFile}; use wallet::Wallet; @@ -207,7 +212,7 @@ pub(crate) fn scene_thread( // store channels state.borrow_mut().put(thread_sx); - state.borrow_mut().put(thread_rx); + state.borrow_mut().put(Arc::new(Mutex::new(thread_rx))); state.borrow_mut().put(global_update_receiver); // store asset server and wallet @@ -300,6 +305,7 @@ pub(crate) fn scene_thread( let start_time = std::time::Instant::now(); let mut prev_time = start_time; let mut elapsed; + let mut reported_errors = 0; loop { let now = std::time::Instant::now(); let dt = now.saturating_duration_since(prev_time); @@ -326,15 +332,22 @@ pub(crate) fn scene_thread( } if let Err(e) = result { - error!("[{scene_id:?}] onUpdate err: {e:?}"); - let _ = state - .borrow_mut() - .take::>() - .send(SceneResponse::Error(scene_id, format!("{e:?}"))); - rt.block_on(async move { - drop(runtime); - }); - return; + reported_errors += 1; + if reported_errors <= 10 { + error!("[{scene_id:?}] uncaught error: {e:?}"); + if reported_errors == 10 { + error!("[{scene_id:?} not logging any further uncaught errors.") + } + } + // we no longer exit on uncaught `onUpdate` errors + // let _ = state + // .borrow_mut() + // .take::>() + // .send(SceneResponse::Error(scene_id, format!("{e:?}"))); + // rt.block_on(async move { + // drop(runtime); + // }); + // return; } } } diff --git a/crates/ipfs/src/lib.rs b/crates/ipfs/src/lib.rs index 09c60eaa..55cfc7c4 100644 --- a/crates/ipfs/src/lib.rs +++ b/crates/ipfs/src/lib.rs @@ -1029,7 +1029,7 @@ impl AssetReader for IpfsIo { .insert(remote.clone(), Instant::now()); return Err(AssetReaderError::Io(Arc::new(std::io::Error::new( ErrorKind::Other, - format!("[{token:?}]: server responded {e} rqeuesting `{remote}`"), + format!("[{token:?}]: server responded `{e}` requesting `{remote}`"), )))); } Ok(response) if !matches!(response.status(), StatusCode::OK) => { diff --git a/crates/scene_runner/src/update_world/scene_ui/mod.rs b/crates/scene_runner/src/update_world/scene_ui/mod.rs index 3d7f8703..98fede84 100644 --- a/crates/scene_runner/src/update_world/scene_ui/mod.rs +++ b/crates/scene_runner/src/update_world/scene_ui/mod.rs @@ -714,10 +714,13 @@ fn layout_scene_ui( true } else { // we use entity id as zindex. this is rubbish but mimics the foundation behaviour for multiple overlapping root nodes. - let mut ent_cmds = commands.spawn(NodeBundle { - z_index: ZIndex::Local(scene_id.id as i32), - ..Default::default() - }); + let mut ent_cmds = commands.spawn(( + NodeBundle { + z_index: ZIndex::Local(scene_id.id as i32), + ..Default::default() + }, + DespawnWith(*bevy_entity), + )); ent_cmds.set_parent(parent_link.content_entity); let ui_entity = ent_cmds.id(); debug!("{scene_id} create linked {:?}", ui_entity); diff --git a/crates/scene_runner/src/update_world/scene_ui/ui_background.rs b/crates/scene_runner/src/update_world/scene_ui/ui_background.rs index 60b2cf7e..a1f1a278 100644 --- a/crates/scene_runner/src/update_world/scene_ui/ui_background.rs +++ b/crates/scene_runner/src/update_world/scene_ui/ui_background.rs @@ -123,6 +123,17 @@ pub fn set_ui_background( } for (scene_ent, background, link) in backgrounds.iter() { + if let Ok(children) = children.get(link.ui_entity) { + for child in children + .iter() + .filter(|c| prev_backgrounds.get(**c).is_ok()) + { + if let Some(commands) = commands.get_entity(*child) { + commands.despawn_recursive(); + } + } + } + let Some(mut commands) = commands.get_entity(link.ui_entity) else { continue; }; @@ -167,21 +178,25 @@ pub fn set_ui_background( center_region: rect.into(), tint: Some(image_color), }, + UiBackgroundMarker, )); }); } BackgroundTextureMode::Stretch(ref uvs) => { commands.try_with_children(|c| { - c.spawn(NodeBundle { - style: Style { - position_type: PositionType::Absolute, - width: Val::Percent(100.0), - height: Val::Percent(100.0), - overflow: Overflow::clip(), + c.spawn(( + NodeBundle { + style: Style { + position_type: PositionType::Absolute, + width: Val::Percent(100.0), + height: Val::Percent(100.0), + overflow: Overflow::clip(), + ..Default::default() + }, ..Default::default() }, - ..Default::default() - }) + UiBackgroundMarker, + )) .try_with_children(|c| { c.spawn((MaterialNodeBundle { style: Style { @@ -203,20 +218,23 @@ pub fn set_ui_background( BackgroundTextureMode::Center => { commands.try_with_children(|c| { // make a stretchy grid - c.spawn(NodeBundle { - style: Style { - position_type: PositionType::Absolute, - left: Val::Px(0.0), - right: Val::Px(0.0), - top: Val::Px(0.0), - bottom: Val::Px(0.0), - justify_content: JustifyContent::Center, - overflow: Overflow::clip(), - width: Val::Percent(100.0), + c.spawn(( + NodeBundle { + style: Style { + position_type: PositionType::Absolute, + left: Val::Px(0.0), + right: Val::Px(0.0), + top: Val::Px(0.0), + bottom: Val::Px(0.0), + justify_content: JustifyContent::Center, + overflow: Overflow::clip(), + width: Val::Percent(100.0), + ..Default::default() + }, ..Default::default() }, - ..Default::default() - }) + UiBackgroundMarker, + )) .try_with_children(|c| { c.spacer(); c.spawn(NodeBundle { diff --git a/crates/scene_runner/src/update_world/scene_ui/ui_pointer.rs b/crates/scene_runner/src/update_world/scene_ui/ui_pointer.rs index ce6fa000..88354f86 100644 --- a/crates/scene_runner/src/update_world/scene_ui/ui_pointer.rs +++ b/crates/scene_runner/src/update_world/scene_ui/ui_pointer.rs @@ -16,7 +16,19 @@ pub fn set_ui_pointer_events( Or<(Changed, Changed)>, ), >, + links: Query<&UiLink>, + mut removed: RemovedComponents, ) { + for ent in removed.read() { + let Ok(link) = links.get(ent) else { + continue; + }; + + if let Some(mut commands) = commands.get_entity(link.ui_entity) { + commands.remove::<(On, On)>(); + } + } + for (ent, link) in pes.iter() { if let Some(mut commands) = commands.get_entity(link.ui_entity) { let is_primary = link.is_window_ui; diff --git a/crates/system_ui/src/discover.rs b/crates/system_ui/src/discover.rs index e36a3cd1..9ecc600e 100644 --- a/crates/system_ui/src/discover.rs +++ b/crates/system_ui/src/discover.rs @@ -19,6 +19,7 @@ use ui_core::{ button::DuiButton, combo_box::ComboBox, interact_style::Active, + text_entry::TextEntryValue, toggle::Toggled, ui_actions::{close_ui_happy, Click, DataChanged, On, UiCaller}, }; @@ -171,12 +172,14 @@ impl SortBy { #[derive(Component, Default)] pub struct DiscoverSettings { - filter: HashSet, + category_filter: HashSet, + search_filter: Option, data: Vec, has_more: bool, task: Option>>, order_by: SortBy, worlds: bool, + search_timer: f32, } impl DiscoverSettings { @@ -188,20 +191,22 @@ impl DiscoverSettings { fn request(&mut self) { let mut url = if self.worlds { - "https://places.decentraland.org/api/worlds/?limit=20" + "https://places.decentraland.org/api/worlds/?limit=50" } else { - "https://places.decentraland.org/api/places/?limit=20" + "https://places.decentraland.org/api/places/?limit=50" } .to_string(); url = format!("{url}&offset={}", self.data.len()); - for cat in &self.filter { + for cat in &self.category_filter { url = format!("{url}&categories={}", cat.param()); } url = format!("{url}&order_by={}", self.order_by.param()); + debug!("request: {url}"); + self.task = Some(IoTaskPool::get().spawn(async move { let mut response = isahc::get_async(url).await?; @@ -273,10 +278,10 @@ fn set_discover_content( if is_active { commands.entity(caller.0).insert(Active(false)); - settings.filter.remove(&cat); + settings.category_filter.remove(&cat); } else { commands.entity(caller.0).insert(Active(true)); - settings.filter.insert(cat); + settings.category_filter.insert(cat); } settings.clear_and_request(); @@ -333,6 +338,34 @@ fn set_discover_content( settings.clear_and_request(); }, ), + ) + .with_prop( + "initial-filter", + discover_settings.search_filter.clone().unwrap_or_default(), + ) + .with_prop( + "filter-changed", + On::::new( + |caller: Res, + q: Query<&TextEntryValue>, + mut settings: Query<&mut DiscoverSettings>| { + let Ok(value) = q.get(caller.0).map(|te| te.0.clone()) else { + warn!("no value from text entry?"); + return; + }; + if settings.single().search_filter.as_deref().unwrap_or("") == value { + // no change + return; + } + if value.is_empty() { + settings.single_mut().search_filter = None; + } else { + let mut settings = settings.single_mut(); + settings.search_filter = Some(value); + settings.search_timer = 1.0; + } + }, + ), ); commands @@ -401,89 +434,157 @@ fn update_results(mut q: Query<&mut DiscoverSettings>) { fn update_page( mut commands: Commands, - settings: Query<(&DiscoverSettings, &DuiEntities), Changed>, - content: Query<(Entity, Option<&Children>)>, + settings: Query<(Entity, &DiscoverSettings, &DuiEntities), Changed>, dui: Res, ipfas: IpfsAssetServer, + mut prev_count: Local, + mut prev_search: Local>, + time: Res