11using CompoundParts ;
22using Highlighting ;
33using KSP . UI . Screens . Flight ;
4+ using KSP . UI . Util ;
45using System ;
56using System . Collections . Generic ;
67using UnityEngine ;
@@ -21,45 +22,226 @@ protected override void ApplyPatches()
2122 AddPatch ( PatchType . Override , typeof ( CModuleLinkedMesh ) , nameof ( CModuleLinkedMesh . TrackAnchor ) ) ;
2223 }
2324
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
2436 private static void TemperatureGaugeSystem_Update_Override ( TemperatureGaugeSystem tgs )
2537 {
2638 if ( ! FlightGlobals . ready || ! HighLogic . LoadedSceneIsFlight )
2739 return ;
2840
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 ( ) )
3044 {
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 ( ) ;
3364 return ;
3465 }
3566
3667 Vessel activeVessel = FlightGlobals . ActiveVessel ;
37- if ( activeVessel . NotDestroyedRefNotEquals ( tgs . activeVessel ) )
38- tgs . CreateGauges ( ) ;
68+ List < Part > parts = activeVessel . parts ;
69+ int partCount = parts . Count ;
3970
40- if ( activeVessel . IsNotNullOrDestroyed ( ) && activeVessel . parts . Count ! = tgs . partCount )
41- tgs . RebuildGaugeList ( ) ;
71+ List < TemperatureGauge > gauges = tgs . gauges ;
72+ tgs . visibleGauges . Clear ( ) ;
4273
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 ;
4583
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 )
48107 {
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+ }
53220 }
54221
55222 tgs . visibleGaugeCount = tgs . visibleGauges . Count ;
56223
57224 if ( tgs . visibleGaugeCount > 0 )
58225 {
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+ }
60237
61238 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+
63245 }
64246 }
65247
0 commit comments