diff options
author | Russell Cohen <rcoh@mit.edu> | 2010-12-03 04:20:17 -0500 |
---|---|---|
committer | Russell Cohen <rcoh@mit.edu> | 2010-12-03 04:20:17 -0500 |
commit | 353ab16db64c86122c0fcb9e1852b85c14b354b8 (patch) | |
tree | 8904f1cd312974a849ad5bcf0c8520fd20175cda | |
parent | 0366f46d3d7e946e254f933888aea4beb4e70658 (diff) |
Speed optimizations abound! Caching on parameter validation, Binary
searching for light locations in pixel mappers, actual thread safety for
inputs means we don't drop them randomly, anymore. Caching on PixelStates to
reduce multi-renderer costs + a short circuit to speed up processing on
pixels that are turned off.
-rw-r--r-- | LightInstallation.py | 14 | ||||
-rw-r--r-- | Util.py | 57 | ||||
-rw-r--r-- | behaviors/ColorChangerBehavior.py | 4 | ||||
-rw-r--r-- | config/LightInstallationConfig.xml | 16 | ||||
-rw-r--r-- | operationscore/Behavior.py | 3 | ||||
-rw-r--r-- | operationscore/Input.py | 3 | ||||
-rw-r--r-- | operationscore/SmootCoreObject.py | 6 | ||||
-rw-r--r-- | pixelcore/Pixel.py | 8 | ||||
-rw-r--r-- | pixelcore/Screen.py | 17 | ||||
-rw-r--r-- | pixelmappers/GaussianMapper.py | 7 |
10 files changed, 103 insertions, 32 deletions
diff --git a/LightInstallation.py b/LightInstallation.py index 849e41f..8f71cf0 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -1,14 +1,17 @@ from xml.etree.ElementTree import ElementTree from pixelcore.Screen import * from pixelcore.PixelStrip import * -import pdb, sys, time, Util +import pdb, sys, time, Util, thread from pygame.locals import * #Python class to instantiate and drive a Screen through different patterns, #and effects. class LightInstallation: def __init__(self, configFileName): + self.timer = Util.Stopwatch() + self.timer.start() self.inputs = {} #dict of inputs and their bound behaviors, keyed by InputId self.behaviors = {} + self.lock = thread.allocate_lock() self.behaviorOutputs = {} #key: [list of output destinations] self.behaviorInputs = {} self.componentDict = {} @@ -39,6 +42,8 @@ class LightInstallation: self.registerComponents(self.inputs) self.registerComponents(self.behaviors) #Done initializing. Lets start this thing! + self.timer.stop() + print 'Initialization done. Time: ', self.timer.elapsed(), 'ms' self.mainLoop() def initializeMapper(self, mapperConfig): self.mapper = self.initializeComponent(mapperConfig)[0] #TODO: support @@ -82,16 +87,21 @@ class LightInstallation: #self.screen.allOn() lastLoopTime = Util.time() refreshInterval = 30 - while 1: + runCount = 1000 + while runCount > 0: + runCount -= 1 loopStart = Util.time() responses = self.evaluateBehaviors() #inputs are all queued when they #happen, so we only need to run the behaviors + self.timer.start() [self.screen.respond(response) for response in responses if response != []] self.screen.timeStep() [r.render(self.screen) for r in self.renderers] loopElapsed = Util.time()-loopStart sleepTime = max(0,refreshInterval-loopElapsed) + self.timer.stop() + #print self.timer.elapsed() if sleepTime > 0: time.sleep(sleepTime/1000) #evaluates all the behaviors (including inter-dependencies) and returns a @@ -1,18 +1,21 @@ import pdb from xml.etree.ElementTree import ElementTree import math,struct +from bisect import * #import json # json.loads() to decode string; json.dumps() to encode data import socket import random from pygame.locals import * import time as clock from pixelevents.StepEvent import * + VERSION = 0x0001 MAGIC = 0x4adc0104 MOREMAGIC = 0xdeadbeef DEEPMAGIC = 0xc001d00d MAGICHASH = 0x69000420 PORTOUT = 0x0108 +classArgsMem = {} UNI = 0 CONFIG_PATH = 'config/' kinetDict = {'flags': 0, 'startcode': 0, 'pad':0} @@ -55,23 +58,38 @@ def randomColor(): def chooseRandomColor(colorList): return random.choice(colorList) def loadParamRequirementDict(className): - return fileToDict(CONFIG_PATH + className) + if not className in classArgsMem: #WOO CACHING + classArgsMem[className] = fileToDict(CONFIG_PATH + className) + return classArgsMem[className] def loadConfigFile(fileName): - fileName = CONFIG_PATH + fileName - if '.params' in fileName: - return fileToDict(fileName) - if '.xml' in fileName: - config = ElementTree() - config.parse(fileName) - return config + try: + fileName = CONFIG_PATH + fileName + if '.params' in fileName: + return fileToDict(fileName) + if '.xml' in fileName: + config = ElementTree() + config.parse(fileName) + return config + except: + return None def fileToDict(fileName): fileText = '' - with open(fileName) as f: - for line in f: - fileText += line.rstrip('\n').lstrip('\t') + ' ' + try: + with open(fileName) as f: + for line in f: + fileText += line.rstrip('\n').lstrip('\t') + ' ' + except IOError: + return {} if fileText == '': return {} return eval(fileText) +def find_le(a, x): + 'Find rightmost value less than or equal to x' + return bisect_right(a, x)-1 + +def find_ge(a, x): + 'Find leftmost value greater than x' + return bisect_left(a, x) def safeColor(c): return [min(channel,255) for channel in c] def combineColors(c1,c2): @@ -186,7 +204,22 @@ def testXMLParse(fileName): config.parse(fileName) print generateArgDict(config.find('ChildElement')) print generateArgDict(config.find('Renderer')) - +class Stopwatch: + def __init__(self): + self.running = False + self.startTime = -1 + self.stopTime = -1 + def start(self): + self.startTime = Util.time() + self.running = True + def elapsed(self): + if self.running: + return Util.time()-self.startTime + else: + return self.stopTime - self.startTime + def stop(self): + self.stopTime = Util.time() + self.running = False ##CONSTANTS## location = 'Location' diff --git a/behaviors/ColorChangerBehavior.py b/behaviors/ColorChangerBehavior.py index ca80eb4..12ef6f4 100644 --- a/behaviors/ColorChangerBehavior.py +++ b/behaviors/ColorChangerBehavior.py @@ -7,8 +7,8 @@ class ColorChangerBehavior(Behavior): for sensory in sensorInputs: newDict = dict(sensory) #don't run into shallow copy issues if self['ColorList'] != None: - newDict['Color'] = Util.randomColor(self['ColorList']) + newDict['Color'] = Util.chooseRandomColor(self['ColorList']) else: - newDict['Color'] = Util.chooseRandomColor() + newDict['Color'] = Util.randomColor() ret.append(newDict) return ret diff --git a/config/LightInstallationConfig.xml b/config/LightInstallationConfig.xml index aa331af..dfa05d7 100644 --- a/config/LightInstallationConfig.xml +++ b/config/LightInstallationConfig.xml @@ -46,7 +46,7 @@ <Id>gaussmap</Id> <CutoffDist>30</CutoffDist> <Width>5</Width> - <Height>.5</Height> + <Height>1</Height> </Args> </PixelMapper> </PixelMapperConfiguration> @@ -77,14 +77,14 @@ <RefreshInterval>100</RefreshInterval> </Args> </InputElement> - <InputElement> + <!--<InputElement> <Class>inputs.PygameInput</Class> - <Args><!--Passed as a dictionary--> + <Args> <Id>followmouse</Id> <RefreshInterval>100</RefreshInterval> <FollowMouse>True</FollowMouse> </Args> - </InputElement> + </InputElement>--> <InputElement> <Class>inputs.UDPInput</Class> <Args> @@ -134,7 +134,7 @@ <Id>debug</Id> <z-index>0</z-index> <Inputs> - <Id>UDP</Id> + <Id>pygame</Id> </Inputs> </Args> </Behavior> @@ -161,7 +161,7 @@ <RenderToScreen>True</RenderToScreen> </Args> </Behavior> - <Behavior> + <!--<Behavior> <Class>behaviors.BehaviorChain</Class> <Args> <Id>mousechaser</Id> @@ -175,7 +175,7 @@ </ChainedBehaviors> <RenderToScreen>True</RenderToScreen> </Args> - </Behavior> + </Behavior>--> <Behavior> <Class>behaviors.RunningBehavior</Class> <Args> @@ -183,7 +183,7 @@ <Inputs> <Id>pygame</Id> </Inputs> - <StepSize>1</StepSize> + <StepSize>10</StepSize> <RenderToScreen>False</RenderToScreen> </Args> </Behavior> diff --git a/operationscore/Behavior.py b/operationscore/Behavior.py index e300f6b..83d2e7d 100644 --- a/operationscore/Behavior.py +++ b/operationscore/Behavior.py @@ -56,5 +56,6 @@ class Behavior(SmootCoreObject): try: return outputs except: - pdb.set_trace() + pass + #pdb.set_trace() return outputs diff --git a/operationscore/Input.py b/operationscore/Input.py index 67a7bb0..9ee59f8 100644 --- a/operationscore/Input.py +++ b/operationscore/Input.py @@ -32,7 +32,10 @@ class Input(threading.Thread): def respond(self, eventDict): #if eventDict != []: #pdb.set_trace() + self.parentScope.lock.acquire() self.parentScope.processResponse(self.argDict, eventDict) + self.parentScope.lock.release() + time.sleep(.001) def newEvent(self, event): #Mostly just useful for grabbing events from the #computer running the sim (key presses, clicks etc.) self.eventQueue.append(event) diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py index 84319af..d29e710 100644 --- a/operationscore/SmootCoreObject.py +++ b/operationscore/SmootCoreObject.py @@ -21,10 +21,8 @@ class SmootCoreObject: def __getiter__(self): return self.argDict.__getiter__() def validateArgs(self, argFileName): - try: - self.validateArgDict(Util.loadParamRequirementDict(argFileName)) - except IOError: - print 'No Arg Dict found for ' + self.className() + self.validateArgDict(Util.loadParamRequirementDict(argFileName))#util + #caches for us, woo! def validateArgDict(self, validationDict): for item in validationDict: if not item in self.argDict: diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py index 8c42581..4c8ec89 100644 --- a/pixelcore/Pixel.py +++ b/pixelcore/Pixel.py @@ -12,6 +12,7 @@ class Pixel: def __init__(self, location): self.location = location self.events = {} + self.memState = None def turnOn(self): self.turnOnFor(-1) #Turn the light white for 'time' ms. Really only meant for testing. Use @@ -28,7 +29,13 @@ class Pixel: self.events = {} #Combines all PixelEvents currently active and computes the current color of #the pixel. + def invalidateState(self): + self.memState = None def state(self): + if self.memState != None: + return self.memState + if len(self.events) == 0: + return (0,0,0) deadEvents = [] currentTime = Util.time() resultingColor = (0,0,0) @@ -40,6 +47,7 @@ class Pixel: else: deadEvents.append(eventTime) [self.events.pop(event) for event in deadEvents] + self.memState = tuple(resultingColor) return tuple(resultingColor) def __str__(self): return 'Loc: ' + str(self.location) diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py index 6b5c737..71b9b0b 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -7,11 +7,24 @@ class Screen: def __init__(self): self.responseQueue = [] self.pixelStrips = [] + self.xSortedPixels = [] + self.xPixelLocs = [] sizeValid = False def addStrip(self, lS): self.pixelStrips.append(lS) self.sizeValid = False #keep track of whether or not our screen size has #been invalidated by adding more pixels + self.computeXSortedPixels() + #Returns (pixelIndex, pixel). Does a binary search. + def pixelsInRange(self, minX, maxX): + minIndex = Util.find_ge(self.xPixelLocs, minX) + maxIndex = Util.find_le(self.xPixelLocs, maxX)+1 + return self.xSortedPixels[minIndex:maxIndex] + def computeXSortedPixels(self): + for pixel in self: + self.xSortedPixels.append((pixel.location[0], pixel)) + self.xSortedPixels.sort() + self.xPixelLocs = [p[0] for p in self.xSortedPixels] def render(self, surface): [lS.render(surface) for lS in self.pixelStrips] def setMapper(self, mapper): @@ -29,6 +42,7 @@ class Screen: self.responseQueue = [] for response in tempQueue: self.processResponse(response) + [p.invalidateState() for p in self] #public def respond(self, responseInfo): self.responseQueue.append(responseInfo) @@ -53,7 +67,8 @@ class Screen: #each to prevent interference #[strip.respond(dict(responseInfo)) for strip in self.pixelStrips] if type(responseInfo) != type(dict()): - pdb.set_trace() + pass + #pdb.set_trace() pixelWeightList = self.mapper.mapEvent(responseInfo['Location'], self) Util.addPixelEventIfMissing(responseInfo) for (pixel, weight) in pixelWeightList: diff --git a/pixelmappers/GaussianMapper.py b/pixelmappers/GaussianMapper.py index 552f5c9..1cf9e88 100644 --- a/pixelmappers/GaussianMapper.py +++ b/pixelmappers/GaussianMapper.py @@ -3,11 +3,14 @@ import Util class GaussianMapper(PixelMapper): def mappingFunction(self, eventLocation, screen): returnPixels = [] - for pixel in screen: + [x,y] = eventLocation + for (x,pixel) in screen.pixelsInRange(x-self['CutoffDist'], \ + x+self['CutoffDist']): pixelDist = Util.dist(pixel.location, eventLocation) if pixelDist < self['CutoffDist']: w = Util.gaussian(pixelDist, self['Height'], 0, self['Width']) if w>1: - pdb.set_trace() + #pdb.set_trace() + pass returnPixels.append((pixel, w)) return returnPixels |