diff --git a/gn/core.gni b/gn/core.gni index 21c302b186..3f156daef9 100644 --- a/gn/core.gni +++ b/gn/core.gni @@ -405,6 +405,7 @@ skia_core_sources = [ "$_src/core/SkYUVPlanesCache.h", "$_src/core/SkYUVASizeInfo.cpp", "$_src/core/SkYUVMath.cpp", + "$_src/core/SkZip.h", "$_src/image/SkImage.cpp", diff --git a/src/core/SkSpan.h b/src/core/SkSpan.h index 5c8299c3b7..8db1866c79 100644 --- a/src/core/SkSpan.h +++ b/src/core/SkSpan.h @@ -9,9 +9,6 @@ #define SkSpan_DEFINED #include -#include -#include - #include "include/private/SkTo.h" template diff --git a/src/core/SkZip.h b/src/core/SkZip.h new file mode 100644 index 0000000000..e65dbb5862 --- /dev/null +++ b/src/core/SkZip.h @@ -0,0 +1,79 @@ +/* + * Copyright 2019 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkZip_DEFINED +#define SkZip_DEFINED + +#include +#include +#include + +#include "include/private/SkTLogic.h" +#include "include/private/SkTemplates.h" +#include "include/private/SkTo.h" +#include "src/core/SkSpan.h" + +// Take a list of things that can be pointers, and use them all in parallel. The iterators and +// accessor operator[] for the class produce a tuple of the items. +template +class SkZip { + using ReturnTuple = std::tuple; + + class Iterator { + public: + Iterator(const SkZip* zip, size_t index) : fZip{zip}, fIndex{index} { } + Iterator(const Iterator& that) : Iterator{ that.fZip, that.fIndex } { } + Iterator& operator++() { ++fIndex; return *this; } + Iterator operator++(int) { Iterator tmp(*this); operator++(); return tmp; } + bool operator==(const Iterator& rhs) const { return fIndex == rhs.fIndex; } + bool operator!=(const Iterator& rhs) const { return fIndex != rhs.fIndex; } + ReturnTuple operator*() { return (*fZip)[fIndex]; } + + private: + const SkZip* const fZip = nullptr; + size_t fIndex = 0; + }; + +public: + SkZip(size_t) = delete; + explicit SkZip(size_t size, Ts*... ts) + : fPointers{ts...} + , fSize{size} {} + + template + SkZip(const SkZip& that) + : fPointers{that.fPointers} + , fSize{that.fSize} {} + + ReturnTuple operator[](size_t i) const { + return this->index(i); + } + + size_t size() const { return fSize; } + bool empty() const { return this->size() == 0; } + ReturnTuple front() const { return this->index(0); } + ReturnTuple back() const { return this->index(this->size() - 1); } + Iterator begin() const { return Iterator{this, 0}; } + Iterator end() const { return Iterator{this, this->size()}; } + template auto get() const { return SkMakeSpan(std::get(fPointers), fSize); } + +private: + ReturnTuple index(size_t i) const { + SkASSERT(this->size() > 0); + SkASSERT(i < this->size()); + return indexDetail(i, skstd::make_index_sequence{}); + } + + template + ReturnTuple indexDetail(size_t i, skstd::index_sequence) const { + return ReturnTuple((std::get(fPointers))[i]...); + } + + std::tuple fPointers; + size_t fSize; +}; +#endif //SkZip_DEFINED diff --git a/tests/UtilsTest.cpp b/tests/UtilsTest.cpp index 5febaca876..c2b57c42ea 100644 --- a/tests/UtilsTest.cpp +++ b/tests/UtilsTest.cpp @@ -10,9 +10,12 @@ #include "src/core/SkSpan.h" #include "src/core/SkTSearch.h" #include "src/core/SkTSort.h" +#include "src/core/SkZip.h" #include "tests/Test.h" #include +#include +#include #include class RefClass : public SkRefCnt { @@ -211,3 +214,108 @@ DEF_TEST(SkMakeSpan, reporter) { REPORTER_ASSERT(reporter, s[3] == 100); } } + +DEF_TEST(SkZip, reporter) { + uint16_t A[] = {1, 2, 3, 4}; + const float B[] = {10.f, 20.f, 30.f, 40.f}; + std::vector C = {{20, 30, 40, 50}}; + std::array D = {{100, 200, 300, 400}}; + SkSpan S = SkMakeSpan(C); + + // Check SkZip calls + SkZip + z{4, &A[0], &B[0], C.data(), D.data(), S.data()}; + + REPORTER_ASSERT(reporter, z.size() == 4); + REPORTER_ASSERT(reporter, !z.empty()); + + { + // Check front + auto t = z.front(); + REPORTER_ASSERT(reporter, std::get<0>(t) == 1); + REPORTER_ASSERT(reporter, std::get<1>(t) == 10.f); + REPORTER_ASSERT(reporter, std::get<2>(t) == 20); + REPORTER_ASSERT(reporter, std::get<3>(t) == 100); + REPORTER_ASSERT(reporter, std::get<4>(t) == 20); + } + + { + // Check back + auto t = z.back(); + REPORTER_ASSERT(reporter, std::get<0>(t) == 4); + REPORTER_ASSERT(reporter, std::get<1>(t) == 40.f); + } + + { + // Check ranged-for + int i = 0; + for (auto t : z) { + uint16_t a; float b; int c; int d; int s; + std::tie(a, b, c, d, s) = t; + REPORTER_ASSERT(reporter, a == A[i]); + REPORTER_ASSERT(reporter, b == B[i]); + REPORTER_ASSERT(reporter, c == C[i]); + REPORTER_ASSERT(reporter, d == D[i]); + REPORTER_ASSERT(reporter, s == S[i]); + + i++; + } + REPORTER_ASSERT(reporter, i = 4); + } + + // Check copy. + auto zz{z}; + { + int i = 0; + for (auto t : zz) { + uint16_t a; float b; int c; int d; int s; + std::tie(a, b, c, d, s) = t; + REPORTER_ASSERT(reporter, a == A[i]); + REPORTER_ASSERT(reporter, b == B[i]); + REPORTER_ASSERT(reporter, c == C[i]); + REPORTER_ASSERT(reporter, d == D[i]); + REPORTER_ASSERT(reporter, s == S[i]); + + i++; + } + REPORTER_ASSERT(reporter, i = 4); + } + + // Check index getter + { + auto span = z.get<1>(); + REPORTER_ASSERT(reporter, span[1] == 20.f); + } + + // The following mutates the data. + { + // Check indexing + auto t = z[1]; + REPORTER_ASSERT(reporter, std::get<0>(t) == 2); + REPORTER_ASSERT(reporter, std::get<1>(t) == 20.f); + REPORTER_ASSERT(reporter, std::get<2>(t) == 30); + REPORTER_ASSERT(reporter, std::get<3>(t) == 200); + REPORTER_ASSERT(reporter, std::get<4>(t) == 30); + + // Check correct refs returned. + REPORTER_ASSERT(reporter, &std::get<0>(t) == &A[1]); + REPORTER_ASSERT(reporter, &std::get<1>(t) == &B[1]); + REPORTER_ASSERT(reporter, &std::get<2>(t) == &C[1]); + REPORTER_ASSERT(reporter, &std::get<3>(t) == &D[1]); + REPORTER_ASSERT(reporter, &std::get<4>(t) == &S[1]); + + // Check assignment + std::get<0>(t) = 20; + // std::get<1>(t) = 300.f; // is const + std::get<2>(t) = 300; + std::get<3>(t) = 2000; + std::get<4>(t) = 300; + + auto t1 = z[1]; + REPORTER_ASSERT(reporter, std::get<0>(t1) == 20); + REPORTER_ASSERT(reporter, std::get<1>(t1) == 20.f); + REPORTER_ASSERT(reporter, std::get<2>(t1) == 300); + REPORTER_ASSERT(reporter, std::get<3>(t1) == 2000); + REPORTER_ASSERT(reporter, std::get<4>(t1) == 300); + } +}