home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Multimedia / k3d-setup-0.7.11.0.exe / lib / site-packages / cgkit / tunnel.py < prev    next >
Encoding:
Python Source  |  2007-01-11  |  18.8 KB  |  534 lines

  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Python Computer Graphics Kit.
  15. #
  16. # The Initial Developer of the Original Code is Matthias Baas.
  17. # Portions created by the Initial Developer are Copyright (C) 2004
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. #
  22. # Alternatively, the contents of this file may be used under the terms of
  23. # either the GNU General Public License Version 2 or later (the "GPL"), or
  24. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25. # in which case the provisions of the GPL or the LGPL are applicable instead
  26. # of those above. If you wish to allow use of your version of this file only
  27. # under the terms of either the GPL or the LGPL, and not to allow others to
  28. # use your version of this file under the terms of the MPL, indicate your
  29. # decision by deleting the provisions above and replace them with the notice
  30. # and other provisions required by the GPL or the LGPL. If you do not delete
  31. # the provisions above, a recipient may use your version of this file under
  32. # the terms of any one of the MPL, the GPL or the LGPL.
  33. #
  34. # ***** END LICENSE BLOCK *****
  35. # $Id: tunnel.py,v 1.2 2005/08/08 18:05:34 mbaas Exp $
  36.  
  37. ## \file tunnel.py
  38. ## Contains the Tunnel class.
  39.  
  40. from cgtypes import *
  41. import component
  42. from slots import *
  43. import _core
  44. import socket, threading, struct
  45. import scene
  46.  
  47. # _ImmediateForwarder
  48. class _ImmediateForwarder(NotificationForwarder):
  49.     """Internal class for the Tunnel component.
  50.  
  51.     This forwarder immediately sends the new value to the server part
  52.     of the tunnel.
  53.     """
  54.     def __init__(self, tunnel, slot, msgid, idx):
  55.         NotificationForwarder.__init__(self, self.sendValue)
  56.         self.tunnel = tunnel
  57.         self.slot = slot
  58.         self.msgid = msgid
  59.         self.idx = idx
  60.  
  61.     def sendValue(self):
  62.         t = self.tunnel
  63.         msg = t.encodeValueMessage(self.msgid, self.idx, self.slot)
  64.         t.send(msg)
  65.  
  66. # _GatherForwarder
  67. class _GatherForwarder(NotificationForwarder):
  68.     """Internal class for the Tunnel component.
  69.  
  70.     This forwarder marks the slot as changed and sends a combined
  71.     message if it's the sender slot (the last slot in the tunnel).
  72.     """
  73.     def __init__(self, tunnel, idx, sender):
  74.         NotificationForwarder.__init__(self, self.markSlot)
  75.         self.tunnel = tunnel
  76.         self.idx = idx
  77.         self.sender = sender
  78.  
  79.     def markSlot(self):
  80.         self.tunnel.markAsChanged(self.idx)
  81.         if self.sender:
  82.             self.tunnel.sendBulk()
  83.  
  84.                  
  85.  
  86. # Tunnel
  87. class Tunnel(component.Component):
  88.     """%Tunnel component which can connect slots on different machines.
  89.  
  90.     A tunnel is a component that has an arbitrary number of input slots
  91.     and a corresponding output slot for each input slot.
  92.     The value of the input slot is simply passed to the output slot.
  93.     The speciality of the tunnel is that both ends may reside on two
  94.     different machines.
  95.  
  96.     \image html tunnel.png
  97.  
  98.     An instance of the tunnel component either represents the input part
  99.     (client) or the output part (server). Value changes on the client are
  100.     then propagated to the server. When creating either side of the tunnel
  101.     you have to define the name and type of the slots that should be created.
  102.     The slot types of the client and the server should always match.
  103.  
  104.     Note: ArraySlots are currently not supported.
  105.  
  106.     Example:
  107.  
  108.     \code
  109.     # On machine A
  110.     t = Tunnel(
  111.        slots = [("pos", "Vec3Slot()"),
  112.                 ("rot", "Mat3Slot()")],
  113.        host  = "<name or IP of machine B>",
  114.     )
  115.  
  116.     # Do connections between other components and t.pos_slot/t.rot_slot
  117.     # or set the values directly on the tunnel
  118.     \endcode
  119.  
  120.     \code
  121.     # On machine B
  122.     t = Tunnel(
  123.        server = True,
  124.        slots = [("pos", "Vec3Slot()"),
  125.                 ("rot", "Mat3Slot()")],
  126.     )
  127.     
  128.     # Make connections between t.pos_slot/t.rot_slot and other components
  129.     # or retrieve the values directly from the tunnel
  130.     \endcode
  131.  
  132.     \par Protocol
  133.  
  134.     The client sends its messages as UDP datagrams to the server.
  135.     One packet may contain several individual messages that are just
  136.     concatenated. The first two bytes of each message is the message
  137.     ID. The remaining part depends on the ID. The byte order is always
  138.     in big endian.
  139.  
  140.     - ID 0: Init message (must be the only message in a packet)
  141.     - ID 1: Binary int value
  142.     - ID 2: Binary double value
  143.     - ID 3: Binary bool value (actually short int)
  144.     - ID 4: Binary vec3 value (3 doubles)
  145.     - ID 5: Binary vec4 value (4 doubles)
  146.     - ID 6: Binary mat3 value (9 doubles)
  147.     - ID 7: Binary mat4 value (16 doubles)
  148.     - ID 8: Binary quat value (4 doubles)
  149.  
  150.     All the value messages are composed of the slot index number (short int),
  151.     followed by the binary value.    
  152.     """
  153.  
  154.     def __init__(self,
  155.                  name = "Tunnel",
  156.                  server = False,
  157.                  slots = None,
  158.                  port = 64738,
  159.                  host = "localhost",
  160.                  gather_messages = False,
  161.                  verbose = False,
  162.                  auto_insert = True):
  163.         """Constructor.
  164.  
  165.         The argument \a slot specifies the slots to create on the tunnel.
  166.         It must be a list of 2-tuples containing two strings. The first
  167.         string is the name of the attribute and the second contains the
  168.         type and initializer of the slot (in Python syntax). The order and
  169.         types of the slots on the client and server should always match.
  170.  
  171.         The arguments \a host and \a gather_messages are only meaningful
  172.         on the client side. If \a gather_messages is True, then value changes
  173.         on a slot are not immediately sent but only when the last slot
  174.         receives a new value. You can use this option if you know that
  175.         all slot values will change at the same time. In this case it's more
  176.         efficient to change all values and then send only one message
  177.         containing all new values instead of sending individual messages for
  178.         every slot.
  179.  
  180.         \param name (\c str) Component name
  181.         \param server (\c bool) True if this is the server part
  182.         \param slots (\c list) A list of slot definitions. Each definition is a tuple (attribute name, slot class and initial arguments).
  183.         \param port (\c int) Port number to use for the data transfer
  184.         \param host (\c str) Host where the tunnel server is running (name or IP address). Client only.
  185.         \param gather_messages (\c bool) True if the messages should be combined. Client only.
  186.         \param verbose (\c bool) True if debug messages should be printed
  187.         \param auto_insert (\c bool) True if the component should be inserted into the scene automatically
  188.         """
  189.         component.Component.__init__(self, name=name, auto_insert=auto_insert)
  190.  
  191.         if slots==None:
  192.             slots = []
  193.  
  194.         self.server = server
  195.         self.host = host
  196.         self.port = port
  197.         self.sock = None
  198.         self.slots = slots
  199.         self.gather_messages = gather_messages
  200.         self.verbose = verbose
  201.  
  202.         if self.server:
  203.             self.host = socket.gethostname()
  204.             
  205.         # A list with tuples (slot, id)
  206.         self.slotobjs = []
  207.         # A dictionary with slot indices as keys. If an index is present
  208.         # in a dictionary then the corresponding slot was modified and the
  209.         # value has to be sent to the server
  210.         self.changed = {}
  211.  
  212.         self.forwarders = []
  213.  
  214.         # Initialize client/server...
  215.         if server:
  216.             self.initServer()
  217.         else:
  218.             self.initClient()
  219.  
  220.     def __str__(self):
  221.         if self.server:
  222.             return 'Tunnel server "%s" listening at %s:%d. %d slots.'%(self.name, self.host, self.port, len(self.slots))
  223.         else:
  224.             return 'Tunnel client "%s". %d slots. Server at %s:%d.'%(self.name, len(self.slots), self.host, self.port)
  225.  
  226.     ## protected:
  227.  
  228.     # initClient
  229.     def initClient(self):
  230.         """Initialization method for the client.
  231.         """
  232.         # Create socket object for sending UDP datagrams
  233.         self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  234.  
  235.         # Create slot attributes...
  236.         self.createClientSlotAttribs()
  237.  
  238.         # Send init message
  239.         s = map(lambda x: "%s:%s"%x, self.slots)
  240.         s = ";".join(s)
  241.         self.send(struct.pack(">H", 0)+s)
  242.  
  243.     # initServer
  244.     def initServer(self):
  245.         """Initialization method for the server.
  246.         """
  247.         # Create slot attributes...
  248.         self.createServerSlotAttribs()
  249.         
  250.         # Open UDP socket
  251.         self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  252.         ipaddr = socket.gethostbyname(self.host)
  253.         if self.verbose:
  254.             print "Open UDP port %d on %s (%s)"%(self.port, ipaddr, self.host)
  255.         self.sock.bind((ipaddr, self.port))
  256.  
  257.         # Split thread with the server loop
  258.         self.serverthread = threading.Thread(name="Tunnel-Server",
  259.                                              target=self.serverLoop)
  260.         self.serverthread.setDaemon(True)
  261.         self.serverthread.start()    
  262.  
  263.     # serverLoop
  264.     def serverLoop(self):
  265.         """Server loop.
  266.  
  267.         This method reads messages from the previously created socket
  268.         and process them.
  269.         The method is run in its own thread (because reading from the
  270.         socket is a blocking operation).
  271.         """
  272.  
  273.         if self.verbose:
  274.             print "Tunnel server running at port",self.port
  275.         while 1:
  276.             # Read a packet (blocking)
  277.             rawdata, addr = self.sock.recvfrom(5000)
  278.             if self.verbose:
  279.                 print "---Tunnel messages--"
  280.             # Process all messages...
  281.             while rawdata!="":
  282.                 # Get the id of the first message...
  283.                 try:
  284.                     id = struct.unpack(">H", rawdata[:2])[0]
  285.                 except:
  286.                     print "Error: Invalid UDP packet (no valid message id)."
  287.                     rawdata = ""
  288.                     continue
  289.  
  290.                 # Process the message...
  291.                 msgdata = rawdata[2:]
  292.                 if id==0:
  293.                     if self.verbose:
  294.                         print "Init"
  295.                     f = msgdata.split(";")
  296.                     localslots = map(lambda x: x[1], list(self.iterSlotDefs(self.slots)))
  297.                     remoteslots = map(lambda x: tuple(x.split(":")), f)
  298.                     remoteslots = map(lambda x: x[1], list(self.iterSlotDefs(remoteslots)))
  299.                     mismatch = True
  300.                     if len(localslots)==len(remoteslots):
  301.                         mismatch = localslots!=remoteslots
  302.                     if mismatch:
  303.                         print "%s: The types of the remote and local slots don't match."%self.name
  304.                     rawdata = ""
  305.                 else:
  306.                     # Decode the slot index and the value to set...
  307.                     try:
  308.                         n,idx,v = self.decodeValueMessage(id, msgdata)
  309.                     except struct.error, e:
  310.                         print e
  311.                         rawdata = ""
  312.                         continue
  313.                     except ValueError, e:
  314.                         print e
  315.                         rawdata = ""
  316.                         continue
  317.                     # Prepare the next message...
  318.                     rawdata = msgdata[n:]
  319.                     if self.verbose:
  320.                         print "Message id: %d - Slot:%d Value:%s"%(id,idx,v)
  321.                     # Get the slot that will receive the new value...
  322.                     try:
  323.                         slot,id = self.slotobjs[idx]
  324.                     except IndexError,e:
  325.                         print "Error: Invalid slot index (%d)"%idx
  326.                     # Set the new value...
  327.                     try:
  328.                         slot.setValue(v)
  329.                     except:
  330.                         print "Error: Could not assign value",v,"to slot of type",slot.typeName()
  331.  
  332.         self.sock.close()
  333.         if self.verbose:
  334.             print "Tunnel server stopped at port",self.port
  335.  
  336.     # markAsChanged
  337.     def markAsChanged(self, idx):
  338.         """Mark a slot that has changed its value.
  339.         """
  340.         self.changed[idx] = 1
  341.  
  342.     # send
  343.     def send(self, msg):
  344.         """Send one or more messages to the server.
  345.  
  346.         \param msg (\c str) Message(s)
  347.         """
  348.         self.sock.sendto(msg, (self.host, self.port))
  349.  
  350.     # sendBuld
  351.     def sendBulk(self):
  352.         """Send the values of all slots that have changed since the last call.
  353.         """
  354.         msgs = ""
  355.         for idx in self.changed.keys():
  356.             slot,id = self.slotobjs[idx]
  357.             msgs += self.encodeValueMessage(id, idx, slot)
  358.  
  359.         self.send(msgs)
  360.         self.changed = {}
  361.  
  362.     # createClientSlotAttribs
  363.     def createClientSlotAttribs(self):
  364.         slots = self.slots
  365.         if slots==None:
  366.             return
  367.  
  368.         self.forwarders = []
  369.         slotnames = ["<none>", "IntSlot", "DoubleSlot", "BoolSlot", "Vec3Slot",
  370.                      "Vec4Slot", "Mat3Slot", "Mat4Slot", "QuatSlot"]
  371.  
  372.         for varname, slotname, slotparams in self.iterSlotDefs(slots):
  373.             # Create the slot object
  374.             exec "slot = %s%s"%(slotname, slotparams)
  375.             # Add the slot as attribute
  376.             setattr(self, "%s_slot"%varname, slot)
  377.             exec "self.addSlot('%s', self.%s_slot)"%(varname, varname)
  378.  
  379.             id = slotnames.index(slotname)
  380.             idx = len(self.forwarders)
  381.             
  382.             self.slotobjs.append((slot,id))
  383.  
  384.             # Create the forwarder object
  385.             if self.gather_messages:
  386.                 f = _GatherForwarder(self, idx, idx==len(slots)-1)
  387.             else:
  388.                 f = _ImmediateForwarder(self, slot, id, idx)
  389.             slot.addDependent(f)
  390.             self.forwarders.append(f)
  391.  
  392.     # createServerSlotAttribs
  393.     def createServerSlotAttribs(self):
  394.         slots = self.slots
  395.         if slots==None:
  396.             return
  397.  
  398.         slotnames = ["<none>", "IntSlot", "DoubleSlot", "BoolSlot", "Vec3Slot",
  399.                      "Vec4Slot", "Mat3Slot", "Mat4Slot", "QuatSlot"]
  400.  
  401.         for varname, slotname, slotparams in self.iterSlotDefs(slots):
  402.             # Create the slot object
  403.             exec "slot = %s%s"%(slotname, slotparams)
  404.             # Add the slot as attribute
  405.             setattr(self, "%s_slot"%varname, slot)
  406.             exec "self.addSlot('%s', self.%s_slot)"%(varname, varname)
  407.  
  408.             id = slotnames.index(slotname)
  409.             idx = len(self.forwarders)
  410.             
  411.             self.slotobjs.append((slot,id))
  412.  
  413.  
  414.     # deleteSlotAttribs
  415.     def deleteSlotAttribs(self):
  416.         if self.slots==None:
  417.             return
  418.         
  419.         for name,slot in self.slots:
  420.             slotname = "%s_slot"%name
  421.             exec "del self.%s"%slotname
  422.  
  423.         self.slots = None
  424.  
  425.     # encodeValueMessage
  426.     def encodeValueMessage(self, id, idx, slot):
  427.         """Encode a binary value message.
  428.  
  429.         \param id (\c int) Message ID
  430.         \param idx (\c int) Slot index
  431.         \param slot (\c Slot) Corresponding slot
  432.         """
  433.  
  434.         # int
  435.         if id==1:
  436.             return struct.pack(">HHi", id, idx, slot.getValue())
  437.         # double
  438.         elif id==2:
  439.             return struct.pack(">HHd", id, idx, slot.getValue())
  440.         # bool
  441.         elif id==3:
  442.             return struct.pack(">HHh", id, idx, slot.getValue())
  443.         # vec3
  444.         elif id==4:
  445.             x,y,z = slot.getValue()
  446.             return struct.pack(">HHddd", id, idx, x, y, z)
  447.         # vec4
  448.         elif id==5:
  449.             x,y,z,w = slot.getValue()
  450.             return struct.pack(">HHdddd", id, idx, x, y, z,w)
  451.         # mat3
  452.         elif id==6:
  453.             M = slot.getValue()
  454.             return struct.pack(">HHddddddddd", id, idx, *M.toList(rowmajor=True))
  455.         # mat4
  456.         elif id==7:
  457.             M = slot.getValue()
  458.             return struct.pack(">HHdddddddddddddddd", id, idx, *M.toList(rowmajor=True))
  459.         # quat
  460.         elif id==8:
  461.             q = slot.getValue()
  462.             return struct.pack(">HHdddd", id, idx, q.w, q.x, q.y, q.z)
  463.  
  464.     # decodeValueMessage
  465.     def decodeValueMessage(self, id, msg):
  466.         """Decode a binary value message.
  467.  
  468.         Raises a ValueError exception if the id is invalid. If the
  469.         size of msg is too short, a struct.error exception is thrown.
  470.  
  471.         \param id (\c int) Message ID
  472.         \param msg (\c str) Data part of the message (may contain additional messages)
  473.         \return Tuple (Message size, slot index, value)
  474.         """
  475.  
  476.         # int
  477.         if id==1:
  478.             id, v = struct.unpack(">Hi", msg[:6])
  479.             return 6,id,v
  480.         # double
  481.         elif id==2:
  482.             id, v = struct.unpack(">Hd", msg[0:10])
  483.             return 10,id,v
  484.         # bool
  485.         elif id==3:
  486.             id, v = struct.unpack(">Hh", msg[:4])
  487.             return 4,id,v
  488.         # vec3
  489.         elif id==4:
  490.             idx, x,y,z = struct.unpack(">Hddd", msg[:26])
  491.             return (26, idx, vec3(x,y,z))
  492.         # vec4
  493.         elif id==5:
  494.             idx, x,y,z,w = struct.unpack(">Hdddd", msg[:34])
  495.             return (34, idx, vec4(x,y,z,w))
  496.         # mat3
  497.         elif id==6:
  498.             idx, m11,m12,m13,m21,m22,m23,m31,m32,m33 = struct.unpack(">Hddddddddd", msg[:74])
  499.             return (74, idx, mat3(m11,m12,m13,m21,m22,m23,m31,m32,m33))
  500.         # mat4
  501.         elif id==7:
  502.             idx, m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44 = struct.unpack(">Hdddddddddddddddd", msg[:130])
  503.             return (130, idx, mat4(m11,m12,m13,m14,m21,m22,m23,m24,m31,m32,m33,m34,m41,m42,m43,m44))
  504.         # quat
  505.         elif id==8:
  506.             idx, w,x,y,z = struct.unpack(">Hdddd", msg[:34])
  507.             return (34, idx, quat(w,x,y,z))
  508.         else:
  509.             raise ValueError, "Unknown message id (%d)"%id
  510.             
  511.  
  512.     # iterSlotDefs
  513.     def iterSlotDefs(self, slots):
  514.         """Generates the variable name, slot name and slot parameters.
  515.  
  516.         This generator method takes a slot definition list and generates
  517.         3-tuples (variable name, slot name, slot parameters).
  518.  
  519.         For example, the slot definition list [("xpos", "IntSlot(2)"),
  520.         ("ypos", "IntSlot(12)")] will generate two tuples ("xpos", "IntSlot",
  521.         "(2)") and ("ypos", "IntSlot", "(12)").
  522.         """
  523.         
  524.         for varname, slotdef in slots:
  525.             n = slotdef.find("(")
  526.             if n!=-1:
  527.                 slotname = slotdef[:n]
  528.                 slotparams = slotdef[n:]
  529.             else:
  530.                 slotname = slotdef
  531.                 slotparams = "()"
  532.             yield varname, slotname, slotparams
  533.         
  534.