1
1
using CompoundParts ;
2
2
using Highlighting ;
3
3
using KSP . UI . Screens . Flight ;
4
+ using KSP . UI . Util ;
4
5
using System ;
5
6
using System . Collections . Generic ;
6
7
using UnityEngine ;
@@ -21,45 +22,226 @@ protected override void ApplyPatches()
21
22
AddPatch ( PatchType . Override , typeof ( CModuleLinkedMesh ) , nameof ( CModuleLinkedMesh . TrackAnchor ) ) ;
22
23
}
23
24
25
+ // Complete reimplementation of TemperatureGaugeSystem :
26
+ // - Minimal update overhead when no gauges are active/shown
27
+ // - Vastly reduced update overhead when gauges/highlight are active/shown
28
+ // - Gauges are now instantiated on demand, eliminating (most of) the cost on vessel load
29
+ // - Gauges are now recycled when the vessel is modified / switched, instead of destroying and re-instantiating them immediately
30
+ // Gauges were previously always instantiated for every part on the active vessel, and this was pretty slow due to triggering
31
+ // a lot of internal UI/layout/graphic dirtying on every gauge instantiation. Overall, the operation can take several hundred
32
+ // milliseconds in not-so large part count situations, leading to very significant hiccups
33
+ // TemperatureGaugeSystem.Update average frame time with a ~500 part vessel, gauges not shown : Stock 0.6%, KSPCF 0.04%
34
+ // TemperatureGaugeSystem.Update average frame time with a ~500 part vessel, ~16 gauges shown : Stock 1.7%, KSPCF 0.3%
35
+ // See https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/194
24
36
private static void TemperatureGaugeSystem_Update_Override ( TemperatureGaugeSystem tgs )
25
37
{
26
38
if ( ! FlightGlobals . ready || ! HighLogic . LoadedSceneIsFlight )
27
39
return ;
28
40
29
- if ( GameSettings . TEMPERATURE_GAUGES_MODE < 1 || CameraManager . Instance . currentCameraMode != CameraManager . CameraMode . Flight )
41
+ if ( GameSettings . TEMPERATURE_GAUGES_MODE == 0
42
+ || CameraManager . Instance . currentCameraMode != CameraManager . CameraMode . Flight
43
+ || FlightGlobals . ActiveVessel . IsNullOrDestroyed ( ) )
30
44
{
31
- if ( tgs . gaugeCount > 0 )
32
- tgs . DestroyGauges ( ) ;
45
+ if ( tgs . visibleGauges . Count == 0 )
46
+ return ;
47
+
48
+ for ( int i = tgs . visibleGauges . Count ; i -- > 0 ; )
49
+ {
50
+ TemperatureGauge gauge = tgs . visibleGauges [ i ] ;
51
+ if ( gauge . gaugeActive )
52
+ gauge . progressBar . gameObject . SetActive ( false ) ;
53
+
54
+ if ( gauge . highlightActive && gauge . part . IsNotNullOrDestroyed ( ) )
55
+ gauge . part . SetHighlightDefault ( ) ;
56
+
57
+ gauge . part = null ;
58
+ gauge . gaugeActive = false ;
59
+ gauge . showGauge = false ;
60
+ gauge . highlightActive = false ;
61
+ }
62
+
63
+ tgs . visibleGauges . Clear ( ) ;
33
64
return ;
34
65
}
35
66
36
67
Vessel activeVessel = FlightGlobals . ActiveVessel ;
37
- if ( activeVessel . NotDestroyedRefNotEquals ( tgs . activeVessel ) )
38
- tgs . CreateGauges ( ) ;
68
+ List < Part > parts = activeVessel . parts ;
69
+ int partCount = parts . Count ;
39
70
40
- if ( activeVessel . IsNotNullOrDestroyed ( ) && activeVessel . parts . Count ! = tgs . partCount )
41
- tgs . RebuildGaugeList ( ) ;
71
+ List < TemperatureGauge > gauges = tgs . gauges ;
72
+ tgs . visibleGauges . Clear ( ) ;
42
73
43
- if ( tgs . gaugeCount == 0 )
44
- return ;
74
+ if ( tgs . activeVessel . IsNullRef ( ) )
75
+ {
76
+ tgs . activeVessel = activeVessel ;
77
+ tgs . partCount = partCount ;
78
+ }
79
+ else if ( tgs . activeVessel . RefNotEquals ( activeVessel ) || tgs . partCount != partCount )
80
+ {
81
+ tgs . activeVessel = activeVessel ;
82
+ tgs . partCount = partCount ;
45
83
46
- tgs . visibleGauges . Clear ( ) ;
47
- for ( int i = tgs . gaugeCount ; i -- > 0 ; )
84
+ for ( int i = gauges . Count ; i -- > 0 ; )
85
+ {
86
+ TemperatureGauge gauge = gauges [ i ] ;
87
+ if ( gauge . IsNullRef ( ) )
88
+ continue ;
89
+
90
+ if ( gauge . gaugeActive )
91
+ gauge . progressBar . gameObject . SetActive ( false ) ;
92
+
93
+ if ( gauge . highlightActive && gauge . part . IsNotNullOrDestroyed ( ) )
94
+ gauge . part . SetHighlightDefault ( ) ;
95
+
96
+ gauge . part = null ;
97
+ gauge . gaugeActive = false ;
98
+ gauge . showGauge = false ;
99
+ gauge . highlightActive = false ;
100
+ }
101
+ }
102
+
103
+ while ( gauges . Count < partCount )
104
+ gauges . Add ( null ) ;
105
+
106
+ while ( gauges . Count > partCount )
48
107
{
49
- TemperatureGauge gauge = tgs . gauges [ i ] ;
50
- gauge . GaugeUpdate ( ) ;
51
- if ( gauge . gaugeActive )
52
- tgs . visibleGauges . Add ( gauge ) ;
108
+ int lastGaugeIdx = gauges . Count - 1 ;
109
+ TemperatureGauge gauge = gauges [ lastGaugeIdx ] ;
110
+ if ( gauge . IsNotNullRef ( ) )
111
+ UnityEngine . Object . Destroy ( gauge . gameObject ) ;
112
+ gauges . RemoveAt ( lastGaugeIdx ) ;
113
+ }
114
+
115
+ float gaugeThreshold = PhysicsGlobals . instance . temperatureGaugeThreshold ;
116
+ float gaugeHighlightThreshold = PhysicsGlobals . instance . temperatureGaugeHighlightThreshold ;
117
+
118
+ bool gaugesEnabled = ( GameSettings . TEMPERATURE_GAUGES_MODE & 1 ) > 0 ;
119
+ bool highlightsEnabled = ( GameSettings . TEMPERATURE_GAUGES_MODE & 2 ) > 0 ;
120
+
121
+ for ( int i = 0 ; i < partCount ; i ++ )
122
+ {
123
+ Part part = parts [ i ] ;
124
+
125
+ float skinTempFactor = ( float ) ( part . skinTemperature / part . skinMaxTemp ) ;
126
+ float tempFactor = ( float ) ( part . temperature / part . maxTemp ) ;
127
+
128
+ if ( skinTempFactor > tempFactor )
129
+ tempFactor = skinTempFactor ;
130
+
131
+ TemperatureGauge gauge = gauges [ i ] ;
132
+
133
+ bool gaugeEnabled = gaugesEnabled && tempFactor > gaugeThreshold * part . gaugeThresholdMult ;
134
+ bool highlightEnabled = highlightsEnabled && tempFactor > gaugeHighlightThreshold * part . edgeHighlightThresholdMult ;
135
+
136
+ if ( gaugeEnabled || highlightEnabled )
137
+ {
138
+ if ( gauge . IsNullRef ( ) )
139
+ {
140
+ gauge = UnityEngine . Object . Instantiate ( tgs . temperatureGaugePrefab ) ;
141
+ gauge . transform . SetParent ( tgs . transform , worldPositionStays : false ) ;
142
+ gauge . Setup ( part , gaugeHighlightThreshold , gaugeThreshold ) ;
143
+ gauges [ i ] = gauge ;
144
+ }
145
+ else if ( part . RefNotEquals ( gauge . part ) )
146
+ {
147
+ gauge . part = part ;
148
+ }
149
+
150
+ if ( gaugeEnabled )
151
+ {
152
+ bool showGauge = true ;
153
+ Vector3 partPos = part . partTransform . position ;
154
+ // this is the main remaining perf hog
155
+ // It should be feasible to grab the camera matrix once and perform the transformation manually, but well...
156
+ gauge . uiPos = RectUtil . WorldToUISpacePos ( partPos , FlightCamera . fetch . mainCamera , MainCanvasUtil . MainCanvasRect , ref showGauge ) ;
157
+
158
+ if ( showGauge )
159
+ {
160
+ tgs . visibleGauges . Add ( gauge ) ;
161
+
162
+ gauge . distanceFromCamera = Vector3 . SqrMagnitude ( FlightCamera . fetch . mainCamera . transform . position - partPos ) ;
163
+ gauge . rTrf . localPosition = gauge . uiPos ;
164
+
165
+ const float minValueChange = 1f / 120f ; // slider is ~60 pixels at 100% UI scale
166
+ float valueChange = Math . Abs ( gauge . progressBar . value - tempFactor ) ;
167
+ if ( valueChange > minValueChange )
168
+ {
169
+ gauge . sliderFill . color = Color . Lerp ( Color . green , Color . red , tempFactor ) ;
170
+ gauge . progressBar . value = tempFactor ; // setting this is awfully slow, so only set it when the slider is visually changing...
171
+ }
172
+ }
173
+
174
+ if ( ! gauge . gaugeActive || showGauge != gauge . showGauge )
175
+ {
176
+ gauge . gaugeActive = true ;
177
+ gauge . showGauge = showGauge ;
178
+ gauge . progressBar . gameObject . SetActive ( showGauge ) ;
179
+ }
180
+ }
181
+ else if ( gauge . gaugeActive )
182
+ {
183
+ gauge . gaugeActive = false ;
184
+ gauge . showGauge = false ;
185
+ gauge . progressBar . gameObject . SetActive ( false ) ;
186
+ }
187
+
188
+ if ( highlightEnabled )
189
+ {
190
+ if ( ! gauge . highlightActive )
191
+ {
192
+ gauge . highlightActive = true ;
193
+ part . SetHighlightType ( Part . HighlightType . AlwaysOn ) ;
194
+ part . SetHighlight ( active : true , recursive : false ) ;
195
+ }
196
+
197
+ gauge . edgeRatio = Mathf . InverseLerp ( gauge . edgeHighlightThreshold * part . edgeHighlightThresholdMult , 1f , tempFactor ) ;
198
+ gauge . colorScale = tempFactor ;
199
+ part . SetHighlightColor ( Color . Lerp ( XKCDColors . Red * tempFactor , XKCDColors . KSPNotSoGoodOrange * tempFactor , gauge . edgeRatio ) ) ;
200
+ }
201
+ else if ( gauge . highlightActive )
202
+ {
203
+ gauge . highlightActive = false ;
204
+ if ( gauge . part . IsNotNullOrDestroyed ( ) )
205
+ part . SetHighlightDefault ( ) ;
206
+ }
207
+ }
208
+ else if ( gauge . IsNotNullRef ( ) && gauge . gaugeActive )
209
+ {
210
+ gauge . progressBar . gameObject . SetActive ( false ) ;
211
+ gauge . gaugeActive = false ;
212
+ gauge . showGauge = false ;
213
+ if ( gauge . highlightActive )
214
+ {
215
+ gauge . highlightActive = false ;
216
+ if ( gauge . part . IsNotNullOrDestroyed ( ) )
217
+ part . SetHighlightDefault ( ) ;
218
+ }
219
+ }
53
220
}
54
221
55
222
tgs . visibleGaugeCount = tgs . visibleGauges . Count ;
56
223
57
224
if ( tgs . visibleGaugeCount > 0 )
58
225
{
59
- tgs . visibleGauges . Sort ( ) ;
226
+ // A simple manual insertion sort is an order of magnitude faster than going through the IComparable interface
227
+ List < TemperatureGauge > visibleGauges = tgs . visibleGauges ;
228
+ for ( int i = 1 ; i < tgs . visibleGaugeCount ; i ++ )
229
+ {
230
+ TemperatureGauge current = visibleGauges [ i ] ;
231
+ int j = i ;
232
+ while ( j > 0 && visibleGauges [ j - 1 ] . distanceFromCamera > current . distanceFromCamera )
233
+ visibleGauges [ j ] = visibleGauges [ -- j ] ;
234
+
235
+ visibleGauges [ j ] = current ;
236
+ }
60
237
61
238
for ( int i = 0 ; i < tgs . visibleGaugeCount ; i ++ )
62
- tgs . visibleGauges [ i ] . rTrf . SetSiblingIndex ( i ) ;
239
+ {
240
+ TemperatureGauge visibleGauge = visibleGauges [ i ] ;
241
+ if ( visibleGauge . rTrf . GetSiblingIndex ( ) != i )
242
+ visibleGauge . rTrf . SetSiblingIndex ( i ) ;
243
+ }
244
+
63
245
}
64
246
}
65
247
0 commit comments