Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- '''
- PyPy Pipe v0.3
- Brett Hartshorn 2010 - License BSD
- goatman.py@gmail.com
- Run "./blender -P pypypipe.py"
- 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:
- make sure this file is named pypypipe.py
- 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'
- def pypy_entry_point_test_bpy():
- ops = bpy_ops_wrapped()
- #debug( ops )
- mesh = ops.mesh()
- mesh.primitive_plane_add()
- debug('bpy ops test complete')
- def pypy_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) ) # not having dynamic attribute access is a good thing!?
- 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
- def pypy_entry_point_test_simple():
- os = os_wrapper()
- print( os.system )
- ## 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 = b'DO_NOT_CREATE'
- import os, sys, time, inspect, types, select, subprocess, math, pickle
- if '.' not in sys.path: sys.path.append( '.' ) # blender253 pickle bug
- sys.path.append(PATH2PYPY) # assumes you have pypy dist in a subfolder, you may need to rename this pypy-trunk
- #import pypy.tool.error
- bpy = glib = gtk = ode = pygame = None
- try:
- import glib, gtk
- import pygame, ode
- except:
- try:
- import bpy
- ops = bpy.ops
- except: pass
- 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().decode().strip()
- if a: print( a )
- if a and a.startswith('>'):
- print('ok')
- 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 )
- try: o = cls()
- except: 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( b'%s\n' %ID )
- write.write( bytes( str(ID), 'utf-8' ) + b'\n' )
- write.flush()
- elif a and 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 gtk and 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 pygame and 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 ode and 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.write( bytes( r, 'utf-8' ) + b'\n' )
- 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 = {}
- GeneratedClasses = {}
- GeneratedRPC = {}
- 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 = GeneratedClasses[ a ]
- return cls( DO_NOT_CREATE, str(ID) )
- else:
- debug( '^^ lost contact with parent process ^^' )
- raise SystemExit
- 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' %(str(self._modulename_), MODULE_SEP, str(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' %str(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()
- INSTANCES = {}
- CALL_GROUPS = { 'tag lambda *args': [], 'pygame.draw.line': [] }
- if gtk:
- CALL_GROUPS[ 'tag lambda *args' ].append( gtk.Object.connect )
- if pygame:
- CALL_GROUPS[ 'pygame.draw.line' ].append( pygame.draw.line )
- CALL_GROUPS[ 'pygame.draw.line' ].append( pygame.draw.aaline )
- RETURN_GROUPS = { tuple : [] }
- if ode:
- RETURN_GROUPS[ tuple ].append(ode.Body.getPosition)
- class WrapFunction(object):
- def __init__(self, fname, func ):
- self._func_name = fname
- self._call_group = None
- self._return_group = None
- for group in CALL_GROUPS:
- if func in CALL_GROUPS[group]:
- self._call_group = group
- break
- for group in RETURN_GROUPS:
- if func in RETURN_GROUPS[group]:
- self._return_group = group
- break
- def unpack( self ): ## interesting pypy needs generated functions so that *args can work properly
- fname = self._func_name
- index = len( GeneratedResponses )
- gupdate = {}
- genres_name = '_generated_RPC_closure%s'%index
- if self._return_group:
- gupdate[ genres_name ] = genres_func = gen_func_response( self._return_group )
- else:
- gupdate[ genres_name ] = genres_func = gen_func_response() # defaults to object
- GeneratedResponses[ genres_name ] = genres_func
- genres_func.func_name = genres_name
- rpc_name = '_generated_RPC%s'%index
- if self._call_group == 'tag lambda *args':
- 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)
- 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) )
- wrap = eval( 'lambda self,tag,cb,*args: %s(%s(self,"%s",tag,cb,*args))' %(genres_name, rpc_name, fname) )
- elif self._call_group == 'pygame.draw.line':
- 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) )
- wrap = eval( 'lambda self,surf,color,start,end,width=1: %s(%s(self,"%s",surf,color,start,end,width))' %(genres_name, rpc_name, 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) )
- wrap = eval( 'lambda self,*args: %s(%s(self,"%s",*args))' %(genres_name, rpc_name, fname) )
- rpc.func_name = rpc_name
- GeneratedRPC[ rpc_name ] = rpc
- gupdate[ rpc_name ] = rpc
- globals().update( gupdate )
- return wrap
- ## these are considered constants ##
- WrappableAttributesTypes = (
- bool,
- int,
- #long,
- float,
- str,
- #unicode,
- )
- INIT_GROUPS = { int:[], object:[] }
- if gtk:
- INIT_GROUPS[ int ].append( gtk.Window )
- if ode:
- INIT_GROUPS[ object ].append( ode.Body )
- INIT_GROUPS_gen = {}
- for tag in INIT_GROUPS: INIT_GROUPS_gen[ tag ] = geninit()
- del tag
- class WrapClass(object):
- def __init__( self, cls, classname, modulename, items={} ):
- if modulename: modulename = str(modulename)
- self._classname = str(classname)
- self._modulename = modulename
- self._items = items
- self._init_type = None
- for group in INIT_GROUPS:
- if cls in INIT_GROUPS[group]:
- self._init_type = group
- def unpack( self ):
- classitems = {}
- for key in self._items:
- #debug( key )
- v = self._items[key]
- if isinstance( v, (WrapClass,WrapFunction) ): v = v.unpack()
- classitems[ key ] = v
- if self._init_type in INIT_GROUPS_gen:
- classitems['__init__'] = INIT_GROUPS_gen[ self._init_type ] # classes that have the same init vars
- self._classname = str(self._classname)
- classitems['_classname_'] = self._classname
- if self._modulename:
- self._modulename = str(self._modulename)
- classitems['_modulename_'] = self._modulename
- metaclass = types.ClassType(self._classname, bases=(Proxy,), dict=classitems)
- if self._modulename: key = '%s.%s' %(self._modulename, self._classname)
- else: key = self._classname
- GeneratedClasses[ key ] = metaclass
- return metaclass
- def wrap_class( cls, classes=False ):
- print( 'wrapping', cls )
- if cls.__class__.__name__ == 'bpy_ops':
- name = 'ops'
- mname = None
- elif cls.__class__.__name__ == 'bpy_ops_submodule':
- name = cls.module # name of submod "mesh" etc..
- mname = 'ops'
- else:
- name = cls.__name__
- if inspect.ismodule( cls ): mname = None
- else: mname = cls.__module__
- if mname: mname = str(mname)
- name = str(name)
- funcs = {}
- fnames = ''
- d = { '_classname_':str(name), '_modulename_':mname }
- for n in dir(cls):
- if n.startswith('_'): continue
- a = getattr( cls, n )
- if inspect.isroutine( a ) or ( isinstance(a,object) and a.__class__.__name__ == 'bpy_ops_submodule_op' ):
- #print( '\tFUNCTION: ', n )
- d[n] = WrapFunction(n,a)
- elif inspect.isclass( a ) and classes:
- #if classes: print '\tCLASS: ', n
- if a.__module__ == 'gtk._gtk': continue
- else: d[ n ] = wrap_class( a )
- elif isinstance(a,object) and a.__class__.__name__ == 'bpy_ops_submodule':
- 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
- #GeneratedClasses[ key ] = mclass
- #globals()[ name ] = mclass
- #return mclass
- wrapped = WrapClass( cls, name, mname, items=d )
- #globals()[ 'wrapped_%s' %time.time() ] = wrapped
- return wrapped
- if '--pypy' in sys.argv:
- from pypy.rlib import streamio
- from pypy.rlib import rpoll
- from pypy.translator.interactive import Translation
- 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)
- dump = pickle.load( open('/tmp/genwrapper.pickle','rb') )
- D = globals()
- for key in dump:
- debug( key )
- metaclass = dump[key].unpack()
- debug( metaclass )
- D[ key ] = metaclass
- for k in globals().keys():
- if not k.startswith('_'):
- debug('global %s' %k )
- if '--bpy' in sys.argv: t = Translation( pypy_entry_point_test_bpy )
- elif '--gtk' in sys.argv: t = Translation( pypy_entry_point )
- else: t = Translation( pypy_entry_point_simple )
- t.annotate(); t.rtype()
- f = t.compile_c()
- f()
- print( 'subprocess exit' )
- elif __name__ == '__main__':
- import pypypipe # blender253 pickle bug
- pypypipe.dump()
- test = ''
- if bpy: test = '--bpy'
- elif gtk: test = '--gtk'
- process = subprocess.Popen( 'python pypypipe.py --pypy %s' %test, stdin=subprocess.PIPE, stdout=subprocess.PIPE, bufsize=0, shell=True )
- write = process.stdin
- read = process.stdout
- print( 'read pipe', read )
- print( 'read write', write )
- if glib:
- glib.timeout_add( 33, loop )
- gtk.main()
- elif bpy:
- bpy.ops.screen.animation_play('EXEC_DEFAULT')
- def draw_callback( area, region ): loop()
- for area in bpy.context.window.screen.areas:
- print(area.type)
- if area.type == 'VIEW_3D':
- for reg in area.regions:
- print( '\tregion: ', reg.type )
- if reg.type == 'WINDOW':
- print( 'adding callback' )
- reg.callback_add(draw_callback, (area,reg), 'POST_PIXEL') # sneaky! unlisted function!!!
- print( 'toplevel exit' )
- #else: # import pypypipe (blender253 pickle bug)
- def dump():
- ## save wrap ##
- D = {}
- if gtk:
- w = wrap_class( gtk, classes=True )
- D['gtk_wrapped'] = w
- #D['pygame_wrapped'] = wrap_class( pygame, classes=True )
- #D['pygame_display_wrapped'] = wrap_class( pygame.display, classes=True )
- #D['pygame_draw_wrapped'] = wrap_class( pygame.draw, classes=True )
- D['ode_wrapped'] = wrap_class( ode, classes=True )
- elif bpy:
- D['bpy_ops_wrapped'] = wrap_class( bpy.ops, classes=True )
- else:
- D['os_wrapped'] = wrap_class( os, classes=True )
- f = open('/tmp/genwrapper.pickle','wb')
- pickle.dump( D, f, protocol=2) #,fix_imports=False )
- f.close()
- print( 'genwrapper.pickle dumped' )
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement