Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added profile pooling #993

Merged
merged 5 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
@@ -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;
Expand Down Expand Up @@ -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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private void PropagateProfileToScene(Profile profile, PlayerCRDTEntity playerCRD
private void SetSceneProfile(Profile profile, PlayerCRDTEntity playerCRDTEntity)
{
SceneEcsExecutor sceneEcsExecutor = playerCRDTEntity.SceneFacade.EcsExecutor;
sceneEcsExecutor.World.Add(playerCRDTEntity.SceneWorldEntity, new Profile(profile.UserId, profile.Name, profile.Avatar));
sceneEcsExecutor.World.Add(playerCRDTEntity.SceneWorldEntity, Profile.Create(profile.UserId, profile.Name, profile.Avatar));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public static class
{
LOD_0_Amount, LOD_1_Amount, LOD_2_Amount, LOD_3_Amount
};


// Textures cache
public static ProfilerCounterValue<int> TexturesAmount =
new (MEMORY, "Textures", ProfilerMarkerDataUnit.Count);
Expand All @@ -95,9 +95,11 @@ public static class
public static ProfilerCounterValue<int> AudioClipsReferenced =
new (MEMORY, "AudioClips Referenced", ProfilerMarkerDataUnit.Count);

// Profiles
public static ProfilerCounterValue<int> ProfileIntentionsInCache =
new (MEMORY, "Profile Intentions In Cache", ProfilerMarkerDataUnit.Count);
public static ProfilerCounterValue<int> ProfilesInCache =
new (MEMORY, "Profiles In Cache", ProfilerMarkerDataUnit.Count);

public static ProfilerCounterValue<int> ProfilesInPool =
new (MEMORY, "Profiles In Pool", ProfilerMarkerDataUnit.Count);

public static void CleanAllCounters()
{
Expand All @@ -123,15 +125,15 @@ 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;

AudioClipsAmount.Value = 0;
AudioClipsInCache.Value = 0;
AudioClipsReferenced.Value = 0;

ProfileIntentionsInCache.Value = 0;
ProfilesInCache.Value = 0;
}
}
}
9 changes: 8 additions & 1 deletion Explorer/Assets/DCL/Profiles/Cache/DefaultProfileCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
Expand All @@ -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
}
}
Expand Down
4 changes: 2 additions & 2 deletions Explorer/Assets/DCL/Profiles/Cache/ProfileIntentionCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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
}
}
Expand Down
100 changes: 72 additions & 28 deletions Explorer/Assets/DCL/Profiles/Components/Profile.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Profile> POOL = new (
() => new Profile(),
actionOnRelease: profile => profile.Clear());

internal HashSet<string>? blocked;
internal List<string>? interests;
Expand Down Expand Up @@ -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; }
Expand All @@ -85,29 +89,50 @@ internal set
public IReadOnlyCollection<string>? Interests => interests;
public IReadOnlyCollection<string>? 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) =>
Expand All @@ -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..]}";
}
}
}
18 changes: 18 additions & 0 deletions Explorer/Assets/DCL/Profiles/DataModels/Avatar.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CommunicationData.URLHelpers;
using DCL.AvatarRendering.Wearables;
using System;
using System.Collections.Generic;
using UnityEngine;

Expand Down Expand Up @@ -45,5 +46,22 @@ public Avatar(BodyShape bodyShape, HashSet<URN> 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);
}
}
}
4 changes: 2 additions & 2 deletions Explorer/Assets/DCL/Profiles/ProfileBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public ProfileBuilder WithBodyShape(BodyShape bodyShape)
this.bodyShape = bodyShape;
return this;
}

public ProfileBuilder WithVersion(int version)
{
this.version = version;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private static GetProfileJsonRootDto NewProfileJsonRootDto(Profile profile, stri
public async UniTask<Profile?> 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");

Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions Explorer/Assets/Scripts/Global/Static/StaticSceneLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -95,7 +95,7 @@ public async UniTask InitializationFlowAsync(CancellationToken ct)

if (!string.IsNullOrEmpty(ownProfileJson))
{
var ownProfile = new Profile();
var ownProfile = Profile.Create();
JsonUtility.FromJson<ProfileJsonDto>(ownProfileJson).CopyTo(ownProfile);
await memoryProfileRepository.SetAsync(ownProfile, ct);
}
Expand Down
Loading