where in the script below can I get the total number of expected sim objects to be loaded?

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;

using Unity.Jobs;
using Unity.Mathematics;
using Unity.Collections;
using UnityEngine;

using OpenMetaverse;
using static OpenMetaverse.Primitive;

#if useHDRP
using UnityEngine.Rendering.HighDefinition;
#endif

using CrystalFrost;
using static CrystalFrost.CFAssetManager;
using CrystalFrost.Assets;
using CrystalFrost.Assets.Textures;
using CrystalFrost.Assets.Mesh;
using CrystalFrost.Config;
using CrystalFrost.Extensions;
using CrystalFrost.Lib;
using CrystalFrost.Logging;
using CrystalFrost.UnityRendering;
using CrystalFrost.WorldState;
using CrystalFrost.ObjectPooling;

using Unity.VisualScripting;

#if USE_KWS
using KWS;
using static KWS.WaterSystem;
#endif

#if USE_FUNLY_SKY
using Funly.SkyStudio;
#endif

using static Bunny.HUDHelper;
using OMVVector2 = OpenMetaverse.Vector2;
using OMVVector3 = OpenMetaverse.Vector3;
using Vector2 = UnityEngine.Vector2;
using Vector3 = UnityEngine.Vector3;
using Mesh = UnityEngine.Mesh;
using Material = UnityEngine.Material;
using Quaternion = UnityEngine.Quaternion;

using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OpenMetaverse.ImportExport.Collada14;

using UnityEngine.Rendering;
using CrystalFrost.Assets.Animation;
using OpenMetaverse.Packets;
using UnityEngine.Animations;
using TMPro;

public class SimManager : MonoBehaviour
{
	GridClient client;
	public string simName;
	public string simOwner;
	public ulong thissim;
	public Simulator _thissim;
	public bool UseAnims = true;
	public bool UseAvatars = true;
	public OpenMetaverse.Vector3 SimPosition = new OpenMetaverse.Vector3();
	public TMP_InputField surlInput;

	public class SimulatorContainer
	{
		public Simulator sim;

		public SimTerrain terrain;

		public uint x;
		public uint y;

		public uint sizeX;
		public uint sizeY;

		public float age = 0f;

		public Transform transform { get { return terrain.Transform; } }

		public SimulatorContainer(Simulator sim, uint x, uint y)
		{
			this.sim = sim;
			this.sizeX = sim.SizeX;
			this.sizeY = sim.SizeY;
			this.x = x;
			this.y = y;

			terrain = new SimTerrainTiles(sim);
		}
	}

	public ConcurrentDictionary<ulong, SimulatorContainer> simulators = new ConcurrentDictionary<ulong, SimulatorContainer>();

	public void ForEachSimContainer(Action<SimulatorContainer> action)
	{
		foreach (var kvp in simulators)
		{
			action(kvp.Value);
		}
	}

	[SerializeField]
	GameObject cube;
	[SerializeField]
	GameObject blank;
	[SerializeField]
	Material opaqueMat;
	[SerializeField]
	Material opaqueFullBrightMat;
	[SerializeField]
	Material alphaMat;
	[SerializeField]
	Material alphaFullBrightMat;

	public Transform water;

	public Queue<PrimEventArgs> objectsToRez = new();

	Avatar avatar;
	public BoundsOctree<GameObject> boundsTree = new(15, new Vector3(127, 0, 127), 1, 1.25f);

	public MeshObjectManager meshObjectManager;

	public Transform[] hudAnchors;

	private IReadyTextureQueue _readyTextureQueue;
	private IDecodedMeshQueue _decodedMeshQueue;
	private IAssetManager _assetManager;
	private CodeConfig _config;
	private IStateManager _stateManager;
	private INewSimObjectQueue _newObjectQueue;
	private ISceneObjectsNeedingRenderersQueue _needRenderDataQueue;
	private IDecodedAnimationQueue _decodedAnimationQueue;

	private IUnityRenderManager _renderManager;
	private ILogger<SimManager> _log;
	private ILMVLogger _lmvLogger;

	private ConcurrentQueue<Tuple<Primitive, UUID>> _loadedAnimationRequest = new();

	public class AnimatedTextureData
	{
		TextureAnimation textureAnimation;
		int currentFrame;
		float lastFrameTime;
		float frameTime;
	}

	private void Awake()
	{
		//_log = Services.GetService<ILogger<SimManager>>();
		//_lmvLogger = Services.GetService<ILMVLogger>();
		_readyTextureQueue = Services.GetService<IReadyTextureQueue>();
		_decodedMeshQueue = Services.GetService<IDecodedMeshQueue>();
		_assetManager = Services.GetService<IAssetManager>();

		if (UseAnims)
		{
			_decodedAnimationQueue = Services.GetService<IDecodedAnimationQueue>();
		}
		_config = Services.GetService<IOptions<CodeConfig>>().Value;

		if (_config.UseNewObjectGraph)
		{
			_stateManager = Services.GetService<IStateManager>();
			_newObjectQueue = Services.GetService<INewSimObjectQueue>();
			_renderManager = Services.GetService<IUnityRenderManager>();
			_needRenderDataQueue = Services.GetService<ISceneObjectsNeedingRenderersQueue>();
		}

		ClientManager.assetManager.simManager = this;
		ClientManager.simManager = this;

		if (this.meshObjectManager == null)
		{
			this.meshObjectManager = FindFirstObjectByType<MeshObjectManager>();
		}
	}

	void Start()
	{
		client = ClientManager.client;

		avatar = gameObject.GetComponent<Avatar>();

		ClientManager.assetManager.materialContainer.Add(UUID.Zero, new MaterialContainer(UUID.Zero, Texture2D.whiteTexture, 3));

		if (UseAvatars)
		{
			client.Objects.AvatarUpdate += new EventHandler<AvatarUpdateEventArgs>(AvatarUpdateHandler);
		}

		//client.Objects.KillObject += new EventHandler<KillObjectEventArgs>(KillObjectEventHandler);
		//client.Objects.KillObjects += new EventHandler<KillObjectsEventArgs>(KillObjectsEventHandler);
		client.Objects.ObjectDataBlockUpdate += new EventHandler<ObjectDataBlockUpdateEventArgs>(ObjectDataBlockUpdateEvent);
		client.Objects.ObjectProperties += new EventHandler<ObjectPropertiesEventArgs>(ObjectPropertiesEventHandler);
		client.Objects.ObjectPropertiesUpdated += new EventHandler<ObjectPropertiesUpdatedEventArgs>(ObjectPropertiesUpdateHandler);
		client.Objects.ObjectUpdate += new EventHandler<PrimEventArgs>(Objects_ObjectUpdate);
		client.Objects.PhysicsProperties += new EventHandler<PhysicsPropertiesEventArgs>(PhysicsPropertiesEvent);
		client.Objects.TerseObjectUpdate += new EventHandler<TerseObjectUpdateEventArgs>(Objects_TerseObjectUpdate);

		if (UseAnims)
		{
			client.Avatars.AvatarAnimation += new EventHandler<AvatarAnimationEventArgs>(AvatarAnimationHandler);
		}

		client.Terrain.LandPatchReceived += new EventHandler<LandPatchReceivedEventArgs>(TerrainEventHandler);

		if (UseAvatars)
		{
			client.Avatars.UUIDNameReply += new EventHandler<UUIDNameReplyEventArgs>(AvatarNamesEventHandler);
		}

		StartCoroutine(UpdateCamera());
		StartCoroutine(CleanUnusedAssets());
		objectProximityHandler = new ObjectProximityHandler(objectUpdates, client);
		//objectProximityHandler.UpdateCameraProperties(Camera.main);
	}

	public void SimConnected(Simulator pSim, uint pX, uint pY, uint pRels, uint pRely)
	{
		var sim = new SimulatorContainer(pSim, pX, pY);
		simulators.TryAdd(pSim.Handle, sim);
	}

	IEnumerator CleanUnusedAssets()
	{
		while (true)
		{
			Resources.UnloadUnusedAssets();
			yield return new WaitForSecondsRealtime(10f);
		}
	}

	readonly ConcurrentQueue<AvatarUpdateEventArgs> avatarUpdates = new();

	public void AvatarUpdateHandler(object sender, AvatarUpdateEventArgs e)
	{
		this.objectProximityHandler.AddPrimPositionID(e.Avatar.LocalID, e.Avatar.ParentID, e.Avatar.Position);
		avatarUpdates.Enqueue(e);
	}

	void AvatarUpdates()
	{
		if (UseAvatars)
		{
			Primitive prim;
			while (avatarUpdates.Count > 0)
			{
				avatarUpdates.TryDequeue(out AvatarUpdateEventArgs e);
				this.meshObjectManager.OnAvatarUpdate(e);
				//if (e.Simulator.Handle != thissim) continue;
				prim = e.Avatar;
				if (e.IsNew)
				{
					GameObject bgo = Instantiate<GameObject>(blank);

					bgo.name = $"avatar {prim.LocalID} heirarchy holder";

					GameObject go = Instantiate<GameObject>(cube);
					go.name = $"avatar {prim.LocalID}";


					GameObject meshHolder = Instantiate<GameObject>(blank);
					go.transform.parent = bgo.transform;
					go.transform.localPosition = Vector3.zero;
					meshHolder.transform.parent = bgo.transform;

					ScenePrimData sPD = new(go, prim);
					if (scenePrims.TryAdd(prim.LocalID, sPD))
					{
						go.transform.localScale = prim.Scale.ToUnity();
						scenePrims[prim.LocalID].uuid = prim.ID;
						scenePrims[prim.LocalID].ConstructionHash = prim.PrimData.GetHashCode();
						if (prim.ParentID != 0)
						{
							if (!scenePrims.ContainsKey(prim.ParentID))
							{
								orphanedPrims.TryAdd(prim.ParentID, new List<Primitive>());
								orphanedPrims[prim.ParentID].Add(prim);
								go.transform.position = new Vector3(5000f, 5000f, 5000f);
							}
							else
							{
								bgo.transform.parent = scenePrims[prim.ParentID].obj.transform.parent;
							}
						}
						bgo.transform.SetLocalPositionAndRotation(
							prim.Position.ToUnity(),
							prim.Rotation.ToUnity());

						if (orphanedPrims.ContainsKey(prim.ParentID))
						{
							foreach (Primitive p in orphanedPrims[prim.ParentID])
							{
								if (p.ParentID == prim.ParentID && scenePrims.ContainsKey(prim.ParentID))
								{
									scenePrims[p.LocalID].obj.transform.parent.parent = scenePrims[prim.ParentID].obj.transform.parent;
									bgo.transform.SetLocalPositionAndRotation(
										prim.Position.ToUnity(),
										p.Rotation.ToUnity());

									if (IsHUD(scenePrims[p.LocalID].prim))
									{
										bgo.SetLayerRecursively(8);
									}

									HandleAttachment(bgo, prim);

									CleanOrphanedPrims(prim);
								}
							}
						}

						scenePrims[prim.LocalID].Render();

						if (ClientManager.isOpenSim)
						{
							scenePrims[prim.LocalID].DoAvatarStuff();
						}
					}
					else
					{
						if (scenePrims.ContainsKey(prim.LocalID))
						{
							Debug.LogWarning($"Tried to create new prim, but localID:{prim.LocalID} already exists in scene.");
						}
						else
						{
							Debug.LogWarning($"Error adding prim to dictionary.");
						}

						Destroy(bgo);
					}

					if (e.Avatar.LocalID == client.Self.LocalID)
					{
						ClientManager.currentOutfitFolder = new CurrentOutfitFolder();
						ScenePrimData spd = ClientManager.simManager.scenePrims[ClientManager.client.Self.LocalID];
						avatar.myAvatar.parent = spd.meshHolder.transform.root;
						avatar.myAvatar.SetLocalPositionAndRotation(Vector3.zero, Quaternion.identity);
						avatar.rotation = spd.prim.Rotation.ToUnity();
					}
				}
				else
				{
					if (scenePrims.ContainsKey(e.Avatar.LocalID))
					{
						scenePrimsContainsAvatar = true;
						scenePrims[e.Avatar.LocalID].prim = e.Avatar;
						scenePrims[e.Avatar.LocalID].velocity = e.Avatar.Velocity.ToVector3();
						scenePrims[e.Avatar.LocalID].omega = e.Avatar.AngularVelocity.ToVector3();
						//scenePrims[e.Avatar.LocalID].
					}
				}

			}
		}
	}

	void HandleAttachment(GameObject bgo, Primitive prim)
	{
		if (prim.IsAttachment)
		{
			Transform _av = bgo.transform.root.Find("Avatar");
			if (_av != null)
			{
				Transform _attachpoint = FindChildByNameRecursive(_av, prim.PrimData.AttachmentPoint.ToString());
				if (_attachpoint != null)
				{
					AddPositionAndRotationConstraints(bgo, _attachpoint, prim.Position.ToVector3(), prim.Rotation.ToUnity().eulerAngles);
				}
			}
		}
	}
	public bool scenePrimsContainsAvatar = false;

	readonly ConcurrentQueue<ObjectPropertiesUpdatedEventArgs> objectPropertiesUpdateEvents = new();
	public void ObjectPropertiesUpdateHandler(object sender, ObjectPropertiesUpdatedEventArgs e)
	{
		this.objectProximityHandler.AddPrimPositionID(e.Prim);
		objectPropertiesUpdateEvents.Enqueue(e);
	}

	public void PhysicsPropertiesEvent(object sender, PhysicsPropertiesEventArgs e)
	{
		return;
	}

	public class UUIDNameReplyEvent
	{
		public UUID uuid;
		public string name;
	}
	public ConcurrentQueue<UUIDNameReplyEvent> nameReplyEvents = new();
	void AvatarNamesEventHandler(object sender, UUIDNameReplyEventArgs e)
	{
		foreach (KeyValuePair<UUID, string> kvp in e.Names)
		{
			if (scenePrimIndexUUID.ContainsKey(kvp.Key))
			{
				if (scenePrims.TryGetValue(scenePrimIndexUUID[kvp.Key], out ScenePrimData sPrim))
				{
					//Debug.Log($"NAME RECEIVED: {kvp.Value}");
					nameReplyEvents.Enqueue(new UUIDNameReplyEvent { uuid = kvp.Key, name = kvp.Value });
					//UnityMainThreadDispatcher.Instance().Enqueue(() => sPrim.SetName(kvp.Value));

				}
			}
			else
			{
				ClientManager.client.Avatars.RequestAvatarName(kvp.Key);
			}
		}
	}

	void NameReplyEvents()
	{
		Queue<UUIDNameReplyEvent> newQueue = new();
		while (nameReplyEvents.Count > 0)
		{
			if (nameReplyEvents.TryDequeue(out UUIDNameReplyEvent nameReplyEvent))
			{
				if (scenePrimIndexUUID.ContainsKey(nameReplyEvent.uuid))
				{
					if (scenePrims.ContainsKey(scenePrimIndexUUID[nameReplyEvent.uuid]))
					{
						scenePrims[scenePrimIndexUUID[nameReplyEvent.uuid]].SetName(nameReplyEvent.name);
						ClientManager.chat.SetKeyToName(nameReplyEvent.uuid, nameReplyEvent.name);
						ClientManager.chatWindow.contactsList.UpdateContact(nameReplyEvent.name, nameReplyEvent.uuid);
					}
					else
					{
						newQueue.Enqueue(nameReplyEvent);

					}
				}
				else
				{
					newQueue.Enqueue(nameReplyEvent);
				}
			}
		}

		while (newQueue.Count > 0)
		{
			nameReplyEvents.Enqueue(newQueue.Dequeue());
		}
	}

	void ObjectPropertyEvents()
	{
		while (objectPropertiesEvents.Count > 0)
		{
			if (objectPropertiesEvents.TryDequeue(out ObjectPropertiesEventArgs objectPropertiesEvent))
			{
				if (scenePrims.ContainsKey(scenePrimIndexUUID[objectPropertiesEvent.Properties.ObjectID]))
				{
					scenePrims[scenePrimIndexUUID[objectPropertiesEvent.Properties.ObjectID]].SetProperties(objectPropertiesEvent.Properties);
				}
			}
		}

		while (objectPropertiesUpdateEvents.Count > 0)
		{
			if (objectPropertiesUpdateEvents.TryDequeue(out ObjectPropertiesUpdatedEventArgs objectPropertiesUpdateEvent))
			{
				if (scenePrims.ContainsKey(scenePrimIndexUUID[objectPropertiesUpdateEvent.Properties.ObjectID]))
				{
					scenePrims[scenePrimIndexUUID[objectPropertiesUpdateEvent.Properties.ObjectID]].SetProperties(objectPropertiesUpdateEvent.Properties);
				}
			}
		}
	}

