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

improve HDF5Viewer, add lock for await_finalize #69

Merged
merged 1 commit into from
Jun 1, 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
1 change: 1 addition & 0 deletions examples/HDF5Viewer/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.1.0"
[deps]
Gtk4 = "9db2cae5-386f-4011-9d63-a5602296539b"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"

[compat]
Gtk4 = "0.6"
Expand Down
119 changes: 75 additions & 44 deletions examples/HDF5Viewer/src/HDF5Viewer.jl
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
module HDF5Viewer

using Gtk4, HDF5, Gtk4.GLib

if Threads.nthreads() == 1 && Threads.nthreads(:interactive) < 1
@warn("For a more responsive UI, run with multiple threads enabled.")
end
using PrecompileTools

export hdf5view

const rprogbar = Ref{GtkProgressBarLeaf}()

# This is really just a toy at this point, but it is a less trivial example of
# using a ListView to show a tree and it uses a second thread to keep the UI
# responsive.
Expand Down Expand Up @@ -48,15 +47,20 @@ end
lmm(g,k) = GtkStringList([join([k,b],'/') for b in keys(g)])
lmm(d::HDF5.Dataset,k) = nothing

function create_tree_model(filename)
h5open(filename,"r") do h
function create_tree_model(filename, pwin)
treemodel = h5open(filename,"r") do h
ks = keys(h)
rootmodel = GtkStringList(ks)
function create_model(pp)
k=pp.string::String
k=Gtk4.G_.get_string(pp)
return lmm(h[k],k)
end
rootmodel = GtkStringList(keys(h))
GtkTreeListModel(GLib.GListModel(rootmodel),false, true, create_model)
end
@idle_add begin
Gtk4.destroy(pwin)
_create_gui(filename, treemodel)
end
end

# Print data in a TextView
Expand Down Expand Up @@ -93,64 +97,78 @@ function match_string(row, entrytext)
return false
end

_render_type(d) = "Array{$(string(HDF5.get_jl_type(d)))}"
_render_type(d::HDF5.Group) = "Group"
_render_size(d) = repr(size(d))
_render_size(d::HDF5.Group) = length(d)!=1 ? "$(length(d)) objects" : "1 object"

function hdf5view(filename::AbstractString)
ispath(filename) || error("Path $filename does not exist")
isfile(filename) || error("File not found")
HDF5.ishdf5(filename) || error("File is not HDF5")
pwin = GtkWindow("Opening $filename",600,100)
rprogbar[] = GtkProgressBar()
b=GtkBox(:v)
push!(b, GtkLabel("Please wait..."))
pwin[] = push!(b,rprogbar[])
t = Threads.@spawn create_tree_model(filename, pwin)
g_timeout_add(100) do
Gtk4.pulse(rprogbar[])
return !istaskdone(t)
end
nothing
end

function _create_filter(b)
search_entry = b["search_entry"]::GtkSearchEntryLeaf # controls the filter
function match(row::GtkTreeListRow)
entrytext = Gtk4.text(GtkEditable(search_entry))
if entrytext == "" || entrytext === nothing
return true
end
match_string(row, entrytext)
end

filt = GtkCustomFilter(match)
signal_connect(search_entry, "search-changed") do widget
@idle_add changed(filt, Gtk4.FilterChange_DIFFERENT)
end
filt
end

function _create_gui(filename, treemodel)
b = GtkBuilder(joinpath(@__DIR__, "HDF5Viewer.ui"))
w = b["win"]::GtkWindowLeaf
Gtk4.title(w, "HDF5 Viewer: $filename")
treemodel = create_tree_model(filename)


factory = GtkSignalListItemFactory(setup_cb, bind_cb)

# bind callback for the type ColumnView
function type_bind_cb(f, li)
label = get_child(li)
row = li[]
k=Gtk4.get_item(row).string
label = get_child(li)::GtkLabelLeaf
row = li[]::GtkTreeListRowLeaf
k=Gtk4.G_.get_string(Gtk4.get_item(row))
h5open(filename,"r") do h
d=h[k]
if isa(d,HDF5.Group)
Gtk4.label(label, "Group")
else
eltyp=string(HDF5.get_jl_type(d))
Gtk4.label(label, "Array{$eltyp}")
end
Gtk4.label(label, _render_type(h[k]))
end
end

# bind callback for the size ColumnView
function size_bind_cb(f, li)
label = get_child(li)
row = li[]
k=Gtk4.get_item(row).string
label = get_child(li)::GtkLabelLeaf
row = li[]::GtkTreeListRowLeaf
k=Gtk4.G_.get_string(Gtk4.get_item(row))
h5open(filename,"r") do h
d=h[k]
if isa(d,HDF5.Group)
n=length(d)
label.label = n!=1 ? "$n objects" : "1 object"
else
label.label = repr(size(d))
end
Gtk4.label(label, _render_size(h[k]))
end
end

type_factory = GtkSignalListItemFactory(list_setup_cb, type_bind_cb)
size_factory = GtkSignalListItemFactory(list_setup_cb, size_bind_cb)

search_entry = b["search_entry"]::GtkSearchEntryLeaf # controls the filter
spinner = b["spinner"]::GtkSpinnerLeaf # spins during possibly long render operations

function match(row::GtkTreeListRow)
entrytext = Gtk4.text(GtkEditable(search_entry))
if entrytext == "" || entrytext === nothing
return true
end
match_string(row, entrytext)
end

filt = GtkCustomFilter(match)
filt = _create_filter(b)
filteredModel = GtkFilterListModel(GListModel(treemodel), filt)

single_sel = GtkSingleSelection(GLib.GListModel(filteredModel))
Expand All @@ -174,7 +192,7 @@ function hdf5view(filename::AbstractString)
position = Gtk4.G_.get_selected(selection)
row = Gtk4.G_.get_row(treemodel,position)
row!==nothing || return
p = Gtk4.get_item(row).string
p = Gtk4.G_.get_string(Gtk4.get_item(row))
start(spinner)
Threads.@spawn h5open(filename,"r") do h
d=h[p]
Expand All @@ -201,14 +219,27 @@ function hdf5view(filename::AbstractString)

signal_connect(on_selected_changed,sel,"selection_changed")

signal_connect(search_entry, "search-changed") do widget
@idle_add changed(filt, Gtk4.FilterChange_DIFFERENT)
end
show(w)

@idle_add on_selected_changed(single_sel, 0, 1)

nothing
end

function __init__()
if Threads.nthreads() == 1 && Threads.nthreads(:interactive) < 1
@warn("For a more responsive UI, run with multiple threads enabled.")
end
end

let
@setup_workload begin
@compile_workload begin
b = GtkBuilder(joinpath(@__DIR__, "HDF5Viewer.ui"))
factory = GtkSignalListItemFactory(setup_cb, bind_cb)
_create_filter(b)
end
end
end

end # module
26 changes: 21 additions & 5 deletions src/GLib/gtype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ function glib_ref_sink(x::Ptr{GObject})
end
const gc_preserve_glib = Dict{Union{WeakRef, GObject}, Bool}() # glib objects
const gc_preserve_glib_lock = Ref(false) # to satisfy this lock, must never decrement a ref counter while it is held
const await_lock = ReentrantLock()
const topfinalizer = Ref(true) # keep recursion to a minimum by only iterating from the top
const await_finalize = Set{Any}()

Expand Down Expand Up @@ -271,7 +272,12 @@ function delref(@nospecialize(x::GObject))
# internal helper function
exiting[] && return # unnecessary to cleanup if we are about to die anyways
if gc_preserve_glib_lock[] || g_yielded[]
push!(await_finalize, x)
lock(await_lock)
try
push!(await_finalize, x)
finally
unlock(await_lock)
end
return # avoid running finalizers at random times
end
finalize_gc_unref(x)
Expand All @@ -296,7 +302,12 @@ function gobject_ref(x::T) where T <: GObject
if ccall((:g_object_get_qdata, libgobject), Ptr{Cvoid},
(Ptr{GObject}, UInt32), x, jlref_quark::UInt32) != C_NULL
# have set up metadata for this before, but its weakref has been cleared. restore the ref.
delete!(await_finalize, x)
lock(await_lock)
try
delete!(await_finalize, x)
finally
unlock(await_lock)
end
finalizer(delref, x)
gc_preserve_glib[WeakRef(x)] = false # record the existence of the object, but allow the finalizer
else
Expand Down Expand Up @@ -330,9 +341,14 @@ function run_delayed_finalizers()
filter!(x->!(isa(x.first,WeakRef) && x.first.value === nothing),gc_preserve_glib)
gc_preserve_glib_lock[] = false
end
while !isempty(await_finalize)
x = pop!(await_finalize)
finalize_gc_unref(x)
lock(await_lock)
try
while !isempty(await_finalize)
x = pop!(await_finalize)
finalize_gc_unref(x)
end
finally
unlock(await_lock)
end
topfinalizer[] = true
end
Expand Down
Loading