Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a C# class PrincipiaTimeSpan to gather time formatting and parsing #2539

Merged
merged 6 commits into from
Apr 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions ksp_plugin_adapter/burn_editor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,20 @@ private void UseTheForceLuke() {
specific_impulse_in_seconds_g0_ = range;
}

internal string FormatPreviousCoastDuration(double value) {
return FlightPlanner.FormatPositiveTimeSpan(TimeSpan.FromSeconds(value));
internal string FormatPreviousCoastDuration(double seconds) {
return new PrincipiaTimeSpan(seconds).FormatPositive(
with_leading_zeroes: true,
with_seconds: true);
}

internal bool TryParsePreviousCoastDuration(string str, out double value) {
internal bool TryParsePreviousCoastDuration(string text, out double value) {
value = 0;
if (!FlightPlanner.TryParseTimeSpan(str, out TimeSpan ts)) {
if (!PrincipiaTimeSpan.TryParse(text,
with_seconds: true,
out PrincipiaTimeSpan ts)) {
return false;
}
value = ts.TotalSeconds;
value = ts.total_seconds;
return true;
}

Expand Down
110 changes: 26 additions & 84 deletions ksp_plugin_adapter/flight_planner.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;

namespace principia {
namespace ksp_plugin_adapter {
Expand Down Expand Up @@ -323,17 +321,16 @@ private void RenderUpcomingEvents() {
UnityEngine.GUILayout.Label("Upcoming manœuvre #" +
(first_future_manœuvre + 1) + ":");
UnityEngine.GUILayout.Label(
"Ignition " + FormatTimeSpan(TimeSpanFromSeconds(
current_time - manœuvre.burn.initial_time)),
"Ignition " +
FormatTimeSpan(current_time - manœuvre.burn.initial_time),
style : Style.RightAligned(UnityEngine.GUI.skin.label));
}
} else {
using (new UnityEngine.GUILayout.HorizontalScope()) {
UnityEngine.GUILayout.Label("Ongoing manœuvre #" +
(first_future_manœuvre + 1) + ":");
UnityEngine.GUILayout.Label(
"Cutoff " + FormatTimeSpan(TimeSpanFromSeconds(
current_time - manœuvre.final_time)),
"Cutoff " + FormatTimeSpan(current_time - manœuvre.final_time),
style : Style.RightAligned(UnityEngine.GUI.skin.label));
}
}
Expand All @@ -360,89 +357,33 @@ private void RenderUpcomingEvents() {
}
}

// A conversion function that swallows the overflow/NaN exceptions and
// saturates. Better than killing the UI.
internal static TimeSpan TimeSpanFromSeconds(double seconds) {
try {
return TimeSpan.FromSeconds(seconds);
} catch (OverflowException) {
if (seconds >= 0.0) {
return TimeSpan.MaxValue;
} else {
return TimeSpan.MinValue;
}
} catch (ArgumentException) {
return TimeSpan.Zero;
}
internal static string FormatPositiveTimeSpan(double seconds) {
return new PrincipiaTimeSpan(seconds).FormatPositive(
with_leading_zeroes: true,
with_seconds: true);
}

internal static string FormatPositiveTimeSpan(TimeSpan span) {
return (GameSettings.KERBIN_TIME
? (span.Days * 4 + span.Hours / 6).ToString("0000;0000") +
" d6 " + (span.Hours % 6).ToString("0;0") + " h "
: span.Days.ToString("000;000") + " d " +
span.Hours.ToString("00;00") + " h ") +
span.Minutes.ToString("00;00") + " min " +
(span.Seconds + span.Milliseconds / 1000m).ToString("00.0;00.0") +
" s";
internal static string FormatTimeSpan (double seconds) {
return new PrincipiaTimeSpan(seconds).Format(
with_leading_zeroes: true,
with_seconds: true);
}

internal static string FormatTimeSpan (TimeSpan span) {
return span.Ticks.ToString("+;-") + FormatPositiveTimeSpan(span);
}

internal static bool TryParseTimeSpan(string str, out TimeSpan value) {
value = TimeSpan.Zero;
// Using a technology that is customarily used to parse HTML.
string pattern = @"^[+]?\s*(\d+)\s*" +
(GameSettings.KERBIN_TIME ? "d6" : "d") +
@"\s*(\d+)\s*h\s*(\d+)\s*min\s*([0-9.,']+)\s*s$";
var regex = new Regex(pattern);
var match = regex.Match(str);
if (!match.Success) {
return false;
}
string days = match.Groups[1].Value;
string hours = match.Groups[2].Value;
string minutes = match.Groups[3].Value;
string seconds = match.Groups[4].Value;
if (!int.TryParse(days, out int d) ||
!int.TryParse(hours, out int h) ||
!int.TryParse(minutes, out int min) ||
!double.TryParse(seconds.Replace(',', '.'),
NumberStyles.AllowDecimalPoint |
NumberStyles.AllowThousands,
Culture.culture.NumberFormat,
out double s)) {
return false;
}
// Fail to parse if the user gives us overflowing input.
try {
value =
TimeSpan.FromDays((double)d / (GameSettings.KERBIN_TIME ? 4 : 1)) +
TimeSpan.FromHours(h) +
TimeSpan.FromMinutes(min) +
TimeSpan.FromSeconds(s);
return true;
} catch (OverflowException) {
return false;
} catch (ArgumentException) {
return false;
}
}

internal string FormatPlanLength(double value) {
return FormatPositiveTimeSpan(TimeSpanFromSeconds(
value - plugin.FlightPlanGetInitialTime(
predicted_vessel.id.ToString())));
return FormatPositiveTimeSpan(
value - plugin.FlightPlanGetInitialTime(
predicted_vessel.id.ToString()));
}

internal bool TryParsePlanLength(string str, out double value) {
internal bool TryParsePlanLength(string text, out double value) {
value = 0;
if (!TryParseTimeSpan(str, out TimeSpan ts)) {
if (!PrincipiaTimeSpan.TryParse(text,
with_seconds: true,
out PrincipiaTimeSpan ts)) {
return false;
}
value = ts.TotalSeconds +
value = ts.total_seconds +
plugin.FlightPlanGetInitialTime(predicted_vessel.id.ToString());
return true;
}
Expand Down Expand Up @@ -476,12 +417,13 @@ private string GetStatusMessage() {

string remedy_message = "changing the flight plan"; // Preceded by "Try".
string status_message = "computation failed"; // Preceded by "The".
string time_out_message =
timed_out ? " after " +
FormatPositiveTimeSpan(TimeSpanFromSeconds(
actual_final_time -
plugin.FlightPlanGetInitialTime(vessel_guid)))
: "";
string time_out_message = timed_out
? " after " +
FormatPositiveTimeSpan(
actual_final_time -
plugin.FlightPlanGetInitialTime(
vessel_guid))
: "";
if (status_.is_aborted()) {
status_message = "integrator reached the maximum number of steps" +
time_out_message;
Expand Down
1 change: 1 addition & 0 deletions ksp_plugin_adapter/ksp_plugin_adapter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
<Compile Include="source_location.cs" />
<Compile Include="style.cs" />
<Compile Include="mono_marshaler.cs" />
<Compile Include="time_span.cs" />
<Compile Include="utf8_marshaler.cs" />
<Compile Include="utf16_marshaler.cs" />
<Compile Include="window_renderer.cs" />
Expand Down
5 changes: 3 additions & 2 deletions ksp_plugin_adapter/map_node_pool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -262,8 +262,9 @@ private KSP.UI.Screens.Mapview.MapNode MakePoolNode() {
}
if (properties.object_type != MapObject.ObjectType.PatchTransition) {
caption.captionLine1 =
"T" + FlightPlanner.FormatTimeSpan(TimeSpan.FromSeconds(
Planetarium.GetUniversalTime() - properties.time));
"T" + new PrincipiaTimeSpan(
Planetarium.GetUniversalTime() - properties.time).
Format(with_leading_zeroes: false, with_seconds: true);
}
};
new_node.OnUpdatePosition +=
Expand Down
105 changes: 31 additions & 74 deletions ksp_plugin_adapter/orbit_analyser.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace principia {
namespace ksp_plugin_adapter {
Expand Down Expand Up @@ -95,39 +90,16 @@ public static string FormatEquatorialAngleInterval(this Interval interval,
// leading 0s on the days; optionally exclude seconds.
public static string FormatDuration(this double seconds,
bool show_seconds = true) {
if (double.IsNaN(seconds)) {
return seconds.ToString();
}
var span = TimeSpan.FromSeconds(seconds);
int days = GameSettings.KERBIN_TIME ? span.Days * 4 + span.Hours / 6
: span.Days;
int hours = GameSettings.KERBIN_TIME ? span.Hours % 6
: span.Hours;
var components = new List<string>();
const string nbsp = "\xA0";
if (days > 0) {
components.Add(GameSettings.KERBIN_TIME ? $"{days}{nbsp}d6"
: $"{days}{nbsp}d");
}
if (components.Count > 0 || hours > 0) {
components.Add(
GameSettings.KERBIN_TIME ? $"{hours:0}{nbsp}h"
: $"{hours:00}{nbsp}h");
}
if (components.Count > 0 || span.Minutes > 0 || !show_seconds) {
components.Add($"{span.Minutes:00}{nbsp}min");
}
if (show_seconds) {
components.Add($"{span.Seconds + span.Milliseconds / 1000m:00.0}{nbsp}s");
}
return string.Join(" ", components.ToArray());
return new PrincipiaTimeSpan(seconds).FormatPositive(
with_leading_zeroes: false,
with_seconds: show_seconds);
}

// Formats an angular frequency (passed in rad/s), in °/d or °/d6.
public static string FormatAngularFrequency(this double radians_per_second) {
const double degree = Math.PI / 180;
double day = GameSettings.KERBIN_TIME ? 6 * 60 * 60 : 24 * 60 * 60;
string day_unit = GameSettings.KERBIN_TIME ? "d6" : "d";
double day = PrincipiaTimeSpan.day_duration;
string day_unit = PrincipiaTimeSpan.day_symbol;
double degrees_per_day = radians_per_second / (degree / day);
return $"{degrees_per_day.FormatN(2)}°/{day_unit}";
}
Expand All @@ -136,38 +108,22 @@ public static string FormatAngularFrequency(this double radians_per_second) {
// seconds (they are irrelevant for a selector that shows durations much
// longer than a revolution).
public static string FormatMissionDuration(double seconds) {
var span = TimeSpan.FromSeconds(seconds);
return (GameSettings.KERBIN_TIME
? (span.Days * 4 + span.Hours / 6).ToString("0000") +
" d6 " + (span.Hours % 6).ToString("0") + " h "
: span.Days.ToString("000") + " d " +
span.Hours.ToString("00") + " h ") +
span.Minutes.ToString("00") + " min";
return new PrincipiaTimeSpan(seconds).FormatPositive(
with_leading_zeroes: true,
with_seconds: false);
}

public static bool TryParseMissionDuration(string str, out double value) {
value = 0;
// Using a technology that is customarily used to parse HTML.
string pattern = @"^[+]?\s*(\d+)\s*" +
(GameSettings.KERBIN_TIME ? "d6" : "d") +
@"\s*(\d+)\s*h\s*(\d+)\s*min$";
var regex = new Regex(pattern);
var match = regex.Match(str);
if (!match.Success) {
return false;
}
string days = match.Groups[1].Value;
string hours = match.Groups[2].Value;
string minutes = match.Groups[3].Value;
if (!int.TryParse(days, out int d) ||
!int.TryParse(hours, out int h) ||
!int.TryParse(minutes, out int min)) {
public static bool TryParseMissionDuration(string str,
out double seconds) {
seconds = 0;
if (PrincipiaTimeSpan.TryParse(str,
with_seconds: false,
out PrincipiaTimeSpan ts)) {
seconds = ts.total_seconds;
return true;
} else {
return false;
}
value = (TimeSpan.FromDays((double)d / (GameSettings.KERBIN_TIME ? 4 : 1)) +
TimeSpan.FromHours(h) +
TimeSpan.FromMinutes(min)).TotalSeconds;
return true;
}
}

Expand Down Expand Up @@ -403,19 +359,20 @@ private void LabeledField(string label, string value) {
private const string em_dash = "—";

private readonly PrincipiaPluginAdapter adapter_;
private DifferentialSlider mission_duration_ = new DifferentialSlider(
label : "Duration",
unit : null,
log10_lower_rate : 0,
log10_upper_rate : 7,
min_value : 10,
max_value : double.PositiveInfinity,
formatter : Formatters.FormatMissionDuration,
parser : Formatters.TryParseMissionDuration,
label_width : 2,
field_width : 5) {
value = 7 * 24 * 60 * 60
};
private readonly DifferentialSlider mission_duration_ =
new DifferentialSlider(
label : "Duration",
unit : null,
log10_lower_rate : 0,
log10_upper_rate : 7,
min_value : 10,
max_value : double.PositiveInfinity,
formatter : Formatters.FormatMissionDuration,
parser : Formatters.TryParseMissionDuration,
label_width : 2,
field_width : 5) {
value = 7 * 24 * 60 * 60
};
private bool autodetect_recurrence_ = true;
private int revolutions_per_cycle_ = 1;
private int days_per_cycle_ = 1;
Expand Down
Loading