ModuleLimitedDataTransmitter: Changes to TransmitData to hopefully improve performance when transmitting directly from an experiment dialog.
[AntennaRange.git] / ModuleLimitedDataTransmitter.cs
blob:a/ModuleLimitedDataTransmitter.cs -> blob:b/ModuleLimitedDataTransmitter.cs
--- a/ModuleLimitedDataTransmitter.cs
+++ b/ModuleLimitedDataTransmitter.cs
@@ -1,25 +1,37 @@
-// AntennaRange © 2014 toadicus
+// AntennaRange
 //
-// AntennaRange provides incentive and requirements for the use of the various antenna parts.
-// Nominally, the breakdown is as follows:
+// ModuleLimitedDataTransmitter.cs
 //
-//     Communotron 16 - Suitable up to Kerbalsynchronous Orbit
-//     Comms DTS-M1 - Suitable throughout the Kerbin subsystem
-//     Communotron 88-88 - Suitable throughout the Kerbol system.
+// Copyright © 2014, toadicus
+// All rights reserved.
 //
-// This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. To view a
-// copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
 //
-// This software uses the ModuleManager library © 2013 ialdabaoth, used under a Creative Commons Attribution-ShareAlike
-// 3.0 Uported License.
+// 1. Redistributions of source code must retain the above copyright notice,
+//    this list of conditions and the following disclaimer.
 //
-// This software uses code from the MuMechLib library, © 2013 r4m0n, used under the GNU GPL version 3.
-
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation and/or other
+//    materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be used
+//    to endorse or promote products derived from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+using KSP;
 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
-using KSP;
+using ToadicusTools;
 using UnityEngine;
 
 namespace AntennaRange
@@ -62,6 +74,12 @@
 		[KSPField(isPersistant = false)]
 		public float nominalRange;
 
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Status")]
+		public string UIrelayStatus;
+
+		[KSPField(isPersistant = false, guiActive = true, guiName = "Relay")]
+		public string UIrelayTarget;
+
 		[KSPField(isPersistant = false, guiActive = true, guiName = "Transmission Distance")]
 		public string UItransmitDistance;
 
@@ -82,6 +100,16 @@
 		// The multipler on packetSize that defines the maximum data bandwidth of the antenna.
 		[KSPField(isPersistant = false)]
 		public float maxDataFactor;
+
+		[KSPField(
+			isPersistant = true,
+			guiName = "Packet Throttle",
+			guiUnits = "%",
+			guiActive = true,
+			guiActiveEditor = false
+		)]
+		[UI_FloatRange(maxValue = 100f, minValue = 2.5f, stepIncrement = 2.5f)]
+		public float packetThrottle;
 
 		protected bool actionUIUpdate;
 
@@ -93,25 +121,72 @@
 		{
 			get
 			{
-				return base.vessel;
-			}
-		}
-
-		// Returns the distance to the nearest relay or Kerbin, whichever is closer.
+				if (base.vessel != null)
+				{
+					return base.vessel;
+				}
+				else if (this.part != null)
+				{
+					return this.part.vessel;
+				}
+
+				else
+				{
+					return null;
+				}
+			}
+		}
+
+		public IAntennaRelay targetRelay
+		{
+			get
+			{
+				if (this.relay == null)
+				{
+					return null;
+				}
+
+				return this.relay.targetRelay;
+			}
+		}
+
+		// Returns the distance to the target relay or Kerbin, whichever is closer.
 		public double transmitDistance
 		{
 			get
 			{
+				if (this.relay == null)
+				{
+					return double.PositiveInfinity;
+				}
+
 				return this.relay.transmitDistance;
 			}
 		}
 
+		public double nominalTransmitDistance
+		{
+			get
+			{
+				return this.nominalRange;
+			}
+		}
+
 		// Returns the maximum distance this module can transmit
-		public float maxTransmitDistance
-		{
-			get
-			{
-				return Mathf.Sqrt (this.maxPowerFactor) * this.nominalRange;
+		public double maxTransmitDistance
+		{
+			get
+			{
+				// TODO: Cache this in a way that doesn't break everything.
+				return Math.Sqrt(this.maxPowerFactor) * this.nominalRange;
+			}
+		}
+
+		public CelestialBody firstOccludingBody
+		{
+			get
+			{
+				return this.relay.firstOccludingBody;
 			}
 		}
 
@@ -179,12 +254,29 @@
 			}
 		}
 
