Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using CrdtEcsBridge.Components;
using System;
using System.Collections.Generic;
using System.Threading;
using Utility.Multithreading;

namespace CrdtEcsBridge.WorldSynchronizer
Expand All @@ -26,7 +25,7 @@ public class CRDTWorldSynchronizer : ICRDTWorldSynchronizer

// We can't use a mutex as it must be acquired and released by the same thread
// and it is not guaranteed as we use thread pools (in the most cases different threads are used for getting and applying command buffers)
private readonly DCLSemaphoreSlim semaphore = new (1, 1);
private readonly DCLSemaphoreSlim semaphore = new ();

private bool disposed;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class ArchipelagoSignedConnection : IArchipelagoLiveConnection

private readonly IArchipelagoLiveConnection origin;

private readonly DCLSemaphoreSlim semaphore = new (1, 1);
private readonly DCLSemaphoreSlim semaphore = new ();

private readonly IMemoryPool memoryPool;
private readonly IMultiPool multiPool;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,23 @@ internal DebugViewCurrentSceneSystem(World world, IDebugContainerBuilder debugBu
.AddCustomMarker("Min FPS (last 256 ticks):", stringBindings.MinFps)
.AddCustomMarker("Max FPS (last 256 ticks):", stringBindings.MaxFps)
.AddCustomMarker("Hiccups (last 256 ticks):", stringBindings.Hiccups)
.AddControl(new DebugLineChartDef(fpsChart, "Tick FPS", new Color(0.18f, 0.80f, 0.44f), DebugLongMarkerDef.Unit.NoFormat), null)
.AddControl(new DebugLineChartDef(fpsChart, "Tick FPS", new Color(0.18f, 0.80f, 0.44f)), null)
.AddCustomMarker("Bytes from scene:", stringBindings.BytesFromTotal)
.AddCustomMarker("Bytes/s from scene:", stringBindings.BytesFromPerSec)
.AddControl(new DebugLineChartDef(bytesFromChart, "Bytes/tick from scene", new Color(0.20f, 0.60f, 0.86f), DebugLongMarkerDef.Unit.Bytes), null)
.AddCustomMarker("Msgs from scene:", stringBindings.MessagesFromTotal)
.AddCustomMarker("Msgs/s from scene:", stringBindings.MessagesFromPerSec)
.AddCustomMarker("Msgs/call min/max from scene:", stringBindings.MessagesFromMinMax)
.AddCustomMarker("Msg hiccups from scene:", stringBindings.MessagesFromHiccups)
.AddControl(new DebugLineChartDef(messagesFromChart, "Msgs/tick from scene", new Color(0.40f, 0.80f, 0.95f), DebugLongMarkerDef.Unit.NoFormat), null)
.AddControl(new DebugLineChartDef(messagesFromChart, "Msgs/tick from scene", new Color(0.40f, 0.80f, 0.95f)), null)
.AddCustomMarker("Bytes to scene:", stringBindings.BytesToTotal)
.AddCustomMarker("Bytes/s to scene:", stringBindings.BytesToPerSec)
.AddControl(new DebugLineChartDef(bytesToChart, "Bytes/tick to scene", new Color(0.91f, 0.30f, 0.55f), DebugLongMarkerDef.Unit.Bytes), null)
.AddCustomMarker("Msgs to scene:", stringBindings.MessagesToTotal)
.AddCustomMarker("Msgs/s to scene:", stringBindings.MessagesToPerSec)
.AddCustomMarker("Msgs/call min/max to scene:", stringBindings.MessagesToMinMax)
.AddCustomMarker("Msg hiccups to scene:", stringBindings.MessagesToHiccups)
.AddControl(new DebugLineChartDef(messagesToChart, "Msgs/tick to scene", new Color(0.98f, 0.55f, 0.75f), DebugLongMarkerDef.Unit.NoFormat), null);
.AddControl(new DebugLineChartDef(messagesToChart, "Msgs/tick to scene", new Color(0.98f, 0.55f, 0.75f)), null);
}

