Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mockingbirdnest/Principia
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 72923f553e45
Choose a base ref
...
head repository: mockingbirdnest/Principia
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 978a344841c0
Choose a head ref
  • 12 commits
  • 3 files changed
  • 2 contributors

Commits on Apr 7, 2019

  1. Copy the full SHA
    ddeffba View commit details
  2. A bit of useful stuff.

    pleroy committed Apr 7, 2019
    Copy the full SHA
    f4240e2 View commit details
  3. Something that vaguely works.

    pleroy committed Apr 7, 2019
    Copy the full SHA
    559703f View commit details
  4. Parsing from the text fields.

    pleroy committed Apr 7, 2019
    Copy the full SHA
    c2c7f0f View commit details
  5. Copy the full SHA
    7c11dcb View commit details

Commits on Apr 8, 2019

  1. Copy the full SHA
    6d9f605 View commit details
  2. Copy the full SHA
    5eb934e View commit details
  3. Copy the full SHA
    59f0030 View commit details
  4. Remove trace.

    pleroy committed Apr 8, 2019
    Copy the full SHA
    02fd220 View commit details

Commits on Apr 9, 2019

  1. After egg's review.

    pleroy committed Apr 9, 2019
    Copy the full SHA
    efeed91 View commit details
  2. After egg's 2nd review.

    pleroy committed Apr 9, 2019
    Copy the full SHA
    b381418 View commit details
  3. Merge pull request #2127 from pleroy/2121

    Add support for editing burn parameters using a text field
    pleroy authored Apr 9, 2019
    Copy the full SHA
    978a344 View commit details
Showing with 207 additions and 39 deletions.
  1. +34 −13 ksp_plugin_adapter/burn_editor.cs
  2. +106 −14 ksp_plugin_adapter/differential_slider.cs
  3. +67 −12 ksp_plugin_adapter/flight_planner.cs
47 changes: 34 additions & 13 deletions ksp_plugin_adapter/burn_editor.cs
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@ public BurnEditor(PrincipiaPluginAdapter adapter,
plugin_ = plugin;
vessel_ = vessel;
index_ = index;
previous_burn_ = previous_burn;
Δv_tangent_ =
new DifferentialSlider(label : "Δv tangent",
unit : "m / s",
@@ -35,21 +36,20 @@ public BurnEditor(PrincipiaPluginAdapter adapter,
log10_lower_rate : Log10ΔvLowerRate,
log10_upper_rate : Log10ΔvUpperRate,
text_colour : XKCDColors.PurplePink);
initial_time_ =
previous_coast_duration_ =
new DifferentialSlider(
label : "t initial",
unit : null,
log10_lower_rate : Log10TimeLowerRate,
log10_upper_rate : Log10TimeUpperRate,
// We cannot have a coast of length 0, so let's make it very
// short: that will be indistinguishable.
zero_value : 0.001,
min_value : 0,
max_value : double.PositiveInfinity,
formatter : value =>
FlightPlanner.FormatPositiveTimeSpan(
TimeSpan.FromSeconds(
value - (previous_burn?.final_time ??
plugin_.FlightPlanGetInitialTime(
vessel_.id.ToString())))));
initial_time_.value = initial_time;
formatter : FormatPreviousCoastDuration,
parser : TryParsePreviousCoastDuration);
previous_coast_duration_.value = initial_time - time_base;
reference_frame_selector_ = new ReferenceFrameSelector(
adapter_,
ReferenceFrameChanged,
@@ -113,7 +113,7 @@ public bool Render(bool enabled) {
changed |= Δv_tangent_.Render(enabled);
changed |= Δv_normal_.Render(enabled);
changed |= Δv_binormal_.Render(enabled);
changed |= initial_time_.Render(enabled);
changed |= previous_coast_duration_.Render(enabled);
UnityEngine.GUILayout.Label(
index_ == 0 ? "Time base: start of flight plan"
: $"Time base: end of manœuvre #{index_}",
@@ -143,7 +143,8 @@ public void Reset(NavigationManoeuvre manoeuvre) {
Δv_tangent_.value = burn.delta_v.x;
Δv_normal_.value = burn.delta_v.y;
Δv_binormal_.value = burn.delta_v.z;
initial_time_.value = burn.initial_time;
previous_coast_duration_.value =
burn.initial_time - time_base;
reference_frame_selector_.SetFrameParameters(burn.frame);
is_inertially_fixed_ = burn.is_inertially_fixed;
duration_ = manoeuvre.duration;
@@ -155,7 +156,7 @@ public Burn Burn() {
thrust_in_kilonewtons = thrust_in_kilonewtons_,
specific_impulse_in_seconds_g0 = specific_impulse_in_seconds_g0_,
frame = reference_frame_selector_.FrameParameters(),
initial_time = initial_time_.value,
initial_time = previous_coast_duration_.value + time_base,
delta_v = new XYZ{x = Δv_tangent_.value,
y = Δv_normal_.value,
z = Δv_binormal_.value},
@@ -255,13 +256,32 @@ private void UseTheForceLuke() {
specific_impulse_in_seconds_g0_ = range;
}

private double final_time => initial_time_.value + duration_;
internal string FormatPreviousCoastDuration(double value) {
return FlightPlanner.FormatPositiveTimeSpan(TimeSpan.FromSeconds(value));
}

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

private double time_base => previous_burn_?.final_time ??
plugin_.FlightPlanGetInitialTime(
vessel_.id.ToString());

private double final_time =>
time_base + previous_coast_duration_.value + duration_;

private bool is_inertially_fixed_;
private DifferentialSlider Δv_tangent_;
private DifferentialSlider Δv_normal_;
private DifferentialSlider Δv_binormal_;
private DifferentialSlider initial_time_;
private DifferentialSlider previous_coast_duration_;
private ReferenceFrameSelector reference_frame_selector_;
private double thrust_in_kilonewtons_;
private double specific_impulse_in_seconds_g0_;
@@ -279,6 +299,7 @@ private void UseTheForceLuke() {
private readonly IntPtr plugin_;
private readonly Vessel vessel_;
private readonly int index_;
private readonly BurnEditor previous_burn_;
private readonly PrincipiaPluginAdapter adapter_;

private bool changed_reference_frame_ = false;
120 changes: 106 additions & 14 deletions ksp_plugin_adapter/differential_slider.cs
Original file line number Diff line number Diff line change
@@ -6,31 +6,59 @@ namespace ksp_plugin_adapter {

internal class DifferentialSlider : ScalingRenderer {
public delegate string ValueFormatter(double value);
public delegate bool ValueParser(string s, out double value);

// Rates are in units of |value| per real-time second.
public DifferentialSlider(string label,
string unit,
double log10_lower_rate,
double log10_upper_rate,
double zero_value = 0,
double min_value = double.NegativeInfinity,
double max_value = double.PositiveInfinity,
ValueFormatter formatter = null,
ValueParser parser = null,
UnityEngine.Color? text_colour = null) {
label_ = label;
unit_ = unit;
if (formatter == null) {
format_ = v => v.ToString("#,0.000", Culture.culture);
formatter_ = v => v.ToString("#,0.000", Culture.culture);
} else {
format_ = formatter;
formatter_ = formatter;
}
if (parser == null) {
// As a special exemption we allow a comma as the decimal separator.
parser_ = (string s, out double value) =>
Double.TryParse(s.Replace(',', '.'),
NumberStyles.AllowDecimalPoint |
NumberStyles.AllowLeadingSign |
NumberStyles.AllowLeadingWhite |
NumberStyles.AllowThousands |
NumberStyles.AllowTrailingWhite,
Culture.culture.NumberFormat,
out value);
} else {
parser_ = parser;
}
log10_lower_rate_ = log10_lower_rate;
log10_upper_rate_ = log10_upper_rate;
zero_value_ = zero_value;
min_value_ = min_value;
max_value_ = max_value;
text_colour_ = text_colour;
}

public double value { get; set; }
public double value {
get {
return value_ ?? 0.0;
}
set {
if (!value_.HasValue || value_ != value) {
value_ = value;
formatted_value_ = formatter_(value_.Value);
}
}
}

// Renders the |DifferentialSlider|. Returns true if and only if |value|
// changed.
@@ -48,11 +76,60 @@ public bool Render(bool enabled) {
style : style);
}

{
if (enabled) {
var style = new UnityEngine.GUIStyle(UnityEngine.GUI.skin.textField);
style.alignment = UnityEngine.TextAnchor.MiddleRight;

// If the text is not syntactically correct, inform the user by drawing
// it in colour. We don't expect to see the "red" case as we should
// revert to a parseable value on exit.
if (!parser_(formatted_value_, out double v1)) {
style.focused.textColor = XKCDColors.Orange;
style.normal.textColor = XKCDColors.Red;
}

// Draw the text field and give it a name to be able to detect if it has
// focus.
String text_field_name = GetHashCode() + ":text_field";
UnityEngine.GUI.SetNextControlName(text_field_name);
formatted_value_ = UnityEngine.GUILayout.TextField(
text : formatted_value_,
style : style,
options : GUILayoutWidth(5 + (unit_ == null ? 2 : 0)));

// See if the user typed 'Return' in the field, or moved focus
// elsewhere, in which case we terminate text entry.
bool terminate_text_entry = false;
var current_event = UnityEngine.Event.current;
if (UnityEngine.Event.current.isKey &&
UnityEngine.Event.current.keyCode == UnityEngine.KeyCode.Return &&
UnityEngine.GUI.GetNameOfFocusedControl() == text_field_name) {
terminate_text_entry = true;
} else if (UnityEngine.GUI.GetNameOfFocusedControl() !=
text_field_name &&
formatted_value_ != formatter_(value_.Value)) {
terminate_text_entry = true;
}
if (terminate_text_entry) {
// Try to parse the input. If that fails, go back to the previous
// legal value.
if (parser_(formatted_value_, out double v2)) {
value_changed = true;
value = v2;
slider_position_ = 0;
// TODO(phl): If the value computed here is rejected by the C++, we
// revert to the previous value just as if there was a parsing error
// and this is not nice.
} else {
// Go back to the previous legal value.
formatted_value_ = formatter_(value_.Value);
}
}
} else {
var style = new UnityEngine.GUIStyle(UnityEngine.GUI.skin.label);
style.alignment = UnityEngine.TextAnchor.MiddleRight;
UnityEngine.GUILayout.Label(
text : format_(value),
text : formatted_value_,
style : style,
options : GUILayoutWidth(5 + (unit_ == null ? 2 : 0)));
}
@@ -72,15 +149,22 @@ public bool Render(bool enabled) {

if (UnityEngine.GUILayout.Button("0", GUILayoutWidth(1))) {
value_changed = true;
value = 0;
// Force a change of value so that any input is discarded.
value_ = null;
value = zero_value_;
}
if (slider_position_ != 0.0) {
value_changed = true;
// Moving the slider doesn't always cause a loss of focus so we
// terminate input if necessary.
if (parser_(formatted_value_, out double v)) {
value = v;
}
value += Math.Sign(slider_position_) *
Math.Pow(10, log10_lower_rate_ +
(log10_upper_rate_ - log10_lower_rate_) *
Math.Abs(slider_position_)) *
(DateTime.Now - last_time_).TotalSeconds;
Math.Pow(10, log10_lower_rate_ +
(log10_upper_rate_ - log10_lower_rate_) *
Math.Abs(slider_position_)) *
(DateTime.Now - last_time_).TotalSeconds;
value = Math.Min(Math.Max(min_value_, value), max_value_);
}
} else {
@@ -91,19 +175,27 @@ public bool Render(bool enabled) {
return value_changed;
}

private float slider_position_ = 0.0f;
private DateTime last_time_;

private readonly string label_;
private readonly string unit_;

private readonly double log10_lower_rate_ = -3;
private readonly double log10_upper_rate_ = 3.5;
private readonly double zero_value_;
private readonly double min_value_;
private readonly double max_value_;

private readonly ValueFormatter format_;
private readonly ValueFormatter formatter_;
private readonly ValueParser parser_;
private readonly UnityEngine.Color? text_colour_;

private float slider_position_ = 0.0f;
private DateTime last_time_;
// It is convenient for the value to be nullable so that we have a way to
// ensure that an assignment to it will actually have an effect and won't be
// optimized due to the existing value. This happens at initialization and
// during some events handling.
private double? value_;
private String formatted_value_;
}

} // namespace ksp_plugin_adapter
79 changes: 67 additions & 12 deletions ksp_plugin_adapter/flight_planner.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;


namespace principia {
@@ -21,11 +23,8 @@ public void Initialize(IntPtr plugin) {
log10_upper_rate : log10_time_upper_rate,
min_value : 10,
max_value : double.PositiveInfinity,
formatter : value =>
FormatPositiveTimeSpan(
TimeSpan.FromSeconds(
value - plugin_.FlightPlanGetInitialTime(
vessel_.id.ToString()))));
formatter : FormatPlanLength,
parser : TryParsePlanLength);
}

public void RenderButton() {
@@ -129,13 +128,16 @@ private void RenderFlightPlan(string vessel_guid) {
double actual_final_time =
plugin_.FlightPlanGetActualFinalTime(vessel_guid);

UnityEngine.GUILayout.TextField(
(final_time_.value == actual_final_time)
? ""
: "Timed out after " +
FormatPositiveTimeSpan(TimeSpan.FromSeconds(
actual_final_time -
plugin_.FlightPlanGetInitialTime(vessel_guid))));
var style = new UnityEngine.GUIStyle(UnityEngine.GUI.skin.textField);
style.normal.textColor = XKCDColors.Orange;
string message = "";
if (final_time_.value != actual_final_time) {
message = "Timed out after " +
FormatPositiveTimeSpan(TimeSpan.FromSeconds(
actual_final_time -
plugin_.FlightPlanGetInitialTime(vessel_guid)));
}
UnityEngine.GUILayout.TextField(message, style);

FlightPlanAdaptiveStepParameters parameters =
plugin_.FlightPlanGetAdaptiveStepParameters(vessel_guid);
@@ -358,6 +360,59 @@ 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$";
Regex regex = new Regex(pattern);
var match = Regex.Match(str, pattern);
if (!match.Success) {
return false;
}
string sign = match.Groups[1].Value;
string days = match.Groups[2].Value;
string hours = match.Groups[3].Value;
string minutes = match.Groups[4].Value;
string seconds = match.Groups[5].Value;
if (!Int32.TryParse(days, out int d) ||
!Int32.TryParse(hours, out int h) ||
!Int32.TryParse(minutes, out int min) ||
!Double.TryParse(seconds.Replace(',', '.'),
NumberStyles.AllowDecimalPoint |
NumberStyles.AllowThousands,
Culture.culture.NumberFormat,
out double s)) {
return false;
}
value = TimeSpan.FromDays((double)d / (GameSettings.KERBIN_TIME ? 4 : 1)) +
TimeSpan.FromHours(h) +
TimeSpan.FromMinutes(min) +
TimeSpan.FromSeconds(s);
if (sign.Length > 0 && sign[0] == '-') {
value = value.Negate();
}
return true;
}

internal string FormatPlanLength(double value) {
return FormatPositiveTimeSpan(TimeSpan.FromSeconds(
value -
plugin_.FlightPlanGetInitialTime(vessel_.id.ToString())));
}

internal bool TryParsePlanLength(string str, out double value) {
value = 0;
TimeSpan ts;
if (!TryParseTimeSpan(str, out ts)) {
return false;
}
value = ts.TotalSeconds +
plugin_.FlightPlanGetInitialTime(vessel_.id.ToString());
return true;
}

// Not owned.
private readonly PrincipiaPluginAdapter adapter_;
private IntPtr plugin_ = IntPtr.Zero;