using SanAndreasUnity.Behaviours.Vehicles; using SanAndreasUnity.Importing.Items; using SanAndreasUnity.Importing.Items.Placements; using SanAndreasUnity.Utilities; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using UnityEngine; //using Facepunch.Networking; namespace SanAndreasUnity.Behaviours.World { public class Cell : MonoBehaviour { private Stopwatch _timer; private List _leaves; private Dictionary m_insts; private MapObject[] m_cars; private List m_enexes; public Division RootDivision { get; private set; } private List CellIds = Enumerable.Range(0, 19).ToList(); public bool HasExterior => this.CellIds.Contains(0); public Camera PreviewCamera; public List focusPoints = new List (); public Water Water; public static Cell Instance { get ; private set; } // Statistics private int totalNumObjects = 0; private int numLeavesLoadedThisFrame = 0; private int numObjectsLoadedThisFrame = 0; private float[] measuredTimes = new float[3]; private int numDivisionsUpdatedLoadOrder = 0; private int numMapObjectsUpdatedLoadOrder = 0; private Division containingDivision = null; public float divisionLoadOrderDistanceFactor = 16; public float divisionRefreshDistanceDelta = 20; [Range(0.1f, 3f)] public float divisionsUpdateInterval = 0.3f; public float maxDrawDistance = 500; public bool loadParkedVehicles = true; public GameObject enexPrefab; private void Awake() { if (null == Instance) Instance = this; } private void Start() { //InvokeRepeating("UpdateDivisions", 0f, 0.1f); StartCoroutine( this.UpdateDivisionsCoroutine () ); } internal void CreateStaticGeometry () { if (RootDivision == null) { RootDivision = Division.Create(transform); RootDivision.SetBounds( new Vector2(-3000f, -3000f), new Vector2(+3000f, +3000f)); } var placements = Item.GetPlacements(CellIds.ToArray()); m_insts = new Dictionary (48 * 1024); foreach (var plcm in placements) { m_insts.Add (plcm, StaticGeometry.Create ()); } //m_insts = placements.ToDictionary(x => x, x => StaticGeometry.Create()); UnityEngine.Debug.Log("Num static geometries " + m_insts.Count); totalNumObjects = m_insts.Count; } internal void InitStaticGeometry () { foreach (var inst in m_insts) { inst.Value.Initialize(inst.Key, m_insts); } } internal void LoadParkedVehicles () { if (loadParkedVehicles) { var parkedVehicles = Item.GetPlacements (CellIds.ToArray ()); m_cars = parkedVehicles.Select (x => VehicleSpawner.Create (x)) .Cast () .ToArray (); UnityEngine.Debug.Log ("Num parked vehicles " + m_cars.Length); } } internal void CreateEnexes() { m_enexes = new List(256); foreach(var enex in Item.Enexes.Where(enex => this.CellIds.Contains(enex.TargetInterior))) { m_enexes.Add(EntranceExitMapObject.Create(enex)); } } internal void AddMapObjectsToDivisions () { var enumerable = m_insts.Values.Cast (); if (m_cars != null) enumerable = enumerable.Concat (m_cars); if (m_enexes != null) enumerable = enumerable.Concat(m_enexes); RootDivision.AddRange (enumerable); } internal void LoadWater () { if (Water != null) { Water.Initialize(new WaterFile(Importing.Archive.ArchiveManager.PathToCaseSensitivePath(Config.GetPath("water_path")))); } } internal void FinalizeLoad () { // set layer recursively for all game objects // this.gameObject.SetLayerRecursive( this.gameObject.layer ); _timer = new Stopwatch(); _leaves = RootDivision.ToList(); } public IEnumerable GetEnexesFromLoadedInteriors() { int[] loadedInteriors = this.CellIds.Where(id => id != 0 && id != 13).ToArray(); foreach(var enex in Importing.Items.Item.Enexes.Where(enex => loadedInteriors.Contains(enex.TargetInterior))) { yield return enex; } } public static TransformDataStruct GetEnexExitTransform(EntranceExit enex) { return new TransformDataStruct(enex.ExitPos + Vector3.up * 0.2f, Quaternion.Euler(0f, enex.ExitAngle, 0f)); } public static TransformDataStruct GetEnexEntranceTransform(EntranceExit enex) { return new TransformDataStruct(enex.EntrancePos + Vector3.up * 0.2f, Quaternion.Euler(0f, enex.EntranceAngle, 0f)); } private void Update() { if (!Loader.HasLoaded) return; //this.Setup (); if (null == _leaves) return; _timer.Reset(); _timer.Start(); numLeavesLoadedThisFrame = 0; numObjectsLoadedThisFrame = 0; this.focusPoints.RemoveDeadObjects(); if (this.focusPoints.Count > 0) { // only update divisions loading if there are focus points - because otherwise, // load order of divisions is not updated this.UpdateDivisionsLoading(); } measuredTimes[2] = (float)_timer.Elapsed.TotalMilliseconds; } void UpdateDivisionsLoading() { foreach (var div in _leaves) { if (float.IsPositiveInfinity(div.LoadOrder)) break; numObjectsLoadedThisFrame += div.LoadWhile(() => _timer.Elapsed.TotalSeconds < 1d / 60d); if (_timer.Elapsed.TotalSeconds >= 1d / 60d) { // break; } else { numLeavesLoadedThisFrame++; } } } System.Collections.IEnumerator UpdateDivisionsCoroutine () { while (true) { // wait 100 ms float timePassed = 0; while (timePassed < this.divisionsUpdateInterval) { yield return null; timePassed += Time.unscaledDeltaTime; } F.RunExceptionSafe (() => this.UpdateDivisions ()); } } private void UpdateDivisions() { if (!Loader.HasLoaded) return; if (_leaves == null) return; this.focusPoints.RemoveAll (t => null == t); if (this.focusPoints.Count < 1) return; numDivisionsUpdatedLoadOrder = 0; numMapObjectsUpdatedLoadOrder = 0; containingDivision = null; _timer.Reset(); _timer.Start(); List positions = this.focusPoints.Select (f => f.position).ToList (); bool toLoad = false; // _leaves.Aggregate(false, (current, leaf) => current | leaf.RefreshLoadOrder(pos)); UnityEngine.Profiling.Profiler.BeginSample ("Update divisions", this); foreach (Division leaf in _leaves) { Vector3 pos = leaf.GetClosestPosition (positions); int count = 0; toLoad |= leaf.RefreshLoadOrder(pos, out count); if (count > 0) { numDivisionsUpdatedLoadOrder++; numMapObjectsUpdatedLoadOrder += count; } if (null == containingDivision && leaf.Contains(pos)) { containingDivision = leaf; } } UnityEngine.Profiling.Profiler.EndSample (); measuredTimes[0] = (float)_timer.Elapsed.TotalMilliseconds; if (!toLoad) return; _timer.Reset(); _timer.Start(); UnityEngine.Profiling.Profiler.BeginSample ("Sort leaves", this); _leaves.Sort(); UnityEngine.Profiling.Profiler.EndSample (); measuredTimes[1] = (float)_timer.Elapsed.TotalMilliseconds; } /* private static Rect windowRect = new Rect(10, 10, 250, 330); private const int windowID = 0; private void OnGUI() { if (!Loader.HasLoaded) return; if (!PlayerController._showMenu) return; windowRect = GUILayout.Window(windowID, windowRect, showWindow, "World statistics"); } */ public void showWindow(int windowID) { GUILayout.Label("draw distance " + this.maxDrawDistance); GUILayout.Label("num focus points " + this.focusPoints.Count); GUILayout.Label("total num divisions " + (null == _leaves ? 0 : _leaves.Count)); GUILayout.Label("total num objects " + totalNumObjects); GUILayout.Label("geometry parts loaded " + SanAndreasUnity.Importing.Conversion.Geometry.NumGeometryPartsLoaded); GUILayout.Label("num objects in current division " + (containingDivision != null ? containingDivision.NumObjects : 0)); GUILayout.Label("num divisions updated " + numDivisionsUpdatedLoadOrder); GUILayout.Label("num objects updated " + numMapObjectsUpdatedLoadOrder); GUILayout.Label("num divisions loading this frame " + numLeavesLoadedThisFrame); GUILayout.Label("num objects loading this frame " + numObjectsLoadedThisFrame); GUILayout.Space(10); string[] timeNames = new string[] { "refresh load order ", "sort ", "load / update display " }; int i = 0; foreach (float time in measuredTimes) { GUILayout.Label(timeNames[i] + Mathf.RoundToInt(time)); i++; } GUI.DragWindow(); } } }