Skip to content

Commit fae59cd

Browse files
committed
switch back to NamedTuple for performance
1 parent 5491a72 commit fae59cd

File tree

3 files changed

+60
-72
lines changed

3 files changed

+60
-72
lines changed

src/basic_types.jl

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -440,18 +440,26 @@ The type of a concrete mesh. It can hold arbitrary vertex data (including FaceVi
440440
struct Mesh{
441441
Dim, T <: Real, # TODO: Number?
442442
FT <: AbstractFace,
443+
Names,
444+
VAT <: Tuple{<: AbstractVector{Point{Dim, T}}, Vararg{VertexAttributeType}},
443445
FVT <: AbstractVector{FT}
444446
} <: AbstractMesh{Dim, T}
445447

446-
vertex_attributes::Dict{Symbol, VertexAttributeType}
448+
vertex_attributes::NamedTuple{Names, VAT}
447449
faces::FVT
448450
views::Vector{UnitRange{Int}}
449451

450452
function Mesh(
451-
va::Dict{Symbol, VertexAttributeType},
453+
vertex_attributes::NamedTuple{Names, VAT},
452454
fs::FVT,
453455
views::Vector{UnitRange{Int}} = UnitRange{Int}[]
454-
) where {FT <: AbstractFace, FVT <: AbstractVector{FT}}
456+
) where {
457+
FT <: AbstractFace, FVT <: AbstractVector{FT}, Names, Dim, T,
458+
VAT <: Tuple{<: AbstractVector{Point{Dim, T}}, Vararg{VertexAttributeType}}
459+
}
460+
461+
va = vertex_attributes
462+
names = Names
455463

