Skip to content
Draft
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 @@ -298,6 +298,7 @@ public UniTask InitializeAsync(StaticSettings settings, CancellationToken ct)
new AssetsCollidersPlugin(sharedDependencies),
new AvatarShapePlugin(globalWorld, componentsContainer.ComponentPoolsRegistry, launchMode, useRemoteAssetBundles),
new PrimitivesRenderingPlugin(sharedDependencies),
new SceneContentStatsPlugin(),
new VisibilityPlugin(),
new AudioSourcesPlugin(sharedDependencies, container.WebRequestsContainer.WebRequestController, container.CacheCleaner, container.assetsProvisioner),
new AudioAnalysisPlugin(sharedDependencies),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public SceneInstanceDependencies(
/* Pass dependencies here if they are needed by the systems */
ecsWorldSharedDependencies = new ECSWorldInstanceSharedDependencies(sceneData, partitionProvider, ecsToCRDTWriter, entitiesMap,
ExceptionsHandler, EntityCollidersCache, SceneStateProvider, entityEventsBuilder, ecsMultiThreadSync,
systemGroupThrottler, systemsUpdateGate);
systemGroupThrottler, systemsUpdateGate, RuntimeMetrics);

ECSWorldFacade = ecsWorldFactory.CreateWorld(new ECSWorldFactoryArgs(ecsWorldSharedDependencies, systemGroupThrottler, sceneData));
CRDTWorldSynchronizer = new CRDTWorldSynchronizer(ECSWorldFacade.EcsWorld, sdkComponentsRegistry, entityFactory, entitiesMap);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,172 @@
using DCL.DebugUtilities;
using DCL.DebugUtilities.UIBindings;
using DCL.Utilities;
using SceneRunner.Scene;
using System;
using System.Globalization;
using UnityEngine;

namespace DCL.Profiling.ECS
{
public partial class DebugViewCurrentSceneSystem
{
// Hardcoded scene limits derived from the parcel count (n), matching the documented
// Decentraland scene limitations; colliders and external content have no documented
// limit, so they use project-chosen caps.
private const int MAX_TRIANGLES_PER_PARCEL = 10_000;
private const int MAX_ENTITIES_PER_PARCEL = 200;
private const int MAX_BODIES_PER_PARCEL = 300;
private const int MAX_COLLIDERS_PER_PARCEL = 300;
private const int MAX_MATERIALS_LOG2_MULTIPLIER = 20;
private const int MAX_TEXTURES_LOG2_MULTIPLIER = 10;
private const int MAX_GEOMETRIES_LOG2_MULTIPLIER = 200;
private const long MAX_CONTENT_BYTES_PER_PARCEL = 15L * 1024 * 1024;
private const int MAX_EXTERNAL_CONTENT = 10;

private const float CAP_WARNING_PERCENT = 80f;

private readonly struct SceneContentCaps
{
public readonly int Entities;
public readonly long Triangles;
public readonly int Bodies;
public readonly int Geometries;
public readonly int Materials;
public readonly int Textures;
public readonly int Colliders;
public readonly long ContentSizeBytes;
public readonly int ExternalContent;

private SceneContentCaps(int entities, long triangles, int bodies, int geometries, int materials, int textures, int colliders, long contentSizeBytes, int externalContent)
{
Entities = entities;
Triangles = triangles;
Bodies = bodies;
Geometries = geometries;
Materials = materials;
Textures = textures;
Colliders = colliders;
ContentSizeBytes = contentSizeBytes;
ExternalContent = externalContent;
}

public static SceneContentCaps ForParcelCount(int parcelCount)
{
float log2 = Mathf.Log(parcelCount + 1, 2f);

return new SceneContentCaps(
entities: parcelCount * MAX_ENTITIES_PER_PARCEL,
triangles: (long)parcelCount * MAX_TRIANGLES_PER_PARCEL,
bodies: parcelCount * MAX_BODIES_PER_PARCEL,
geometries: Mathf.FloorToInt(log2 * MAX_GEOMETRIES_LOG2_MULTIPLIER),
materials: Mathf.FloorToInt(log2 * MAX_MATERIALS_LOG2_MULTIPLIER),
textures: Mathf.FloorToInt(log2 * MAX_TEXTURES_LOG2_MULTIPLIER),
colliders: parcelCount * MAX_COLLIDERS_PER_PARCEL,
contentSizeBytes: parcelCount * MAX_CONTENT_BYTES_PER_PARCEL,
externalContent: MAX_EXTERNAL_CONTENT);
}
}

private readonly struct ContentStatsBindings
{
public readonly ElementBinding<string> Entities;
public readonly ElementBinding<string> Triangles;
public readonly ElementBinding<string> Bodies;
public readonly ElementBinding<string> Geometries;
public readonly ElementBinding<string> Materials;
public readonly ElementBinding<string> Textures;
public readonly ElementBinding<string> Colliders;
public readonly ElementBinding<string> ContentSize;
public readonly ElementBinding<string> ExternalContent;

private ContentStatsBindings(
ElementBinding<string> entities,
ElementBinding<string> triangles,
ElementBinding<string> bodies,
ElementBinding<string> geometries,
ElementBinding<string> materials,
ElementBinding<string> textures,
ElementBinding<string> colliders,
ElementBinding<string> contentSize,
ElementBinding<string> externalContent)
{
Entities = entities;
Triangles = triangles;
Bodies = bodies;
Geometries = geometries;
Materials = materials;
Textures = textures;
Colliders = colliders;
ContentSize = contentSize;
ExternalContent = externalContent;
}

public static ContentStatsBindings Create() =>
new (
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty),
new ElementBinding<string>(string.Empty));
}

private static void UpdateContentStatsBindings(in ContentStatsBindings bindings, SceneContentStats stats, in SceneContentCaps caps)
{
if (!stats.HasData)
{
bindings.Entities.Value = "—";
bindings.Triangles.Value = "—";
bindings.Bodies.Value = "—";
bindings.Geometries.Value = "—";
bindings.Materials.Value = "—";
bindings.Textures.Value = "—";
bindings.Colliders.Value = "—";
bindings.ContentSize.Value = "—";
bindings.ExternalContent.Value = "—";
return;
}

bindings.Entities.Value = FormatCapped(stats.Entities, caps.Entities);
bindings.Triangles.Value = FormatCapped(stats.Triangles, caps.Triangles);
bindings.Bodies.Value = FormatCapped(stats.Bodies, caps.Bodies);
bindings.Geometries.Value = FormatCapped(stats.Geometries, caps.Geometries);
bindings.Materials.Value = FormatCapped(stats.Materials, caps.Materials);
bindings.Textures.Value = FormatCapped(stats.Textures, caps.Textures);
bindings.Colliders.Value = FormatCapped(stats.Colliders, caps.Colliders);
bindings.ContentSize.Value = FormatCappedBytes(stats.ContentSizeBytes, caps.ContentSizeBytes);
bindings.ExternalContent.Value = FormatCapped(stats.ExternalContent, caps.ExternalContent);
}

private static string FormatCapped(long current, long cap)
{
if (cap <= 0)
return current.ToString("N0", CultureInfo.InvariantCulture);

float percent = current * 100f / cap;
return $"<color={CapColor(percent)}>{current.ToString("N0", CultureInfo.InvariantCulture)} / {cap.ToString("N0", CultureInfo.InvariantCulture)} ({percent:F0}%)</color>";
}

private static string FormatCappedBytes(long current, long cap)
{
string currentNormalized = BytesFormatter.Normalize((ulong)Math.Max(0L, current), false);

if (cap <= 0)
return currentNormalized;

float percent = current * 100f / cap;
return $"<color={CapColor(percent)}>{currentNormalized} / {BytesFormatter.Normalize((ulong)cap, false)} ({percent:F0}%)</color>";
}

private static string CapColor(float percent) =>
percent switch
{
>= 100f => "red",
>= CAP_WARNING_PERCENT => "yellow",
_ => "green",
};

private readonly struct StringBindings
{
public readonly ElementBinding<string> RealFps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public partial class DebugViewCurrentSceneSystem : BaseUnityLoopSystem
private readonly DebugWidgetVisibilityBinding visibility;

private readonly StringBindings stringBindings;
private readonly ContentStatsBindings contentStatsBindings;

private readonly ElementBinding<LineChartBuffer> fpsChart;
private readonly ElementBinding<LineChartBuffer> bytesFromChart;
Expand All @@ -50,6 +51,7 @@ public partial class DebugViewCurrentSceneSystem : BaseUnityLoopSystem
private readonly Action<ISceneFacade?>? onCurrentSceneChanged;

private ISceneFacade? currentScene;
private SceneContentCaps contentCaps;
private long lastBytesFromScene;
private long lastBytesToScene;
private long lastMessagesFromScene;
Expand All @@ -64,6 +66,7 @@ internal DebugViewCurrentSceneSystem(World world, IDebugContainerBuilder debugBu

visibility = new DebugWidgetVisibilityBinding(true);
stringBindings = StringBindings.Create();
contentStatsBindings = ContentStatsBindings.Create();

fpsChart = new ElementBinding<LineChartBuffer>(new LineChartBuffer(fpsRing, 0, 0, 0));
bytesFromChart = new ElementBinding<LineChartBuffer>(new LineChartBuffer(bytesFromRing, 0, 0, 0));
Expand All @@ -83,6 +86,15 @@ internal DebugViewCurrentSceneSystem(World world, IDebugContainerBuilder debugBu
OnCurrentSceneChanged(scenesCache.CurrentScene.Value);

widgetBuilder.SetVisibilityBinding(visibility)
.AddCustomMarker("Entities:", contentStatsBindings.Entities)
.AddCustomMarker("Triangles:", contentStatsBindings.Triangles)
.AddCustomMarker("Meshes (bodies):", contentStatsBindings.Bodies)
.AddCustomMarker("Geometries:", contentStatsBindings.Geometries)
.AddCustomMarker("Materials:", contentStatsBindings.Materials)
.AddCustomMarker("Textures:", contentStatsBindings.Textures)
.AddCustomMarker("Colliders:", contentStatsBindings.Colliders)
.AddCustomMarker("Content size:", contentStatsBindings.ContentSize)
.AddCustomMarker("External content:", contentStatsBindings.ExternalContent)
.AddCustomMarker("Real tick FPS:", stringBindings.RealFps)
.AddCustomMarker("Min FPS (last 256 ticks):", stringBindings.MinFps)
.AddCustomMarker("Max FPS (last 256 ticks):", stringBindings.MaxFps)
Expand Down Expand Up @@ -110,10 +122,12 @@ protected override void Update(float t)
{
if (!widgetEnabled) return;
if (!realmData.Configured) return;
if (!visibility.IsConnectedAndExpanded) return;
if (currentScene == null) return;

SceneRuntimeMetrics metrics = currentScene.RuntimeMetrics;
metrics.ContentStats.CollectionRequested = visibility.IsConnectedAndExpanded;

if (!visibility.IsConnectedAndExpanded) return;

long bytesFrom = metrics.BytesFromScene.Total;
long bytesTo = metrics.BytesToScene.Total;
Expand Down Expand Up @@ -150,6 +164,7 @@ protected override void Update(float t)
framesSinceMetricsUpdate = 0;
UpdateStringBindings(in stringBindings, metrics, currentFpsValue, minFpsValue, maxFpsValue, hiccupCount,
deltaBytesFrom, deltaBytesTo, deltaMessagesFrom, deltaMessagesTo, dt);
UpdateContentStatsBindings(in contentStatsBindings, metrics.ContentStats, in contentCaps);
}
}

Expand All @@ -161,7 +176,11 @@ protected override void OnDispose()

private void OnCurrentSceneChanged(ISceneFacade? scene)
{
if (currentScene != null)
currentScene.RuntimeMetrics.ContentStats.CollectionRequested = false;

currentScene = scene;
contentCaps = scene != null ? SceneContentCaps.ForParcelCount(scene.SceneData.Parcels.Count) : default(SceneContentCaps);
ResetLocalState();

if (scene != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace DCL.Profiling
{
/// <summary>
/// Per-scene content statistics for the "Current scene" debug widget. Written by
/// <c>SceneContentStatsSystem</c> in the scene world and read by <c>DebugViewCurrentSceneSystem</c>
/// in the global world; both run on the Unity main thread.
/// </summary>
public sealed class SceneContentStats
{
/// <summary>
/// While false the scene world skips collection entirely, so the counters cost nothing
/// when the debug panel is disabled or the widget is collapsed.
/// </summary>
public bool CollectionRequested;

/// <summary>
/// False until the first collection pass completes for this scene.
/// </summary>
public bool HasData;

public int Entities;
public long Triangles;
public int Bodies;
public int Geometries;
public int Materials;
public int Textures;
public int Colliders;
public long ContentSizeBytes;
public int ExternalContent;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public sealed class SceneRuntimeMetrics
public readonly SampledCounter MessagesFromScene = new ();
public readonly SampledCounter MessagesToScene = new ();

/// <summary>
/// Unlike the counters above, written and read on the Unity main thread only.
/// </summary>
public readonly SceneContentStats ContentStats = new ();

public int TargetFps { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using CrdtEcsBridge.ECSToCRDTWriter;
using CrdtEcsBridge.UpdateGate;
using DCL.Interaction.Utility;
using DCL.Profiling;
using ECS.Abstract;
using ECS.Prioritization.Components;
using SceneRunner.Scene;
Expand All @@ -25,6 +26,7 @@ public readonly struct ECSWorldInstanceSharedDependencies
public readonly MultiThreadSync MultiThreadSync;
public readonly ISystemGroupsUpdateGate EcsGroupThrottler;
public readonly ISystemsUpdateGate EcsSystemsGate;
public readonly SceneRuntimeMetrics RuntimeMetrics;

public ECSWorldInstanceSharedDependencies(
ISceneData sceneData,
Expand All @@ -35,7 +37,8 @@ public ECSWorldInstanceSharedDependencies(
IEntityCollidersSceneCache entityCollidersSceneCache,
ISceneStateProvider sceneStateProvider, EntityEventsBuilder entityEventsBuilder,
MultiThreadSync multiThreadSync,
ISystemGroupsUpdateGate ecsGroupThrottler, ISystemsUpdateGate ecsSystemsGate)
ISystemGroupsUpdateGate ecsGroupThrottler, ISystemsUpdateGate ecsSystemsGate,
SceneRuntimeMetrics runtimeMetrics)
{
SceneData = sceneData;
EcsToCRDTWriter = ecsToCRDTWriter;
Expand All @@ -48,6 +51,7 @@ public ECSWorldInstanceSharedDependencies(
EcsGroupThrottler = ecsGroupThrottler;
EcsSystemsGate = ecsSystemsGate;
EntityEventsBuilder = entityEventsBuilder;
RuntimeMetrics = runtimeMetrics;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Arch.SystemGroups;
using DCL.PluginSystem.World.Dependencies;
using DCL.SDKComponents.SceneContentDebug.Systems;
using ECS.LifeCycle;
using System.Collections.Generic;

namespace DCL.PluginSystem.World
{
public class SceneContentStatsPlugin : IDCLWorldPluginWithoutSettings
{
public void InjectToWorld(ref ArchSystemsWorldBuilder<Arch.Core.World> builder, in ECSWorldInstanceSharedDependencies sharedDependencies, in SystemsDependencies systemsDependencies, in PersistentEntities persistentEntities, List<IFinalizeWorldSystem> finalizeWorldSystems, List<ISceneIsCurrentListener> sceneIsCurrentListeners)
{
SceneContentStatsSystem.InjectToWorld(ref builder, sharedDependencies.RuntimeMetrics, sharedDependencies.EntitiesMap);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Explorer/Assets/DCL/SDKComponents/SceneContentDebug.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading