Advertisement
Guest User

PyPy Pipe 0.2

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