-		// Reports whether this antenna has been checked as a viable relay already in the current FindNearestRelay.
-		public bool relayChecked
-		{
-			get
-			{
-				return this.relay.relayChecked;
+		public bool KerbinDirect
+		{
+			get
+			{
+				if (this.relay != null)
+				{
+					return this.relay.KerbinDirect;
+				}
+
+				return false;
+			}
+		}
+
+		public string Title
+		{
+			get
+			{
+				if (this.part != null && this.part.partInfo != null)
+				{
+					return this.part.partInfo.title;
+				}
+
+				return string.Empty;
 			}
 		}
 
@@ -195,34 +287,12 @@
 		public ModuleLimitedDataTransmitter () : base()
 		{
 			this.ErrorMsg = new ScreenMessage("", 4f, false, ScreenMessageStyle.UPPER_LEFT);
-		}
-
-		// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
-		public override void OnStart (StartState state)
-		{
-			base.OnStart (state);
-
-			if (state >= StartState.PreLaunch)
-			{
-				this.relay = new AntennaRelay(this);
-				this.relay.maxTransmitDistance = this.maxTransmitDistance;
-
-				this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
-
-				GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
-				GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
-			}
-		}
-
-		// When the module loads, fetch the Squad KSPFields from the base.  This is necessary in part because
-		// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
-		// work.
-		public override void OnLoad(ConfigNode node)
-		{
-			this.Fields.Load(node);
-			base.Fields.Load(node);
-
-			base.OnLoad (node);
+			this.packetThrottle = 100f;
+		}
+
+		public override void OnAwake()
+		{
+			base.OnAwake();
 
 			this._basepacketSize = base.packetSize;
 			this._basepacketResourceCost = base.packetResourceCost;
@@ -243,15 +313,40 @@
 			));
 		}
 
+		// At least once, when the module starts with a state on the launch pad or later, go find Kerbin.
+		public override void OnStart (StartState state)
+		{
+			base.OnStart (state);
+
+			if (state >= StartState.PreLaunch)
+			{
+				this.relay = new AntennaRelay(this);
+				this.relay.maxTransmitDistance = this.maxTransmitDistance;
+				this.relay.nominalTransmitDistance = this.nominalRange;
+
+				this.UImaxTransmitDistance = Tools.MuMech_ToSI(this.maxTransmitDistance) + "m";
+
+				GameEvents.onPartActionUICreate.Add(this.onPartActionUICreate);
+				GameEvents.onPartActionUIDismiss.Add(this.onPartActionUIDismiss);
+			}
+		}
+
+		// When the module loads, fetch the Squad KSPFields from the base.  This is necessary in part because
+		// overloading packetSize and packetResourceCostinto a property in ModuleLimitedDataTransmitter didn't
+		// work.
+		public override void OnLoad(ConfigNode node)
+		{
+			this.Fields.Load(node);
+			base.Fields.Load(node);
+
+			base.OnLoad (node);
+		}
+
 		// Post an error in the communication messages describing the reason transmission has failed.  Currently there
 		// is only one reason for this.
 		protected void PostCannotTransmitError()
 		{
-			string ErrorText = string.Format (
-				"Unable to transmit: out of range!  Maximum range = {0}m; Current range = {1}m.",
-				Tools.MuMech_ToSI((double)this.maxTransmitDistance, 2),
-				Tools.MuMech_ToSI((double)this.transmitDistance, 2)
-				);
+			string ErrorText = string.Intern("Unable to transmit: no visible receivers in range!");
 
 			this.ErrorMsg.message = string.Format(
 				"<color='#{0}{1}{2}{3}'><b>{4}</b></color>",
@@ -272,31 +367,53 @@
 		// transmission fails (see CanTransmit).
 		protected void PreTransmit_SetPacketResourceCost()
 		{
-			if (this.transmitDistance <= this.nominalRange)
+			if (ARConfiguration.FixedPowerCost || this.transmitDistance <= this.nominalRange)
 			{
 				base.packetResourceCost = this._basepacketResourceCost;
 			}
 			else
 			{
+				double rangeFactor = (this.transmitDistance / this.nominalRange);
+				rangeFactor *= rangeFactor;
+
 				base.packetResourceCost = this._basepacketResourceCost
-					* (float)Math.Pow (this.transmitDistance / this.nominalRange, 2);
-			}
+					* (float)rangeFactor;
+
+				Tools.PostDebugMessage(
+					this,
+					"Pretransmit: packet cost set to {0} before throttle (rangeFactor = {1}).",
+					base.packetResourceCost,
+					rangeFactor);
+			}
+
+			base.packetResourceCost *= this.packetThrottle / 100f;
 		}
 
 		// Before transmission, set packetSize.  Per above, packet size increases with the inverse square of
 		// distance.  packetSize maxes out at _basepacketSize * maxDataFactor.
 		protected void PreTransmit_SetPacketSize()
 		{
-			if (this.transmitDistance >= this.nominalRange)
+			if (!ARConfiguration.FixedPowerCost && this.transmitDistance >= this.nominalRange)
 			{
 				base.packetSize = this._basepacketSize;
 			}
 			else
 			{
+				double rangeFactor = (this.nominalRange / this.transmitDistance);
+				rangeFactor *= rangeFactor;
+
 				base.packetSize = Math.Min(
-					this._basepacketSize * (float)Math.Pow (this.nominalRange / this.transmitDistance, 2),
+					this._basepacketSize * (float)rangeFactor,
 					this._basepacketSize * this.maxDataFactor);
-			}
+
+				Tools.PostDebugMessage(
+					this,
+					"Pretransmit: packet size set to {0} before throttle (rangeFactor = {1}).",
+					base.packetSize,
+					rangeFactor);
+			}
+
+			base.packetSize *= this.packetThrottle / 100f;
 		}
 
 		// Override ModuleDataTransmitter.GetInfo to add nominal and maximum range to the VAB description.
