pkulchenko / ZeroBraneStudio

Lightweight Lua-based IDE for Lua with code completion, syntax highlighting, live coding, remote debugger, and code analyzer; supports Lua 5.1, 5.2, 5.3, 5.4, LuaJIT and other Lua interpreters on Windows, macOS, and Linux
http://studio.zerobrane.com/
Other
2.59k stars 518 forks source link

Livecoding for GSLShell #239

Closed marcelvanherk closed 10 years ago

marcelvanherk commented 10 years ago

Hi Paul,

why is live coding not enabled for GSLShell? That would make a very cool feature if possible.

Marcel

pkulchenko commented 10 years ago

Marcel, I enabled live coding and it does work on simple scripts, but I ran into two issues:

  1. (already fixed in GSL-shell and will go into 2.3.2 as per franko/gsl-shell#17) use seems to have an issue with importing into "global" environment while debugging. This is the background; I opened a ticket for this (franko/gsl-shell#17). This issue only happens when you use F5/Debug; you can still use F6/Run as you did before, but you still need to specify require('mobdebug').start(), which you didn't need to do in the former case. Live coding is not affected by this either, only debugging.
  2. The second issue is that graph methods leave plots on screen when aborted. This seems to be a feature, but it does create a strange effect of a new window being opened on every change instead of the old one being changed. I can't do anything about it, but this makes live coding much less useful for any changes involving graphs. Ideally, all the graph windows need to be closed on errors (which is what live coding is using), but that's not currently the case.

Maybe @franko can comment on this or offer a workaround?

franko commented 10 years ago

Hi,

the problem with "use" was fixed in the master branch. You may want to update the "import.lua" file to fix the problem.

As for the graph problem, following the request of some users I've configured https://github.com/franko/gsl-shell/blob/master/gsl-shell-jit.c to keep the graph windows active when gsl-shell is invoked in non-interactive mode. Here a snippet of the code:

    if ((flags & FLAGS_INTERACTIVE)) {
        print_jit_status(L);
        dotty(L);
        s->keep_windows = 0;
    } else if (script == 0 && !(flags & (FLAGS_EXEC|FLAGS_VERSION))) {
        if (lua_stdin_is_tty()) {
            print_version();
            print_jit_status(L);
            print_help_message();
            dotty(L);
            s->keep_windows = 0;
        } else {
            dofile(L, NULL);  /* executes stdin as a file */
        }
    }

I don't know how this play with ZBS. Probably gsl-shell believe it is running in interactive mode.

May be I can add a flag to explicitly ask to close windows on exit ?

Francesco

pkulchenko commented 10 years ago

Francesco, thank you for looking into this and for a quick fix on the use from coroutines.

I think the situation with the graph windows is interesting. Basically, the live coding component of the debugger is using error() call to abort the currently executed fragment, but this still leaves the graph window on the screen, so when the application is restarted (because of live coding changes), the user gets two windows as so on.

May be I can add a flag to explicitly ask to close windows on exit ?

I don't think that interactive status is misinterpreted as I do see the window correctly closed when the application is completed, but it's not closed after an error. Maybe there has to be an option to close it when there is a run-time error in the application?

For example, from one of your tests:

use 'math'
require 'plot3d'
x = |u,v| (1 + 1/2 * v *cos(u/2))*cos(u)
y = |u,v| (1 + 1/2 * v *cos(u/2))*sin(u)
z = |u,v| 1/2 * v * sin(u/2)

graph.surfplot({x, y, z}, 0, -1, 2*pi, 1, {gridu= 60, gridv= 5, stroke= true})
a() --<-- this is just something to cause a run-time error
print("done")

In this case the graph is still on even though there is a run-time error in the application.

I had a similar problem with I implemented live-coding for a wxlua-based application. I wanted to be able to draw something, but also to change the image by doing some code changes. I ended up storing the window information and reusing it when I get a new request for the same window. This worked well as it removed the flicker caused by closing/opening a window, but I don't know if this is going to work in your case as a script may open multiple graph windows.

At the very minimum, it would be nice to be able to either (1) close windows on error, or (2) use existing windows when the app is restarted.

franko commented 10 years ago

Hi Paul,

I'm perplexed about that. Actually I don't want to change gsl shell to close the windows on errors, I think this could be very annoying for the users.

To fix the live coding problem, if you can suggest me something that doesn't affect the normal usage of gsl shell I will make an effort to implement it.

Francesco

pkulchenko commented 10 years ago

To fix the live coding problem, if you can suggest me something that doesn't affect the normal usage of gsl shell I will make an effort to implement it.

Francesco, I agree and wouldn't want to change the behavior that your users clearly prefer.

It's quite possible that your library already provides a way to change the graph that is already displayed. The documentation also mentions that you can do graph animations, but I didn't find an example of this. If you can show me how this can be done, I'll probably come up with a way to integrate it with live-coding without additional changes.

Essentially, I want to be able to show a graph, and then show a (slightly) different graph (because some of the parameters have changed) in the same window. How would I do that? Thank you.

franko commented 10 years ago

Here a function that returns all the active graphical windows:

local function get_active_windows()
   local reg = debug.getregistry()
   return reg['GSL.reg.wins']
end

It does return a table that can have holes so you should traverse it with pairs.

Right now you can close a window with w:close() but once this is done the window is definitely closed. May be I can add for you a function that can close a window but keep it alive to be restarted later.

For the animations, here a simple illustrations:

use 'math'

function demo2()
   local f = |x| exp(-0.1*x) * cos(x)
   local p = graph.plot 'y = f(x)'
   local x0, x1 = 0, 10*pi
   local cc = graph.fxline(f, x0, x1, k)
   p.sync = false
   p:limits(0, -1, 10*pi, 1)
   p:pushlayer()
   p:show()
   local N = 256
   local yellow = graph.rgba(255,255,0,155)
   for k= 2, N do
      local x = x0 + k * (x1-x0) / N
      local ca = graph.fxline(f, x0, x, k)
      ca:line_to(x, 0); ca:line_to(0, 0); ca:close()
      p:clear()
      p:add(ca, yellow)
      p:addline(cc)
      p:flush()
   end
end

Note that you always works incrementally on a plot but plots and windows are not the same things. Usually there is a one-to-one relation between them but this is not always true.

Let me know if the "hide" method for the windows will be fine for you.

Francesco

marcelvanherk commented 10 years ago

Hi,

this is very nice for live coding:

-- create plot area if p==nil then p = graph.plot('radial scatter kernel (red) and radial integral (blue)') p:show() end

-- draw cross for while busy p:addline(graph.fxline(|x| x/10, 0, 10)) p:addline(graph.fxline(|x| 1-x/10, 0, 10))

-- draw stuff pow1 = 2.00 pow2 = 0.00 mu = 0.61

function kernel(mu, delta) --return function(r) return r_math.exp(-mur^pow1)/(r^pow2+0.001) end return function(r) return r(math.exp(-1.36_r^2)+math.exp(-0.01*r^2)/6.2) end end

local fk = kernel(mu, delta) local fi = function(t) return num.integ(fk, 0, t) end local fi10= fi(100)

local ln1 = graph.fxline(|t| fk(t)/fi10, 0, 18) local ln2 = graph.fxline(|t| fi(t)/fi10, 0, 18, 60)

-- plot it p:clear() p:addline(ln1, 'red') p:addline(ln2, 'blue')

Thanks,

Marcel

pkulchenko commented 10 years ago

Very clever! Thanks for the example Marcel! You can improve the performance by a bit if you wrap the calculation-intensive part into off/on calls:

-- create plot area
if p==nil then
p = graph.plot('radial scatter kernel (red) and radial integral (blue)')
p:show()
end

-- draw stuff
pow1 = 5.5
pow2 = 2.05
mu = 1

function kernel(mu, delta) 
  return function(r) return r*math.exp(-mu*r^pow1)/(r^pow2+0.001) end
end

require('mobdebug').off() --<-- disable debugging
local fk = kernel(mu, delta)
local fi = function(t) return num.integ(fk, 0, t) end
local fi10= fi(100)

local ln1 = graph.fxline(|t| fk(t)/fi10, 0, 18)
local ln2 = graph.fxline(|t| fi(t)/fi10, 0, 18, 60)

-- plot it
p:clear()
p:addline(ln1, 'red')
p:addline(ln2, 'blue')
require('mobdebug').on() --<-- enable it back
marcelvanherk commented 10 years ago

Hi Paul,

much faster indeed! Thanks,

Marcel

marcelvanherk commented 10 years ago

Can plugins make buttons in ZBS?

Would be nice to make a button group for:

Live coding New graph Clear graph Replot without clearing (on/off)

Marcel

pkulchenko commented 10 years ago

Marcel, I can add API calls to add buttons to the toolbar, but there are two issues with that: (1) you need to have icons for those buttons (ZBS only includes icons that it uses), and (2) you may not get the functionality you need.

The main reason for that is that any toolbar actions run in the IDE and not in the application you are running/debugging. This means that you should be able to start live coding (this what Live coding buttons will presumably do), but I'm not sure what you expect New graph and Clear graph buttons to do.

marcelvanherk commented 10 years ago

Hi,

Basically, I would like to run functions as in the remote console, or even paste text into the remote console and run it.

Marcel

pkulchenko commented 10 years ago

I would like to run functions as in the remote console, or even paste text into the remote console and run it.

That you may be able to do, although this is a bit tricky as well, as you need to pause app execution, execute the command, and then resume the execution. I might be able to come up with some wrapper code for you, but the question of icons still remains. What I probably can do is to add an API callback that is triggered when an icon is requested; you can then return your own bitmap based on something simple, even XPM. We'd still need yet another (API) call to update the toolbar.

pkulchenko commented 10 years ago

Marcel,

This plugin should add a live coding button to the toolbar (it's using XPM format for the icon). You'll need the latest git version as it adds one API call used in the plugin.

local tool
return {
  name = "Livecoding toolbar button",
  description = "Adds livecoding toolbar button.",
  author = "Paul Kulchenko",
  version = 0.1,

  onRegister = function(self)
    local tb = ide:GetToolBar()
    local pos = tb:GetToolPos(ID_STARTDEBUG)
    tool = tb:InsertTool(pos+1, ID_RUNNOW, "Run as Scratchpad", wx.wxBitmap({
      "16 16 4 1",
      "       c None",
      ".      c black",
      "X      c #808080",
      "o      c white",
      "                ",
      "  ..            ",
      " .Xo.    ...    ",
      " .Xoo. ..oo.    ",
      " .Xooo.Xooo...  ",
      " .Xooo.oooo.X.  ",
      " .Xooo.Xooo.X.  ",
      " .Xooo.oooo.X.  ",
      " .Xooo.Xooo.X.  ",
      " .Xooo.oooo.X.  ",
      "  .Xoo.Xoo..X.  ",
      "   .Xo.o..ooX.  ",
      "    .X..XXXXX.  ",
      "    ..X.......  ",
      "     ..         ",
      "                "}), wx.wxBitmap(), wx.wxITEM_CHECK)
    tb:Realize()
  end,

  onUnRegister = function(self)
    local tb = ide:GetToolBar()
    tb:DeleteTool(tool)
    tb:Realize()
  end,
}
marcelvanherk commented 10 years ago

Thanks Paul,

can you give a hint to the other request? The plugin would act as a small GUI for gsl-shell.

Marcel

pkulchenko commented 10 years ago

can you give a hint to the other request? The plugin would act as a small GUI for gsl-shell.

Marcel, yes, I'm thinking about it, but it's not that easy. Basically, you'd need to:

-- 1. Pause the scratchpad: 
ide:GetMainFrame():ProcessEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, ID_BREAK))
-- 2. Execute local console command
ShellExecuteCode(code)
-- 3. Continue execution
ide:GetMainFrame():ProcessEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, ID_STARTDEBUG))

It does look fairly straightforward, but the main issue is that each of these commands is an asynchronous request, so you can't really run them one by one and hope that the previous finished by the time next starts. That's what I was trying to figure out. You can probably check for debugger.running as it is set to true when debugger is waiting for a response, but you need to be careful not to block the IDE indefinitely. You also need to call wx.wxWakeUpIdle before checking debugger.running as async socket communication is done during an IDLE event.

You can try to figure it out on your own, but I'm thinking about writing a function that will encapsulate all this logic and will simply take code to run as a parameter. You can also probably specify if you want to see the command in the local console or if you want it to be complete invisible.

marcelvanherk commented 10 years ago

Paul,

Sounds like a perfect plan and a very nice extension.

Marcel


Van: Paul Kulchenko [notifications@github.com] Verzonden: vrijdag 24 januari 2014 1:56 Aan: pkulchenko/ZeroBraneStudio CC: Marcel van Herk Onderwerp: Re: [ZeroBraneStudio] Livecoding for GSLShell (#239)

can you give a hint to the other request? The plugin would act as a small GUI for gsl-shell.

Marcel, yes, I'm thinking about it, but it's not that easy. Basically, you'd need to:

-- 1. Pause the scratchpad: ide:GetMainFrame():ProcessEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, ID_BREAK)) -- 2. Execute local console command ShellExecuteCode(code) -- 3. Continue execution ide:GetMainFrame():ProcessEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, ID_STARTDEBUG))

It does look fairly straightforward, but the main issue is that each of these commands is an asynchronous request, so you can't really run them one by one and hope that the previous finished by the time next starts. That's what I was trying to figure out. You can probably check for debugger.running as it is set to true when debugger is waiting for a response, but you need to be careful not to block the IDE indefinitely. You also need to call wx.wxWakeUpIdle before checking debugger.running as async socket communication is done during an IDLE event.

You can try to figure it out on your own, but I'm thinking about writing a function that will encapsulate all this logic and will simply take code to run as a parameter. You can also probably specify if you want to see the command in the local console or if you want it to be complete invisible.

— Reply to this email directly or view it on GitHubhttps://github.com/pkulchenko/ZeroBraneStudio/issues/239#issuecomment-33187749.

pkulchenko commented 10 years ago

@marcelvanherk, Marcel, are you still interested in this "async" execution? I'm taking a look at how this can be done.

BTW, there was a recent somewhat related change: I added breakpoints that can be set/removed at run-time without stopping the application first.

marcelvanherk commented 10 years ago

Sure,

I would love to make some plugins to make plotting from GSL shell more user friendly.

Marcel