Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/python
- '''
- PyPy Pipe v0.2
- Brett Hartshorn 2010 - License BSD
- goatman.py@gmail.com
- Use CPython as a host for external libs, tested with PyGTK, PyODE, and Pygame
- return types limited: bool, tuple, object # TODO add more
- no keyword arguments can be used from RPython, unless you define a custom wrapper.
- only simple lambda callbacks are working (these operate in CPython space)
- TODO: callback from CPython to RPython
- Getting Started:
- copy this file to your pypy trunk folder, or add the path to pypy below PATH2PYPY
- make sure you have pygame, pygtk, and pyode
- Hacking:
- Dynamic attribute access is not possible, but function calls are ok.
- This is wrapper incomplete, any class with unique init args, or functions with unique
- args must be hand wrapped. Even the return type may need to be defined.
- See below:
- gen_func_response
- genRPC
- INIT_GROUPS
- pypy hacking notes:
- s = s[1:len(s)-1] # pypy.rpython.error.TyperError: slice stop must be proved non-negative
- s = s.replace('<','').replace('>','') # pypy.rpython.error.TyperError: replace only works for char args
- long(string, 16) not allowed
- globals() not allowed
- function return types can not be mixed (required generated return functions per incompatible types)
- '''
- ## Set your PyPy root ##
- PATH2PYPY = 'pypy'
- ## Reserved Characters, can not be passed from rpython to Cpython by function call in a string
- ## you can change them if you need ##
- INSTANCE_SEP = '|'
- FUNCTION_SEP = '$'
- FUNC_ARG_SEP = '^'
- ARGUMENT_SEP = '~'
- MODULE_SEP = '!'
- DO_NOT_CREATE = 'DO_NOT_CREATE'
- import os, sys, time, inspect, types, select, subprocess, math
- sys.path.append(PATH2PYPY) # assumes you have pypy dist in a subfolder, you may need to rename this pypy-trunk
- import pypy.tool.error
- import glib, gtk
- import pygame, ode
- DEBUG = False
- def debug( string ):
- stderr.write( '\t[debug][%s]\n' %string )
- stderr.flush()
- degToRad = math.pi / 180.0
- def radians(x):
- """radians(x) -> converts angle x from degrees to radians
- """
- return x * degToRad
- def loop():
- #time.sleep(0.01)
- rlist,wlist,xlist = select.select( [read], [write], [], 0.01 )
- if rlist and wlist: # must wait for both read and write, because read can be selected by itself, but we must also respond
- a = read.readline().strip()
- if DEBUG and a: print a
- if a.startswith('>'):
- cname = header = a.split('>')[-1].strip()
- mod = globals()
- if MODULE_SEP in header:
- mname, cname = header.split( MODULE_SEP ) # should raise syntax error if ! was in arg string
- mod = mod[ mname ]
- if FUNC_ARG_SEP in cname:
- cname, args = cname.split( FUNC_ARG_SEP )
- nargs = string2args( args )
- cls = getattr( mod, cname )
- # workaround for functions that return objects
- if DO_NOT_CREATE in nargs:
- if DEBUG: print 'not creating instance'
- ID = nargs[-1]; o = INSTANCES[ int(ID) ]
- else: o = cls( *nargs )
- else:
- cls = getattr( mod, cname )
- o = cls()
- else: ## module ##
- #if cname not in mod: ## this is not useful because it needs to be wrapped beforehand
- # print '------importing-------'
- # o = __import__( cname, mod )
- #else:
- if DEBUG: print '-----getting module------'
- if '.' in cname:
- x,y = cname.split('.')
- o = getattr(mod[ x ], y )
- else: o = mod[ cname ]
- ID = id( o ) # cpython object
- INSTANCES[ ID ] = o
- if DEBUG:
- print '$cpython instance', o
- print '$instance ID', ID
- write.write( '%s\n' %ID )
- write.flush()
- elif a.startswith('@'):
- ID, b = a.split( FUNCTION_SEP )
- ID = int(ID[1:]) # strip the @
- o = INSTANCES[ID]
- if DEBUG: print '$got instance', o
- fname,args = b.split(FUNC_ARG_SEP)
- func = getattr( o, fname )
- nargs = string2args( args )
- if DEBUG: print 'calling', func
- res = func( *nargs ) # call the function
- ## check the output of the function ##
- r = ''
- if isinstance(res,gtk.Object):
- sID = id(res)
- if sID not in INSTANCES: INSTANCES[ sID ] = res
- r += '<gtk.%s%s%s>' %(res.__class__.__name__, INSTANCE_SEP, sID)
- elif isinstance( res, pygame.Surface ):
- sID = id(res)
- if sID not in INSTANCES: INSTANCES[ sID ] = res
- r += '<pygame.%s%s%s>' %(res.__class__.__name__, INSTANCE_SEP, sID)
- elif isinstance( res, (ode.World,ode.Body) ):
- sID = id(res)
- if sID not in INSTANCES: INSTANCES[ sID ] = res
- r += '<ode.%s%s%s>' %(res.__class__.__name__, INSTANCE_SEP, sID)
- else: r = str( res )
- #sres = sres.replace(' instance at ', INSTANCE_SEP)
- #if type(res) in (tuple,list):
- if DEBUG: print 'piping back ->', r
- write.write( '%s\n' %r )
- write.flush()
- return True
- def pipe( string ): # the types of *args can not change in pypy, convert to strings first
- rl,wl,xl = rpoll.select( [], [1], [], 0 )
- if wl:
- stdout.write( '%s\n' %string )
- stdout.flush()
- def string2args( string ):
- nargs = []
- for arg in string.split(ARGUMENT_SEP):
- if arg.strip():
- if arg.startswith('@'): nargs.append( INSTANCES[int(arg[1:])] )
- else: nargs.append( eval(arg) )
- return nargs
- GeneratedResponses = []
- def gen_func_response( rtype=object ):
- if rtype is bool:
- def get_function_response(dummy):
- if DEBUG: debug( 'waiting for function response' )
- rl,wl,xl = rpoll.select( [0], [], [], 60 ) # wait 10 seconds, subprocess only needs to wait for reading
- if rl:
- if DEBUG: debug( 'readline ready' )
- s = string = stdin.readline().strip('\n').strip(' ')
- if DEBUG: debug( '^got function response: %s' %string )
- if s == 'True': return True
- elif s == 'False': return False
- else:
- debug( '^^ lost contact with parent process ^^' )
- raise SystemExit
- elif rtype is tuple:
- def get_function_response(dummy):
- if DEBUG: debug( 'waiting for function response' )
- rl,wl,xl = rpoll.select( [0], [], [], 60 ) # wait 10 seconds, subprocess only needs to wait for reading
- if rl:
- if DEBUG: debug( 'readline ready' )
- s = string = stdin.readline().strip('\n').strip(' ')
- if DEBUG: debug( '^got function response: %s' %string )
- s = s.replace('(',' ').replace(')',' ').strip(' ')
- return [ float(x) for x in s.split(',') ]
- else:
- debug( '^^ lost contact with parent process ^^' )
- raise SystemExit
- elif rtype is object:
- def get_function_response(dummy):
- if DEBUG: debug( 'waiting for function response' )
- rl,wl,xl = rpoll.select( [0], [], [], 60 ) # wait 10 seconds, subprocess only needs to wait for reading
- if rl:
- if DEBUG: debug( 'readline ready' )
- s = string = stdin.readline().strip('\n').strip(' ')
- if DEBUG: debug( '^got function response: %s' %string )
- if s.startswith('<') and s.endswith('>'):
- s = s.replace('<',' ').replace('>',' ').strip(' ')
- if INSTANCE_SEP in s:
- a,b = s.split( INSTANCE_SEP ) # pypy only splits on a single char
- ID = int( b )
- if ID in Cache.instances:
- if DEBUG: debug( 'proxy already exists' )
- return Cache.instances[ID]
- else:
- if DEBUG: debug( 'creating new rproxy' )
- cls = CLASSES[ a ]
- return cls( DO_NOT_CREATE, str(ID) )
- else:
- debug( '^^ lost contact with parent process ^^' )
- raise SystemExit
- GeneratedResponses.append( get_function_response )
- return get_function_response
- class RCache(object):
- def __init__(self):
- self.instances = {}
- Cache = RCache()
- class Proxy(object): ## pypy only allows __init__ and __del__
- pass
- def geninit(): # generating init for each class is too slow
- def __init__(self, *args):
- if self._modulename_: header = '%s%s%s' %(self._modulename_, MODULE_SEP, self._classname_)
- else: header = self._classname_
- if args:
- a = ''
- for arg in list(args):
- if isinstance(arg,Proxy): a += '@%s%s' %(arg._ID, ARGUMENT_SEP)
- elif isinstance( arg, str ): # type(arg) is str # not allowed in pypy
- a += '"%s"%s' %(arg, ARGUMENT_SEP)
- else: a += '%s%s' %(arg, ARGUMENT_SEP)
- pipe( '>create>%s%s%s' %(header, FUNC_ARG_SEP, a) )
- else: pipe( '>create>%s' %header )
- self._ID = 0
- rl,wl,xl = rpoll.select( [0], [], [], 10.0 ) # wait 10 seconds, subprocess only needs to wait for reading
- if rl:
- ID = stdin.readline().strip('\n').strip(' ')
- self._ID = int(ID)
- Cache.instances[ self._ID ] = self
- else:
- pipe( '^^ lost contact with parent process ^^' )
- raise SystemExit
- return __init__
- Proxy.__init__ = geninit()
- CLASSES = {}
- INSTANCES = {}
- Generated = []
- def genRPC( fname, func ): ## interesting pypy needs generated functions so that *args can work properly
- index = len( Generated )
- gupdate = {}
- if func is ode.Body.getPosition:
- gupdate[ '_genRes%s'%index ] = gen_func_response( tuple )
- else:
- gupdate[ '_genRes%s'%index ] = gen_func_response() # defaults to object
- if func is gtk.Object.connect:
- def rpc( self, name, tag, callback, *args ):
- if DEBUG: debug( '-rpc %s %s' %(self, name) )
- a = '"%s"%s' %(tag, ARGUMENT_SEP)
- a += '%s%s' %(callback, ARGUMENT_SEP) # TODO, can we send the byte codes of lambda? or how to get source?
- for arg in list(args):
- if isinstance(arg,Proxy): a += '@%s%s' %(arg._ID, ARGUMENT_SEP)
- elif isinstance( arg, str ): # type(arg) is str # not allowed in pypy
- a += '"%s"%s' %(arg, ARGUMENT_SEP)
- else: a += '%s%s' %(arg, ARGUMENT_SEP)
- pipe( '@%s%s%s%s%s' %(self._ID, FUNCTION_SEP, name, FUNC_ARG_SEP, a) )
- #return get_function_response()
- wrap = eval( 'lambda self,tag,cb,*args: _genRes%s(_genRPC%s(self,"%s",tag,cb,*args))' %(index,index,fname) )
- elif func in (pygame.draw.line, pygame.draw.aaline):
- def rpc( self, name, surf, color, start, end, width=1 ):
- if DEBUG: debug( '-rpc %s %s' %(self, name) )
- a = '@%s%s' %(surf._ID,ARGUMENT_SEP)
- a += '%s%s' %(color,ARGUMENT_SEP)
- a += '%s%s' %(start,ARGUMENT_SEP)
- a += '%s%s' %(end,ARGUMENT_SEP)
- a += '%s%s' %(width,ARGUMENT_SEP)
- pipe( '@%s%s%s%s%s' %(self._ID, FUNCTION_SEP, name, FUNC_ARG_SEP, a) )
- #return get_function_response()
- wrap = eval( 'lambda self,surf,color,start,end,width=1: _genRes%s(_genRPC%s(self,"%s",surf,color,start,end,width))' %(index,index,fname) )
- else:
- def rpc( self, name, *args ):
- if DEBUG: debug( '-rpc %s %s' %(self, name) )
- a = ''
- for arg in list(args):
- if isinstance(arg,Proxy): a += '@%s%s' %(arg._ID, ARGUMENT_SEP)
- elif isinstance( arg, str ): # type(arg) is str # not allowed in pypy
- a += '"%s"%s' %(arg, ARGUMENT_SEP)
- else: a += '%s%s' %(arg, ARGUMENT_SEP)
- pipe( '@%s%s%s%s%s' %(self._ID, FUNCTION_SEP, name, FUNC_ARG_SEP, a) )
- #return get_function_response()
- wrap = eval( 'lambda self,*args: _genRes%s(_genRPC%s(self,"%s",*args))' %(index,index,fname) )
- Generated.append( rpc )
- gupdate[ '_genRPC%s'%index ] = rpc
- globals().update( gupdate )
- return wrap, index
- ## these are considered constants ##
- WrappableAttributesTypes = (
- bool,
- int,
- long,
- float,
- str,
- unicode,
- )
- INIT_GROUPS = {
- (gtk.Window,) : geninit(), # int (enum) group
- (ode.Body,): geninit(), # object (proxy) group
- }
- def wrap_class( cls, classes=False ):
- name = cls.__name__
- if inspect.ismodule( cls ): mname = None
- else: mname = cls.__module__
- funcs = {}
- fnames = ''
- d = { '_classname_':name, '_modulename_':mname }
- for group in INIT_GROUPS:
- for sclass in group:
- if cls is sclass:
- d['__init__'] = INIT_GROUPS[ group ] # classes that have the same init vars
- break
- #d['__init__'] = geninit() # takes too long to generate init for all classes, create a INIT_GROUP see above.
- for n in dir(cls):
- if n.startswith('_'): continue
- a = getattr( cls, n )
- if inspect.isroutine( a ):
- #if classes: print '\tFUNCTION: ', n
- genfunc, index = genRPC(n,a)
- d[n] = genfunc
- elif inspect.isclass( a ) and classes:
- #if classes: print '\tCLASS: ', n
- d[ n ] = wrap_class( a )
- elif type(a) in WrappableAttributesTypes:
- d[ n ] = a
- elif type(a) in (list,tuple):
- safe = True
- for item in a:
- if type(item) not in WrappableAttributesTypes:
- safe = False; break
- if safe:
- d[ n ] = a
- else:
- ## check for gtk enum
- items = dir(a)
- enum = True
- for test in 'conjugate denominator imag numerator real'.split():
- if test not in items: enum = False; break
- if enum:
- d[ n ] = a.real
- mclass = types.ClassType(name, bases=(Proxy,), dict=d)
- if mname: key = '%s.%s' %(mname,name)
- else: key = name
- CLASSES[ key ] = mclass
- return mclass
- if '--pypy' in sys.argv:
- from pypy.rlib import streamio
- from pypy.rlib import rpoll
- stdin = streamio.fdopen_as_stream(0, 'r', 0) # fd, mode, buffering
- stdout = streamio.fdopen_as_stream(1, 'w', 0)
- stderr = streamio.fdopen_as_stream(2, 'w', 0)
- from pypy.translator.interactive import Translation
- gtk_wrapped = wrap_class( gtk, classes=True )
- pygame_wrapped = wrap_class( pygame, classes=True )
- pygame_display_wrapped = wrap_class( pygame.display, classes=True )
- pygame_draw_wrapped = wrap_class( pygame.draw, classes=True )
- ode_wrapped = wrap_class( ode, classes=True )
- def entry_point():
- ## init module level proxies ##
- gtk = gtk_wrapped()
- pygame = pygame_wrapped()
- pygame.display = pygame_display_wrapped()
- pygame.draw = pygame_draw_wrapped()
- ode = ode_wrapped()
- world = ode.World()
- #world.setERP( 0.8 )
- #world.setCFM( 0.1 )
- #world.setGravity( ( 0, -0.6*10000, 0 ) )
- body = ode.Body( world )
- body.addForce( (1,0,0) )
- w = gtk.Window()
- w.set_size_request( 320,200 )
- w.set_title('PyPyGTK v0.1b')
- root = gtk.VBox(); w.add( root )
- root.set_border_width(50)
- lab = gtk.Label()
- lab.set_text( 'hello world' )
- root.pack_start( lab )
- b = gtk.Button( 'click me' )
- b.connect('clicked', "lambda b,l: l.set_text('clicked test')", lab ) # note lambdas are passed as strings
- root.pack_start( b )
- root2 = b.get_parent()
- root2.pack_start( gtk.Label('return instance from CPython works') )
- b = gtk.Button('exit demo')
- b.connect('clicked', 'lambda b: gtk.main_quit()' )
- root2.pack_start( b )
- frame = gtk.Frame('PyODE body position')
- root2.pack_start( frame )
- bodylab = gtk.Label('body pos')
- frame.add( bodylab )
- w.show_all()
- w2 = gtk.Window( gtk.WINDOW_POPUP )
- w2.move( 20,240 )
- w2.set_size_request( 320,100 )
- r2 = gtk.VBox(); w2.add( r2 )
- lab2 = gtk.Label('enum values work (this is a popup window)' )
- r2.pack_start( lab2 )
- b2 = gtk.Button('close')
- b2.connect('clicked', "lambda b,w: w.destroy()", w2)
- r2.pack_start( b2 )
- w2.show_all()
- pygame.init()
- surf = pygame.display.set_mode( (320,240) )
- i = 0
- while True: # does not block gtk because we have a glib timeout in the other process
- x = math.sin( radians(i) ) * 100
- y = math.cos( radians(i) ) * 100
- pygame.draw.aaline( surf, (255,255,255,255), (160,120), (int(160+x),int(120+y)) )
- pygame.display.flip()
- world.quickStep( 0.001 )
- pos = body.getPosition()
- bodylab.set_text( str(pos) )
- i += 1
- if i == 360: raise SystemExit
- if '--pypy' in sys.argv:
- t = Translation( entry_point )
- t.annotate(); t.rtype()
- f = t.compile_c()
- f()
- print( 'subprocess exit' )
- else:
- process = subprocess.Popen( 'python %s --pypy' %sys.argv[0], stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0, shell=True )
- write = process.stdin
- read = process.stdout
- print( 'read pipe', read )
- print( 'read write', write )
- glib.timeout_add( 33, loop )
- gtk.main()
- print( 'toplevel exit' )
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement