| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734 |
- """Contains miscellaneous utility functions and classes."""
- __all__ = ['indent',
- 'doc', 'adjust', 'difference', 'intersection', 'union',
- 'sameElements', 'makeList', 'makeTuple', 'list2dict', 'invertDict',
- 'invertDictLossless', 'uniqueElements', 'disjoint', 'contains',
- 'replace', 'reduceAngle', 'fitSrcAngle2Dest', 'fitDestAngle2Src',
- 'closestDestAngle2', 'closestDestAngle', 'getSetterName',
- 'getSetter', 'Functor', 'Stack', 'Queue',
- 'bound', 'clamp', 'lerp', 'average', 'addListsByValue',
- 'boolEqual', 'lineupPos', 'formatElapsedSeconds', 'solveQuadratic',
- 'findPythonModule', 'mostDerivedLast',
- 'clampScalar', 'weightedChoice', 'randFloat', 'normalDistrib',
- 'weightedRand', 'randUint31', 'randInt32',
- 'SerialNumGen', 'serialNum', 'uniqueName', 'Enum', 'Singleton',
- 'SingletonError', 'printListEnum', 'safeRepr',
- 'fastRepr', 'isDefaultValue',
- 'ScratchPad', 'Sync', 'itype', 'getNumberedTypedString',
- 'getNumberedTypedSortedString',
- 'printNumberedTyped', 'DelayedCall', 'DelayedFunctor',
- 'FrameDelayedCall', 'SubframeCall', 'getBase', 'GoldenRatio',
- 'GoldenRectangle', 'rad90', 'rad180', 'rad270', 'rad360',
- 'nullGen', 'loopGen', 'makeFlywheelGen', 'flywheel',
- 'listToIndex2item', 'listToItem2index',
- 'formatTimeCompact','deeptype','StdoutCapture','StdoutPassthrough',
- 'Averager', 'getRepository', 'formatTimeExact', 'startSuperLog', 'endSuperLog',
- 'typeName', 'safeTypeName', 'histogramDict', 'unescapeHtmlString']
- if __debug__:
- __all__ += ['StackTrace', 'traceFunctionCall', 'traceParentCall', 'printThisCall',
- 'stackEntryInfo', 'lineInfo', 'callerInfo', 'lineTag',
- 'profileFunc', 'profiled', 'startProfile', 'printProfile',
- 'getProfileResultString', 'printStack', 'printReverseStack']
- import types
- import math
- import os
- import sys
- import random
- import time
- __report_indent = 3
- from panda3d.core import ConfigVariableBool
- if sys.version_info >= (3, 0):
- import builtins
- xrange = range
- else:
- import __builtin__ as builtins
- """
- # with one integer positional arg, this uses about 4/5 of the memory of the Functor class below
- def Functor(function, *args, **kArgs):
- argsCopy = args[:]
- def functor(*cArgs, **ckArgs):
- kArgs.update(ckArgs)
- return function(*(argsCopy + cArgs), **kArgs)
- return functor
- """
- try:
- import importlib
- except ImportError:
- # Backward compatibility for Python 2.6.
- def _resolve_name(name, package, level):
- if not hasattr(package, 'rindex'):
- raise ValueError("'package' not set to a string")
- dot = len(package)
- for x in xrange(level, 1, -1):
- try:
- dot = package.rindex('.', 0, dot)
- except ValueError:
- raise ValueError("attempted relative import beyond top-level "
- "package")
- return "%s.%s" % (package[:dot], name)
- def import_module(name, package=None):
- if name.startswith('.'):
- if not package:
- raise TypeError("relative imports require the 'package' argument")
- level = 0
- for character in name:
- if character != '.':
- break
- level += 1
- name = _resolve_name(name[level:], package, level)
- __import__(name)
- return sys.modules[name]
- imp = import_module('imp')
- importlib = imp.new_module("importlib")
- importlib._resolve_name = _resolve_name
- importlib.import_module = import_module
- sys.modules['importlib'] = importlib
- class Functor:
- def __init__(self, function, *args, **kargs):
- assert callable(function), "function should be a callable obj"
- self._function = function
- self._args = args
- self._kargs = kargs
- if hasattr(self._function, '__name__'):
- self.__name__ = self._function.__name__
- else:
- self.__name__ = str(itype(self._function))
- if hasattr(self._function, '__doc__'):
- self.__doc__ = self._function.__doc__
- else:
- self.__doc__ = self.__name__
- def destroy(self):
- del self._function
- del self._args
- del self._kargs
- del self.__name__
- del self.__doc__
- def _do__call__(self, *args, **kargs):
- _kargs = self._kargs.copy()
- _kargs.update(kargs)
- return self._function(*(self._args + args), **_kargs)
- __call__ = _do__call__
- def __repr__(self):
- s = 'Functor(%s' % self._function.__name__
- for arg in self._args:
- try:
- argStr = repr(arg)
- except:
- argStr = 'bad repr: %s' % arg.__class__
- s += ', %s' % argStr
- for karg, value in list(self._kargs.items()):
- s += ', %s=%s' % (karg, repr(value))
- s += ')'
- return s
- class Stack:
- def __init__(self):
- self.__list = []
- def push(self, item):
- self.__list.append(item)
- def top(self):
- # return the item on the top of the stack without popping it off
- return self.__list[-1]
- def pop(self):
- return self.__list.pop()
- def clear(self):
- self.__list = []
- def isEmpty(self):
- return len(self.__list) == 0
- def __len__(self):
- return len(self.__list)
- class Queue:
- # FIFO queue
- # interface is intentionally identical to Stack (LIFO)
- def __init__(self):
- self.__list = []
- def push(self, item):
- self.__list.append(item)
- def top(self):
- # return the next item at the front of the queue without popping it off
- return self.__list[0]
- def front(self):
- return self.__list[0]
- def back(self):
- return self.__list[-1]
- def pop(self):
- return self.__list.pop(0)
- def clear(self):
- self.__list = []
- def isEmpty(self):
- return len(self.__list) == 0
- def __len__(self):
- return len(self.__list)
- def indent(stream, numIndents, str):
- """
- Write str to stream with numIndents in front of it
- """
- # To match emacs, instead of a tab character we will use 4 spaces
- stream.write(' ' * numIndents + str)
- if __debug__:
- import traceback
- import marshal
- class StackTrace:
- def __init__(self, label="", start=0, limit=None):
- """
- label is a string (or anything that be be a string)
- that is printed as part of the trace back.
- This is just to make it easier to tell what the
- stack trace is referring to.
- start is an integer number of stack frames back
- from the most recent. (This is automatically
- bumped up by one to skip the __init__ call
- to the StackTrace).
- limit is an integer number of stack frames
- to record (or None for unlimited).
- """
- self.label = label
- if limit is not None:
- self.trace = traceback.extract_stack(sys._getframe(1+start),
- limit=limit)
- else:
- self.trace = traceback.extract_stack(sys._getframe(1+start))
- def compact(self):
- r = ''
- comma = ','
- for filename, lineNum, funcName, text in self.trace:
- r += '%s.%s:%s%s' % (filename[:filename.rfind('.py')][filename.rfind('\\')+1:], funcName, lineNum, comma)
- if len(r):
- r = r[:-len(comma)]
- return r
- def reverseCompact(self):
- r = ''
- comma = ','
- for filename, lineNum, funcName, text in self.trace:
- r = '%s.%s:%s%s%s' % (filename[:filename.rfind('.py')][filename.rfind('\\')+1:], funcName, lineNum, comma, r)
- if len(r):
- r = r[:-len(comma)]
- return r
- def __str__(self):
- r = "Debug stack trace of %s (back %s frames):\n"%(
- self.label, len(self.trace),)
- for i in traceback.format_list(self.trace):
- r+=i
- r+="***** NOTE: This is not a crash. This is a debug stack trace. *****"
- return r
- def printStack():
- print(StackTrace(start=1).compact())
- return True
- def printReverseStack():
- print(StackTrace(start=1).reverseCompact())
- return True
- def printVerboseStack():
- print(StackTrace(start=1))
- return True
- #-----------------------------------------------------------------------------
- def traceFunctionCall(frame):
- """
- return a string that shows the call frame with calling arguments.
- e.g.
- foo(x=234, y=135)
- """
- f = frame
- co = f.f_code
- dict = f.f_locals
- n = co.co_argcount
- if co.co_flags & 4: n = n+1
- if co.co_flags & 8: n = n+1
- r=''
- if 'self' in dict:
- r = '%s.'%(dict['self'].__class__.__name__,)
- r+="%s("%(f.f_code.co_name,)
- comma=0 # formatting, whether we should type a comma.
- for i in range(n):
- name = co.co_varnames[i]
- if name=='self':
- continue
- if comma:
- r+=', '
- else:
- # ok, we skipped the first one, the rest get commas:
- comma=1
- r+=name
- r+='='
- if name in dict:
- v=safeRepr(dict[name])
- if len(v)>2000:
- # r+="<too big for debug>"
- r += (v[:2000] + "...")
- else:
- r+=v
- else: r+="*** undefined ***"
- return r+')'
- def traceParentCall():
- return traceFunctionCall(sys._getframe(2))
- def printThisCall():
- print(traceFunctionCall(sys._getframe(1)))
- return 1 # to allow "assert printThisCall()"
- # Magic numbers: These are the bit masks in func_code.co_flags that
- # reveal whether or not the function has a *arg or **kw argument.
- _POS_LIST = 4
- _KEY_DICT = 8
- def doc(obj):
- if (isinstance(obj, types.MethodType)) or \
- (isinstance(obj, types.FunctionType)):
- print(obj.__doc__)
- def adjust(command = None, dim = 1, parent = None, **kw):
- """
- adjust(command = None, parent = None, **kw)
- Popup and entry scale to adjust a parameter
- Accepts any Slider keyword argument. Typical arguments include:
- command: The one argument command to execute
- min: The min value of the slider
- max: The max value of the slider
- resolution: The resolution of the slider
- text: The label on the slider
- These values can be accessed and/or changed after the fact
- >>> vg = adjust()
- >>> vg['min']
- 0.0
- >>> vg['min'] = 10.0
- >>> vg['min']
- 10.0
- """
- # Make sure we enable Tk
- # Don't use a regular import, to prevent ModuleFinder from picking
- # it up as a dependency when building a .p3d package.
- Valuator = importlib.import_module('direct.tkwidgets.Valuator')
- # Set command if specified
- if command:
- kw['command'] = lambda x: command(*x)
- if parent is None:
- kw['title'] = command.__name__
- kw['dim'] = dim
- # Create toplevel if needed
- if not parent:
- vg = Valuator.ValuatorGroupPanel(parent, **kw)
- else:
- vg = Valuator.ValuatorGroup(parent, **kw)
- vg.pack(expand = 1, fill = 'x')
- return vg
- def difference(a, b):
- """
- difference(list, list):
- """
- if not a: return b
- if not b: return a
- d = []
- for i in a:
- if (i not in b) and (i not in d):
- d.append(i)
- for i in b:
- if (i not in a) and (i not in d):
- d.append(i)
- return d
- def intersection(a, b):
- """
- intersection(list, list):
- """
- if not a: return []
- if not b: return []
- d = []
- for i in a:
- if (i in b) and (i not in d):
- d.append(i)
- for i in b:
- if (i in a) and (i not in d):
- d.append(i)
- return d
- def union(a, b):
- """
- union(list, list):
- """
- # Copy a
- c = a[:]
- for i in b:
- if (i not in c):
- c.append(i)
- return c
- def sameElements(a, b):
- if len(a) != len(b):
- return 0
- for elem in a:
- if elem not in b:
- return 0
- for elem in b:
- if elem not in a:
- return 0
- return 1
- def makeList(x):
- """returns x, converted to a list"""
- if type(x) is list:
- return x
- elif type(x) is tuple:
- return list(x)
- else:
- return [x,]
- def makeTuple(x):
- """returns x, converted to a tuple"""
- if type(x) is list:
- return tuple(x)
- elif type(x) is tuple:
- return x
- else:
- return (x,)
- def list2dict(L, value=None):
- """creates dict using elements of list, all assigned to same value"""
- return dict([(k, value) for k in L])
- def listToIndex2item(L):
- """converts list to dict of list index->list item"""
- d = {}
- for i, item in enumerate(L):
- d[i] = item
- return d
- assert listToIndex2item(['a','b']) == {0: 'a', 1: 'b',}
- def listToItem2index(L):
- """converts list to dict of list item->list index
- This is lossy if there are duplicate list items"""
- d = {}
- for i, item in enumerate(L):
- d[item] = i
- return d
- assert listToItem2index(['a','b']) == {'a': 0, 'b': 1,}
- def invertDict(D, lossy=False):
- """creates a dictionary by 'inverting' D; keys are placed in the new
- dictionary under their corresponding value in the old dictionary.
- It is an error if D contains any duplicate values.
- >>> old = {'key1':1, 'key2':2}
- >>> invertDict(old)
- {1: 'key1', 2: 'key2'}
- """
- n = {}
- for key, value in D.items():
- if not lossy and value in n:
- raise Exception('duplicate key in invertDict: %s' % value)
- n[value] = key
- return n
- def invertDictLossless(D):
- """similar to invertDict, but values of new dict are lists of keys from
- old dict. No information is lost.
- >>> old = {'key1':1, 'key2':2, 'keyA':2}
- >>> invertDictLossless(old)
- {1: ['key1'], 2: ['key2', 'keyA']}
- """
- n = {}
- for key, value in D.items():
- n.setdefault(value, [])
- n[value].append(key)
- return n
- def uniqueElements(L):
- """are all elements of list unique?"""
- return len(L) == len(list2dict(L))
- def disjoint(L1, L2):
- """returns non-zero if L1 and L2 have no common elements"""
- used = dict([(k, None) for k in L1])
- for k in L2:
- if k in used:
- return 0
- return 1
- def contains(whole, sub):
- """
- Return 1 if whole contains sub, 0 otherwise
- """
- if (whole == sub):
- return 1
- for elem in sub:
- # The first item you find not in whole, return 0
- if elem not in whole:
- return 0
- # If you got here, whole must contain sub
- return 1
- def replace(list, old, new, all=0):
- """
- replace 'old' with 'new' in 'list'
- if all == 0, replace first occurrence
- otherwise replace all occurrences
- returns the number of items replaced
- """
- if old not in list:
- return 0
- if not all:
- i = list.index(old)
- list[i] = new
- return 1
- else:
- numReplaced = 0
- for i in xrange(len(list)):
- if list[i] == old:
- numReplaced += 1
- list[i] = new
- return numReplaced
- rad90 = math.pi / 2.
- rad180 = math.pi
- rad270 = 1.5 * math.pi
- rad360 = 2. * math.pi
- def reduceAngle(deg):
- """
- Reduces an angle (in degrees) to a value in [-180..180)
- """
- return (((deg + 180.) % 360.) - 180.)
- def fitSrcAngle2Dest(src, dest):
- """
- given a src and destination angle, returns an equivalent src angle
- that is within [-180..180) of dest
- examples:
- fitSrcAngle2Dest(30, 60) == 30
- fitSrcAngle2Dest(60, 30) == 60
- fitSrcAngle2Dest(0, 180) == 0
- fitSrcAngle2Dest(-1, 180) == 359
- fitSrcAngle2Dest(-180, 180) == 180
- """
- return dest + reduceAngle(src - dest)
- def fitDestAngle2Src(src, dest):
- """
- given a src and destination angle, returns an equivalent dest angle
- that is within [-180..180) of src
- examples:
- fitDestAngle2Src(30, 60) == 60
- fitDestAngle2Src(60, 30) == 30
- fitDestAngle2Src(0, 180) == -180
- fitDestAngle2Src(1, 180) == 180
- """
- return src + (reduceAngle(dest - src))
- def closestDestAngle2(src, dest):
- # The function above didn't seem to do what I wanted. So I hacked
- # this one together. I can't really say I understand it. It's more
- # from impirical observation... GRW
- diff = src - dest
- if diff > 180:
- # if the difference is greater that 180 it's shorter to go the other way
- return dest - 360
- elif diff < -180:
- # or perhaps the OTHER other way...
- return dest + 360
- else:
- # otherwise just go to the original destination
- return dest
- def closestDestAngle(src, dest):
- # The function above didn't seem to do what I wanted. So I hacked
- # this one together. I can't really say I understand it. It's more
- # from impirical observation... GRW
- diff = src - dest
- if diff > 180:
- # if the difference is greater that 180 it's shorter to go the other way
- return src - (diff - 360)
- elif diff < -180:
- # or perhaps the OTHER other way...
- return src - (360 + diff)
- else:
- # otherwise just go to the original destination
- return dest
- class StdoutCapture:
- # redirects stdout to a string
- def __init__(self):
- self._oldStdout = sys.stdout
- sys.stdout = self
- self._string = ''
- def destroy(self):
- sys.stdout = self._oldStdout
- del self._oldStdout
- def getString(self):
- return self._string
- # internal
- def write(self, string):
- self._string = ''.join([self._string, string])
- class StdoutPassthrough(StdoutCapture):
- # like StdoutCapture but also allows output to go through to the OS as normal
- # internal
- def write(self, string):
- self._string = ''.join([self._string, string])
- self._oldStdout.write(string)
- # constant profile defaults
- if __debug__:
- from io import StringIO
- PyUtilProfileDefaultFilename = 'profiledata'
- PyUtilProfileDefaultLines = 80
- PyUtilProfileDefaultSorts = ['cumulative', 'time', 'calls']
- _ProfileResultStr = ''
- def getProfileResultString():
- # if you called profile with 'log' not set to True,
- # you can call this function to get the results as
- # a string
- global _ProfileResultStr
- return _ProfileResultStr
- def profileFunc(callback, name, terse, log=True):
- global _ProfileResultStr
- if 'globalProfileFunc' in builtins.__dict__:
- # rats. Python profiler is not re-entrant...
- base.notify.warning(
- 'PythonUtil.profileStart(%s): aborted, already profiling %s'
- #'\nStack Trace:\n%s'
- % (name, builtins.globalProfileFunc,
- #StackTrace()
- ))
- return
- builtins.globalProfileFunc = callback
- builtins.globalProfileResult = [None]
- prefix = '***** START PROFILE: %s *****' % name
- if log:
- print(prefix)
- startProfile(cmd='globalProfileResult[0]=globalProfileFunc()', callInfo=(not terse), silent=not log)
- suffix = '***** END PROFILE: %s *****' % name
- if log:
- print(suffix)
- else:
- _ProfileResultStr = '%s\n%s\n%s' % (prefix, _ProfileResultStr, suffix)
- result = globalProfileResult[0]
- del builtins.__dict__['globalProfileFunc']
- del builtins.__dict__['globalProfileResult']
- return result
- def profiled(category=None, terse=False):
- """ decorator for profiling functions
- turn categories on and off via "want-profile-categoryName 1"
- e.g.
- @profiled('particles')
- def loadParticles():
- ...
- want-profile-particles 1
- """
- assert type(category) in (str, type(None)), "must provide a category name for @profiled"
- # allow profiling in published versions
- """
- try:
- null = not __dev__
- except:
- null = not __debug__
- if null:
- # if we're not in __dev__, just return the function itself. This
- # results in zero runtime overhead, since decorators are evaluated
- # at module-load.
- def nullDecorator(f):
- return f
- return nullDecorator
- """
- def profileDecorator(f):
- def _profiled(*args, **kArgs):
- name = '(%s) %s from %s' % (category, f.__name__, f.__module__)
- # showbase might not be loaded yet, so don't use
- # base.config. Instead, query the ConfigVariableBool.
- if (category is None) or ConfigVariableBool('want-profile-%s' % category, 0).getValue():
- return profileFunc(Functor(f, *args, **kArgs), name, terse)
- else:
- return f(*args, **kArgs)
- _profiled.__doc__ = f.__doc__
- return _profiled
- return profileDecorator
- # intercept profile-related file operations to avoid disk access
- movedOpenFuncs = []
- movedDumpFuncs = []
- movedLoadFuncs = []
- profileFilenames = set()
- profileFilenameList = Stack()
- profileFilename2file = {}
- profileFilename2marshalData = {}
- def _profileOpen(filename, *args, **kArgs):
- # this is a replacement for the file open() builtin function
- # for use during profiling, to intercept the file open
- # operation used by the Python profiler and profile stats
- # systems
- if filename in profileFilenames:
- # if this is a file related to profiling, create an
- # in-RAM file object
- if filename not in profileFilename2file:
- file = StringIO()
- file._profFilename = filename
- profileFilename2file[filename] = file
- else:
- file = profileFilename2file[filename]
- else:
- file = movedOpenFuncs[-1](filename, *args, **kArgs)
- return file
- def _profileMarshalDump(data, file):
- # marshal.dump doesn't work with StringIO objects
- # simulate it
- if isinstance(file, StringIO) and hasattr(file, '_profFilename'):
- if file._profFilename in profileFilenames:
- profileFilename2marshalData[file._profFilename] = data
- return None
- return movedDumpFuncs[-1](data, file)
- def _profileMarshalLoad(file):
- # marshal.load doesn't work with StringIO objects
- # simulate it
- if isinstance(file, StringIO) and hasattr(file, '_profFilename'):
- if file._profFilename in profileFilenames:
- return profileFilename2marshalData[file._profFilename]
- return movedLoadFuncs[-1](file)
- def _installProfileCustomFuncs(filename):
- assert filename not in profileFilenames
- profileFilenames.add(filename)
- profileFilenameList.push(filename)
- movedOpenFuncs.append(builtins.open)
- builtins.open = _profileOpen
- movedDumpFuncs.append(marshal.dump)
- marshal.dump = _profileMarshalDump
- movedLoadFuncs.append(marshal.load)
- marshal.load = _profileMarshalLoad
- def _getProfileResultFileInfo(filename):
- return (profileFilename2file.get(filename, None),
- profileFilename2marshalData.get(filename, None))
- def _setProfileResultsFileInfo(filename, info):
- f, m = info
- if f:
- profileFilename2file[filename] = f
- if m:
- profileFilename2marshalData[filename] = m
- def _clearProfileResultFileInfo(filename):
- profileFilename2file.pop(filename, None)
- profileFilename2marshalData.pop(filename, None)
- def _removeProfileCustomFuncs(filename):
- assert profileFilenameList.top() == filename
- marshal.load = movedLoadFuncs.pop()
- marshal.dump = movedDumpFuncs.pop()
- builtins.open = movedOpenFuncs.pop()
- profileFilenames.remove(filename)
- profileFilenameList.pop()
- profileFilename2file.pop(filename, None)
- # don't let marshalled data pile up
- profileFilename2marshalData.pop(filename, None)
- # call this from the prompt, and break back out to the prompt
- # to stop profiling
- #
- # OR to do inline profiling, you must make a globally-visible
- # function to be profiled, i.e. to profile 'self.load()', do
- # something like this:
- #
- # def func(self=self):
- # self.load()
- # import builtins
- # builtins.func = func
- # PythonUtil.startProfile(cmd='func()', filename='profileData')
- # del builtins.func
- #
- def _profileWithoutGarbageLeak(cmd, filename):
- # The profile module isn't necessarily installed on every Python
- # installation, so we import it here, instead of in the module
- # scope.
- import profile
- # this is necessary because the profile module creates a memory leak
- Profile = profile.Profile
- statement = cmd
- sort = -1
- retVal = None
- #### COPIED FROM profile.run ####
- prof = Profile()
- try:
- prof = prof.run(statement)
- except SystemExit:
- pass
- if filename is not None:
- prof.dump_stats(filename)
- else:
- #return prof.print_stats(sort) #DCR
- retVal = prof.print_stats(sort) #DCR
- #################################
- # eliminate the garbage leak
- del prof.dispatcher
- return retVal
- def startProfile(filename=PyUtilProfileDefaultFilename,
- lines=PyUtilProfileDefaultLines,
- sorts=PyUtilProfileDefaultSorts,
- silent=0,
- callInfo=1,
- useDisk=False,
- cmd='run()'):
- # uniquify the filename to allow multiple processes to profile simultaneously
- filename = '%s.%s%s' % (filename, randUint31(), randUint31())
- if not useDisk:
- # use a RAM file
- _installProfileCustomFuncs(filename)
- _profileWithoutGarbageLeak(cmd, filename)
- if silent:
- extractProfile(filename, lines, sorts, callInfo)
- else:
- printProfile(filename, lines, sorts, callInfo)
- if not useDisk:
- # discard the RAM file
- _removeProfileCustomFuncs(filename)
- else:
- os.remove(filename)
- # call these to see the results again, as a string or in the log
- def printProfile(filename=PyUtilProfileDefaultFilename,
- lines=PyUtilProfileDefaultLines,
- sorts=PyUtilProfileDefaultSorts,
- callInfo=1):
- import pstats
- s = pstats.Stats(filename)
- s.strip_dirs()
- for sort in sorts:
- s.sort_stats(sort)
- s.print_stats(lines)
- if callInfo:
- s.print_callees(lines)
- s.print_callers(lines)
- # same args as printProfile
- def extractProfile(*args, **kArgs):
- global _ProfileResultStr
- # capture print output
- sc = StdoutCapture()
- # print the profile output, redirected to the result string
- printProfile(*args, **kArgs)
- # make a copy of the print output
- _ProfileResultStr = sc.getString()
- # restore stdout to what it was before
- sc.destroy()
- def getSetterName(valueName, prefix='set'):
- # getSetterName('color') -> 'setColor'
- # getSetterName('color', 'get') -> 'getColor'
- return '%s%s%s' % (prefix, valueName[0].upper(), valueName[1:])
- def getSetter(targetObj, valueName, prefix='set'):
- # getSetter(smiley, 'pos') -> smiley.setPos
- return getattr(targetObj, getSetterName(valueName, prefix))
- def mostDerivedLast(classList):
- """pass in list of classes. sorts list in-place, with derived classes
- appearing after their bases"""
- class ClassSortKey(object):
- __slots__ = 'classobj',
- def __init__(self, classobj):
- self.classobj = classobj
- def __lt__(self, other):
- return issubclass(other.classobj, self.classobj)
- classList.sort(key=ClassSortKey)
- def bound(value, bound1, bound2):
- """
- returns value if value is between bound1 and bound2
- otherwise returns bound that is closer to value
- """
- if bound1 > bound2:
- return min(max(value, bound2), bound1)
- else:
- return min(max(value, bound1), bound2)
- clamp = bound
- def lerp(v0, v1, t):
- """
- returns a value lerped between v0 and v1, according to t
- t == 0 maps to v0, t == 1 maps to v1
- """
- return v0 + ((v1 - v0) * t)
- def getShortestRotation(start, end):
- """
- Given two heading values, return a tuple describing
- the shortest interval from 'start' to 'end'. This tuple
- can be used to lerp a camera between two rotations
- while avoiding the 'spin' problem.
- """
- start, end = start % 360, end % 360
- if abs(end - start) > 180:
- if end < start:
- end += 360
- else:
- start += 360
- return (start, end)
- def average(*args):
- """ returns simple average of list of values """
- val = 0.
- for arg in args:
- val += arg
- return val / len(args)
- class Averager:
- def __init__(self, name):
- self._name = name
- self.reset()
- def reset(self):
- self._total = 0.
- self._count = 0
- def addValue(self, value):
- self._total += value
- self._count += 1
- def getAverage(self):
- return self._total / self._count
- def getCount(self):
- return self._count
- def addListsByValue(a, b):
- """
- returns a new array containing the sums of the two array arguments
- (c[0] = a[0 + b[0], etc.)
- """
- c = []
- for x, y in zip(a, b):
- c.append(x + y)
- return c
- def boolEqual(a, b):
- """
- returns true if a and b are both true or both false.
- returns false otherwise
- (a.k.a. xnor -- eXclusive Not OR).
- """
- return (a and b) or not (a or b)
- def lineupPos(i, num, spacing):
- """
- use to line up a series of 'num' objects, in one dimension,
- centered around zero
- 'i' is the index of the object in the lineup
- 'spacing' is the amount of space between objects in the lineup
- """
- assert num >= 1
- assert i >= 0 and i < num
- pos = float(i) * spacing
- return pos - ((float(spacing) * (num-1))/2.)
- def formatElapsedSeconds(seconds):
- """
- Returns a string of the form "mm:ss" or "hh:mm:ss" or "n days",
- representing the indicated elapsed time in seconds.
- """
- sign = ''
- if seconds < 0:
- seconds = -seconds
- sign = '-'
- # We use math.floor() instead of casting to an int, so we avoid
- # problems with numbers that are too large to represent as
- # type int.
- seconds = math.floor(seconds)
- hours = math.floor(seconds / (60 * 60))
- if hours > 36:
- days = math.floor((hours + 12) / 24)
- return "%s%d days" % (sign, days)
- seconds -= hours * (60 * 60)
- minutes = (int)(seconds / 60)
- seconds -= minutes * 60
- if hours != 0:
- return "%s%d:%02d:%02d" % (sign, hours, minutes, seconds)
- else:
- return "%s%d:%02d" % (sign, minutes, seconds)
- def solveQuadratic(a, b, c):
- # quadratic equation: ax^2 + bx + c = 0
- # quadratic formula: x = [-b +/- sqrt(b^2 - 4ac)] / 2a
- # returns None, root, or [root1, root2]
- # a cannot be zero.
- if a == 0.:
- return None
- # calculate the determinant (b^2 - 4ac)
- D = (b * b) - (4. * a * c)
- if D < 0:
- # there are no solutions (sqrt(negative number) is undefined)
- return None
- elif D == 0:
- # only one root
- return (-b) / (2. * a)
- else:
- # OK, there are two roots
- sqrtD = math.sqrt(D)
- twoA = 2. * a
- root1 = ((-b) - sqrtD) / twoA
- root2 = ((-b) + sqrtD) / twoA
- return [root1, root2]
- if __debug__:
- def stackEntryInfo(depth=0, baseFileName=1):
- """
- returns the sourcefilename, line number, and function name of
- an entry in the stack.
- 'depth' is how far back to go in the stack; 0 is the caller of this
- function, 1 is the function that called the caller of this function, etc.
- by default, strips off the path of the filename; override with baseFileName
- returns (fileName, lineNum, funcName) --> (string, int, string)
- returns (None, None, None) on error
- """
- import inspect
- try:
- stack = None
- frame = None
- try:
- stack = inspect.stack()
- # add one to skip the frame associated with this function
- frame = stack[depth+1]
- filename = frame[1]
- if baseFileName:
- filename = os.path.basename(filename)
- lineNum = frame[2]
- funcName = frame[3]
- result = (filename, lineNum, funcName)
- finally:
- del stack
- del frame
- except:
- result = (None, None, None)
- return result
- def lineInfo(baseFileName=1):
- """
- returns the sourcefilename, line number, and function name of the
- code that called this function
- (answers the question: 'hey lineInfo, where am I in the codebase?')
- see stackEntryInfo, above, for info on 'baseFileName' and return types
- """
- return stackEntryInfo(1, baseFileName)
- def callerInfo(baseFileName=1, howFarBack=0):
- """
- returns the sourcefilename, line number, and function name of the
- caller of the function that called this function
- (answers the question: 'hey callerInfo, who called me?')
- see stackEntryInfo, above, for info on 'baseFileName' and return types
- """
- return stackEntryInfo(2+howFarBack, baseFileName)
- def lineTag(baseFileName=1, verbose=0, separator=':'):
- """
- returns a string containing the sourcefilename and line number
- of the code that called this function
- (equivalent to lineInfo, above, with different return type)
- see stackEntryInfo, above, for info on 'baseFileName'
- if 'verbose' is false, returns a compact string of the form
- 'fileName:lineNum:funcName'
- if 'verbose' is true, returns a longer string that matches the
- format of Python stack trace dumps
- returns empty string on error
- """
- fileName, lineNum, funcName = callerInfo(baseFileName)
- if fileName is None:
- return ''
- if verbose:
- return 'File "%s", line %s, in %s' % (fileName, lineNum, funcName)
- else:
- return '%s%s%s%s%s' % (fileName, separator, lineNum, separator,
- funcName)
- def findPythonModule(module):
- # Look along the python load path for the indicated filename.
- # Returns the located pathname, or None if the filename is not
- # found.
- filename = module + '.py'
- for dir in sys.path:
- pathname = os.path.join(dir, filename)
- if os.path.exists(pathname):
- return pathname
- return None
- def clampScalar(value, a, b):
- # calling this ought to be faster than calling both min and max
- if a < b:
- if value < a:
- return a
- elif value > b:
- return b
- else:
- return value
- else:
- if value < b:
- return b
- elif value > a:
- return a
- else:
- return value
- def weightedChoice(choiceList, rng=random.random, sum=None):
- """given a list of (weight, item) pairs, chooses an item based on the
- weights. rng must return 0..1. if you happen to have the sum of the
- weights, pass it in 'sum'."""
- # TODO: add support for dicts
- if sum is None:
- sum = 0.
- for weight, item in choiceList:
- sum += weight
- rand = rng()
- accum = rand * sum
- for weight, item in choiceList:
- accum -= weight
- if accum <= 0.:
- return item
- # rand is ~1., and floating-point error prevented accum from hitting 0.
- # Or you passed in a 'sum' that was was too large.
- # Return the last item.
- return item
- def randFloat(a, b=0., rng=random.random):
- """returns a random float in [a, b]
- call with single argument to generate random float between arg and zero
- """
- return lerp(a, b, rng())
- def normalDistrib(a, b, gauss=random.gauss):
- """
- NOTE: assumes a < b
- Returns random number between a and b, using gaussian distribution, with
- mean=avg(a, b), and a standard deviation that fits ~99.7% of the curve
- between a and b.
- For ease of use, outlying results are re-computed until result is in [a, b]
- This should fit the remaining .3% of the curve that lies outside [a, b]
- uniformly onto the curve inside [a, b]
- ------------------------------------------------------------------------
- http://www-stat.stanford.edu/~naras/jsm/NormalDensity/NormalDensity.html
- The 68-95-99.7% Rule
- ====================
- All normal density curves satisfy the following property which is often
- referred to as the Empirical Rule:
- 68% of the observations fall within 1 standard deviation of the mean.
- 95% of the observations fall within 2 standard deviations of the mean.
- 99.7% of the observations fall within 3 standard deviations of the mean.
- Thus, for a normal distribution, almost all values lie within 3 standard
- deviations of the mean.
- ------------------------------------------------------------------------
- In calculating our standard deviation, we divide (b-a) by 6, since the
- 99.7% figure includes 3 standard deviations _on_either_side_ of the mean.
- """
- while True:
- r = gauss((a+b)*.5, (b-a)/6.)
- if (r >= a) and (r <= b):
- return r
- def weightedRand(valDict, rng=random.random):
- """
- pass in a dictionary with a selection -> weight mapping. Eg.
- {"Choice 1": 10,
- "Choice 2": 30,
- "bear": 100}
- -Weights need not add up to any particular value.
- -The actual selection will be returned.
- """
- selections = list(valDict.keys())
- weights = list(valDict.values())
- totalWeight = 0
- for weight in weights:
- totalWeight += weight
- # get a random value between 0 and the total of the weights
- randomWeight = rng() * totalWeight
- # find the index that corresponds with this weight
- for i in range(len(weights)):
- totalWeight -= weights[i]
- if totalWeight <= randomWeight:
- return selections[i]
- assert True, "Should never get here"
- return selections[-1]
- def randUint31(rng=random.random):
- """returns a random integer in [0..2^31).
- rng must return float in [0..1]"""
- return int(rng() * 0x7FFFFFFF)
- def randInt32(rng=random.random):
- """returns a random integer in [-2147483648..2147483647].
- rng must return float in [0..1]
- """
- i = int(rng() * 0x7FFFFFFF)
- if rng() < .5:
- i *= -1
- return i
- class SerialNumGen:
- """generates serial numbers"""
- def __init__(self, start=None):
- if start is None:
- start = 0
- self.__counter = start-1
- def next(self):
- self.__counter += 1
- return self.__counter
- class SerialMaskedGen(SerialNumGen):
- def __init__(self, mask, start=None):
- self._mask = mask
- SerialNumGen.__init__(self, start)
- def next(self):
- v = SerialNumGen.next(self)
- return v & self._mask
- _serialGen = SerialNumGen()
- def serialNum():
- global _serialGen
- return _serialGen.next()
- def uniqueName(name):
- global _serialGen
- return '%s-%s' % (name, _serialGen.next())
- class EnumIter:
- def __init__(self, enum):
- self._values = list(enum._stringTable.keys())
- self._index = 0
- def __iter__(self):
- return self
- def __next__(self):
- if self._index >= len(self._values):
- raise StopIteration
- self._index += 1
- return self._values[self._index-1]
- next = __next__
- class Enum:
- """Pass in list of strings or string of comma-separated strings.
- Items are accessible as instance.item, and are assigned unique,
- increasing integer values. Pass in integer for 'start' to override
- starting value.
- Example:
- >>> colors = Enum('red, green, blue')
- >>> colors.red
- 0
- >>> colors.green
- 1
- >>> colors.blue
- 2
- >>> colors.getString(colors.red)
- 'red'
- """
- if __debug__:
- # chars that cannot appear within an item string.
- def _checkValidIdentifier(item):
- import string
- invalidChars = string.whitespace + string.punctuation
- invalidChars = invalidChars.replace('_', '')
- invalidFirstChars = invalidChars+string.digits
- if item[0] in invalidFirstChars:
- raise SyntaxError("Enum '%s' contains invalid first char" %
- item)
- if not disjoint(item, invalidChars):
- for char in item:
- if char in invalidChars:
- raise SyntaxError(
- "Enum\n'%s'\ncontains illegal char '%s'" %
- (item, char))
- return 1
- _checkValidIdentifier = staticmethod(_checkValidIdentifier)
- def __init__(self, items, start=0):
- if isinstance(items, str):
- items = items.split(',')
- self._stringTable = {}
- # make sure we don't overwrite an existing element of the class
- assert self._checkExistingMembers(items)
- assert uniqueElements(items)
- i = start
- for item in items:
- # remove leading/trailing whitespace
- item = item.strip()
- # is there anything left?
- if len(item) == 0:
- continue
- # make sure there are no invalid characters
- assert Enum._checkValidIdentifier(item)
- self.__dict__[item] = i
- self._stringTable[i] = item
- i += 1
- def __iter__(self):
- return EnumIter(self)
- def hasString(self, string):
- return string in set(self._stringTable.values())
- def fromString(self, string):
- if self.hasString(string):
- return self.__dict__[string]
- # throw an error
- {}[string]
- def getString(self, value):
- return self._stringTable[value]
- def __contains__(self, value):
- return value in self._stringTable
- def __len__(self):
- return len(self._stringTable)
- def copyTo(self, obj):
- # copies all members onto obj
- for name, value in self._stringTable:
- setattr(obj, name, value)
- if __debug__:
- def _checkExistingMembers(self, items):
- for item in items:
- if hasattr(self, item):
- return 0
- return 1
- ############################################################
- # class: Singleton
- # Purpose: This provides a base metaclass for all classes
- # that require one and only one instance.
- #
- # Example: class mySingleton:
- # __metaclass__ = PythonUtil.Singleton
- # def __init__(self, ...):
- # ...
- #
- # Note: This class is based on Python's New-Style Class
- # design. An error will occur if a defined class
- # attemps to inherit from a Classic-Style Class only,
- # ie: class myClassX:
- # def __init__(self, ...):
- # ...
- #
- # class myNewClassX(myClassX):
- # __metaclass__ = PythonUtil.Singleton
- # def __init__(self, ...):
- # myClassX.__init__(self, ...)
- # ...
- #
- # This causes problems because myNewClassX is a
- # New-Style class that inherits from only a
- # Classic-Style base class. There are two ways
- # simple ways to resolve this issue.
- #
- # First, if possible, make myClassX a
- # New-Style class by inheriting from object
- # object. IE: class myClassX(object):
- #
- # If for some reason that is not an option, make
- # myNewClassX inherit from object and myClassX.
- # IE: class myNewClassX(object, myClassX):
- ############################################################
- class Singleton(type):
- def __init__(cls, name, bases, dic):
- super(Singleton, cls).__init__(name, bases, dic)
- cls.instance=None
- def __call__(cls, *args, **kw):
- if cls.instance is None:
- cls.instance=super(Singleton, cls).__call__(*args, **kw)
- return cls.instance
- class SingletonError(ValueError):
- """ Used to indicate an inappropriate value for a Singleton."""
- def printListEnumGen(l):
- # log each individual item with a number in front of it
- digits = 0
- n = len(l)
- while n > 0:
- digits += 1
- n //= 10
- format = '%0' + '%s' % digits + 'i:%s'
- for i in range(len(l)):
- print(format % (i, l[i]))
- yield None
- def printListEnum(l):
- for result in printListEnumGen(l):
- pass
- # base class for all Panda C++ objects
- # libdtoolconfig doesn't seem to have this, grab it off of TypedObject
- dtoolSuperBase = None
- def _getDtoolSuperBase():
- global dtoolSuperBase
- from panda3d.core import TypedObject
- dtoolSuperBase = TypedObject.__bases__[0]
- assert dtoolSuperBase.__name__ == 'DTOOL_SUPER_BASE'
- safeReprNotify = None
- def _getSafeReprNotify():
- global safeReprNotify
- from direct.directnotify.DirectNotifyGlobal import directNotify
- safeReprNotify = directNotify.newCategory("safeRepr")
- return safeReprNotify
- def safeRepr(obj):
- global dtoolSuperBase
- if dtoolSuperBase is None:
- _getDtoolSuperBase()
- global safeReprNotify
- if safeReprNotify is None:
- _getSafeReprNotify()
- if isinstance(obj, dtoolSuperBase):
- # repr of C++ object could crash, particularly if the object has been deleted
- # log that we're calling repr
- safeReprNotify.info('calling repr on instance of %s.%s' % (obj.__class__.__module__, obj.__class__.__name__))
- sys.stdout.flush()
- try:
- return repr(obj)
- except:
- return '<** FAILED REPR OF %s instance at %s **>' % (obj.__class__.__name__, hex(id(obj)))
- def safeReprTypeOnFail(obj):
- global dtoolSuperBase
- if dtoolSuperBase is None:
- _getDtoolSuperBase()
- global safeReprNotify
- if safeReprNotify is None:
- _getSafeReprNotify()
- if isinstance(obj, dtoolSuperBase):
- return type(obj)
- try:
- return repr(obj)
- except:
- return '<** FAILED REPR OF %s instance at %s **>' % (obj.__class__.__name__, hex(id(obj)))
- def fastRepr(obj, maxLen=200, strFactor=10, _visitedIds=None):
- """ caps the length of iterable types, so very large objects will print faster.
- also prevents infinite recursion """
- try:
- if _visitedIds is None:
- _visitedIds = set()
- if id(obj) in _visitedIds:
- return '<ALREADY-VISITED %s>' % itype(obj)
- if type(obj) in (tuple, list):
- s = ''
- s += {tuple: '(',
- list: '[',}[type(obj)]
- if maxLen is not None and len(obj) > maxLen:
- o = obj[:maxLen]
- ellips = '...'
- else:
- o = obj
- ellips = ''
- _visitedIds.add(id(obj))
- for item in o:
- s += fastRepr(item, maxLen, _visitedIds=_visitedIds)
- s += ', '
- _visitedIds.remove(id(obj))
- s += ellips
- s += {tuple: ')',
- list: ']',}[type(obj)]
- return s
- elif type(obj) is dict:
- s = '{'
- if maxLen is not None and len(obj) > maxLen:
- o = list(obj.keys())[:maxLen]
- ellips = '...'
- else:
- o = list(obj.keys())
- ellips = ''
- _visitedIds.add(id(obj))
- for key in o:
- value = obj[key]
- s += '%s: %s, ' % (fastRepr(key, maxLen, _visitedIds=_visitedIds),
- fastRepr(value, maxLen, _visitedIds=_visitedIds))
- _visitedIds.remove(id(obj))
- s += ellips
- s += '}'
- return s
- elif type(obj) is str:
- if maxLen is not None:
- maxLen *= strFactor
- if maxLen is not None and len(obj) > maxLen:
- return safeRepr(obj[:maxLen])
- else:
- return safeRepr(obj)
- else:
- r = safeRepr(obj)
- maxLen *= strFactor
- if len(r) > maxLen:
- r = r[:maxLen]
- return r
- except:
- return '<** FAILED REPR OF %s **>' % obj.__class__.__name__
- def convertTree(objTree, idList):
- newTree = {}
- for key in list(objTree.keys()):
- obj = (idList[key],)
- newTree[obj] = {}
- r_convertTree(objTree[key], newTree[obj], idList)
- return newTree
- def r_convertTree(oldTree, newTree, idList):
- for key in list(oldTree.keys()):
- obj = idList.get(key)
- if(not obj):
- continue
- obj = str(obj)[:100]
- newTree[obj] = {}
- r_convertTree(oldTree[key], newTree[obj], idList)
- def pretty_print(tree):
- for name in tree.keys():
- print(name)
- r_pretty_print(tree[name], 0)
- def r_pretty_print(tree, num):
- num+=1
- for name in tree.keys():
- print(" "*num,name)
- r_pretty_print(tree[name],num)
- def isDefaultValue(x):
- return x == type(x)()
- def appendStr(obj, st):
- """adds a string onto the __str__ output of an instance"""
- def appendedStr(oldStr, st, self):
- return oldStr() + st
- oldStr = getattr(obj, '__str__', None)
- if oldStr is None:
- def stringer(s):
- return s
- oldStr = Functor(stringer, str(obj))
- stringer = None
- obj.__str__ = types.MethodType(Functor(appendedStr, oldStr, st), obj, obj.__class__)
- appendedStr = None
- return obj
- class ScratchPad:
- """empty class to stick values onto"""
- def __init__(self, **kArgs):
- for key, value in kArgs.items():
- setattr(self, key, value)
- self._keys = set(kArgs.keys())
- def add(self, **kArgs):
- for key, value in kArgs.items():
- setattr(self, key, value)
- self._keys.update(list(kArgs.keys()))
- def destroy(self):
- for key in self._keys:
- delattr(self, key)
- # allow dict [] syntax
- def __getitem__(self, itemName):
- return getattr(self, itemName)
- def get(self, itemName, default=None):
- return getattr(self, itemName, default)
- # allow 'in'
- def __contains__(self, itemName):
- return itemName in self._keys
- class Sync:
- _SeriesGen = SerialNumGen()
- def __init__(self, name, other=None):
- self._name = name
- if other is None:
- self._series = self._SeriesGen.next()
- self._value = 0
- else:
- self._series = other._series
- self._value = other._value
- def invalidate(self):
- self._value = None
- def change(self):
- self._value += 1
- def sync(self, other):
- if (self._series != other._series) or (self._value != other._value):
- self._series = other._series
- self._value = other._value
- return True
- else:
- return False
- def isSynced(self, other):
- return ((self._series == other._series) and
- (self._value == other._value))
- def __repr__(self):
- return '%s(%s)<family=%s,value=%s>' % (self.__class__.__name__,
- self._name, self._series, self._value)
- def itype(obj):
- # version of type that gives more complete information about instance types
- global dtoolSuperBase
- t = type(obj)
- if sys.version_info < (3, 0) and t is types.InstanceType:
- return "<type 'instance' of <class %s>>" % (obj.__class__)
- else:
- # C++ object instances appear to be types via type()
- # check if this is a C++ object
- if dtoolSuperBase is None:
- _getDtoolSuperBase()
- if isinstance(obj, dtoolSuperBase):
- return "<type 'instance' of %s>" % (obj.__class__)
- return t
- def deeptype(obj, maxLen=100, _visitedIds=None):
- if _visitedIds is None:
- _visitedIds = set()
- if id(obj) in _visitedIds:
- return '<ALREADY-VISITED %s>' % itype(obj)
- t = type(obj)
- if t in (tuple, list):
- s = ''
- s += {tuple: '(',
- list: '[',}[type(obj)]
- if maxLen is not None and len(obj) > maxLen:
- o = obj[:maxLen]
- ellips = '...'
- else:
- o = obj
- ellips = ''
- _visitedIds.add(id(obj))
- for item in o:
- s += deeptype(item, maxLen, _visitedIds=_visitedIds)
- s += ', '
- _visitedIds.remove(id(obj))
- s += ellips
- s += {tuple: ')',
- list: ']',}[type(obj)]
- return s
- elif type(obj) is dict:
- s = '{'
- if maxLen is not None and len(obj) > maxLen:
- o = list(obj.keys())[:maxLen]
- ellips = '...'
- else:
- o = list(obj.keys())
- ellips = ''
- _visitedIds.add(id(obj))
- for key in o:
- value = obj[key]
- s += '%s: %s, ' % (deeptype(key, maxLen, _visitedIds=_visitedIds),
- deeptype(value, maxLen, _visitedIds=_visitedIds))
- _visitedIds.remove(id(obj))
- s += ellips
- s += '}'
- return s
- else:
- return str(itype(obj))
- def getNumberedTypedString(items, maxLen=5000, numPrefix=''):
- """get a string that has each item of the list on its own line,
- and each item is numbered on the left from zero"""
- digits = 0
- n = len(items)
- while n > 0:
- digits += 1
- n //= 10
- digits = digits
- format = numPrefix + '%0' + '%s' % digits + 'i:%s \t%s'
- first = True
- s = ''
- snip = '<SNIP>'
- for i in xrange(len(items)):
- if not first:
- s += '\n'
- first = False
- objStr = fastRepr(items[i])
- if len(objStr) > maxLen:
- objStr = '%s%s' % (objStr[:(maxLen-len(snip))], snip)
- s += format % (i, itype(items[i]), objStr)
- return s
- def getNumberedTypedSortedString(items, maxLen=5000, numPrefix=''):
- """get a string that has each item of the list on its own line,
- the items are stringwise-sorted, and each item is numbered on
- the left from zero"""
- digits = 0
- n = len(items)
- while n > 0:
- digits += 1
- n //= 10
- digits = digits
- format = numPrefix + '%0' + '%s' % digits + 'i:%s \t%s'
- snip = '<SNIP>'
- strs = []
- for item in items:
- objStr = fastRepr(item)
- if len(objStr) > maxLen:
- objStr = '%s%s' % (objStr[:(maxLen-len(snip))], snip)
- strs.append(objStr)
- first = True
- s = ''
- strs.sort()
- for i in xrange(len(strs)):
- if not first:
- s += '\n'
- first = False
- objStr = strs[i]
- s += format % (i, itype(items[i]), strs[i])
- return s
- def printNumberedTyped(items, maxLen=5000):
- """print out each item of the list on its own line,
- with each item numbered on the left from zero"""
- digits = 0
- n = len(items)
- while n > 0:
- digits += 1
- n //= 10
- digits = digits
- format = '%0' + '%s' % digits + 'i:%s \t%s'
- for i in xrange(len(items)):
- objStr = fastRepr(items[i])
- if len(objStr) > maxLen:
- snip = '<SNIP>'
- objStr = '%s%s' % (objStr[:(maxLen-len(snip))], snip)
- print(format % (i, itype(items[i]), objStr))
- def printNumberedTypesGen(items, maxLen=5000):
- digits = 0
- n = len(items)
- while n > 0:
- digits += 1
- n //= 10
- digits = digits
- format = '%0' + '%s' % digits + 'i:%s'
- for i in xrange(len(items)):
- print(format % (i, itype(items[i])))
- yield None
- def printNumberedTypes(items, maxLen=5000):
- """print out the type of each item of the list on its own line,
- with each item numbered on the left from zero"""
- for result in printNumberedTypesGen(items, maxLen):
- yield result
- class DelayedCall:
- """ calls a func after a specified delay """
- def __init__(self, func, name=None, delay=None):
- if name is None:
- name = 'anonymous'
- if delay is None:
- delay = .01
- self._func = func
- self._taskName = 'DelayedCallback-%s' % name
- self._delay = delay
- self._finished = False
- self._addDoLater()
- def destroy(self):
- self._finished = True
- self._removeDoLater()
- def finish(self):
- if not self._finished:
- self._doCallback()
- self.destroy()
- def _addDoLater(self):
- taskMgr.doMethodLater(self._delay, self._doCallback, self._taskName)
- def _removeDoLater(self):
- taskMgr.remove(self._taskName)
- def _doCallback(self, task):
- self._finished = True
- func = self._func
- del self._func
- func()
- class FrameDelayedCall:
- """ calls a func after N frames """
- def __init__(self, name, callback, frames=None, cancelFunc=None):
- # checkFunc is optional; called every frame, if returns True, FrameDelay is cancelled
- # and callback is not called
- if frames is None:
- frames = 1
- self._name = name
- self._frames = frames
- self._callback = callback
- self._cancelFunc = cancelFunc
- self._taskName = uniqueName('%s-%s' % (self.__class__.__name__, self._name))
- self._finished = False
- self._startTask()
- def destroy(self):
- self._finished = True
- self._stopTask()
- def finish(self):
- if not self._finished:
- self._finished = True
- self._callback()
- self.destroy()
- def _startTask(self):
- taskMgr.add(self._frameTask, self._taskName)
- self._counter = 0
- def _stopTask(self):
- taskMgr.remove(self._taskName)
- def _frameTask(self, task):
- if self._cancelFunc and self._cancelFunc():
- self.destroy()
- return task.done
- self._counter += 1
- if self._counter >= self._frames:
- self.finish()
- return task.done
- return task.cont
- class DelayedFunctor:
- """ Waits for this object to be called, then calls supplied functor after a delay.
- Effectively inserts a time delay between the caller and the functor. """
- def __init__(self, functor, name=None, delay=None):
- self._functor = functor
- self._name = name
- # FunctionInterval requires __name__
- self.__name__ = self._name
- self._delay = delay
- def _callFunctor(self):
- cb = Functor(self._functor, *self._args, **self._kwArgs)
- del self._functor
- del self._name
- del self._delay
- del self._args
- del self._kwArgs
- del self._delayedCall
- del self.__name__
- cb()
- def __call__(self, *args, **kwArgs):
- self._args = args
- self._kwArgs = kwArgs
- self._delayedCall = DelayedCall(self._callFunctor, self._name, self._delay)
- class SubframeCall:
- """Calls a callback at a specific time during the frame using the
- task system"""
- def __init__(self, functor, taskPriority, name=None):
- self._functor = functor
- self._name = name
- self._taskName = uniqueName('SubframeCall-%s' % self._name)
- taskMgr.add(self._doCallback,
- self._taskName,
- priority=taskPriority)
- def _doCallback(self, task):
- functor = self._functor
- del self._functor
- functor()
- del self._name
- self._taskName = None
- return task.done
- def cleanup(self):
- if (self._taskName):
- taskMgr.remove(self._taskName)
- self._taskName = None
- class PStatScope:
- collectors = {}
- def __init__(self, level = None):
- self.levels = []
- if level:
- self.levels.append(level)
- def copy(self, push = None):
- c = PStatScope()
- c.levels = self.levels[:]
- if push:
- c.push(push)
- return c
- def __repr__(self):
- return 'PStatScope - \'%s\'' % (self,)
- def __str__(self):
- return ':'.join(self.levels)
- def push(self, level):
- self.levels.append(level.replace('_',''))
- def pop(self):
- return self.levels.pop()
- def start(self, push = None):
- if push:
- self.push(push)
- pass
- self.getCollector().start()
- def stop(self, pop = False):
- self.getCollector().stop()
- if pop:
- self.pop()
- def getCollector(self):
- label = str(self)
- if label not in self.collectors:
- from panda3d.core import PStatCollector
- self.collectors[label] = PStatCollector(label)
- pass
- # print ' ',self.collectors[label]
- return self.collectors[label]
- def pstatcollect(scope, level = None):
- def decorator(f):
- return f
- try:
- if not (__dev__ or ConfigVariableBool('force-pstatcollect', False)) or \
- not scope:
- return decorator
- def decorator(f):
- def wrap(*args, **kw):
- scope.start(push = (level or f.__name__))
- val = f(*args, **kw)
- scope.stop(pop = True)
- return val
- return wrap
- pass
- except:
- pass
- return decorator
- __report_indent = 0
- def report(types = [], prefix = '', xform = None, notifyFunc = None, dConfigParam = []):
- """
- This is a decorator generating function. Use is similar to
- a @decorator, except you must be sure to call it as a function.
- It actually returns the decorator which is then used to transform
- your decorated function. Confusing at first, I know.
- Decoration occurs at function definition time.
- If __dev__ is not defined, or resolves to False, this function
- has no effect and no wrapping/transform occurs. So in production,
- it's as if the report has been asserted out.
- Parameters::
- types : A subset list of ['timeStamp', 'frameCount', 'avLocation']
- This allows you to specify certain useful bits of info.
- module: Prints the module that this report statement
- can be found in.
- args: Prints the arguments as they were passed to
- this function.
- timeStamp: Adds the current frame time to the output.
- deltaStamp: Adds the current AI synched frame time to
- the output
- frameCount: Adds the current frame count to the output.
- Usually cleaner than the timeStamp output.
- avLocation: Adds the localAvatar's network location
- to the output. Useful for interest debugging.
- interests: Prints the current interest state after the
- report.
- stackTrace: Prints a stack trace after the report.
- prefix: Optional string to prepend to output, just before the function.
- Allows for easy grepping and is useful when merging AI/Client
- reports into a single file.
- xform: Optional callback that accepts a single parameter: argument 0 to
- the decorated function. (assumed to be 'self')
- It should return a value to be inserted into the report output string.
- notifyFunc: A notify function such as info, debug, warning, etc.
- By default the report will be printed to stdout. This
- will allow you send the report to a designated 'notify'
- output.
- dConfigParam: A list of Config.prc string variables.
- By default the report will always print. If you
- specify this param, it will only print if one of the
- specified config strings resolve to True.
- """
- def indent(str):
- global __report_indent
- return ' '*__report_indent+str
- def decorator(f):
- return f
- try:
- if not (__dev__ or config.GetBool('force-reports', 0)):
- return decorator
- # determine whether we should use the decorator
- # based on the value of dConfigParam.
- dConfigParamList = []
- doPrint = False
- if not dConfigParam:
- doPrint = True
- else:
- if not isinstance(dConfigParam, (list,tuple)):
- dConfigParams = (dConfigParam,)
- else:
- dConfigParams = dConfigParam
- dConfigParamList = [param for param in dConfigParams \
- if config.GetBool('want-%s-report' % (param,), 0)]
- doPrint = bool(dConfigParamList)
- pass
- if not doPrint:
- return decorator
- # Determine any prefixes defined in our Config.prc.
- if prefix:
- prefixes = set([prefix])
- else:
- prefixes = set()
- pass
- for param in dConfigParamList:
- prefix = config.GetString('prefix-%s-report' % (param,), '')
- if prefix:
- prefixes.add(prefix)
- pass
- pass
- except NameError as e:
- return decorator
- globalClockDelta = importlib.import_module("direct.distributed.ClockDelta").globalClockDelta
- def decorator(f):
- def wrap(*args,**kwargs):
- if args:
- rArgs = [args[0].__class__.__name__ + ', ']
- else:
- rArgs = []
- if 'args' in types:
- rArgs += [repr(x)+', ' for x in args[1:]] + \
- [ x + ' = ' + '%s, ' % repr(y) for x,y in kwargs.items()]
- if not rArgs:
- rArgs = '()'
- else:
- rArgs = '(' + reduce(str.__add__,rArgs)[:-2] + ')'
- outStr = '%s%s' % (f.__name__, rArgs)
- # Insert prefix place holder, if needed
- if prefixes:
- outStr = '%%s %s' % (outStr,)
- if 'module' in types:
- outStr = '%s {M:%s}' % (outStr, f.__module__.split('.')[-1])
- if 'frameCount' in types:
- outStr = '%-8d : %s' % (globalClock.getFrameCount(), outStr)
- if 'timeStamp' in types:
- outStr = '%-8.3f : %s' % (globalClock.getFrameTime(), outStr)
- if 'deltaStamp' in types:
- outStr = '%-8.2f : %s' % (globalClock.getRealTime() - \
- globalClockDelta.delta, outStr)
- if 'avLocation' in types:
- outStr = '%s : %s' % (outStr, str(localAvatar.getLocation()))
- if xform:
- outStr = '%s : %s' % (outStr, xform(args[0]))
- if prefixes:
- # This will print the same report once for each prefix
- for prefix in prefixes:
- if notifyFunc:
- notifyFunc(outStr % (prefix,))
- else:
- print(indent(outStr % (prefix,)))
- else:
- if notifyFunc:
- notifyFunc(outStr)
- else:
- print(indent(outStr))
- if 'interests' in types:
- base.cr.printInterestSets()
- if 'stackTrace' in types:
- print(StackTrace())
- global __report_indent
- rVal = None
- try:
- __report_indent += 1
- rVal = f(*args,**kwargs)
- finally:
- __report_indent -= 1
- if rVal is not None:
- print(indent(' -> '+repr(rVal)))
- pass
- pass
- return rVal
- wrap.__name__ = f.__name__
- wrap.__dict__ = f.__dict__
- wrap.__doc__ = f.__doc__
- wrap.__module__ = f.__module__
- return wrap
- return decorator
- def getBase():
- try:
- return base
- except:
- return simbase
- def getRepository():
- try:
- return base.cr
- except:
- return simbase.air
- exceptionLoggedNotify = None
- if __debug__:
- def exceptionLogged(append=True):
- """decorator that outputs the function name and all arguments
- if an exception passes back through the stack frame
- if append is true, string is appended to the __str__ output of
- the exception. if append is false, string is printed to the log
- directly. If the output will take up many lines, it's recommended
- to set append to False so that the exception stack is not hidden
- by the output of this decorator.
- """
- try:
- null = not __dev__
- except:
- null = not __debug__
- if null:
- # if we're not in __dev__, just return the function itself. This
- # results in zero runtime overhead, since decorators are evaluated
- # at module-load.
- def nullDecorator(f):
- return f
- return nullDecorator
- def _decoratorFunc(f, append=append):
- global exceptionLoggedNotify
- if exceptionLoggedNotify is None:
- from direct.directnotify.DirectNotifyGlobal import directNotify
- exceptionLoggedNotify = directNotify.newCategory("ExceptionLogged")
- def _exceptionLogged(*args, **kArgs):
- try:
- return f(*args, **kArgs)
- except Exception as e:
- try:
- s = '%s(' % f.__name__
- for arg in args:
- s += '%s, ' % arg
- for key, value in list(kArgs.items()):
- s += '%s=%s, ' % (key, value)
- if len(args) or len(kArgs):
- s = s[:-2]
- s += ')'
- if append:
- appendStr(e, '\n%s' % s)
- else:
- exceptionLoggedNotify.info(s)
- except:
- exceptionLoggedNotify.info(
- '%s: ERROR IN PRINTING' % f.__name__)
- raise
- _exceptionLogged.__doc__ = f.__doc__
- return _exceptionLogged
- return _decoratorFunc
- # http://en.wikipedia.org/wiki/Golden_ratio
- GoldenRatio = (1. + math.sqrt(5.)) / 2.
- class GoldenRectangle:
- @staticmethod
- def getLongerEdge(shorter):
- return shorter * GoldenRatio
- @staticmethod
- def getShorterEdge(longer):
- return longer / GoldenRatio
- def nullGen():
- # generator that ends immediately
- if False:
- # yield that never runs but still exists, making this func a generator
- yield None
- def loopGen(l):
- # generator that yields the items of an iterable object forever
- def _gen(l):
- while True:
- for item in l:
- yield item
- gen = _gen(l)
- # don't leak
- _gen = None
- return gen
- def makeFlywheelGen(objects, countList=None, countFunc=None, scale=None):
- # iterates and finally yields a flywheel generator object
- # the number of appearances for each object is controlled by passing in
- # a list of counts, or a functor that returns a count when called with
- # an object from the 'objects' list.
- # if scale is provided, all counts are scaled by the scale value and then int()'ed.
- def flywheel(index2objectAndCount):
- # generator to produce a sequence whose elements appear a specific number of times
- while len(index2objectAndCount):
- keyList = list(index2objectAndCount.keys())
- for key in keyList:
- if index2objectAndCount[key][1] > 0:
- yield index2objectAndCount[key][0]
- index2objectAndCount[key][1] -= 1
- if index2objectAndCount[key][1] <= 0:
- del index2objectAndCount[key]
- # if we were not given a list of counts, create it by calling countFunc
- if countList is None:
- countList = []
- for object in objects:
- yield None
- countList.append(countFunc(object))
- if scale is not None:
- # scale the counts if we've got a scale factor
- for i in xrange(len(countList)):
- yield None
- if countList[i] > 0:
- countList[i] = max(1, int(countList[i] * scale))
- # create a dict for the flywheel to use during its iteration to efficiently select
- # the objects for the sequence
- index2objectAndCount = {}
- for i in xrange(len(countList)):
- yield None
- index2objectAndCount[i] = [objects[i], countList[i]]
- # create the flywheel generator
- yield flywheel(index2objectAndCount)
- def flywheel(*args, **kArgs):
- # create a flywheel generator
- # see arguments and comments in flywheelGen above
- # example usage:
- """
- >>> for i in flywheel([1,2,3], countList=[10, 5, 1]):
- ... print i,
- ...
- 1 2 3 1 2 1 2 1 2 1 2 1 1 1 1 1
- """
- for flywheel in makeFlywheelGen(*args, **kArgs):
- pass
- return flywheel
- if __debug__:
- def quickProfile(name="unnamed"):
- import pstats
- def profileDecorator(f):
- if(not config.GetBool("use-profiler",0)):
- return f
- def _profiled(*args, **kArgs):
- # must do this in here because we don't have base/simbase
- # at the time that PythonUtil is loaded
- if(not config.GetBool("profile-debug",0)):
- #dumb timings
- st=globalClock.getRealTime()
- f(*args,**kArgs)
- s=globalClock.getRealTime()-st
- print("Function %s.%s took %s seconds"%(f.__module__, f.__name__,s))
- else:
- import profile as prof, pstats
- #detailed profile, stored in base.stats under (
- if(not hasattr(base,"stats")):
- base.stats={}
- if(not base.stats.get(name)):
- base.stats[name]=[]
- prof.runctx('f(*args, **kArgs)', {'f':f,'args':args,'kArgs':kArgs},None,"t.prof")
- s=pstats.Stats("t.prof")
- #p=hotshot.Profile("t.prof")
- #p.runctx('f(*args, **kArgs)', {'f':f,'args':args,'kArgs':kArgs},None)
- #s = hotshot.stats.load("t.prof")
- s.strip_dirs()
- s.sort_stats("cumulative")
- base.stats[name].append(s)
- _profiled.__doc__ = f.__doc__
- return _profiled
- return profileDecorator
- def getTotalAnnounceTime():
- td=0
- for objs in base.stats.values():
- for stat in objs:
- td+=getAnnounceGenerateTime(stat)
- return td
- def getAnnounceGenerateTime(stat):
- val=0
- stats=stat.stats
- for i in list(stats.keys()):
- if(i[2]=="announceGenerate"):
- newVal=stats[i][3]
- if(newVal>val):
- val=newVal
- return val
- class MiniLog:
- def __init__(self, name):
- self.indent = 1
- self.name = name
- self.lines = []
- def __str__(self):
- return '%s\nMiniLog: %s\n%s\n%s\n%s' % \
- ('*'*50, self.name, '-'*50, '\n'.join(self.lines), '*'*50)
- def enterFunction(self, funcName, *args, **kw):
- rArgs = [repr(x)+', ' for x in args] + \
- [ x + ' = ' + '%s, ' % repr(y) for x,y in kw.items()]
- if not rArgs:
- rArgs = '()'
- else:
- rArgs = '(' + reduce(str.__add__,rArgs)[:-2] + ')'
- line = '%s%s' % (funcName, rArgs)
- self.appendFunctionCall(line)
- self.indent += 1
- return line
- def exitFunction(self):
- self.indent -= 1
- return self.indent
- def appendFunctionCall(self, line):
- self.lines.append(' '*(self.indent*2) + line)
- return line
- def appendLine(self, line):
- self.lines.append(' '*(self.indent*2) + '<< ' + line + ' >>')
- return line
- def flush(self):
- outStr = str(self)
- self.indent = 0
- self.lines = []
- return outStr
- class MiniLogSentry:
- def __init__(self, log, funcName, *args, **kw):
- self.log = log
- if self.log:
- self.log.enterFunction(funcName, *args, **kw)
- def __del__(self):
- if self.log:
- self.log.exitFunction()
- del self.log
- def logBlock(id, msg):
- print('<< LOGBLOCK(%03d)' % id)
- print(str(msg))
- print('/LOGBLOCK(%03d) >>' % id)
- class HierarchyException(Exception):
- JOSWILSO = 0
- def __init__(self, owner, description):
- self.owner = owner
- self.desc = description
- def __str__(self):
- return '(%s): %s' % (self.owner, self.desc)
- def __repr__(self):
- return 'HierarchyException(%s)' % (self.owner, )
- def formatTimeCompact(seconds):
- # returns string in format '1d3h22m43s'
- result = ''
- a = int(seconds)
- seconds = a % 60
- a //= 60
- if a > 0:
- minutes = a % 60
- a //= 60
- if a > 0:
- hours = a % 24
- a //= 24
- if a > 0:
- days = a
- result += '%sd' % days
- result += '%sh' % hours
- result += '%sm' % minutes
- result += '%ss' % seconds
- return result
- if __debug__ and __name__ == '__main__':
- ftc = formatTimeCompact
- assert ftc(0) == '0s'
- assert ftc(1) == '1s'
- assert ftc(60) == '1m0s'
- assert ftc(64) == '1m4s'
- assert ftc(60*60) == '1h0m0s'
- assert ftc(24*60*60) == '1d0h0m0s'
- assert ftc(24*60*60 + 2*60*60 + 34*60 + 12) == '1d2h34m12s'
- del ftc
- def formatTimeExact(seconds):
- # like formatTimeCompact but leaves off '0 seconds', '0 minutes' etc. for
- # times that are e.g. 1 hour, 3 days etc.
- # returns string in format '1d3h22m43s'
- result = ''
- a = int(seconds)
- seconds = a % 60
- a //= 60
- if a > 0:
- minutes = a % 60
- a //= 60
- if a > 0:
- hours = a % 24
- a //= 24
- if a > 0:
- days = a
- result += '%sd' % days
- if hours or minutes or seconds:
- result += '%sh' % hours
- if minutes or seconds:
- result += '%sm' % minutes
- if seconds or result == '':
- result += '%ss' % seconds
- return result
- if __debug__ and __name__ == '__main__':
- fte = formatTimeExact
- assert fte(0) == '0s'
- assert fte(1) == '1s'
- assert fte(2) == '2s'
- assert fte(61) == '1m1s'
- assert fte(60) == '1m'
- assert fte(60*60) == '1h'
- assert fte(24*60*60) == '1d'
- assert fte((24*60*60) + (2 * 60)) == '1d0h2m'
- del fte
- class AlphabetCounter:
- # object that produces 'A', 'B', 'C', ... 'AA', 'AB', etc.
- def __init__(self):
- self._curCounter = ['A']
- def next(self):
- result = ''.join([c for c in self._curCounter])
- index = -1
- while True:
- curChar = self._curCounter[index]
- if curChar == 'Z':
- nextChar = 'A'
- carry = True
- else:
- nextChar = chr(ord(self._curCounter[index])+1)
- carry = False
- self._curCounter[index] = nextChar
- if carry:
- if (-index) == len(self._curCounter):
- self._curCounter = ['A',] + self._curCounter
- break
- else:
- index -= 1
- carry = False
- else:
- break
- return result
- if __debug__ and __name__ == '__main__':
- def testAlphabetCounter():
- tempList = []
- ac = AlphabetCounter()
- for i in xrange(26*3):
- tempList.append(ac.next())
- assert tempList == [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
- 'AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ',
- 'BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ',]
- ac = AlphabetCounter()
- num = 26 # A-Z
- num += (26*26) # AA-ZZ
- num += 26 # AAZ
- num += 1 # ABA
- num += 2 # ABC
- for i in xrange(num):
- x = ac.next()
- assert x == 'ABC'
- testAlphabetCounter()
- del testAlphabetCounter
- class Default:
- # represents 'use the default value'
- # useful for keyword arguments to virtual methods
- pass
- superLogFile = None
- def startSuperLog(customFunction = None):
- global superLogFile
- if(not superLogFile):
- superLogFile = open("c:\\temp\\superLog.txt", "w")
- def trace_dispatch(a,b,c):
- if(b=='call' and a.f_code.co_name != '?' and a.f_code.co_name.find("safeRepr")<0):
- vars = dict(a.f_locals)
- if 'self' in vars:
- del vars['self']
- if '__builtins__' in vars:
- del vars['__builtins__']
- for i in vars:
- vars[i] = safeReprTypeOnFail(vars[i])
- if customFunction:
- superLogFile.write( "before = %s\n"%customFunction())
- superLogFile.write( "%s(%s):%s:%s\n"%(a.f_code.co_filename.split("\\")[-1],a.f_code.co_firstlineno, a.f_code.co_name, vars))
- if customFunction:
- superLogFile.write( "after = %s\n"%customFunction())
- return trace_dispatch
- sys.settrace(trace_dispatch)
- def endSuperLog():
- global superLogFile
- if(superLogFile):
- sys.settrace(None)
- superLogFile.close()
- superLogFile = None
- def configIsToday(configName):
- # TODO: replace usage of strptime with something else
- # returns true if config string is a valid representation of today's date
- today = time.localtime()
- confStr = config.GetString(configName, '')
- for format in ('%m/%d/%Y', '%m-%d-%Y', '%m.%d.%Y'):
- try:
- confDate = time.strptime(confStr, format)
- except ValueError:
- pass
- else:
- if (confDate.tm_year == today.tm_year and
- confDate.tm_mon == today.tm_mon and
- confDate.tm_mday == today.tm_mday):
- return True
- return False
- def typeName(o):
- if hasattr(o, '__class__'):
- return o.__class__.__name__
- else:
- return o.__name__
- def safeTypeName(o):
- try:
- return typeName(o)
- except:
- pass
- try:
- return type(o)
- except:
- pass
- return '<failed safeTypeName()>'
- def histogramDict(l):
- d = {}
- for e in l:
- d.setdefault(e, 0)
- d[e] += 1
- return d
- def unescapeHtmlString(s):
- # converts %## to corresponding character
- # replaces '+' with ' '
- result = ''
- i = 0
- while i < len(s):
- char = s[i]
- if char == '+':
- char = ' '
- elif char == '%':
- if i < (len(s)-2):
- num = int(s[i+1:i+3], 16)
- char = chr(num)
- i += 2
- i += 1
- result += char
- return result
- class PriorityCallbacks:
- """ manage a set of prioritized callbacks, and allow them to be invoked in order of priority """
- def __init__(self):
- self._callbacks = []
- def clear(self):
- del self._callbacks[:]
- def add(self, callback, priority=None):
- if priority is None:
- priority = 0
- callbacks = self._callbacks
- lo = 0
- hi = len(callbacks)
- while lo < hi:
- mid = (lo + hi) // 2
- if priority < callbacks[mid][0]:
- hi = mid
- else:
- lo = mid + 1
- item = (priority, callback)
- callbacks.insert(lo, item)
- return item
- def remove(self, item):
- self._callbacks.remove(item)
- def __call__(self):
- for priority, callback in self._callbacks:
- callback()
- builtins.Functor = Functor
- builtins.Stack = Stack
- builtins.Queue = Queue
- builtins.Enum = Enum
- builtins.SerialNumGen = SerialNumGen
- builtins.SerialMaskedGen = SerialMaskedGen
- builtins.ScratchPad = ScratchPad
- builtins.uniqueName = uniqueName
- builtins.serialNum = serialNum
- if __debug__:
- builtins.profiled = profiled
- builtins.exceptionLogged = exceptionLogged
- builtins.itype = itype
- builtins.appendStr = appendStr
- builtins.bound = bound
- builtins.clamp = clamp
- builtins.lerp = lerp
- builtins.makeList = makeList
- builtins.makeTuple = makeTuple
- if __debug__:
- builtins.printStack = printStack
- builtins.printReverseStack = printReverseStack
- builtins.printVerboseStack = printVerboseStack
- builtins.DelayedCall = DelayedCall
- builtins.DelayedFunctor = DelayedFunctor
- builtins.FrameDelayedCall = FrameDelayedCall
- builtins.SubframeCall = SubframeCall
- builtins.invertDict = invertDict
- builtins.invertDictLossless = invertDictLossless
- builtins.getBase = getBase
- builtins.getRepository = getRepository
- builtins.safeRepr = safeRepr
- builtins.fastRepr = fastRepr
- builtins.nullGen = nullGen
- builtins.flywheel = flywheel
- builtins.loopGen = loopGen
- if __debug__:
- builtins.StackTrace = StackTrace
- builtins.report = report
- builtins.pstatcollect = pstatcollect
- builtins.MiniLog = MiniLog
- builtins.MiniLogSentry = MiniLogSentry
- builtins.logBlock = logBlock
- builtins.HierarchyException = HierarchyException
- builtins.deeptype = deeptype
- builtins.Default = Default
- builtins.configIsToday = configIsToday
- builtins.typeName = typeName
- builtins.safeTypeName = safeTypeName
- builtins.histogramDict = histogramDict
|