VOID_DataLogger: Finish writing and close the file when we're destructed.
VOID_DataLogger: Finish writing and close the file when we're destructed.

--- a/IVOID_Module.cs
+++ b/IVOID_Module.cs
@@ -51,6 +51,7 @@
 	{
 		void Update();
 		void FixedUpdate();
+		void OnDestroy();
 	}
 
 	public interface IVOID_EditorModule : IVOID_Module {}

--- a/Properties/AssemblyInfo.cs
+++ b/Properties/AssemblyInfo.cs
@@ -39,7 +39,7 @@
 // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
 // The form "{Major}.{Minor}.*" will automatically update the build and revision,
 // and "{Major}.{Minor}.{Build}.*" will update just the revision.
-[assembly: AssemblyVersion("0.14.3.*")]
+[assembly: AssemblyVersion("0.15.*")]
 // The following attributes are used to specify the signing key for the assembly,
 // if desired. See the Mono documentation for more information about signing.
 //[assembly: AssemblyDelaySign(false)]

--- a/VOID_Core.cs
+++ b/VOID_Core.cs
@@ -121,7 +121,7 @@
 
 		protected bool GUIStylesLoaded = false;
 
-		protected CelestialBody _Kerbin;
+		protected CelestialBody _homeBody;
 
 		[AVOID_SaveValue("togglePower")]
 		public VOID_SaveValue<bool> togglePower = true;
@@ -243,19 +243,19 @@
 			private set;
 		}
 
