Open gotmachine opened 7 years ago
First of all, thanks a lot for your continuous contributions to this project. I see that you are growing familiar with the codebase, and in general I like what you are doing here. I just added a couple comments in the changes about minor performance issues.
Now, you are probably aware I'm about to refactor/rewrite a lot of stuff. One of the potential things is the modifier, that I want to improve.
I already got a 'generic expression parser' done. Its very fast, and the only limitation is that it doesn't support operator precedence.
So I was thinking to introduce the concept of a 'modifier-expression'.
That way you can write modifier-expressions like:
modifier = max(env.temperature - 295, 5) - 5
to get temperature above survival range. Note how the actual settings of survival range just become a part of the modifier.
Or you could write a modifier-expression like:
modifier = Atmosphere.rate > 0
that will evaluate to 1.0 when something is pressurizing the habitat, and 0.0 otherwise.
Or this one:
modifier = Atmosphere.level > 0.9
that is 1.0 if pressure is ~90 kPA, and 0.0 otherwise.
This new 'modifier-expressions' concept is also going to be used in two other (awesome) new things that are coming: a new telemetry system where arbitrary modifier-expression readings are provided by parts/rules, and a new alert system where arbitrary modifier-expression conditions are checked by parts/rules.
The new telemetry system in particular is going to be relevant here. You have already understood the fundamental impossibility of providing the current planner analysis panels in a generic rule-driven framework. Therefore the current planner panels are a sort of 'hack', good enough but show its limitations as soon as the user start specifying rules very different than the ones I developed against. I hope the new telemetry system may completely replace the planner panels too.
This is all very early-design / experiments. I'm really interested in what you think about these proposed improvements. Best!
Thanks for the reviewing of my code. I'm a hobbyist programmer and while I usually succeed in making things work, I'm really struggling to find the most efficient/fast/clean way of doing it, so your remarks are super useful.
All this is meant to be an implementation proposal, not a real PR. It would require a testing / fixing / optimization pass as well as a rebasing on your dev branch.
The modifier change seems a good idea, indeed having to maintain duplicates of functional code for flight and editor (planner) situations is not ideal. My tough on this when I discovered how all this work is that ideally, the "Vessel" reference in the "vessel_info" class should be allowed to be null. Then all the functions used to calculate the "vessel_info" properties should be able to perform with the actual vessel being optional.
This is what I did for the Habitat static function that calculate atmospheric flux and use the root part skinTemp when loaded to account for reentry heat. This allow me to use the same function in planner and vessel_info:
double Habitat.atmo_flux(CelestialBody mainBody, double altitude, double surface, double env_temperature, Vessel v = null)
That allow me to use it like that in vessel_info :
Habitat.atmo_flux(v.mainBody, v.altitude, surface, temperature, v)
And like that in the planner :
Habitat.atmo_flux(env.body, env.altitude, surface, env.temperature)
Then in the function, to illustrate :
some_value = (v == null) ? fallback_value_for_planner : value_when_loaded;
Unless I misunderstand what you are proposing, it seems to me that this is what you are trying to do : an unified vessel variables cache that would be indifferent to the game context (editor/flight loaded/flight unloaded). If this happen and together with your (awesome) "modifier-expression" idea, this could indeed allow a config-driven planner (and in-flight monitor) UI system. Another thing that I was thinking is that processes and rules should be merged in a single system. Maybe this is too much but here is some prospective work for an unified system, using habitat thermal control as an example :
Rules
{
// a VesselValue is like a resource, excepted it isn't stored in a part but by vessel
// when docking, the resulting vessel get the sum of the two vessels amount & capacity
// the capacity modifier allow to determine factor to split the stored amount between the two vessels after undocking
VesselValue
{
name = neg_kjoule // this can be used like a resource in modifier-expression (amount/capacity/level...)
title = kJ // name of the value to display in the GUI
capacity = neg_kjoule_amount // modifier used to calculate max amount of VesselValue, if unspecified capacity = amount at all times
}
Modifier
{
name = neg_kjoule_amount
expression = env.volume * 100000.0 * 25.0 // min temperature is ideal temperature - 25 K
}
// heat losses from environment
ValueInput
{
name = heat losses // reference/id to use in the GUI
value = neg_kjoule // value to add
rate = 1.0
interval = 1.0
modifier = watt_flux_neg
}
// heat gains from environment
ValueOutput
{
name = heat gains
value = neg_kjoule
rate = 1.0
interval = 1.0
modifier = watt_flux_pos
}
// calculate net thermal flux (kW) for the habitat (real formula would more complex)
Modifier
{
name = net_flux
expression = ((5,67E-08 * env.surface * env.temperature * env.temperature * env.temperature * env.temperature) - (5,67E-08 * env.surface * 0.5 * env.temperature * env.temperature * env.temperature * env.temperature ) + (env.crew_count * 30.0)) * 0.001
// would be great to be able to do pow(env.temperature,4.0)
}
Modifier
{
name = watt_flux_pos
expression = (net_flux > 0.0) * net_flux
}
Modifier
{
name = watt_flux_neg
expression = (net_flux < 0.0) * net_flux * -1.0 // is abs(net_flux) possible ?
}
// used to determine the amount of ECLSS heater available, for use in the ProcessControler module
// no capacity specified : capacity is ignored (made equal to the amount at all times)
VesselValue
{
name = _heater // this can be used like a resource in modifier-expression (amount/capacity/level...)
title = ECLSS heater
}
// the heater should not remove more heat than the stored heat !
Modifier
{
name = heater_output
expression = min(neg_kjoule.amount, _heater.amount)
}
// ECLSS heater input
ResourceInput
{
name = heater
resource = ElectricCharge
rate = 1.0
modifier = heater_output
}
// ECLSS heater output
ValueInput
{
name = heater
value = neg_kjoule
rate = 1.0
interval = 1.0
modifier = heater_output
}
// a KerbalValue is like a resource, excepted it isn't stored in a part but by each Kerbal on the vessel
KerbalValue
{
name = kerbal_neg_temp // this can be used like a resource in modifier-expression (amount/capacity/level...)
capacity = kerbal_neg_temp_amount // modifier used to calculate the capacity
ksc_reset = true // is the KerbalValue amount set to 0.0 when the kerbal get back to KSC
vessel_reset = true // is the KerbalValue amount set to 0.0 when the vessel the kerbal is in is modified
}
Modifier
{
name = kerbal_neg_temp_amount
expression = 60 * 60 * 24 // 24 hours to death at min temperature
}
ValueInput
{
value = kerbal_neg_temp
rate = 1.0
interval = 1.0
modifier = hab_neg_temp_input
}
Modifier
{
name = hab_neg_temp_input
expression = pow(neg_kjoule.level,5.0)
}
ValueOuput
{
value = kerbal_neg_temp
rate = 1.0
interval = 1.0
modifier = hab_pos_temp_output
}
Modifier
{
name = hab_neg_temp_output
expression = neg_kjoule.level < 0.01
}
Event
{
text = $ON_VESSEL$KERBAL froze to death // the text to show on screen
timewarp_break = true // does the event stop timewarp
unfocused_show = true // does the text show up if this isn't the current vessel
modifier = neg_temp_death // modifier-expression that trigger the event when turning from false to true
consequence = death // death/breakdown/none
}
Modifier
{
name = pos_temp_death
expression = kerbal_pos_temp.amount == kerbal_pos_temp.capacity
}
Modifier
{
name = watt_flux_pos
expression = info.tempertature * info.
}
PlannerPanel
{
title = Thermal control // name of the panel
section = 3 // where to put it in the planner window
}
PlannerLine
{
panel = Thermal control // the line should be in this panel
title = Environment flux (kW) // the line left text
value = net_flux // the line right text, modifier or use "" to evaluate as a string
}
PlannerLine
{
panel = Thermal control // the line should be in this panel
title = death by freezing // the line left text
value = freezing_time // the line right text, modifier or use "" to evaluate as a string
value_format = duration // returned value is Lib.HumanReadableDuration(value)
color = #ff0000 // color of the value
visible = freezing_isvisible // modifier-expression that determine visibility
}
PlannerLine
{
panel = Thermal control // the line should be in this panel
title = temperature is stable // the line left text
color = #00ff00 // color of the value
visible = tempstable_isvisible // modifier-expression that determine visibility
}
Modifier
{
name = freezing_time
expression = (neg_kjoule.capacity / neg_kjoule.rate) + (60 * 60 * 24)
}
Modifier
{
name = freezing_isvisible
expression = neg_kjoule.rate > 0.0
}
Modifier
{
name = tempstable_isvisible
expression = (neg_kjoule.rate == 0.0) && (pos_kjoule.rate == 0.0)
}
Another GUI example for stress :
Rule
{
...
Event
{
text = The crew is feeling gravity again // the text to show on screen
timewarp_break = false // does the event stop timewarp
unfocused_show = false // does the text show up if this isn't the current vessel
modifier = gravity_message // modifier-expression that trigger the alert when turning from false to true
consequence = none // death/breakdown/none
}
Modifier
{
name = gravity_message
expression = env.firm_ground == 1.0
}
PlannerLine
{
title = breakdown // the line left text
value = 1.0 / (degen modifier formula) // the line right text, modifier-expression or use "" to evaluate as a string
value_format = duration // returned value is Lib.HumanReadableDuration(value)
visible = info.comfort < 0.2 // modifier-expression that determine visibility
}
PlannerLine
{
title = comfort // the line left text
value = "poor" // the line right text, modifier-expression or use "" to evaluate as a string
color = #ff0000 // color of the value
visible = info.comfort < 0.2 // modifier-expression that determine visibility
}
PlannerLine
{
title = comfort
value = "modest"
color = #ff8300
visible = (info.comfort >= 0.2) && (info.comfort < 0.4)
}
...
PlannerTooltip
{
line_title = comfort // show tooltip on Rule_UI_line whose title = comfort
title = firm ground // the line left text
value = info.firm_ground // the line right text, modifier-expression or use "" to evaluate as a string
value_format = boolean // returned value is Lib.HumanReadableBoolean(bool value) {return value ? "true" : "false";}
color = #00ff00 // color of the value
}
...
}
@gotmachine That's very good ideas and I feel the need to speed-update you on what's going on, so we can design this together. I'll create a new issue with the proposed changes to the mod architecture and we'll discuss there.
The PR contains a proposal for the thermal control system and various tweaks. Meant as a reference for future implementation
CHANGES :
Issue #81 : Being able to use different modifiers for input rate and degeneration rate in a rule
Added an optional "modifier_degen" field to rule, that allow to specify a separate set of modifiers for the degeneration rate. I tried to check "modifiers_degen" in all places where rules modifiers existence was checked, but this may need to be reviewed.
Issue #114 : Temperature inconsistency between loaded / unloaded vessels
Sim.Temperature() now doesn't use the skinTemp of the vessel root part.
Issue #83 : Thermal control
The proposed system has the following features :
Simulate the thermal flux that affect the vessel habitat, accounting for :
Simulate the temperature change in habitat accounting for thermal inertia
Kill the kerbals when the temperature change is too drastic
A ECLSS heater process use EC to compensate heat losses
Radiators produce a Coolant resource using EC to compensate heat gains
The Coolant output of radiators account for
Exothermic ECLSS/ISRU processes have a Coolant input, meaning that they require radiators to work
Related settings :
Radiator partmodule config :
New modifiers :
Other modifiers changes / addition
Planner changes