Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

5chmidti
Copy link
Contributor

@5chmidti 5chmidti commented Jun 6, 2020

Alright this one is a bit bigger, maybe too big :) so I know it will take time to go through the changes. I've been sitting on this since april and rebased the commits recently so here we go.

@kriegsman looking at the commits this is your code.

This started as an idea that the palettes are very similar/the same in their implementation and that it would be better to make them have a base class to avoid code duplication and slight differences between them. The CRGBPalettes have the CRGBPalette to inherit from, so they inherit the things from TColorPalette as well as constructors for CHSV colors and palettes.

Do note that there are no virtual functions/operators/constructors. The inheritance makes it look look like we call multiple constructors when we use the ones from TColorPalette. That is true when compiling with -O0 (i.e. no optimization), but even -Os optimizes this away (should be default for embedded ide's).

proof of concept for no hit in speed (orange means warnings, this is because -std=c++11 or -std=gnu++11 is not set (all green if set), default in Arduino IDE since 1.6.6 and it was also default for me in platformio, so should be default for all relatively up to date environments) (note: only use x86 compilers, as I've used std::time in this example to make sure the constructors wont get optimized too far). You can inspect the generated code on the right, if you specify any optimization level other than -O0 there are no calls to constructors from base classes.

Basics:

Color palettes inherit from a common template class base, to make the color palette implementations more consistent and avoid code duplication. Examples of differences between palettes in the current implementation:

  • all CHSVPalettes have no copy assignment constructor and assignment for CHSV[16/32/256].
  • missing upscaling constructors in CHSVPalettes (with this easier to see them).
  • UpscalePalette functions do different things for different palettes

Changes:

  • TColorPalette<typename TColor, int size> is the base class that all palettes inherit from
  • CRGBPalette<int size> is a proxy class that acts as a base for all CRGBPalettes to allow for construction and assignment from CHSV colors and palettes
  • each palette has to define their own default constructor and copy constructors and assignments as well as a destructor
  • use references for the comparison operator of all palettes, before this, every time a palette gets compared to another a copy of one of the palettes would be made
  • const args for subscript [] operator
  • generic versions of UpscalePalette and one that Interpolates the values like in the current XPalette256 upscaling versions. Making it explicit what version is used in the upscaling constructors for the palettes, and when used on their own (for XPalette256's this could change the behavior of older code).
  • moved fill_solid implementations into header (no reason)
  • made fill_rainbow generic
  • renamed fill_gradient_RGB to fill_gradient and removed fill_gradient_HSV synonym
  • removed directionCode from CPixelViews fill_gradient_RGB (and renamed fill_gradient functions to comply with removal of _CRGB and _CHSV synonyms)
  • note: I've implemented Random red pixel fix #943 as well (please merge that pr first to give credit)

Things to discuss:

  • UpscalePalette-/Interpolate: older user code can observe a change in behavior when using the UpscalePalette function with a 256 entry palette (using the constructors of the palettes do the same thing though).
  • UpscalePalette-/Interpolate: I've decided to make the instantiated functions extern to avoid users to make either downscaling calls or plain copy calls that could be costly. The current implementation throws linkage errors upon misuse. Another way would be to allow what I've said before, and just trust the user. Then it would be possible to convert CHSVPalettes into CRGB ones, by implicit conversion of the entries/calculated colors. Undefined conversions would give a compilation error and downscaling/copy calls would just work, but a static_assert could check for size differences between src and dest. Note that fill_rainbow is not that restrictive, so maybe we should just trust the user.
  • fill_gradient: I have a commit ready that reintroduces the fill_gradient_RGB and fill_gradient_HSV synonyms via always inlined functions. I guess that would be better as to not break user code. The reason why I changed it in the first place was to allow for overload resolution to pick the correct constructor for the inherited TColorPalette constructors.

Moving forward

Definition of the CHSVPalette16 as an example
class CHSVPalette16 : public TColorPalette<CHSV,16> {
public:
    CHSVPalette16() = default; 
    using TColorPalette<CHSV,16>::TColorPalette;
    using TColorPalette<CHSV,16>::operator=;
    using TColorPalette<CHSV,16>::operator==;
    using TColorPalette<CHSV,16>::operator!=;
    using TColorPalette<CHSV,16>::operator[];
    using TColorPalette<CHSV,16>::operator CHSV *;
    using TColorPalette<CHSV,16>::entries;

    CHSVPalette16( const CHSVPalette16& rhs) : TColorPalette<CHSV,16>()
    {
        memmove8( &(entries[0]), &(rhs.entries[0]), sizeof(entries));
    }    
    CHSVPalette16& operator=( const CHSVPalette16& rhs)
    {
        memmove8( &(entries[0]), &(rhs.entries[0]), sizeof(entries));
        return *this;
    }

    ~CHSVPalette16() = default;

    CHSVPalette16( const TProgmemHSVPalette16& rhs)
    {
        for( uint8_t i = 0; i < 16; ++i) {
            CRGB xyz   =  FL_PGM_READ_DWORD_NEAR( rhs + i);
            entries[i].hue = xyz.red;
            entries[i].sat = xyz.green;
            entries[i].val = xyz.blue;
        }
    }
    CHSVPalette16& operator=( const TProgmemHSVPalette16& rhs)
    {
        for( uint8_t i = 0; i < 16; ++i) {
            CRGB xyz   =  FL_PGM_READ_DWORD_NEAR( rhs + i);
            entries[i].hue = xyz.red;
            entries[i].sat = xyz.green;
            entries[i].val = xyz.blue;
        }
        return *this;
    }
};

This could also proof useful for the future when CRGBW get's introduced (I know you guys are currently working on it), as the amount of things to define is greatly reduced. The above example (maybe without the progmem part) could be quite similar to a CRGBWPalette16. The only things left to do after defining the palette like above would be declaring the upscaling functions (way of declaration and if needed depends on discussion) and defining fill_gradient functions, as well as the ColorFromPalette functions of course (no change here).

Test

code used to test the implementation (note does not compile on current master bc of the mentioned missing constructors)
#include <FastLED.h>

#ifdef ESP32_1
#define NUM_LEDS 194
#pragma message "ESP32_1"

#elif defined ESP32_2
#define NUM_LEDS 17
#pragma message "ESP32_2"

#elif defined ESP32_3
#define NUM_LEDS 6
#pragma message "ESP32_3"

#endif

//LEDS
#define DATA_PIN 19
const int delay_amt{250};

CRGB *leds = (CRGB *)malloc(NUM_LEDS * sizeof(CRGB));
int brightness = 255;

template <typename TPalette, typename TColor, int size>
void testCopySemantics()
{
  printf("\t\ttesting copy semantics\n");
  TPalette defaultConstr;
  TPalette copyConstruction(defaultConstr);
  TPalette copyAssignment = copyConstruction;
  copyAssignment = copyConstruction;
  assert(copyAssignment == copyConstruction);
  assert(copyAssignment == defaultConstr);
  assert(copyConstruction == defaultConstr);
}

template <typename TPalette, typename TColor>
void testMoveConstruction()
{
  printf("\t\ttesting move semantics\n");
  TPalette p(TColor(0, 0, 0),
             TColor(255, 255, 255));
  TPalette p_bckp{p};
  TPalette p_bckp_assign{p};
  TPalette p2{std::move(p)};
  TPalette p3;
  p3 = std::move(p_bckp_assign);
  assert(p_bckp == p2);
  assert(p_bckp == p3);
}

template <typename TPalette, typename TColor, int size>
void testConstructionFromColorObj()
{
  printf("\t\ttesting construction from color objects\n");
  TPalette singleColor(TColor(1, 1, 1));
  TPalette twoColors(TColor(1, 1, 1),
                     TColor(1, 1, 1));
  TPalette threeColors(TColor(1, 1, 1),
                       TColor(1, 1, 1),
                       TColor(1, 1, 1));
  TPalette fourColors(TColor(1, 1, 1),
                      TColor(1, 1, 1),
                      TColor(1, 1, 1),
                      TColor(1, 1, 1));
  assert(singleColor == twoColors);
  assert(singleColor == threeColors);
  assert(singleColor == fourColors);
}

template <typename TPalette, typename TColor, int size>
void testArrayConstruction()
{
  printf("\t\ttesting construction from color array\n");
  TPalette defaultConstr;
  TColor colorArray[size];
  for (int i{0}; i < size; ++i)
    colorArray[i] = TColor(1, 255, 255);
  TPalette constrFromArray(colorArray);
  TPalette copyAssignmentFromArray;
  copyAssignmentFromArray = colorArray;

  assert(constrFromArray == copyAssignmentFromArray);
  assert(defaultConstr != constrFromArray);
}

void testUpscalingConstructors()
{
  CRGBPalette16 p1{CRGB{1, 1, 1}, CRGB{255, 255, 255}};
  CRGBPalette32 p2{p1};
  CRGBPalette256 p3{p1};
  CRGBPalette256 p8{p2};

  for (auto i{0}; i < 16; ++i)
  {
    assert((CRGB)p1[i] == (CRGB)p2[2 * i]);
    assert((CRGB)p1[i] == (CRGB)p3[16 * i]);
  }

  for (auto i{0}; i < 32; ++i)
  {
    assert(p2[i] == p8[8 * i]);
  }

  CHSVPalette16 p4(CHSV{1, 1, 1}, CHSV{255, 255, 255});
  CHSVPalette32 p5(p4);
  CHSVPalette256 p6(p4);
  CHSVPalette256 p7(p5);

  for (auto i{0}; i < 16; ++i)
  {
    assert(p4[i] == p5[2 * i]);
    assert(p4[i] == p6[16 * i]);
  }

  for (auto i{0}; i < 32; ++i)
  {
    assert(p5[i] == p7[8 * i]);
  }
}

template <typename TPalette, typename TColor, int size>
void testBasePaletteFunctions()
{
  TPalette defaultConstr;
  TPalette defaultConstr2{};
  TPalette constrFrom16(TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1),
                        TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1),
                        TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1),
                        TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1), TColor(1, 1, 1));
  for (int i{0}; i < size; ++i)
  {
    leds[0] = constrFrom16[i];
    FastLED.show();
  }

  testCopySemantics<TPalette, TColor, size>();
  testMoveConstruction<TPalette, TColor>();
  testArrayConstruction<TPalette, TColor, size>();
  testConstructionFromColorObj<TPalette, TColor, size>();
}

