Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
A live REPL like we have in Ruby — Gideros Forum

A live REPL like we have in Ruby

______ Member
edited July 2012 in Suggestions & requests
I'm not sure if this can be done, but this has been done with Ruby before.

What I want is a live REPL to the lua instance that is being run on the player.

I should get a normal REPL like "lua" on command line does, and I should be able to run code, make changes to the elements, and they should be executed live on the player.

Example in Ruby: Pry/IRB (Ruby REPLs) can be invoked dynamically from within code, starting an interactive session. In runs great on Ruby on Rails.

I can find some video demos of the Ruby ones if anyone is interested :)
People call me "underscore".
«13

Comments

  • gorkemgorkem Maintainer
    You found our secret weapon^H^H^H errr feature ;-)
  • ______ Member
    Please. This would be a killer feature. RubyMotion has it (A static compiler for a large subset of Ruby, which has access to Objective-C and C apis).

    :D
    People call me "underscore".
  • You can do this using interactive console in ZeroBrane Studio IDE (http://studio.zerobrane.com/). I have couple of demos showing how this can be used here: http://studio.zerobrane.com/tutorials.html (for example, tutorial 4). It would work the same way with Gideros applications.

    Dislikes: Yan

    +1 -1 (+0 / -1 )Share on Facebook
  • bowerandybowerandy Guru
    edited October 2012
    @paulclinger, I've been using the "Remote console" feature and, yes, I can set variables. However, I'd also like to be able to print expressions too. I've tried print() but the output seems to disappear into the ether. Is there a correct way to do this?

    Also, I watched your tutorial videos for the first time and I hadn't realised until now that your live coding was influenced by Bret Victor. When I first found Gideros I realized it has potential for this sort of coding and posted this message on Increasing the Awesome. I have to say that with Zero Brane Studio we are getting even closer to total awesomeness.

    So here are a couple of ideas for ZBS, just in case you can see any way to make them a reality with Gideros.

    1) I'd like to be able to do programming in the debugger. I come from a Smalltalk background and over there this goes on all the time. There are two reasons to do this.

    The first is to fix code as you are debugging. One problem with game programming is that, quite often, one has to do a lot of work to set up a test scenario. If, while you are debugging, you then come across a simple problem you want to be able to fix it without having to restart the whole program and have to go through the setup scenario again. I think in MSVC they call this Edit and Continue. I've already tested this out somewhat in ZBS and it seems you can change functions at runtime by assigning them in the Remote Console. The real trick, however, would be to recompile the method that you are in right now (the one in the top stack frame) and then restart it.

    Once you can do this, you can also do some other cool stuff. In Smalltalk you can flesh out an entire program like this by programming in the debugger. Basically, the idea is that you start writing your code, calling functions that don't yet exist. When you then run the code and it hits one of these errors, instead of quitting, it brings up the debugger at that point on the stack. At that point you can write the missing code (possibly even calling more methods that don't yet exist). You then hit Continue and wait till you come across another bit that needs implementing. When you no longer hit non-existent methods calls, your app is finished!

    This is an incredibly quick way to develop and works really well with Test Driven Design. And one of the great things about it is that, while you are editing these non-existent methods in the debugger you have the entire calling context available to you; you can see exactly what parameters a function should expect because they are there on the stack!

    2) The next suggestion is: can we somehow get the Live Coding working with Gideros? I'm not exactly sure how it would work. I guess you are just looping around the live code repeatedly and allowing the programmer to interact with it via sliders etc. Since we already have an inherent display loop in a Gideros app there would be no need for an additional one.

    So, I'm thinking that you might just allow the user to select one or more functions to be "live functions" during a particular debugging run. Using the programming in the debugger idea above, these functions could be continuously recompiled as they are edited in your Live Code pane or as sliders are moved. If an error occurs in compilation then the original (non-live) copy could be temporarily substituted back to things keep moving.

    So how would this help? It would allow the sort of program exploration that Bret Victor mentions. Tweaking aspects of a game dynamically can reveal new things about the way the interactions work. Also, if you have a physics game that relies on positioning of objects these positions can be experimented with directly in the live game without having to restart each time. Even if you just have your screen layouts set up in various methods this Live Coding would be able to replace much of the need for an external layout editor. It would also make for an extremely groovy video demonstration.

    Just some ideas to keep you busy. I look forward to the next version of ZBS - tomorrow perhaps? ;-)

    best regards
  • @paulclinger, I've been using the "Remote console" feature and, yes, I can set variables. However, I'd also like to be able to print expressions too. I've tried print() but the output seems to disappear into the ether. Is there a correct way to do this?
    Just found the Evaluate in Console right click over variable command.

    best regards

  • paulclingerpaulclinger Member
    edited October 2012
    @bowerandy, yes, you can print the variables in the console; just do "variable" or "expression" and it will print the value (or multiple values). It will actually be pretty-printed, so {1,2,3} will be printed as a table (as you'd prefer in most cases).

    If your data structure is large, you can do "=variable" or "=expression" to pretty-print it in a block format.

    When you do "print(...)" in the remote console, it's printed to the "output" window, but it may be buffered, so you don't see it immediately. You may want to add "io.stdout:setvbuf('no')" to your script (or run this expression in the console) to eliminate the delay.
  • @bowerandy: On "Evaluate in Console": you can also select an expression (an entire function if you want) and evaluate it in the console. You can then update the function in the console (it keeps the history and allows multi-line updates) and re-evaluate it as many times as you need. This is probably the closest you can get to Smalltalk REPL paradigm. However, with real-time live coding you can still do a bit better.

    Your post on live coding and total awesomeness warrants some experimentation ;). I've tried live coding in the Player when I first came across Gideros, but it didn't work; I think I now have a better idea what to try to make it work. Will update shortly.
  • paulclingerpaulclinger Member
    edited October 2012
    @bowerandy, I agree, having live coding would be awesome. It seems like it could be possible to make it work, but I'm running into some issues with Gideros that may be attributed to my lack of experience with it.

    Let's take Sleeping Bodies example. If I change the onTimer function, I need to re-register the event, and this is where I run into problems. You can try this yourself, maybe you can suggest me the right way to do this.

    Run the example under the debugger and pause somewhere (for example, inside the onEnterFrame function). Then run the following commands in the remote console (from the attached file):

    > resetListeners(initialized)

    Essentially, I remove and add back the same listener. When I continue the script, it crashes somewhere inside the Player and I get these two messages:

    AL lib: ALc.c:2325: exit(): closing 1 Device
    AL lib: ALc.c:2247: alcCloseDevice(): destroying 1 Context(s)

    All I do there is to re-register the same listener. It seems to be crashing in the player before entering onEnterFrame.

    Interestingly enough, if you pause inside onEnterFrame and then run this command -- world:createBody{type = b2.DYNAMIC_BODY, position = {x = math.random(0, 320), y = 56}} -- it crashes the player right away, which may be a clue. It seems like there are some modifications to the world caused by createBody/destroyBody or addEventListener/removeEventListener that crash the player when executed in the debugger.

    If you can help me with getting this to work, I think we can get the live coding working. Thanks.

    This is the code I experimented with (I replaced some local functions with globals to allow them to be recreated dynamically without resetting the state). Pasting "main.lua" here as I couldn't uploaded it ("uploaded file type is not allowed").
    -- this function creates a box sprite with 2 happy and sad children
    function createBoxSprite(sx, sy)
    	local happy = Bitmap.new(Texture.new("happy-box.png", true))
    	happy:setAnchorPoint(0.5, 0.5)
     
    	local sad = Bitmap.new(Texture.new("sad-box.png", true))
    	sad:setAnchorPoint(0.5, 0.5)
     
    	local sprite = Sprite.new()
    	sprite:addChild(happy)
    	sprite:addChild(sad)
     
    	sprite:setScale(sx, sy)
     
    	return sprite
    end
     
    -- every 3 seconds, we create a random box
    function onTimer()
    	local sx = math.random(70, 100) / 100
    	local sy = math.random(70, 100) / 100
     
    	local body = world:createBody{type = b2.DYNAMIC_BODY, position = {x = math.random(0, 320), y = 56}}
     
    	local shape = b2.PolygonShape.new()
    	-- box images are 70x70 pixels. we create bodies 1 pixel smaller than that.
    	shape:setAsBox(34.5 * sx, 34.5 * sy)
    	body:createFixture{shape = shape, density = 1, restitution = 0.1, friction = 0.3}
     
    	local sprite = createBoxSprite(sx, sy)
    	stage:addChild(sprite)
     
    	actors[body] = sprite
    end
     
    -- step the world and then update the position and rotation of sprites
    function onEnterFrame()
    	world:step(1/60, 8, 3)
     
      for body,sprite in pairs(actors) do
    		sprite:setPosition(body:getPosition())
    		sprite:setRotation(body:getAngle() * 180 / math.pi)
     
    		if body:isAwake() then
    			sprite:getChildAt(1):setVisible(false)
    			sprite:getChildAt(2):setVisible(true)
    		else
    			sprite:getChildAt(1):setVisible(true)
    			sprite:getChildAt(2):setVisible(false)
    		end
    	end
     
    	for body,sprite in pairs(actors) do
    		if sprite:getY() > 600 then
    			world:destroyBody(body)
    			stage:removeChild(sprite)
    			actors[body] = nil
    		end	
    	end
    end
     
    function resetListeners(initialized)
      stage:removeEventListener(Event.ENTER_FRAME, onEnterFrame)
      stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
     
      --timer:removeEventListener(Event.TIMER, initialized.onTimer)
      --timer:addEventListener(Event.TIMER, onTimer)
      initialized = {onEnterFrame = onEnterFrame, onTimer = onTimer}
    end
     
    function main()
      if initialized then -- re-register listeners; only run initialization once
        resetListeners(initialized)
        return
      end
      initialized = {onEnterFrame = onEnterFrame, onTimer = onTimer}
     
      require('mobdebug').start()
      require "box2d"
      b2.setScale(20)
     
      -- this table holds the dynamic bodies and their sprites
      actors = {}
     
      -- create world
      world = b2.World.new(0, 9.8)
     
      -- create a ground body and attach an edge shape
      ground = world:createBody({})
      shape = b2.EdgeShape.new(-200, 480, 520, 480)
      ground:createFixture({shape = shape, density = 0})
     
      -- set the timer for every 3 seconds
      timer = Timer.new(3000, 0)
      timer:addEventListener(Event.TIMER, onTimer)
      timer:start()
      onTimer() -- create a box immediately
     
      stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    end
     
    main()
  • atilimatilim Maintainer
    @paulclinger to attach a file, zip the files and attach the zip file.
  • @atilim: will keep in mind; thanks for the tip.
  • @atilim, while you are reading this ;), any idea what may be going on? Essentially, I would like to be able to create a new function, un-register the current one from an event and register a new one for the same event (this is all from the debugger). For some reason it crashes on subsequent "world" or "stage" operations (or at least that's what it seems).

    Your object structure is quite complex (with keys/values being tables and userdata), so I'm not ruling out possible issues with the debugger, but it has already been used/tested with various engines without (known) issues. Any help would be appreciated. Thank you.
  • atilimatilim Maintainer
    edited October 2012
    @paulclinger On my side, after pausing, the line
    world:createBody{type = b2.DYNAMIC_BODY, position = {x = math.random(0, 320), y = 56}}
    creates the body and doesn't crash the player.

    And these lines on remote console
    stage:removeEventListener(Event.ENTER_FRAME, onEnterFrame)
    onEnterFrame = function() end
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    worked well.

    Also I've changed the last part of Sleeping Bodies example as:
    function onEnterFrame()
    -- here comes the original onEnterFrame
    end
     
    local function onEnterFrameWrapper()
      onEnterFrame()
    end
     
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrameWrapper)
    and then I was able to change onEnterFrame without adding/removing listeners.

    I really wonder why the crashing occurs on your side. (Currently I'm on Windows, should I try on Mac also?)
  • paulclingerpaulclinger Member
    edited October 2012
    > I really wonder why the crashing occurs on your side. (Currently I'm on Windows, should I try on Mac also?)

    @atilim, not sure either; I'm also on Windows at the moment. I'll give your suggestions a try shortly (good idea with the wrapper). Thanks for looking into this.
    stage:removeEventListener(Event.ENTER_FRAME, onEnterFrame)
    onEnterFrame = function() end
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    Have you tried continuing the execution (Continue/F5)? It was failing before the next onEnterFrame().
  • atilimatilim Maintainer
    edited October 2012
    You need to wrap your code fragment between
    <pre lang="lua">
    </pre>

    Here are my exact steps:
    1. Opened the original Sleeping Bodies example.
    2. Changed onEnterFrame function from local to global
    3. Pressed pause: it breaks at the first line of onEnterFrame
    4. Entered these lines on remote console
    stage:removeEventListener(Event.ENTER_FRAME, onEnterFrame)
    onEnterFrame = function() end
    stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
    5. Pressed continue.
  • atilimatilim Maintainer
    btw, I've downloaded the latest version from GitHub as .zip file.
  • atilimatilim Maintainer
    edited October 2012
    Also I must say it works like a magic :)
  • bowerandybowerandy Guru
    edited October 2012
    @paulclinger, @atilim, I see that I've been away for ten minutes and things have moved on a bit. Anyway, I tried the example you posted, and for me too, it doesn't crash. I am on a Mac and it works both with the desktop player and on a real device.

    I suspect that it may be a Windows/Android issue?

    best regards
  • @bowerandy, the screencast looks good (I see your got the buttons working; I also fixed them in the manifest); was there audio too? ;)
  • Yay! With @bowerandy's push and @atilim's help I got the live coding working! Give me few minutes to check it in.
  • @bowerandy, @atilim, get the updated version from the repo and give it a try with the following file. Press Run as Scratchpad/Ctrl-F6 (it was disabled previously) and then try to change numbers (manually or with sliders) in onTimer. For example, change 30 to something larger; or change 0.1 (restitution) to 1.5 to get bouncing boxes.
    -- this function creates a box sprite with 2 happy and sad children
    function createBoxSprite(sx, sy)
    	local happy = Bitmap.new(Texture.new("happy-box.png", true))
    	happy:setAnchorPoint(0.5, 0.5)
     
    	local sad = Bitmap.new(Texture.new("sad-box.png", true))
    	sad:setAnchorPoint(0.5, 0.5)
     
    	local sprite = Sprite.new()
    	sprite:addChild(happy)
    	sprite:addChild(sad)
     
    	sprite:setScale(sx, sy)
     
    	return sprite
    end
     
    -- every 3 seconds, we create a random box
    function onTimer()
    	local sx = math.random(70, 100) / 100
    	local sy = math.random(70, 100) / 100
     
    	local body = world:createBody{type = b2.DYNAMIC_BODY, position = {x = math.random(0, 320), y = 30}}
     
    	local shape = b2.PolygonShape.new()
    	-- box images are 70x70 pixels. we create bodies 1 pixel smaller than that.
    	shape:setAsBox(34.5 * sx, 34.5 * sy)
    	body:createFixture{shape = shape,
        density = 1, restitution = 0.1, friction = 0.3}
     
    	local sprite = createBoxSprite(sx, sy)
    	stage:addChild(sprite)
     
    	actors[body] = sprite
    end
     
    -- step the world and then update the position and rotation of sprites
    function onEnterFrame()
    	world:step(1/60, 8, 3)
     
      for body,sprite in pairs(actors) do
    		sprite:setPosition(body:getPosition())
    		sprite:setRotation(body:getAngle() * 180 / math.pi)
     
    		if body:isAwake() then
    			sprite:getChildAt(1):setVisible(false)
    			sprite:getChildAt(2):setVisible(true)
    		else
    			sprite:getChildAt(1):setVisible(true)
    			sprite:getChildAt(2):setVisible(false)
    		end
    	end
     
    	for body,sprite in pairs(actors) do
    		if sprite:getY() > 600 then
    			world:destroyBody(body)
    			stage:removeChild(sprite)
    			actors[body] = nil
    		end	
    	end
    end
     
    function main()
      if initialized then return end -- only run initialization once
      initialized = true
     
      require('mobdebug').start()
      require "box2d"
      b2.setScale(20)
     
      -- this table holds the dynamic bodies and their sprites
      actors = {}
     
      -- create world
      world = b2.World.new(0, 9.8)
     
      -- create a ground body and attach an edge shape
      ground = world:createBody({})
      shape = b2.EdgeShape.new(-200, 480, 520, 480)
      ground:createFixture({shape = shape, density = 0})
     
      -- set the timer for every 3 seconds
      timer = Timer.new(3000, 0)
      timer:addEventListener(Event.TIMER, function(...) return onTimer(...) end)
      timer:start()
      onTimer() -- create a box immediately
     
      stage:addEventListener(Event.ENTER_FRAME, function(...) return onEnterFrame(...) end)
    end
     
    main()

    Likes: atilim

    +1 -1 (+1 / -0 )Share on Facebook
  • atilimatilim Maintainer
    @bowerandy same here.

    btw, when I start debugging, it always breaks at the require('mobdebug').start() line and then I need to press continue. do you think it is normal?
  • > when I start debugging, it always breaks at the require('mobdebug').start() line and then I need to press continue. do you think it is normal?

    @atilim, it's a feature, but it may be possible to disable that; would you prefer to run immediately? it would require you to have a breakpoint or to use "break" to stop in the app.
  • @bowerandy, I'd be curious to see if you can get live coding working on a real device.
  • @atilim, yes this is normal and I think I prefer that rather than it jumping straight in.
  • @paulclinger. Cripes I'm several posts behind.

    The video lost the audio, so I actually deleted it from the post but you must have got in first. Let me try the stuff you've checked in now.
  • atilimatilim Maintainer
    @bowerandy @paulclinger I see. I think both ways are ok.

    And I can change onTimer and onEnterFrame on the fly. It's amazing! :O
  • @paulclinger. I am speechless. It is @%^**%$£ amazing. I'll record a video of it working on a real iPad. I'll make sure the audio works but, as I say, I'm speechless so it won't reallt be necessary.

    best regards

    Likes: atilim

    +1 -1 (+1 / -0 )Share on Facebook
  • atilimatilim Maintainer
    edited October 2012
    @paulclinger hmm.. I couldn't run on real device. when I press Ctrl-F6, zerobrane starts the desktop player and the player on iPad gives 'module 'mobdebug' not found' error. How can you embed mobdebug automatically?
    I'll make sure the audio works but, as I say, I'm speechless so it won't reallt be necessary.
    :))

  • Super. I'm excited too.

    @bowerandy, @atilim, have you noticed I included auto-complete for Gideros API too?

    I think we are good for now; I'll go work on a couple of blog posts ;).
  • atilimatilim Maintainer
    oh yes. also autocomplete works like a charm.
Sign In or Register to comment.