Move all the logic from eos graph to gui graph for warp time

Now backend graphs have to be aware of handles used in UI graphs, so why not
This commit is contained in:
DarkPhoenix
2019-06-28 15:44:50 +03:00
parent c2017f3cb9
commit d195ec7e68
5 changed files with 222 additions and 148 deletions

View File

@@ -27,26 +27,7 @@ class Graph(metaclass=ABCMeta):
def __init__(self):
self._cache = {}
@abstractmethod
def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt):
raise NotImplementedError
_normalizers = {}
def _normalizeParams(self, mainInput, miscInputs, fit, tgt):
if mainInput.unit in self._normalizers:
normalizer = self._normalizers[mainInput.unit]
newMainInput = [mainInput.handle, tuple(normalizer(v) for v in mainInput.value)]
else:
newMainInput = [mainInput.handle, mainInput.value]
newMiscInputs = []
for miscInput in miscInputs:
if miscInput.unit in self._normalizers:
newMiscInput = [miscInput.handle, self._normalizers[miscInput.unit](miscInput.value)]
else:
newMiscInput = [miscInput.handle, miscInput.value]
newMiscInputs.append(newMiscInput)
return newMainInput, newMiscInputs
### Old stuff

View File

@@ -1,111 +0,0 @@
import math
from eos.const import FittingModuleState
from .base import Graph
AU_METERS = 149597870700
class FitWarpTimeGraph(Graph):
def __init__(self):
super().__init__()
self.subwarpSpeed = None
def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt):
mainInput, miscInputs = self._normalizeParams(mainInput, miscInputs, fit, tgt)
# limit all parameters to be within limits (time)
# pick getter according to x handle and y handle and run it
# un-render returned parameters if passed x is relative
return [], []
_normalizers = {
'AU': lambda x: x * AU_METERS,
'km': lambda x: x * 1000}
def getYForX(self, fit, extraData, distance):
if distance == 0:
return 0
if fit.ID not in self._cache:
self.__generateCache(fit)
maxWarpSpeed = fit.warpSpeed
subwarpSpeed = self._cache[fit.ID]['cleanSubwarpSpeed']
time = calculate_time_in_warp(maxWarpSpeed, subwarpSpeed, distance * AU_METERS)
return time
def _getXLimits(self, fit, extraData):
return 0, fit.maxWarpDistance
def __generateCache(self, fit):
modStates = {}
for mod in fit.modules:
if mod.item is not None and mod.item.group.name in ('Propulsion Module', 'Mass Entanglers', 'Cloaking Device') 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()
self._cache[fit.ID] = {'cleanSubwarpSpeed': fit.ship.getModifiedItemAttr('maxVelocity')}
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()
# 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):
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

View File

@@ -1,6 +1,6 @@
# noinspection PyUnresolvedReferences
from gui.builtinGraphs import ( # noqa: E402,F401
fitDamageStats,
# fitDamageStats,
# fitDmgVsTime,
# fitShieldRegenVsShieldPerc,
# fitShieldAmountVsTime,

View File

@@ -22,18 +22,24 @@ from abc import ABCMeta, abstractmethod
from collections import OrderedDict, namedtuple
YDef = namedtuple('YDef', ('handle', 'unit', 'label'))
XDef = namedtuple('XDef', ('handle', 'unit', 'label', 'mainInput'))
Input = namedtuple('Input', ('handle', 'unit', 'label', 'iconID', 'defaultValue', 'defaultRange', 'mainOnly'))
VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle', 'angleUnit', 'label'))
class Graph(metaclass=ABCMeta):
# UI stuff
views = []
yTypes = None
@classmethod
def register(cls):
Graph.views.append(cls)
def __init__(self, eosGraph):
self._eosGraph = eosGraph
self._cache = {}
def __init__(self):
self._plotCache = {}
self._calcCache = {}
@property
@abstractmethod
@@ -84,25 +90,112 @@ class Graph(metaclass=ABCMeta):
def getPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt=None):
try:
plotData = self._cache[fit.ID][(ySpec, xSpec)]
plotData = self._plotCache[fit.ID][(ySpec, xSpec)]
except KeyError:
plotData = self._eosGraph.getPlotPoints(mainInput, miscInputs, xSpec, ySpec, fit, tgt)
fitCache = self._cache.setdefault(fit.ID, {})
plotData = self._calcPlotPoints(mainInput, miscInputs, xSpec, ySpec, fit, tgt)
fitCache = self._plotCache.setdefault(fit.ID, {})
fitCache[(ySpec, xSpec)] = plotData
return plotData
def clearCache(self, key=None):
if key is None:
self._cache.clear()
elif key in self._cache:
del self._cache[key]
self._eosGraph.clearCache(key=key)
self._plotCache.clear()
self._calcCache.clear()
if key in self._plotCache:
del self._plotCache[key]
if key in self._calcCache:
del self._calcCache[key]
# Calculation stuff
def _calcPlotPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt):
mainInput, miscInputs = self._normalizeParams(mainInput, miscInputs, fit, tgt)
mainInput, miscInputs = self._limitParams(mainInput, miscInputs, fit, tgt)
xs, ys = self._getPoints(mainInput, miscInputs, xSpec, ySpec, fit, tgt)
xs = self._denormalizeValues(xs, xSpec, fit, tgt)
ys = self._denormalizeValues(ys, ySpec, fit, tgt)
return xs, ys
YDef = namedtuple('YDef', ('handle', 'unit', 'label'))
XDef = namedtuple('XDef', ('handle', 'unit', 'label', 'mainInput'))
Input = namedtuple('Input', ('handle', 'unit', 'label', 'iconID', 'defaultValue', 'defaultRange', 'mainOnly'))
VectorDef = namedtuple('VectorDef', ('lengthHandle', 'lengthUnit', 'angleHandle', 'angleUnit', 'label'))
_normalizers = {}
def _normalizeParams(self, mainInput, miscInputs, fit, tgt):
key = (mainInput.handle, mainInput.unit)
if key in self._normalizers:
normalizer = self._normalizers[key]
newMainInput = (mainInput.handle, tuple(normalizer(v, fit, tgt) for v in mainInput.value))
else:
newMainInput = (mainInput.handle, mainInput.value)
newMiscInputs = []
for miscInput in miscInputs:
key = (miscInput.handle, miscInput.unit)
if key in self._normalizers:
normalizer = self._normalizers[key]
newMiscInput = (miscInput.handle, normalizer(miscInput.value))
else:
newMiscInput = (miscInput.handle, miscInput.value)
newMiscInputs.append(newMiscInput)
return newMainInput, newMiscInputs
_limiters = {}
def _limitParams(self, mainInput, miscInputs, fit, tgt):
def limitToRange(val, limitRange):
if val is None:
return None
val = max(val, min(limitRange))
val = min(val, max(limitRange))
return val
mainHandle, mainValue = mainInput
if mainHandle in self._limiters:
limiter = self._limiters[mainHandle]
newMainInput = (mainHandle, tuple(limitToRange(v, limiter(fit, tgt)) for v in mainValue))
else:
newMainInput = mainInput
newMiscInputs = []
for miscInput in miscInputs:
miscHandle, miscValue = miscInput
if miscHandle in self._limiters:
limiter = self._limiters[miscHandle]
newMiscInput = (miscHandle, limitToRange(miscValue, limiter(fit, tgt)))
newMiscInputs.append(newMiscInput)
else:
newMiscInputs.append(miscInput)
return newMainInput, newMiscInputs
_getters = {}
def _getPoints(self, mainInput, miscInputs, xSpec, ySpec, fit, tgt):
try:
getter = self._getters[(xSpec.handle, ySpec.handle)]
except KeyError:
return [], []
else:
return getter(self, mainInput, miscInputs, fit, tgt)
_denormalizers = {}
def _denormalizeValues(self, values, axisSpec, fit, tgt):
key = (axisSpec.handle, axisSpec.unit)
if key in self._denormalizers:
denormalizer = self._denormalizers[key]
values = [denormalizer(v, fit, tgt) for v in values]
return values
def _iterLinear(self, valRange, resolution=100):
rangeLow = min(valRange)
rangeHigh = max(valRange)
# Amount is amount of ranges between points here, not amount of points
step = (rangeHigh - rangeLow) / resolution
if step == 0:
yield rangeLow
else:
current = rangeLow
# Take extra half step to make sure end of range is always included
# despite any possible float errors
while current <= (rangeHigh + step / 2):
yield current
current += step
# noinspection PyUnresolvedReferences

View File

@@ -18,17 +18,23 @@
# =============================================================================
from eos.graph.fitWarpTime import FitWarpTimeGraph as EosGraph
import math
from eos.const import FittingModuleState
from .base import Graph, XDef, YDef, Input
AU_METERS = 149597870700
class FitWarpTimeGraph(Graph):
name = 'Warp Time'
def __init__(self):
super().__init__(EosGraph())
super().__init__()
# UI stuff
@property
def xDefs(self):
return [
@@ -45,5 +51,110 @@ class FitWarpTimeGraph(Graph):
Input(handle='distance', unit='AU', label='Distance', iconID=1391, defaultValue=50, defaultRange=(0, 50), mainOnly=False),
Input(handle='distance', unit='km', label='Distance', iconID=1391, defaultValue=10000, defaultRange=(150, 5000), mainOnly=False)]
# 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 _distance2time(self, mainInput, miscInputs, fit, tgt):
xs = []
ys = []
subwarpSpeed = self.__getSubwarpSpeed(fit)
warpSpeed = fit.warpSpeed
for distance in self._iterLinear(mainInput[1]):
time = calculate_time_in_warp(subwarpSpeed, warpSpeed, distance)
xs.append(distance)
ys.append(time)
return xs, ys
_getters = {
('distance', 'time'): _distance2time}
def __getSubwarpSpeed(self, fit):
try:
subwarpSpeed = self._calcCache[fit.ID]['cleanSubwarpSpeed']
except KeyError:
modStates = {}
for mod in fit.modules:
if mod.item is not None and mod.item.group.name in ('Propulsion Module', 'Mass Entanglers', 'Cloaking Device') 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._calcCache[fit.ID] = {'cleanSubwarpSpeed': 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()