@@ -311,6 +428,11 @@
 		// Override ModuleDataTransmitter.CanTransmit to return false when transmission is not possible.
 		public new bool CanTransmit()
 		{
+			if (this.part == null || this.relay == null)
+			{
+				return false;
+			}
+
 			PartStates partState = this.part.State;
 			if (partState == PartStates.DEAD || partState == PartStates.DEACTIVATED)
 			{
@@ -335,30 +457,81 @@
 
 			if (this.CanTransmit())
 			{
-				StringBuilder message = new StringBuilder();
-
-				message.Append("[");
-				message.Append(base.part.partInfo.title);
-				message.Append("]: ");
-
-				message.Append("Beginning transmission ");
-
-				if (this.relay.nearestRelay == null)
-				{
-					message.Append("directly to Kerbin.");
-				}
-				else
-				{
-					message.Append("via ");
-					message.Append(this.relay.nearestRelay);
-				}
-
-				ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
+				ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
 
 				base.TransmitData(dataQueue);
 			}
 			else
 			{
+				Tools.PostDebugMessage(this, "{0} unable to transmit during TransmitData.", this.part.partInfo.title);
+
+				var logger = Tools.DebugLogger.New(this);
+
+				foreach (ModuleScienceContainer	scienceContainer in this.vessel.getModulesOfType<ModuleScienceContainer>())
+				{
+					logger.AppendFormat("Checking ModuleScienceContainer in {0}\n",
+						scienceContainer.part.partInfo.title);
+
+					if (
+						scienceContainer.capacity != 0 &&
+						scienceContainer.GetScienceCount() >= scienceContainer.capacity
+					)
+					{
+						logger.Append("\tInsufficient capacity, skipping.\n");
+						continue;
+					}
+
+					List<ScienceData> dataStored = new List<ScienceData>();
+
+					foreach (ScienceData data in dataQueue)
+					{
+						if (!scienceContainer.allowRepeatedSubjects && scienceContainer.HasData(data))
+						{
+							logger.Append("\tAlready contains subject and repeated subjects not allowed, skipping.\n");
+							continue;
+						}
+
+						logger.AppendFormat("\tAcceptable, adding data on subject {0}... ", data.subjectID);
+						if (scienceContainer.AddData(data))
+						{
+							logger.Append("done, removing from queue.\n");
+
+							dataStored.Add(data);
+						}
+						#if DEBUG
+						else
+						{
+							logger.Append("failed.\n");
+						}
+						#endif
+					}
+
+					dataQueue.RemoveAll(i => dataStored.Contains(i));
+
+					logger.AppendFormat("\t{0} data left in queue.", dataQueue.Count);
+				}
+
+				logger.Print();
+
+				if (dataQueue.Count > 0)
+				{
+					StringBuilder msg = new StringBuilder();
+
+					msg.Append('[');
+					msg.Append(this.part.partInfo.title);
+					msg.AppendFormat("]: {0} data items could not be saved: no space available in data containers.\n");
+					msg.Append("Data to be discarded:\n");
+
+					foreach (ScienceData data in dataQueue)
+					{
+						msg.AppendFormat("\n{0}\n", data.title);
+					}
+
+					ScreenMessages.PostScreenMessage(msg.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+					Tools.PostDebugMessage(msg.ToString());
+				}
+
 				this.PostCannotTransmitError ();
 			}
 
@@ -384,41 +557,50 @@
 
 			if (this.CanTransmit())
 			{
-				StringBuilder message = new StringBuilder();
-
-				message.Append("[");
-				message.Append(base.part.partInfo.title);
-				message.Append("]: ");
-
-				message.Append("Beginning transmission ");
-
-				if (this.relay.nearestRelay == null)
-				{
-					message.Append("directly to Kerbin.");
+				ScreenMessages.PostScreenMessage(this.buildTransmitMessage(), 4f, ScreenMessageStyle.UPPER_LEFT);
+
+				base.StartTransmission();
+			}
+			else
+			{
+				this.PostCannotTransmitError ();
+			}
+		}
+
+		public void Update()
+		{
+			if (this.actionUIUpdate)
+			{
+				if (this.CanTransmit())
+				{
+					this.UIrelayStatus = "Connected";
+					this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
+					this.UIpacketSize = Tools.MuMech_ToSI(this.DataRate) + "MiT";
+					this.UIpacketCost = Tools.MuMech_ToSI(this.DataResourceCost) + "E";
 				}
 				else
 				{
-					message.Append("via ");
-					message.Append(this.relay.nearestRelay);
-				}
-
-				ScreenMessages.PostScreenMessage(message.ToString(), 4f, ScreenMessageStyle.UPPER_LEFT);
-
-				base.StartTransmission();
-			}
-			else
-			{
-				this.PostCannotTransmitError ();
-			}
-		}
-
-		public void Update()
-		{
-			if (this.actionUIUpdate)
-			{
-				this.UItransmitDistance = Tools.MuMech_ToSI(this.transmitDistance) + "m";
-				this.UIpacketSize = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataRate) + "MiT" : "N/A";
-				this.UIpacketCost = this.CanTransmit() ? Tools.MuMech_ToSI(this.DataResourceCost) + "E" : "N/A";
+					if (this.relay.firstOccludingBody == null)
+					{
+						this.UIrelayStatus = "Out of range";
+					}
+					else
+					{
+						this.UIrelayStatus = string.Format("Blocked by {0}", this.relay.firstOccludingBody.bodyName);
+					}
+					this.UImaxTransmitDistance = "N/A";
+					this.UIpacketSize = "N/A";
+					this.UIpacketCost = "N/A";
+				}
+
+				if (this.KerbinDirect)
+				{
+					this.UIrelayTarget = AntennaRelay.Kerbin.bodyName;
+				}
+				else
+				{
+					this.UIrelayTarget = this.targetRelay.ToString();
+				}
 			}
 		}
 
@@ -449,12 +631,45 @@
 				msg.Append(" on ");
 				msg.Append(vessel.vesselName);
 			}