template <typename TPalette, int size>
void testCRGBPalettes()
{
  printf("\ttesting construction from CRGB objects\n");
  testBasePaletteFunctions<TPalette, CRGB, size>();
  printf("\ttesting construction from CHSV objects\n");
  testArrayConstruction<TPalette, CHSV, size>();
  testConstructionFromColorObj<TPalette, CHSV, size>();
}

template <typename TPalette, int size>
void testCHSVPalettes()
{
  testBasePaletteFunctions<TPalette, CHSV, size>();
}

template <typename TTargetColor, typename TSourceColor>
void test_fill_solid()
{
  TTargetColor l[NUM_LEDS];
  fill_solid(l, NUM_LEDS, TSourceColor(128, 255, 255));
}

template <typename TSourceColor>
void test_fill_solid()
{
  fill_solid(leds, NUM_LEDS, TSourceColor(128, 255, 255));
  FastLED.show();
  delay(delay_amt);
}

template <typename TColor>
void test_fill_rainbow()
{
  TColor l[NUM_LEDS];
  fill_rainbow(l, NUM_LEDS, 12, 255 / NUM_LEDS);
}

template <>
void test_fill_rainbow<CRGB>()
{
  fill_rainbow(leds, NUM_LEDS, 12, 255 / NUM_LEDS);
  FastLED.show();
  delay(delay_amt);
}

template <typename TTargetColor, typename TSourceColor>
void test_fill_gradient()
{
  TTargetColor l[NUM_LEDS];
  fill_gradient(l, 0, TSourceColor(32, 255, 255), NUM_LEDS - 1, TSourceColor(128, 255, 64));
  fill_gradient(l, NUM_LEDS, TSourceColor(32, 255, 255), TSourceColor(128, 255, 64));
  fill_gradient(l, NUM_LEDS, TSourceColor(32, 255, 255), TSourceColor(128, 255, 64), TSourceColor(0, 255, 255));
  fill_gradient(l, NUM_LEDS, TSourceColor(32, 255, 255), TSourceColor(128, 255, 64), TSourceColor(0, 255, 255), TSourceColor(13, 25, 128));
}

template <typename TSourceColor>
void test_fill_gradient()
{
  TSourceColor a(32, 255, 255);
  TSourceColor b(128, 255, 64);
  TSourceColor c(0, 255, 255);
  TSourceColor d(13, 25, 128);
  fill_gradient(leds, 0, a,
                NUM_LEDS - 1, b);
  FastLED.show();
  delay(delay_amt);
  fill_gradient(leds, NUM_LEDS, a, b);
  FastLED.show();
  delay(delay_amt);
  fill_gradient(leds, NUM_LEDS, a, b, c);
  FastLED.show();
  delay(delay_amt);
  fill_gradient(leds, NUM_LEDS, a, b, c, d);
  FastLED.show();
  delay(delay_amt);
}