protected override void Update(float t)
Expand Down
108 changes: 64 additions & 44 deletions Explorer/Assets/DCL/SDKComponents/MediaStream/IYouTubeVideoClient.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using Cysharp.Threading.Tasks;
using DCL.Diagnostics;
using DCL.SDKComponents.MediaStream.YouTube;
using DCL.Utilities.Extensions;
using DCL.Utility.Types;
using DCL.WebRequests;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -106,35 +107,79 @@ public async UniTask<string> GetStreamingManifestUrlAsync(VideoId videoId, Cance
/// fmp4 fragment instead of a single segment over the entire file. AVPro can then
/// start playback after fetching the first ~5-10s chunk rather than the whole body
/// (issue #8350). If sidx fetch or parse fails, falls back to single-segment.
///
/// Deliberately contains no try/catch/using around its await: an exception unwinding
/// through this async state machine's catch and finally funclets crashes IL2CPP on
/// Windows inside catch-clause matching (Sentry UNITY-EXPLORER-P77). The fetch is
/// therefore exception-free by contract and everything that can throw lives in the
/// synchronous <see cref="WriteSynthesizedHls"/>.
/// </summary>
private async UniTask<string?> TryWriteSynthesizedHlsAsync(VideoId videoId, PlayerResponse response, CancellationToken ct)
{
try
if (response.DurationSeconds <= 0
|| !HlsManifestBuilder.TrySelectVideoAndAudio(response.AdaptiveFormats, out AdaptiveFormatData videoStream, out AdaptiveFormatData audioStream))
{
if (response.DurationSeconds <= 0
|| !HlsManifestBuilder.TrySelectVideoAndAudio(response.AdaptiveFormats, out AdaptiveFormatData videoStream, out AdaptiveFormatData audioStream))
{
ReportHub.Log(ReportCategory.MEDIA_STREAM,
$"[{TAG}] HLS synthesis skipped for {videoId.Value} — no usable mp4 video+audio adaptive pair");
return null;
}
ReportHub.Log(ReportCategory.MEDIA_STREAM,
$"[{TAG}] HLS synthesis skipped for {videoId.Value} — no usable mp4 video+audio adaptive pair");
return null;
}

// Fetch both sidx boxes in parallel. Each is typically a few KB.
(Result<byte[]> videoSidx, Result<byte[]> audioSidx) = await UniTask.WhenAll(
FetchByteRangeAsync(videoStream.Url, videoStream.IndexRangeStart, videoStream.IndexRangeEnd, ct),
FetchByteRangeAsync(audioStream.Url, audioStream.IndexRangeStart, audioStream.IndexRangeEnd, ct));

if (ct.IsCancellationRequested)
return null;

return WriteSynthesizedHls(videoId, videoStream, audioStream, response.DurationSeconds, videoSidx, audioSidx);
}

/// <summary>
/// Fetches an inclusive byte range from <paramref name="url"/> via the project's
/// <see cref="IWebRequestController"/>, using the built-in <c>Range</c> header support.
/// Never throws — failures and cancellation both surface as an unsuccessful
/// <see cref="Result{T}" /> so no exception crosses the <c>UniTask.WhenAll</c> boundary.
/// </summary>
private UniTask<Result<byte[]>> FetchByteRangeAsync(string url, long start, long endInclusive, CancellationToken ct) =>
webRequestController
.GetAsync(url, ct, ReportCategory.MEDIA_STREAM,
headersInfo: new WebRequestHeadersInfo().WithRange(start, endInclusive),
suppressErrors: true)
.GetDataCopyAsync()
// Fixes: https://github.com/decentraland/unity-explorer/issues/9758
.SuppressToResultAsync();

/// <summary>
/// Parses the pre-fetched sidx boxes, builds the 3 playlists and writes them to the
/// temp cache, returning the absolute path of the master playlist. Returns null if
/// any step fails. Synchronous on purpose — see <see cref="TryWriteSynthesizedHlsAsync"/>.
/// </summary>
private static string? WriteSynthesizedHls(
VideoId videoId,
AdaptiveFormatData videoStream,
AdaptiveFormatData audioStream,
int durationSeconds,
Result<byte[]> videoSidx,
Result<byte[]> audioSidx)
{
try
{
using var _ = ListPool<SidxParser.SegmentInfo>.Get(out var videoSegments);
using var __ = ListPool<SidxParser.SegmentInfo>.Get(out var audioSegments);

// Fetch both sidx boxes in parallel. Each is typically a few KB.
(byte[]? videoSidx, byte[]? audioSidx) = await UniTask.WhenAll(
TryFetchByteRangeAsync(videoStream.Url, videoStream.IndexRangeStart, videoStream.IndexRangeEnd, ct),
TryFetchByteRangeAsync(audioStream.Url, audioStream.IndexRangeStart, audioStream.IndexRangeEnd, ct));
if (videoSidx.Success)
SidxParser.TryParse(videoSidx.Value, videoStream.IndexRangeEnd + 1, videoSegments);
else
ReportHub.Log(ReportCategory.MEDIA_STREAM, $"[{TAG}] video sidx byte-range fetch failed: {videoSidx.ErrorMessage}");

if (videoSidx != null)
SidxParser.TryParse(videoSidx, videoStream.IndexRangeEnd + 1, videoSegments);

if (audioSidx != null)
SidxParser.TryParse(audioSidx, audioStream.IndexRangeEnd + 1, audioSegments);
if (audioSidx.Success)
SidxParser.TryParse(audioSidx.Value, audioStream.IndexRangeEnd + 1, audioSegments);
else
ReportHub.Log(ReportCategory.MEDIA_STREAM, $"[{TAG}] audio sidx byte-range fetch failed: {audioSidx.ErrorMessage}");

HlsManifestBuilder.PlaylistSet playlists =
HlsManifestBuilder.Build(videoStream, audioStream, response.DurationSeconds, videoSegments, audioSegments);
HlsManifestBuilder.Build(videoStream, audioStream, durationSeconds, videoSegments, audioSegments);

// Per-video subdirectory keeps the 3 files together so the master playlist's
// relative URIs (audio.m3u8, video.m3u8) resolve correctly. Unity's
Expand All @@ -150,37 +195,12 @@ public async UniTask<string> GetStreamingManifestUrlAsync(VideoId videoId, Cance

return masterPath;
}
catch (OperationCanceledException) { return null; }
catch (Exception ex)
{
ReportHub.LogWarning(ReportCategory.MEDIA_STREAM,
$"[{TAG}] HLS synthesis failed for {videoId.Value}: {ex.Message}");
return null;
}
}

/// <summary>
/// Fetches an inclusive byte range from <paramref name="url"/> via the project's
/// <see cref="IWebRequestController"/>, using the built-in <c>Range</c> header support.
/// Returns the response body on success or null on failure — callers treat null as
/// "fall back to non-segmented playlist." Cancellation propagates.
/// </summary>
private async UniTask<byte[]?> TryFetchByteRangeAsync(string url, long start, long endInclusive, CancellationToken ct)
{
try
{
return await webRequestController
.GetAsync(url, ct, ReportCategory.MEDIA_STREAM,
headersInfo: new WebRequestHeadersInfo().WithRange(start, endInclusive),
suppressErrors: true)
.GetDataCopyAsync();
}
catch (OperationCanceledException) { throw; }
catch (Exception ex)
{
ReportHub.Log(ReportCategory.MEDIA_STREAM, $"[{TAG}] sidx byte-range fetch failed: {ex.Message}");
return null;
}
}
}
}
4 changes: 2 additions & 2 deletions Explorer/Assets/DCL/Social/RPCSocialServices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ public class RPCSocialServices : IRPCSocialServices
/// <summary>
/// Used to ensure that only one connection establishment process is running at a time.
/// </summary>
private readonly DCLSemaphoreSlim connectionEstablishingMutex = new (1, 1);
private readonly DCLSemaphoreSlim connectionEstablishingMutex = new ();

/// <summary>
/// Used to ensure that handshake and disconnection processes do not overlap.
/// </summary>
private readonly DCLSemaphoreSlim handshakeMutex = new (1, 1);
private readonly DCLSemaphoreSlim handshakeMutex = new ();

private readonly Uri apiUrl;
private readonly IWeb3IdentityCache identityCache;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public partial class DappWeb3EthereumApi : IEthereumApi
private readonly int? identityExpirationDuration;

// Allow only one web3 operation at a time
private readonly DCLSemaphoreSlim mutex = new (1, 1);
private readonly DCLSemaphoreSlim mutex = new ();
private readonly byte[] rpcByteBuffer = new byte[RPC_BUFFER_SIZE];
private readonly URLBuilder urlBuilder = new ();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class ThirdWebEthereumApi

public TransactionConfirmationDelegate? TransactionConfirmationCallback { private get; set; }

private readonly DCLSemaphoreSlim mutex = new (1, 1);
private readonly DCLSemaphoreSlim mutex = new ();
private readonly ThirdWebMetaTxService metaTxService;

public ThirdWebEthereumApi(
Expand Down
Loading