+			else if (
+				this.part != null &&
+				this.part.protoPartSnapshot != null &&
+				this.part.protoPartSnapshot != null &&
+				this.part.protoPartSnapshot.pVesselRef != null
+			)
+			{
+				msg.Append(" on ");
+				msg.Append(this.part.protoPartSnapshot.pVesselRef.vesselName);
+			}
 
 			return msg.ToString();
 		}
 
+		private string buildTransmitMessage()
+		{
+			StringBuilder message = new StringBuilder();
+
+			message.Append("[");
+			message.Append(base.part.partInfo.title);
+			message.Append("]: ");
+
+			message.Append("Beginning transmission ");
+
+			if (this.KerbinDirect)
+			{
+				message.Append("directly to Kerbin.");
+			}
+			else
+			{
+				message.Append("via ");
+				message.Append(this.relay.targetRelay);
+			}
+
+			return message.ToString();
+		}
+
+		#if DEBUG
 		// When debugging, it's nice to have a button that just tells you everything.
-		#if DEBUG
 		[KSPEvent (guiName = "Show Debug Info", active = true, guiActive = true)]
 		public void DebugInfo()
 		{
@@ -474,8 +689,9 @@
 				"DataRate: {9}\n" +
 				"DataResourceCost: {10}\n" +
 				"TransmitterScore: {11}\n" +
-				"NearestRelay: {12}\n" +
-				"Vessel ID: {13}",
+				"targetRelay: {12}\n" +
+				"KerbinDirect: {13}\n" +
+				"Vessel ID: {14}",
 				this.name,
 				this._basepacketSize,
 				base.packetSize,
@@ -488,10 +704,12 @@
 				this.DataRate,
 				this.DataResourceCost,
 				ScienceUtil.GetTransmitterScore(this),
-				this.relay.FindNearestRelay(),
+				this.relay.targetRelay == null ? "null" : this.relay.targetRelay.ToString(),
+				this.KerbinDirect,
 				this.vessel.id
 				);
-			Tools.PostDebugMessage(msg);
+
+			Tools.PostLogMessage(msg);
 		}
 
 		[KSPEvent (guiName = "Dump Vessels", active = true, guiActive = true)]
@@ -508,7 +726,7 @@
 
 			Tools.PostDebugMessage(sb.ToString());
 		}
-
+		 
 		[KSPEvent (guiName = "Dump RelayDB", active = true, guiActive = true)]
 		public void DumpRelayDB()
 		{