-		public CelestialBody Kerbin
-		{
-			get
-			{
-				if (this._Kerbin == null)
-				{
-					if (FlightGlobals.Bodies != null)
+		public CelestialBody HomeBody
+		{
+			get
+			{
+				if (this._homeBody == null)
+				{
+					if (Planetarium.fetch != null)
 					{
-						this._Kerbin = FlightGlobals.Bodies.First(b => b.name == "Kerbin");
+						this._homeBody = Planetarium.fetch.Home;
 					}
 				}
 
-				return this._Kerbin;
+				return this._homeBody;
 			}
 		}
 
@@ -605,10 +605,23 @@
 				}
 			}
 
-			foreach (IVOID_BehaviorModule module in
-			         this._modules.OfType<IVOID_BehaviorModule>().Where(m => !m.GetType().IsAbstract))
-			{
-				module.FixedUpdate();
+			foreach (IVOID_Module module in this.Modules)
+			{
+				if (module is IVOID_BehaviorModule)
+				{
+					((IVOID_BehaviorModule)module).FixedUpdate();
+				}
+			}
+		}
+
+		public void OnDestroy()
+		{
+			foreach (IVOID_Module module in this.Modules)
+			{
+				if (module is IVOID_BehaviorModule)
+				{
+					((IVOID_BehaviorModule)module).OnDestroy();
+				}
 			}
 		}
 

--- a/VOID_Data.cs
+++ b/VOID_Data.cs
@@ -37,6 +37,15 @@
 {
 	public static class VOID_Data
 	{
+		private static Dictionary<int, IVOID_DataValue> dataValues = new Dictionary<int, IVOID_DataValue>();
+		public static Dictionary<int, IVOID_DataValue> DataValues
+		{
+			get
+			{
+				return dataValues;
+			}
+		}
+
 		#region Constants
 
 		private static double kerbinGee;
@@ -47,7 +56,7 @@
 			{
 				if (kerbinGee == default(double))
 				{
-					kerbinGee = core.Kerbin.gravParameter / (core.Kerbin.Radius * core.Kerbin.Radius);
+					kerbinGee = core.HomeBody.gravParameter / (core.HomeBody.Radius * core.HomeBody.Radius);
 				}
 
 				return kerbinGee;

--- a/VOID_DataLogger.cs
+++ b/VOID_DataLogger.cs
@@ -29,6 +29,7 @@
 using KSP;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Text;
 using ToadicusTools;
 using UnityEngine;
@@ -42,7 +43,7 @@
 		 * */
 		protected bool stopwatch1_running;
 
-		protected bool csv_logging;
+		protected bool _loggingActive;
 		protected bool first_write;
 
 		protected double stopwatch1;
@@ -51,15 +52,100 @@
 
 		protected float csv_log_interval;
 
-		protected double csvWriteTimer;
 		protected double csvCollectTimer;
+
+		protected System.Text.UTF8Encoding utf8Encoding;
+		protected FileStream _outputFile;
+
+		protected uint outstandingWrites;
 
 		protected List<string> csvList = new List<string>();
 
 		/*
 		 * Properties
 		 * */
-
+		// TODO: Add configurable or incremental file names.
+		protected bool loggingActive
+		{
+			get
+			{
+				return this._loggingActive;
+			}
+			set
+			{
+				if (value != this._loggingActive)
+				{
+					if (value)
+					{
+
+					}
+					else
+					{
+						if (this._outputFile != null)
+						{
+							Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+
+							logger.Append("CSV logging disabled, ");
+
+							logger.Append("disposing file.");
+							logger.Print();
+							this.outputFile.Dispose();
+							this._outputFile = null;
+						}
+					}
+
+					this._loggingActive = value;
+				}
+			}
+		}
+		protected string fileName
+		{
+			get
+			{
+				return KSP.IO.IOUtils.GetFilePathFor(
+					typeof(VOID_Core),
+					string.Format(
+						"{0}_{1}",
+						this.vessel.vesselName,
+						"data.csv"
+					),
+					null
+				);
+			}
+		}
+
+		protected FileStream outputFile
+		{
+			get
+			{
+				if (this._outputFile == null)
+				{
+					Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+					logger.AppendFormat("Initializing output file '{0}' with mode ", this.fileName);
+
+					if (File.Exists(this.fileName))
+					{
+						logger.Append("append");
+						this._outputFile = new FileStream(this.fileName, FileMode.Append, FileAccess.Write, FileShare.Write, 512, true);
+					}
+					else
+					{
+						logger.Append("create");
+						this._outputFile = new FileStream(this.fileName, FileMode.Create, FileAccess.Write, FileShare.Write, 512, true);
+
+						byte[] bom = utf8Encoding.GetPreamble();
+
+						logger.Append(" and writing preamble");
+						outputFile.Write(bom, 0, bom.Length);
+					}
+
+					logger.Append('.');
+					logger.Print();
+				}
+
+				return this._outputFile;
+			}
+		}
 
 		/*
 		 * Methods
@@ -70,14 +156,14 @@
 
 			this.stopwatch1_running = false;
 
-			this.csv_logging = false;
+			this.loggingActive = false;
 			this.first_write = true;
 
 			this.stopwatch1 = 0;
 			this.csv_log_interval_str = "0.5";
 
-			this.csvWriteTimer = 0;
 			this.csvCollectTimer = 0;
+			this.outstandingWrites = 0;
 
 			this.WindowPos.x = Screen.width - 520;
 			this.WindowPos.y = 85;
@@ -118,18 +204,19 @@
 
 			GUIStyle label_style = txt_white;
 			string log_label = "Inactive";
-			if (csv_logging && vessel.situation.ToString() == "PRELAUNCH")
+			if (loggingActive && vessel.situation.ToString() == "PRELAUNCH")
 			{
 				log_label = "Awaiting launch";
 				label_style = txt_yellow;
 			}
-			if (csv_logging && vessel.situation.ToString() != "PRELAUNCH")
+			if (loggingActive && vessel.situation.ToString() != "PRELAUNCH")
 			{
 				log_label = "Active";
 				label_style = txt_green;
 			}
 			GUILayout.BeginHorizontal(GUILayout.ExpandWidth(true));
-			csv_logging = GUILayout.Toggle(csv_logging, "Data logging: ", GUILayout.ExpandWidth(false));
+			this.loggingActive = GUILayout.Toggle(loggingActive, "Data logging: ", GUILayout.ExpandWidth(false));
+
 			GUILayout.Label(log_label, label_style, GUILayout.ExpandWidth(true));
 			GUILayout.EndHorizontal();
 
@@ -153,11 +240,10 @@
 		{
 			// CSV Logging
 			// from ISA MapSat
-			if (csv_logging)
+			if (loggingActive)
 			{
 				//data logging is on
 				//increment timers
-				csvWriteTimer += Time.deltaTime;
 				csvCollectTimer += Time.deltaTime;
 
 				if (csvCollectTimer >= csv_log_interval && vessel.situation != Vessel.Situations.PRELAUNCH)
@@ -167,22 +253,20 @@
 					line_to_csvList();  //write to the csv
 				}
 
-				if (csvList.Count != 0 && csvWriteTimer >= 15f)
+				if (csvList.Count > 0)
 				{
 					// csvList is not empty and interval between writings to file has elapsed
 					//write it
-					string[] csvData;
-					csvData = (string[])csvList.ToArray();
-					Innsewerants_writeData(csvData);
-					csvList.Clear();
-					csvWriteTimer = 0f;
+
+					// Tools.PostDebugMessage("")
+
+					this.AsyncWriteData();
 				}
 			}
 			else
 			{
 				//data logging is off
 				//reset any timers and clear anything from csvList
-				csvWriteTimer = 0f;
 				csvCollectTimer = 0f;
 				if (csvList.Count > 0) csvList.Clear();
 			}
@@ -195,14 +279,72 @@
 
 		public void FixedUpdate() {}
 
-		private void Innsewerants_writeData(string[] csvArray)
-		{
-			var efile = KSP.IO.File.AppendText<VOID_Core>(vessel.vesselName + "_data.csv", null);
-			foreach (string line in csvArray)
-			{
-				efile.Write(line);
-			}
-			efile.Close();
+		public void OnDestroy()
+		{
+			Tools.DebugLogger logger = Tools.DebugLogger.New(this);
+
+			logger.Append("Destroying...");
+
+			if (this.csvList.Count > 0)
+			{
+				logger.Append(" Writing final data...");
+				this.AsyncWriteData();
+			}
+
+			while (this.outstandingWrites > 0)
+			{
+				System.Threading.Thread.Sleep(10);
+			}
+
+			if (this._outputFile != null)
+			{
+				logger.Append(" Closing File...");
+				this.outputFile.Close();
+			}
+
+			logger.Append(" Done.");
+			logger.Print();
+		}
+
+		~VOID_DataLogger()
+		{
+			this.OnDestroy();
+		}
+
+		protected void AsyncWriteCallback(IAsyncResult result)
+		{
+			Tools.PostDebugMessage(this, "Got async callback, IsCompleted = {0}", result.IsCompleted);
+
+			this.outputFile.EndWrite(result);
+			this.outstandingWrites--;
+		}
+
+		private void AsyncWriteData()
+		{
+			if (this.utf8Encoding == null)
+			{
+				this.utf8Encoding = new System.Text.UTF8Encoding(true, true);
+			}
+
+			List<byte> bytes = new List<byte>();
+
+			foreach (string line in this.csvList)
+			{
+				byte[] lineBytes = utf8Encoding.GetBytes(line);
+				bytes.AddRange(lineBytes);
+			}
+
+			WriteState state = new WriteState();
+
+			state.bytes = bytes.ToArray();
+			state.stream = this.outputFile;
+
+			this.outstandingWrites++;
+			var writeCallback = new AsyncCallback(this.AsyncWriteCallback);
+
+			this.outputFile.BeginWrite(state.bytes, 0, state.bytes.Length, writeCallback, state);
+
+			this.csvList.Clear();
 		}
 
 		private void line_to_csvList()
@@ -212,93 +354,102 @@
 
 			StringBuilder line = new StringBuilder();
 
-			if (first_write && !KSP.IO.File.Exists<VOID_Core>(vessel.vesselName + "_data.csv", null))
+			if (first_write)
 			{
 				first_write = false;
 				line.Append(
-					"Mission Elapsed Time (s);" +
-					"Altitude ASL (m);" +
-					"Altitude above terrain (m);" +
-					"Surface Latitude (°);" +
-					"Surface Longitude (°);" +
-					"Orbital Velocity (m/s);" +
-					"Surface Velocity (m/s);" +
-					"Vertical Speed (m/s);" +
-					"Horizontal Speed (m/s);" +
-					"Gee Force (gees);" +
-					"Temperature (°C);" +
-					"Gravity (m/s²);" +
-					"Atmosphere Density (g/m³);" +
-					"Downrange Distance  (m);" +
+					"\"Mission Elapsed Time (s)\t\"," +
+					"\"Altitude ASL (m)\"," +
+					"\"Altitude above terrain (m)\"," +
+					"\"Surface Latitude (°)\"," +
+					"\"Surface Longitude (°)\"," +
+					"\"Orbital Velocity (m/s)\"," +
+					"\"Surface Velocity (m/s)\"," +
+					"\"Vertical Speed (m/s)\"," +
+					"\"Horizontal Speed (m/s)\"," +
+					"\"Gee Force (gees)\"," +
+					"\"Temperature (°C)\"," +
+					"\"Gravity (m/s²)\"," +
+					"\"Atmosphere Density (g/m³)\"," +
+					"\"Downrange Distance  (m)\"," +
 					"\n"
 				);
 			}
 
 			//Mission time
 			line.Append(vessel.missionTime.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//Altitude ASL
 			line.Append(vessel.orbit.altitude.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//Altitude (true)
 			double alt_true = vessel.orbit.altitude - vessel.terrainAltitude;
 			if (vessel.terrainAltitude < 0) alt_true = vessel.orbit.altitude;
 			line.Append(alt_true.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			// Surface Latitude
+			line.Append('"');
 			line.Append(VOID_Data.surfLatitude.Value);
-			line.Append(';');
+			line.Append('"');
+			line.Append(',');
 
 			// Surface Longitude
+			line.Append('"');
 			line.Append(VOID_Data.surfLongitude.Value);
-			line.Append(';');
+			line.Append('"');
+			line.Append(',');
 
 			//Orbital velocity
 			line.Append(vessel.orbit.vel.magnitude.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//surface velocity
 			line.Append(vessel.srf_velocity.magnitude.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//vertical speed
 			line.Append(vessel.verticalSpeed.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//horizontal speed
 			line.Append(vessel.horizontalSrfSpeed.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//gee force
 			line.Append(vessel.geeForce.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//temperature
 			line.Append(vessel.flightIntegrator.getExternalTemperature().ToString("F2"));
-			line.Append(';');
+			line.Append(',');
 
 			//gravity
 			double r_vessel = vessel.mainBody.Radius + vessel.mainBody.GetAltitude(vessel.findWorldCenterOfMass());
 			double g_vessel = (VOID_Core.Constant_G * vessel.mainBody.Mass) / (r_vessel * r_vessel);
 			line.Append(g_vessel.ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			//atm density
 			line.Append((vessel.atmDensity * 1000).ToString("F3"));
-			line.Append(';');
+			line.Append(',');
 
 			// Downrange Distance
 			line.Append((VOID_Data.downrangeDistance.Value.ToString("G3")));
-			line.Append(';');
 
 			line.Append('\n');
 
 			csvList.Add(line.ToString());
 
 			csvCollectTimer = 0f;
+		}
+
+		private class WriteState
+		{
+			public byte[] bytes;
+			public FileStream stream;
 		}
 	}
 }

--- a/VOID_DataValue.cs
+++ b/VOID_DataValue.cs
@@ -34,6 +34,10 @@
 {
 	public interface IVOID_DataValue
 	{
+		string Label { get; }
+		string Units { get; }
+		object Value { get; }
+
 		void Refresh();
 		string ValueUnitString();
 		void DoGUIHorizontal();
@@ -64,6 +68,14 @@
 		 * */
 		public string Label { get; protected set; }
 		public string Units { get; protected set; }
+
+		object IVOID_DataValue.Value
+		{
+			get
+			{
+				return (object)this.Value;
+			}
+		}
 
 		public T Value
 		{
@@ -90,6 +102,8 @@
 			this.Units = Units;
 			this.ValueFunc = ValueFunc;
 			this.lastUpdate = 0;
+
+			VOID_Data.DataValues[this.GetHashCode()] = this;
 		}
 
 		public void Refresh()
@@ -115,6 +129,21 @@
 			GUILayout.FlexibleSpace ();
 			GUILayout.Label (this.ValueUnitString(), GUILayout.ExpandWidth (false));
 			GUILayout.EndHorizontal ();
+		}
+
+		public override int GetHashCode()
+		{
+			int hash;
+			unchecked
+			{
+				hash = 79999;
+
+				hash = hash * 104399 + this.Label.GetHashCode();
+				hash = hash * 104399 + this.ValueFunc.GetHashCode();
+				hash = hash * 104399 + this.Units.GetHashCode();
+			}
+
+			return hash;
 		}
 
 		public override string ToString()

--- a/VOID_HUD.cs
+++ b/VOID_HUD.cs
@@ -191,7 +191,7 @@
 				);
 
 				if (
-					this.core.vessel.mainBody == this.core.Kerbin &&
+					this.core.vessel.mainBody == this.core.HomeBody &&
 					(
 						this.core.vessel.situation == Vessel.Situations.FLYING ||
 						this.core.vessel.situation == Vessel.Situations.SUB_ORBITAL ||

--- a/VOID_StageInfo.cs
+++ b/VOID_StageInfo.cs
@@ -20,6 +20,7 @@
 		private Table.Column<int> stageNumberCol;
 		private Table.Column<double> stageDeltaVCol;
 		private Table.Column<double> stageTotalDVCol;
+		private Table.Column<double> stageInvertDVCol;
 		private Table.Column<double> stageMassCol;
 		private Table.Column<double> stageTotalMassCol;
 		private Table.Column<double> stageThrustCol;
@@ -51,27 +52,31 @@
 			this.stageNumberCol = new Table.Column<int>("Stage", 40f);
 			this.stageTable.Add(this.stageNumberCol);
 
-			this.stageDeltaVCol = new Table.Column<double>("DeltaV [m/s]", 80f);
+			this.stageDeltaVCol = new Table.Column<double>("DeltaV [m/s]", 60f);
 			this.stageDeltaVCol.Format = "S2";
 			this.stageTable.Add(this.stageDeltaVCol);
 
-			this.stageTotalDVCol = new Table.Column<double>("Total ΔV [m/s]", 80f);
+			this.stageTotalDVCol = new Table.Column<double>("Total ΔV [m/s]", 60f);
 			this.stageTotalDVCol.Format = "S2";
 			this.stageTable.Add(this.stageTotalDVCol);
 
-			this.stageMassCol = new Table.Column<double>("Mass [Mg]", 80f);
+			this.stageInvertDVCol = new Table.Column<double>("Invert ΔV [m/s]", 60f);
+			this.stageInvertDVCol.Format = "S2";
+			this.stageTable.Add(this.stageInvertDVCol);
+
+			this.stageMassCol = new Table.Column<double>("Mass [Mg]", 60f);
 			this.stageMassCol.Format = "#.#";
 			this.stageTable.Add(this.stageMassCol);
 
-			this.stageTotalMassCol = new Table.Column<double>("Total Mass [Mg]", 80f);
+			this.stageTotalMassCol = new Table.Column<double>("Total [Mg]", 60f);
 			this.stageTotalMassCol.Format = "#.#";
 			this.stageTable.Add(this.stageTotalMassCol);
 
-			this.stageThrustCol = new Table.Column<double>("Thrust [N]", 80f);
+			this.stageThrustCol = new Table.Column<double>("Thrust [N]", 60f);
 			this.stageThrustCol.Format = "S2";
 			this.stageTable.Add(this.stageThrustCol);
 
-			this.stageTWRCol = new Table.Column<double>("T/W Ratio", 80f);
+			this.stageTWRCol = new Table.Column<double>("T/W Ratio", 60f);
 			this.stageTWRCol.Format = "#.#";
 			this.stageTable.Add(this.stageTWRCol);
 		}
@@ -127,12 +132,13 @@
 
 				this.stageDeltaVCol.Add(stage.deltaV);
 				this.stageTotalDVCol.Add(stage.totalDeltaV);
+				this.stageInvertDVCol.Add(stage.inverseTotalDeltaV);
 
 				this.stageMassCol.Add(stage.mass);
 				this.stageTotalMassCol.Add(stage.totalMass);      
 
 				this.stageThrustCol.Add(stage.thrust * 1000f);
-				this.stageTWRCol.Add(stage.thrustToWeight / (this.selectedBody ?? core.Kerbin).GeeASL);
+				this.stageTWRCol.Add(stage.thrustToWeight / (this.selectedBody ?? core.HomeBody).GeeASL);
 			}
 
 			this.stageTable.Render();
@@ -146,7 +152,7 @@
 					this.bodyIdx--;
 				}
 
-				this.showBodyList = GUILayout.Toggle(this.showBodyList, (this.selectedBody ?? core.Kerbin).bodyName, GUI.skin.button);
+				this.showBodyList = GUILayout.Toggle(this.showBodyList, (this.selectedBody ?? core.HomeBody).bodyName, GUI.skin.button);
 				Rect bodyButtonPos = GUILayoutUtility.GetLastRect();
 
 				if (Event.current.type == EventType.Repaint)

--- a/VOID_Styles.cs
+++ b/VOID_Styles.cs
@@ -28,14 +28,6 @@
 
 using System;
 using UnityEngine;
-
-#if COMMENT
-this.LabelStyles["link"] = new GUIStyle(GUI.skin.label);
-this.LabelStyles["center"] = new GUIStyle(GUI.skin.label);
-this.LabelStyles["center_bold"] = new GUIStyle(GUI.skin.label);
-this.LabelStyles["right"] = new GUIStyle(GUI.skin.label);
-labelRed = new GUIStyle(GUI.skin.label);
-#endif
 
 namespace VOID
 {