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:
blitzmann
2015-07-07 21:48:04 -04:00
committed by blitzmann
parent 23309a5da6
commit c17bce55bb
4 changed files with 124 additions and 78 deletions

View File

@@ -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"

View File

@@ -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.

View File

@@ -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)

View File

@@ -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