Replace debug QObject models with Pydantic.

This requires less duplication and paves the way for us replacing
QWebChannel with FastAPI, which in turn gets us closer to not needing
Qt.
This commit is contained in:
Dan Albert 2022-02-13 14:33:49 -08:00
parent 1adafac35f
commit 6ebda41922
6 changed files with 111 additions and 179 deletions

View File

@ -1,67 +1,33 @@
from __future__ import annotations from __future__ import annotations
from PySide2.QtCore import Property, QObject, Signal from pydantic import BaseModel, Field
from game import Game from game import Game
from game.ato import Flight from game.ato import Flight
from game.flightplan import HoldZoneGeometry from game.flightplan import HoldZoneGeometry
from .config import ENABLE_EXPENSIVE_DEBUG_TOOLS from .config import ENABLE_EXPENSIVE_DEBUG_TOOLS
from .leaflet import LeafletLatLon, LeafletPoly from .leaflet import LeafletPoly
from .shapelyutil import ShapelyUtil from .shapelyutil import ShapelyUtil
class HoldZonesJs(QObject): class HoldZonesJs(BaseModel):
homeBubbleChanged = Signal() home_bubble: LeafletPoly = Field(alias="homeBubble")
targetBubbleChanged = Signal() target_bubble: LeafletPoly = Field(alias="targetBubble")
joinBubbleChanged = Signal() join_bubble: LeafletPoly = Field(alias="joinBubble")
excludedZonesChanged = Signal() excluded_zones: list[LeafletPoly] = Field(alias="excludedZones")
permissibleZonesChanged = Signal() permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
preferredLinesChanged = Signal() preferred_lines: list[LeafletPoly] = Field(alias="preferredLines")
def __init__(
self,
home_bubble: LeafletPoly,
target_bubble: LeafletPoly,
join_bubble: LeafletPoly,
excluded_zones: list[LeafletPoly],
permissible_zones: list[LeafletPoly],
preferred_lines: list[list[LeafletLatLon]],
) -> None:
super().__init__()
self._home_bubble = home_bubble
self._target_bubble = target_bubble
self._join_bubble = join_bubble
self._excluded_zones = excluded_zones
self._permissible_zones = permissible_zones
self._preferred_lines = preferred_lines
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
return self._home_bubble
@Property(list, notify=targetBubbleChanged)
def targetBubble(self) -> LeafletPoly:
return self._target_bubble
@Property(list, notify=joinBubbleChanged)
def joinBubble(self) -> LeafletPoly:
return self._join_bubble
@Property(list, notify=excludedZonesChanged)
def excludedZones(self) -> list[LeafletPoly]:
return self._excluded_zones
@Property(list, notify=permissibleZonesChanged)
def permissibleZones(self) -> list[LeafletPoly]:
return self._permissible_zones
@Property(list, notify=preferredLinesChanged)
def preferredLines(self) -> list[list[LeafletLatLon]]:
return self._preferred_lines
@classmethod @classmethod
def empty(cls) -> HoldZonesJs: def empty(cls) -> HoldZonesJs:
return HoldZonesJs([], [], [], [], [], []) return HoldZonesJs(
homeBubble=[],
targetBubble=[],
joinBubble=[],
excludedZones=[],
permissibleZones=[],
preferredLines=[],
)
@classmethod @classmethod
def for_flight(cls, flight: Flight, game: Game) -> HoldZonesJs: def for_flight(cls, flight: Flight, game: Game) -> HoldZonesJs:
@ -77,10 +43,18 @@ class HoldZonesJs(QObject):
target.position, home.position, ip, join, game.blue, game.theater target.position, home.position, ip, join, game.blue, game.theater
) )
return HoldZonesJs( return HoldZonesJs(
ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater), homeBubble=ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.target_bubble, game.theater), targetBubble=ShapelyUtil.poly_to_leaflet(
ShapelyUtil.poly_to_leaflet(geometry.join_bubble, game.theater), geometry.target_bubble, game.theater
ShapelyUtil.polys_to_leaflet(geometry.excluded_zones, game.theater), ),
ShapelyUtil.polys_to_leaflet(geometry.permissible_zones, game.theater), joinBubble=ShapelyUtil.poly_to_leaflet(geometry.join_bubble, game.theater),
ShapelyUtil.lines_to_leaflet(geometry.preferred_lines, game.theater), excludedZones=ShapelyUtil.polys_to_leaflet(
geometry.excluded_zones, game.theater
),
permissibleZones=ShapelyUtil.polys_to_leaflet(
geometry.permissible_zones, game.theater
),
preferredLines=ShapelyUtil.lines_to_leaflet(
geometry.preferred_lines, game.theater
),
) )

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from PySide2.QtCore import Property, QObject, Signal from pydantic import BaseModel, Field
from game import Game from game import Game
from game.ato import Flight from game.ato import Flight
@ -10,44 +10,15 @@ from .leaflet import LeafletPoly
from .shapelyutil import ShapelyUtil from .shapelyutil import ShapelyUtil
class IpZonesJs(QObject): class IpZonesJs(BaseModel):
homeBubbleChanged = Signal() home_bubble: LeafletPoly = Field(alias="homeBubble")
ipBubbleChanged = Signal() ipBubble: LeafletPoly = Field(alias="ipBubble")
permissibleZoneChanged = Signal() permissibleZone: LeafletPoly = Field(alias="permissibleZone")
safeZonesChanged = Signal() safeZones: list[LeafletPoly] = Field(alias="safeZones")
def __init__(
self,
home_bubble: LeafletPoly,
ip_bubble: LeafletPoly,
permissible_zone: LeafletPoly,
safe_zones: list[LeafletPoly],
) -> None:
super().__init__()
self._home_bubble = home_bubble
self._ip_bubble = ip_bubble
self._permissible_zone = permissible_zone
self._safe_zones = safe_zones
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
return self._home_bubble
@Property(list, notify=ipBubbleChanged)
def ipBubble(self) -> LeafletPoly:
return self._ip_bubble
@Property(list, notify=permissibleZoneChanged)
def permissibleZone(self) -> LeafletPoly:
return self._permissible_zone
@Property(list, notify=safeZonesChanged)
def safeZones(self) -> list[LeafletPoly]:
return self._safe_zones
@classmethod @classmethod
def empty(cls) -> IpZonesJs: def empty(cls) -> IpZonesJs:
return IpZonesJs([], [], [], []) return IpZonesJs(homeBubble=[], ipBubble=[], permissibleZone=[], safeZones=[])
@classmethod @classmethod
def for_flight(cls, flight: Flight, game: Game) -> IpZonesJs: def for_flight(cls, flight: Flight, game: Game) -> IpZonesJs:
@ -57,8 +28,10 @@ class IpZonesJs(QObject):
home = flight.departure home = flight.departure
geometry = IpZoneGeometry(target.position, home.position, game.blue) geometry = IpZoneGeometry(target.position, home.position, game.blue)
return IpZonesJs( return IpZonesJs(
ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater), homeBubble=ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater), ipBubble=ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.permissible_zone, game.theater), permissibleZone=ShapelyUtil.poly_to_leaflet(
ShapelyUtil.polys_to_leaflet(geometry.safe_zones, game.theater), geometry.permissible_zone, game.theater
),
safeZones=ShapelyUtil.polys_to_leaflet(geometry.safe_zones, game.theater),
) )

