diff --git a/Explorer/Assets/DCL/AvatarRendering/DemoScripts/Systems/InstantiateRandomAvatarsSystem.cs b/Explorer/Assets/DCL/AvatarRendering/DemoScripts/Systems/InstantiateRandomAvatarsSystem.cs index 7129f7c05d..1be20aedf8 100644 --- a/Explorer/Assets/DCL/AvatarRendering/DemoScripts/Systems/InstantiateRandomAvatarsSystem.cs +++ b/Explorer/Assets/DCL/AvatarRendering/DemoScripts/Systems/InstantiateRandomAvatarsSystem.cs @@ -1,7 +1,6 @@ using Arch.Core; using Arch.System; using Arch.SystemGroups; -using Arch.SystemGroups.DefaultSystemGroups; using CommunicationData.URLHelpers; using CrdtEcsBridge.Physics; using DCL.AvatarRendering.AvatarShape; @@ -311,7 +310,7 @@ private void GenerateRandomAvatars(int randomAvatarsToInstantiate, Vector3 camer foreach (string wearable in wearables) wearablesURN.Add(new URN(wearable)); - var avatarShape = new Profile( + var avatarShape = Profile.Create( StringUtils.GenerateRandomString(5), StringUtils.GenerateRandomString(5), new Avatar(BodyShape.FromStringSafe(bodyShape), wearablesURN, WearablesConstants.DefaultColors.GetRandomEyesColor(), WearablesConstants.DefaultColors.GetRandomHairColor(), WearablesConstants.DefaultColors.GetRandomSkinColor())); diff --git a/Explorer/Assets/DCL/Multiplayer/SDK/Systems/GlobalWorld/PlayerProfileDataPropagationSystem.cs b/Explorer/Assets/DCL/Multiplayer/SDK/Systems/GlobalWorld/PlayerProfileDataPropagationSystem.cs index aad9b3c391..899eb9ee0e 100644 --- a/Explorer/Assets/DCL/Multiplayer/SDK/Systems/GlobalWorld/PlayerProfileDataPropagationSystem.cs +++ b/Explorer/Assets/DCL/Multiplayer/SDK/Systems/GlobalWorld/PlayerProfileDataPropagationSystem.cs @@ -42,7 +42,7 @@ private void PropagateProfileToScene(Profile profile, PlayerCRDTEntity playerCRD private void SetSceneProfile(Profile profile, PlayerCRDTEntity playerCRDTEntity) { SceneEcsExecutor sceneEcsExecutor = playerCRDTEntity.SceneFacade.EcsExecutor; - var newProfile = new Profile(profile.UserId, profile.Name, profile.Avatar); // TODO reuse the profile object + var newProfile = Profile.Create(profile.UserId, profile.Name, profile.Avatar); ref var profileComponent = ref sceneEcsExecutor.World.AddOrGet(playerCRDTEntity.SceneWorldEntity); profileComponent = newProfile; } diff --git a/Explorer/Assets/DCL/PerformanceAndDiagnostics/Profiling/ProfilingCounters.cs b/Explorer/Assets/DCL/PerformanceAndDiagnostics/Profiling/ProfilingCounters.cs index e52fcc9e57..3e128701ad 100644 --- a/Explorer/Assets/DCL/PerformanceAndDiagnostics/Profiling/ProfilingCounters.cs +++ b/Explorer/Assets/DCL/PerformanceAndDiagnostics/Profiling/ProfilingCounters.cs @@ -76,8 +76,8 @@ public static class { LOD_0_Amount, LOD_1_Amount, LOD_2_Amount, LOD_3_Amount }; - - + + // Textures cache public static ProfilerCounterValue TexturesAmount = new (MEMORY, "Textures", ProfilerMarkerDataUnit.Count); @@ -95,9 +95,11 @@ public static class public static ProfilerCounterValue AudioClipsReferenced = new (MEMORY, "AudioClips Referenced", ProfilerMarkerDataUnit.Count); - // Profiles - public static ProfilerCounterValue ProfileIntentionsInCache = - new (MEMORY, "Profile Intentions In Cache", ProfilerMarkerDataUnit.Count); + public static ProfilerCounterValue ProfilesInCache = + new (MEMORY, "Profiles In Cache", ProfilerMarkerDataUnit.Count); + + public static ProfilerCounterValue ProfilesInPool = + new (MEMORY, "Profiles In Pool", ProfilerMarkerDataUnit.Count); public static void CleanAllCounters() { @@ -123,7 +125,7 @@ public static void CleanAllCounters() LOD_1_Amount.Value = 0; LOD_2_Amount.Value = 0; Failling_LOD_Amount.Value = 0; - + TexturesAmount.Value = 0; TexturesInCache.Value = 0; @@ -131,7 +133,7 @@ public static void CleanAllCounters() AudioClipsInCache.Value = 0; AudioClipsReferenced.Value = 0; - ProfileIntentionsInCache.Value = 0; + ProfilesInCache.Value = 0; } } } diff --git a/Explorer/Assets/DCL/Profiles/Cache/DefaultProfileCache.cs b/Explorer/Assets/DCL/Profiles/Cache/DefaultProfileCache.cs index eaab628fb5..329cb4e2d7 100644 --- a/Explorer/Assets/DCL/Profiles/Cache/DefaultProfileCache.cs +++ b/Explorer/Assets/DCL/Profiles/Cache/DefaultProfileCache.cs @@ -13,6 +13,10 @@ public class DefaultProfileCache : IProfileCache public void Set(string id, Profile profile) { + if (profiles.TryGetValue(id, out Profile existingProfile)) + if (existingProfile != profile) + existingProfile.Dispose(); + profiles[id] = profile; UpdateProfilingCounter(); @@ -27,6 +31,9 @@ public void Unload(IPerformanceBudget concurrentBudgetProvider, int maxAmount) public void Remove(string id) { + if (profiles.TryGetValue(id, out Profile existingProfile)) + existingProfile.Dispose(); + profiles.Remove(id); UpdateProfilingCounter(); @@ -35,7 +42,7 @@ public void Remove(string id) private void UpdateProfilingCounter() { #if UNITY_EDITOR || DEVELOPMENT_BUILD - ProfilingCounters.ProfileIntentionsInCache.Value = profiles.Count; + ProfilingCounters.ProfilesInCache.Value = profiles.Count; #endif } } diff --git a/Explorer/Assets/DCL/Profiles/Cache/ProfileIntentionCache.cs b/Explorer/Assets/DCL/Profiles/Cache/ProfileIntentionCache.cs index e1699db242..b41e5a4152 100644 --- a/Explorer/Assets/DCL/Profiles/Cache/ProfileIntentionCache.cs +++ b/Explorer/Assets/DCL/Profiles/Cache/ProfileIntentionCache.cs @@ -39,7 +39,7 @@ public void Add(in GetProfileIntention key, Profile asset) unloadQueue.Enqueue(key, MultithreadingUtility.FrameCount); #if UNITY_EDITOR || DEVELOPMENT_BUILD - ProfilingCounters.ProfileIntentionsInCache.Value = cache.Count; + ProfilingCounters.ProfilesInCache.Value = cache.Count; #endif } @@ -52,7 +52,7 @@ public void Unload(IPerformanceBudget frameTimeBudgetProvider, int maxUnloadAmou cache.Remove(key); #if UNITY_EDITOR || DEVELOPMENT_BUILD - ProfilingCounters.ProfileIntentionsInCache.Value = cache.Count; + ProfilingCounters.ProfilesInCache.Value = cache.Count; #endif } } diff --git a/Explorer/Assets/DCL/Profiles/Components/Profile.cs b/Explorer/Assets/DCL/Profiles/Components/Profile.cs index bc69c55c7c..dd78fa0840 100644 --- a/Explorer/Assets/DCL/Profiles/Components/Profile.cs +++ b/Explorer/Assets/DCL/Profiles/Components/Profile.cs @@ -1,6 +1,7 @@ using DCL.AvatarRendering.Wearables; using DCL.AvatarRendering.Wearables.Helpers; using DCL.ECSComponents; +using DCL.Optimization.ThreadSafePool; using ECS.StreamableLoading.Common.Components; using System; using System.Collections.Generic; @@ -9,9 +10,12 @@ namespace DCL.Profiles { - public class Profile : IDirtyMarker + public class Profile : IDirtyMarker, IDisposable { private static readonly Regex VALID_NAME_CHARACTERS = new ("[a-zA-Z0-9]"); + private static readonly ThreadSafeObjectPool POOL = new ( + () => new Profile(), + actionOnRelease: profile => profile.Clear()); internal HashSet? blocked; internal List? interests; @@ -59,19 +63,19 @@ internal set } public bool HasConnectedWeb3 { get; internal set; } - public string Description { get; internal set; } + public string? Description { get; internal set; } public int TutorialStep { get; internal set; } - public string Email { get; internal set; } - public string Country { get; internal set; } - public string EmploymentStatus { get; internal set; } - public string Gender { get; internal set; } - public string Pronouns { get; internal set; } - public string RelationshipStatus { get; internal set; } - public string SexualOrientation { get; internal set; } - public string Language { get; internal set; } - public string Profession { get; internal set; } - public string RealName { get; internal set; } - public string Hobbies { get; internal set; } + public string? Email { get; internal set; } + public string? Country { get; internal set; } + public string? EmploymentStatus { get; internal set; } + public string? Gender { get; internal set; } + public string? Pronouns { get; internal set; } + public string? RelationshipStatus { get; internal set; } + public string? SexualOrientation { get; internal set; } + public string? Language { get; internal set; } + public string? Profession { get; internal set; } + public string? RealName { get; internal set; } + public string? Hobbies { get; internal set; } public DateTime? Birthdate { get; internal set; } public int Version { get; internal set; } public Avatar Avatar { get; internal set; } @@ -85,29 +89,50 @@ internal set public IReadOnlyCollection? Interests => interests; public IReadOnlyCollection? Links => links; - public Profile() { } + public static Profile Create() => + POOL.Get(); - public Profile(string userId, string name, Avatar avatar) + public static Profile Create(string userId, string name, Avatar avatar) + { + Profile profile = Create(); + profile.UserId = userId; + profile.Name = name; + profile.Avatar = avatar; + return profile; + } + + internal Profile() { } + + internal Profile(string userId, string name, Avatar avatar) { UserId = userId; Name = name; Avatar = avatar; } - private string GenerateDisplayName() + public void Clear() { - if (string.IsNullOrEmpty(Name)) return ""; - - var result = ""; - MatchCollection matches = VALID_NAME_CHARACTERS.Matches(Name); - - foreach (Match match in matches) - result += match.Value; - - if (HasClaimedName) - return result; - - return string.IsNullOrEmpty(UserId) || UserId.Length < 4 ? result : $"{result}#{UserId[^4..]}"; + this.blocked?.Clear(); + this.interests?.Clear(); + this.links?.Clear(); + this.Birthdate = null; + this.Avatar.Clear(); + this.Country = default(string?); + this.Email = default(string?); + this.Gender = default(string?); + this.Description = default(string?); + this.Hobbies = default(string?); + this.Language = default(string?); + this.Profession = default(string?); + this.Pronouns = default(string?); + this.Version = default(int); + this.HasClaimedName = default(bool); + this.EmploymentStatus = default(string?); + this.UserId = ""; + this.Name = ""; + this.TutorialStep = default(int); + this.HasConnectedWeb3 = default(bool); + this.IsDirty = false; } public static Profile NewRandomProfile(string? userId) => @@ -122,5 +147,24 @@ private string GenerateDisplayName() WearablesConstants.DefaultColors.GetRandomSkinColor() ) ); + + public void Dispose() => + POOL.Release(this); + + private string GenerateDisplayName() + { + if (string.IsNullOrEmpty(Name)) return ""; + + var result = ""; + MatchCollection matches = VALID_NAME_CHARACTERS.Matches(Name); + + foreach (Match match in matches) + result += match.Value; + + if (HasClaimedName) + return result; + + return string.IsNullOrEmpty(UserId) || UserId.Length < 4 ? result : $"{result}#{UserId[^4..]}"; + } } } diff --git a/Explorer/Assets/DCL/Profiles/DataModels/Avatar.cs b/Explorer/Assets/DCL/Profiles/DataModels/Avatar.cs index ee3aaddbe9..78da09e8a7 100644 --- a/Explorer/Assets/DCL/Profiles/DataModels/Avatar.cs +++ b/Explorer/Assets/DCL/Profiles/DataModels/Avatar.cs @@ -1,5 +1,6 @@ using CommunicationData.URLHelpers; using DCL.AvatarRendering.Wearables; +using System; using System.Collections.Generic; using UnityEngine; @@ -45,5 +46,22 @@ public Avatar(BodyShape bodyShape, HashSet wearables, Color eyesColor, Colo FaceSnapshotUrl = URLAddress.EMPTY; BodySnapshotUrl = URLAddress.EMPTY; } + + public void Clear() + { + this.wearables.Clear(); + this.forceRender.Clear(); + + for (var i = 0; i < this.emotes.Length; i++) + this.emotes[i] = ""; + + this.BodyShape = default(BodyShape); + this.EyesColor = default(Color); + this.HairColor = default(Color); + this.SkinColor = default(Color); + this.SkinColor = default(Color); + this.BodySnapshotUrl = default(URLAddress); + this.FaceSnapshotUrl = default(URLAddress); + } } } diff --git a/Explorer/Assets/DCL/Profiles/ProfileBuilder.cs b/Explorer/Assets/DCL/Profiles/ProfileBuilder.cs index a3a67f85df..eaae1412ba 100644 --- a/Explorer/Assets/DCL/Profiles/ProfileBuilder.cs +++ b/Explorer/Assets/DCL/Profiles/ProfileBuilder.cs @@ -111,7 +111,7 @@ public ProfileBuilder WithBodyShape(BodyShape bodyShape) this.bodyShape = bodyShape; return this; } - + public ProfileBuilder WithVersion(int version) { this.version = version; @@ -120,7 +120,7 @@ public ProfileBuilder WithVersion(int version) public Profile Build() { - var profile = new Profile(); + var profile = Profile.Create(); profile.RealName = realName ?? ""; profile.UserId = userId!; profile.Version = version; diff --git a/Explorer/Assets/DCL/Profiles/Repository/IProfileRepository.cs b/Explorer/Assets/DCL/Profiles/Repository/IProfileRepository.cs index f50b25d3e0..e5526e6b9d 100644 --- a/Explorer/Assets/DCL/Profiles/Repository/IProfileRepository.cs +++ b/Explorer/Assets/DCL/Profiles/Repository/IProfileRepository.cs @@ -24,7 +24,14 @@ public class Fake : IProfileRepository public UniTask SetAsync(Profile profile, CancellationToken ct) { - profiles[new Key(profile)] = profile; + var key = new Key(profile); + + if (profiles.TryGetValue(key, out Profile? existingProfile)) + if (existingProfile != profile) + existingProfile.Dispose(); + + profiles[key] = profile; + return UniTask.CompletedTask; } diff --git a/Explorer/Assets/DCL/Profiles/Repository/RealmProfileRepository.cs b/Explorer/Assets/DCL/Profiles/Repository/RealmProfileRepository.cs index d1f351d24d..2422bc5f78 100644 --- a/Explorer/Assets/DCL/Profiles/Repository/RealmProfileRepository.cs +++ b/Explorer/Assets/DCL/Profiles/Repository/RealmProfileRepository.cs @@ -88,7 +88,7 @@ private static GetProfileJsonRootDto NewProfileJsonRootDto(Profile profile, stri public async UniTask GetAsync(string id, int version, CancellationToken ct) { if (string.IsNullOrEmpty(id)) return null; - if (TryProfileFromCache(id, version, out var profileInCache)) return profileInCache; + if (TryProfileFromCache(id, version, out Profile? profileInCache)) return profileInCache; Assert.IsTrue(realm.Configured, "Can't get profile if the realm is not configured"); @@ -109,11 +109,11 @@ private static GetProfileJsonRootDto NewProfileJsonRootDto(Profile profile, stri // Reusing the profile in cache does not allow other systems to properly update. // It impacts on the object state and does not allow to make comparisons on change. // For example the multiplayer system, whenever a remote profile update comes in, - // it compares the version of the profile to check if it has changed - // By overriding the version here, the check always fails - // Profile profile = profileInCache ?? new Profile(); - Profile profile = new Profile(); + // it compares the version of the profile to check if it has changed. By overriding the version here, + // the check always fails. So its necessary to get a new instance each time + Profile profile = Profile.Create(); profileDto.CopyTo(profile); + profileCache.Set(id, profile); return profile; diff --git a/Explorer/Assets/Scripts/Global/Static/StaticSceneLauncher.cs b/Explorer/Assets/Scripts/Global/Static/StaticSceneLauncher.cs index a06b7e4f27..56a3db3a71 100644 --- a/Explorer/Assets/Scripts/Global/Static/StaticSceneLauncher.cs +++ b/Explorer/Assets/Scripts/Global/Static/StaticSceneLauncher.cs @@ -56,7 +56,7 @@ public async UniTask InitializationFlowAsync(CancellationToken ct) // Initialize .NET logging ASAP since it might be used by another systems // Otherwise we might get exceptions in different platforms DotNetLoggingPlugin.Initialize(); - + if (useStoredCredentials // avoid storing invalid credentials @@ -95,7 +95,7 @@ public async UniTask InitializationFlowAsync(CancellationToken ct) if (!string.IsNullOrEmpty(ownProfileJson)) { - var ownProfile = new Profile(); + var ownProfile = Profile.Create(); JsonUtility.FromJson(ownProfileJson).CopyTo(ownProfile); await memoryProfileRepository.SetAsync(ownProfile, ct); }