From d6f67ee98506ce4fb4c02d5d71c4914197a5bab4 Mon Sep 17 00:00:00 2001 From: Jared Wahlstrand Date: Sat, 1 Jun 2024 17:44:33 -0400 Subject: [PATCH] improve HDF5Viewer, add lock around await_finalize --- examples/HDF5Viewer/Project.toml | 1 + examples/HDF5Viewer/src/HDF5Viewer.jl | 119 ++++++++++++++++---------- src/GLib/gtype.jl | 26 ++++-- 3 files changed, 97 insertions(+), 49 deletions(-) diff --git a/examples/HDF5Viewer/Project.toml b/examples/HDF5Viewer/Project.toml index a4a0d6bf..d9c358b1 100644 --- a/examples/HDF5Viewer/Project.toml +++ b/examples/HDF5Viewer/Project.toml @@ -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" diff --git a/examples/HDF5Viewer/src/HDF5Viewer.jl b/examples/HDF5Viewer/src/HDF5Viewer.jl index 83ab19d7..040969e8 100644 --- a/examples/HDF5Viewer/src/HDF5Viewer.jl +++ b/examples/HDF5Viewer/src/HDF5Viewer.jl @@ -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. @@ -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 @@ -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)) @@ -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] @@ -201,9 +219,6 @@ 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) @@ -211,4 +226,20 @@ function hdf5view(filename::AbstractString) 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 diff --git a/src/GLib/gtype.jl b/src/GLib/gtype.jl index 6dcdc12b..f3bde02d 100644 --- a/src/GLib/gtype.jl +++ b/src/GLib/gtype.jl @@ -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}() @@ -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) @@ -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 @@ -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