View File

@ -1,67 +1,34 @@
from __future__ import annotations from __future__ import annotations
from PySide2.QtCore import Property, QObject, Signal from pydantic import Field
from pydantic.main import BaseModel
from game import Game from game import Game
from game.ato import Flight from game.ato import Flight
from game.flightplan import JoinZoneGeometry from game.flightplan import JoinZoneGeometry
from .config import ENABLE_EXPENSIVE_DEBUG_TOOLS from .config import ENABLE_EXPENSIVE_DEBUG_TOOLS
from .leaflet import LeafletLatLon, LeafletPoly from .leaflet import LeafletPoly
from .shapelyutil import ShapelyUtil from .shapelyutil import ShapelyUtil
class JoinZonesJs(QObject): class JoinZonesJs(BaseModel):
homeBubbleChanged = Signal() home_bubble: LeafletPoly = Field(alias="homeBubble")
targetBubbleChanged = Signal() target_bubble: LeafletPoly = Field(alias="targetBubble")
ipBubbleChanged = Signal() ip_bubble: LeafletPoly = Field(alias="ipBubble")
excludedZonesChanged = Signal() excluded_zones: list[LeafletPoly] = Field(alias="excludedZones")
permissibleZonesChanged = Signal() permissible_zones: list[LeafletPoly] = Field(alias="permissibleZones")
preferredLinesChanged = Signal() preferred_lines: list[LeafletPoly] = Field(alias="preferredLines")
def __init__(
self,
home_bubble: LeafletPoly,
target_bubble: LeafletPoly,
ip_bubble: LeafletPoly,
excluded_zones: list[LeafletPoly],
permissible_zones: list[LeafletPoly],
preferred_lines: list[list[LeafletLatLon]],
) -> None:
super().__init__()
self._home_bubble = home_bubble
self._target_bubble = target_bubble
self._ip_bubble = ip_bubble
self._excluded_zones = excluded_zones
self._permissible_zones = permissible_zones
self._preferred_lines = preferred_lines
@Property(list, notify=homeBubbleChanged)
def homeBubble(self) -> LeafletPoly:
return self._home_bubble
@Property(list, notify=targetBubbleChanged)
def targetBubble(self) -> LeafletPoly:
return self._target_bubble
@Property(list, notify=ipBubbleChanged)
def ipBubble(self) -> LeafletPoly:
return self._ip_bubble
@Property(list, notify=excludedZonesChanged)
def excludedZones(self) -> list[LeafletPoly]:
return self._excluded_zones
@Property(list, notify=permissibleZonesChanged)
def permissibleZones(self) -> list[LeafletPoly]:
return self._permissible_zones
@Property(list, notify=preferredLinesChanged)
def preferredLines(self) -> list[list[LeafletLatLon]]:
return self._preferred_lines
@classmethod @classmethod
def empty(cls) -> JoinZonesJs: def empty(cls) -> JoinZonesJs:
return JoinZonesJs([], [], [], [], [], []) return JoinZonesJs(
homeBubble=[],
targetBubble=[],
ipBubble=[],
excludedZones=[],
permissibleZones=[],
preferredLines=[],
)
@classmethod @classmethod
def for_flight(cls, flight: Flight, game: Game) -> JoinZonesJs: def for_flight(cls, flight: Flight, game: Game) -> JoinZonesJs:
@ -74,10 +41,18 @@ class JoinZonesJs(QObject):
ip = flight.package.waypoints.ingress ip = flight.package.waypoints.ingress
geometry = JoinZoneGeometry(target.position, home.position, ip, game.blue) geometry = JoinZoneGeometry(target.position, home.position, ip, game.blue)
return JoinZonesJs( return JoinZonesJs(
ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater), homeBubble=ShapelyUtil.poly_to_leaflet(geometry.home_bubble, game.theater),
ShapelyUtil.poly_to_leaflet(geometry.target_bubble, game.theater), targetBubble=ShapelyUtil.poly_to_leaflet(
ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater), geometry.target_bubble, game.theater
ShapelyUtil.polys_to_leaflet(geometry.excluded_zones, game.theater), ),
ShapelyUtil.polys_to_leaflet(geometry.permissible_zones, game.theater), ipBubble=ShapelyUtil.poly_to_leaflet(geometry.ip_bubble, game.theater),
ShapelyUtil.lines_to_leaflet(geometry.preferred_lines, game.theater), excludedZones=ShapelyUtil.polys_to_leaflet(
geometry.excluded_zones, game.theater
),
permissibleZones=ShapelyUtil.polys_to_leaflet(
geometry.permissible_zones, game.theater
),
preferredLines=ShapelyUtil.lines_to_leaflet(
geometry.preferred_lines, game.theater
),
) )

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import json
import logging import logging
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
@ -396,17 +397,19 @@ class MapModel(QObject):
def unculledZones(self) -> list[UnculledZone]: def unculledZones(self) -> list[UnculledZone]:
return self._unculled_zones return self._unculled_zones
@Property(IpZonesJs, notify=ipZonesChanged) @Property(str, notify=ipZonesChanged)
def ipZones(self) -> IpZonesJs: def ipZones(self) -> str:
return self._ip_zones return json.dumps(self._ip_zones.dict(by_alias=True))
@Property(JoinZonesJs, notify=joinZonesChanged) @Property(str, notify=joinZonesChanged)
def joinZones(self) -> JoinZonesJs: def joinZones(self) -> str:
return self._join_zones # Must be dumped as a string and deserialized in js because QWebChannel can't
# handle a dict. Can be cleaned up by switching from QWebChannel to FastAPI.
return json.dumps(self._join_zones.dict(by_alias=True))
@Property(HoldZonesJs, notify=holdZonesChanged) @Property(str, notify=holdZonesChanged)
def holdZones(self) -> HoldZonesJs: def holdZones(self) -> str:
return self._hold_zones return json.dumps(self._hold_zones.dict(by_alias=True))
def reset_combats(self) -> None: def reset_combats(self) -> None:
self._air_combats = [] self._air_combats = []

