Lots of stuff
- Added logging and replaced Timer class with more useful one - Move projected fit loop out of runtimes - Eliminate recursions from Fit.clear() - Clean up overall fit calc logic
This commit is contained in:
@@ -17,7 +17,7 @@ debug = False
|
||||
# Defines if our saveddata will be in pyfa root or not
|
||||
saveInRoot = False
|
||||
|
||||
logLevel = logging.WARN
|
||||
logLevel = logging.DEBUG
|
||||
|
||||
# Version data
|
||||
version = "1.12.1"
|
||||
|
||||
@@ -29,6 +29,8 @@ from eos.saveddata.module import State
|
||||
from eos.saveddata.mode import Mode
|
||||
import eos.db
|
||||
import time
|
||||
from utils.timer import Timer
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -353,10 +355,28 @@ class Fit(object):
|
||||
del self.__calculatedTargets[:]
|
||||
del self.__extraDrains[:]
|
||||
|
||||
if self.ship is not None: self.ship.clear()
|
||||
c = chain(self.modules, self.drones, self.boosters, self.implants, self.projectedDrones, self.projectedModules, self.projectedFits, (self.character, self.extraAttributes))
|
||||
if self.ship:
|
||||
self.ship.clear()
|
||||
|
||||
c = chain(
|
||||
self.modules,
|
||||
self.drones,
|
||||
self.boosters,
|
||||
self.implants,
|
||||
self.projectedDrones,
|
||||
self.projectedModules,
|
||||
(self.character, self.extraAttributes),
|
||||
)
|
||||
|
||||
# If we are in a root ship, add projected fits to clear list
|
||||
# Do not add projected fits if self is already projected - this can
|
||||
# cause infinite recursion in projection loop, eg A > B > C > A
|
||||
if self.projectionInfo is None:
|
||||
c = chain(c, self.projectedFits)
|
||||
|
||||
for stuff in c:
|
||||
if stuff is not None and stuff != self: stuff.clear()
|
||||
if stuff is not None and stuff != self:
|
||||
stuff.clear()
|
||||
|
||||
#Methods to register and get the thing currently affecting the fit,
|
||||
#so we can correctly map "Affected By"
|
||||
@@ -370,7 +390,38 @@ class Fit(object):
|
||||
def getModifier(self):
|
||||
return self.__modifier
|
||||
|
||||
def __calculateGangBoosts(self, runTime):
|
||||
for name, info in self.gangBoosts.iteritems():
|
||||
# Unpack all data required to run effect properly
|
||||
effect, thing = info[1]
|
||||
if effect.runTime == runTime:
|
||||
context = ("gang", thing.__class__.__name__.lower())
|
||||
if isinstance(thing, Module):
|
||||
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
|
||||
(effect.isType("active") and thing.state >= State.ACTIVE):
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
|
||||
def calculateModifiedAttributes(self, targetFit=None, withBoosters=False, dirtyStorage=None):
|
||||
timer = Timer('Fit: %d, %s'%(self.ID, self.name), logger)
|
||||
logger.debug("Starting fit calculation on: %d %s (%s)" %
|
||||
(self.ID, self.name, self.ship.item.name))
|
||||
|
||||
if targetFit:
|
||||
logger.debug("Applying projections to target: %d %s (%s)" %
|
||||
(targetFit.ID, targetFit.name, targetFit.ship.item.name))
|
||||
|
||||
refreshBoosts = False
|
||||
if withBoosters is True:
|
||||
refreshBoosts = True
|
||||
@@ -379,6 +430,7 @@ class Fit(object):
|
||||
if dirtyStorage is not None:
|
||||
dirtyStorage.update(self.boostsFits)
|
||||
if self.fleet is not None and refreshBoosts is True:
|
||||
logger.debug("Fleet is set, gathering gang boosts")
|
||||
self.gangBoosts = self.fleet.recalculateLinear(withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||
elif self.fleet is None:
|
||||
self.gangBoosts = None
|
||||
@@ -387,84 +439,70 @@ class Fit(object):
|
||||
dirtyStorage.remove(self.ID)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# If we're not explicitly asked to project fit onto something,
|
||||
# set self as target fit
|
||||
if targetFit is None:
|
||||
targetFit = self
|
||||
forceProjected = False
|
||||
projected = False
|
||||
# Else, we're checking all target projectee fits
|
||||
elif targetFit not in self.__calculatedTargets:
|
||||
logger.debug("Target fit has not been calculated, calculating first")
|
||||
# target fit is required to be calculated before we do projections
|
||||
# @todo: is there any situation where a projected ship would get here and the targte fit not be calculated already? See if we can get rid of this block of code
|
||||
self.__calculatedTargets.append(targetFit)
|
||||
targetFit.calculateModifiedAttributes(dirtyStorage=dirtyStorage)
|
||||
forceProjected = True
|
||||
projected = True
|
||||
# Or do nothing if target fit is calculated
|
||||
else:
|
||||
return
|
||||
|
||||
# If fit is calculated and we have nothing to do here, get out
|
||||
if self.__calculated == True and forceProjected == False:
|
||||
if self.__calculated and not projected:
|
||||
logger.debug("Fit has already been calculated and is not projected, returning")
|
||||
return
|
||||
|
||||
# Mark fit as calculated
|
||||
self.__calculated = True
|
||||
|
||||
# There's a few things to keep in mind here
|
||||
# 1: Early effects first, then regular ones, then late ones, regardless of anything else
|
||||
# 2: Some effects aren't implemented
|
||||
# 3: Some effects are implemented poorly and will just explode on us
|
||||
# 4: Errors should be handled gracefully and preferably without crashing unless serious
|
||||
for runTime in ("early", "normal", "late"):
|
||||
# Build a little chain of stuff
|
||||
# Avoid adding projected drones and modules when fit is projected onto self
|
||||
# TODO: remove this workaround when proper self-projection using virtual duplicate fits is implemented
|
||||
if forceProjected is True:
|
||||
# if fit is being projected onto another fit
|
||||
c = chain((self.character, self.ship), self.drones, self.boosters, self.appliedImplants, self.modules)
|
||||
else:
|
||||
c = chain((self.character, self.ship, self.mode), self.drones, self.boosters, self.appliedImplants, self.modules,
|
||||
self.projectedDrones, self.projectedModules)
|
||||
c = chain(
|
||||
(self.character, self.ship),
|
||||
self.drones,
|
||||
self.boosters,
|
||||
self.appliedImplants,
|
||||
self.modules
|
||||
)
|
||||
|
||||
if not projected:
|
||||
# if not a projected fit, add a couple of more things
|
||||
c = chain(c, self.projectedDrones, self.projectedModules)
|
||||
|
||||
for runTime in ("early", "normal", "late"):
|
||||
# We calculate gang bonuses first so that projected fits get them
|
||||
if self.gangBoosts is not None:
|
||||
contextMap = {Skill: "skill",
|
||||
Ship: "ship",
|
||||
Module: "module",
|
||||
Implant: "implant"}
|
||||
for name, info in self.gangBoosts.iteritems():
|
||||
# Unpack all data required to run effect properly
|
||||
effect, thing = info[1]
|
||||
if effect.runTime == runTime:
|
||||
context = ("gang", contextMap[type(thing)])
|
||||
if isinstance(thing, Module):
|
||||
if effect.isType("offline") or (effect.isType("passive") and thing.state >= State.ONLINE) or \
|
||||
(effect.isType("active") and thing.state >= State.ACTIVE):
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
# Run effect, and get proper bonuses applied
|
||||
try:
|
||||
self.register(thing)
|
||||
effect.handler(self, thing, context)
|
||||
except:
|
||||
pass
|
||||
self.__calculateGangBoosts(runTime)
|
||||
|
||||
for item in c:
|
||||
# Registering the item about to affect the fit allows us to track "Affected By" relations correctly
|
||||
# Registering the item about to affect the fit allows us to
|
||||
# track "Affected By" relations correctly
|
||||
if item is not None:
|
||||
self.register(item)
|
||||
item.calculateModifiedAttributes(self, runTime, False)
|
||||
if forceProjected is True:
|
||||
targetFit.register(item)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
if projected is True:
|
||||
for _ in xrange(self.projectionInfo.amount):
|
||||
targetFit.register(item)
|
||||
item.calculateModifiedAttributes(targetFit, runTime, True)
|
||||
|
||||
for fit in self.projectedFits:
|
||||
if fit.projectionInfo.active:
|
||||
#for _ in xrange(fit.projectionInfo.amount):
|
||||
timer.checkpoint('Done with runtime: %s'%runTime)
|
||||
|
||||
# Only apply projected fits if fit it not projected itself.
|
||||
if not projected:
|
||||
for fit in self.projectedFits:
|
||||
if fit.projectionInfo.active:
|
||||
fit.calculateModifiedAttributes(self, withBoosters=withBoosters, dirtyStorage=dirtyStorage)
|
||||
|
||||
timer.checkpoint('Done with fit calculation')
|
||||
|
||||
def fill(self):
|
||||
"""
|
||||
Fill this fit's module slots with enough dummy slots so that all slots are used.
|
||||
|
||||
@@ -36,7 +36,7 @@ from service.fleet import Fleet
|
||||
from service.settings import SettingsProvider
|
||||
from service.port import Port
|
||||
|
||||
logger = logging.getLogger("pyfa.service.fit")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class FitBackupThread(threading.Thread):
|
||||
def __init__(self, path, callback):
|
||||
@@ -236,6 +236,7 @@ class Fit(object):
|
||||
return None
|
||||
fit = eos.db.getFit(fitID)
|
||||
inited = getattr(fit, "inited", None)
|
||||
|
||||
if inited is None or inited is False:
|
||||
sFleet = Fleet.getInstance()
|
||||
f = sFleet.getLinearFleet(fit)
|
||||
@@ -373,7 +374,7 @@ class Fit(object):
|
||||
def changeAmount(self, fitID, projected_fit, amount):
|
||||
"""Change amount of projected fits"""
|
||||
fit = eos.db.getFit(fitID)
|
||||
amount = min(5, max(1, amount)) # 1 <= a <= 5
|
||||
amount = min(20, max(1, amount)) # 1 <= a <= 5
|
||||
|
||||
if projected_fit.projectionInfo is not None:
|
||||
projected_fit.projectionInfo.amount = amount
|
||||
@@ -936,7 +937,8 @@ class Fit(object):
|
||||
self.recalc(fit)
|
||||
|
||||
def recalc(self, fit, withBoosters=False):
|
||||
logger.debug("="*10+"recalc"+"="*10)
|
||||
if fit.factorReload is not self.serviceFittingOptions["useGlobalForceReload"]:
|
||||
fit.factorReload = self.serviceFittingOptions["useGlobalForceReload"]
|
||||
fit.clear()
|
||||
fit.clear()
|
||||
fit.calculateModifiedAttributes(withBoosters=withBoosters, dirtyStorage=self.dirtyFitIDs)
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
import time
|
||||
|
||||
class Timer(object):
|
||||
"""
|
||||
Generic timing class for simple profiling.
|
||||
class Timer():
|
||||
def __init__(self, name='', logger=None):
|
||||
self.name = name
|
||||
self.start = time.time()
|
||||
self.__last = self.start
|
||||
self.logger = logger
|
||||
|
||||
Usage:
|
||||
@property
|
||||
def elapsed(self):
|
||||
return (time.time() - self.start)*1000
|
||||
|
||||
with Timer(verbose=True) as t:
|
||||
# code to be timed
|
||||
time.sleep(5)
|
||||
@property
|
||||
def last(self):
|
||||
return (time.time() - self.__last)*1000
|
||||
|
||||
Output:
|
||||
elapsed time: 5000.000 ms
|
||||
|
||||
Can also access time with t.secs
|
||||
"""
|
||||
def __init__(self, verbose=False):
|
||||
self.verbose = verbose
|
||||
def checkpoint(self, name=''):
|
||||
text = 'Timer - {timer} - {checkpoint} - {last:.2f}ms ({elapsed:.2f}ms elapsed)'.format(
|
||||
timer=self.name,
|
||||
checkpoint=name,
|
||||
last=self.last,
|
||||
elapsed=self.elapsed
|
||||
).strip()
|
||||
self.__last = time.time()
|
||||
if self.logger:
|
||||
self.logger.debug(text)
|
||||
else:
|
||||
print text
|
||||
|
||||
def __enter__(self):
|
||||
self.start = time.time()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.end = time.time()
|
||||
self.secs = self.end - self.start
|
||||
self.msecs = self.secs * 1000 # millisecs
|
||||
if self.verbose:
|
||||
print 'elapsed time: %f ms' % self.msecs
|
||||
def __exit__(self, type, value, traceback):
|
||||
self.checkpoint('finished')
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user