	readonly ConcurrentQueue<ObjectPropertiesEventArgs> objectPropertiesEvents = new();
	void ObjectPropertiesEventHandler(object sender, ObjectPropertiesEventArgs e)
	{
		if (scenePrims.TryGetValue(scenePrimIndexUUID[e.Properties.ObjectID], out ScenePrimData sPrim))
		{
			objectPropertiesEvents.Enqueue(e);
		}
	}

	public ConcurrentQueue<ObjectDataBlockUpdateEventArgs> objectDataBlockUpdates = new();
	void ObjectDataBlockUpdateEvent(object sender, ObjectDataBlockUpdateEventArgs e)
	{
		objectDataBlockUpdates.Enqueue(e);
	}


	public void AvatarAnimationHandler(object sender, AvatarAnimationEventArgs e)
	{
		var map = this.meshObjectManager.avatarPrimitiveMap;
		if (map.TryGetValue(e.AvatarID, out Primitive prim))
		{
			foreach (OpenMetaverse.Animation anim in e.Animations)
			{
				if (SkeletonManager.HasClip(anim.AnimationID))
				{
					_loadedAnimationRequest.Enqueue(new Tuple<Primitive, UUID>(prim, anim.AnimationID));
				}
				else
				{
					ClientManager.assetManager.RequestAnimation(prim, anim.AnimationID);
				}
			}
		}
	}

	public class KillObjectEventData
	{
		public object sender;
		public KillObjectEventArgs e;

		public KillObjectEventData(object sender, KillObjectEventArgs e)
		{
			this.sender = sender;
			this.e = e;
		}
	}

	readonly ConcurrentQueue<KillObjectEventData> killObjectQueue = new();

	void KillObjectEventHandler(object sender, KillObjectEventArgs e)
	{
		killObjectQueue.Enqueue(new KillObjectEventData(sender, e));
	}

	void KillObjectsEventHandler(object sender, KillObjectsEventArgs e)
	{
		foreach (uint id in e.ObjectLocalIDs)
		{
			killObjectQueue.Enqueue(new KillObjectEventData(sender, new KillObjectEventArgs(e.Simulator, id)));
		}
	}

	void KillObjects()
	{
		Material dissolver = Resources.Load<Material>("Dissolver");
		Material original;
		Material dissolvemat;
		List<MeshRequestData> newmrd = new();

		while (killObjectQueue.Count > 0)
		{
			if (killObjectQueue.TryDequeue(out KillObjectEventData data))
			{
				if (scenePrims.ContainsKey(data.e.ObjectLocalID))
				{
					if (orphanedPrims.ContainsKey(data.e.ObjectLocalID))
					{
						orphanedPrims.Remove(data.e.ObjectLocalID);
					}
					if (scenePrims[data.e.ObjectLocalID].obj == null) continue;
					if (scenePrims[data.e.ObjectLocalID].prim.RegionHandle != data.e.Simulator.Handle)
					{
						continue;
					}
					GameObject go = scenePrims[data.e.ObjectLocalID].obj;
					Renderer[] rs = go.GetComponentsInChildren<Renderer>();
					foreach (Renderer r in rs)
					{
						original = r.material;
						dissolvemat = Instantiate<Material>(dissolver);
						dissolvemat.SetTexture("_MainTex", original.GetTexture("_MainTex"));
						r.material = dissolvemat;
						r.gameObject.AddComponent<DissolveOut>();
						r.GetComponent<MeshCollider>().enabled = false;
					}
					newmrd = new List<MeshRequestData>();
					foreach (MeshRequestData mrd in meshRequests)
					{
						if (mrd.meshHolder.gameObject != null) newmrd.Add(mrd);
					}
					meshRequests = newmrd;

					StartCoroutine(DissolveObject(scenePrims[data.e.ObjectLocalID].obj));
					if (scenePrims.ContainsKey(data.e.ObjectLocalID)) scenePrims.Remove(data.e.ObjectLocalID, out ScenePrimData spd);
				}
			}
		}
	}

	IEnumerator DissolveObject(GameObject go)
	{
		yield return new WaitForSeconds(10f);
		if (go != null)
			if (!go.IsDestroyed())
				Destroy(go.transform.parent.gameObject);


	}

	public Transform sun;

#if USE_FUNLY_SKY
	public TimeOfDayController timeOfDayController;
#endif

	public void Update()
	{
		KillObjects();
		ScanForFaces();
		if (!ClientManager.active) return;
		float t = Time.deltaTime;

		ServiceReadyMeshQueue();
		ServiceReadyAnimationQueue();
		AllTextureRequests();
		if (!_config.UseNewObjectGraph)
		{
			AllObjectsUpdate(t);
			ObjectBlockUpdates(t);
			TerseUpdates(t);
		}
		NameReplyEvents();
		ObjectPropertyEvents();
		if (UseAvatars)
		{
			AvatarUpdates();
		}

		float time = Time.deltaTime;
		ForEachSimContainer(simContainer =>
		{
			simContainer.terrain.TerrainUpdate(simContainer);
			simContainer.age += time;
		});

		var sunPhase = ClientManager.client?.Grid?.SunPhase ?? 0f;
#if USE_FUNLY_SKY
        timeOfDayController.skyTime = Mathf.Repeat((sunPhase * 0.15915494309189533576888376337251f) + 0.25f, 1f);
#else

#endif
		if (_config.UseNewObjectGraph)
		{
			ServiceNewObjectQueue();
			ServiceSceneObjectsNeedingRenderersQueue();
		}
	}

	const float DEG_TO_RAD = 0.017453292519943295769236907684886f;
	const float RAD_TO_DEG = 57.295779513082320876798154814105f;

	public List<uint> movingObjects = new();

	void TranslateObjects(float t)
	{
		foreach (uint localID in movingObjects)
		{
			try
			{
				if (scenePrims[localID].velocity != Vector3.zero || scenePrims[localID].omega != Vector3.zero)
				{
					//scenePrims[localID].TranslateObject(t);
				}
			}
			catch
			{

			}
		}
	}

	private IEnumerator UpdateCamera()
	{
		while (true)
		{
			if (client.Settings.SEND_AGENT_UPDATES && ClientManager.active)
			{
				// Update camera position and orientation to the client
				Vector3 cameraPosition = Camera.main.transform.position;
				Vector3 lookAtPoint = cameraPosition + (Camera.main.transform.forward * 7.5f);

				client.Self.Movement.Camera.LookAt(
					new OMVVector3(cameraPosition.x, cameraPosition.z, cameraPosition.y),
					new OMVVector3(lookAtPoint.x, lookAtPoint.z, lookAtPoint.y)
				);

				// Update camera-specific properties
				//objectProximityHandler.UpdateCameraProperties(Camera.main);

				client.Self.Movement.Camera.Far = 8000f;

				// Update screen dimensions
				client.Self.SetHeightWidth((ushort)Screen.width, (ushort)Screen.height);

				// Update camera Field Of View (FOV)
				client.Self.Movement.SetFOVVerticalAngle(Camera.main.fieldOfView * DEG_TO_RAD);

				// Send camera movement updates to the client
				client.Self.Movement.SendUpdate();
			}

			// Wait for the next update
			yield return new WaitForSeconds(_config.updateCameraInterval);
		}
	}

	private static readonly WaitForSecondsRealtime Wait100ms = new(0.1f);

	IEnumerator TextureRequests()
	{
		while (true)
		{
			var totalAllowedInOneFrame = 10;
			while (_readyTextureQueue.TryDequeue(out var textureItem) && textureItem is not null && totalAllowedInOneFrame-- > 0)
			{
				ClientManager.assetManager.MainThreadTextureReinitialize(textureItem.Data, textureItem.UUID, textureItem.Width, textureItem.Height, textureItem.Components);
			}
			yield return Wait100ms;
		}
	}

	void AllTextureRequests()
	{
		var totalAllowedInOneFrame = 10;
		while (_readyTextureQueue.TryDequeue(out var textureItem) && textureItem is not null && totalAllowedInOneFrame-- > 0)
		{
			ClientManager.assetManager.MainThreadTextureReinitialize(textureItem.Data, textureItem.UUID, textureItem.Width, textureItem.Height, textureItem.Components);
		}
	}

	void ServiceReadyAnimationQueue()
	{
		if (UseAnims)
		{
			ServiceQueueRepeatedly(_decodedAnimationQueue, this.meshObjectManager.SetupAnimation);

			while (_loadedAnimationRequest.TryDequeue(out var animationItem) && animationItem is not null)
			{
				this.meshObjectManager.SetupAnimation(animationItem.Item2, animationItem.Item1);
			}
		}
	}

	void ServiceReadyMeshQueue()
	{
		ServiceQueueRepeatedly(_decodedMeshQueue, this.meshObjectManager.SetupMeshObject);
	}

	private void ServiceQueueRepeatedly<TQueueItem>(IConcurrentQueue<TQueueItem> queue, Action<TQueueItem> action)
	{
		uint limit = _config.LimitQueueItemsPerUpdateTo;
		uint count = 0;
		do
		{
			if (!queue.TryDequeue(out var item) || item is null) return;
			action(item);
		} while (count++ < limit);
	}

	bool DoOneDecodedMesh()
	{
		if (!_decodedMeshQueue.TryDequeue(out var item) || item is null) return false;
		// DoDecodedMesh(item);
		this.meshObjectManager.SetupMeshObject(item);
		return true;
	}

	private ConcurrentQueue<ScenePrimData> unTexturedPrims = new();

	void AllObjectsUpdate(float t)
	{
		while (objectUpdates.TryDequeue(out var update) && update is not null)
		{
			if (update.IsNew && !scenePrims.ContainsKey(update.Prim.LocalID))
			{
				NewObject(update.Prim);
				continue;
			}
			else if (scenePrims.ContainsKey(update.Prim.LocalID))
			{
				scenePrims[update.Prim.LocalID].UpdateObject(update, t);
			}
		}

	}

	void ObjectBlockUpdates(float t)
	{
		while (objectDataBlockUpdates.TryDequeue(out var update) && update is not null)
		{
			if (scenePrims.ContainsKey(update.Prim.LocalID))
			{
				scenePrims[update.Prim.LocalID].ObjectBlockUpdate(update, t);
			}
		}
	}

	public void TerrainEventHandler(object sender, LandPatchReceivedEventArgs e)
	{
		simulators[e.Simulator.Handle].terrain.TerrainPatchUpdate(e);
	}


	public ConcurrentQueue<TerseObjectUpdateEventArgs> terseUpdates = new();

	void Objects_TerseObjectUpdate(object sender, TerseObjectUpdateEventArgs e)
	{
		this.objectProximityHandler.AddPrimPositionID(e.Prim);
		terseUpdates.Enqueue(e);
	}

	void TerseUpdates(float t)
	{
		while (terseUpdates.TryDequeue(out var update) && update is not null)
		{
			if (scenePrims.ContainsKey(update.Update.LocalID))
			{
				scenePrims[update.Update.LocalID].TerseUpdate(update, t);
			}
		}
	}

	public ConcurrentQueue<PrimEventArgs> objectUpdates = new();
	public ObjectProximityHandler objectProximityHandler;
	void Objects_ObjectUpdate(object sender, PrimEventArgs _event)
	{
		objectProximityHandler.AddObject(_event);
	}

	public ConcurrentDictionary<UUID, uint> scenePrimIndexUUID = new();

	public ConcurrentDictionary<uint, ScenePrimData> scenePrims = new();
	public Dictionary<uint, List<Primitive>> orphanedPrims = new();

	public class MeshRequestData
	{
		public uint localID;
		public UUID sculptTextureUUID;
		public GameObject meshHolder;
		public MeshRequestData(uint lid, UUID uuid, GameObject mh)
		{
			localID = lid;
			sculptTextureUUID = uuid;
			meshHolder = mh;
		}
	}

	public List<MeshRequestData> meshRequests = new();

	public static void PreTextureFace(Primitive prim, int j, Renderer rendr)
	{
		TextureEntryFace tef = prim.Textures.GetFace((uint)j);
		UUID uuid = tef.TextureID;
		Color color = tef.RGBA.ToUnity();

		if (!ClientManager.assetManager.materialContainer.ContainsKey(uuid))
		{
			ClientManager.assetManager.materialContainer.Add(uuid, new MaterialContainer(uuid, Texture2D.Instantiate(Texture2D.whiteTexture), 3));
			if (!ClientManager.assetManager.materials.ContainsKey(uuid))
			{
				ClientManager.assetManager.materials.Add(uuid, new List<Renderer>());
			}
			ClientManager.assetManager.materials[uuid].Add(rendr);
		}

		DissolveIn dis = rendr.gameObject.AddComponent<DissolveIn>();
		dis.texture = ClientManager.assetManager.materialContainer[uuid].texture;
		dis.color = color;
		dis.newMat = ClientManager.assetManager.materialContainer[uuid].GetMaterial(color, tef.Glow, tef.Fullbright);
	}

	public void TextureFace(Primitive prim, int subMeshIndex, Renderer rendr)
	{
		if (prim.Textures == null)
		{

		}

		TextureEntryFace textureEntryFace = prim.Textures.GetFace((uint)subMeshIndex);
		textureEntryFace.GetOSD(subMeshIndex);

		Color color = textureEntryFace.RGBA.ToUnity();

		Material newMaterial = ClientManager.assetManager.RequestTexture(textureEntryFace.TextureID, rendr, subMeshIndex, color, textureEntryFace.Glow, textureEntryFace.Fullbright);

		if (textureEntryFace.Fullbright) rendr.gameObject.layer = 7;
		else rendr.gameObject.layer = 0;

		if (scenePrims.ContainsKey(prim.LocalID))
		{
			if (IsHUD(scenePrims[prim.LocalID].prim))
			{
				rendr.gameObject.layer = 8;
			}
			if (scenePrims.ContainsKey(scenePrims[prim.LocalID].prim.ParentID))
			{
				if (IsHUD(scenePrims[scenePrims[prim.LocalID].prim.ParentID].prim))
				{
					rendr.gameObject.layer = 8;
				}
			}
		}
	}

	void ScanForFaces()
	{
		if (!ClientManager.active && Time.frameCount % 20 != 0) return;

		var unProcessed = new List<ScenePrimData>();
		while (unTexturedPrims.TryDequeue(out var kvp))
		{
			if (kvp.obj == null) continue;
			var pis = kvp.obj.transform.GetChild(0).GetComponentsInChildren<PrimInfo>();

			if (pis.Length == 0)
			{
				unProcessed.Add(kvp); continue;
			}

			foreach (PrimInfo _pi in pis)
			{
				kvp.obj.name += " " + _pi.face;

				if (!_pi.isTextured && _pi.face > -1)
				{
					//validate++;
					_pi.isTextured = true;
					try
					{
						TextureFace(_pi.prim, _pi.face, _pi.GetComponent<Renderer>());
					}
					catch (Exception e)
					{
						Debug.LogWarning(e);
					}
				}
			}

		}

		foreach (var pi in unProcessed)
		{
			unTexturedPrims.Enqueue(pi);
		}
	}

	private void ServiceSceneObjectsNeedingRenderersQueue()
	{
		ServiceQueueRepeatedly(_needRenderDataQueue, DoSceneObjectNeedingRenderer);
	}

	private void DoSceneObjectNeedingRenderer(SceneObject obj)
	{
		_log.Render_SceneObjectNeedsRenderers(obj.LocalID);

		switch (obj.SimObject.PrimType)
		{
			case PrimType.Unknown:
				break;
			case PrimType.Sculpt:
				break;
			case PrimType.Mesh:
				break;
			case PrimType.Box:
				break;
			case PrimType.Cylinder:
				break;
			case PrimType.Sphere:
				break;
			case PrimType.Prism:
				break;
			case PrimType.Torus:
				break;
			case PrimType.Tube:
				break;
			case PrimType.Ring:
				break;
			default:
				break;
		}
	}

	private void ServiceNeedRenderDataQueue()
	{
		ServiceQueueRepeatedly(_needRenderDataQueue, SetupRenderDataForObject);
	}

	private void SetupRenderDataForObject(SceneObject sceneObject)
	{
		RemoveRenderers(sceneObject);

		var simObject = sceneObject.SimObject;

		if (!simObject.IsAttachment &&
			simObject.SimVelocity == Vector3.zero &&
			simObject.SimAngularVelocity == Vector3.zero)
		{
			ClientManager.client.Objects.SelectObject(
				ClientManager.client.Network.CurrentSim,
				simObject.LocalID);
		}

		switch (simObject.PrimType)
		{
			case PrimType.Sculpt:
				SetupSculptRenderer(sceneObject);
				break;
			case PrimType.Unknown:
				break;
			case PrimType.Mesh:
				SetupMeshRenderer(sceneObject);
				break;
			case PrimType.Box:
				SetupClassicPrimRenderer(sceneObject);
				break;
			case PrimType.Cylinder:
				SetupClassicPrimRenderer(sceneObject);
				break;
			case PrimType.Torus:
				SetupClassicPrimRenderer(sceneObject);
				break;
			case PrimType.Tube:
				SetupClassicPrimRenderer(sceneObject);
				break;
			case PrimType.Sphere:
				SetupClassicPrimRenderer(sceneObject);
				break;
			case PrimType.Prism:
				SetupClassicPrimRenderer(sceneObject);
				break;
			case PrimType.Ring:
				SetupClassicPrimRenderer(sceneObject);
				break;
			default:
				SetupClassicPrimRenderer(sceneObject);
				break;
		}
	}

