Advertisement
Guest User

PyPy Pipe 0.2

a guest
Jul 26th, 2010
214
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.28 KB | None | 0 0
  1. #!/usr/bin/python
  2. '''
  3. PyPy Pipe v0.2
  4. Brett Hartshorn 2010 - License BSD
  5. goatman.py@gmail.com
  6.  
  7. Use CPython as a host for external libs, tested with PyGTK, PyODE, and Pygame
  8. return types limited: bool, tuple, object   # TODO add more
  9. no keyword arguments can be used from RPython, unless you define a custom wrapper.
  10. only simple lambda callbacks are working (these operate in CPython space)
  11. TODO: callback from CPython to RPython
  12.  
  13. Getting Started:
  14.     copy this file to your pypy trunk folder, or add the path to pypy below PATH2PYPY
  15.     make sure you have pygame, pygtk, and pyode
  16.  
  17. Hacking:
  18.     Dynamic attribute access is not possible, but function calls are ok.
  19.     This is wrapper incomplete, any class with unique init args, or functions with unique
  20.     args must be hand wrapped.  Even the return type may need to be defined.
  21.     See below:
  22.         gen_func_response
  23.         genRPC
  24.         INIT_GROUPS
  25.  
  26.  
  27. pypy hacking notes:
  28.     s = s[1:len(s)-1]       # pypy.rpython.error.TyperError: slice stop must be proved non-negative
  29.     s = s.replace('<','').replace('>','')   # pypy.rpython.error.TyperError: replace only works for char args
  30.     long(string, 16) not allowed
  31.     globals() not allowed
  32.     function return types can not be mixed (required generated return functions per incompatible types)
  33.  
  34. '''
  35. ## Set your PyPy root ##
  36. PATH2PYPY = 'pypy'
  37.  
  38. ## Reserved Characters, can not be passed from rpython to Cpython by function call in a string
  39. ## you can change them if you need ##
  40. INSTANCE_SEP = '|'
  41. FUNCTION_SEP = '$'
  42. FUNC_ARG_SEP = '^'
  43. ARGUMENT_SEP = '~'
  44. MODULE_SEP = '!'
  45. DO_NOT_CREATE = 'DO_NOT_CREATE'
  46.  
  47. import os, sys, time, inspect, types, select, subprocess, math
  48. sys.path.append(PATH2PYPY)      # assumes you have pypy dist in a subfolder, you may need to rename this pypy-trunk
  49. import pypy.tool.error
  50. import glib, gtk
  51. import pygame, ode
  52.  
  53. DEBUG = False
  54.  
  55. def debug( string ):
  56.     stderr.write( '\t[debug][%s]\n' %string )
  57.     stderr.flush()
  58.  
  59. degToRad = math.pi / 180.0
  60. def radians(x):
  61.     """radians(x) -> converts angle x from degrees to radians
  62.     """
  63.     return x * degToRad
  64.  
  65. def loop():
  66.     #time.sleep(0.01)
  67.     rlist,wlist,xlist = select.select( [read], [write], [], 0.01 )     
  68.     if rlist and wlist: # must wait for both read and write, because read can be selected by itself, but we must also respond
  69.         a = read.readline().strip()
  70.         if DEBUG and a: print a
  71.         if a.startswith('>'):
  72.             cname = header = a.split('>')[-1].strip()
  73.             mod = globals()
  74.             if MODULE_SEP in header:
  75.                 mname, cname = header.split( MODULE_SEP )   # should raise syntax error if ! was in arg string
  76.                 mod = mod[ mname ]
  77.  
  78.                 if FUNC_ARG_SEP in cname:
  79.                     cname, args = cname.split( FUNC_ARG_SEP )
  80.                     nargs = string2args( args )
  81.                     cls = getattr( mod, cname )
  82.                     # workaround for functions that return objects
  83.                     if DO_NOT_CREATE in nargs:
  84.                         if DEBUG: print 'not creating instance'
  85.                         ID = nargs[-1]; o = INSTANCES[ int(ID) ]
  86.                     else: o = cls( *nargs )
  87.                 else:
  88.                     cls = getattr( mod, cname )
  89.                     o = cls()
  90.  
  91.             else: ## module ##
  92.                 #if cname not in mod:   ## this is not useful because it needs to be wrapped beforehand
  93.                 #   print '------importing-------'
  94.                 #   o = __import__( cname, mod )
  95.                 #else:
  96.                 if DEBUG: print '-----getting module------'
  97.                 if '.' in cname:
  98.                     x,y = cname.split('.')
  99.                     o = getattr(mod[ x ], y )
  100.                 else: o = mod[ cname ]
  101.  
  102.             ID = id( o )        # cpython object
  103.             INSTANCES[ ID ] = o
  104.             if DEBUG:
  105.                 print '$cpython instance', o
  106.                 print '$instance ID', ID
  107.             write.write( '%s\n' %ID )
  108.             write.flush()
  109.  
  110.         elif a.startswith('@'):
  111.             ID, b = a.split( FUNCTION_SEP )
  112.             ID = int(ID[1:])        # strip the @
  113.             o = INSTANCES[ID]
  114.             if DEBUG: print '$got instance', o
  115.             fname,args = b.split(FUNC_ARG_SEP)
  116.             func = getattr( o, fname )
  117.             nargs = string2args( args )
  118.             if DEBUG: print 'calling', func
  119.             res = func( *nargs )        # call the function
  120.             ## check the output of the function ##
  121.             r = ''
  122.             if isinstance(res,gtk.Object):
  123.                 sID = id(res)
  124.                 if sID not in INSTANCES: INSTANCES[ sID ] = res
  125.                 r += '<gtk.%s%s%s>' %(res.__class__.__name__, INSTANCE_SEP, sID)
  126.             elif isinstance( res, pygame.Surface ):
  127.                 sID = id(res)
  128.                 if sID not in INSTANCES: INSTANCES[ sID ] = res
  129.                 r += '<pygame.%s%s%s>' %(res.__class__.__name__, INSTANCE_SEP, sID)
  130.             elif isinstance( res, (ode.World,ode.Body) ):
  131.                 sID = id(res)
  132.                 if sID not in INSTANCES: INSTANCES[ sID ] = res
  133.                 r += '<ode.%s%s%s>' %(res.__class__.__name__, INSTANCE_SEP, sID)
  134.  
  135.  
  136.             else: r = str( res )
  137.             #sres = sres.replace(' instance at ', INSTANCE_SEP)
  138.             #if type(res) in (tuple,list):
  139.             if DEBUG: print 'piping back ->', r
  140.             write.write( '%s\n' %r )
  141.             write.flush()
  142.  
  143.     return True
  144.  
  145.  
  146. def pipe( string ): # the types of *args can not change in pypy, convert to strings first
  147.     rl,wl,xl = rpoll.select( [], [1], [], 0 )
  148.     if wl:
  149.         stdout.write( '%s\n' %string )
  150.         stdout.flush()
  151.  
  152. def string2args( string ):
  153.     nargs = []
  154.     for arg in string.split(ARGUMENT_SEP):
  155.         if arg.strip():
  156.             if arg.startswith('@'): nargs.append( INSTANCES[int(arg[1:])] )
  157.             else: nargs.append( eval(arg) )
  158.     return nargs
  159.  
  160. GeneratedResponses = []
  161.  
  162. def gen_func_response( rtype=object ):
  163.     if rtype is bool:
  164.         def get_function_response(dummy):
  165.             if DEBUG: debug( 'waiting for function response' )
  166.             rl,wl,xl = rpoll.select( [0], [], [], 60 )  # wait 10 seconds, subprocess only needs to wait for reading
  167.             if rl:
  168.                 if DEBUG: debug( 'readline ready' )
  169.                 s = string = stdin.readline().strip('\n').strip(' ')
  170.                 if DEBUG: debug( '^got function response: %s' %string )
  171.                 if s == 'True': return True
  172.                 elif s == 'False': return False
  173.             else:
  174.                 debug( '^^ lost contact with parent process ^^' )
  175.                 raise SystemExit
  176.     elif rtype is tuple:
  177.         def get_function_response(dummy):
  178.             if DEBUG: debug( 'waiting for function response' )
  179.             rl,wl,xl = rpoll.select( [0], [], [], 60 )  # wait 10 seconds, subprocess only needs to wait for reading
  180.             if rl:
  181.                 if DEBUG: debug( 'readline ready' )
  182.                 s = string = stdin.readline().strip('\n').strip(' ')
  183.                 if DEBUG: debug( '^got function response: %s' %string )
  184.                 s = s.replace('(',' ').replace(')',' ').strip(' ')
  185.                 return [ float(x) for x in s.split(',') ]
  186.             else:
  187.                 debug( '^^ lost contact with parent process ^^' )
  188.                 raise SystemExit
  189.  
  190.     elif rtype is object:
  191.         def get_function_response(dummy):
  192.             if DEBUG: debug( 'waiting for function response' )
  193.             rl,wl,xl = rpoll.select( [0], [], [], 60 )  # wait 10 seconds, subprocess only needs to wait for reading
  194.             if rl:
  195.                 if DEBUG: debug( 'readline ready' )
  196.                 s = string = stdin.readline().strip('\n').strip(' ')
  197.                 if DEBUG: debug( '^got function response: %s' %string )
  198.                 if s.startswith('<') and s.endswith('>'):
  199.                     s = s.replace('<',' ').replace('>',' ').strip(' ')
  200.                     if INSTANCE_SEP in s:
  201.                         a,b = s.split( INSTANCE_SEP )       # pypy only splits on a single char
  202.                         ID = int( b )
  203.                         if ID in Cache.instances:
  204.                             if DEBUG: debug( 'proxy already exists' )
  205.                             return Cache.instances[ID]
  206.                         else:
  207.                             if DEBUG: debug( 'creating new rproxy' )
  208.                             cls = CLASSES[ a ]
  209.                             return cls( DO_NOT_CREATE, str(ID) )
  210.             else:
  211.                 debug( '^^ lost contact with parent process ^^' )
  212.                 raise SystemExit
  213.  
  214.     GeneratedResponses.append( get_function_response )
  215.     return get_function_response
  216.  
  217. class RCache(object):
  218.     def __init__(self):
  219.         self.instances = {}
  220. Cache = RCache()
  221.  
  222.  
  223. class Proxy(object):        ## pypy only allows __init__ and __del__
  224.     pass
  225. def geninit():      # generating init for each class is too slow
  226.     def __init__(self, *args):
  227.         if self._modulename_: header = '%s%s%s' %(self._modulename_, MODULE_SEP, self._classname_)
  228.         else: header = self._classname_
  229.         if args:
  230.             a = ''
  231.             for arg in list(args):
  232.                 if isinstance(arg,Proxy): a += '@%s%s' %(arg._ID, ARGUMENT_SEP)
  233.                 elif isinstance( arg, str ):        # type(arg) is str # not allowed in pypy
  234.                     a += '"%s"%s' %(arg, ARGUMENT_SEP)
  235.                 else: a += '%s%s' %(arg, ARGUMENT_SEP)
  236.             pipe( '>create>%s%s%s' %(header, FUNC_ARG_SEP, a) )
  237.         else: pipe( '>create>%s' %header )
  238.         self._ID = 0
  239.         rl,wl,xl = rpoll.select( [0], [], [], 10.0 )    # wait 10 seconds, subprocess only needs to wait for reading
  240.         if rl:
  241.             ID = stdin.readline().strip('\n').strip(' ')
  242.             self._ID = int(ID)
  243.             Cache.instances[ self._ID ] = self
  244.         else:
  245.             pipe( '^^ lost contact with parent process ^^' )
  246.             raise SystemExit
  247.     return __init__
  248. Proxy.__init__ = geninit()
  249.  
  250. CLASSES = {}
  251. INSTANCES = {}
  252. Generated = []
  253.  
  254. def genRPC( fname, func ):      ## interesting pypy needs generated functions so that *args can work properly
  255.     index = len( Generated )
  256.     gupdate = {}
  257.     if func is ode.Body.getPosition:
  258.         gupdate[ '_genRes%s'%index ] = gen_func_response( tuple )
  259.     else:
  260.         gupdate[ '_genRes%s'%index ] = gen_func_response()      # defaults to object
  261.  
  262.     if func is gtk.Object.connect:
  263.         def rpc( self, name, tag, callback, *args ):
  264.             if DEBUG: debug( '-rpc %s %s' %(self, name) )
  265.             a = '"%s"%s' %(tag, ARGUMENT_SEP)
  266.             a += '%s%s' %(callback, ARGUMENT_SEP)       # TODO, can we send the byte codes of lambda? or how to get source?
  267.             for arg in list(args):
  268.                 if isinstance(arg,Proxy): a += '@%s%s' %(arg._ID, ARGUMENT_SEP)
  269.                 elif isinstance( arg, str ):        # type(arg) is str # not allowed in pypy
  270.                     a += '"%s"%s' %(arg, ARGUMENT_SEP)
  271.                 else: a += '%s%s' %(arg, ARGUMENT_SEP)
  272.             pipe( '@%s%s%s%s%s' %(self._ID, FUNCTION_SEP, name, FUNC_ARG_SEP, a) )
  273.             #return get_function_response()
  274.         wrap = eval( 'lambda self,tag,cb,*args: _genRes%s(_genRPC%s(self,"%s",tag,cb,*args))' %(index,index,fname) )
  275.  
  276.     elif func in (pygame.draw.line, pygame.draw.aaline):
  277.         def rpc( self, name, surf, color, start, end, width=1 ):
  278.             if DEBUG: debug( '-rpc %s %s' %(self, name) )
  279.             a = '@%s%s' %(surf._ID,ARGUMENT_SEP)
  280.             a += '%s%s' %(color,ARGUMENT_SEP)
  281.             a += '%s%s' %(start,ARGUMENT_SEP)
  282.             a += '%s%s' %(end,ARGUMENT_SEP)
  283.             a += '%s%s' %(width,ARGUMENT_SEP)
  284.             pipe( '@%s%s%s%s%s' %(self._ID, FUNCTION_SEP, name, FUNC_ARG_SEP, a) )
  285.             #return get_function_response()
  286.         wrap = eval( 'lambda self,surf,color,start,end,width=1: _genRes%s(_genRPC%s(self,"%s",surf,color,start,end,width))' %(index,index,fname) )
  287.  
  288.  
  289.     else:
  290.         def rpc( self, name, *args ):
  291.             if DEBUG: debug( '-rpc %s %s' %(self, name) )
  292.             a = ''
  293.             for arg in list(args):
  294.                 if isinstance(arg,Proxy): a += '@%s%s' %(arg._ID, ARGUMENT_SEP)
  295.                 elif isinstance( arg, str ):        # type(arg) is str # not allowed in pypy
  296.                     a += '"%s"%s' %(arg, ARGUMENT_SEP)
  297.                 else: a += '%s%s' %(arg, ARGUMENT_SEP)
  298.             pipe( '@%s%s%s%s%s' %(self._ID, FUNCTION_SEP, name, FUNC_ARG_SEP, a) )
  299.             #return get_function_response()
  300.         wrap = eval( 'lambda self,*args: _genRes%s(_genRPC%s(self,"%s",*args))' %(index,index,fname) )
  301.  
  302.     Generated.append( rpc )
  303.     gupdate[ '_genRPC%s'%index ] = rpc
  304.     globals().update( gupdate )
  305.  
  306.     return wrap, index
  307.  
  308. ## these are considered constants ##
  309. WrappableAttributesTypes = (
  310.     bool,
  311.     int,
  312.     long,
  313.     float,
  314.     str,
  315.     unicode,
  316. )
  317. INIT_GROUPS = {
  318.     (gtk.Window,) : geninit(),      # int (enum) group
  319.     (ode.Body,): geninit(),         # object (proxy) group
  320. }
  321. def wrap_class( cls, classes=False ):
  322.     name = cls.__name__
  323.     if inspect.ismodule( cls ): mname = None
  324.     else: mname = cls.__module__
  325.  
  326.     funcs = {}
  327.     fnames = ''
  328.     d = { '_classname_':name, '_modulename_':mname }
  329.     for group in INIT_GROUPS:
  330.         for sclass in group:
  331.             if cls is sclass:
  332.                 d['__init__'] = INIT_GROUPS[ group ]    # classes that have the same init vars
  333.                 break
  334.     #d['__init__'] = geninit()  # takes too long to generate init for all classes, create a INIT_GROUP see above.
  335.  
  336.     for n in dir(cls):
  337.         if n.startswith('_'): continue
  338.         a = getattr( cls, n )
  339.         if inspect.isroutine( a ):
  340.             #if classes: print '\tFUNCTION: ', n
  341.             genfunc, index = genRPC(n,a)
  342.             d[n] = genfunc
  343.         elif inspect.isclass( a ) and classes:
  344.             #if classes: print '\tCLASS: ', n
  345.             d[ n ] = wrap_class( a )
  346.         elif type(a) in WrappableAttributesTypes:
  347.             d[ n ] = a
  348.         elif type(a) in (list,tuple):
  349.             safe = True
  350.             for item in a:
  351.                 if type(item) not in WrappableAttributesTypes:
  352.                     safe = False; break
  353.             if safe:
  354.                 d[ n ] = a
  355.         else:
  356.             ## check for gtk enum
  357.             items = dir(a)
  358.             enum = True
  359.             for test in 'conjugate denominator imag numerator real'.split():
  360.                 if test not in items: enum = False; break
  361.             if enum:
  362.                 d[ n ] = a.real
  363.  
  364.     mclass = types.ClassType(name, bases=(Proxy,), dict=d)
  365.     if mname: key = '%s.%s' %(mname,name)
  366.     else: key = name
  367.     CLASSES[ key ] = mclass
  368.     return mclass
  369.  
  370.  
  371. if '--pypy' in sys.argv:
  372.     from pypy.rlib import streamio
  373.     from pypy.rlib import rpoll
  374.  
  375.     stdin = streamio.fdopen_as_stream(0, 'r', 0)        # fd, mode, buffering
  376.     stdout = streamio.fdopen_as_stream(1, 'w', 0)
  377.     stderr = streamio.fdopen_as_stream(2, 'w', 0)
  378.  
  379.     from pypy.translator.interactive import Translation
  380.     gtk_wrapped = wrap_class( gtk, classes=True )
  381.     pygame_wrapped = wrap_class( pygame, classes=True )
  382.     pygame_display_wrapped = wrap_class( pygame.display, classes=True )
  383.     pygame_draw_wrapped = wrap_class( pygame.draw, classes=True )
  384.  
  385.     ode_wrapped = wrap_class( ode, classes=True )
  386.  
  387. def entry_point():
  388.     ## init module level proxies ##
  389.     gtk = gtk_wrapped()
  390.     pygame = pygame_wrapped()
  391.     pygame.display = pygame_display_wrapped()
  392.     pygame.draw = pygame_draw_wrapped()
  393.     ode = ode_wrapped()
  394.  
  395.     world = ode.World()
  396.     #world.setERP( 0.8 )
  397.     #world.setCFM( 0.1 )
  398.     #world.setGravity( ( 0, -0.6*10000, 0 ) )
  399.     body = ode.Body( world )
  400.     body.addForce( (1,0,0) )
  401.  
  402.     w = gtk.Window()
  403.     w.set_size_request( 320,200 )
  404.     w.set_title('PyPyGTK v0.1b')
  405.     root = gtk.VBox(); w.add( root )
  406.     root.set_border_width(50)
  407.     lab = gtk.Label()
  408.     lab.set_text( 'hello world' )
  409.     root.pack_start( lab )
  410.     b = gtk.Button( 'click me' )
  411.     b.connect('clicked', "lambda b,l: l.set_text('clicked test')", lab )        # note lambdas are passed as strings
  412.     root.pack_start( b )
  413.     root2 = b.get_parent()
  414.     root2.pack_start( gtk.Label('return instance from CPython works') )
  415.     b = gtk.Button('exit demo')
  416.     b.connect('clicked', 'lambda b: gtk.main_quit()' )
  417.     root2.pack_start( b )
  418.     frame = gtk.Frame('PyODE body position')
  419.     root2.pack_start( frame )
  420.     bodylab = gtk.Label('body pos')
  421.     frame.add( bodylab )
  422.     w.show_all()
  423.  
  424.     w2 = gtk.Window( gtk.WINDOW_POPUP )
  425.     w2.move( 20,240 )
  426.     w2.set_size_request( 320,100 )
  427.     r2 = gtk.VBox(); w2.add( r2 )
  428.     lab2 = gtk.Label('enum values work (this is a popup window)' )
  429.     r2.pack_start( lab2 )
  430.     b2 = gtk.Button('close')
  431.     b2.connect('clicked', "lambda b,w: w.destroy()", w2)
  432.     r2.pack_start( b2 )
  433.     w2.show_all()
  434.  
  435.     pygame.init()
  436.     surf = pygame.display.set_mode( (320,240) )
  437.     i = 0
  438.     while True:     # does not block gtk because we have a glib timeout in the other process
  439.         x = math.sin( radians(i) ) * 100
  440.         y = math.cos( radians(i) ) * 100
  441.         pygame.draw.aaline( surf, (255,255,255,255), (160,120), (int(160+x),int(120+y)) )
  442.         pygame.display.flip()
  443.  
  444.         world.quickStep( 0.001 )
  445.         pos = body.getPosition()
  446.         bodylab.set_text( str(pos) )
  447.  
  448.         i += 1
  449.         if i == 360: raise SystemExit
  450.  
  451. if '--pypy' in sys.argv:
  452.     t = Translation( entry_point )
  453.     t.annotate(); t.rtype()
  454.     f = t.compile_c()
  455.     f()
  456.     print( 'subprocess exit' )
  457.  
  458. else:
  459.     process = subprocess.Popen( 'python %s --pypy' %sys.argv[0], stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0, shell=True )
  460.     write = process.stdin
  461.     read = process.stdout
  462.     print( 'read pipe', read )
  463.     print( 'read write', write )
  464.     glib.timeout_add( 33, loop )
  465.     gtk.main()
  466.     print( 'toplevel exit' )
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement