From e9bc281eb26a60a0485a11ec2f37c48494b30b55 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Thu, 16 Nov 2023 18:22:30 +0100 Subject: [PATCH 1/4] Add swizzling and color accessors --- src/MVector.jl | 72 ++++++++++++++++++++++++++++++++++++++++--------- test/MVector.jl | 46 ++++++++++++++++++++++--------- test/SVector.jl | 29 ++++++++++++++------ 3 files changed, 113 insertions(+), 34 deletions(-) diff --git a/src/MVector.jl b/src/MVector.jl index 2819078f7..9e2940885 100644 --- a/src/MVector.jl +++ b/src/MVector.jl @@ -14,22 +14,68 @@ macro MVector(ex) end # Named field access for the first four elements, using the conventional field -# names from low-dimensional geometry (x,y,z) and computer graphics (w). -let dimension_names = QuoteNode.([:x, :y, :z, :w]) - body = :(getfield(v, name)) - for (i,dim_name) in enumerate(dimension_names) - body = :(name === $(dimension_names[i]) ? getfield(v, :data)[$i] : $body) +# names from low-dimensional geometry and computer graphics: +# - (x, y, z, w) where w is a homogenous coordinate. +# - (r, g, b, a) where a is an alpha component. +# - (xy, rg, xyz, ...) to obtain a subset of the vector via swizzling. + +@inline function _select(v::SVector, index, indices...) + isempty(indices) && return v[index] + indices = (index, indices...) + SVector(ntuple(i -> v[indices[i]], length(indices))) +end +@inline function _select(v::MVector, index, indices...) + isempty(indices) && return v[index] + indices = (index, indices...) + MVector(ntuple(i -> v[indices[i]], length(indices))) +end + +@inline function _set!(v::MVector, value, indices...) + for (i, index) in enumerate(indices) + setindex!(v, value[i], index) + end + value +end + +let dimension_names = zip((:x, :y, :z, :w), (:r, :g, :b, :a)) + getproperty_bodies = [Expr[], Expr[], Expr[], Expr[]] + setproperty_bodies = [Expr[], Expr[], Expr[], Expr[]] + for (i, (dx1, dr1)) in enumerate(dimension_names) + field1 = dx1 + field2 = dr1 + push!(getproperty_bodies[i], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i))) + push!(setproperty_bodies[i], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i))) + for (j, (dx2, dr2)) in enumerate(dimension_names) + i == j && continue + field1 = Symbol(dx1, dx2) + field2 = Symbol(dr1, dr2) + push!(getproperty_bodies[max(i, j)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i, $j))) + push!(setproperty_bodies[max(i, j)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i, $j))) + for (k, (dx3, dr3)) in enumerate(dimension_names) + (k == j || k == i) && continue + field1 = Symbol(dx1, dx2, dx3) + field2 = Symbol(dr1, dr2, dr3) + push!(getproperty_bodies[max(i, j, k)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i, $j, $k))) + push!(setproperty_bodies[max(i, j, k)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i, $j, $k))) + for (l, (dx4, dr4)) in enumerate(dimension_names) + (l == k || l == j || l == i) && continue + field1 = Symbol(dx1, dx2, dx3, dx4) + field2 = Symbol(dr1, dr2, dr3, dr4) + push!(getproperty_bodies[max(i, j, k, l)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i, $j, $k, $l))) + push!(setproperty_bodies[max(i, j, k, l)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i, $j, $k, $l))) + end + end + end + end + for i in 1:4 @eval @inline function Base.getproperty(v::Union{SVector{$i},MVector{$i}}, name::Symbol) - $body + $(foldl(append!, getproperty_bodies[1:i])...) + getfield(v, name) end - end - - body = :(setfield!(v, name, e)) - for (i,dim_name) in enumerate(dimension_names) - body = :(name === $dim_name ? @inbounds(v[$i] = e) : $body) - @eval @inline function Base.setproperty!(v::MVector{$i}, name::Symbol, e) - $body + @eval @inline function Base.setproperty!(v::MVector{$i}, name::Symbol, value) + $(foldl(append!, setproperty_bodies[1:i])...) + setfield!(v, name, value) end end end diff --git a/test/MVector.jl b/test/MVector.jl index 4c270f8d3..27dc8e4eb 100644 --- a/test/MVector.jl +++ b/test/MVector.jl @@ -105,30 +105,50 @@ @testset "Named field access - getproperty/setproperty!" begin # getproperty v4 = @MVector [10,20,30,40] - @test v4.x == 10 - @test v4.y == 20 - @test v4.z == 30 - @test v4.w == 40 + @test v4.x == v4.r == 10 + @test v4.y == v4.g == 20 + @test v4.z == v4.b == 30 + @test v4.w == v4.a == 40 + @test v4.xy == v4.rg == @MVector [v4.x, v4.y] + @test v4.wzx == v4.abr == @MVector [v4.w, v4.z, v4.x] + @test v4.xyzw == v4.rgba == v4 + @test_throws ErrorException v4.xyx + @test_throws ErrorException v4.xgb v2 = @MVector [10,20] - @test v2.x == 10 - @test v2.y == 20 + @test v2.x == v2.r == 10 + @test v2.y == v2.g == 20 @test_throws ErrorException v2.z @test_throws ErrorException v2.w + @test_throws ErrorException v2.b + @test_throws ErrorException v2.a + @test v2.xy == v2.rg == v2 + @test v2.yx == v2.gr == @MVector [v2.y, v2.x] + @test_throws ErrorException v2.ba + @test_throws ErrorException v2.rgb + @test_throws ErrorException v2.rgba # setproperty! - @test (v4.x = 100) == 100 - @test (v4.y = 200) == 200 - @test (v4.z = 300) == 300 - @test (v4.w = 400) == 400 + @test (v4.x = 100) == (v4.r = 100) == 100 + @test (v4.y = 200) == (v4.g = 200) == 200 + @test (v4.z = 300) == (v4.b = 300) == 300 + @test (v4.w = 400) == (v4.a = 400) == 400 @test v4[1] == 100 @test v4[2] == 200 @test v4[3] == 300 @test v4[4] == 400 - - @test (v2.x = 100) == 100 - @test (v2.y = 200) == 200 + @test (v4.xy = (1000, 2000)) == (v4.rg = (1000, 2000)) == (1000, 2000) + @test v4[1] == 1000 + @test v4[2] == 2000 + @test (v4.xywz = (10, 20, 40, 30)) == (v4.rgab = (10, 20, 40, 30)) == (10, 20, 40, 30) + @test v4 == @MVector [10, 20, 30, 40] + @test_throws ErrorException (v2.xyx = (100, 200, 100)) + + @test (v2.x = 100) == (v2.r = 100) == 100 + @test (v2.y = 200) == (v2.g = 200) == 200 @test_throws ErrorException (v2.z = 200) @test_throws ErrorException (v2.w = 200) + @test_throws ErrorException (v2.xz = (100, 300)) + @test_throws ErrorException (v2.wx = (400, 100)) end end diff --git a/test/SVector.jl b/test/SVector.jl index d9231e9a1..539d23e72 100644 --- a/test/SVector.jl +++ b/test/SVector.jl @@ -112,16 +112,29 @@ end @testset "Named field access - getproperty" begin - v4 = SA[10,20,30,40] - @test v4.x == 10 - @test v4.y == 20 - @test v4.z == 30 - @test v4.w == 40 - v2 = SA[10,20] - @test v2.x == 10 - @test v2.y == 20 + v4 = @SVector [10,20,30,40] + @test v4.x == v4.r == 10 + @test v4.y == v4.g == 20 + @test v4.z == v4.b == 30 + @test v4.w == v4.a == 40 + @test v4.xy === v4.rg === @SVector [v4.x, v4.y] + @test v4.wzx === v4.abr === @SVector [v4.w, v4.z, v4.x] + @test v4.xyzw == v4.rgba == v4 + @test_throws ErrorException v4.xyx + @test_throws ErrorException v4.xgb + + v2 = @SVector [10,20] + @test v2.x == v2.r == 10 + @test v2.y == v2.g == 20 @test_throws ErrorException v2.z @test_throws ErrorException v2.w + @test_throws ErrorException v2.b + @test_throws ErrorException v2.a + @test v2.xy == v2.rg == v2 + @test v2.yx === v2.gr === @SVector [v2.y, v2.x] + @test_throws ErrorException v2.ba + @test_throws ErrorException v2.rgb + @test_throws ErrorException v2.rgba end @testset "issue 1042" begin From 2d5b4c088f9d0413deb7109484bfc60a38dd5533 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Thu, 16 Nov 2023 18:33:59 +0100 Subject: [PATCH 2/4] Allow repetition in getproperty swizzles --- src/MVector.jl | 25 +++++++++++-------------- test/MVector.jl | 2 +- test/SVector.jl | 2 +- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/MVector.jl b/src/MVector.jl index 9e2940885..22ea7cde2 100644 --- a/src/MVector.jl +++ b/src/MVector.jl @@ -19,18 +19,18 @@ end # - (r, g, b, a) where a is an alpha component. # - (xy, rg, xyz, ...) to obtain a subset of the vector via swizzling. -@inline function _select(v::SVector, index, indices...) +@inline function swizzle(v::SVector, index, indices...) isempty(indices) && return v[index] indices = (index, indices...) SVector(ntuple(i -> v[indices[i]], length(indices))) end -@inline function _select(v::MVector, index, indices...) +@inline function swizzle(v::MVector, index, indices...) isempty(indices) && return v[index] indices = (index, indices...) MVector(ntuple(i -> v[indices[i]], length(indices))) end -@inline function _set!(v::MVector, value, indices...) +@inline function swizzle!(v::MVector, value, indices...) for (i, index) in enumerate(indices) setindex!(v, value[i], index) end @@ -43,26 +43,23 @@ let dimension_names = zip((:x, :y, :z, :w), (:r, :g, :b, :a)) for (i, (dx1, dr1)) in enumerate(dimension_names) field1 = dx1 field2 = dr1 - push!(getproperty_bodies[i], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i))) - push!(setproperty_bodies[i], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i))) + push!(getproperty_bodies[i], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle(v, $i))) + push!(setproperty_bodies[i], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle!(v, value, $i))) for (j, (dx2, dr2)) in enumerate(dimension_names) - i == j && continue field1 = Symbol(dx1, dx2) field2 = Symbol(dr1, dr2) - push!(getproperty_bodies[max(i, j)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i, $j))) - push!(setproperty_bodies[max(i, j)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i, $j))) + push!(getproperty_bodies[max(i, j)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle(v, $i, $j))) + j ≠ i && push!(setproperty_bodies[max(i, j)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle!(v, value, $i, $j))) for (k, (dx3, dr3)) in enumerate(dimension_names) - (k == j || k == i) && continue field1 = Symbol(dx1, dx2, dx3) field2 = Symbol(dr1, dr2, dr3) - push!(getproperty_bodies[max(i, j, k)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i, $j, $k))) - push!(setproperty_bodies[max(i, j, k)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i, $j, $k))) + push!(getproperty_bodies[max(i, j, k)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle(v, $i, $j, $k))) + (k ≠ j && k ≠ i) && push!(setproperty_bodies[max(i, j, k)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle!(v, value, $i, $j, $k))) for (l, (dx4, dr4)) in enumerate(dimension_names) - (l == k || l == j || l == i) && continue field1 = Symbol(dx1, dx2, dx3, dx4) field2 = Symbol(dr1, dr2, dr3, dr4) - push!(getproperty_bodies[max(i, j, k, l)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_select(v, $i, $j, $k, $l))) - push!(setproperty_bodies[max(i, j, k, l)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return $_set!(v, value, $i, $j, $k, $l))) + push!(getproperty_bodies[max(i, j, k, l)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle(v, $i, $j, $k, $l))) + (l ≠ k && l ≠ j && l ≠ i) && push!(setproperty_bodies[max(i, j, k, l)], :((name === $(QuoteNode(field1)) || name === $(QuoteNode(field2))) && return swizzle!(v, value, $i, $j, $k, $l))) end end end diff --git a/test/MVector.jl b/test/MVector.jl index 27dc8e4eb..6a3bc7d92 100644 --- a/test/MVector.jl +++ b/test/MVector.jl @@ -111,8 +111,8 @@ @test v4.w == v4.a == 40 @test v4.xy == v4.rg == @MVector [v4.x, v4.y] @test v4.wzx == v4.abr == @MVector [v4.w, v4.z, v4.x] + @test v4.xyx == @MVector [v4.x, v4.y, v4.x] @test v4.xyzw == v4.rgba == v4 - @test_throws ErrorException v4.xyx @test_throws ErrorException v4.xgb v2 = @MVector [10,20] diff --git a/test/SVector.jl b/test/SVector.jl index 539d23e72..db95b6b35 100644 --- a/test/SVector.jl +++ b/test/SVector.jl @@ -119,8 +119,8 @@ @test v4.w == v4.a == 40 @test v4.xy === v4.rg === @SVector [v4.x, v4.y] @test v4.wzx === v4.abr === @SVector [v4.w, v4.z, v4.x] + @test v4.xyx === @SVector [v4.x, v4.y, v4.x] @test v4.xyzw == v4.rgba == v4 - @test_throws ErrorException v4.xyx @test_throws ErrorException v4.xgb v2 = @SVector [10,20] From dbd4fc2325f4861d727fa5d93dc80b884b269359 Mon Sep 17 00:00:00 2001 From: serenity4 Date: Thu, 16 Nov 2023 19:18:58 +0100 Subject: [PATCH 3/4] Remove inline annotation --- src/MVector.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MVector.jl b/src/MVector.jl index 22ea7cde2..83ccef682 100644 --- a/src/MVector.jl +++ b/src/MVector.jl @@ -65,12 +65,12 @@ let dimension_names = zip((:x, :y, :z, :w), (:r, :g, :b, :a)) end end for i in 1:4 - @eval @inline function Base.getproperty(v::Union{SVector{$i},MVector{$i}}, + @eval function Base.getproperty(v::Union{SVector{$i},MVector{$i}}, name::Symbol) $(foldl(append!, getproperty_bodies[1:i])...) getfield(v, name) end - @eval @inline function Base.setproperty!(v::MVector{$i}, name::Symbol, value) + @eval function Base.setproperty!(v::MVector{$i}, name::Symbol, value) $(foldl(append!, setproperty_bodies[1:i])...) setfield!(v, name, value) end From ce2e525e8f5cfadd97c02880716555ec923bb2ed Mon Sep 17 00:00:00 2001 From: serenity4 Date: Fri, 24 Nov 2023 18:15:31 +0100 Subject: [PATCH 4/4] Add fast path for `.data` field access --- src/MVector.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/MVector.jl b/src/MVector.jl index 83ccef682..c747606ae 100644 --- a/src/MVector.jl +++ b/src/MVector.jl @@ -67,10 +67,12 @@ let dimension_names = zip((:x, :y, :z, :w), (:r, :g, :b, :a)) for i in 1:4 @eval function Base.getproperty(v::Union{SVector{$i},MVector{$i}}, name::Symbol) + name === :data && return getfield(v, :data) $(foldl(append!, getproperty_bodies[1:i])...) getfield(v, name) end @eval function Base.setproperty!(v::MVector{$i}, name::Symbol, value) + name === :data && return setfield!(v, :data, value) $(foldl(append!, setproperty_bodies[1:i])...) setfield!(v, name, value) end