Dan Albert 350f08be2f Add FastAPI interface between the game and map.
A possible explanation for the infrequent CTDs we've been seeing since
adding fast forward is that QWebChannel doesn't keep a reference to the
python objects that it passes to js, so if the object is GC'd before the
front end is done with it, it crashes.

We don't really like QWebChannel anyway, so this begins replacing that
with FastAPI.
2022-02-15 18:14:34 -08:00

123 lines
4.0 KiB
Python

from __future__ import annotations
from typing import List
from PySide2.QtCore import Property, QObject, Signal, Slot
from shapely.geometry import LineString, Point as ShapelyPoint
from game.ato import Flight, FlightWaypoint
from game.ato.flightstate import InFlight
from game.ato.flightwaypointtype import FlightWaypointType
from game.server.leaflet import LeafletLatLon, LeafletPoly, ShapelyUtil
from game.theater import ConflictTheater
from game.utils import meters
from gen.flights.flightplan import CasFlightPlan, PatrollingFlightPlan
from qt_ui.models import AtoModel
from .waypointjs import WaypointJs
class FlightJs(QObject):
positionChanged = Signal()
flightPlanChanged = Signal()
blueChanged = Signal()
selectedChanged = Signal()
commitBoundaryChanged = Signal()
originChanged = Signal()
@Property(list, notify=originChanged)
def origin(self) -> LeafletLatLon:
return self._waypoints[0].position
targetChanged = Signal()
@Property(list, notify=targetChanged)
def target(self) -> LeafletLatLon:
ll = self.theater.point_to_ll(self.flight.package.target.position)
return [ll.latitude, ll.longitude]
def __init__(
self,
flight: Flight,
selected: bool,
theater: ConflictTheater,
ato_model: AtoModel,
) -> None:
super().__init__()
self.flight = flight
self._selected = selected
self.theater = theater
self.ato_model = ato_model
self._waypoints = self.make_waypoints()
def update_waypoints(self) -> None:
for waypoint in self._waypoints:
waypoint.timingChanged.emit()
def make_waypoints(self) -> List[WaypointJs]:
departure = FlightWaypoint(
FlightWaypointType.TAKEOFF,
self.flight.departure.position.x,
self.flight.departure.position.y,
meters(0),
)
departure.alt_type = "RADIO"
waypoints = []
for idx, point in enumerate([departure] + self.flight.points):
waypoint = WaypointJs(point, idx, self, self.theater, self.ato_model)
waypoint.positionChanged.connect(self.update_waypoints)
waypoints.append(waypoint)
return waypoints
@Property(list, notify=positionChanged)
def position(self) -> LeafletLatLon:
if isinstance(self.flight.state, InFlight):
ll = self.theater.point_to_ll(self.flight.state.estimate_position())
return [ll.latitude, ll.longitude]
return []
@Property(list, notify=flightPlanChanged)
def flightPlan(self) -> List[WaypointJs]:
return self._waypoints
@Property(bool, notify=blueChanged)
def blue(self) -> bool:
return self.flight.departure.captured
@Property(bool, notify=selectedChanged)
def selected(self) -> bool:
return self._selected
@Slot(result=bool)
def flightIsInAto(self) -> bool:
if self.flight.package not in self.flight.squadron.coalition.ato.packages:
return False
if self.flight not in self.flight.package.flights:
return False
return True
def set_selected(self, value: bool) -> None:
self._selected = value
self.selectedChanged.emit()
@Property(list, notify=commitBoundaryChanged)
def commitBoundary(self) -> LeafletPoly:
if not isinstance(self.flight.flight_plan, PatrollingFlightPlan):
return []
start = self.flight.flight_plan.patrol_start
end = self.flight.flight_plan.patrol_end
if isinstance(self.flight.flight_plan, CasFlightPlan):
center = self.flight.flight_plan.target.position
commit_center = ShapelyPoint(center.x, center.y)
else:
commit_center = LineString(
[
ShapelyPoint(start.x, start.y),
ShapelyPoint(end.x, end.y),
]
)
bubble = commit_center.buffer(
self.flight.flight_plan.engagement_distance.meters
)
return ShapelyUtil.poly_to_leaflet(bubble, self.theater)