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

A live REPL like we have in Ruby

2

Comments

  • paulclingerpaulclinger Member
    edited October 2012
    @atilim, @bowerandy, right, forgot to mention a small but significant detail.

    To try live coding (or debugging) on the device you need to include mobdebug.lua in the project (I think @bowerandy also included socket.lua, but not sure if it's needed as you seem to support luasocket).

    Then you start the application on the device; it will start a debugging session in the IDE (you should see a green arrow). Only **then** you press Ctrl-F6 to switch to live coding.

    @atilim, see @bowerandy's instructions on how to run on the device here: http://giderosmobile.com/forum/discussion/comment/12680#Comment_12680

    I don't guarantee this will work (as I have never tried this with Gideros), but this is how it works with some other frameworks I tested with.
  • atilimatilim Maintainer
    Also I may have a recommendation: if the output of 'gdrbridge isconntected' is already 1, then don't start the desktop player and directly play the .gproj file.
  • bowerandybowerandy Guru
    edited October 2012
    @atlim. To run on a real device you don't use Start Debugging. What you do it to find out your desktop machine's IP address and stick in as a parameter to the mob debug.start call. e.g.

    require('mobdebug').start("192.168.1.101")

    You have to launch from Gideros Studio. Select main.lua in Gideros and in ZBS and start the player on the device. The press run in Gideros and switch back to ZBS. After a short time you should seen the green breakpoint cursor appear in the ZBS editor and you are good to debug as normal.

    @paulclinger, I couldn't get Run as Scratchpad to be enabled when I'm running on a device, only the desktop player. Is there any way around this?

    best regards
  • atilimatilim Maintainer
    edited October 2012
  • > I couldn't get Run as Scratchpad to be enabled when I'm running on a device, only the desktop player. Is there any way around this?

    @bowerandy, I'll need to check the conditions that enable Run as Scratchpad for external connections. I'll post an update when I figure it out.
  • @bowerandy, @atilim, get the latest from the repo and give it a try. When you start the IDE, don't forget to select Project | Start Debugger Server, otherwise the client won't be able to connect to start debugging.

    After the debugging is initiated (follow @bowerandy's instructions above), "Run as Scratchpad" option should be enabled. When you select it, you switch to scratchpad mode.

    Let me know how it goes. Hope it's all for today ;).
  • Also I may have a recommendation: if the output of 'gdrbridge isconntected' is already 1, then don't start the desktop player and directly play the .gproj file.
    @atilim, makes sense. This may allow to start even remote Scratchpad using Ctrl-F6 as long as the player is running and the bridge is connected. There are some technical issues to resolve, but I'll give it a try.

  • bowerandybowerandy Guru
    edited October 2012
    @paulclinger. Ok, I'm having a bit of difficulty putting together a little demo for this live coding. I can get your modification of Sleeping Bodies going but, as you've obviously spotted, there is some trickiness needed when replacing methods that are given out to addEventListener() and other such places.

    So I thought I would build a much simpler example to explore what exactly can and can't be done. However, when I do this I can't get any live changes to register. I find myself wondering what it is exactly that changing something in the scratchpad pane does. So here's my code in "main.lua":
    if not(BhLiveCode) then
            -- Don't refine a class if it is already defined
    	BhLiveCode=Core.class(Sprite)
    	require("mobdebug").start("192.168.1.101")
    end
     
    function BhLiveCode:init(scale)
    	local image=Bitmap.new(Texture.new("Widget.png"))
    	image:setScale(1.5*scale)
    	self:addChild(image)
    	stage:addChild(self)
    	BhLiveCode.sharedInstance=self
    end
     
    if BhLiveCode.sharedInstance then
            -- If we already have a shared instance then destroy it to start over
    	BhLiveCode.sharedInstance:removeFromParent()
    	BhLiveCode.sharedInstance=nil
    end
     
    BhLiveCode.new(1.1)
    So I have been assuming that if I change anything in the live code pane then the whole file is recompiled and re-run. Is that right? Re-running the above should:

    a) Not redefine the class. I'm not sure what this would do but it seems like a bad idea.

    b) Redefine function init() in the BhLiveCode table

    c) Removed any old instance from the stage which should be GC'd

    d) Run the new() again to create a new instance with the new code.

    So I would expect to be able to drag the sliders on the 1.1 and 1.5 numbers above and see the a new image be added with a new size? However, nothing appears to happen.

    Any ideas?
  • @bowerandy, I think you are on the right track, but it doesn't quite work like this. To reload the updated code from the IDE, the debugger needs to do two things:

    (1) it needs to abort the running process.
    (2) it needs to re-evaluate the new code fragment in the context of your application.

    It can only execute step 2 when it can abort the running process. In your case there are no Lua code fragments that get executed, so there is nothing to interrupt to allow the new fragment to be loaded. In the earlier example, we had ENTER_FRAME and TIMER events registered that allowed the script to be interrupted.

    Adding one of those should allow you to stop and reload the script. It won't guarantee that it will work though as you are trying to do something similar to what I was trying and couldn't quite do yesterday (like removing and adding listeners, which seem to work for you). For some reason Gideros didn't like my world/stage modifications and the player would crash.
  • atilimatilim Maintainer
    edited October 2012
    @paulclinger, I was thinking exactly as @bowerandy and after reading your comment, I've added an empty ENTER_FRAME function to BhLiveCode and then it worked as expected.

    btw, is player still crashing on your side?
  • bowerandybowerandy Guru
    edited October 2012
    @paulclinger, @atlim, please try this:
    if not(BhLiveCode) then
    	BhLiveCode=Core.class(Sprite)
    	require("mobdebug").start("192.168.1.101")
    end
     
    function BhLiveCode:onEnterFrame()
    	-- Comment in this line to see if active movement can be altered (I find it can)
    	-- self.image:setY(self.image:getY()+9)
    end
     
    function rgb(r, g, b)
    	return ((r*256)+g)*256+b
    end
     
    function BhLiveCode:init(x, y)
    	application:setBackgroundColor(rgb(150, 146, 97))
    	local image=Bitmap.new(Texture.new("MyPicture.png"))
    	image:setScale(0.53)
    	image:setAnchorPoint(0.5, 0.5)
    	image:setPosition(x, y)
    	image:setRotation(4)
            self.image=image
     
    	self:addChild(image)
    	stage:addChild(self)
     
    	self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
    	self:addEventListener(Event.MOUSE_DOWN, self.onMouseDown, self)
    end
     
    function Application:clearStage()
    	while stage:getNumChildren()>0 do
    		stage:removeChildAt(1)
    	end
    end
     
    application:clearStage()
    BhLiveCode.new(162, 416)
    BhLiveCode.new(446, 476)
    This works fine for me on an iOS device and in desktop player on Mac. If, it crashes for you in Windows/Android, any chance @atilim could take a look at it? It would be a shame to only be able to use this on iOS.

    Paul, are there any other slider types apart from the numerical one? You can see I've added an RGB colour feature because I didn't know whether there was a colour palette available. I wonder if there's any way we could slide through a table based on it's keys. Not sure how that would work.

    Anyway, I'm getting closer to being able put up a demo.

    best regards
  • atilimatilim Maintainer
    edited October 2012
    @bowerandy, your code worked fine on desktop player on my Windows. Now I'll try on Android.

    btw, as a reference, I apply these steps before live editing:
    1. Add
    if not initialized then
    	initialized = true
    else
    	return
    end
     
    require("mobdebug").start()
    to the beginning of main.lua.


    2. Define the classes as:
    MySprite = MySprite or Core.class(Sprite)

    3. Register the events as:
    self:addEventListener(Event.ENTER_FRAME, function(self, event) self:onEnterFrame(event) end, self)
  • @atilim, #2 is a great idea; why didn't I think of that?

    With #3, why do you need to do that? I saw that @paulcllinger did it in yesterday's example but I didn't understand why. I was trying to avoid as many magic incantations
    as possible to avoid confusing too many people.

    If you do #1, surely that means that you never get to reach the rest of file so any live editing won't have an effect. Or am I missing something?

    best regards
  • atilimatilim Maintainer
    edited October 2012
    If you register events as
    self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self)
    after changing the MySprite:onEnterFrame, it continues to execute the original one. Because although MySprite:onEnterFrame function is changed, MySprite object holds the original function reference internally. Currently, I don't know if there is another easy way.

    And about #1, you're right. I just want to execute main.lua once even when it's changed. Maybe #1 is not necessary at all.
  • @atilim, in my example that rebuilds everything from scratch, I don't think this is necessary. Try just commenting in the line in the onEnterFrame() handler and you should be able to fiddle with the speed slider (9) in that method.

    best regards
  • @bowerandy, there are no other sliders at the moment, but it *may* be possible to add more; I was thinking about selecting a code fragment (for example, "1,2,3") and then using right click menu to select what you want to change. It may show you a color wheel and then adjust those numbers according to the position on the wheel, which will update the script. The rest is already done.

    I'm not sure I'm doing everything correctly with @bowerandy's example. First of all, the example references self.onMouseDown, which I don't see in the code, so I removed that listener. Second, I get an error on self.image:setY saying that self.image is nil, so I put it behind "if", but it doesn't do anything on the screen.

    If I fix these two issues (and also fix IP address in "start") and run the example as is, it crashes for me. Even after I move addEventListener call and wrap it as @atilim suggested and as I did yesterday, it still crashes.

    It's interesting that it works for @atilim. Maybe I need to upgrade to a newer version of Gideros (mine is from 9/5). Let me try with a newer version.
  • atilimatilim Maintainer
    oh yes, you're right. And your example is like a realtime scene editor. It's really fun to play with it (and still I can't believe how this is possible :) )
  • atilimatilim Maintainer
    @paulclinger I also removed self.onMouseDown and put self.image = image under line local image = ....

    I hope everything will work after upgrading. Also I can try to run desktop player in debug mode. So that maybe I can catch a clue.
  • @atilim, @bowerandy, yes, it works after upgrade to 2012.8.4. I can move both of the boxes and see the immediate effect.

    This is the code that I'm running (I've incorporated some of @atilim's suggestions):
    BhLiveCode = BhLiveCode or Core.class(Sprite)
     
    function BhLiveCode:onEnterFrame()
    	-- Comment in this line to see if active movement can be altered (I find it can)
    	-- self.image:setY(self.image:getY()+9)
    end
     
    function rgb(r, g, b)
    	return ((r*256)+g)*256+b
    end
     
    function BhLiveCode:init(x, y)
    	application:setBackgroundColor(rgb(150, 146, 97))
    	local image=Bitmap.new(Texture.new("MyPicture.png"))
    	image:setScale(0.53)
    	image:setAnchorPoint(0.5, 0.5)
    	image:setPosition(x, y)
    	image:setRotation(4)
            self.image=image
     
    	self:addChild(image)
    	stage:addChild(self)
    end
     
    function Application:clearStage()
    	while stage:getNumChildren()>0 do
    		stage:removeChildAt(1)
    	end
    end
     
    application:clearStage()
    BhLiveCode.new(149, 387)
    BhLiveCode.new(232, 276)
     
    if initialized then return end
    initialized = true
    require("mobdebug").start()
    stage:addEventListener(Event.ENTER_FRAME, function(...) BhLiveCode.onEnterFrame(...) end)
  • atilimatilim Maintainer
    edited October 2012
  • Guys, sorry about my code not working correctly.

    But @paulclinger, I think that is as a result of editing stuff in the code window and not knowing it's changed. Do you think it would be a good idea to offer the ability to revert to the original version after messing with Live Code?

    best regards
  • @bowerandy, you can still use Undo (Cmd-Z/Ctrl-Z) to undo your actions, although it may be too many to undo if you spent quite a bit of time making changes. The file is saved before starting debugging, so you can simply close and re-open it.

    I can possibly add a hotkey/button for "return to the last saved state" (which is not the same as 'reload' because you can still keep you Redo history in case you want a replay), but I need to experiment with it a bit. Also, I'm hesitant to add too many features quickly as I'm looking for support from multiple users for features before adding them.
  • Another thought is that if you can find a good lua-based diff'er, I may be able to include it to show differences between the current content in the editor tab and a saved version.
  • bowerandybowerandy Guru
    edited October 2012
    @paulclinger,

    Just back from the pub and looking at this demo program. I'm getting a program exit if I try and edit the onEnterFrame method and there is a syntax error. Presumably, if a method doesn't compile it should abort and not install it and leave the previous method present? And also not run the following code?

    best regards
  • @bowerandy, try it with the script I posted. You probably don't need/want to re-register event listeners; you just want to use a wrapper that will make the same listener to work with the new function.

    If there are any compilation errors, the code won't be executed at all (and you will see the error in the Output window). If there are some run-time errors, then whatever code got already executed, will have the effect, but the rest of the code will not (and your app may end up in some weird state. For example, you can remove listener successfully, but not register a new one because you have a run-time error on that line. There is not much I can do with that other than hope that whatever next change you do will fix that state.
  • bowerandybowerandy Guru
    edited October 2012
    @paulclinger, @atilim. The issue was caused by a runtime (not compile time) error occurring in the event handler. Doing this... fixes the problem...
    self:addEventListener(Event.ENTER_FRAME, 
            function(...) 
                pcall(self.onEnterFrame, ...) 
            end , self)
    @atilim, if I do this then will it be possible to remove that handler? i.e. what function would I pass to removeEventListener()?

    Guys, I'm doing a "promo" video for this live coding stuff. Do you have any high res images of the Gideros and ZeroBrane logos that you can send me?

    best regards

    Likes: gorkem

    +1 -1 (+1 / -0 )Share on Facebook
  • atilimatilim Maintainer
    edited October 2012
    I was about to suggest using pcall :)

    > @atilim, if I do this then will it be possible to remove that handler?
    In this case, you need to hold a reference to the registered function like:
    self.penterFrame = function(...) 
                pcall(self.onEnterFrame, ...) 
            end 
    self:addEventListener(Event.ENTER_FRAME, self.penterFrame, self)
    -- and later
    self:removeEventListener(Event.ENTER_FRAME, self.penterFrame, self)
    But I'm planning to implement removeAllEventListeners function to remove all registered event listeners or remove registered event listeners with a specific type. e.g.
    self:removeAllEventListeners() -- remove all event listeners
    self:removeAllEventListeners(Event.ENTER_FRAME) -- remove all ENTER_FRAME event listeners
    > Guys, I'm doing a "promo" video for this live coding stuff. Do you have
    > any high res images of the Gideros and ZeroBrane logos that you can send me?
    Yay! You can use the logos here: http://www.giderosmobile.com/press


  • bowerandybowerandy Guru
    edited October 2012
    @atilim, logos look fine.

    I think you could also make removeEventListener() do the following without any compatibility issues:
    object:removeEventListener(Event.ENTER_FRAME, nil, target)
    This would remove the ENTER_FRAME listener(s) for a particular target. I always wondered why we didn't have that capability. I think that needing to know the registered function rather than just the event is a bit odd?

    BTW, rather than me overriding EventDispatcher:registerEventHandler to wrap the passed function in a pcall (when using live coding), is there any way for me to override dispatchEvent() to do the pcall instead (such that the whole system will use it while live coding).

    best regards
  • atilimatilim Maintainer
    edited October 2012
    > object:removeEventListener(Event.ENTER_FRAME, nil, target)
    good idea!

    It's possible to add more then one listener for a particular event type like:
    object:addEventListener(Event.ENTER_FRAME, function1)
    object:addEventListener(Event.ENTER_FRAME, function2)
    object:addEventListener(Event.ENTER_FRAME, function3)
    And my initial thought was if it's possible to register multiple listeners for a particular event type, then they also should be removed one by one. I just want addEventListener and removeEventListener be totally symmetric to each other.

    Overriding dispatchEvent doesn't help because it's not used internally to dispatch events like ENTER_FRAME, MOUSE_DOWN, etc.

    But in the future I can add a function to Application class to suppress runtime errors like:
    application:suppressErrors(true)
    or forward errors to a custom error handler function like:
    application:registerErrorFunction(func)
  • > object:removeEventListener(Event.ENTER_FRAME, nil, target)
    good idea!

    It's possible to add more then one listener for a particular event type like:
    object:addEventListener(Event.ENTER_FRAME, function1)
    object:addEventListener(Event.ENTER_FRAME, function2)
    object:addEventListener(Event.ENTER_FRAME, function3)
    Yes, that's true but it's unlikely that you would be registering three different listeners for the same event AND the same target. And if you did, I think it would still be "symmetrical" to remove all the listeners for that event AND that target with one call. Otherwise it would work just as it does now.

    Overriding dispatchEvent doesn't help because it's not used internally to dispatch events like ENTER_FRAME, MOUSE_DOWN, etc.
    Ok, understood.

    But in the future I can add a function to Application class to suppress runtime errors like:
    application:suppressErrors(true)
    or forward errors to a custom error handler function like:
    application:registerErrorFunction(func)
    Yes, registering a custom handler would be good.

    best regards
Sign In or Register to comment.