template <typename TPalette>
void projectPalette(CRGB *leds, const int num_leds, TPalette &p)
{
  for (int i{0}; i < num_leds; ++i)
    leds[i] = ColorFromPalette(p, (i * 255) / num_leds);
}

template <typename TCRGBPalette, typename TCHSVPalette>
void testCrossConstructionCRGB_part1()
{
  printf("\t\tTCHSVPalette p{CHSV(123, 255, 255)}\n");
  TCHSVPalette p{CHSV(123, 255, 255)};
  projectPalette(leds, NUM_LEDS, p);
  FastLED.show();
  delay(delay_amt);

  printf("\t\tTCRGBPalette p1{p}\n");
  TCRGBPalette p1{p};
  projectPalette(leds, NUM_LEDS, p1);
  FastLED.show();
  delay(delay_amt);
  printf("\t\tTCRGBPalette p2 = p\n");
  TCRGBPalette p2 = p;
  projectPalette(leds, NUM_LEDS, p2);
  FastLED.show();
  delay(delay_amt);
  assert(p1 == p2);
}

template <typename TCRGBPalette, typename TCHSVPalette>
void testCrossConstructionCRGB_part2()
{
  TCHSVPalette p;
  constexpr int size{sizeof(p.entries) / sizeof(p.entries[0])};
  CHSV a[size];
  for (int i{0}; i < size; ++i)
    a[i] = CHSV(static_cast<uint8_t>(i), 255, 255);

  printf("\t\tTCRGBPalette p3{a}\n");
  TCRGBPalette p3{a};
  projectPalette(leds, NUM_LEDS, p3);
  FastLED.show();
  delay(delay_amt);
  printf("\t\tTCRGBPalette p4 = a\n");
  TCRGBPalette p4 = a;
  projectPalette(leds, NUM_LEDS, p4);
  FastLED.show();
  delay(delay_amt);
  assert(p3 == p4);
}

template <typename TCRGBPalette, typename TCHSVPalette>
void testCrossConstructionCRGB_part3()
{
  printf("\t\tTCRGBPalette p5{CHSV(64, 200, 255)}\n");
  TCRGBPalette p5{CHSV(0, 200, 255)};
  projectPalette(leds, NUM_LEDS, p5);
  FastLED.show();
  delay(delay_amt);
  printf("\t\tTCRGBPalette p6{CHSV(64, 200, 255), CHSV(12, 255, 255)}\n");
  TCRGBPalette p6{CHSV(64, 200, 255), CHSV(12, 255, 255)};
  projectPalette(leds, NUM_LEDS, p6);
  FastLED.show();
  delay(delay_amt);
  printf("\t\tTCRGBPalette p7{CHSV(64, 200, 255), CHSV(12, 255, 255), CHSV(240, 255, 200)}\n");
  TCRGBPalette p7{CHSV(64, 200, 255), CHSV(12, 255, 255), CHSV(240, 255, 200)};
  projectPalette(leds, NUM_LEDS, p7);
  FastLED.show();
  delay(delay_amt);
  printf("\t\tTCRGBPalette p8{CHSV(64, 200, 255), CHSV(12, 255, 255), CHSV(240, 255, 200), CHSV(240, 255, 200)}\n");
  TCRGBPalette p8{CHSV(64, 200, 255), CHSV(12, 255, 255), CHSV(240, 255, 200), CHSV(240, 255, 200)};
  projectPalette(leds, NUM_LEDS, p8);
  FastLED.show();
  delay(delay_amt);
}

template <typename TCRGBPalette, typename TCHSVPalette>
void testCrossConstructionCRGB()
{
  testCrossConstructionCRGB_part1<TCRGBPalette, TCHSVPalette>();
  testCrossConstructionCRGB_part2<TCRGBPalette, TCHSVPalette>();
  testCrossConstructionCRGB_part3<TCRGBPalette, TCHSVPalette>();
}

void setup()
{
  Serial.begin(115200);
  delay(delay_amt);
  pinMode(BUILTIN_LED, OUTPUT);
  digitalWrite(BUILTIN_LED, HIGH);

  // FastLED
  FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(brightness);
  FastLED.setCorrection(TypicalLEDStrip);
  fadeToBlackBy(leds, NUM_LEDS, 255);

  printf("testing CRGBPalette16\n");
  testCRGBPalettes<CRGBPalette16, 16>();
  printf("testing CRGBPalette32\n");
  testCRGBPalettes<CRGBPalette32, 32>();
  printf("testing CRGBPalette256\n");
  testCRGBPalettes<CRGBPalette256, 256>();

  printf("testing CHSVPalette16\n");
  testCHSVPalettes<CHSVPalette16, 16>();
  printf("testing CHSVPalette32\n");
  testCHSVPalettes<CHSVPalette32, 32>();
  printf("testing CHSVPalette256\n");
  testCHSVPalettes<CHSVPalette256, 256>();

  printf("testing UpscalingConstructors\n");
  testUpscalingConstructors();

  printf("testing fill_solid<CRGB, CRGB>\n");
  test_fill_solid<CRGB>();
  printf("testing fill_solid<CRGB, CHSV>\n");
  test_fill_solid<CHSV>();
  printf("testing fill_solid<CHSV, CHSV>\n");
  test_fill_solid<CHSV, CHSV>();

  fadeToBlackBy(leds, NUM_LEDS, 255);

  printf("testing fill_rainbow<CRGB>\n");
  test_fill_rainbow<CRGB>();
  printf("testing fill_rainbow<CHSV>\n");
  test_fill_rainbow<CHSV>();

  fadeToBlackBy(leds, NUM_LEDS, 255);

  printf("testing fill_gradient<CRGB,CRGB>\n");
  test_fill_gradient<CRGB>();
  printf("testing fill_gradient<CRGB,CHSV>\n");
  test_fill_gradient<CHSV>();
  printf("testing fill_gradient<CHSV,CHSV>\n");
  test_fill_gradient<CHSV, CHSV>();

  fadeToBlackBy(leds, NUM_LEDS, 255);

  CRGBPalette16 p1(CRGB(255, 255, 0));
  CRGBPalette16 p2(CHSV(255, 255, 0));
  CHSVPalette16 p3(CHSV(255, 255, 0));
  // CHSVPalette16 p4(CRGB(255, 255, 0)); // error: no matching function for call to 'CHSVPalette16::CHSVPalette16(CRGB)
  // as there is no construction from CRGB to CHSV
  CHSV chsv;
  CRGB crgb;
  CRGBPalette256 p123;
  // UpscalePalette(p123, p1);   // linkage error: undefined reference
  // fill_solid(&chsv, 1, crgb); // error: no matching function for call to 'fill_solid(CHSV*, int, CRGB&)
  fill_solid(&chsv, 1, chsv);
  fill_solid(&crgb, 1, crgb);
  fill_solid(&crgb, 1, chsv);

  printf("testing CrossConstructionCRGB<CRGBPalette16, CHSVPalette16>\n");
  testCrossConstructionCRGB<CRGBPalette16, CHSVPalette16>();
  printf("testing CrossConstructionCRGB<CRGBPalette32, CHSVPalette32>\n");
  testCrossConstructionCRGB<CRGBPalette32, CHSVPalette32>();
  printf("testing CrossConstructionCRGB<CRGBPalette256, CHSVPalette256>\n");
  testCrossConstructionCRGB<CRGBPalette256, CHSVPalette256>();

  printf("tests were successful\n");

  digitalWrite(BUILTIN_LED, LOW);
}

void loop()
{
}

I've tested a lot of parts in compiler explorer using gcc 4.8.1. I've also compared compiled assembly (esp32) (regex to remove all code addresses, bc those would change), of an example (only tested one palette, but only got like 4 changes or so in multiple of tens of thousands of lines).

