/// <summary> /// Recalculates the transmission rates. /// </summary> public void RecalculateTransmissionRates() { if (!this.canTransmit) { this.moduleRef.CurrentLinkCost = RelayDataCost.Infinity; return; } RelayDataCost cost = this.GetPotentialLinkCost(this.CurrentLinkSqrDistance, this.NominalLinkSqrDistance); this.moduleRef.CurrentLinkCost = cost; }
/// <summary> /// Gets the potential link cost, in EC/MiT. /// </summary> /// <returns>The potential link cost, in EC/MiT.</returns> /// <param name="currentSqrDistance">Square of the current distance to the target</param> /// <param name="nominalSqrDistance">Square of the nominal range to the target.</param> public RelayDataCost GetPotentialLinkCost(double currentSqrDistance, double nominalSqrDistance) { RelayDataCost linkCost = new RelayDataCost(); float rangeFactor = (float)(nominalSqrDistance / currentSqrDistance); RelayDataCost baseCost = this.moduleRef?.BaseLinkCost ?? RelayDataCost.Infinity; if (ARConfiguration.FixedPowerCost) { linkCost.PacketResourceCost = baseCost.PacketResourceCost; linkCost.PacketSize = Mathf.Min( baseCost.PacketSize * rangeFactor, baseCost.PacketSize * this.moduleRef.MaxDataFactor ); } else { if (currentSqrDistance > nominalSqrDistance) { linkCost.PacketSize = baseCost.PacketSize; linkCost.PacketResourceCost = baseCost.PacketResourceCost / rangeFactor; } else { linkCost.PacketSize = Mathf.Min( baseCost.PacketSize * rangeFactor, baseCost.PacketSize * this.moduleRef.MaxDataFactor ); linkCost.PacketResourceCost = baseCost.PacketResourceCost; } } linkCost.PacketResourceCost *= this.moduleRef.PacketThrottle / 100f; linkCost.PacketSize *= this.moduleRef.PacketThrottle / 100f; return(linkCost); }
/// <summary> /// Finds the nearest relay. /// </summary> /// <returns>The nearest relay or null, if no relays in range.</returns> public void FindNearestRelay() { if (!FlightGlobals.ready) { return; } PooledDebugLogger log; #if DEBUG log = PooledDebugLogger.New(this); #endif #if BENCH this.performanceTimer.Restart(); long startVesselLoopTicks; long totalVesselLoopTicks; string slowestLOSVesselName = string.Empty; long slowestLOSVesselTicks = long.MinValue; long startLOSVesselTicks; long totalLOSVesselTicks; string slowestCircularVesselName = string.Empty; long slowestCircularVesselTicks = long.MinValue; long startCircularVesselTicks; long totalCircularVesselTicks; long startKerbinLOSTicks; long totalKerbinLOSTicks; long statusResolutionTicks; ushort usefulVesselCount = 0; #endif log.AppendFormat("{0}: Target search started).", this.ToString()); #if DEBUG try { #endif // Declare a bunch of variables we'll be using. CelestialBody bodyOccludingBestOccludedRelay = null; IAntennaRelay needle; RelayDataCost cheapestRelayRate = RelayDataCost.Infinity; RelayDataCost cheapestOccludedRelayRate = RelayDataCost.Infinity; RelayDataCost potentialRelayRate; RelayDataCost kerbinRelayRate = this.GetPotentialLinkCost(Kerbin); bool isCircular; int iterCount; // Blank everything we're trying to find before the search. this.firstOccludingBody = null; this.bestOccludedRelay = null; this.targetRelay = null; this.nearestRelay = null; // Default to KerbinDirect = true in case something in here doesn't work right. this.KerbinDirect = true; /* * Loop through the useful relays as determined by ARFlightController and check each for line of sight and * distance, searching for the relay with the best distance/maxRange ratio that is in sight, in range, and * can transmit, also stashing the "best" relay outside of line of sight for failure report. * */ IAntennaRelay potentialBestRelay; CelestialBody fob; #if BENCH startVesselLoopTicks = performanceTimer.ElapsedTicks; #endif for (int rIdx = 0; rIdx < ARFlightController.UsefulRelays.Count; rIdx++) { potentialBestRelay = ARFlightController.UsefulRelays[rIdx]; log.AppendFormat("\n\tgot useful relay {0}", potentialBestRelay == null ? "null" : potentialBestRelay.ToString()); if (potentialBestRelay == null) { log.Append("\n\t...skipping null relay"); continue; } if (potentialBestRelay == this || potentialBestRelay.vessel == this.vessel) { log.AppendFormat( "\n\t...skipping relay {0} because it or its vessel ({1}) is the same as ours" + "\n\t\t(our vessel is {2})", potentialBestRelay, potentialBestRelay.vessel == null ? "null" : potentialBestRelay.vessel.vesselName, this.vessel == null ? "null" : this.vessel.vesselName ); continue; } #if BENCH usefulVesselCount++; #endif // Find the distance from here to the vessel... log.Append("\n\tgetting cost to potential vessel"); potentialRelayRate = potentialBestRelay.CurrentNetworkLinkCost + this.GetPotentialLinkCost(potentialBestRelay); log.AppendFormat( "\n\tpotentialRelayRate = {0} ({1} + {2})", potentialRelayRate, potentialBestRelay.CurrentNetworkLinkCost, this.GetPotentialLinkCost(potentialBestRelay) ); #if BENCH startLOSVesselTicks = performanceTimer.ElapsedTicks; #endif log.Append("\n\t\tdoing LOS check"); // Skip vessels to which we do not have line of sight. if ( ARConfiguration.RequireLineOfSight && !this.vessel.hasLineOfSightTo(potentialBestRelay.vessel, out fob, ARConfiguration.RadiusRatio) ) { #if BENCH totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks; if (totalLOSVesselTicks > slowestLOSVesselTicks) { slowestLOSVesselTicks = totalLOSVesselTicks; slowestLOSVesselName = vessel.vesselName; } #endif log.Append("\n\t\t...failed LOS check"); log.AppendFormat("\n\t\t\t{0}: Relay {1} not in line of sight.", this.ToString(), potentialBestRelay); log.AppendFormat("\n\t\t\tpotentialRelayRate: {0}", potentialRelayRate); log.AppendFormat("\n\t\t\tcheapestOccludedRelayRate: {0}", cheapestOccludedRelayRate); if ( (potentialRelayRate < cheapestRelayRate) && this.IsInRangeOf(potentialBestRelay) && potentialBestRelay.CanTransmit() ) { log.Append("\n\t\t...vessel is cheapest and in range and potentialBestRelay can transmit"); log.AppendFormat("\n\t\t...{0} found new best occluded relay {1}", this, potentialBestRelay); this.bestOccludedRelay = potentialBestRelay; bodyOccludingBestOccludedRelay = fob; cheapestOccludedRelayRate = potentialRelayRate; } else { log.Append("\n\t\t...vessel is not close enough to check for occluded relays, carrying on"); } continue; } #if BENCH else { totalLOSVesselTicks = performanceTimer.ElapsedTicks - startLOSVesselTicks; } if (totalLOSVesselTicks > slowestLOSVesselTicks) { slowestLOSVesselTicks = totalLOSVesselTicks; slowestLOSVesselName = vessel.vesselName; } #endif log.Append("\n\t\t...passed LOS check"); /* * ...so that we can skip the vessel if it is further away than a vessel we've already checked. * */ if (potentialRelayRate > cheapestRelayRate) { log.AppendFormat( "\n\t{0}: Relay {1} discarded because it is more expensive than the cheapest relay." + "\n\t\t({2}, {3} > {4})", this.ToString(), potentialBestRelay, this.nearestRelay == null ? "NULL" : this.nearestRelay.ToString(), potentialRelayRate, cheapestRelayRate ); continue; } log.Append("\n\t\t...passed distance check"); if (potentialBestRelay.CanTransmit()) { #if BENCH startCircularVesselTicks = performanceTimer.ElapsedTicks; #endif needle = potentialBestRelay; isCircular = false; iterCount = 0; while (needle != null) { iterCount++; if (needle.KerbinDirect) { break; } if (needle.targetRelay == null) { break; } if (needle.targetRelay.vessel == this.vessel || needle == this.moduleRef) { isCircular = true; break; } // Avoid infinite loops when we're not catching things right. if (iterCount > FlightGlobals.Vessels.Count) { this.LogError( "iterCount exceeded while checking for circular network; assuming it is circular" + "\n\tneedle={0}" + "\n\tthis.moduleRef={1}", needle == null ? "null" : string.Format( "{0}, needle.KerbinDirect={1}, needle.targetRelay={2}", needle, needle.KerbinDirect, needle.targetRelay == null ? "null" : string.Format( "{0}\n\tneedle.targetRelay.vessel={1}", needle.targetRelay, needle.targetRelay.vessel == null ? "null" : needle.targetRelay.vessel.vesselName ) ), this.moduleRef == null ? "null" : this.moduleRef.ToString() ); isCircular = true; break; } needle = needle.targetRelay; } if (!isCircular) { cheapestRelayRate = potentialRelayRate; this.nearestRelay = potentialBestRelay; log.AppendFormat("\n\t{0}: found new cheapest relay {1} ({2} EC/MiT)", this.ToString(), this.nearestRelay.ToString(), cheapestRelayRate ); } else { log.AppendFormat("\n\t\t...connection to {0} would result in a circular network, skipping", potentialBestRelay ); } #if BENCH totalCircularVesselTicks = performanceTimer.ElapsedTicks - startCircularVesselTicks; if (totalCircularVesselTicks > slowestCircularVesselTicks) { slowestCircularVesselName = vessel.vesselName; slowestCircularVesselTicks = totalCircularVesselTicks; } #endif } } #if BENCH totalVesselLoopTicks = performanceTimer.ElapsedTicks - startVesselLoopTicks; #endif CelestialBody bodyOccludingKerbin = null; log.AppendFormat("\n{0} ({1}): Search done, figuring status.", this.ToString(), this.GetType().Name); log.AppendFormat( "\n{0}: nearestRelay={1} ({2})), bestOccludedRelay={3} ({4}), kerbinRelayRate={5} EC/MiT)", this, this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), cheapestRelayRate, this.bestOccludedRelay == null ? "null" : this.bestOccludedRelay.ToString(), cheapestOccludedRelayRate, kerbinRelayRate ); #if BENCH startKerbinLOSTicks = this.performanceTimer.ElapsedTicks; #endif // If we don't have LOS to Kerbin, focus on relays if ( ARConfiguration.RequireLineOfSight && !this.vessel.hasLineOfSightTo(Kerbin, out bodyOccludingKerbin, ARConfiguration.RadiusRatio) ) { #if BENCH totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks; #endif log.AppendFormat("\n\tKerbin LOS is blocked by {0}.", bodyOccludingKerbin.bodyName); // If we're in range of the "nearest" (actually cheapest) relay, use it. if (this.IsInRangeOf(this.nearestRelay)) { log.AppendFormat("\n\t\tCan transmit to nearby relay {0}).", this.nearestRelay == null ? "null" : this.nearestRelay.ToString() ); this.KerbinDirect = false; this.canTransmit = true; this.targetRelay = this.nearestRelay; } // If this isn't true, we can't transmit, but pick a second best of bestOccludedRelay and Kerbin anyway else { log.AppendFormat("\n\t\tCan't transmit to nearby relay {0}.", this.nearestRelay == null ? "null" : this.nearestRelay.ToString() ); this.canTransmit = false; // If the best occluded relay is cheaper than Kerbin, check it against the nearest relay. // Since cheapestOccludedRelayRate is infinity if there are no occluded relays, this is safe if (cheapestOccludedRelayRate < kerbinRelayRate) { log.AppendFormat("\n\t\t\tBest occluded relay is cheaper than Kerbin ({0} < {1})", cheapestOccludedRelayRate, kerbinRelayRate); this.KerbinDirect = false; // If the nearest relay is cheaper than the best occluded relay, pick it. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. if (cheapestRelayRate < cheapestOccludedRelayRate) { log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.", cheapestRelayRate, cheapestOccludedRelayRate); this.targetRelay = this.nearestRelay; this.firstOccludingBody = null; } // Otherwise, target the best occluded relay. else { log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.", cheapestRelayRate, cheapestOccludedRelayRate); this.targetRelay = bestOccludedRelay; this.firstOccludingBody = bodyOccludingBestOccludedRelay; } } // Otherwise, check Kerbin against the "nearest" (cheapest) relay. else { log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})", cheapestOccludedRelayRate, kerbinRelayRate); // If the "nearest" (cheapest) relay is cheaper than Kerbin, pick it. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. if (cheapestRelayRate < kerbinRelayRate) { log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.", cheapestRelayRate, kerbinRelayRate); // Since we have LOS, blank the first occluding body. this.firstOccludingBody = null; this.KerbinDirect = false; this.targetRelay = this.nearestRelay; } // Otherwise, pick Kerbin. else { log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.", cheapestRelayRate, kerbinRelayRate); this.KerbinDirect = true; this.firstOccludingBody = bodyOccludingKerbin; this.targetRelay = null; } } } } // If we do have LOS to Kerbin, try to prefer the closest of nearestRelay and Kerbin else { #if BENCH totalKerbinLOSTicks = this.performanceTimer.ElapsedTicks - startKerbinLOSTicks; #endif log.AppendFormat("\n\tKerbin is in LOS."); // If the nearest relay is in range, we can transmit. if (this.IsInRangeOf(this.nearestRelay)) { log.AppendFormat("\n\t\tCan transmit to nearby relay {0} (in range).", this.nearestRelay == null ? "null" : this.nearestRelay.ToString() ); this.canTransmit = true; // If the nearestRelay is closer than Kerbin, use it. if (cheapestRelayRate < kerbinRelayRate) { log.AppendFormat("\n\t\t\tPicking relay {0} over Kerbin ({1} < {2}).", this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), cheapestRelayRate, kerbinRelayRate); this.KerbinDirect = false; this.targetRelay = this.nearestRelay; } // Otherwise, Kerbin is closer, so use it. else { log.AppendFormat("\n\t\t\tBut picking Kerbin over nearby relay {0} ({1} >= {2}).", this.nearestRelay == null ? "null" : this.nearestRelay.ToString(), cheapestRelayRate, kerbinRelayRate); this.KerbinDirect = true; this.targetRelay = null; } } // If the nearest relay is out of range, we still need to check on Kerbin. else { log.AppendFormat("\n\t\tCheapest relay {0} is out of range.", this.nearestRelay == null ? "null" : this.nearestRelay.ToString() ); // If Kerbin is in range, use it. if (this.IsInRangeOf(Kerbin)) { log.AppendFormat("\n\t\t\tCan transmit to Kerbin (in range)."); this.canTransmit = true; this.KerbinDirect = true; this.targetRelay = null; } // If Kerbin is out of range and the nearest relay is out of range, pick a second best between // Kerbin and bestOccludedRelay else { log.AppendFormat("\n\t\t\tCan't transmit to Kerbin (out of range)."); this.canTransmit = false; // If the best occluded relay is cheaper than Kerbin, check it against the nearest relay. // Since bestOccludedSqrDistance is infinity if there are no occluded relays, this is safe if (cheapestOccludedRelayRate < kerbinRelayRate) { log.AppendFormat("\n\t\t\tBest occluded relay is closer than Kerbin ({0} < {1})", cheapestOccludedRelayRate, kerbinRelayRate); this.KerbinDirect = false; // If the nearest relay is closer than the best occluded relay, pick it. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. if (cheapestRelayRate < cheapestOccludedRelayRate) { log.AppendFormat("\n\t\t\t\t...but the cheapest relay is cheaper ({0} < {1}), so picking it.", cheapestRelayRate, cheapestOccludedRelayRate); this.targetRelay = this.nearestRelay; this.firstOccludingBody = null; } // Otherwise, target the best occluded relay. else { log.AppendFormat("\n\t\t\t\t...and cheaper than the cheapest relay ({0} >= {1}), so picking it.", cheapestRelayRate, cheapestOccludedRelayRate); this.targetRelay = bestOccludedRelay; this.firstOccludingBody = bodyOccludingBestOccludedRelay; } } // Otherwise, check Kerbin against the nearest relay. // Since we have LOS, blank the first occluding body. else { log.AppendFormat("\n\t\t\tKerbin is cheaper than the best occluded relay ({0} >= {1})", cheapestOccludedRelayRate, kerbinRelayRate); this.firstOccludingBody = null; // If the nearest relay is cheaper than Kerbin, pick it. // Since cheapestRelayRate is infinity if there are no nearby relays, this is safe. if (cheapestRelayRate < kerbinRelayRate) { log.AppendFormat("\n\t\t\t\t...but the nearest relay is cheaper ({0} < {1}), so picking it.", cheapestRelayRate, kerbinRelayRate); this.KerbinDirect = false; this.targetRelay = this.nearestRelay; } // Otherwise, pick Kerbin. else { log.AppendFormat("\n\t\t\t\t...and cheaper than the nearest relay ({0} >= {1}), so picking it.", cheapestRelayRate, kerbinRelayRate); this.KerbinDirect = true; this.targetRelay = null; } } } } } if (ARConfiguration.UseAdditiveRanges) { if (this.KerbinDirect) { this.NominalLinkSqrDistance = this.nominalTransmitDistance * ARConfiguration.KerbinNominalRange; this.MaximumLinkSqrDistance = this.maxTransmitDistance * ARConfiguration.KerbinRelayRange; } else { this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.targetRelay.nominalTransmitDistance; this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.targetRelay.maxTransmitDistance; } } else { this.NominalLinkSqrDistance = this.nominalTransmitDistance * this.nominalTransmitDistance; this.MaximumLinkSqrDistance = this.maxTransmitDistance * this.maxTransmitDistance; } if (this.canTransmit) { if (this.CurrentLinkSqrDistance < this.NominalLinkSqrDistance) { this.LinkStatus = ConnectionStatus.Optimal; } else { this.LinkStatus = ConnectionStatus.Suboptimal; } } else { this.LinkStatus = ConnectionStatus.None; } #if BENCH statusResolutionTicks = performanceTimer.ElapsedTicks - startKerbinLOSTicks - totalKerbinLOSTicks; #endif log.AppendFormat("\n{0}: Target search and status determination complete.", this.ToString()); #if DEBUG } catch (Exception ex) { log.AppendFormat("\nCaught {0}: {1}\n{2}", ex.GetType().FullName, ex.ToString(), ex.StackTrace); #if QUIT_ON_EXCEPTION UnityEngine.Application.Quit(); #endif } finally { #endif log.Print(false); #if DEBUG } #endif #if BENCH AntennaRelay.searchTimer += (ulong)this.performanceTimer.ElapsedTicks; AntennaRelay.searchCount++; this.performanceTimer.Stop(); double averageSearchTime = (double)AntennaRelay.searchTimer / (double)AntennaRelay.searchCount; if (AntennaRelay.searchCount >= 8000u / (ulong)ARConfiguration.UpdateDelay) { AntennaRelay.searchCount = 0u; AntennaRelay.searchTimer = 0u; AntennaRelay.averager.AddItem(averageSearchTime); AntennaRelay.doubleAverageTime = (long)(AntennaRelay.averager.Average * 2d); } if (this.performanceTimer.ElapsedTicks > AntennaRelay.doubleAverageTime) { System.Text.StringBuilder sb = Tools.GetStringBuilder(); sb.AppendFormat(Tools.SIFormatter, "[AntennaRelay] FindNearestRelay search for {0}" + " took significantly longer than average ({1:S3}s vs {2:S3}s)", this.ToString(), (double)this.performanceTimer.ElapsedTicks / (double)System.Diagnostics.Stopwatch.Frequency, (double)AntennaRelay.averager.Average / (double)System.Diagnostics.Stopwatch.Frequency ); sb.AppendFormat(Tools.SIFormatter, "\n\tVessel loop time: {0:S3}s", (double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency ); sb.AppendFormat(Tools.SIFormatter, "\n\t\tAverage vessel time for each of {1} vessels: {0:S3}s", (double)totalVesselLoopTicks / (double)System.Diagnostics.Stopwatch.Frequency / (double)usefulVesselCount, usefulVesselCount ); sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest vessel LOS check: {0:S3}s to {1}", (double)slowestLOSVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency, slowestLOSVesselName ); sb.AppendFormat(Tools.SIFormatter, "\n\t\tSlowest circular relay check: {0:S3}s for {1}", (double)slowestCircularVesselTicks / (double)System.Diagnostics.Stopwatch.Frequency, slowestCircularVesselName ); sb.AppendFormat(Tools.SIFormatter, "\n\tKerbin LOS check: {0:S3}s", (double)totalKerbinLOSTicks / (double)System.Diagnostics.Stopwatch.Frequency ); sb.AppendFormat(Tools.SIFormatter, "\n\tStatus resolution check: {0:S3}s", (double)statusResolutionTicks / (double)System.Diagnostics.Stopwatch.Frequency ); // sb.AppendFormat(Tools.SIFormatter, "", start) this.LogWarning(sb.ToString()); Tools.PutStringBuilder(sb); } #endif }