Sunday, July 17, 2011

My Dream Edition of D&D 5e

Spurred on by this ENWorld thread, I got to thinking about what my dream 5e would look like. And since I can't stand to miss a chance to pontificate, and this is way too long to break the board's formatting with, why not  put it here. To start with, for those that don't know, I prefer 4th edition D&D, but I think all of the editions have done something right. And I frequently use inspiration, convert variant rules from older editions, or Pathfinder. With that in mind, let's take a look at what my dream edition of D&D would look like.

Saturday, July 16, 2011

CherryPy, Server Sent Events, and You.

There was a dearth of decent information on how to implement server side events for anything but PHP, so, being a mediocre python coder, I felt the need to correct this for my favorite web framework. A far better general (or client-side) writeup is at HTML5Rocks. The basic idea is pretty straightforward, subscribe to an event stream on the client, the server then sends a message when something happens, and there you go.

Because of the limits of HTTP, it's not quite "true" push. It's closer in implementation to typical Comet pattern, but an order of magnitude easier to actually use. This does have an effect on how you'd implement it in CherryPy, however.

First, our simple HTML file.

<html>
 <head>
  <title>Server Side Event Test</title>
  
 </head>
 <body>
  <script type="text/javascript">
   document.addEventListener('DOMContentLoaded', function () {
  
     var timeSrc  = new EventSource('sendTime');     
     timeSrc.addEventListener('time', function (event) {
     document.getElementById("Test").innerHTML += "\n " + event.data
     
     });
   
   
   }, false);
  </script>
  
  Test box! 
  <br/>
  <textarea id="Test"></textarea>

 </body>
</html>
Pretty straightforward. That sets up our event listener to listen for an event named 'time', from the 'sendTime' URL. (And appends it to a text box, to make it easier to see.).

import cherrypy
import os.path
import json
import time
from cherrypy.lib.static import serve_file

current_dir = os.path.dirname(os.path.abspath(__file__))


class Root():
    
    #Our toggle variable.
    timeFeedEnabled = False
    
    @cherrypy.expose
    def index(self):
        return serve_file(os.path.join(current_dir, 'index.html'), content_type='text/html')
        #serves our index client page.
    
    @cherrypy.expose
    def timeSwitch(self):
        #When this page gets requested, it'll toggle the time feed updating
        #and return an OK message.
        if self.timeFeedEnabled:    
            self.timeFeedEnabled = False
        else:
            self.timeFeedEnabled = True
        return "Feed Toggled"
    
    @cherrypy.expose
    def sendTime(self):
        #Set the expected headers...
        cherrypy.response.headers["Content-Type"] = "text/event-stream"
        if self.timeFeedEnabled:
            return "event: time\n" + "data: " + str(time.time()) + "\n\n";
        else:
            pass
        

if __name__ == '__main__':
    pageroot = Root()
    #And, standard cherrypy quickstart.
    cherrypy.quickstart(pageroot, config="test.conf")
No big shockers here. The main thing you might wonder about, is why our sendTime url is just a normal request handler. That's the underlying comet pattern helping us out a little. What'll happen, is the browser will ping the server, and if the feed is toggled on, it'll send the info. Which means, if you only want the feed to update when you have something worth updating, you'll need something similar to the timeFeedEnabled variable I have set up. If you don't, it'll just pass out, and it'll keep working.

If you want the feed to stop completely and not come back until a page refresh or other events (say, on log out or something), you can call  .close() on the javascript side, or change the headers to something other than 'text/event-stream', which will also make the web browser disconnect without retrying.

This is a quick and dirty example without any error checking, security, or any real robustness. For example, you'd want authentication and session stuff for anything major, obviously, as well as making sure the event-stream came from your domain. But it does show how to do it, without getting overly complicated.