Use TACAN channels more selectively, use pytest (#1554)

* Use TACAN channels more selectively

* Increase tacan range to 126

* Use pytest and add workflow

* Skip faction tests due to outdated test data

* Run mypy on tests directory also

* Use iterators for bands AND usages, add tests
This commit is contained in:
Magnus Wolffelt
2021-08-17 23:14:54 +02:00
committed by GitHub
parent 57e78d5c55
commit f63a35b1fa
10 changed files with 229 additions and 19 deletions

View File

@@ -92,7 +92,7 @@ from gen.flights.flight import (
from gen.lasercoderegistry import LaserCodeRegistry
from gen.radios import RadioFrequency, RadioRegistry
from gen.runways import RunwayData
from gen.tacan import TacanBand, TacanRegistry
from gen.tacan import TacanBand, TacanRegistry, TacanUsage
from .airsupport import AirSupport, AwacsInfo, TankerInfo
from .callsigns import callsign_for_support_unit
from .flights.flightplan import (
@@ -435,7 +435,7 @@ class AircraftConflictGenerator:
if isinstance(flight.flight_plan, RefuelingFlightPlan):
callsign = callsign_for_support_unit(group)
tacan = self.tacan_registy.alloc_for_band(TacanBand.Y)
tacan = self.tacan_registy.alloc_for_band(TacanBand.Y, TacanUsage.AirToAir)
self.air_support.tankers.append(
TankerInfo(
group_name=str(group.name),

View File

@@ -22,7 +22,7 @@ from .conflictgen import Conflict
from .flights.ai_flight_planner_db import AEWC_CAPABLE
from .naming import namegen
from .radios import RadioRegistry
from .tacan import TacanBand, TacanRegistry
from .tacan import TacanBand, TacanRegistry, TacanUsage
if TYPE_CHECKING:
from game import Game
@@ -89,7 +89,9 @@ class AirSupportConflictGenerator:
# TODO: Make loiter altitude a property of the unit type.
alt, airspeed = self._get_tanker_params(tanker_unit_type.dcs_unit_type)
freq = self.radio_registry.alloc_uhf()
tacan = self.tacan_registry.alloc_for_band(TacanBand.Y)
tacan = self.tacan_registry.alloc_for_band(
TacanBand.Y, TacanUsage.AirToAir
)
tanker_heading = Heading.from_degrees(
self.conflict.red_cp.position.heading_between_point(
self.conflict.blue_cp.position

View File

@@ -58,7 +58,7 @@ from game.unitmap import UnitMap
from game.utils import Heading, feet, knots, mps
from .radios import RadioFrequency, RadioRegistry
from .runways import RunwayData
from .tacan import TacanBand, TacanChannel, TacanRegistry
from .tacan import TacanBand, TacanChannel, TacanRegistry, TacanUsage
if TYPE_CHECKING:
from game import Game
@@ -377,7 +377,9 @@ class GenericCarrierGenerator(GenericGroundObjectGenerator[GenericCarrierGroundO
for unit in group.units[1:]:
ship_group.add_unit(self.create_ship(unit, atc))
tacan = self.tacan_registry.alloc_for_band(TacanBand.X)
tacan = self.tacan_registry.alloc_for_band(
TacanBand.X, TacanUsage.TransmitReceive
)
tacan_callsign = self.tacan_callsign()
icls = next(self.icls_alloc)

View File

@@ -4,13 +4,37 @@ from enum import Enum
from typing import Dict, Iterator, Set
class TacanUsage(Enum):
TransmitReceive = "transmit receive"
AirToAir = "air to air"
class TacanBand(Enum):
X = "X"
Y = "Y"
def range(self) -> Iterator["TacanChannel"]:
"""Returns an iterator over the channels in this band."""
return (TacanChannel(x, self) for x in range(1, 100))
return (TacanChannel(x, self) for x in range(1, 126 + 1))
def valid_channels(self, usage: TacanUsage) -> Iterator["TacanChannel"]:
for x in self.range():
if x.number not in UNAVAILABLE[usage][self]:
yield x
# Avoid certain TACAN channels for various reasons
# https://forums.eagle.ru/topic/276390-datalink-issue/
UNAVAILABLE = {
TacanUsage.TransmitReceive: {
TacanBand.X: set(range(2, 30 + 1)) | set(range(47, 63 + 1)),
TacanBand.Y: set(range(2, 30 + 1)) | set(range(64, 92 + 1)),
},
TacanUsage.AirToAir: {
TacanBand.X: set(range(1, 36 + 1)) | set(range(64, 99 + 1)),
TacanBand.Y: set(range(1, 36 + 1)) | set(range(64, 99 + 1)),
},
}
@dataclass(frozen=True)
@@ -36,30 +60,42 @@ class TacanChannelInUseError(RuntimeError):
super().__init__(f"{channel} is already in use")
class TacanChannelForbiddenError(RuntimeError):
"""Raised when attempting to reserve a, for technical reasons, forbidden channel."""
def __init__(self, channel: TacanChannel) -> None:
super().__init__(f"{channel} is forbidden")
class TacanRegistry:
"""Manages allocation of TACAN channels."""
def __init__(self) -> None:
self.allocated_channels: Set[TacanChannel] = set()
self.band_allocators: Dict[TacanBand, Iterator[TacanChannel]] = {}
self.allocators: Dict[TacanBand, Dict[TacanUsage, Iterator[TacanChannel]]] = {}
for band in TacanBand:
self.band_allocators[band] = band.range()
self.allocators[band] = {}
for usage in TacanUsage:
self.allocators[band][usage] = band.valid_channels(usage)
def alloc_for_band(self, band: TacanBand) -> TacanChannel:
def alloc_for_band(
self, band: TacanBand, intended_usage: TacanUsage
) -> TacanChannel:
"""Allocates a TACAN channel in the given band.
Args:
band: The TACAN band to allocate a channel for.
intended_usage: What the caller intends to use the tacan channel for.
Returns:
A TACAN channel in the given band.
Raises:
OutOfChannelsError: All channels compatible with the given radio are
OutOfTacanChannelsError: All channels compatible with the given radio are
already allocated.
"""
allocator = self.band_allocators[band]
allocator = self.allocators[band][intended_usage]
try:
while (channel := next(allocator)) in self.allocated_channels:
pass
@@ -67,17 +103,21 @@ class TacanRegistry:
except StopIteration:
raise OutOfTacanChannelsError(band)
def reserve(self, channel: TacanChannel) -> None:
def reserve(self, channel: TacanChannel, intended_usage: TacanUsage) -> None:
"""Reserves the given channel.
Reserving a channel ensures that it will not be allocated in the future.
Args:
channel: The channel to reserve.
intended_usage: What the caller intends to use the tacan channel for.
Raises:
ChannelInUseError: The given frequency is already in use.
TacanChannelInUseError: The given channel is already in use.
TacanChannelForbiddenError: The given channel is forbidden.
"""
if channel.number in UNAVAILABLE[intended_usage][channel.band]:
raise TacanChannelForbiddenError(channel)
if channel in self.allocated_channels:
raise TacanChannelInUseError(channel)
self.allocated_channels.add(channel)