456464
# verify type
457465
if !haskey(va, :position )
@@ -460,12 +468,13 @@ struct Mesh{
460468

461469
if haskey(va, :normals)
462470
@warn "`normals` as a vertex attribute name has been deprecated in favor of `normal` to bring it in line with mesh.position and mesh.uv"
463-
va[:normal] = pop!(va, :normals)
471+
names = ntuple(i -> ifelse(names[i] == :normal, :normal, names[i]), length(names))
472+
va = NamedTuple{names}(values(va))
464473
end
465474

466475
# verify that faces of FaceViews match `fs` (in length per face)
467476
N = maximum(f -> value(maximum(f)), fs, init = 0)
468-
for (name, attrib) in va
477+
for (name, attrib) in pairs(va)
469478
if attrib isa FaceView
470479
try
471480
verify(fs, attrib)
@@ -477,10 +486,7 @@ struct Mesh{
477486
end
478487
end
479488

480-
D = length(eltype(va[:position]))
481-
T = eltype(eltype(va[:position]))
482-
483-
return new{D, T, FT, FVT}(va, fs, views)
489+
return new{Dim, T, FT, names, VAT, FVT}(va, fs, views)
484490
end
485491
end
486492

@@ -489,7 +495,7 @@ end
489495
@warn "mesh.normals has been deprecated in favor of mesh.normal to bring it in line with mesh.position and mesh.uv"
490496
return hasproperty(mesh, :normal)
491497
end
492-
return haskey(getfield(mesh, :vertex_attributes), field) || hasfield(Mesh, field)
498+
return hasproperty(getfield(mesh, :vertex_attributes), field) || hasfield(Mesh, field)
493499
end
494500
@inline function Base.getproperty(mesh::Mesh, field::Symbol)
495501
if hasfield(Mesh, field)
@@ -498,11 +504,11 @@ end
498504
@warn "mesh.normals has been deprecated in favor of mesh.normal to bring it in line with mesh.position and mesh.uv"
499505
return getproperty(mesh, :normal)
500506
else
501-
return getindex(getfield(mesh, :vertex_attributes), field)
507+
return getproperty(getfield(mesh, :vertex_attributes), field)
502508
end
503509
end
504510
@inline function Base.propertynames(mesh::Mesh)
505-
return (fieldnames(Mesh)..., keys(getfield(mesh, :vertex_attributes))...)
511+
return (fieldnames(Mesh)..., propertynames(getfield(mesh, :vertex_attributes))...)
506512
end
507513

508514
coordinates(mesh::Mesh) = mesh.position
@@ -530,21 +536,6 @@ function Base.iterate(mesh::Mesh, i=1)
530536
return i - 1 < length(mesh) ? (mesh[i], i + 1) : nothing
531537
end
532538

533-
function add_vertex_attribute!(mesh::Mesh, val::AbstractVector, name::Symbol)
534-
@boundscheck begin
535-
N = maximum(f -> value(maximum(f)), faces(mesh))
536-
length(val) < N && error("Given vertex data not large enough to be adressed by all faces ($N required, $(length(val)) given)")
537-
end
538-
mesh.vertex_attributes[name] = val
539-
return mesh
540-
end
541-
542-
function add_vertex_attribute!(mesh::Mesh, val::FaceView, name::Symbol)
543-
@boundscheck verify(faces(mesh), val)
544-
mesh.vertex_attributes[name] = val
545-
return mesh
546-
end
547-
548539
"""
549540
Mesh(faces[; views, attributes...])
550541
Mesh(positions, faces[; views])
@@ -568,14 +559,13 @@ sub-meshes. This is done by providing ranges for indexing faces which correspond
568559
to the sub-meshes. By default this is left empty.
569560
"""
570561
function Mesh(faces::AbstractVector{<:AbstractFace}; views::Vector{UnitRange{Int}} = UnitRange{Int}[], attributes...)
571-
return Mesh(Dict{Symbol, VertexAttributeType}(attributes), faces, views)
562+
return Mesh(NamedTuple(attributes), faces, views)
572563
end
573564

574565
function Mesh(points::AbstractVector{Point{Dim, T}},
575566
faces::AbstractVector{<:AbstractFace};
576567
views = UnitRange{Int}[], kwargs...) where {Dim, T}
577-
va = Dict{Symbol, VertexAttributeType}(kwargs)
578-
va[:position] = points
568+
va = (position = points, kwargs...)
579569
return Mesh(va, faces, views)
580570
end
581571

@@ -585,9 +575,10 @@ function Mesh(points::AbstractVector{<:Point}, faces::AbstractVector{<:Integer},
585575
end
586576

587577
function Mesh(; kwargs...)
588-
va = Dict{Symbol, VertexAttributeType}(kwargs)
589-
fs = faces(va[:position]::FaceView)
590-
va[:position] = values(va[:position])
578+
fs = faces(kwargs[:position]::FaceView)
579+
va = NamedTuple{keys(kwargs)}(map(keys(kwargs)) do k
580+
return k == :position ? values(kwargs[k]) : kwargs[k]
581+
end)
591582
return Mesh(va, fs)
592583
end
593584

src/meshes.jl

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,10 @@ function mesh(
5656

5757
fs = decompose(facetype, faces)
5858

59-
va = Dict{Symbol, VertexAttributeType}()
60-
for k in keys(vertex_attributes)
61-
if !isnothing(vertex_attributes[k])
62-
va[k] = convert_facetype(facetype, vertex_attributes[k])
63-
end
64-
end
59+
names = keys(vertex_attributes)
60+
valid_names = filter(name -> !isnothing(vertex_attributes[name]), names)
61+
vals = convert_facetype.(Ref(facetype), getindex.(Ref(vertex_attributes), valid_names))
62+
va = NamedTuple{valid_names}(vals)
6563

6664
return Mesh(positions, fs; va...)
6765
end
@@ -80,22 +78,22 @@ function mesh(
8078
attributes...
8179
) where {D, T, FT <: AbstractFace}
8280

83-
va = Dict{Symbol, VertexAttributeType}()
84-
for k in keys(attributes)
85-
isnothing(attributes[k]) || setindex!(va, attributes[k], k)
86-
end
81+
names = keys(attributes)
82+
valid_names = filter(name -> !isnothing(attributes[name]), names)
8783

88-
if isempty(va) && (GeometryBasics.pointtype(mesh) == pointtype) && (FT == facetype)
84+
if isempty(valid_names) && (GeometryBasics.pointtype(mesh) == pointtype) && (FT == facetype)
8985
return mesh
9086
else
91-
# 1. add vertex attributes, 2. convert position attribute
87+
vals = getindex.(Ref(attributes), valid_names)
88+
va = NamedTuple{valid_names}(vals)
89+
90+
# add vertex attributes
9291
va = merge(vertex_attributes(mesh), va)
93-
va[:position] = decompose(pointtype, va[:position])
94-
95-
# Resolve facetype conversions of FaceViews
96-
for (k, v) in va
97-
va[k] = convert_facetype(facetype, v)
98-
end
92+
# convert position attribute and facetypes in FaceViews
93+
va = NamedTuple{keys(va)}(map(keys(va)) do name
94+
val = name == :position ? decompose(pointtype, va[:position]) : va[name]
95+
return convert_facetype(facetype, val)
96+
end)
9997

10098
# update main face type
10199
f, views = decompose(facetype, faces(mesh), mesh.views)
@@ -275,18 +273,18 @@ function Base.merge(meshes::AbstractVector{<:Mesh})
275273
if consistent_face_views
276274

277275
# All the same kind of face, can just merge
278-
new_attribs = Dict{Symbol, VertexAttributeType}(map(collect(names)) do name
279-
return name => reduce(vcat, getproperty.(meshes, name))
276+
new_attribs = NamedTuple{names}(map(names) do name
277+
return reduce(vcat, getproperty.(meshes, name))
280278
end)
281279
fs = reduce(vcat, faces.(meshes))
282280

283281
# TODO: is the type difference in offset bad?
284282
idx = length(faces(m1))
285-
offset = length(coordinates(m1))::Int
283+
offset = length(coordinates(m1))::Int # TODO: unnecessary
286284
views = isempty(m1.views) ? UnitRange{Int64}[1:idx] : copy(m1.views)
287285

288286
Ns = length.(faces.(meshes))
289-
Ms = length.(coordinates.(meshes))::Vector{Int}
287+
Ms = length.(coordinates.(meshes))::Vector{Int} # TODO: unnecessary
290288
for (mesh, N, M) in Iterators.drop(zip(meshes, Ns, Ms), 1)
291289
# update face indices
292290
for i = idx .+ (1:N)
@@ -330,11 +328,11 @@ function clear_faceviews(mesh::Mesh)
330328
main_fs = faces(mesh)
331329
va = vertex_attributes(mesh)
332330

333-
names = filter(name -> va[name] isa FaceView, collect(keys(va)))
331+
names = filter(name -> va[name] isa FaceView, keys(va))
334332
isempty(names) && return mesh
335333

336334
other_fs = faces.(getproperty.((mesh,), names))
337-
pushfirst!(names, :position)
335+
names = (:position, names...)
338336
all_fs = tuple(main_fs, other_fs...)
339337

340338
if isempty(mesh.views)
@@ -343,21 +341,19 @@ function clear_faceviews(mesh::Mesh)
343341

344342
named_maps = NamedTuple{tuple(names...)}(maps)
345343

346-
new_va = Dict{Symbol, VertexAttributeType}()
347-
for (name, attrib) in va
348-
new_va[name] = values(attrib)[get(named_maps, name, maps[1])]
349-
end
344+
new_va = NamedTuple{keys(va)}(map(keys(va)) do name
345+
values(va[name])[get(named_maps, name, maps[1])]
346+
end)
350347

351348
return Mesh(new_va, new_fs)
352349

353350
else
354351

355352
new_fs = sizehint!(eltype(main_fs)[], length(main_fs))
356353
new_views = sizehint!(UnitRange{Int}[], length(mesh.views))
357-
new_va = Dict{Symbol, VertexAttributeType}()
358-
for (name, attrib) in va
359-
new_va[name] = sizehint!(eltype(values(attrib))[], length(attrib))
360-
end
354+
new_va = NamedTuple{keys(va)}(map(keys(va)) do name
355+
sizehint!(similar(values(va[name]), 0), length(va[name]))
356+
end)
361357

362358
vertex_index_counter = eltype(first(main_fs))(1)
363359

@@ -448,16 +444,17 @@ This also removes unused vertices.
448444
function split_mesh(mesh::Mesh, views::Vector{UnitRange{Int}} = mesh.views)
449445
return map(views) do idxs
450446
new_fs, maps = merge_vertex_indices((view(faces(mesh), idxs),))
451-
new_va = Dict{Symbol, VertexAttributeType}()
452-
453-
for (k, v) in vertex_attributes(mesh)
447+
448+
names = keys(vertex_attributes(mesh))
449+
new_va = NamedTuple{names}(map(names) do name
450+
v = getproperty(mesh, name)
454451
if v isa FaceView
455452
_fs, _maps = merge_vertex_indices((view(faces(v), idxs),))
456-
new_va[k] = FaceView(values(v)[_maps[1]], _fs)
453+
return FaceView(values(v)[_maps[1]], _fs)
457454
else
458-
new_va[k] = v[maps[1]]
455+
return v[maps[1]]
459456
end
460-
end
457+
end)
461458

462459
return Mesh(new_va, new_fs)
463460
end

test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ end
4545
@test mesh.position === points
4646
@test GeometryBasics.faces(mesh) === tfaces
4747
# TODO: Is the order variable with Dict?
48-
@test propertynames(mesh) == (:vertex_attributes, :faces, :views, :normal, :position, :stress)
48+
@test propertynames(mesh) == (:vertex_attributes, :faces, :views, :position, :normal, :stress)
4949
end
5050
end
5151

0 commit comments

Comments
 (0)