From 5540bc2c29c2e7a66856aaa3f803777cae261c0f Mon Sep 17 00:00:00 2001 From: Robert Lipe Date: Tue, 7 Apr 2026 02:36:56 -0500 Subject: [PATCH] Fix static initialization order fiasco for psram_allocator globals Wrap global static objects that use psram_allocator in Meyer's singletons (Construct-On-First-Use) to prevent PSRAM allocation before the memory subsystem is initialized during app_main(). Affected globals: - AnimatedGIFs map and g_ptrGIFDecoder (PatternAnimatedGIF.h) - weatherIcons map (PatternWeather.h) - CWebServer::mySettingSpecs (webserver.cpp/h) --- include/effects/matrix/PatternAnimatedGIF.h | 48 +++++++++++-------- include/effects/matrix/PatternWeather.h | 52 +++++++++++---------- include/webserver.h | 2 +- src/webserver.cpp | 10 ++-- 4 files changed, 64 insertions(+), 48 deletions(-) diff --git a/include/effects/matrix/PatternAnimatedGIF.h b/include/effects/matrix/PatternAnimatedGIF.h index 3cad669e0..e4ab1ea39 100644 --- a/include/effects/matrix/PatternAnimatedGIF.h +++ b/include/effects/matrix/PatternAnimatedGIF.h @@ -103,18 +103,22 @@ struct GIFInfo : public EmbeddedFile {} }; -static const std::map, psram_allocator>> AnimatedGIFs = +static const std::map, psram_allocator>>& GetAnimatedGIFs() { - // Banana has 8 frames. Most music is around 120BPM, so we need to play each frame for 1/15th of a second to somewhat align with a typical beat - { GIFIdentifier::Banana, GIFInfo(banana_start, banana_end, 32, 32, 10 ) }, // 4 KB - { GIFIdentifier::Nyancat, GIFInfo(nyancat_start, nyancat_end, 64, 32, 18 ) }, // 20 KB - { GIFIdentifier::Pacman, GIFInfo(pacman_start, pacman_end, 64, 12, 20 ) }, // 36 KB - { GIFIdentifier::Atomic, GIFInfo(atomic_start, atomic_end, 32, 32, 60 ) }, // 21 KB - { GIFIdentifier::ColorSphere, GIFInfo(colorsphere_start, colorsphere_end, 32, 32, 16 ) }, // 52 KB - { GIFIdentifier::ThreeRings, GIFInfo(threerings_start, threerings_end, 64, 32, 24 ) }, // 9 KB - { GIFIdentifier::Tesseract, GIFInfo(tesseract_start, tesseract_end, 40, 32, 40 ) }, // 24 KB - { GIFIdentifier::Firelog, GIFInfo(firelog_start, firelog_end, 64, 32, 16 ) }, // 24 KB -}; + static const std::map, psram_allocator>> AnimatedGIFs = + { + // Banana has 8 frames. Most music is around 120BPM, so we need to play each frame for 1/15th of a second to somewhat align with a typical beat + { GIFIdentifier::Banana, GIFInfo(banana_start, banana_end, 32, 32, 10 ) }, // 4 KB + { GIFIdentifier::Nyancat, GIFInfo(nyancat_start, nyancat_end, 64, 32, 18 ) }, // 20 KB + { GIFIdentifier::Pacman, GIFInfo(pacman_start, pacman_end, 64, 12, 20 ) }, // 36 KB + { GIFIdentifier::Atomic, GIFInfo(atomic_start, atomic_end, 32, 32, 60 ) }, // 21 KB + { GIFIdentifier::ColorSphere, GIFInfo(colorsphere_start, colorsphere_end, 32, 32, 16 ) }, // 52 KB + { GIFIdentifier::ThreeRings, GIFInfo(threerings_start, threerings_end, 64, 32, 24 ) }, // 9 KB + { GIFIdentifier::Tesseract, GIFInfo(tesseract_start, tesseract_end, 40, 32, 40 ) }, // 24 KB + { GIFIdentifier::Firelog, GIFInfo(firelog_start, firelog_end, 64, 32, 16 ) }, // 24 KB + }; + return AnimatedGIFs; +} // The decoder needs us to track some state, but there's only one instance of the decoder, and // we can't pass it a pointer to our state because the callback doesn't allow you to pass any @@ -140,7 +144,11 @@ g_gifDecoderState; // We dynamically allocate the GIF decoder because it's pretty big and we don't want to waste the base // ram on it. This way it, and the GIFs it decodes, can live in PSRAM. -const std::unique_ptr> g_ptrGIFDecoder = make_unique_psram>(); +static GifDecoder& GetGIFDecoder() +{ + static const std::unique_ptr> g_ptrGIFDecoder = make_unique_psram>(); + return *g_ptrGIFDecoder; +} // PatternAnimatedGIF // @@ -260,8 +268,8 @@ class PatternAnimatedGIF : public EffectWithId // Open the GIF and start decoding - auto gif = AnimatedGIFs.find(_gifIndex); - if (gif == AnimatedGIFs.end()) + auto gif = GetAnimatedGIFs().find(_gifIndex); + if (gif == GetAnimatedGIFs().end()) throw std::runtime_error(str_sprintf("Unable to locate GIF by index %d in the map.", (int) _gifIndex).c_str()); // Set up the gifDecoderState with all of the context that it will need to decode and @@ -310,12 +318,12 @@ class PatternAnimatedGIF : public EffectWithId // Set the GIF decoder callbacks to our static functions - g_ptrGIFDecoder->setScreenClearCallback( screenClearCallback ); - g_ptrGIFDecoder->setUpdateScreenCallback( updateScreenCallback ); - g_ptrGIFDecoder->setDrawPixelCallback( drawPixelCallback ); - g_ptrGIFDecoder->setDrawLineCallback( drawLineCallback ); + GetGIFDecoder().setScreenClearCallback( screenClearCallback ); + GetGIFDecoder().setUpdateScreenCallback( updateScreenCallback ); + GetGIFDecoder().setDrawPixelCallback( drawPixelCallback ); + GetGIFDecoder().setDrawLineCallback( drawLineCallback ); - _gifReadyToDraw = (ERROR_NONE == g_ptrGIFDecoder->startDecoding((uint8_t *) gif->second.contents, gif->second.length)); + _gifReadyToDraw = (ERROR_NONE == GetGIFDecoder().startDecoding((uint8_t *) gif->second.contents, gif->second.length)); if (!_gifReadyToDraw) debugW("Failed to start decoding GIF"); } @@ -341,7 +349,7 @@ class PatternAnimatedGIF : public EffectWithId g()->Clear(_bkColor); if (_gifReadyToDraw) - g_ptrGIFDecoder->decodeFrame(false); + GetGIFDecoder().decodeFrame(false); } }; diff --git a/include/effects/matrix/PatternWeather.h b/include/effects/matrix/PatternWeather.h index bc9d68ccf..80d262f56 100644 --- a/include/effects/matrix/PatternWeather.h +++ b/include/effects/matrix/PatternWeather.h @@ -100,27 +100,31 @@ extern const uint8_t thunderstorm_night_end[] asm("_binary_assets_bmp_thun static constexpr auto pszDaysOfWeek = to_array( { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" } ); -static std::map, psram_allocator>> weatherIcons = +static std::map, psram_allocator>>& GetWeatherIcons() { - { "01d", EmbeddedFile(clearsky_start, clearsky_end) }, - { "02d", EmbeddedFile(fewclouds_start, fewclouds_end) }, - { "03d", EmbeddedFile(scatteredclouds_start, scatteredclouds_end) }, - { "04d", EmbeddedFile(brokenclouds_start, brokenclouds_end) }, - { "09d", EmbeddedFile(showerrain_start, showerrain_end) }, - { "10d", EmbeddedFile(rain_start, rain_end) }, - { "11d", EmbeddedFile(thunderstorm_start, thunderstorm_end) }, - { "13d", EmbeddedFile(snow_start, snow_end) }, - { "50d", EmbeddedFile(mist_start, mist_end) }, - { "01n", EmbeddedFile(clearsky_night_start, clearsky_night_end) }, - { "02n", EmbeddedFile(fewclouds_night_start, fewclouds_night_end) }, - { "03n", EmbeddedFile(scatteredclouds_night_start, scatteredclouds_night_end) }, - { "04n", EmbeddedFile(brokenclouds_night_start, brokenclouds_night_end) }, - { "09n", EmbeddedFile(showerrain_night_start, showerrain_night_end) }, - { "10n", EmbeddedFile(rain_night_start, rain_night_end) }, - { "11n", EmbeddedFile(thunderstorm_night_start, thunderstorm_night_end) }, - { "13n", EmbeddedFile(snow_night_start, snow_night_end) }, - { "50n", EmbeddedFile(mist_night_start, mist_night_end) } -}; + static std::map, psram_allocator>> weatherIcons = + { + { "01d", EmbeddedFile(clearsky_start, clearsky_end) }, + { "02d", EmbeddedFile(fewclouds_start, fewclouds_end) }, + { "03d", EmbeddedFile(scatteredclouds_start, scatteredclouds_end) }, + { "04d", EmbeddedFile(brokenclouds_start, brokenclouds_end) }, + { "09d", EmbeddedFile(showerrain_start, showerrain_end) }, + { "10d", EmbeddedFile(rain_start, rain_end) }, + { "11d", EmbeddedFile(thunderstorm_start, thunderstorm_end) }, + { "13d", EmbeddedFile(snow_start, snow_end) }, + { "50d", EmbeddedFile(mist_start, mist_end) }, + { "01n", EmbeddedFile(clearsky_night_start, clearsky_night_end) }, + { "02n", EmbeddedFile(fewclouds_night_start, fewclouds_night_end) }, + { "03n", EmbeddedFile(scatteredclouds_night_start, scatteredclouds_night_end) }, + { "04n", EmbeddedFile(brokenclouds_night_start, brokenclouds_night_end) }, + { "09n", EmbeddedFile(showerrain_night_start, showerrain_night_end) }, + { "10n", EmbeddedFile(rain_night_start, rain_night_end) }, + { "11n", EmbeddedFile(thunderstorm_night_start, thunderstorm_night_end) }, + { "13n", EmbeddedFile(snow_night_start, snow_night_end) }, + { "50n", EmbeddedFile(mist_night_start, mist_night_end) } + }; + return weatherIcons; +} /** * @brief This class implements the Weather Data effect @@ -510,16 +514,16 @@ class PatternWeather : public EffectWithId } // Draw the graphics - auto iconEntry = weatherIcons.find(iconToday); - if (iconEntry != weatherIcons.end()) + auto iconEntry = GetWeatherIcons().find(iconToday); + if (iconEntry != GetWeatherIcons().end()) { auto icon = iconEntry->second; if (JDR_OK != TJpgDec.drawJpg(0, 10, icon.contents, icon.length)) // Draw the image debugW("Could not display icon %s", iconToday.c_str()); } - iconEntry = weatherIcons.find(iconTomorrow); - if (iconEntry != weatherIcons.end()) + iconEntry = GetWeatherIcons().find(iconTomorrow); + if (iconEntry != GetWeatherIcons().end()) { auto icon = iconEntry->second; if (JDR_OK != TJpgDec.drawJpg(xHalf+1, 10, icon.contents, icon.length)) // Draw the image diff --git a/include/webserver.h b/include/webserver.h index 1c51bc2af..9301f0fdb 100644 --- a/include/webserver.h +++ b/include/webserver.h @@ -108,7 +108,7 @@ class CWebServer } }; - static std::vector> mySettingSpecs; + static std::vector>& GetMySettingSpecs(); static std::vector> deviceSettingSpecs; static const std::map settingValidators; diff --git a/src/webserver.cpp b/src/webserver.cpp index b201b5ffe..742ecb860 100644 --- a/src/webserver.cpp +++ b/src/webserver.cpp @@ -57,7 +57,11 @@ const std::map CWebServer::settingValidators { DeviceConfig::BrightnessTag, [](const String& value) { return g_ptrSystem->GetDeviceConfig().ValidateBrightness(value); } } }; -std::vector> CWebServer::mySettingSpecs = {}; +std::vector>& CWebServer::GetMySettingSpecs() +{ + static std::vector> instance; + return instance; +} std::vector> CWebServer::deviceSettingSpecs{}; // Member function template specializations @@ -479,13 +483,13 @@ const std::vector> & CWebServer::LoadDeviceS { if (deviceSettingSpecs.empty()) { - mySettingSpecs.emplace_back( + GetMySettingSpecs().emplace_back( "effectInterval", "Effect interval", "The duration in milliseconds that an individual effect runs, before the next effect is activated.", SettingSpec::SettingType::PositiveBigInteger ); - deviceSettingSpecs.insert(deviceSettingSpecs.end(), mySettingSpecs.begin(), mySettingSpecs.end()); + deviceSettingSpecs.insert(deviceSettingSpecs.end(), GetMySettingSpecs().begin(), GetMySettingSpecs().end()); auto deviceConfigSpecs = g_ptrSystem->GetDeviceConfig().GetSettingSpecs(); deviceSettingSpecs.insert(deviceSettingSpecs.end(), deviceConfigSpecs.begin(), deviceConfigSpecs.end());