From 62fbb7c9c8783b8a0dd15aa5860e843eadbfedc1 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Fri, 2 Aug 2019 09:36:22 +0300 Subject: [PATCH] Rework warp time graph to use new getter approach --- gui/builtinGraphs/__init__.py | 4 +- gui/builtinGraphs/base.py | 61 +++++- gui/builtinGraphs/fitWarpTime.py | 189 ------------------ gui/builtinGraphs/fitWarpTime/__init__.py | 1 + gui/builtinGraphs/fitWarpTime/getter.py | 74 +++++++ gui/builtinGraphs/fitWarpTime/graph.py | 66 ++++++ gui/builtinGraphs/fitWarpTime/subwarpCache.py | 82 ++++++++ 7 files changed, 278 insertions(+), 199 deletions(-) delete mode 100644 gui/builtinGraphs/fitWarpTime.py create mode 100644 gui/builtinGraphs/fitWarpTime/__init__.py create mode 100644 gui/builtinGraphs/fitWarpTime/getter.py create mode 100644 gui/builtinGraphs/fitWarpTime/graph.py create mode 100644 gui/builtinGraphs/fitWarpTime/subwarpCache.py diff --git a/gui/builtinGraphs/__init__.py b/gui/builtinGraphs/__init__.py index 82c10e1f7..6f55feae6 100644 --- a/gui/builtinGraphs/__init__.py +++ b/gui/builtinGraphs/__init__.py @@ -1,9 +1,7 @@ # noinspection PyUnresolvedReferences from gui.builtinGraphs import ( # noqa: E402,F401 fitDamageStats, - # fitDmgVsTime, fitShieldRegen, fitCapRegen, fitMobility, - fitWarpTime -) + fitWarpTime) diff --git a/gui/builtinGraphs/base.py b/gui/builtinGraphs/base.py index adb6e624d..b6cc457f3 100644 --- a/gui/builtinGraphs/base.py +++ b/gui/builtinGraphs/base.py @@ -232,13 +232,12 @@ class FitGraph(metaclass=ABCMeta): def _getPoints(self, mainParam, miscParams, xSpec, ySpec, fit, tgt): try: - fullGetter, singleGetter, cacheGetter = self._getters[(xSpec.handle, ySpec.handle)] + getterClass = self._getters[(xSpec.handle, ySpec.handle)] except KeyError: return [], [] else: - return fullGetter( - self, cacheGetter=cacheGetter, singleGetter=singleGetter, - mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt) + getter = getterClass(graph=self) + return getter.getRange(mainParam=mainParam, miscParams=miscParams, fit=fit, tgt=tgt) _denormalizers = {} @@ -249,11 +248,52 @@ class FitGraph(metaclass=ABCMeta): values = [denormalizer(v, fit, tgt) for v in values] return values - def _iterLinear(self, valRange, segments=200): + +class PointGetter(metaclass=ABCMeta): + + def __init__(self, graph): + self.graph = graph + + @abstractmethod + def getRange(self, mainParam, miscParams, fit, tgt): + raise NotImplementedError + + @abstractmethod + def getPoint(self, mainParam, miscParams, fit, tgt, cache=None): + raise NotImplementedError + + +class SmoothPointGetter(PointGetter, metaclass=ABCMeta): + + def __init__(self, graph, baseResolution=200): + super().__init__(graph) + self._baseResolution = baseResolution + + def getRange(self, mainParam, miscParams, fit, tgt): + xs = [] + ys = [] + commonData = self._getCommonData(miscParams=miscParams, fit=fit, tgt=tgt) + for x in self._iterLinear(mainParam[1]): + y = self._calculatePoint( + mainParam=x, miscParams=miscParams, + fit=fit, tgt=tgt, commonData=commonData) + xs.append(x) + ys.append(y) + return xs, ys + + def getPoint(self, mainParam, miscParams, fit, tgt): + commonData = self._getCommonData(miscParams=miscParams, fit=fit, tgt=tgt) + y = self._calculatePoint( + mainParam=mainParam, miscParams=miscParams, + fit=fit, tgt=tgt, commonData=commonData) + return mainParam, y + + def _iterLinear(self, valRange): rangeLow = min(valRange) rangeHigh = max(valRange) - # Amount is amount of ranges between points here, not amount of points - step = (rangeHigh - rangeLow) / segments + # Resolution defines amount of ranges between points here, + # not amount of points + step = (rangeHigh - rangeLow) / self._baseResolution if step == 0 or math.isnan(step): yield rangeLow else: @@ -264,6 +304,13 @@ class FitGraph(metaclass=ABCMeta): yield current current += step + def _getCommonData(self, miscParams, fit, tgt): + return {} + + @abstractmethod + def _calculatePoint(self, mainParam, miscParams, fit, tgt, commonData): + raise NotImplementedError + class FitDataCache: diff --git a/gui/builtinGraphs/fitWarpTime.py b/gui/builtinGraphs/fitWarpTime.py deleted file mode 100644 index da9bc9bca..000000000 --- a/gui/builtinGraphs/fitWarpTime.py +++ /dev/null @@ -1,189 +0,0 @@ -# ============================================================================= -# Copyright (C) 2010 Diego Duclos -# -# This file is part of pyfa. -# -# pyfa is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# pyfa is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with pyfa. If not, see . -# ============================================================================= - - -import math - -from eos.const import FittingModuleState -from service.const import GraphCacheCleanupReason -from .base import FitGraph, XDef, YDef, Input, FitDataCache - - -AU_METERS = 149597870700 - - -class FitWarpTimeGraph(FitGraph): - - def __init__(self): - super().__init__() - self._subspeedCache = SubwarpSpeedCache() - - def _clearInternalCache(self, reason, extraData): - if reason in (GraphCacheCleanupReason.fitChanged, GraphCacheCleanupReason.fitRemoved): - self._subspeedCache.clearForFit(extraData) - elif reason == GraphCacheCleanupReason.graphSwitched: - self._subspeedCache.clearAll() - - # UI stuff - internalName = 'warpTimeGraph' - name = 'Warp Time' - xDefs = [ - XDef(handle='distance', unit='AU', label='Distance', mainInput=('distance', 'AU')), - XDef(handle='distance', unit='km', label='Distance', mainInput=('distance', 'km'))] - yDefs = [ - YDef(handle='time', unit='s', label='Warp time')] - inputs = [ - Input(handle='distance', unit='AU', label='Distance', iconID=1391, defaultValue=20, defaultRange=(0, 50)), - Input(handle='distance', unit='km', label='Distance', iconID=1391, defaultValue=1000, defaultRange=(150, 5000))] - srcExtraCols = ('WarpSpeed', 'WarpDistance') - - # Calculation stuff - _normalizers = { - ('distance', 'AU'): lambda v, fit, tgt: v * AU_METERS, - ('distance', 'km'): lambda v, fit, tgt: v * 1000} - _limiters = { - 'distance': lambda fit, tgt: (0, fit.maxWarpDistance * AU_METERS)} - _denormalizers = { - ('distance', 'AU'): lambda v, fit, tgt: v / AU_METERS, - ('distance', 'km'): lambda v, fit, tgt: v / 1000} - - def _distance2timeCache(self, miscParams, fit, tgt): - return { - 'subwarpSpeed': self._subspeedCache.getSubwarpSpeed(fit), - 'warpSpeed': fit.warpSpeed} - - def _distance2timeFull(self, cacheGetter, singleGetter, mainParam, miscParams, fit, tgt): - xs = [] - ys = [] - cache = cacheGetter(self, miscParams=miscParams, fit=fit, tgt=tgt) - for distance in self._iterLinear(mainParam[1]): - time = singleGetter( - self, cacheGetter=cacheGetter, mainParam=distance, miscParams=miscParams, - fit=fit, tgt=tgt, cache=cache) - xs.append(distance) - ys.append(time) - return xs, ys - - def _distance2timeSingle(self, cacheGetter, mainParam, miscParams, fit, tgt, cache=None): - if cache is None: - cache = cacheGetter(self, miscParams=miscParams, fit=fit, tgt=tgt) - time = calculate_time_in_warp( - max_subwarp_speed=cache['subwarpSpeed'], - max_warp_speed=cache['warpSpeed'], - warp_dist=mainParam) - return time - - _getters = { - ('distance', 'time'): (_distance2timeFull, _distance2timeSingle, _distance2timeCache)} - - -class SubwarpSpeedCache(FitDataCache): - - def getSubwarpSpeed(self, fit): - try: - subwarpSpeed = self._data[fit.ID] - except KeyError: - modStates = {} - disallowedGroups = ( - # Active modules which affect ship speed and cannot be used in warp - 'Propulsion Module', - 'Mass Entanglers', - 'Cloaking Device', - # Those reduce ship speed to 0 - 'Siege Module', - 'Super Weapon', - 'Cynosural Field Generator', - 'Clone Vat Bay', - 'Jump Portal Generator') - for mod in fit.modules: - if mod.item is not None and mod.item.group.name in disallowedGroups and mod.state >= FittingModuleState.ACTIVE: - modStates[mod] = mod.state - mod.state = FittingModuleState.ONLINE - projFitStates = {} - for projFit in fit.projectedFits: - projectionInfo = projFit.getProjectionInfo(fit.ID) - if projectionInfo is not None and projectionInfo.active: - projFitStates[projectionInfo] = projectionInfo.active - projectionInfo.active = False - projModStates = {} - for mod in fit.projectedModules: - if not mod.isExclusiveSystemEffect and mod.state >= FittingModuleState.ACTIVE: - projModStates[mod] = mod.state - mod.state = FittingModuleState.ONLINE - projDroneStates = {} - for drone in fit.projectedDrones: - if drone.amountActive > 0: - projDroneStates[drone] = drone.amountActive - drone.amountActive = 0 - projFighterStates = {} - for fighter in fit.projectedFighters: - if fighter.active: - projFighterStates[fighter] = fighter.active - fighter.active = False - fit.calculateModifiedAttributes() - subwarpSpeed = fit.ship.getModifiedItemAttr('maxVelocity') - self._data[fit.ID] = subwarpSpeed - for projInfo, state in projFitStates.items(): - projInfo.active = state - for mod, state in modStates.items(): - mod.state = state - for mod, state in projModStates.items(): - mod.state = state - for drone, amountActive in projDroneStates.items(): - drone.amountActive = amountActive - for fighter, state in projFighterStates.items(): - fighter.active = state - fit.calculateModifiedAttributes() - return subwarpSpeed - - -# Taken from https://wiki.eveuniversity.org/Warp_time_calculation#Implementation -# with minor modifications -# Warp speed in AU/s, subwarp speed in m/s, distance in m -def calculate_time_in_warp(max_warp_speed, max_subwarp_speed, warp_dist): - - if warp_dist == 0: - return 0 - - k_accel = max_warp_speed - k_decel = min(max_warp_speed / 3, 2) - - warp_dropout_speed = max_subwarp_speed / 2 - max_ms_warp_speed = max_warp_speed * AU_METERS - - accel_dist = AU_METERS - decel_dist = max_ms_warp_speed / k_decel - - minimum_dist = accel_dist + decel_dist - - cruise_time = 0 - - if minimum_dist > warp_dist: - max_ms_warp_speed = warp_dist * k_accel * k_decel / (k_accel + k_decel) - else: - cruise_time = (warp_dist - minimum_dist) / max_ms_warp_speed - - accel_time = math.log(max_ms_warp_speed / k_accel) / k_accel - decel_time = math.log(max_ms_warp_speed / warp_dropout_speed) / k_decel - - total_time = cruise_time + accel_time + decel_time - return total_time - - -FitWarpTimeGraph.register() diff --git a/gui/builtinGraphs/fitWarpTime/__init__.py b/gui/builtinGraphs/fitWarpTime/__init__.py new file mode 100644 index 000000000..860a43eb2 --- /dev/null +++ b/gui/builtinGraphs/fitWarpTime/__init__.py @@ -0,0 +1 @@ +import gui.builtinGraphs.fitWarpTime.graph # noqa: E402,F401 diff --git a/gui/builtinGraphs/fitWarpTime/getter.py b/gui/builtinGraphs/fitWarpTime/getter.py new file mode 100644 index 000000000..89c181460 --- /dev/null +++ b/gui/builtinGraphs/fitWarpTime/getter.py @@ -0,0 +1,74 @@ +# ============================================================================= +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +# ============================================================================= + + +import math + +from gui.builtinGraphs.base import SmoothPointGetter + + +AU_METERS = 149597870700 + + +class Distance2TimeGetter(SmoothPointGetter): + + def _getCommonData(self, miscParams, fit, tgt): + return { + 'subwarpSpeed': self.graph._subspeedCache.getSubwarpSpeed(fit), + 'warpSpeed': fit.warpSpeed} + + def _calculatePoint(self, mainParam, miscParams, fit, tgt, commonData): + time = calculate_time_in_warp( + max_subwarp_speed=commonData['subwarpSpeed'], + max_warp_speed=commonData['warpSpeed'], + warp_dist=mainParam) + return time + + +# Taken from https://wiki.eveuniversity.org/Warp_time_calculation#Implementation +# with minor modifications +# Warp speed in AU/s, subwarp speed in m/s, distance in m +def calculate_time_in_warp(max_warp_speed, max_subwarp_speed, warp_dist): + + if warp_dist == 0: + return 0 + + k_accel = max_warp_speed + k_decel = min(max_warp_speed / 3, 2) + + warp_dropout_speed = max_subwarp_speed / 2 + max_ms_warp_speed = max_warp_speed * AU_METERS + + accel_dist = AU_METERS + decel_dist = max_ms_warp_speed / k_decel + + minimum_dist = accel_dist + decel_dist + + cruise_time = 0 + + if minimum_dist > warp_dist: + max_ms_warp_speed = warp_dist * k_accel * k_decel / (k_accel + k_decel) + else: + cruise_time = (warp_dist - minimum_dist) / max_ms_warp_speed + + accel_time = math.log(max_ms_warp_speed / k_accel) / k_accel + decel_time = math.log(max_ms_warp_speed / warp_dropout_speed) / k_decel + + total_time = cruise_time + accel_time + decel_time + return total_time diff --git a/gui/builtinGraphs/fitWarpTime/graph.py b/gui/builtinGraphs/fitWarpTime/graph.py new file mode 100644 index 000000000..986c6da0a --- /dev/null +++ b/gui/builtinGraphs/fitWarpTime/graph.py @@ -0,0 +1,66 @@ +# ============================================================================= +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +# ============================================================================= + + +from service.const import GraphCacheCleanupReason +from gui.builtinGraphs.base import FitGraph, XDef, YDef, Input +from .getter import Distance2TimeGetter, AU_METERS +from .subwarpCache import SubwarpSpeedCache + + +class FitWarpTimeGraph(FitGraph): + + def __init__(self): + super().__init__() + self._subspeedCache = SubwarpSpeedCache() + + def _clearInternalCache(self, reason, extraData): + if reason in (GraphCacheCleanupReason.fitChanged, GraphCacheCleanupReason.fitRemoved): + self._subspeedCache.clearForFit(extraData) + elif reason == GraphCacheCleanupReason.graphSwitched: + self._subspeedCache.clearAll() + + # UI stuff + internalName = 'warpTimeGraph' + name = 'Warp Time' + xDefs = [ + XDef(handle='distance', unit='AU', label='Distance', mainInput=('distance', 'AU')), + XDef(handle='distance', unit='km', label='Distance', mainInput=('distance', 'km'))] + yDefs = [ + YDef(handle='time', unit='s', label='Warp time')] + inputs = [ + Input(handle='distance', unit='AU', label='Distance', iconID=1391, defaultValue=20, defaultRange=(0, 50)), + Input(handle='distance', unit='km', label='Distance', iconID=1391, defaultValue=1000, defaultRange=(150, 5000))] + srcExtraCols = ('WarpSpeed', 'WarpDistance') + + # Calculation stuff + _normalizers = { + ('distance', 'AU'): lambda v, fit, tgt: v * AU_METERS, + ('distance', 'km'): lambda v, fit, tgt: v * 1000} + _limiters = { + 'distance': lambda fit, tgt: (0, fit.maxWarpDistance * AU_METERS)} + _denormalizers = { + ('distance', 'AU'): lambda v, fit, tgt: v / AU_METERS, + ('distance', 'km'): lambda v, fit, tgt: v / 1000} + + _getters = { + ('distance', 'time'): Distance2TimeGetter} + + +FitWarpTimeGraph.register() diff --git a/gui/builtinGraphs/fitWarpTime/subwarpCache.py b/gui/builtinGraphs/fitWarpTime/subwarpCache.py new file mode 100644 index 000000000..2eabb9be7 --- /dev/null +++ b/gui/builtinGraphs/fitWarpTime/subwarpCache.py @@ -0,0 +1,82 @@ +# ============================================================================= +# Copyright (C) 2010 Diego Duclos +# +# This file is part of pyfa. +# +# pyfa is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# pyfa is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with pyfa. If not, see . +# ============================================================================= + + +from eos.const import FittingModuleState +from gui.builtinGraphs.base import FitDataCache + + +class SubwarpSpeedCache(FitDataCache): + + def getSubwarpSpeed(self, fit): + try: + subwarpSpeed = self._data[fit.ID] + except KeyError: + modStates = {} + disallowedGroups = ( + # Active modules which affect ship speed and cannot be used in warp + 'Propulsion Module', + 'Mass Entanglers', + 'Cloaking Device', + # Those reduce ship speed to 0 + 'Siege Module', + 'Super Weapon', + 'Cynosural Field Generator', + 'Clone Vat Bay', + 'Jump Portal Generator') + for mod in fit.modules: + if mod.item is not None and mod.item.group.name in disallowedGroups and mod.state >= FittingModuleState.ACTIVE: + modStates[mod] = mod.state + mod.state = FittingModuleState.ONLINE + projFitStates = {} + for projFit in fit.projectedFits: + projectionInfo = projFit.getProjectionInfo(fit.ID) + if projectionInfo is not None and projectionInfo.active: + projFitStates[projectionInfo] = projectionInfo.active + projectionInfo.active = False + projModStates = {} + for mod in fit.projectedModules: + if not mod.isExclusiveSystemEffect and mod.state >= FittingModuleState.ACTIVE: + projModStates[mod] = mod.state + mod.state = FittingModuleState.ONLINE + projDroneStates = {} + for drone in fit.projectedDrones: + if drone.amountActive > 0: + projDroneStates[drone] = drone.amountActive + drone.amountActive = 0 + projFighterStates = {} + for fighter in fit.projectedFighters: + if fighter.active: + projFighterStates[fighter] = fighter.active + fighter.active = False + fit.calculateModifiedAttributes() + subwarpSpeed = fit.ship.getModifiedItemAttr('maxVelocity') + self._data[fit.ID] = subwarpSpeed + for projInfo, state in projFitStates.items(): + projInfo.active = state + for mod, state in modStates.items(): + mod.state = state + for mod, state in projModStates.items(): + mod.state = state + for drone, amountActive in projDroneStates.items(): + drone.amountActive = amountActive + for fighter, state in projFighterStates.items(): + fighter.active = state + fit.calculateModifiedAttributes() + return subwarpSpeed