Skip to content

Commit b606846

Browse files
committed
Add common DragCubeTool, now with caching
1 parent 57562b4 commit b606846

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed

Source/ROUtils/ROUtils.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
<Compile Include="CachedObject.cs" />
128128
<Compile Include="SingletonHost.cs" />
129129
<Compile Include="Utils\ContinuousLogger.cs" />
130+
<Compile Include="Utils\DragCubeTool.cs" />
130131
<Compile Include="Utils\DTUtils.cs" />
131132
<Compile Include="Utils\HyperEdit_Utilities.cs" />
132133
<Compile Include="Utils\KSPUtils.cs" />
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using UnityEngine;
5+
using UnityEngine.Profiling;
6+
7+
namespace ROUtils
8+
{
9+
public class DragCubeTool : MonoBehaviour
10+
{
11+
private static readonly Dictionary<string, DragCube> _cacheDict = new Dictionary<string, DragCube>();
12+
private static bool _statsRoutineStarted = false;
13+
private static uint _cubesRenderedThisFrame = 0;
14+
private static uint _cacheHitsThisFrame = 0;
15+
private static long _elapsedTicks = 0;
16+
17+
private string _shapeKey;
18+
19+
public static bool UseCache { get; set; } = true;
20+
public static bool ValidateCubes { get; set; } = false;
21+
public static uint MaxCacheSize { get; set; } = 5000;
22+
23+
public Part Part { get; private set; }
24+
25+
public static DragCubeTool UpdateDragCubes(Part p, string shapeKey = null)
26+
{
27+
var tool = p.GetComponent<DragCubeTool>();
28+
if (tool == null)
29+
{
30+
tool = p.gameObject.AddComponent<DragCubeTool>();
31+
tool.Part = p;
32+
tool._shapeKey = shapeKey;
33+
}
34+
return tool;
35+
}
36+
37+
public static void UpdateDragCubesImmediate(Part p, string shapeKey = null)
38+
{
39+
if (!Ready(p))
40+
throw new InvalidOperationException("Not ready for drag cube rendering yet");
41+
42+
UpdateCubes(p, shapeKey);
43+
}
44+
45+
public void FixedUpdate()
46+
{
47+
if (Ready())
48+
UpdateCubes();
49+
}
50+
51+
public bool Ready() => Ready(Part);
52+
53+
private static bool Ready(Part p)
54+
{
55+
if (HighLogic.LoadedSceneIsFlight)
56+
return FlightGlobals.ready;
57+
if (HighLogic.LoadedSceneIsEditor)
58+
return p.localRoot == EditorLogic.RootPart && p.gameObject.layer != LayerMask.NameToLayer("TransparentFX");
59+
return true;
60+
}
61+
62+
private void UpdateCubes()
63+
{
64+
UpdateCubes(Part, _shapeKey);
65+
Destroy(this);
66+
}
67+
68+
private static void UpdateCubes(Part p, string shapeKey = null)
69+
{
70+
if (ModUtils.IsFARInstalled)
71+
p.SendMessage("GeometryPartModuleRebuildMeshData");
72+
73+
Profiler.BeginSample("UpdateCubes");
74+
var sw = System.Diagnostics.Stopwatch.StartNew();
75+
if (!UseCache || shapeKey == null || !_cacheDict.TryGetValue(shapeKey, out DragCube dragCube))
76+
{
77+
dragCube = DragCubeSystem.Instance.RenderProceduralDragCube(p);
78+
_cubesRenderedThisFrame++;
79+
80+
if (UseCache && shapeKey != null && PartLoader.Instance.IsReady())
81+
{
82+
// Keep a pristine copy in cache. I.e the instance must not be be used by a part.
83+
DragCube clonedCube = CloneCube(dragCube);
84+
_cacheDict[shapeKey] = clonedCube;
85+
}
86+
}
87+
else
88+
{
89+
_cacheHitsThisFrame++;
90+
dragCube = CloneCube(dragCube);
91+
if (ValidateCubes)
92+
RunCubeValidation(p, dragCube, shapeKey);
93+
}
94+
95+
p.DragCubes.ClearCubes();
96+
p.DragCubes.Cubes.Add(dragCube);
97+
p.DragCubes.ResetCubeWeights();
98+
p.DragCubes.ForceUpdate(true, true, false);
99+
p.DragCubes.SetDragWeights();
100+
101+
_elapsedTicks += sw.ElapsedTicks;
102+
Profiler.EndSample();
103+
104+
if (!_statsRoutineStarted)
105+
p.StartCoroutine(StatsCoroutine());
106+
}
107+
108+
private static IEnumerator StatsCoroutine()
109+
{
110+
_statsRoutineStarted = true;
111+
yield return new WaitForEndOfFrame();
112+
_statsRoutineStarted = false;
113+
114+
double timeMs = _elapsedTicks / (System.Diagnostics.Stopwatch.Frequency / 1000d);
115+
Debug.Log($"[DragCubeTool] Rendered {_cubesRenderedThisFrame} cubes; fetched {_cacheHitsThisFrame} from cache; exec time: {timeMs:F1}ms");
116+
_cacheHitsThisFrame = 0;
117+
_cubesRenderedThisFrame = 0;
118+
_elapsedTicks = 0;
119+
120+
if (_cacheDict.Count > MaxCacheSize)
121+
{
122+
Debug.Log($"[DragCubeTool] Cache limit reached ({_cacheDict.Count} / {MaxCacheSize}), emptying...");
123+
_cacheDict.Clear();
124+
}
125+
}
126+
127+
private static DragCube CloneCube(DragCube dragCube)
128+
{
129+
return new DragCube
130+
{
131+
area = dragCube.area,
132+
drag = dragCube.drag,
133+
depth = dragCube.depth,
134+
dragModifiers = dragCube.dragModifiers,
135+
center = dragCube.center,
136+
size = dragCube.size,
137+
name = dragCube.name
138+
};
139+
}
140+
141+
private static void RunCubeValidation(Part p, DragCube cacheCube, string shapeKey)
142+
{
143+
DragCube renderedCube = DragCubeSystem.Instance.RenderProceduralDragCube(p);
144+
if (!ArraysNearlyEqual(cacheCube.area, renderedCube.area, 0.005f) ||
145+
//!ArraysNearlyEqual(cacheCube.drag, renderedCube.drag, 0.05f) || // drag components switch places so validating them is painful
146+
!ArraysNearlyEqual(cacheCube.depth, renderedCube.depth, 0.01f) ||
147+
!ArraysNearlyEqual(cacheCube.dragModifiers, renderedCube.dragModifiers, 0.005f) ||
148+
!VectorsNearlyEqual(cacheCube.center, renderedCube.center) ||
149+
!VectorsNearlyEqual(cacheCube.size, renderedCube.size))
150+
{
151+
Debug.LogError($"[DragCubeTool] Mismatch in cached cube for part {p.partInfo.name}, key {shapeKey}:");
152+
Debug.LogError($"Cache: {cacheCube.SaveToString()}");
153+
Debug.LogError($"Renderd: {renderedCube.SaveToString()}");
154+
}
155+
}
156+
157+
private static bool ArraysNearlyEqual(float[] arr1, float[] arr2, float tolerance)
158+
{
159+
bool equal = true;
160+
for (int i = 0; i < arr1.Length; i++)
161+
{
162+
float a = arr1[i];
163+
float b = arr2[i];
164+
equal &= Math.Abs(a - b) < tolerance;
165+
}
166+
return equal;
167+
}
168+
169+
private static bool VectorsNearlyEqual(Vector3 v1, Vector3 v2)
170+
{
171+
return (v1 - v2).sqrMagnitude < 2.5E-5f;
172+
}
173+
}
174+
}

Source/ROUtils/Utils/ModUtils.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,19 @@ public static bool IsRP1Installed
2424
}
2525
}
2626

27-
27+
private static bool? _isFARInstalled;
28+
public static bool IsFARInstalled
29+
{
30+
get
31+
{
32+
if (!_isFARInstalled.HasValue)
33+
{
34+
_isFARInstalled = AssemblyLoader.loadedAssemblies.Any(a => a.assembly.GetName().Name == "FerramAerospaceResearch");
35+
}
36+
return _isFARInstalled.Value;
37+
}
38+
}
39+
2840
private static bool? _isTestFlightInstalled = null;
2941
private static bool? _isTestLiteInstalled = null;
3042

0 commit comments

Comments
 (0)