-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add common DragCubeTool, now with caching
- Loading branch information
Showing
4 changed files
with
225 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using UnityEngine; | ||
using UnityEngine.Profiling; | ||
|
||
namespace ROUtils | ||
{ | ||
/// <summary> | ||
/// Common tool for various procedural part mods for generating drag cubes. | ||
/// </summary> | ||
public class DragCubeTool : MonoBehaviour | ||
{ | ||
private static readonly Dictionary<string, DragCube> _cacheDict = new Dictionary<string, DragCube>(); | ||
private static bool _statsRoutineStarted = false; | ||
private static uint _cubesRenderedThisFrame = 0; | ||
private static uint _cacheHitsThisFrame = 0; | ||
private static long _elapsedTicks = 0; | ||
|
||
private string _shapeKey; | ||
|
||
/// <summary> | ||
/// Globally enable of disable drag cube caching. | ||
/// </summary> | ||
public static bool UseCache { get; set; } = true; | ||
/// <summary> | ||
/// Whether to validate all cubes that got fetched from cache against freshly-rendered ones. | ||
/// </summary> | ||
public static bool ValidateCubes { get; set; } = false; | ||
/// <summary> | ||
/// Max number of items in cache. Once that number is reached then the cache is cleared entirely. | ||
/// </summary> | ||
public static uint MaxCacheSize { get; set; } = 5000; | ||
|
||
public Part Part { get; private set; } | ||
|
||
/// <summary> | ||
/// Creates and assigns a drag cube for the given procedural part. | ||
/// This process can have one to many frames of delay. | ||
/// </summary> | ||
/// <param name="p">Part to create drag cube for</param> | ||
/// <param name="shapeKey">Key that uniquely identifies the geometry of the part.Used in caching logic. Use null if no caching is desired.</param> | ||
/// <returns></returns> | ||
public static DragCubeTool UpdateDragCubes(Part p, string shapeKey = null) | ||
{ | ||
var tool = p.GetComponent<DragCubeTool>(); | ||
if (tool == null) | ||
{ | ||
tool = p.gameObject.AddComponent<DragCubeTool>(); | ||
tool.Part = p; | ||
tool._shapeKey = shapeKey; | ||
} | ||
tool._shapeKey = shapeKey; | ||
return tool; | ||
} | ||
|
||
/// <summary> | ||
/// Creates and assigns a drag cube for the given procedural part. | ||
/// Use only when you know that the part is ready for drag cube rendering. Otherwise use UpdateDragCubes. | ||
/// </summary> | ||
/// <param name="p">Part to create drag cube for</param> | ||
/// <param name="shapeKey">Key that uniquely identifies the geometry of the part.Used in caching logic. Use null if no caching is desired.</param> | ||
/// <exception cref="InvalidOperationException">Thrown when the part is not yet ready for drag cube rendering</exception> | ||
public static void UpdateDragCubesImmediate(Part p, string shapeKey = null) | ||
{ | ||
if (!Ready(p)) | ||
throw new InvalidOperationException("Not ready for drag cube rendering yet"); | ||
|
||
UpdateCubes(p, shapeKey); | ||
} | ||
|
||
public void FixedUpdate() | ||
{ | ||
if (Ready()) | ||
UpdateCubes(); | ||
} | ||
|
||
public bool Ready() => Ready(Part); | ||
|
||
private static bool Ready(Part p) | ||
{ | ||
if (HighLogic.LoadedSceneIsFlight) | ||
return FlightGlobals.ready; | ||
if (HighLogic.LoadedSceneIsEditor) | ||
return p.localRoot == EditorLogic.RootPart && p.gameObject.layer != LayerMask.NameToLayer("TransparentFX"); | ||
return true; | ||
} | ||
|
||
private void UpdateCubes() | ||
{ | ||
UpdateCubes(Part, _shapeKey); | ||
Destroy(this); | ||
} | ||
|
||
private static void UpdateCubes(Part p, string shapeKey = null) | ||
{ | ||
if (ModUtils.IsFARInstalled) | ||
p.SendMessage("GeometryPartModuleRebuildMeshData"); | ||
|
||
Profiler.BeginSample("UpdateCubes"); | ||
long startTicks = System.Diagnostics.Stopwatch.GetTimestamp(); | ||
if (!UseCache || shapeKey == null || !_cacheDict.TryGetValue(shapeKey, out DragCube dragCube)) | ||
{ | ||
dragCube = DragCubeSystem.Instance.RenderProceduralDragCube(p); | ||
_cubesRenderedThisFrame++; | ||
|
||
if (UseCache && shapeKey != null && PartLoader.Instance.IsReady()) | ||
{ | ||
// Keep a pristine copy in cache. I.e the instance must not be be used by a part. | ||
DragCube clonedCube = CloneCube(dragCube); | ||
_cacheDict[shapeKey] = clonedCube; | ||
} | ||
} | ||
else | ||
{ | ||
_cacheHitsThisFrame++; | ||
dragCube = CloneCube(dragCube); | ||
if (ValidateCubes) | ||
RunCubeValidation(p, dragCube, shapeKey); | ||
} | ||
|
||
p.DragCubes.ClearCubes(); | ||
p.DragCubes.Cubes.Add(dragCube); | ||
p.DragCubes.ResetCubeWeights(); | ||
p.DragCubes.ForceUpdate(true, true, false); | ||
p.DragCubes.SetDragWeights(); | ||
|
||
_elapsedTicks += System.Diagnostics.Stopwatch.GetTimestamp() - startTicks; | ||
Profiler.EndSample(); | ||
|
||
if (!_statsRoutineStarted) | ||
p.StartCoroutine(StatsCoroutine()); | ||
} | ||
|
||
private static IEnumerator StatsCoroutine() | ||
{ | ||
_statsRoutineStarted = true; | ||
yield return new WaitForEndOfFrame(); | ||
_statsRoutineStarted = false; | ||
|
||
double timeMs = _elapsedTicks / (System.Diagnostics.Stopwatch.Frequency / 1000d); | ||
Debug.Log($"[DragCubeTool] Rendered {_cubesRenderedThisFrame} cubes; fetched {_cacheHitsThisFrame} from cache; exec time: {timeMs:F1}ms"); | ||
_cacheHitsThisFrame = 0; | ||
_cubesRenderedThisFrame = 0; | ||
_elapsedTicks = 0; | ||
|
||
if (_cacheDict.Count > MaxCacheSize) | ||
{ | ||
Debug.Log($"[DragCubeTool] Cache limit reached ({_cacheDict.Count} / {MaxCacheSize}), emptying..."); | ||
_cacheDict.Clear(); | ||
} | ||
} | ||
|
||
private static DragCube CloneCube(DragCube dragCube) | ||
{ | ||
return new DragCube | ||
{ | ||
area = dragCube.area, | ||
drag = dragCube.drag, | ||
depth = dragCube.depth, | ||
dragModifiers = dragCube.dragModifiers, | ||
center = dragCube.center, | ||
size = dragCube.size, | ||
name = dragCube.name | ||
}; | ||
} | ||
|
||
private static void RunCubeValidation(Part p, DragCube cacheCube, string shapeKey) | ||
{ | ||
DragCube renderedCube = DragCubeSystem.Instance.RenderProceduralDragCube(p); | ||
|
||
// drag components randomly switch places so sort the arrays before comparing | ||
var cacheSortedDrag = cacheCube.drag.OrderBy(v => v).ToArray(); | ||
var renderSortedDrag = renderedCube.drag.OrderBy(v => v).ToArray(); | ||
|
||
if (!ArraysNearlyEqual(cacheCube.area, renderedCube.area, 0.005f) || | ||
!ArraysNearlyEqual(cacheSortedDrag, renderSortedDrag, 0.05f) || | ||
!ArraysNearlyEqual(cacheCube.depth, renderedCube.depth, 0.01f) || | ||
!ArraysNearlyEqual(cacheCube.dragModifiers, renderedCube.dragModifiers, 0.005f) || | ||
!VectorsNearlyEqual(cacheCube.center, renderedCube.center, 0.005f) || | ||
!VectorsNearlyEqual(cacheCube.size, renderedCube.size, 0.005f)) | ||
{ | ||
Debug.LogError($"[DragCubeTool] Mismatch in cached cube for part {p.partInfo.name}, key {shapeKey}:"); | ||
Debug.LogError($"Cache: {cacheCube.SaveToString()}"); | ||
Debug.LogError($"Renderd: {renderedCube.SaveToString()}"); | ||
} | ||
} | ||
|
||
private static bool ArraysNearlyEqual(float[] arr1, float[] arr2, float tolerance) | ||
{ | ||
for (int i = 0; i < arr1.Length; i++) | ||
{ | ||
float a = arr1[i]; | ||
float b = arr2[i]; | ||
if (Math.Abs(a - b) > tolerance) | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
private static bool VectorsNearlyEqual(Vector3 v1, Vector3 v2, float tolerance) | ||
{ | ||
return (v1 - v2).sqrMagnitude < tolerance * tolerance; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters