Thursday, August 29, 2013

Twisted magic so webpages can wait for longer term process to finish

Computahs!

I occasionally want to show images on my face research website that show off the average of hella (technical term) images blended together. Problem is, sometimes I haven't finished making these images yet, because they can have thousands of images in them and take a couple seconds to produce.

I totally just implemented a thing that is handy. It is a Twisted thing.

A webpage can request something (in my case, an image url), and if the image is ready, it gets it right away.

If the image is not ready, however, the webpage doesn't poll or ask again later, it just sits on the line waiting for the image to be ready. And if it (or the viewer) gets bored, they can refresh/re-ask, and it'll be like, "yo dude, it's still not ready, be patient and you can have it when it's done."


This is one of the things I'm making. It's an average of 600 of my faces!

DIAGRAM
Diagramorama! The jobs are part of the twisted server. And they are actually a dictionary. And all the other boxy things are  browser windows with lil tabs.

The part that makes the work happen

cluster_image_jobs = {}

def makeClusterAverage(cluster_id):

    @defer.inlineCallbacks
    def _makeDeferred():
        # this uses inline callbacks to the thing itself is a deferred
        print "making cluster average", cluster_id
        
        yield askDbForSomeStuff()
        yield doDirtyWorkOfMakingCluster() 
        yield moreStuffThatIsPartOfTheHardWork()
        
        defer.returnValue(cluster_id)

    # CALL the deferred thing to actually start running the job
    # notice how we return it to the outer function 
    # in case you really want to wait for it/add more callbacks 
    
    job = _makeDeferred()

    # the cleanup function
    def _removeJobFromList(cluster_id):
        print "Done with cluster job %d, removing from list" % (cluster_id)
        del cluster_image_jobs[cluster_id]

    # if there's not already a job in the list, add it
    # i only add a job if one's not already there 
    # so an older job can't remove a newer, in progress job from the list
    
    if cluster_id not in cluster_image_jobs:
        job.addCallback(_removeJobFromList)
        cluster_image_jobs[cluster_id] = job

    return job


The part that lets browsers ask for stuff 

@defer.inlineCallbacks
def getClusterImage(cluster_id):

    # if the job is in the list, sit around and wait for it to finish
    if cluster_id in cluster_image_jobs:
        print "Here I am waiting for the job to finish..."
        yield cluster_image_jobs[cluster_id]
        print "Sweet! The job has finished now and I can get on with my life!"
    
    some_id_that_i_needed = yield doSomeDatabaseStuffThatIWantedToDo()
    
    cluster_image = "image%d.jpg" % (some_id_that_i_needed)
    
    print "returning cluster image: ", cluster_image
    defer.returnValue(cluster_image)

Monday, August 5, 2013

One week of Fitbit

I didn't give much of a crap about the Fitbit until something clicked about 2 weeks ago and I impulsively ordered one for me and one for Adam. I think I was walking up the stairs in my building and I wanted credit for walking up the stairs. And for some reason, buying an expensive little device was the answer.

Some observations and anecdotes:

1. I wear in on my bra and I feel like Iron Man when I start and stop activities by pressing the center of my chest. Riding on the bus gives me a couple hundred steps and I don't want that because I didn't earn those steps! So I log when I'm on the bus and mark it as bus-riding on the website later.

Fitbit kinda makes me feel like Iron Man but slightly less of a badass.

2. Adam got one, too, and we have discovered many a late night happy hour around Capitol Hill while trying to make our 10,000 step quotas. Not sure if increased money expenditure and food/drink consumption is what we were going for, but hey, we are finally getting out and exploring our fine neighborhood. For example:
  • La Cocina Oaxaquena has $5 margaritas and $5 tacos (2x) with homemade tortillas that are quite tasty! 
  • Capitol Cider is still neat with its plethora of cider options and fancy paintings. I made Adam try some habanero cider.
  • The Pie Bar that replaced Saley's Crepes (which luckily just moved down the street) is pretty cute! 
  • Need to try moar places!! While we are still young and hip and living in the center of everything.

3. I like getting feedback on my sleep. I got the Fitbit One that does sleep tracking, and feel like that part is more novel to me (than exercise/activity), because I've never been able to track that before. I'd like to discover how much sleep I really need, because without an alarm, I seem to sleep for 9 or 10 hours. I don't know if those long sleeps are always after a few nights of not enough sleep or in the middle of the cold, dark winter or when I'm hiding from work or what. 



4. Yes, the presence of the fitbit does get me to "choose" a more active route when given an opportunity to decide, like between stairs or no stairs, or taking the bus that is better timed but involves more walking. Apparently walking slightly more is not valuable to me on its own, but when a snazzy little device gives me feedback, I want to get the positive feedback! 


5. Gamification? This makes me think about Jesse Schell's Dice 2010 talk that includes a little segment about "what if we gamify everything in the real world?" I said at the beginning that I got the fitbit because I wanted credit for taking the stairs instead of the elevator. (Is that bad? I feel like an animal! I am an animal!) Obviously I am getting some amount of joy/information/learning/life feedback from the fitbit, so I don't think of it as evil. And I don't think of it as a game, I think of it as a little pet, like a tamagotchi (I had a Giga Pet, actually) that needs walkies. Or that takes care of me and tells me I need walkies. I don't know, I'm conflicted. Gamification seems bad, but feedback can be really useful, and it can come in the form of a game/game-like elements.