	private void SetupParticles(SceneObject sceneObject)
	{
		sceneObject.GameObject.isStatic = true;
	}

	private void SetupClassicPrimRenderer(SceneObject sceneObject)
	{
		sceneObject.GameObject.isStatic = true;
	}

	private void SetupMeshRenderer(SceneObject sceneObject)
	{
		sceneObject.GameObject.isStatic = true;
	}

	private void SetupSculptRenderer(SceneObject sceneObject)
	{
		sceneObject.GameObject.isStatic = true;
	}

	private void RemoveRenderers(SceneObject sceneObject)
	{
		if (sceneObject.Renderers == null) return; // sanity check

		foreach (var r in sceneObject.Renderers)
		{
			if (r != null)
			{
				r.enabled = true;
			}
		}

#if USE_KWS
		if (sceneObject.IsWater)
		{
			sceneObject.IsWater = false;
			SimManager.Destroy(sceneObject.WaterSystem);
			SimManager.Destroy(sceneObject.WaterBox);
			sceneObject.WaterSystem = null;
			sceneObject.WaterBox = null;
		}
#endif
	}

	private void ServiceNewObjectQueue()
	{
		ServiceQueueRepeatedly(_newObjectQueue, DoNewSceneObject);
	}

	private void DoNewSceneObject(SimObject simObject)
	{
		System.Threading.Thread.Sleep(150);
		if (_config.LimitToCurrentRegion) return;

		var localID = simObject.LocalID;

		var heirarchyHolder = Instantiate(blank);
		heirarchyHolder.name = $"Heirarch Holder ({localID})";

		var gameObject = Instantiate(cube);
		gameObject.name = $"Object ({localID})";
		gameObject.transform.parent = heirarchyHolder.transform;
		gameObject.transform.localPosition = Vector3.zero;

		var meshRenderer = gameObject.GetComponent<MeshRenderer>();
		meshRenderer.enabled = false;

		var meshHolder = Instantiate(blank);
		meshHolder.name = $"Mesh Holder({localID})";
		meshHolder.transform.parent = heirarchyHolder.transform;

		var sceneObject = new SceneObject()
		{
			LocalID = simObject.LocalID,
			HeirachyHolder = heirarchyHolder,
			GameObject = gameObject,
			MeshHolder = meshHolder,
			SimObject = simObject,
		};

		if (!_renderManager.SceneObjects.Add(sceneObject))
		{
			Destroy(heirarchyHolder);
		}

		gameObject.transform.localScale = simObject.Scale;

		if (simObject.ParentID != 0)
		{
			var sceneParent = _renderManager.SceneObjects.Get(simObject.ParentID);
			if (sceneParent is null)
			{
				_log.Render_NewOrphanObject(simObject.LocalID, simObject.ParentID);
				_newObjectQueue.Enqueue(simObject);
			}

			if (simObject.IsHud())
			{
				int anchorIndex = (int)simObject.AttachmentPoint - 31;
				heirarchyHolder.transform.parent = hudAnchors[anchorIndex];
				heirarchyHolder.SetLayerRecursively(8);
			}
			else
			{
				heirarchyHolder.transform.parent = sceneParent.GameObject.transform.parent;
			}
		}

		heirarchyHolder.transform.SetLocalPositionAndRotation(
			simObject.SimPosition,
			simObject.SimRotation);

		_needRenderDataQueue.Enqueue(sceneObject);
	}

	void NewObject(Primitive prim)
	{
		DebugStatsManager.AddStateUpdate(DebugStatsType.NewPrim, prim.Type.ToString());

		GameObject bgo = Instantiate<GameObject>(blank);
		bgo.transform.parent = simulators[prim.RegionHandle].transform;
		bgo.name = $"object {prim.LocalID} hierarchy holder";

		GameObject go = Instantiate<GameObject>(cube);
		go.name = $"object {prim.LocalID}";

		GameObject meshHolder = Instantiate<GameObject>(blank);
		go.transform.parent = bgo.transform;
		go.transform.localPosition = Vector3.zero;

		// Set the GameObjects to static
		bgo.isStatic = true;
		go.isStatic = true;
		meshHolder.isStatic = true;

		meshHolder.transform.parent = bgo.transform;

		ScenePrimData sPD = new ScenePrimData(go, prim);

		if (prim.Type == PrimType.Sculpt || prim.Type == PrimType.Mesh)
			unTexturedPrims.Enqueue(sPD);

		if (scenePrims.TryAdd(prim.LocalID, sPD))
		{
			go.transform.localScale = prim.Scale.ToUnity();
			scenePrims[prim.LocalID].uuid = prim.ID;
			scenePrims[prim.LocalID].ConstructionHash = prim.PrimData.GetHashCode();

			if (prim.ParentID != 0)
			{
				if (!scenePrims.ContainsKey(prim.ParentID))
				{
					orphanedPrims.TryAdd(prim.ParentID, new List<Primitive>());
					orphanedPrims[prim.ParentID].Add(prim);
					go.transform.position = new Vector3(5000f, 5000f, 5000f);
				}
				else
				{
					if (!IsHUD(prim))
					{
						bgo.transform.parent = scenePrims[prim.ParentID].obj.transform.parent;
						if (IsHUD(scenePrims[prim.ParentID].prim))
						{
							scenePrims[prim.LocalID].parentIsHUD = true;
						}
					}
					else
					{
						bgo.transform.parent = hudAnchors[(int)prim.PrimData.AttachmentPoint - 31];
						bgo.SetLayerRecursively(8);
					}
				}
			}

			if (IsHUD(prim))
			{
				bgo.transform.SetLocalPositionAndRotation(
					GetHUDPosition(prim),
					prim.Rotation.ToUnity());
			}
			else
			{
				HandleAttachment(bgo, prim);
				bgo.transform.SetLocalPositionAndRotation(
					prim.Position.ToUnity(),
					prim.Rotation.ToUnity());
			}

			if (orphanedPrims.ContainsKey(prim.ParentID))
			{
				foreach (Primitive p in orphanedPrims[prim.ParentID])
				{
					if (p.ParentID == prim.ParentID && scenePrims.ContainsKey(prim.ParentID))
					{
						scenePrims[p.LocalID].obj.transform.parent.parent = scenePrims[prim.ParentID].obj.transform.parent;
						bgo.transform.SetLocalPositionAndRotation(
							prim.Position.ToUnity(),
							prim.Rotation.ToUnity());

						if (IsHUD(scenePrims[prim.ParentID].prim))
						{
							scenePrims[p.LocalID].parentIsHUD = true;
						}
					}
				}
			}

			scenePrims[prim.LocalID].Render();
		}
		else
		{
			if (scenePrims.ContainsKey(prim.LocalID))
			{
				// Handle existing local ID case if needed
			}
			else
			{
				// Handle case where local ID does not exist
			}

			Destroy(bgo);
		}
	}

	void AddPositionAndRotationConstraints(GameObject obj, Transform anchor, Vector3 posOffset, Vector3 rotOffset)
	{
		PositionConstraint positionConstraint = obj.AddComponent<PositionConstraint>();

		ConstraintSource positionSource = new ConstraintSource();
		positionSource.sourceTransform = anchor;
		positionSource.weight = 1;

		positionConstraint.AddSource(positionSource);
		positionConstraint.translationOffset = posOffset;
		positionConstraint.constraintActive = true;
		positionConstraint.locked = true;

		RotationConstraint rotationConstraint = obj.AddComponent<RotationConstraint>();

		ConstraintSource rotationSource = new ConstraintSource();
		rotationSource.sourceTransform = anchor;
		rotationSource.weight = 1;

		rotationConstraint.AddSource(rotationSource);
		rotationConstraint.rotationOffset = rotOffset;
		rotationConstraint.constraintActive = true;
		rotationConstraint.locked = true;
	}

	Transform FindChildByNameRecursive(Transform parent, string name)
	{
		foreach (Transform child in parent)
		{
			if (child.name == name)
			{
				return child;
			}

			Transform result = FindChildByNameRecursive(child, name);
			if (result != null)
			{
				return result;
			}
		}

		return null;
	}

	public void CustomTeleport(string Simulator, UnityEngine.Vector3 Sim_Pos)
	{
		SimPosition.X = Sim_Pos.x;
		SimPosition.Y = Sim_Pos.y;
		SimPosition.Z = Sim_Pos.z;

		client.Self.Teleport(Simulator, SimPosition);
	}

	public void CustomProcess()
	{
		ParseSecondLifeUrl(surlInput.text, out string simName, out UnityEngine.Vector3 simPos);
		CustomTeleport(simName, simPos);
	}

	public bool ParseSecondLifeUrl(string url, out string simName, out UnityEngine.Vector3 simPos)
	{
		simName = string.Empty;
		simPos = UnityEngine.Vector3.zero;

		// Check if the URL is valid
		if (string.IsNullOrEmpty(url) || !url.StartsWith("http://maps.secondlife.com/secondlife/"))
		{
			Debug.LogError("Invalid Second Life URL.");
			return false;
		}

		// Remove the base URL part
		string path = url.Substring("http://maps.secondlife.com/secondlife/".Length);

		// Split the path by '/'
		string[] parts = path.Split('/');

		// Ensure we have at least 4 parts (sim name and 3 coordinates)
		if (parts.Length < 4)
		{
			Debug.LogError("URL does not contain enough information.");
			return false;
		}

		// Decode the sim name (it might be URL encoded)
		simName = Uri.UnescapeDataString(parts[0]);

		// Parse the coordinates
		if (!float.TryParse(parts[1], out float x) ||
			!float.TryParse(parts[2], out float y) ||
			!float.TryParse(parts[3], out float z))
		{
			Debug.LogError("Failed to parse coordinates.");
			return false;
		}

		simPos = new UnityEngine.Vector3(x, y, z);
		return true;
	}

	void CleanOrphanedPrims(Primitive prim)
	{
		if (orphanedPrims.ContainsKey(prim.ParentID))
		{
			orphanedPrims[prim.ParentID].Remove(prim);
			if (orphanedPrims[prim.ParentID].Count == 0)
			{
				orphanedPrims.Remove(prim.ParentID);
			}
		}
	}

	void NewAvatar(OpenMetaverse.Avatar av)
	{

	}
}

public struct MovePrims : IJobParallelFor
{
	public NativeArray<float3> positionArray;
	public NativeArray<float3> newpositionArray;
	public NativeArray<quaternion> rotationArray;
	public NativeArray<quaternion> newrotationArray;

	public void Execute(int index)
	{
		positionArray[index] = newpositionArray[index];
		rotationArray[index] = newrotationArray[index];
	}
}

public struct OmegaSpin : IJobParallelFor
{
	public NativeArray<quaternion> rotationArray;
	public NativeArray<quaternion> newrotationArray;
	[ReadOnly] public float deltaTime;

	public void Execute(int index)
	{
		rotationArray[index] = newrotationArray[index];
	}
}