Note: the diff in github is quite messy, I suggest opening up the branch in another window and comparing with master side by side as well as the diff.

...

Too far? ;D

@samguyer
Copy link
Contributor

Hello @5chmidti ! Thank you for your contribution to FastLED -- we really appreciate all the thought and hard work that goes into a change like this one. A few questions for you:

First, can you check on the speed on compilation? Sometimes template-izing part of the code makes compile time slower. Ideally, if you can compare compile times on a relatively slow computer, that would tell us a lot.

Second, can you look at the resulting code size? I would not expect this number to be different, but you never know until you look.

Finally, while we certainly appreciate the value of code cleanup (and making the interfaces more uniform), what do you see as the big advantages of this approach over, say, adding the missing functionality to the existing code?

Thank you!

@5chmidti
Copy link
Contributor Author

Hi,

here some measurements.

Compile time (measured compile time three times each) (RPi 3B, platformio cli, esp32 arduino framework):

template palette:
real	1m37.234s   1m38.425s   1m26.929s
user	3m24.446s   3m24.316s   3m24.673s
sys	0m20.710s   0m20.278s   0m20.392s

master:
real	1m32.872s   1m26.573s	1m33.124s
user	3m23.051s   3m24.877s	3m24.071s
sys	0m20.467s   0m20.008s   0m20.189s

Binary size:

master:
210688 Bytes

template palette:
210960 Bytes

If we were add the functionality where it is missing, we have to check where it is missing, and we have to check if it is correct in every implementation. Doing it like this reduces this checking for completeness and correctness of palettes's member functions and puts it into one single common base class (for the member functions that allow it).
How the palettes are defined in master right now means a lot of code that is the same across palettes (operator==, operator[], construction from 1,2,3,4 colors, and so on). Doing it like this removes the code duplication and makes the implementations more uniform.
Adding new palettes is quite simple, as we don't have to define the almost same operators and constructors for every palette, but inherit them from one base class. We only need to define the default constructor as well as the copy constructor / assignment, if the palette does not need any extras (like progmem constructors, constructors form another color type or upscaling constructors).

Example: lets create a 64 entry CRGB palette
class CRGBPalette64 : public CRGBPalette<64>
{
public:
    CRGBPalette64() = default; 
    using CRGBPalette<64>::CRGBPalette;
    using CRGBPalette<64>::operator=;
    using CRGBPalette<64>::operator==;
    using CRGBPalette<64>::operator!=;
    using CRGBPalette<64>::operator[];
    using CRGBPalette<64>::operator CRGB *;
    using CRGBPalette<64>::entries;

    CRGBPalette64( const CRGBPalette64& rhs) : CRGBPalette<64>()
    {
        memmove8( &(entries[0]), &(rhs.entries[0]), sizeof(entries));
    }    
    CRGBPalette64& operator=( const CRGBPalette64& rhs)
    {
        memmove8( &(entries[0]), &(rhs.entries[0]), sizeof(entries));
        return *this;
    }

    ~CRGBPalette64() = default;
}

and all that is missing is for this to work is the ColorFromPalette function for this palette.
(without upscaling or progmem constructors, but inherited constructors from CHSV via CRGBPalette<> )

@5chmidti 5chmidti closed this Nov 18, 2020
@5chmidti 5chmidti deleted the templatePalettes branch November 18, 2020 13:17
@5chmidti 5chmidti restored the templatePalettes branch November 18, 2020 13:17
@5chmidti 5chmidti reopened this Nov 18, 2020
@5chmidti
Copy link
Contributor Author

5chmidti commented Nov 18, 2020

accidentally deleted the branch in the fork... restored it and reopened

@zackees
Copy link
Member

zackees commented Mar 19, 2025

The regression in compile times to not bother that much.

However, the binary size increase means this is dead on arrival. I am super strict about this and our automated testing is set up to fail with the slightest increase in binary size on the avr platforms.

You might want to rebase from master and update this PR. If you do this, then there is a binary size profile that will run now so we can see where the size increase is coming from.

@zackees zackees force-pushed the master branch 10 times, most recently from fcf8a90 to 4796b69 Compare June 25, 2025 04:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants