Created
March 22, 2012 22:35
-
-
Save rscottm/2165163 to your computer and use it in GitHub Desktop.
Initial pass at using the dexmaker project to generate Ruboto callbacks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ###################################################### | |
| # | |
| # ruboto_generate.rb (by Scott Moyer) | |
| # | |
| # This is an initial pass at using the dexmaker | |
| # project (http://code.google.com/p/dexmaker/) to | |
| # generate Ruboto callbacks. The first half of this | |
| # script generates the callback. The second half is | |
| # the standard demo-opengl.rb script with minor | |
| # modifications to allow it to use the generated | |
| # RubotoGLSurfaceViewRenderer instead of the class | |
| # compiled into Ruboto IRB. To run this you'll need | |
| # to compile a version of Ruboto IRB with the | |
| # dexmaker.jar. | |
| # | |
| ###################################################### | |
| ###################################################### | |
| # | |
| # Expand the functionality of TypeId | |
| # | |
| java_import 'com.google.dexmaker.TypeId' | |
| class TypeId | |
| @@convert_hash = { | |
| nil => VOID, | |
| "java.lang.String" => STRING, | |
| "java.lang.Object" => OBJECT | |
| } | |
| @@corresponding_class = { | |
| INT => get("Ljava/lang/Integer;"), | |
| FLOAT => get("Ljava/lang/Float;"), | |
| DOUBLE => get("Ljava/lang/Double;"), | |
| BYTE => get("Ljava/lang/Byte;"), | |
| BOOLEAN => get("Ljava/lang/Boolean;"), | |
| CHAR => get("Ljava/lang/Char;"), | |
| SHORT => get("Ljava/lang/Short;"), | |
| LONG => get("Ljava/lang/Long;") | |
| } | |
| @@primitive_types = [] | |
| %w(int float double byte boolean char short long).each do |i| | |
| @@convert_hash[i] = const_get(i.upcase) | |
| @@primitive_types << const_get(i.upcase) | |
| end | |
| def self.convert_type(type) | |
| # TODO: Handling arrays | |
| @@convert_hash[type] || get("L#{type.gsub('.', '/')};") | |
| end | |
| def corresponding_class | |
| @@corresponding_class[self] | |
| end | |
| def primitive? | |
| @@primitive_types.include?(self) | |
| end | |
| end | |
| ###################################################### | |
| # | |
| # Generate a new class for an interface | |
| # | |
| def ruboto_generate(name, interface_name) | |
| # TODO: Already generated? | |
| java_import 'java.lang.reflect.Modifier' | |
| java_import 'com.google.dexmaker.Label' | |
| java_import "com.google.dexmaker.Comparison" | |
| dex_maker = com.google.dexmaker.DexMaker.new | |
| interface = eval("Java::#{interface_name.gsub('$', '::')}") | |
| class_type_id = TypeId.convert_type(name) | |
| dex_maker.declare(class_type_id, | |
| "#{name.split('.')[-1]}.generated", | |
| Modifier::PUBLIC, | |
| TypeId::OBJECT, | |
| TypeId.convert_type(interface_name)) | |
| # | |
| # Create callbacks field | |
| # | |
| callbackProcs_field = class_type_id.getField(TypeId.get("[Ljava/lang/Object;"), "callbackProcs") | |
| dex_maker.declare(callbackProcs_field, Modifier::PRIVATE, nil) | |
| # | |
| # Build constructor and create callbacks array | |
| # | |
| constructor_id = class_type_id.getConstructor | |
| dex_maker.declare(constructor_id, Modifier::PUBLIC).instance_eval do | |
| array = newLocal(TypeId.get("[Ljava/lang/Object;")) | |
| size = newLocal(TypeId::INT) | |
| invokeDirect(TypeId::OBJECT.getConstructor, nil, getThis(class_type_id)) | |
| loadConstant(size, interface.java_class.declared_instance_methods.length) | |
| newArray(array, size) | |
| iput(callbackProcs_field, getThis(class_type_id), array) | |
| returnVoid | |
| end | |
| # | |
| # Build setCallbackProc method | |
| # | |
| method_id = class_type_id.getMethod(TypeId::VOID, "setCallbackProc", TypeId::INT, TypeId::OBJECT) | |
| dex_maker.declare(method_id, Modifier::PUBLIC).instance_eval do | |
| index = getParameter(0, TypeId::INT) | |
| block = getParameter(1, TypeId::OBJECT) | |
| array = newLocal(TypeId.get("[Ljava/lang/Object;")) | |
| iget(callbackProcs_field, array, getThis(class_type_id)) | |
| aput(array, index, block) | |
| returnVoid | |
| end | |
| # | |
| # Loop through and build a constant and method for each method in the interface | |
| # | |
| interface.java_class.declared_instance_methods.each_with_index do |m,i| | |
| # Define the constant | |
| constant_name = "CB_" + m.name.gsub(/[A-Z]/, '_\0').upcase.gsub(/^ON_/, "") | |
| const = class_type_id.getField(TypeId::INT, constant_name) | |
| dex_maker.declare(const, Modifier::PUBLIC | Modifier::STATIC | Modifier::FINAL, i.to_java(:int)) | |
| # Build the method | |
| parameter_type_array = m.parameter_types.map{|j| TypeId.convert_type(j.name)} | |
| method_id = class_type_id.getMethod(TypeId.convert_type(m.return_type), m.name, *parameter_type_array) | |
| dex_maker.declare(method_id, Modifier::PUBLIC).instance_eval do | |
| parameter_array = [] | |
| parameter_type_array.each_with_index{|j, k| parameter_array << getParameter(k, j)} | |
| # Callback procs array | |
| index = newLocal(TypeId::INT) | |
| array = newLocal(TypeId.get("[Ljava/lang/Object;")) | |
| block = newLocal(TypeId::OBJECT) | |
| # Call arguments array | |
| p_arr = newLocal(TypeId.get("[Ljava/lang/Object;")) | |
| p_size = newLocal(TypeId::INT) | |
| p_index = newLocal(TypeId::INT) | |
| # Holds nil for comparison | |
| null = newLocal(TypeId::OBJECT) | |
| # Holds method name | |
| call_string = newLocal(TypeId::STRING) | |
| # Create a local to help convert primitives | |
| tmp_locals = {} | |
| parameter_type_array.each do |p| | |
| tmp_locals[p] = newLocal(p.corresponding_class) if p.primitive? | |
| end | |
| # Local for posible return | |
| ret = m.return_type ? newLocal(TypeId.convert_type(m.return_type)) : nil | |
| no_block = Label.new | |
| iget(callbackProcs_field, array, getThis(class_type_id)) | |
| loadConstant(index, i) | |
| aget(block, array, index) | |
| loadConstant(null, nil) | |
| compare(Comparison::EQ, no_block, block, null) | |
| # TODO: Handle multiple parameters differently | |
| script_class_type_id = TypeId.convert_type("org.ruboto.Script") | |
| # Create and populate an array for method parameters | |
| loadConstant(p_size, parameter_type_array.length) | |
| newArray(p_arr, p_size) | |
| parameter_array.each_with_index do |k, j| | |
| loadConstant(p_index, j) | |
| # Need to convert primitives to add to array | |
| if k.type.primitive? | |
| newInstance(tmp_locals[k.type], k.type.corresponding_class.getConstructor(k.type), k) | |
| aput(p_arr, p_index, tmp_locals[k.type]) | |
| else | |
| aput(p_arr, p_index, k) | |
| end | |
| end | |
| # Call Script to call the method | |
| # TODO: Test non void returns | |
| method_parameters = [TypeId::VOID, "callMethod", TypeId::OBJECT, TypeId::STRING, TypeId.get("[Ljava/lang/Object;")] | |
| method_parameters << TypeId.convert_type("java.lang.Class") if ret | |
| script_call_method_id = script_class_type_id.getMethod(*method_parameters) | |
| loadConstant(call_string, "call") | |
| method_parameters = [script_call_method_id, ret, block, call_string, p_arr] | |
| method_parameters << m.return_type if ret # Might need .java_class | |
| invokeStatic(*method_parameters) | |
| mark(no_block) | |
| ret ? returnValue(ret) : returnVoid | |
| end | |
| end | |
| # TODO: Consider using the byte array instead of writing to a file | |
| # TODO: Use default directory | |
| loader = dex_maker.generateAndLoad( | |
| com.google.dexmaker.DexMaker.java_class.class_loader, | |
| java.io.File.new("/sdcard/jruby")) | |
| runtime = org.jruby.Ruby.getGlobalRuntime | |
| rv = org.jruby.javasupport.Java.getProxyClass( | |
| runtime, | |
| org.jruby.javasupport.JavaClass.get(runtime, loader.loadClass(name))) | |
| Dir.glob("/sdcard/jruby/Generated*"){|f| File.delete f} | |
| return rv | |
| end | |
| ###################################################### | |
| # | |
| # Generate RubotoGLSurfaceViewRenderer | |
| # | |
| RubotoGLSurfaceViewRenderer = ruboto_generate( | |
| "org.rubyandroid.RubotoGLSurfaceViewRenderer", | |
| "android.opengl.GLSurfaceView$Renderer") | |
| require 'ruboto/base' | |
| RubotoGLSurfaceViewRenderer.class_eval do | |
| extend Ruboto::CallbackClass | |
| include Ruboto::Callbacks | |
| end | |
| ####################################################### | |
| # | |
| # glsurfaceview.rb (by Scott Moyer) | |
| # | |
| # This demo ports the GLSurfaceView demo from the Android | |
| # API Demos to Ruboto. | |
| # | |
| ####################################################### | |
| require 'ruboto/activity' | |
| confirm_ruboto_version(10, false) | |
| java_import "android.opengl.GLSurfaceView" | |
| java_import "javax.microedition.khronos.egl.EGL10" | |
| java_import "javax.microedition.khronos.egl.EGLConfig" | |
| java_import "javax.microedition.khronos.opengles.GL10" | |
| java_import "java.nio.ByteBuffer" | |
| java_import "java.nio.ByteOrder" | |
| java_import "java.nio.IntBuffer" | |
| # A class implementing GLSurfaceView.Renderer | |
| #ruboto_import "org.ruboto.callbacks.RubotoGLSurfaceViewRenderer" | |
| ####################################################### | |
| # | |
| # Cube Class | |
| # | |
| # Needs to get set up before the Activity tries to use it | |
| # | |
| class Cube | |
| def initialize | |
| one = 0x10000 | |
| vertices = [ | |
| -one, -one, -one, | |
| one, -one, -one, | |
| one, one, -one, | |
| -one, one, -one, | |
| -one, -one, one, | |
| one, -one, one, | |
| one, one, one, | |
| -one, one, one, | |
| ] | |
| colors = [ | |
| 0, 0, 0, one, | |
| one, 0, 0, one, | |
| one, one, 0, one, | |
| 0, one, 0, one, | |
| 0, 0, one, one, | |
| one, 0, one, one, | |
| one, one, one, one, | |
| 0, one, one, one, | |
| ] | |
| indices = [ | |
| 0, 4, 5, 0, 5, 1, | |
| 1, 5, 6, 1, 6, 2, | |
| 2, 6, 7, 2, 7, 3, | |
| 3, 7, 4, 3, 4, 0, | |
| 4, 7, 6, 4, 6, 5, | |
| 3, 0, 1, 3, 1, 2 | |
| ] | |
| vbb = ByteBuffer.allocateDirect(vertices.length*4) | |
| vbb.order(ByteOrder.nativeOrder) | |
| @vertex_buffer = vbb.asIntBuffer | |
| @vertex_buffer.put(vertices.to_java(:int)) | |
| @vertex_buffer.position(0) | |
| cbb = ByteBuffer.allocateDirect(colors.length*4) | |
| cbb.order(ByteOrder.nativeOrder) | |
| @color_buffer = cbb.asIntBuffer | |
| @color_buffer.put(colors.to_java(:int)) | |
| @color_buffer.position(0) | |
| @index_buffer = ByteBuffer.allocateDirect(indices.length) | |
| @index_buffer.put(indices.to_java(:byte)) | |
| @index_buffer.position(0) | |
| end | |
| def draw(gl) | |
| gl.glFrontFace(GL10::GL_CW) | |
| gl.glVertexPointer(3, GL10::GL_FIXED, 0, @vertex_buffer) | |
| gl.glColorPointer(4, GL10::GL_FIXED, 0, @color_buffer) | |
| gl.glDrawElements(GL10::GL_TRIANGLES, 36, GL10::GL_UNSIGNED_BYTE, @index_buffer) | |
| end | |
| end | |
| ####################################################### | |
| # | |
| # Activity | |
| # | |
| # Start a new activity or connect to $activity | |
| # | |
| $activity.start_ruboto_activity "$glsurface" do | |
| setTitle "GLSurfaceView" | |
| def on_create(bundle) | |
| @surface_view = GLSurfaceView.new(self) | |
| @surface_view.renderer = @renderer | |
| self.content_view = @surface_view | |
| end | |
| def on_resume | |
| @surface_view.on_resume | |
| end | |
| def on_pause | |
| @surface_view.on_pause | |
| end | |
| @renderer = RubotoGLSurfaceViewRenderer.new_with_callbacks do | |
| @translucent_background = false | |
| @cube = Cube.new | |
| @angle = 0.0 | |
| def on_draw_frame(gl) | |
| gl.glClear(GL10::GL_COLOR_BUFFER_BIT | GL10::GL_DEPTH_BUFFER_BIT) | |
| gl.glMatrixMode(GL10::GL_MODELVIEW) | |
| gl.glLoadIdentity | |
| gl.glTranslatef(0, 0, -3.0) | |
| gl.glRotatef(@angle, 0, 1, 0) | |
| gl.glRotatef(@angle*0.25, 1, 0, 0) | |
| gl.glEnableClientState(GL10::GL_VERTEX_ARRAY) | |
| gl.glEnableClientState(GL10::GL_COLOR_ARRAY) | |
| @cube.draw(gl) | |
| gl.glRotatef(@angle*2.0, 0, 1, 1) | |
| gl.glTranslatef(0.5, 0.5, 0.5) | |
| @cube.draw(gl) | |
| @angle += 1.2 | |
| end | |
| def on_surface_changed(gl, width, height) | |
| gl.glViewport(0, 0, width, height) | |
| ratio = width.to_f / height.to_f | |
| gl.glMatrixMode(GL10::GL_PROJECTION) | |
| gl.glLoadIdentity | |
| gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10) | |
| end | |
| def on_surface_created(gl, config) | |
| gl.glDisable(GL10::GL_DITHER) | |
| gl.glHint(GL10::GL_PERSPECTIVE_CORRECTION_HINT, GL10::GL_FASTEST) | |
| if (@translucent_background) | |
| gl.glClearColor(0,0,0,0) | |
| else | |
| gl.glClearColor(1,1,1,1) | |
| end | |
| gl.glEnable(GL10::GL_CULL_FACE) | |
| gl.glShadeModel(GL10::GL_SMOOTH) | |
| gl.glEnable(GL10::GL_DEPTH_TEST) | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment