From f60e984b9c9e83b599668adbd6c7879e63d1cb90 Mon Sep 17 00:00:00 2001 From: Juan Ignacio Polanco Date: Wed, 18 Sep 2024 14:57:48 +0200 Subject: [PATCH] Make `close` actually save VTK files (#149) * Explicitly define Base.close (and isopen, show) * Add finalisers to XMLDocument objects Allow the GC to free LightXML memory when the parent object (<:VTKFile) is destroyed. * Make Base.close equivalent to vtk_save * Prefer close to vtk_save (part 1) * Prefer `close` in docs * Fix tests? --- docs/src/grids/syntax.md | 2 +- docs/src/metadata/multiblock.md | 2 +- docs/src/metadata/parallel.md | 4 +-- docs/src/metadata/paraview_collections.md | 4 +-- docs/src/tools/surface.md | 2 +- src/WriteVTK.jl | 35 ++++++++++++++++------- src/gridtypes/ParaviewCollection.jl | 11 ++++--- src/gridtypes/multiblock.jl | 24 +++++++--------- src/gridtypes/pvtk_grid.jl | 12 +++++--- src/save_files.jl | 4 +-- test/pvdCollection.jl | 6 ++-- test/rectilinear.jl | 3 +- test/structured.jl | 6 +++- test/surface.jl | 4 ++- 14 files changed, 74 insertions(+), 45 deletions(-) diff --git a/docs/src/grids/syntax.md b/docs/src/grids/syntax.md index 2ec3a197..0ba7026d 100644 --- a/docs/src/grids/syntax.md +++ b/docs/src/grids/syntax.md @@ -25,7 +25,7 @@ is equivalent to: ```julia vtk = vtk_grid(filename, points..., [cells]; kws...) # add datasets here... -saved_files = vtk_save(vtk) +saved_files = close(vtk) ``` ## Data formatting options diff --git a/docs/src/metadata/multiblock.md b/docs/src/metadata/multiblock.md index d587b6c3..7dfb2304 100644 --- a/docs/src/metadata/multiblock.md +++ b/docs/src/metadata/multiblock.md @@ -57,7 +57,7 @@ vtk = vtk_grid(yet_another_block, "my_deeply_nested_file", x4, y4, z4) Finally, only the multiblock file needs to be saved explicitly: ``` julia -outfiles = vtk_save(vtm) +outfiles = close(vtm) ``` WriteVTK will write out a multiblock VTK file that looks like something like this (in addition to all the VTK files contained in the multiblock file): diff --git a/docs/src/metadata/parallel.md b/docs/src/metadata/parallel.md index 413531a1..17cfb1b9 100644 --- a/docs/src/metadata/parallel.md +++ b/docs/src/metadata/parallel.md @@ -21,8 +21,8 @@ pvtk_grid( which returns a handler representing a parallel VTK file that can be appended with cell and point data and eventually written to disk with -[`vtk_save`](@ref) as usual. -In an MPI job, `vtk_save` will cause each rank to write a serial file and just +[`close`](@ref) as usual. +In an MPI job, `close` will cause each rank to write a serial file and just a single rank (e.g., rank 0) will write the header file. This signature is valid for **unstructured grids**. diff --git a/docs/src/metadata/paraview_collections.md b/docs/src/metadata/paraview_collections.md index 6961ccf6..cf46fabc 100644 --- a/docs/src/metadata/paraview_collections.md +++ b/docs/src/metadata/paraview_collections.md @@ -31,13 +31,13 @@ collection_add_timestep(pvd, vtk, time) ``` Here, `time` is a real number that represents the current time (or timestep) in -the simulation. Note that both options implicitly call `vtk_save(vtk)` so adding +the simulation. Note that both options implicitly call `close(vtk)` so adding the VTK file to the collection must be done after adding data to the file. When all the files are added to the `pvd` file, it can be saved using: ``` julia -vtk_save(pvd) +close(pvd) ``` ## Working example diff --git a/docs/src/tools/surface.md b/docs/src/tools/surface.md index 5a9fa22f..76ca721b 100644 --- a/docs/src/tools/surface.md +++ b/docs/src/tools/surface.md @@ -22,7 +22,7 @@ julia> zs = @. cos(xs) + sin(ys'); julia> vtk = vtk_surface("surf", xs, ys, zs) VTK file 'surf.vtu' (UnstructuredGrid file, open) -julia> vtk_save(vtk) +julia> close(vtk) 1-element Vector{String}: "surf.vtu" ``` diff --git a/src/WriteVTK.jl b/src/WriteVTK.jl index c58ad54e..049d1b9d 100644 --- a/src/WriteVTK.jl +++ b/src/WriteVTK.jl @@ -21,8 +21,6 @@ using FillArrays: Zeros using Base64: base64encode -import Base: close, isopen, show - using VTKBase: VTKBase, VTKCellTypes, # cell type definitions as in vtkCellType.h @@ -50,7 +48,7 @@ const HeaderType = UInt64 # should be UInt32 or UInt64 """ VTKFile -Abstract type describing a VTK file that may be written using [`vtk_save`](@ref). +Abstract type describing a VTK file that may be written using [`close`](@ref). """ abstract type VTKFile end @@ -104,8 +102,10 @@ struct DatasetFile <: VTKFile end end -DatasetFile(dtype, xdoc::XMLDocument, fname::AbstractString, args...; kwargs...) = +function DatasetFile(dtype, xdoc::XMLDocument, fname::AbstractString, args...; kwargs...) + finalizer(LightXML.free, xdoc) DatasetFile(xdoc, add_extension(fname, dtype), xml_name(dtype), args...; kwargs...) +end function data_format(vtk::DatasetFile) if vtk.appended @@ -117,24 +117,39 @@ function data_format(vtk::DatasetFile) end end -function show(io::IO, vtk::DatasetFile) +function Base.show(io::IO, vtk::DatasetFile) open_str = isopen(vtk) ? "open" : "closed" print(io, "VTK file '$(vtk.path)' ($(vtk.grid_type) file, $open_str)") end """ - close(vtk::VTKFile) + Base.close(vtk::VTKFile) -> Vector{String} Write and close VTK file. + +Returns a list of paths pointing to the written VTK files (typically just one file, but can +be more for e.g. `MultiblockFile`). + +--- + + Base.close(vtm::MultiblockFile) -> Vector{String} + +Save and close multiblock file (`.vtm`). +The VTK files included in the multiblock file are also saved. """ -close(vtk::VTKFile) = free(vtk.xdoc) +Base.close(vtk::VTKFile) = vtk_save(vtk) # for backwards compatibility, the actual implementation is in vtk_save (which still works) + +# Free LightXML memory. Note that this is also called when an xdoc object is finalised, but +# it seems to be OK to call `free` multiple times. +# After calling this, the VTK file is considered as closed (see `isopen` below). +close_xml(vtk::VTKFile) = LightXML.free(vtk.xdoc) """ - isopen(vtk::VTKFile) + Base.isopen(vtk::VTKFile) Check if VTK file is still being written. """ -isopen(vtk::VTKFile) = (vtk.xdoc.ptr != C_NULL) +Base.isopen(vtk::VTKFile) = (vtk.xdoc.ptr != C_NULL) # Add a default extension to the filename, unless the user have already given # the correct one. @@ -201,7 +216,7 @@ for func in (:vtk_grid, :pvtk_grid, :vtk_multiblock, :paraview_collection, try f(vtk) finally - outfiles = vtk_save(vtk) + outfiles = close(vtk) end outfiles :: Vector{String} end diff --git a/src/gridtypes/ParaviewCollection.jl b/src/gridtypes/ParaviewCollection.jl index 43fed6c4..0bfa8c61 100644 --- a/src/gridtypes/ParaviewCollection.jl +++ b/src/gridtypes/ParaviewCollection.jl @@ -7,7 +7,10 @@ struct CollectionFile <: VTKFile xdoc::XMLDocument path::String timeSteps::Vector{String} - CollectionFile(xdoc, path) = new(xdoc, path, String[]) + function CollectionFile(xdoc, path) + finalizer(LightXML.free, xdoc) + new(xdoc, path, String[]) + end end function paraview_collection(filename::AbstractString; @@ -65,7 +68,7 @@ function collection_add_timestep(pvd::CollectionFile, datfile::VTKFile, set_attribute(xDataSet, "timestep", string(time)) set_attribute(xDataSet, "part", "0") set_attribute(xDataSet, "file", fname) - append!(pvd.timeSteps, vtk_save(datfile)) + append!(pvd.timeSteps, close(datfile)) return end @@ -75,8 +78,8 @@ Base.setindex!(pvd::CollectionFile, datfile::VTKFile, time::Real) = function vtk_save(pvd::CollectionFile) outfiles = [pvd.path; pvd.timeSteps]::Vector{String} if isopen(pvd) - save_file(pvd.xdoc, pvd.path) - close(pvd) + LightXML.save_file(pvd.xdoc, pvd.path) + close_xml(pvd) end return outfiles end diff --git a/src/gridtypes/multiblock.jl b/src/gridtypes/multiblock.jl index 33bac44f..da2ce4b5 100644 --- a/src/gridtypes/multiblock.jl +++ b/src/gridtypes/multiblock.jl @@ -10,6 +10,7 @@ struct VTKBlock VTKBlock(xelm) = new(xelm, Union{VTKFile,VTKBlock}[]) end +Base.close(vtb::VTKBlock) = vtk_save(vtb) xml_block_root(vtb::VTKBlock) = vtb.xelm """ @@ -21,7 +22,10 @@ struct MultiblockFile <: VTKFile xdoc::XMLDocument path::String blocks::Vector{Union{VTKFile,VTKBlock}} - MultiblockFile(xdoc, path) = new(xdoc, path, Union{VTKFile,VTKBlock}[]) + function MultiblockFile(xdoc, path) + finalizer(LightXML.free, xdoc) + new(xdoc, path, Union{VTKFile,VTKBlock}[]) + end end function xml_block_root(vtm::MultiblockFile) @@ -39,9 +43,9 @@ Initialise VTK multiblock file, linking multiple VTK dataset files. Returns a handler for a multiblock file. To recursively save the multiblock file and linked dataset files, call -[`vtk_save`](@ref) on the returned handler. +[`close`](@ref) on the returned handler. -Note that `vtk_save` is implicitly called if the optional `f` argument is passed. +Note that `close` is implicitly called if the optional `f` argument is passed. This is in particular what happens when using the do-block syntax. """ function vtk_multiblock(filename::AbstractString) @@ -103,20 +107,14 @@ function _generate_gridfile_basename(vtm::VTKBlock) end end -""" - vtk_save(vtm::MultiblockFile) - -Save and close multiblock file (`.vtm`). -The VTK files included in the multiblock file are also saved. -""" function vtk_save(vtm::MultiblockFile) outfiles = [vtm.path]::Vector{String} for vtk in vtm.blocks - append!(outfiles, vtk_save(vtk)) + append!(outfiles, close(vtk)) end if isopen(vtm) - save_file(vtm.xdoc, vtm.path) - close(vtm) + LightXML.save_file(vtm.xdoc, vtm.path) + close_xml(vtm) end outfiles end @@ -125,7 +123,7 @@ function vtk_save(vtm::VTKBlock) # Saves VTKBlocks. outfiles = String[] for vtk in vtm.blocks - append!(outfiles, vtk_save(vtk)) + append!(outfiles, close(vtk)) end return outfiles end diff --git a/src/gridtypes/pvtk_grid.jl b/src/gridtypes/pvtk_grid.jl index e4e779dd..290eb034 100644 --- a/src/gridtypes/pvtk_grid.jl +++ b/src/gridtypes/pvtk_grid.jl @@ -12,6 +12,10 @@ struct PVTKFile <: VTKFile xdoc::XMLDocument vtk::DatasetFile path::String + function PVTKFile(args, xdoc, vtk, path) + finalizer(LightXML.free, xdoc) + new(args, xdoc, vtk, path) + end end # This is just to make a PVTKFile work like a DatasetFile. @@ -52,9 +56,9 @@ compute_whole_extent(::Nothing) = nothing ) Returns a handler representing a parallel VTK file, which can be -eventually written to file with `vtk_save`. +eventually written to file with [`close`](@ref). -Positional and keyword arguments in `args` and `kwargs` are passed to `vtk_grid` +Positional and keyword arguments in `args` and `kwargs` are passed to [`vtk_grid`](@ref) verbatim. Note that serial filenames are automatically generated from `filename` and from the process id `part`. @@ -186,8 +190,8 @@ function vtk_save(pvtk::PVTKFile) save_file(pvtk.xdoc, pvtk.path) push!(outfiles, pvtk.path) end - append!(outfiles, vtk_save(pvtk.vtk)) - close(pvtk) + append!(outfiles, close(pvtk.vtk)) + close_xml(pvtk) end outfiles end diff --git a/src/save_files.jl b/src/save_files.jl index 470eb628..d2958f42 100644 --- a/src/save_files.jl +++ b/src/save_files.jl @@ -6,8 +6,8 @@ function vtk_save(vtk::DatasetFile) save_file(vtk.xdoc, vtk.path) end end - if isopen(vtk) # just in case the file was closed by calls to save_* above - close(vtk) + if isopen(vtk) # just in case the XML handler was freed by calls to save_* above + close_xml(vtk) end return [vtk.path] :: Vector{String} end diff --git a/test/pvdCollection.jl b/test/pvdCollection.jl index aaf5c333..a61bfff5 100644 --- a/test/pvdCollection.jl +++ b/test/pvdCollection.jl @@ -1,6 +1,7 @@ #!/usr/bin/env julia using WriteVTK +using Test using Printf: @sprintf @@ -69,7 +70,8 @@ function main() vtk["q_values"] = q vtk["myVector"] = vec vtk["myCellData"] = cdata - vtk_save(vtk) + close(vtk) + @test isopen(vtk) == false pvd[float(it + 1)] = vtk end end @@ -83,7 +85,7 @@ function main() # add a vtk file vtk_reload = vtk_grid("collection_reload", [1, 2, 3], [1, 2, 3]) pvd_reload[5.0] = vtk_reload - pvd_reload_files = vtk_save(pvd_reload) + pvd_reload_files = close(pvd_reload) append!(outfiles, pvd_reload_files) println("Saved: ", join(outfiles, " ")) diff --git a/test/rectilinear.jl b/test/rectilinear.jl index f31d20fa..50b19ee4 100644 --- a/test/rectilinear.jl +++ b/test/rectilinear.jl @@ -99,7 +99,8 @@ function main() vtk["myCellData"] = cdata # Save and close vtk file. - append!(outfiles, vtk_save(vtk)) + append!(outfiles, close(vtk)) + @test isopen(vtk) == false end end # dim loop diff --git a/test/structured.jl b/test/structured.jl index 413599f0..4ae621c9 100644 --- a/test/structured.jl +++ b/test/structured.jl @@ -2,6 +2,7 @@ using WriteVTK using StaticArrays: SVector +using Test const FloatType = Float32 const vtk_filename_noext = "structured" @@ -111,8 +112,11 @@ function generate_structured(grid_format, ::Val{dim}) where {dim} vtk["myVector.SVector"] = vs # Save and close vtk file. - vtk_save(vtk) + files = close(vtk) + @test isopen(vtk) == false end + + files end function main() diff --git a/test/surface.jl b/test/surface.jl index 658467d6..ceb2d80b 100644 --- a/test/surface.jl +++ b/test/surface.jl @@ -13,7 +13,9 @@ files = String[] let @time output = let vtk = vtk_surface("surface_basic", xs, ys, zs) - vtk_save(vtk) + output = close(vtk) + @test isopen(vtk) == false + output end append!(files, output) end