diff --git a/src/InPartS.jl b/src/InPartS.jl index 698bb3e4e4038df711d58fddf6c8a13dfddc2410..47956dcfb5e12ba8dd22d7440645ac69469682e3 100644 --- a/src/InPartS.jl +++ b/src/InPartS.jl @@ -116,6 +116,7 @@ include("callbacks.jl") include("evolve.jl") # IO +include("export/constants.jl") include("export/generated.jl") include("export/legacy.jl") include("export/generic.jl") diff --git a/src/export/constants.jl b/src/export/constants.jl new file mode 100644 index 0000000000000000000000000000000000000000..6fdcc8011a2452561433afc49e463fde8977e14a --- /dev/null +++ b/src/export/constants.jl @@ -0,0 +1,27 @@ + +const PTTYPENAME = "pttype" +const PMTYPENAME = "pmtype" +const PTCTYPENAME = "ptctype" +const OBSTACLEGROUPNAME = "obstacles" +const PARAMGROUPNAME = "params" +const REGNAME = "registrar" +const RNGNAME = "rng" +const SNAPGROUPNAME = "snapshots" +const SNAPTIMENAME = "time" +const SNAPSTEPNAME = "step" +const SNAPRTSNAME = "writetime" +const SNAPNEXTIDNAME = "next_id" +const STRUCTTYPE = "_type" +const TSSGROUPNAME = "timestepping" +const DOMAINGROUPNAME = "domain" +const DOMAINSIZENAME = "size" +const DOMAINBCNAME = "boundaries" + +const VERSIONNAME = "InPartS" +const TREEHASHNAME = "treehash" + +# Estimated number of snapshots in a file +# JLD2 will allocate a chunk of memory to fit links to approximately EST_NUM_SNAPSHOT +# snapshot groups. Speeds up reading files with many entries. +# Cost is a constant "large" amount of unused space (a few kb) for smaller files. +const EST_NUM_SNAPSHOTS = 2000 diff --git a/src/export/generated.jl b/src/export/generated.jl index 545fcd6705d2e66d0f5930594d90b39a3d2ae4cb..8c80d1185aeeceb4a6cc090185efaf391e5c9cc3 100644 --- a/src/export/generated.jl +++ b/src/export/generated.jl @@ -222,7 +222,7 @@ This module serves as a namespace for all functions generated by the import/expo """ module ExportUtils using InPartS - import InPartS:SV, ExportItem, FieldExport, AdditionalExport + import InPartS:SV, STRUCTTYPE, ExportItem, FieldExport, AdditionalExport """ exportfield(::Type, ::ExportItem) @@ -254,12 +254,26 @@ module ExportUtils importname(::Type, ::FieldExport{S}) where S = S # if you value your life, do NOT overwrite this method!!!!! importname(t::Type, a::AdditionalExport) = Symbol(exportname(t, a)) + # + """ + isserializable(T::Type) → Bool + isserializable(item::T) → Bool + Indicates whether serialize!/deserialize were defined for the type T or item(s) of type T. The type + for which `true` is returned always corresponds to the type that can also be passed to `serialize!`. + For `VectorExport`s, this means that the relevant serializable type is `Vector{T}`, not `T` itself. + + For more information on the export system, consult the InPartS manual. + """ + isserializable(::Type) = false + isserializable(::T) where {T} = isserializable(T) + #modname(::Type) = "Main" # more defaults! """ - serialize!(group, data::Vector{<:AbstractParticle}; [sim::Simulation]) - serialize!(group, data::AbstractParams) - Serialize the `data` into `group`. + serialize!(group, data::T) + serialize!(group, data::Vector{T<:AbstractParticle}; sim::Simulation) + Serialize the `data` of serializable type T into `group`. For types with `ExportStyle(T) == VectorExport()` + (currently only `AbstractParticle`), the second form applies. For more information on the export system, consult the InPartS manual. """ @@ -267,14 +281,39 @@ module ExportUtils serialize!(::Any, @nospecialize(a), @nospecialize(s::InPartS.ExportStyle); kwargs...) = error("InPartS: no $s serializer defined for type $(typeof(a))") """ - deserialize(group, type::Type{T}; [sim::Simulation]) where {T<:AbstractParticle} → particles::Vector{T} - deserialize(group, type::Type{T}) where {T<:AbstractParams} → param::T - Deserializes the `group` into either a vector of `type` or a single instance of `type`. + serializetodict(data::T) → Dict{String, Any} + serializetodict(data::Vector{T<:AbstractParticle}; sim::Simulation) → Dict{String, Any} + Serialize the `data` of serializable type `T` into a Dict{String, Any}, including the name of the + type `T` itself, which is stored at the key "$(STRUCTTYPE)". For types with `ExportStyle(T) == VectorExport()` + (currently only `AbstractParticle`), the second form applies. The result can be deserialzed using + [`deserializefromdict`](@ref). + + For more information on the export system, consult the InPartS manual. + """ + serializetodict(d::T; kwargs...) where {T} = serialize!(Dict{String, Any}(STRUCTTYPE => string(T)), d; kwargs...) + serializetodict(d::Vector{T}; kwargs...) where {T} = serialize!(Dict{String, Any}(STRUCTTYPE => string(T)), d; kwargs...) + + """ + deserialize(group, type::Type{T}) → result::T + deserialize(group, type::Type{T<:AbstractParticle}; sim::Simulation) → particles::Vector{T} + Deserializes the `group` into either a single instance of `type` or a vector of `type`. For types with + `ExportStyle(T) == VectorExport()` (currently only `AbstractParticle`), the latter, and thus, the second form, applies. + + For more information on the export system, consult the InPartS manual. + """ + deserialize(::Any, @nospecialize(a::Type); kwargs...) = error("InPartS: no deserializer defined for type $a") + + """ + deserializefromdict(dict, stype = Any; [sim::Simulation]) → Vector{T} where {T<:AbstractParticle} + deserializefromdict(dict, stype = Any) → T + The reverse operation of [`serializetodict`](@ref). Deserializes the `dict` into either a vector or + a single instance of the type T saved in `dict["$(STRUCTTYPE)"]`, depending on whether `T` + The optional `stype` argument is checked to be `T`'s supertype during reconstruction. For more information on the export system, consult the InPartS manual. """ - deserialize(g, t; kwargs...) = serialize!(g, d, InPartS.ExportStyle(t); kwags...) - deserialize(::Any, @nospecialize(a::Type), @nospecialize(s::InPartS.ExportStyle); kwargs...) = error("InPartS: no $s deserializer defined for type $a") + deserializefromdict(dict, stype = Any; kwargs...) = deserialize(dict, InPartS.reconstruct_subtype(dict[STRUCTTYPE], stype); kwargs...) + end """ @@ -326,7 +365,7 @@ of `type` is itself an InPartS-exportable type (produced, e.g., with @genexport) This macro produces code ```julia -@exporttransform {type} {fieldname} ExportUtils.serialize!(Dict{String, Any}("_type" => string(typeof(value))), value) ExportUtils.deserialize(data, InPartS.reconstruct_subtype(data["_type"], {fieldtype})) +@exporttransform {type} {fieldname} ExportUtils.serializetodict(value) ExportUtils.deserializefromdict(data, {fieldtype}) ``` where `fieldtype` is the automatically determined type of the field. """ @@ -334,7 +373,7 @@ macro exportrecursive(type, field::Symbol) check_type(type) local ft = fieldtype(__module__.eval(type), field) return quote - @exporttransform $(esc(type)) $(field) ExportUtils.serialize!(Dict{String, Any}("_type" => string(typeof(value))), value) ExportUtils.deserialize(data, InPartS.reconstruct_subtype(data["_type"], $(ft))) + @exporttransform $(esc(type)) $(field) ExportUtils.serializetodict(value) ExportUtils.deserializefromdict(data, $(ft)) end end @@ -437,17 +476,12 @@ end @genexport(type<:AbstractParticle) Generates the import/export functions for the given particle and its [`InPartS.ParamType`](@ref). -!!! warning - - This macro evals generated code into `InPartS.ExportUtils`. Using it as a top-level - statement in a precompiled package will bring naught but pain and destruction. - - If you want to automatically generate export/import functions when loading a package, - consider putting `@genexport` in the [`__init__()`](@ref) function. +For more information on the export system, consult the InPartS manual. """ macro genexport(type::Union{Symbol,Expr}) return quote local mod = @__MODULE__ + Base.eval(mod, _isserializable_function($(esc(type)))) Base.eval(mod, _serialize_function($(esc(type)))) Base.eval(mod, _deserialize_function($(esc(type)))) #ExportUtils.modname(::Type{mod.$type}) = "$mod" @@ -455,6 +489,22 @@ macro genexport(type::Union{Symbol,Expr}) end +## isserializable +""" + _isserializable_function(T) +Returns an expression containing the definition of [`ExportUtils.isserializable()`](@ref) +for the given type `T`. +""" +_isserializable_function(T::Type) = _isserializable_function(T, ExportStyle(T)) + +_isserializable_function(T::Type, ::ScalarExport) = quote + ExportUtils.isserializable(::Type{<:$T}) = true +end + +_isserializable_function(T::Type, ::VectorExport) = quote + ExportUtils.isserializable(::Type{Vector{ET}}) where {ET<:$T} = true +end + ## Serialization diff --git a/src/export/generic.jl b/src/export/generic.jl index 7e0af3929c6349d7c9a51fa43bf685e43156a14d..3d6432c9d25f1c8c837a5c6d8830621e3be660d0 100644 --- a/src/export/generic.jl +++ b/src/export/generic.jl @@ -1,32 +1,5 @@ export SaveCallback, BackupCallback, readsim -const PTTYPENAME = "pttype" -const PMTYPENAME = "pmtype" -const PTCTYPENAME = "ptctype" -const OBSTACLEGROUPNAME = "obstacles" -const PARAMGROUPNAME = "params" -const REGNAME = "registrar" -const RNGNAME = "rng" -const SNAPGROUPNAME = "snapshots" -const SNAPTIMENAME = "time" -const SNAPSTEPNAME = "step" -const SNAPRTSNAME = "writetime" -const SNAPNEXTIDNAME = "next_id" -const STRUCTTYPE = "_type" -const TSSGROUPNAME = "timestepping" -const DOMAINGROUPNAME = "domain" -const DOMAINSIZENAME = "size" -const DOMAINBCNAME = "boundaries" - -const VERSIONNAME = "InPartS" -const TREEHASHNAME = "treehash" - -# Estimated number of snapshots in a file -# JLD2 will allocate a chunk of memory to fit links to approximately EST_NUM_SNAPSHOT -# snapshot groups. Speeds up reading files with many entries. -# Cost is a constant "large" amount of unused space (a few kb) for smaller files. -const EST_NUM_SNAPSHOTS = 2000 - ## Datafile interface definitions """ dfopen(backend, filename; kwargs...)