Differentiate total time and travel time.

There's an ugly special case in flight simulation to handle hold points
because we don't differentiate between the total time between two
waypoints (which can include delays from actions like holding) and
travel time. Split those up and remove the special case.
This commit is contained in:
Dan Albert 2023-08-13 11:16:14 -07:00 committed by Raffson
parent 5ca344e87b
commit 75498fe061
No known key found for this signature in database
GPG Key ID: B0402B2C9B764D99
6 changed files with 27 additions and 44 deletions

View File

@ -231,8 +231,23 @@ class FlightPlan(ABC, Generic[LayoutT]):
) )
for previous_waypoint, waypoint in self.edges(until=destination): for previous_waypoint, waypoint in self.edges(until=destination):
total += self.travel_time_between_waypoints(previous_waypoint, waypoint) total += self.total_time_between_waypoints(previous_waypoint, waypoint)
return total # Trim microseconds. Our simulation tick rate is 1 second, so anything that
# takes 100.1 or 100.9 seconds will take 100 seconds. DCS doesn't handle
# sub-second resolution for tasks anyway, nor are they interesting from a
# mission planning perspective, so there's little value to keeping them in the
# model.
return timedelta(seconds=math.floor(total.total_seconds()))
def total_time_between_waypoints(
self, a: FlightWaypoint, b: FlightWaypoint
) -> timedelta:
"""Returns the total time spent between a and b.
The total time between waypoints differs from the travel time in that it may
include additional time for actions such as loitering.
"""
return self.travel_time_between_waypoints(a, b)
def travel_time_between_waypoints( def travel_time_between_waypoints(
self, a: FlightWaypoint, b: FlightWaypoint self, a: FlightWaypoint, b: FlightWaypoint

View File

@ -14,7 +14,6 @@ from game.utils import Speed, meters, nautical_miles, feet
from .flightplan import FlightPlan from .flightplan import FlightPlan
from .formation import FormationFlightPlan, FormationLayout from .formation import FormationFlightPlan, FormationLayout
from .ibuilder import IBuilder from .ibuilder import IBuilder
from .planningerror import PlanningError
from .waypointbuilder import StrikeTarget, WaypointBuilder from .waypointbuilder import StrikeTarget, WaypointBuilder
from .. import FlightType from .. import FlightType
from ..flightwaypoint import FlightWaypoint from ..flightwaypoint import FlightWaypoint
@ -56,42 +55,19 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
"RADIO", "RADIO",
) )
@property
def travel_time_to_target(self) -> timedelta:
"""The estimated time between the first waypoint and the target."""
destination = self.tot_waypoint
total = timedelta()
for previous_waypoint, waypoint in self.edges():
if waypoint == self.tot_waypoint:
# For anything strike-like the TOT waypoint is the *flight's*
# mission target, but to synchronize with the rest of the
# package we need to use the travel time to the same position as
# the others.
total += self.travel_time_between_waypoints(
previous_waypoint, self.target_area_waypoint
)
break
total += self.travel_time_between_waypoints(previous_waypoint, waypoint)
else:
raise PlanningError(
f"Did not find destination waypoint {destination} in "
f"waypoints for {self.flight}"
)
return total
@property @property
def join_time(self) -> timedelta: def join_time(self) -> timedelta:
travel_time = self.travel_time_between_waypoints( travel_time = self.total_time_between_waypoints(
self.layout.join, self.layout.ingress self.layout.join, self.layout.ingress
) )
return self.ingress_time - travel_time return self.ingress_time - travel_time
@property @property
def split_time(self) -> timedelta: def split_time(self) -> timedelta:
travel_time_ingress = self.travel_time_between_waypoints( travel_time_ingress = self.total_time_between_waypoints(
self.layout.ingress, self.target_area_waypoint self.layout.ingress, self.target_area_waypoint
) )
travel_time_egress = self.travel_time_between_waypoints( travel_time_egress = self.total_time_between_waypoints(
self.target_area_waypoint, self.layout.split self.target_area_waypoint, self.layout.split
) )
minutes_at_target = 0.75 * len(self.layout.targets) minutes_at_target = 0.75 * len(self.layout.targets)
@ -106,7 +82,7 @@ class FormationAttackFlightPlan(FormationFlightPlan, ABC):
@property @property
def ingress_time(self) -> timedelta: def ingress_time(self) -> timedelta:
tot = self.tot tot = self.tot
travel_time = self.travel_time_between_waypoints( travel_time = self.total_time_between_waypoints(
self.layout.ingress, self.target_area_waypoint self.layout.ingress, self.target_area_waypoint
) )
return tot - travel_time return tot - travel_time

View File

@ -33,10 +33,10 @@ class LoiterFlightPlan(StandardFlightPlan[Any], ABC):
return self.push_time + self.tot_offset return self.push_time + self.tot_offset
return None return None
def travel_time_between_waypoints( def total_time_between_waypoints(
self, a: FlightWaypoint, b: FlightWaypoint self, a: FlightWaypoint, b: FlightWaypoint
) -> timedelta: ) -> timedelta:
travel_time = super().travel_time_between_waypoints(a, b) travel_time = super().total_time_between_waypoints(a, b)
if a != self.layout.hold: if a != self.layout.hold:
return travel_time return travel_time
return travel_time + self.hold_duration return travel_time + self.hold_duration

View File

@ -59,10 +59,10 @@ class PackageRefuelingFlightPlan(RefuelingFlightPlan):
"REFUEL", FlightWaypointType.REFUEL, refuel, altitude "REFUEL", FlightWaypointType.REFUEL, refuel, altitude
) )
delay_target_to_split: timedelta = self.travel_time_between_waypoints( delay_target_to_split: timedelta = self.total_time_between_waypoints(
self.target_area_waypoint(), split_waypoint self.target_area_waypoint(), split_waypoint
) )
delay_split_to_refuel: timedelta = self.travel_time_between_waypoints( delay_split_to_refuel: timedelta = self.total_time_between_waypoints(
split_waypoint, refuel_waypoint split_waypoint, refuel_waypoint
) )

View File

@ -56,7 +56,7 @@ class SweepFlightPlan(LoiterFlightPlan):
@property @property
def sweep_start_time(self) -> timedelta: def sweep_start_time(self) -> timedelta:
travel_time = self.travel_time_between_waypoints( travel_time = self.total_time_between_waypoints(
self.layout.sweep_start, self.layout.sweep_end self.layout.sweep_start, self.layout.sweep_end
) )
return self.sweep_end_time - travel_time return self.sweep_end_time - travel_time

View File

@ -51,17 +51,9 @@ class InFlight(FlightState, ABC):
return index <= self.waypoint_index return index <= self.waypoint_index
def travel_time_between_waypoints(self) -> timedelta: def travel_time_between_waypoints(self) -> timedelta:
travel_time = self.flight.flight_plan.travel_time_between_waypoints( return self.flight.flight_plan.travel_time_between_waypoints(
self.current_waypoint, self.next_waypoint self.current_waypoint, self.next_waypoint
) )
if self.current_waypoint.waypoint_type is FlightWaypointType.LOITER:
# Loiter time is already built into travel_time_between_waypoints. If we're
# at a loiter point but still a regular InFlight (Loiter overrides this
# method) that means we're traveling from the loiter point but no longer
# loitering.
assert self.flight.flight_plan.is_loiter(self.flight.flight_plan)
travel_time -= self.flight.flight_plan.hold_duration
return travel_time
@abstractmethod @abstractmethod
def estimate_position(self) -> Point: def estimate_position(self) -> Point: