livebook-dev / kino

Client-driven interactive widgets for Livebook

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Kino.Input.text slow to render new input

clayscode opened this issue · comments

Hi all,

I'm having an issue with Kino.Input.text rendering the output very slowly. Sometimes I even have to click around the page a few times or move the element around before the input gets rendered.

Livebook Version: v0.10.0
Elixir Version: v1.15.2

Mix.install([
  {:kino_db, "~> 0.2.1"},
  {:postgrex, "~> 0.17"},
  {:kino_explorer, "~> 0.1.4"},
  {:adbc, "~> 0.1"}
])

alias Explorer.DataFrame, as: DF
alias Explorer.Series, as: Series
alias Explorer.Query, as: Query
frame = Kino.Frame.new()
button = Kino.Control.button("Click") |> Kino.render()
items = [[a: "a", b: "b"], [a: "c",b: "d"]]
Kino.animate(button, 0, fn _event, counter ->
  new_counter = counter + 1
  current = Enum.at(items, counter)

  inputs = [
    example_a: Kino.Input.text("Example a: ", default: "#{current[:a]}"),
    example_b: Kino.Input.text("Example b: ", default: "#{current[:b]}")
  ]
  form = Kino.Control.form(inputs, submit: "Send")
  Kino.Frame.clear(frame)
  Kino.Frame.render(frame, form)

  {:cont, frame, new_counter}
end)

The purpose of the above is to iterate over some objects and fill in the default Kino.Input.text values with them. However, when the input boxes are rendered, they're blank and it often takes a few seconds the default values to show up. If I move the element up or down, they populate the input box immediately. The frame clear and render at the end isn't necessary - I was just trying to see if I could force the text to render some how. Any idea what might be going on?

animate already keeps a frame for you, so I wonder if the issue is being caused by rendering the same frame twice. Try this instead:

button = Kino.Control.button("Click") |> Kino.render()
items = [[a: "a", b: "b"], [a: "c",b: "d"]]

Kino.animate(button, 0, fn _event, counter ->
  new_counter = counter + 1
  current = Enum.at(items, counter)

  inputs = [
    example_a: Kino.Input.text("Example a: ", default: "#{current[:a]}"),
    example_b: Kino.Input.text("Example b: ", default: "#{current[:b]}")
  ]
  form = Kino.Control.form(inputs, submit: "Send")
  {:cont, form, new_counter}
end)

animate already keeps a frame for you, so I wonder if the issue is being caused by rendering the same frame twice. Try this instead:

button = Kino.Control.button("Click") |> Kino.render()
items = [[a: "a", b: "b"], [a: "c",b: "d"]]

Kino.animate(button, 0, fn _event, counter ->
  new_counter = counter + 1
  current = Enum.at(items, counter)

  inputs = [
    example_a: Kino.Input.text("Example a: ", default: "#{current[:a]}"),
    example_b: Kino.Input.text("Example b: ", default: "#{current[:b]}")
  ]
  form = Kino.Control.form(inputs, submit: "Send")
  {:cont, form, new_counter}
end)

Still have the same issue with this code

Edit: Just to confirm it's not an issue with my browser, I switched from Firefox to Chrome, but still run into the same problem.

I think there is a race condition happening in

kino/lib/kino/frame.ex

Lines 172 to 177 in f961f8d

def handle_call({:render, term, destination}, _from, state) do
output = Kino.Render.to_livebook(term)
put_update(destination, state.ref, [output], :replace)
state = update_outputs(state, destination, fn _ -> [output] end)
{:reply, :ok, state}
end

When putting an inspect after output

 def handle_call({:render, term, destination}, _from, state) do 
   output = Kino.Render.to_livebook(term) |> IO.inspect()
   put_update(destination, state.ref, [output], :replace) 
   state = update_outputs(state, destination, fn _ -> [output] end) 
   {:reply, :ok, state} 
 end 

it magically works :-D
Couldn't figure out the details yet.

Here is a simple way to reproduce the error:

frame = Kino.Frame.new()
Kino.Frame.render(frame, Kino.Input.text("Test"))
button = Kino.Control.button("Click") |> Kino.render()
items = [[a: "a", b: "b"], [a: "c",b: "d"]]

Kino.animate(button, 0, fn _event, counter ->
  new_counter = counter + 1
  current = Enum.at(items, counter)

  inputs = [
    example_a: Kino.Input.text("Example a: ", default: "#{current[:a]}"),
    example_b: Kino.Input.text("Example b: ", default: "#{current[:b]}")
  ]
  form = Kino.Control.form(inputs, submit: "Send")
  {:cont, form, new_counter}
end)

In the latest Livebook nightly build, this example doesn't even work - the whole session crashes 😅

I think there is a race condition happening in

kino/lib/kino/frame.ex

Lines 172 to 177 in f961f8d

def handle_call({:render, term, destination}, _from, state) do
output = Kino.Render.to_livebook(term)
put_update(destination, state.ref, [output], :replace)
state = update_outputs(state, destination, fn _ -> [output] end)
{:reply, :ok, state}
end

When putting an inspect after output

 def handle_call({:render, term, destination}, _from, state) do 
   output = Kino.Render.to_livebook(term) |> IO.inspect()
   put_update(destination, state.ref, [output], :replace) 
   state = update_outputs(state, destination, fn _ -> [output] end) 
   {:reply, :ok, state} 
 end 

it magically works :-D Couldn't figure out the details yet.

Here is a simple way to reproduce the error:

frame = Kino.Frame.new()
Kino.Frame.render(frame, Kino.Input.text("Test"))

I can't replicate my issue with this code - this seems to render correctly. I checked out the .10 branch of both Livebook and Kino and changed it to output = Kino.Render.to_livebook(term) |> IO.inspect() but that doesn't seem to resolve the issue with Kino.animate

@clayscode I pushed a fix to livebook main. Not sure if its the correct way to tackle the issue, but it works on my side.
Lets see what @jonatanklosko thinks :)

@jannikbecher your PR seems to fix my issue, awesome!