View File

@ -25,6 +25,7 @@ platformdirs==2.5.0
pluggy==1.0.0 pluggy==1.0.0
pre-commit==2.17.0 pre-commit==2.17.0
py==1.11.0 py==1.11.0
pydantic==1.9.0
-e git+https://github.com/pydcs/dcs@63863a88e0a43cb0a310dbab3ce2c7800a099dbb#egg=pydcs -e git+https://github.com/pydcs/dcs@63863a88e0a43cb0a310dbab3ce2c7800a099dbb#egg=pydcs
pyinstaller==4.9 pyinstaller==4.9
pyinstaller-hooks-contrib==2022.1 pyinstaller-hooks-contrib==2022.1

View File

@ -1106,25 +1106,27 @@ function drawIpZones() {
return; return;
} }
L.polygon(game.ipZones.homeBubble, { const iz = JSON.parse(game.ipZones);
L.polygon(iz.homeBubble, {
color: Colors.Highlight, color: Colors.Highlight,
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(ipZones); }).addTo(ipZones);
L.polygon(game.ipZones.ipBubble, { L.polygon(iz.ipBubble, {
color: "#bb89ff", color: "#bb89ff",
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(ipZones); }).addTo(ipZones);
L.polygon(game.ipZones.permissibleZone, { L.polygon(iz.permissibleZone, {
color: "#ffffff", color: "#ffffff",
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(ipZones); }).addTo(ipZones);
for (const zone of game.ipZones.safeZones) { for (const zone of iz.safeZones) {
L.polygon(zone, { L.polygon(zone, {
color: Colors.Green, color: Colors.Green,
fillOpacity: 0.1, fillOpacity: 0.1,
@ -1140,25 +1142,27 @@ function drawJoinZones() {
return; return;
} }
L.polygon(game.joinZones.homeBubble, { const jz = JSON.parse(game.joinZones);
L.polygon(jz.homeBubble, {
color: Colors.Highlight, color: Colors.Highlight,
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(joinZones); }).addTo(joinZones);
L.polygon(game.joinZones.targetBubble, { L.polygon(jz.targetBubble, {
color: "#bb89ff", color: "#bb89ff",
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(joinZones); }).addTo(joinZones);
L.polygon(game.joinZones.ipBubble, { L.polygon(jz.ipBubble, {
color: "#ffffff", color: "#ffffff",
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(joinZones); }).addTo(joinZones);
for (const zone of game.joinZones.excludedZones) { for (const zone of jz.excludedZones) {
L.polygon(zone, { L.polygon(zone, {
color: "#ffa500", color: "#ffa500",
fillOpacity: 0.2, fillOpacity: 0.2,
@ -1167,14 +1171,14 @@ function drawJoinZones() {
}).addTo(joinZones); }).addTo(joinZones);
} }
for (const zone of game.joinZones.permissibleZones) { for (const zone of jz.permissibleZones) {
L.polygon(zone, { L.polygon(zone, {
color: Colors.Green, color: Colors.Green,
interactive: false, interactive: false,
}).addTo(joinZones); }).addTo(joinZones);
} }
for (const line of game.joinZones.preferredLines) { for (const line of jz.preferredLines) {
L.polyline(line, { L.polyline(line, {
color: Colors.Green, color: Colors.Green,
interactive: false, interactive: false,
@ -1189,25 +1193,27 @@ function drawHoldZones() {
return; return;
} }
L.polygon(game.holdZones.homeBubble, { const hz = JSON.parse(game.holdZones);
L.polygon(hz.homeBubble, {
color: Colors.Highlight, color: Colors.Highlight,
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(holdZones); }).addTo(holdZones);
L.polygon(game.holdZones.targetBubble, { L.polygon(hz.targetBubble, {
color: Colors.Highlight, color: Colors.Highlight,
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(holdZones); }).addTo(holdZones);
L.polygon(game.holdZones.joinBubble, { L.polygon(hz.joinBubble, {
color: Colors.Highlight, color: Colors.Highlight,
fillOpacity: 0.1, fillOpacity: 0.1,
interactive: false, interactive: false,
}).addTo(holdZones); }).addTo(holdZones);
for (const zone of game.holdZones.excludedZones) { for (const zone of hz.excludedZones) {
L.polygon(zone, { L.polygon(zone, {
color: "#ffa500", color: "#ffa500",
fillOpacity: 0.2, fillOpacity: 0.2,
@ -1216,14 +1222,14 @@ function drawHoldZones() {
}).addTo(holdZones); }).addTo(holdZones);
} }
for (const zone of game.holdZones.permissibleZones) { for (const zone of hz.permissibleZones) {
L.polygon(zone, { L.polygon(zone, {
color: Colors.Green, color: Colors.Green,
interactive: false, interactive: false,
}).addTo(holdZones); }).addTo(holdZones);
} }
for (const line of game.holdZones.preferredLines) { for (const line of hz.preferredLines) {
L.polyline(line, { L.polyline(line, {
color: Colors.Green, color: Colors.Green,
interactive: false, interactive: false,