Skip to content

Commit

Permalink
improve HDF5Viewer, add lock around await_finalize (#69)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwahlstrand authored Jun 1, 2024
1 parent 6d8d644 commit ee7b5d7
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 49 deletions.
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

0 comments on commit ee7b5d7

Please sign in to comment.