Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert container contents to output lazily #393

Merged
merged 1 commit into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions lib/kino/frame.ex
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ defmodule Kino.Frame do
placeholder: boolean()
}

@typedoc false
@type state :: %{outputs: list(Kino.Output.t())}

@doc """
Creates a new frame.

Expand Down Expand Up @@ -151,47 +148,47 @@ defmodule Kino.Frame do
end

@doc false
@spec get_outputs(t()) :: list(Kino.Output.t())
def get_outputs(frame) do
GenServer.call(frame.pid, :get_outputs)
@spec get_items(t()) :: list(term())
def get_items(frame) do
GenServer.call(frame.pid, :get_items)
end

@impl true
def init(ref) do
{:ok, %{ref: ref, outputs: []}}
{:ok, %{ref: ref, items: []}}
end

@impl true
def handle_cast({:clear, destination}, state) do
put_update(destination, state.ref, [], :replace)
state = update_outputs(state, destination, fn _ -> [] end)
state = update_items(state, destination, fn _ -> [] end)
{:noreply, state}
end

@impl true
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)
state = update_items(state, destination, fn _ -> [term] end)
{:reply, :ok, state}
end

def handle_call({:append, term, destination}, _from, state) do
output = Kino.Render.to_livebook(term)
put_update(destination, state.ref, [output], :append)
state = update_outputs(state, destination, &[output | &1])
state = update_items(state, destination, &[term | &1])
{:reply, :ok, state}
end

def handle_call(:get_outputs, _from, state) do
{:reply, state.outputs, state}
def handle_call(:get_items, _from, state) do
{:reply, state.items, state}
end

defp update_outputs(state, :default, update_fun) do
update_in(state.outputs, update_fun)
defp update_items(state, :default, update_fun) do
update_in(state.items, update_fun)
end

defp update_outputs(state, _destination, _update_fun), do: state
defp update_items(state, _destination, _update_fun), do: state

defp put_update(destination, ref, outputs, type) do
output = %{type: :frame_update, ref: ref, update: {type, outputs}}
Expand Down
10 changes: 4 additions & 6 deletions lib/kino/layout.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ defmodule Kino.Layout do
Layout utilities for arranging multiple kinos together.
"""

defstruct [:type, :outputs, :info]
defstruct [:type, :items, :info]

@opaque t :: %__MODULE__{
type: :tabs | :grid,
outputs: list(Kino.Output.t()),
items: list(term()),
info: map()
}

Expand All @@ -31,9 +31,8 @@ defmodule Kino.Layout do
def tabs(tabs) do
{labels, terms} = Enum.unzip(tabs)
labels = Enum.map(labels, &to_string/1)
outputs = Enum.map(terms, &Kino.Render.to_livebook/1)
info = %{labels: labels}
%Kino.Layout{type: :tabs, outputs: outputs, info: info}
%Kino.Layout{type: :tabs, items: terms, info: info}
end

@doc """
Expand Down Expand Up @@ -65,14 +64,13 @@ defmodule Kino.Layout do
@spec grid(list(term()), keyword()) :: t()
def grid(terms, opts \\ []) do
opts = Keyword.validate!(opts, columns: 1, boxed: false, gap: 8)
outputs = Enum.map(terms, &Kino.Render.to_livebook/1)

info = %{
columns: opts[:columns],
boxed: opts[:boxed],
gap: opts[:gap]
}

%Kino.Layout{type: :grid, outputs: outputs, info: info}
%Kino.Layout{type: :grid, items: terms, info: info}
end
end
9 changes: 6 additions & 3 deletions lib/kino/render.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,23 @@ defimpl Kino.Render, for: Kino.Frame do

def to_livebook(kino) do
Kino.Bridge.reference_object(kino.pid, self())
outputs = Kino.Frame.get_outputs(kino)
outputs = kino |> Kino.Frame.get_items() |> Enum.map(&Kino.Render.to_livebook/1)
%{type: :frame, ref: kino.ref, outputs: outputs, placeholder: kino.placeholder}
end
end

defimpl Kino.Render, for: Kino.Layout do
def to_livebook(%{type: :tabs} = kino) do
%{type: :tabs, outputs: kino.outputs, labels: kino.info.labels}
outputs = Enum.map(kino.items, &Kino.Render.to_livebook/1)
%{type: :tabs, outputs: outputs, labels: kino.info.labels}
end

def to_livebook(%{type: :grid} = kino) do
outputs = Enum.map(kino.items, &Kino.Render.to_livebook/1)

%{
type: :grid,
outputs: kino.outputs,
outputs: outputs,
columns: kino.info.columns,
gap: kino.info.gap,
boxed: kino.info.boxed
Expand Down
24 changes: 22 additions & 2 deletions test/kino/frame_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule Kino.FrameTest do
%{type: :frame_update, update: {:replace, [%{type: :terminal_text, text: "\e[34m1\e[0m"}]}}
)

assert Kino.Frame.get_outputs(frame) == []
assert Kino.Frame.get_items(frame) == []
end

test "render/2 sends output directly to clients when :temporary is true" do
Expand All @@ -42,7 +42,7 @@ defmodule Kino.FrameTest do
update: {:replace, [%{type: :terminal_text, text: "\e[34m1\e[0m"}]}
})

assert Kino.Frame.get_outputs(frame) == []
assert Kino.Frame.get_items(frame) == []
end

test "render/2 raises when :to and :temporary is disabled" do
Expand Down Expand Up @@ -87,4 +87,24 @@ defmodule Kino.FrameTest do
update: {:append, [%{type: :terminal_text, text: "\e[34m1\e[0m"}]}
})
end

test "Kino.Render.to_livebook/1 returns the current value for a nested frame" do
frame = Kino.Frame.new()

frame_inner = Kino.Frame.new()

Kino.Frame.render(frame, frame_inner)

assert %{
type: :frame,
outputs: [%{type: :frame, outputs: []}]
} = Kino.Render.to_livebook(frame)

Kino.Frame.render(frame_inner, 1)

assert %{
type: :frame,
outputs: [%{type: :frame, outputs: [%{type: :terminal_text, text: "\e[34m1\e[0m"}]}]
} = Kino.Render.to_livebook(frame)
end
end
47 changes: 47 additions & 0 deletions test/kino/layout_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
defmodule Kino.LayoutTest do
use Kino.LivebookCase, async: true

describe "tabs" do
test "Kino.Render.to_livebook/1 returns the current value for a nested frame" do
frame_inner = Kino.Frame.new()

tabs = Kino.Layout.tabs(frame: frame_inner)

assert %{
type: :tabs,
outputs: [%{type: :frame, outputs: []}]
} = Kino.Render.to_livebook(tabs)

Kino.Frame.render(frame_inner, 1)

assert %{
type: :tabs,
outputs: [
%{type: :frame, outputs: [%{type: :terminal_text, text: "\e[34m1\e[0m"}]}
]
} = Kino.Render.to_livebook(tabs)
end
end

describe "grid" do
test "Kino.Render.to_livebook/1 returns the current value for a nested frame" do
frame_inner = Kino.Frame.new()

grid = Kino.Layout.grid([frame_inner])

assert %{
type: :grid,
outputs: [%{type: :frame, outputs: []}]
} = Kino.Render.to_livebook(grid)

Kino.Frame.render(frame_inner, 1)

assert %{
type: :grid,
outputs: [
%{type: :frame, outputs: [%{type: :terminal_text, text: "\e[34m1\e[0m"}]}
]
} = Kino.Render.to_livebook(grid)
end
end
end
2 changes: 1 addition & 1 deletion test/kino/tree_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ defmodule Kino.TreeTest do
defp tree(input) do
%Kino.Layout{
type: :grid,
outputs: [%{type: :js, js_view: %{ref: ref}}]
items: [%Kino.JS{ref: ref}]
} = Kino.Tree.new(input)

send(Kino.JS.DataStore, {:connect, self(), %{origin: "client:#{inspect(self())}", ref: ref}})
Expand Down
Loading