#pragma once #include "Arduino.h" #include #include #include #include #include #include #include #ifndef PS_PTR_CLASS #define PS_PTR_CLASS 1 /** Auxiliary functions for using Unique Pointers in ESP32 PSRAM **/ // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // PSRAM Deleter (ps_malloc → free) // Deleter for PSRAM // single objekt (int -> PSRAM) // auto pint = ps_make_unique(123); // Serial.printf("Wert: %d\n", *pint); // single objekt (std::string -> PSRAM) // auto pstr = ps_make_unique("Hallo PSRAM"); // Serial.printf("String: %s\n", pstr->c_str()); // array (char-Buffer) // auto buf = ps_make_unique(256); // std::strcpy(buf.get(), "Text im PSRAM"); // array (int-Buffer) // auto iarr = ps_make_unique(64); // iarr[0] = 42 struct PsramDeleter { void operator()(void* ptr) const noexcept { if (ptr) { free(ptr); // PSRAM freigeben } } }; // create individual object in PSRAM template std::unique_ptr ps_make_unique(Args&&... args) { // rohen Speicher im PSRAM holen void* raw = ps_malloc(sizeof(T)); if (!raw) { throw std::bad_alloc(); } // Objekt mit placement-new konstruieren T* obj = new (raw) T(std::forward(args)...); // unique_ptr mit eigenem Deleter zurückgeben return std::unique_ptr(obj); } // create an array of objects in PSRAM template std::unique_ptr ps_make_unique(size_t count) { T* raw = static_cast(ps_malloc(sizeof(T) * count)); if (!raw) { printf("OOM: ps_malloc failed (%zu bytes)\n", sizeof(T) * count); return std::unique_ptr(nullptr); // kein throw } return std::unique_ptr(raw); } // Auxiliary function: Comparison of two strings case inensitive, only up to n characters inline int strncasecmp_local(const char* s1, const char* s2, std::size_t n) { for (std::size_t i = 0; i < n; ++i) { unsigned char c1 = static_cast(s1[i]); unsigned char c2 = static_cast(s2[i]); if (tolower(c1) != tolower(c2)) return tolower(c1) - tolower(c2); if (c1 == '\0') break; } return 0; } template class ps_ptr { private: std::unique_ptr mem; size_t allocated_size = 0; char* name = nullptr; // member for object name static inline T dummy{}; // For invalid accesses size_t length_ = 0; // actual number of characters public: // Auxiliary function for setting the name void set_name(const char* new_name) { if (name) { free(name); name = nullptr; } if (new_name) { std::size_t len = std::strlen(new_name) + 1; if (psramFound()) { name = static_cast(ps_malloc(len)); } else { name = static_cast(malloc(len)); } if (name) { std::memcpy(name, new_name, len); } else { printf("OOM: failed to allocate %zu bytes for name %s\n", len, new_name); } } } ps_ptr() = default; // default constructor ~ps_ptr() { // destructor if (mem) { // log_w("Destructor called for %s: Freeing %zu bytes at %p", name ? name : "unnamed", allocated_size * sizeof(T), mem.get()); } else { // log_w("Destructor called for %s: No memory to free.", name ? name : "unnamed"); } if (name) { free(name); name = nullptr; } } explicit operator bool() const { return mem.get() != nullptr; // Or just 'return (bool) mem;'if unique_ptr :: operator bool () is used } // constructor for C-strings (only active if t == char) ps_ptr(const char* src) { if constexpr (std::is_same_v) { assign(src); } else { static_assert(!std::is_same_v, "This constructor is only available for ps_ptr"); } } ps_ptr(const char* src, size_t len) { if (src && len > 0) { allocated_size = len + 1; mem = ps_make_unique(allocated_size); // sauber! if (mem.get() && allocated_size > len) { // additional bounds check std::memcpy(mem.get(), src, len); // suppress warning: We know that allocated_size = len + 1 and therefore index [len] is valid #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-overflow" mem.get()[len] = '\0'; #pragma GCC diagnostic pop } else { allocated_size = 0; // Reset if allocation fails } } } ps_ptr(ps_ptr&& other) noexcept { // move-constructor mem = std::move(other.mem); allocated_size = other.allocated_size; name = other.name; other.allocated_size = 0; other.name = nullptr; } // copy constructor (only for Char sensible, deep copy) ps_ptr(const ps_ptr& other) { if constexpr (std::is_same_v) { assign(other.get()); } else { // Für Nicht-Char-Typen: Kopieren verboten (wie vorher) static_assert(!std::is_same_v, "Copy constructor disabled for this type"); } } // 🆕 alloc constructor, e.g. ps_ptrbuff(1024) explicit ps_ptr(size_t n) { alloc(n); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A L L O C 📌📌📌 // ps_ptr test; // test.alloc(100"); // if(test.valid()) { // printf("Größe: %u\n", test.size()); // test.clear(); // memset "0" // } bool alloc(std::size_t size, const char* alloc_name = nullptr, bool usePSRAM = true) { size = (size + 15) & ~15; // Align to 16 bytes if (psramFound() && usePSRAM) { // Check at the runtime whether PSRAM is available mem.reset(static_cast(ps_malloc(size))); // <--- Important! } else { mem.reset(static_cast(malloc(size))); // <--- Important! } allocated_size = size; if (alloc_name) { set_name(alloc_name); } if (!mem) { printf("OOM: failed to allocate %zu bytes for %s\n", size, name ? name : "unnamed"); return false; } return true; } bool alloc(const char* alloc_name = nullptr) { // alloc for single objects/structures reset(); // Freigabe des zuvor gehaltenen Speichers void* raw_mem = nullptr; if (psramFound()) { // Check at the runtime whether PSRAM is available raw_mem = ps_malloc(sizeof(T)); // allocated im PSRAM } else { raw_mem = malloc(sizeof(T)); // allocated im RAM } if (alloc_name) { set_name(alloc_name); } if (raw_mem) { mem.reset(new (raw_mem) T()); // placed new: constructor of T is called up in PSRAM allocated_size = sizeof(T); } else { printf("OOM: failed to allocate %zu bytes for %s\n", sizeof(T), name ? name : "unnamed"); allocated_size = 0; // make sure that allocated_size is 0 if allocation fails return false; } return true; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C A L L O C 📌📌📌 /** * @brief Allocates and zeroes out memory for an array of elements. * Chooses between PSRAM (if available) and DRAM. * @param num_elements The number of elements to allocate space for. */ bool calloc(std::size_t num_elements, const char* alloc_name = nullptr, bool usePSRAM = true) { size_t total_size = num_elements * sizeof(T); total_size = (total_size + 15) & ~15; // Align to 16 bytes, consistent with your alloc() reset(); // Release of the previously held memory if (alloc_name) { set_name(alloc_name); } void* raw_mem = nullptr; if (psramFound() && usePSRAM) { // Check at the runtime whether PSRAM is available raw_mem = ps_malloc(total_size); } else { raw_mem = malloc(total_size); } if (raw_mem) { // Initialize memory with zeros how Calloc () does it memset(raw_mem, 0, total_size); // Connect the allocated memory to the Unique_PTR mem.reset(static_cast(raw_mem)); allocated_size = total_size; } else { // Error treatment for storage allocation printf("OOM: failed to calloc %zu bytes for %s\n", total_size, name ? name : "unnamed"); allocated_size = 0; // Sicherstellen, dass allocated_size 0 ist, wenn Allokation fehlschlägt return false; } return true; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A L L O C _ A R R A Y 📌📌📌 bool alloc_array(std::size_t count, const char* alloc_name = nullptr) { if (alloc_name) { set_name(alloc_name); } bool res = alloc(sizeof(T) * count); // clear(); return res; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C A L L O C _ A R R A Y 📌📌📌 bool calloc_array(std::size_t count, const char* alloc_name = nullptr) { if (alloc_name) { set_name(alloc_name); } // rohen Speicher holen bool res = alloc(sizeof(T) * count); if (!res) { return false; } // alle Elemente sauber value-initialisieren for (std::size_t i = 0; i < count; i++) { // placement-new mit {} ruft den Default-Konstruktor / value-init auf new (&(get()[i])) T{}; } return true; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 Z E R O _ M E M 📌📌📌 /** * @brief Sets the allocated memory block to all zeroes. * Only operates if memory is currently allocated. */ void zero_mem() { if (mem) { // Check whether memory is assigned (use the new operator bool ()) // Use allocated_aize to zero the actual size of the assigned block. // This is important because alloc() can also be called up with a specific size. memset(mem.get(), 0, allocated_size); } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E A L L O C 📌📌📌 // test.realloc(200); // printf("new size: %i\n", test.size()); // printf("%s\n", test.get()); void realloc(size_t new_size) { void* new_mem = nullptr; if (psramFound()) { // Check at the runtime whether PSRAM is available new_mem = ps_malloc(new_size); } else { new_mem = malloc(new_size); } if (!new_mem) { printf("OOM: failed to realloc %zu bytes for %s\n", new_size, name ? name : "unnamed"); return; } if (mem && allocated_size > 0) { std::memcpy(new_mem, mem.get(), std::min(allocated_size, new_size)); } mem.reset(static_cast(new_mem)); allocated_size = new_size; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A S S I G N 📌📌📌 // ps_ptr my_str1; // my_str1.assign("Hallo", "my_str1"); // ps_strdup() // printf("%s\n", my_str1.get()); // my_str1.get()[3] = 'x'; // printf("%s\n", my_str1.get()); void assign(const char* src) { if constexpr (std::is_same_v) { if (!src) { reset(); return; } std::size_t len = std::strlen(src) + 1; alloc(len); if (mem) { std::memcpy(mem.get(), src, len); } } else { static_assert(std::is_same_v, "assign(const char*) is only valid for ps_ptr"); } } // ps_ptr my_str2; // my_str2.assign("Hallo", 5, "my_str1"); // ps_strndup() // printf("%s\n", my_str2.get()); // Only for T T = char: similar to strndup (max. n chars) void assign(const char* src, std::size_t max_len) { static_assert(std::is_same_v, "assign(const char*, size_t) is only valid for ps_ptr"); if (!src) { reset(); return; } std::size_t actual_len = strnlen(src, max_len); std::size_t total = actual_len + 1; alloc(total); if (mem) { std::memcpy(mem.get(), src, actual_len); static_cast(mem.get())[actual_len] = '\0'; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C O P Y _ F R O M 📌📌📌 // Counted Count elements from the external pointer to the PSRAM, similar to memcpy // Strings // const char* msg = "Hallo"; // ps_ptr text; // text.copy_from(msg, strlen(msg) + 1, "text"); // Integer-Array // int32_t values[] = {10, 20, 30}; // ps_ptr data; // data.copy_from(values, 3, "data"); // Float-Array // float samples[] = {0.1f, 0.2f, 0.3f}; // ps_ptr buf; // buf.copy_from(samples, 3, "buffer"); // printf("%f\n", buf.get()[2]); // buf.get()[2] = 4.5; // printf("%f\n", buf.get()[2]); void copy_from(const T* src, std::size_t count) { std::size_t bytes = (count + 1) * sizeof(T); // +1 For zero terminator alloc(bytes); if (mem && src) { std::memcpy(mem.get(), src, count * sizeof(T)); mem.get()[count] = '\0'; // Zero } } size_t copy_from(const T* src) { // for strings if (src == nullptr) { log_e("arg. is null"); return 0; } std::size_t count = std::strlen(src) + 1; std::size_t bytes = count * sizeof(T); alloc(bytes); if (mem && src) { std::memcpy(mem.get(), src, bytes); } return bytes; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C O P Y _ F R O M _ U T F 1 6 📌📌📌 // copies all characters up to the terminator "\0\0" and converted into UTF-8 // source: 0x00, 0x4C, 0x00, 0x69, 0x00, 0x74, 0x00, 0x74, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x20, 0x00, 0x4C, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x64, 0x00, 0x6F, 0x00, 0x6E, 0x00, 0x20, 0x00, 0x47, 0x00, // 0x69, 0x00, 0x72, 0x00, 0x6C, 0x00, 0x00 // is UTF-16LE and will converted to: "Little London Girl" // UTF-16LE and UTF-16BE is often found in ID3 header #include #include #include #include // convert UTF-16 to UTF-8 and stop at zero terminator size_t copy_from_utf16(const uint8_t* src, bool is_big_endian = false) { if (!src) { log_e("arg. is null"); return 0; } std::vector out; size_t i = 0; // BOM-Handling if (src[i] == 0xFF && src[i + 1] == 0xFE) { is_big_endian = false; // UTF-16LE i += 2; } else if (src[i] == 0xFE && src[i + 1] == 0xFF) { is_big_endian = true; // UTF-16BE i += 2; } while (true) { // Prüfe, ob genug Bytes für ein UTF-16-Zeichen vorhanden sind if (i + 1 >= std::numeric_limits::max() || (src[i] == 0x00 && src[i + 1] == 0x00)) { break; // Nullterminator oder Ende des Puffers } uint16_t ch; if (is_big_endian) { ch = (src[i] << 8) | src[i + 1]; } else { ch = (src[i + 1] << 8) | src[i]; } i += 2; uint32_t codepoint = ch; // Prüfe auf Surrogatenpaare if (ch >= 0xD800 && ch <= 0xDBFF) { // High surrogate if ((i + 1 >= std::numeric_limits::max()) || (src[i] == 0x00 && src[i + 1] == 0x00)) { log_e("Invalid surrogate pair: missing low surrogate"); break; } uint16_t ch2; if (is_big_endian) { ch2 = (src[i] << 8) | src[i + 1]; } else { ch2 = (src[i + 1] << 8) | src[i]; } if (ch2 < 0xDC00 || ch2 > 0xDFFF) { log_e("Invalid surrogate pair: invalid low surrogate"); break; } i += 2; codepoint = 0x10000 + ((ch - 0xD800) << 10) + (ch2 - 0xDC00); } else if (ch >= 0xDC00 && ch <= 0xDFFF) { log_e("Invalid surrogate pair: unexpected low surrogate"); break; } // UTF-16 → UTF-8 if (codepoint < 0x80) { out.push_back(static_cast(codepoint)); } else if (codepoint < 0x800) { out.push_back(0xC0 | (codepoint >> 6)); out.push_back(0x80 | (codepoint & 0x3F)); } else if (codepoint < 0x10000) { out.push_back(0xE0 | (codepoint >> 12)); out.push_back(0x80 | ((codepoint >> 6) & 0x3F)); out.push_back(0x80 | (codepoint & 0x3F)); } else if (codepoint < 0x110000) { out.push_back(0xF0 | (codepoint >> 18)); out.push_back(0x80 | ((codepoint >> 12) & 0x3F)); out.push_back(0x80 | ((codepoint >> 6) & 0x3F)); out.push_back(0x80 | (codepoint & 0x3F)); } else { log_e("Invalid codepoint"); break; } } // Nullterminator hinzufügen out.push_back('\0'); // Speicher allozieren und kopieren std::size_t bytes = out.size(); alloc(bytes); std::memcpy(mem.get(), out.data(), bytes); return i; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C O P Y _ F R O M _ I S O _ 8 8 5 9 - 1 📌📌📌 // convert ISO 8859-1 to UTF-8 and stop at zero terminator // 0x48 0x65 0x6C 0x6C 0x6F 0x20 0xC3 0xA4 0x62 0x63 0x00 -> "Hello äbc" size_t copy_from_iso8859_1(const uint8_t* src) { if (!src) { log_e("arg. is null"); return 0; } std::vector out; size_t i = 0; while (true) { uint8_t ch = src[i]; if (ch == 0x00) { break; // 'Nullterminator' } // ISO-8859-1 → UTF-8 if (ch < 0x80) { out.push_back(static_cast(ch)); // Ascii area remains unchanged } else { // chars from 0x80 to 0xff are coded as 2-byte sequences in UTF-8 out.push_back(0xC0 | (ch >> 6)); out.push_back(0x80 | (ch & 0x3F)); } i++; } // add zero terminator out.push_back('\0'); // allocate and copy memory std::size_t bytes = out.size(); alloc(bytes); std::memcpy(mem.get(), out.data(), bytes); return i; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 U R L D E C O D E 📌📌📌 // Decodes the current string in-place from URL encoding. // Example: // ps_ptr url = "%D0%B8%D1%81%D0%BF%D1%8B%D1%82%D0%B0%D0%BD%D0%B8%D0%B5.mp3"; // url.urldecode(); // → "испытание.mp3" // url = "Born%20On%20The%20B.mp3"; url.urldecode(); // → "Born On The B.mp3" // url = "A+Test.mp3"; url.urldecode(); // → "A Test.mp3" void urldecode() { static_assert(std::is_same_v, "urldecode() is only valid for ps_ptr"); if (!mem || !get()) { log_e("urldecode: No valid string data"); return; } char* str = get(); uint16_t p1 = 0, p2 = 0; char a, b; while (str[p1]) { if ((str[p1] == '%') && ((a = str[p1 + 1]) && (b = str[p1 + 2])) && (isxdigit(a) && isxdigit(b))) { // Normalize lowercase to uppercase if (a >= 'a') a -= ('a' - 'A'); if (b >= 'a') b -= ('a' - 'A'); // Convert hex digits to numeric a = (a >= 'A') ? (a - 'A' + 10) : (a - '0'); b = (b >= 'A') ? (b - 'A' + 10) : (b - '0'); str[p2++] = (a << 4) | b; p1 += 3; } else if (str[p1] == '+') { str[p2++] = ' '; p1++; } else { str[p2++] = str[p1++]; } } str[p2] = '\0'; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C L O N E _ F R O M 📌📌📌 // ps_ptr source; // source.assign("Hello World"); // ps_ptr copy; // copy.clone_from(source); // printf("%s\n", copy.get()); // → Hello World void clone_from(const ps_ptr& other) { if (!other.valid() || other.size() == 0) { reset(); return; } if constexpr (std::is_same_v) { assign(other.get()); return; } std::size_t sz = other.size(); alloc(sz); if (mem && sz > 0) { std::memcpy(mem.get(), other.get(), sz); } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S W A P 📌📌📌 // A.swap(B); void swap(ps_ptr& other) noexcept { std::swap(this->mem, other.mem); std::swap(this->allocated_size, other.allocated_size); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S W A P W I T H R A W P O I N T E R 📌📌📌 void swap_with_pointer(T*& raw_ptr) noexcept { T* temp = get(); mem.release(); // Gib Besitz auf, ohne zu löschen mem = std::unique_ptr(raw_ptr); // Übernehme neuen Zeiger raw_ptr = temp; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A P P E N D 📌📌📌 // ps_ptr text1; // like Strcat with automatic new allocation // text1.assign("Hallo", "text"); // text1.append(", Welt!"); // printf("%s\n", text1.get()); // → "Hallo, Welt!" template requires std::is_same_v void append(const char* suffix) { if (!suffix || !*suffix) return; std::size_t old_len = mem ? std::strlen(static_cast(mem.get())) : 0; std::size_t add_len = std::strlen(suffix); std::size_t new_len = old_len + add_len + 1; // +1 für null-Terminator // Alten Speicher übernehmen char* old_data = static_cast(mem.release()); if (psramFound()) { // Check at the runtime whether PSRAM is available mem.reset(static_cast(ps_malloc(new_len))); // <--- Important! } else { mem.reset(static_cast(malloc(new_len))); } if (!mem) { printf("OOM: append() failed for %zu bytes\n", new_len); return; } // Copy existing content if (old_data) { std::memcpy(mem.get(), old_data, old_len); free(old_data); } // Append suffix std::memcpy(static_cast(mem.get()) + old_len, suffix, add_len + 1); allocated_size = new_len; static_cast(mem.get())[old_len + add_len] = '\0'; } // ps_ptr text1; // like Strcat with automatic new allocation // text1.assign("Hallo", "text"); // text1.append(", Welt!", 4); // printf("%s\n", text1.get()); // → "Hallo, We" template requires std::is_same_v void append(const char* suffix, std::size_t len) { if (!suffix || len == 0) return; std::size_t old_len = mem ? std::strlen(static_cast(mem.get())) : 0; std::size_t new_len = old_len + len + 1; // +1 für null-Terminator char* old_data = static_cast(mem.release()); if (psramFound()) { // Check at the runtime whether PSRAM is available mem.reset(static_cast(ps_malloc(new_len))); } else { mem.reset(static_cast(malloc(new_len))); } if (!mem) { printf("OOM: append(len) failed for %zu bytes\n", new_len); return; } if (old_data) { std::memcpy(mem.get(), old_data, old_len); free(old_data); } std::memcpy(static_cast(mem.get()) + old_len, suffix, len); static_cast(mem.get())[old_len + len] = '\0'; } // example: ps_ptr a="Hello "; ps_ptr b="World"; a.append(b); template requires std::is_same_v void append(const ps_ptr& other) { const char* suffix = other.get(); if (!suffix || !*suffix) return; // append nothing if empty size_t add_len = std::strlen(suffix); size_t old_len = mem ? std::strlen(static_cast(mem.get())) : 0; size_t new_len = old_len + add_len + 1; char* old_data = static_cast(mem.release()); if (psramFound()) { mem.reset(static_cast(ps_malloc(new_len))); } else { mem.reset(static_cast(malloc(new_len))); } if (!mem) { printf("OOM: append(ps_ptr) failed for %zu bytes\n", new_len); if (old_data) free(old_data); return; } if (old_data) { std::memcpy(mem.get(), old_data, old_len); free(old_data); } std::memcpy(mem.get() + old_len, suffix, add_len + 1); allocated_size = new_len; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 P U S H _ B A C K 📌📌📌 // append individual characters // ps_ptrs; s = "abc"; s.push_back('1); -> abc1 void push_back(char c) { if (length + 1 >= capacity()) { // Wenn zu klein, Kapazität verdoppeln (wie std::string) size_t new_cap = (capacity() == 0) ? 16 : capacity() * 2; reserve(new_cap); } mem.get()[length++] = c; mem.get()[length] = '\0'; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 L E N G T H 📌📌📌 size_t length() const { return length_; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C A P A C I T Y 📌📌📌 size_t capacity() const { return allocated_size ? allocated_size - 1 : 0; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E S E R V E 📌📌📌 void reserve(size_t new_cap) { if (new_cap + 1 <= allocated_size) return; // genug Platz vorhanden char* old_data = mem.release(); size_t old_len = length(); size_t new_size = new_cap + 1; // +1 für '\0' if (psramFound()) mem.reset(static_cast(ps_malloc(new_size))); else mem.reset(static_cast(malloc(new_size))); if (!mem) { printf("OOM: reserve(%zu) failed\n", new_size); if (old_data) free(old_data); return; } if (old_data) { if (old_len > 0) std::memcpy(mem.get(), old_data, old_len + 1); else mem.get()[0] = '\0'; free(old_data); } else { mem.get()[0] = '\0'; } allocated_size = new_size; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S T A R T S _ W I T H 📌📌📌 // ps_ptr s; // s.assign("https://example.com/file.mp3"); // // if (s.starts_with("https://")) { // printf("https-link recognized\n"); // } // Only for T = Char: Check whether the string begins with the given prefix template requires std::is_same_v bool starts_with(const char* prefix) const { if (!mem || !prefix) return false; const char* str = static_cast(mem.get()); std::string_view sv(str); // C++17, sicherer als strlen return sv.starts_with(prefix); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 E N D S _ W I T H 📌📌📌 // ps_ptr s; // s.assign("https://example.com/file.mp3"); // if (s.ends_with(".mp3")) { // printf("mp3-file recognized\n"); // } // Only for T = Char: Check whether the string ends with the given suffix template requires std::is_same_v bool ends_with(const char* prefix) const { if (!mem || !prefix) return false; const char* str = static_cast(mem.get()); std::string_view sv(str); // C++17, sicherer als strlen return sv.ends_with(prefix); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S T A R T S _ W I T H _ I C A S E 📌📌📌 // ps_ptr url; // url.assign("https://example.com/TRACK.MP3"); // // if (url.starts_with_icase("HTtp")) { // printf("http recognized (case-insensitive)\n"); // } // case non-sensitive: starts with Prefix? template requires std::is_same_v bool starts_with_icase(const char* prefix) const { if (!mem || !prefix) return false; const char* str = static_cast(mem.get()); std::size_t prefix_len = std::strlen(prefix); std::size_t str_len = std::strlen(str); if (prefix_len > str_len) return false; return strncasecmp_local(str, prefix, prefix_len) == 0; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 E N D S _ W I T H _ I C A S E 📌📌📌 // ps_ptr url; // url.assign("https://example.com/TRACK.MP3"); // // if (url.ends_with_icase(".mp3")) { // printf("MP3 recognized (case-insensitive)\n"); // } // case non-sensitive: ends with suffix? template requires std::is_same_v bool ends_with_icase(const char* suffix) const { if (!mem || !suffix) return false; const char* str = static_cast(mem.get()); std::size_t suffix_len = std::strlen(suffix); std::size_t str_len = std::strlen(str); if (suffix_len > str_len) return false; return strncasecmp_local(str + str_len - suffix_len, suffix, suffix_len) == 0; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 E Q U A L S 📌📌📌 // my_ps_ptr t1, t2; // t1.assign("Hallo"); // t2.assign("Hallo"); // // if (t1.equals(t2)) { // 👍 is equal // } bool equals(const ps_ptr& other) const { if (!this->valid() || !other.valid()) return false; if (!this->get() || !other.get()) return false; return strcmp(this->get(), other.get()) == 0; } bool equals(const char* other) const { if (!this->valid()) return false; const char* myStr = this->get(); if (!myStr || !other) return false; return strcmp(myStr, other) == 0; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A S S I G N F 📌📌📌 // ps_ptr message; // message.assignf("Code {}, Modul {}", 404, "Network"); // printf("%s\n", message.get()); // → Error: Code 404, Modul Network // e.g. {}, {:02}, {:04X}, {:.2f}, {:20}, {:-20} // assignf("v={:02} hex={:04X} pi={:.2f}", 7, 0xAF, 3.14159); --> v=07 hex=00AF pi=3.14 // bool --> true or false template requires std::is_same_v void assignf(const char* fmt, Args&&... args) { if (!fmt) return; constexpr size_t arg_count = sizeof...(Args); size_t placeholder_count = count_placeholders(fmt); if (placeholder_count != arg_count) { printf("assignf(): \033[31m placeholder mismatch (expected %zu, got %zu), content: '%s' \033[0m\n", placeholder_count, arg_count, fmt); return; } std::string tmp; format_append(tmp, fmt, std::forward(args)...); reset(); alloc(tmp.size() + 1); if (!mem) { printf("OOM: assignf() failed for %zu bytes\n", tmp.size() + 1); return; } memcpy(mem.get(), tmp.c_str(), tmp.size() + 1); allocated_size = tmp.size(); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A P P E N D F 📌📌📌 // ps_ptr message; // message.assign("Error: "); // message.appendf("Code {}, Modul {}", 404, "Network"); // printf("%s\n", message.get()); // → Error: Code 404, Modul Network // only activate if T = char template requires std::is_same_v void appendf(const char* fmt, Args&&... args) { if (!fmt) return; constexpr size_t arg_count = sizeof...(Args); size_t placeholder_count = count_placeholders(fmt); if (placeholder_count != arg_count) { printf("appendf(): \033[31m placeholder mismatch (expected %zu, got %zu), content: '%s' \033[0m\n", placeholder_count, arg_count, fmt); return; } std::string tmp; format_append(tmp, fmt, std::forward(args)...); std::size_t old_len = mem ? std::strlen(mem.get()) : 0; std::size_t add_len = tmp.size(); std::size_t new_len = old_len + add_len + 1; char* old_data = static_cast(mem.release()); reset(); alloc(new_len); if (!mem) { printf("OOM: appendf1() failed for %zu bytes\n", new_len); if (old_data) { free(old_data); } return; } // alten Text kopieren if (old_data && old_len > 0) { std::memcpy(mem.get(), old_data, old_len); free(old_data); } // neuen Text anhängen std::memcpy(mem.get() + old_len, tmp.c_str(), add_len + 1); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 I N D E X _ O F 📌📌📌 // char // ps_ptr text3; // text3.assign("Hello, World!"); // int pos = text3.index_of('l'); // → 2 // printf("found %i\n", pos); // int not_found = text3.index_of('x'); // → -1 // printf("not_found %i\n", not_found); // pos = text3.index_of('l', 5); // printf("found %i\n", pos); // const char* // ps_ptr path; // path.assign("audio/music/song.mp3"); // int i1 = path.index_of('s'); // → z.B. 6 // int i2 = path.index_of("song"); // → 12 // int i3 = path.index_of("audio"); // → 0 // int i4 = path.index_of("test"); // → -1 // int i5 = path.index_of("music", 7); // → -1 // Array // ps_ptr numbers; // numbers.copy_from((int[]){10, 20, 30, 40, 50}, 5); // int idx = numbers.index_of(30); // → 2 // printf("pos %i\n", idx); // General version: for any T (e.g. Int, Float, Structs) int index_of(const T& value, std::size_t start = 0) const { if (!mem || allocated_size < sizeof(T)) return -1; std::size_t count = allocated_size / sizeof(T); if (start >= count) return -1; T* data = get(); for (std::size_t i = start; i < count; ++i) { if (data[i] == value) return static_cast(i); } return -1; } // Specialized version: only for T = char (search for individual characters) template requires std::is_same_v int index_of(char ch, std::size_t start = 0) const { if (!mem) return -1; const char* str = static_cast(mem.get()); std::size_t len = std::strlen(str); if (start >= len) return -1; for (std::size_t i = start; i < len; ++i) { if (str[i] == ch) return static_cast(i); } return -1; } // Overload for const char* (substring search) template requires std::is_same_v int index_of(const char* substr, std::size_t start = 0) const { if (!mem || !substr || !*substr) return -1; const char* str = static_cast(mem.get()); std::size_t len = std::strlen(str); if (start >= len) return -1; const char* found = std::strstr(str + start, substr); if (!found) return -1; return static_cast(found - str); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S P E C I A L _ I N D E X _ O F 📌📌📌 // Searches for the sequence needle within the buffer managed by ps_ptr (haystack), up to max_length bytes. // Returns the offset of the first occurrence of needle relative to the buffer start, or -1 if not found. // Ignores null bytes in both haystack and needle, making it suitable for binary data searches. // Example: // ps_ptr haystack; // Contains data, e.g., stsd atom content // int32_t idx = haystack.index_of("mp4a", 1024); // Search for "mp4a" // int32_t idx2 = haystack.index_of("\x00\x01\xFF", 3, 1024); // Search for byte sequence int32_t special_index_of(const char* needle, uint32_t needle_length, uint32_t max_length) const { static_assert(std::is_same_v, "index_of is only valid for ps_ptr"); if (!mem || !get()) { log_e("index_of: No valid buffer data"); return -1; } if (!needle || needle_length == 0) { log_e("index_of: Invalid needle (null or empty)"); return -1; } if (max_length < needle_length) { log_e("index_of: max_length (%u) too short to find needle of length %u", max_length, needle_length); return -1; } const char* data = get(); for (uint32_t i = 0; i <= max_length - needle_length; ++i) { if (std::memcmp(data + i, needle, needle_length) == 0) { // log_i("index_of: Found needle at offset %u", i); return static_cast(i); } } log_d("index_of: Needle not found within %lu bytes", max_length); return -1; } template requires std::is_same_v int32_t special_index_of(const char* needle, uint32_t max_length) const { return special_index_of(needle, needle ? std::strlen(needle) : 0, max_length); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 L A S T _ S P E C I A L _ I N D E X _ O F 📌📌📌 // Overload for C-string needle (automatically determines needle length, excluding null terminator) // Searches backwards for needle in the buffer. // Returns the offset of the last occurrence or -1 if not found. int32_t last_special_index_of(const char* needle, uint32_t needle_length, uint32_t max_length) const { static_assert(std::is_same_v, "last_special_index_of is only valid for ps_ptr"); if (!mem || !get()) { log_e("last_special_index_of: No valid buffer data"); return -1; } if (!needle || needle_length == 0) { log_e("last_special_index_of: Invalid needle (null or empty)"); return -1; } if (max_length < needle_length) { log_e("last_special_index_of: max_length (%u) too short to find needle of length %u", max_length, needle_length); return -1; } const char* data = get(); for (int32_t i = static_cast(max_length - needle_length); i >= 0; --i) { if (std::memcmp(data + i, needle, needle_length) == 0) { return i; } } log_d("last_special_index_of: Needle not found within %lu bytes", max_length); return -1; } // Overload for C-string needle template requires std::is_same_v int32_t last_special_index_of(const char* needle, uint32_t max_length) const { return last_special_index_of(needle, needle ? std::strlen(needle) : 0, max_length); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 I N D E X _ O F _ I C A S E 📌📌📌 // ps_ptr s; // s.assign("Content-Type: audio/mp3"); // // int idx1 = s.index_of_icase("CONTENT"); // 0 // int idx2 = s.index_of_icase("audio"); // 14 // int idx3 = s.index_of_icase("MP3"); // 20 // int idx4 = s.index_of_icase("notfound"); // -1 // Case-insensitive substring search template requires std::is_same_v int index_of_icase(const char* substr, std::size_t start = 0) const { if (!mem || !substr || !*substr) return -1; const char* str = static_cast(mem.get()); std::size_t len_str = std::strlen(str); std::size_t len_sub = std::strlen(substr); if (start >= len_str || len_sub == 0 || len_sub > len_str) return -1; for (std::size_t i = start; i <= len_str - len_sub; ++i) { bool match = true; for (std::size_t j = 0; j < len_sub; ++j) { if (std::tolower(str[i + j]) != std::tolower(substr[j])) { match = false; break; } } if (match) return static_cast(i); } return -1; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 L A S T _ I N D E X _ O F 📌📌📌 template requires std::is_same_v int last_index_of(char ch, int start_pos = -1) const { if (!mem) return -1; const char* str = static_cast(mem.get()); int len = static_cast(std::strlen(str)); // if no start position is specified, start at the end if (start_pos < 0 || start_pos >= len) start_pos = len - 1; for (int i = start_pos; i >= 0; --i) { if (str[i] == ch) return i; } return -1; } // ps_ptr str1; // str1.assign("OpenAI API Test"); // int last_i = str1.last_index_of('i'); // → -1 (no 'i', but 'I' ≠ 'i') // int last_A = str1.last_index_of('A'); // → 5 // printf("last_i %i\n", last_i); // printf("last_A %i\n", last_A); // ps_ptr str; // str.assign("/audiofiles/my_playlist/podcast/h.mp3"); // int last = str.last_index_of('/'); // → 32 (the last '/') // int prev = str.last_index_of('/', last - 1); // → 23 (the second to last '/') template requires std::is_same_v int last_index_of(const char ch, int start_pos = -1) const { if (!mem) return -1; const char* str = static_cast(mem.get()); int len = static_cast(std::strlen(str)); // if no start position is specified, start at the end if (start_pos < 0 || start_pos >= len) start_pos = len - 1; for (int i = start_pos; i >= 0; --i) { if (str[i] == ch) return i; } return -1; } // ps_ptr str; // str.assign("/audiofiles/my_playlist/podcast/h.mp3"); // int p1 = str.last_index_of("/"); // 31 // int p2 = str.last_index_of("podcast"); // 24 // int p3 = str.last_index_of(".mp3"); // 33 // int p4 = str.last_index_of("xyz"); // -1 // ps_ptr str; // str.assign("abc test abc test abc"); // int last = str.last_index_of("abc"); // 18 // int prev = str.last_index_of("abc", last - 1); // 9 template requires std::is_same_v int last_index_of(const char* substr, int start_pos = -1) const { if (!mem || !substr || !*substr) return -1; const char* str = static_cast(mem.get()); int len = static_cast(std::strlen(str)); int sub_len = static_cast(std::strlen(substr)); if (sub_len > len) return -1; // Standard: am Ende beginnen if (start_pos < 0 || start_pos > len - sub_len) { start_pos = len - sub_len; } for (int i = start_pos; i >= 0; --i) { if (std::strncmp(str + i, substr, sub_len) == 0) { return i; } } return -1; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 I N D E X _ O F _ S U B S T R 📌📌📌 // source must not be null terminated // ps_ptr buffer; // buffer.assign("abc123ID3TAGthisisjustatest...", "mp3scan"); // int pos1 = buffer.index_of_substr("ID3", 20); // → finds "ID3" at index 6 // printf("ID3 found at: %d\n", pos1); template requires std::is_same_v int index_of_substr(const char* needle, std::size_t max_pos = SIZE_MAX) const { if (!mem || !needle || !*needle) return -1; const char* haystack = static_cast(mem.get()); std::size_t hay_len = std::min(std::strlen(haystack), max_pos); std::size_t needle_len = std::strlen(needle); if (needle_len > hay_len) return -1; for (std::size_t i = 0; i <= hay_len - needle_len; ++i) { if (std::memcmp(haystack + i, needle, needle_len) == 0) { return static_cast(i); } } return -1; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S U B S T R 📌📌📌 ps_ptr substr(size_t pos, size_t count = std::string::npos) const { const char* src = mem.get(); if (!src) return ps_ptr{}; size_t len = std::strlen(src); if (pos >= len) return ps_ptr{}; // leer zurück size_t n = (count == std::string::npos || pos + count > len) ? (len - pos) : count; return ps_ptr(src + pos, n); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T O L O W E R C A S E 📌📌📌 // ps_ptr a; // ascii and ISO-8859-1 only! // a = "Hallo Welt!"; // a.toLowerCase(); // printf("%s\n", a.get()); // hallo welt! template requires std::is_same_v void toLowerCase() { if (!mem) return; char* str = get(); if (!str) return; for (; *str; ++str) { *str = static_cast(std::tolower(static_cast(*str))); } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T O U P P E R C A S E 📌📌📌 // ps_ptr a; // ascii and ISO-8859-1 only! // a = "Hallo Welt!"; // a.toUpperCase(); // printf("%s\n", a.get()); // HALLO WELT! template requires std::is_same_v void toUpperCase() { if (!mem) return; char* str = get(); if (!str) return; for (; *str; ++str) { *str = static_cast(std::toupper(static_cast(*str))); } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S T R L E N 📌📌📌 // ps_ptr text; // text.assign("Hello"); // text.strlen(); // --> 5 template requires std::is_same_v size_t strlen() const { if (!valid()) return 0; return std::strlen(get()); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 U T F 8 _ S T R L E N 📌📌📌 // ps_ptr colored; // ignoring ANSI // colored.assign("\033[31mПривет\033[0m", "russian red"); // size_t visibleLetters = colored.utf8_strlen; // --> 6 size_t utf8_strlen() const { if (!get()) return 0; size_t count = 0; const unsigned char* s = reinterpret_cast(get()); while (*s) { // ANSI-Escape-Sequenz starts with ESC [ if (*s == 0x1B && s[1] == '[') { s += 2; while (*s && !(*s >= '@' && *s <= '~')) { ++s; // Parameter sign (numbers, semicola etc.) } if (*s) ++s; // Final letter z.B. 'M' continue; } // UTF-8-Count characters if ((*s & 0x80) == 0) s += 1; // ASCII else if ((*s & 0xE0) == 0xC0) s += 2; // 2 Byte else if ((*s & 0xF0) == 0xE0) s += 3; // 3 Byte else if ((*s & 0xF8) == 0xF0) s += 4; // 4 Byte else s += 1; // invalid ? carefully next ++count; } return count; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 I S _ U T F 8 📌📌📌 // ps_ptr a = "Hello"; // ASCII → valid UTF-8 // ps_ptr b = u8"Привет"; // Russian → valid UTF-8 // ps_ptr c = "\xC3\x28"; // invalid (bad UTF-8 sequence) // if (is_utf8(a)) printf("a is UTF-8\n"); // if (is_utf8(b)) printf("b is UTF-8\n"); // if (!is_utf8(c)) printf("c is not UTF-8\n"); bool is_utf8() const { const unsigned char* s = reinterpret_cast(mem.get()); if (!s) return false; while (*s) { unsigned char c = *s++; // 1-byte ASCII (0xxxxxxx) if (c < 0x80) continue; // 2-byte-sequence (110xxxxx 10xxxxxx) if ((c >> 5) == 0x6) { if ((s[0] & 0xC0) != 0x80) return false; s += 1; continue; } // 3-byte-sequence (1110xxxx 10xxxxxx 10xxxxxx) if ((c >> 4) == 0xE) { if ((s[0] & 0xC0) != 0x80 || (s[1] & 0xC0) != 0x80) return false; unsigned int cp = ((c & 0x0F) << 12) | ((s[0] & 0x3F) << 6) | (s[1] & 0x3F); // Excessive coding or surrogate? if (cp < 0x800 || (cp >= 0xD800 && cp <= 0xDFFF)) return false; s += 2; continue; } // 4-byte-sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx) if ((c >> 3) == 0x1E) { if ((s[0] & 0xC0) != 0x80 || (s[1] & 0xC0) != 0x80 || (s[2] & 0xC0) != 0x80) return false; unsigned int cp = ((c & 0x07) << 18) | ((s[0] & 0x3F) << 12) | ((s[1] & 0x3F) << 6) | (s[2] & 0x3F); // Overlong or out of Unicode range if (cp < 0x10000 || cp > 0x10FFFF) return false; s += 3; continue; } // Invalid starting byte return false; } return true; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 I S _ J S O N 📌📌📌 // ps_ptrjsonIn; // jsonIn.assign("{\"status\":1,\"message\":\"Ok\",\"result\":\"Ok\",\"errorCode\":0}"); bool isJson() const { if (!mem || allocated_size < 2) return false; const char* p = static_cast(mem.get()); // Skip leading whitespace while (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; // Check if it starts with '{' if (*p != '{') return false; // Skip to end, ignoring trailing whitespace const char* end = mem.get() + std::strlen(mem.get()) - 1; while (end > p && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) end--; // Check if it ends with '}' if (*end != '}') return false; // Basic JSON structure validation bool in_string = false; int brace_count = 0; const char* curr = p; while (curr <= end) { if (!in_string) { if (*curr == '{') brace_count++; else if (*curr == '}') brace_count--; else if (*curr == '"') in_string = true; else if (*curr == ':' || *curr == ',') { // Allow colons and commas outside strings } else if (*curr != ' ' && *curr != '\t' && *curr != '\n' && *curr != '\r') { // Allow digits, true, false, null, etc., but for simplicity, we just check for valid chars if (!(*curr >= '0' && *curr <= '9') && *curr != '-' && *curr != '.' && *curr != 't' && *curr != 'f' && *curr != 'n' && *curr != '[' && *curr != ']') { return false; // Invalid character outside string } } } else { if (*curr == '"') in_string = false; else if (*curr == '\\') curr++; // Skip escaped character } curr++; if (brace_count < 0) return false; // Unmatched closing brace } return brace_count == 0; // Ensure all braces are matched } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 U N I C O D E _ T O _ U T F 8 📌📌📌 // ps_ptr jsonIn, decoded; // jsonIn.assign("\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442"); // Привет // decoded.unicodeToUTF8(jsonIn.get()); // log_i("%s", decoded.get()); // shows: Привет void unicodeToUTF8(const char* src) { this->clear(); if (!src) return; auto encodeCodepointToUTF8 = [](uint32_t cp, char* out) -> int { if (cp <= 0x7F) { out[0] = cp; return 1; } else if (cp <= 0x7FF) { out[0] = 0xC0 | (cp >> 6); out[1] = 0x80 | (cp & 0x3F); return 2; } else if (cp <= 0xFFFF) { out[0] = 0xE0 | (cp >> 12); out[1] = 0x80 | ((cp >> 6) & 0x3F); out[2] = 0x80 | (cp & 0x3F); return 3; } else if (cp <= 0x10FFFF) { out[0] = 0xF0 | (cp >> 18); out[1] = 0x80 | ((cp >> 12) & 0x3F); out[2] = 0x80 | ((cp >> 6) & 0x3F); out[3] = 0x80 | (cp & 0x3F); return 4; } return 0; }; const char* ptr = src; char utf8[5]; while (*ptr) { if (ptr[0] == '\\' && ptr[1] == 'u') { uint32_t codepoint = 0; if (sscanf(ptr + 2, "%4lx", &codepoint) == 1) { int len = encodeCodepointToUTF8(codepoint, utf8); utf8[len] = 0; this->append(utf8); ptr += 6; // überspringe \uXXXX } else { this->append(ptr, 1); ptr++; } } else { this->append(ptr, 1); ptr++; } } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 H T M L T O U T F 8 📌📌📌 // ps_ptr html, utf8; // html.assign("Hello%20World"); // utf8.unicodeToUTF8(html.get()); // log_i("%s", utf8.get()); // shows: Hallo World void htmlToUTF8(const char* src) { this->clear(); if (!src) return; struct EntityMap { const char* name; uint32_t codepoint; }; static const EntityMap entities[] = { {"amp", 0x0026}, // & {"lt", 0x003C}, // < {"gt", 0x003E}, // > {"quot", 0x0022}, // " {"apos", 0x0027}, // ' {"nbsp", 0x00A0}, // non-breaking space {"euro", 0x20AC}, // € {"copy", 0x00A9}, // © {"reg", 0x00AE}, // ® {"trade", 0x2122}, // ™ {"hellip", 0x2026}, // … {"ndash", 0x2013}, // – {"mdash", 0x2014}, // — {"sect", 0x00A7}, // § {"para", 0x00B6} // ¶ }; auto encodeCodepointToUTF8 = [](uint32_t cp, char* out) -> int { if (cp <= 0x7F) { out[0] = (char)cp; return 1; } else if (cp <= 0x7FF) { out[0] = (char)(0xC0 | (cp >> 6)); out[1] = (char)(0x80 | (cp & 0x3F)); return 2; } else if (cp <= 0xFFFF) { out[0] = (char)(0xE0 | (cp >> 12)); out[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); out[2] = (char)(0x80 | (cp & 0x3F)); return 3; } else if (cp <= 0x10FFFF) { out[0] = (char)(0xF0 | (cp >> 18)); out[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); out[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); out[3] = (char)(0x80 | (cp & 0x3F)); return 4; } return 0; }; auto find_named_entity = [&](const char* p, uint32_t* cp_out, int* entity_len) -> bool { for (size_t i = 0; i < sizeof(entities) / sizeof(entities[0]); ++i) { const char* name = entities[i].name; size_t len = std::strlen(name); if (strncmp(p + 1, name, len) == 0 && p[1 + len] == ';') { *cp_out = entities[i].codepoint; *entity_len = (int)(len + 2); // &name; return true; } } return false; }; const char* p = src; char utf8[5]; while (*p) { if (p[0] == '&') { uint32_t cp = 0; int ent_len = 0; // 1) Named entity if (find_named_entity(p, &cp, &ent_len)) { int n = encodeCodepointToUTF8(cp, utf8); if (n > 0) { utf8[n] = '\0'; this->append(utf8); p += ent_len; continue; } } // 2) Numeric entity if (p[1] == '#') { const char* q = p + 2; uint32_t value = 0; if (*q == 'x' || *q == 'X') { // Hex 😊 q++; char* endptr = nullptr; unsigned long tmp = strtoul(q, &endptr, 16); if (endptr && *endptr == ';' && tmp <= 0x10FFFF) { value = (uint32_t)tmp; int n = encodeCodepointToUTF8(value, utf8); if (n > 0) { utf8[n] = '\0'; this->append(utf8); p = endptr + 1; continue; } } } else { // Decimal Ӓ char* endptr = nullptr; unsigned long tmp = strtoul(q, &endptr, 10); if (endptr && *endptr == ';' && tmp <= 0x10FFFF) { value = (uint32_t)tmp; int n = encodeCodepointToUTF8(value, utf8); if (n > 0) { utf8[n] = '\0'; this->append(utf8); p = endptr + 1; continue; } } } } // Unbekannte Entity → nur '&' übernehmen this->append(p, 1); p++; continue; } // Normales Zeichen this->append(p, 1); p++; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 G E T 📌📌📌 T* get() const { return static_cast(mem.get()); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C_G E T (safe) 📌📌📌 const char* c_get(const char* fallback = "") const { if constexpr (std::is_same_v) { return mem ? mem.get() : fallback; } else { return fallback; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 P R I N T 📌📌📌 // Prints the stored string to the standard output using printf. // Only valid for ps_ptr. Uses c_get() to safely handle null cases. template requires std::is_same_v void print() const { printf("%s: %s", name ? name : "unnamed", c_get()); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 P R I N T L N 📌📌📌 // Prints the stored string to the standard output using printf. // Only valid for ps_ptr. Uses c_get() to safely handle null cases. template requires std::is_same_v void println() const { printf("%s: %s\n", name ? name : "unnamed", c_get()); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S E T 📌📌📌 // ps_ptr p; // uint32_t* raw = (uint32_t*)malloc(100 * sizeof(uint32_t)); // create memory manually // for (int i = 0; i < 100; ++i) {// write data // raw[i] = i * i; // } // p.set(raw, 100 * sizeof(uint32_t)); // ps_ptr takes over the pointer and remembers the size // for (int i = 0; i < 5; ++i) {// access with ps_ptr // printf("%u ", p[i]); // } // later at p.reset () or automatically in the destructor `free(raw)` void set(T* ptr, std::size_t size = 0) { if (mem.get() != ptr) { mem.reset(ptr); allocated_size = size; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A T 📌📌📌 // Special method for ps_ptr> template auto at(size_t index) -> typename std::enable_if>::value, ps_ptr&>::type { return static_cast*>(get())[index]; } using element_type = T; // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A S 📌📌📌 // ps_ptr generic; // generic.alloc(64); // uint32_t* p = generic.as(); template U* as() const { return reinterpret_cast(get()); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 I N S E R T 📌📌📌 // ps_ptr audioPath; // audioPath.assign("mp3files/test.mp3", "audioPath"); // audioPath.insert("/", 0); // result: "/mp3files/test.mp3" // // audioPath.insert("local/", 1); // result: "/local/mp3files/test.mp3" bool insert(const char* insertStr, std::size_t pos) { if (!insertStr || !valid()) return false; std::size_t originalLen = std::strlen(get()); std::size_t insertLen = std::strlen(insertStr); // Position outside the valid area?Then add to the end if (pos > originalLen) pos = originalLen; // New total length + 1 for '\ 0' std::size_t newLen = originalLen + insertLen + 1; ps_ptr temp; temp.alloc(newLen); if (!temp.valid()) return false; char* dst = temp.get(); // Copy up to the insertion position std::memcpy(dst, get(), pos); // Add new content std::memcpy(dst + pos, insertStr, insertLen); // Copy the rest of the original std::memcpy(dst + pos + insertLen, get() + pos, originalLen - pos + 1); // +1 für \0 // Take over new content this->assign(temp.get()); return true; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S H R I N K _ T O _ F I T 📌📌📌 // Only for Char: Put the buffer on the actual length +1 (for \ 0) template requires std::is_same_v void shrink_to_fit() { if (!mem) return; std::size_t len = std::strlen(get()); if (len == 0) return; ps_ptr temp; temp.alloc(len + 1); if (!temp.valid()) return; std::memcpy(temp.get(), get(), len + 1); // inklusive \0 this->assign(temp.get()); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T O _ U I N T 6 4 📌📌📌 // Retrieves the numeric value (uint64_t) from the stored string, parsing it as a number in the specified base (default: 16 for hexadecimal). // Example: If the stored string is "0x12345678", returns 0x12345678. // If the string is empty, null, or invalid, returns 0 and logs an error. // ps_ptr mediaSeqStr = "227213779"; // uint64_t mediaSeq = mediaSeqStr.to_uint64(10); // // ps_ptr addr = "0x1A3B"; // uint64_t val = addr.to_uint64(16); template requires std::is_same_v uint64_t to_uint64(int base = 10) const { static_assert(std::is_same_v, "to_uint64 is only valid for ps_ptr"); if (!mem || !get()) { log_e("to_uint64: No valid string data"); return 0; } const char* str = get(); char* end = nullptr; uint64_t result = std::strtoull(str, &end, base); if (end == str) { log_e("to_uint64: Invalid numeric value in '%s' for base %d", str, base); return 0; } return result; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T O _ U I N T 3 2 📌📌📌 // Retrieves the numeric value (uint32_t) from the stored string, parsing it as a number in the specified base (default: 10 for decimal). // Example: If the stored string is "0x1A3B", returns 0x1A3B (6715) for base 16. // If the string is empty, null, invalid, or exceeds UINT32_MAX (4294967295), returns 0 and logs an error. // Usage: // ps_ptr size = "227213779"; uint32_t val = size.to_uint32(10); // Returns 227213779 // ps_ptr addr = "0x1A3B"; uint32_t val = addr.to_uint32(16); // Returns 6715 uint32_t to_uint32(int base = 10) const { static_assert(std::is_same_v, "to_uint32 is only valid for ps_ptr"); if (!mem || !get()) { log_e("to_uint32: No valid string data"); return 0; } const char* str = get(); char* end = nullptr; unsigned long result = std::strtoul(str, &end, base); if (end == str) { log_e("to_uint32: Invalid numeric value in '%s' for base %i", str, base); return 0; } if (result > UINT32_MAX) { log_e("to_uint32: Value in '%s' exceeds UINT32_MAX (%u) for base %i", str, UINT32_MAX, base); return 0; } return static_cast(result); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T O _ I N T 3 2 📌📌📌 // Retrieves the numeric value (int32_t) from the stored string, parsing it as a number in the specified base (default: 10 for decimal). // Example: // ps_ptr temp = "-12345"; int32_t val = temp.to_int32(10); // Returns -12345 // ps_ptr hex = "0x7FFF"; int32_t val = hex.to_int32(16); // Returns 32767 // If the string is empty, null, invalid, or exceeds INT32 range (-2147483648 ... 2147483647), returns 0 and logs an error. int32_t to_int32(int base = 10) const { static_assert(std::is_same_v, "to_int32 is only valid for ps_ptr"); if (!mem || !get()) { log_e("to_int32: No valid string data"); return 0; } const char* str = get(); char* end = nullptr; long result = std::strtol(str, &end, base); if (result == 0 && errno == EINVAL) { log_e("invalid content %s", str); return 0; } if (end == str) { log_e("to_int32: Invalid numeric value in '%s' for base %i", str, base); return 0; } if (result < INT32_MIN || result > INT32_MAX) { log_e("to_int32: Value in '%s' exceeds INT32 range (%ld..%ld) for base %i", str, (long)INT32_MIN, (long)INT32_MAX, base); return 0; } return static_cast(result); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T O _ I N T 6 4 📌📌📌 // Retrieves the numeric value (int64_t) from the stored string, parsing it as a number // in the specified base (default: 10 for decimal). // // Examples: // ps_ptr val1 = "9223372036854775807"; int64_t v1 = val1.to_int64(); // OK // ps_ptr val2 = "-1234567890123"; int64_t v2 = val2.to_int64(); // OK // ps_ptr val3 = "0x7FFFFFFFFFFFFFFF"; int64_t v3 = val3.to_int64(16); // OK // // If the string is empty, invalid, or exceeds INT64 range (-9223372036854775808 .. 9223372036854775807), // returns 0 and logs an error. int64_t to_int64(int base = 10) const { static_assert(std::is_same_v, "to_int64 is only valid for ps_ptr"); if (!mem || !get()) { log_e("to_int64: No valid string data"); return 0; } const char* str = get(); char* end = nullptr; long long result = std::strtoll(str, &end, base); if (end == str) { log_e("to_int64: Invalid numeric value in '%s' for base %i", str, base); return 0; } if (result < INT64_MIN || result > INT64_MAX) { log_e("to_int64: Value in '%s' exceeds INT64 range (%lld..%lld) for base %i", str, (long long)INT64_MIN, (long long)INT64_MAX, base); return 0; } return static_cast(result); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 B I G _ E N D I A N 📌📌📌 // Reads up to 8 bytes from a uint8_t array in big-endian order and stores the value as a hexadecimal string (e.g., "0x12345678"). // Example: uint8_t data[] = {0x12, 0x34, 0x56, 0x78}; → stores "0x12345678" // If size > 8, only the first 8 bytes are processed. If size = 0 or data = nullptr, stores "0x0". template requires std::is_same_v void big_endian(const uint8_t* data, uint8_t size) { static_assert(std::is_same_v, "big_endian is only valid for ps_ptr"); if (!data || size == 0) { log_e("big_endian: Invalid input (data is null or size is 0)"); assign("0x0"); return; } if (size > 8) { log_e("big_endian: Size %u exceeds 8 bytes, truncating to 8", size); size = 8; } uint64_t result = 0; for (uint8_t i = 0; i < size; ++i) { result = (result << 8) | data[i]; } char buffer[19]; // Max: "0x" + 16 chars for uint64_t + null terminator snprintf(buffer, sizeof(buffer), "0x%llx", result); assign(buffer); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 L I T T L E _ E N D I A N 📌📌📌 // Reads up to 8 bytes from a uint8_t array in little-endian order and stores the value as a hexadecimal string (e.g., "0x12345678"). // Example: uint8_t data[] = {0x78, 0x56, 0x34, 0x12}; → stores "0x12345678" // If size > 8, only the first 8 bytes are processed. If size = 0 or data = nullptr, stores "0x0". template requires std::is_same_v void little_endian(const uint8_t* data, uint8_t size) { static_assert(std::is_same_v, "little_endian is only valid for ps_ptr"); if (!data || size == 0) { log_e("little_endian: Invalid input (data is null or size is 0)"); assign("0x0"); return; } if (size > 8) { log_e("little_endian: Size %u exceeds 8 bytes, truncating to 8", size); size = 8; } uint64_t result = 0; for (int i = size - 1; i >= 0; --i) { result = (result << 8) | data[i]; } char buffer[19]; // Max: "0x" + 16 chars for uint64_t + null terminator snprintf(buffer, sizeof(buffer), "0x%llx", result); assign(buffer); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E M O V E _ C H A R S 📌📌📌 // ps_ptr txt; // txt.copy_from("[00:12.45]", "time"); // txt.remove_chars("[]:."); // → 001245 void remove_chars(const char* chars) { if (!valid() || !chars) return; char* n = name; char* dst = get(); char* src = get(); while (*src) { if (!std::strchr(chars, *src)) { *dst++ = *src; } ++src; } *dst = '\0'; name = n; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E P L A C E 📌📌📌 // ps_ptr path; // path.assign("/user/temp/file.tmp"); // path.replace("temp", "music"); // result: "/user/music/file.tmp" bool replace(const char* from, const char* to) { if (!valid() || !from || !*from || !to) return false; const char* src = get(); std::size_t fromLen = std::strlen(from); std::size_t toLen = std::strlen(to); if (fromLen == 0) return false; // Nichts zu ersetzen std::vector result; const char* read = src; while (*read) { if (std::strncmp(read, from, fromLen) == 0) { // Match gefunden result.insert(result.end(), to, to + toLen); read += fromLen; } else { result.push_back(*read++); } } result.push_back('\0'); // Kopiere Ergebnis zurück this->copy_from(result.data(), result.size() - 1); return true; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T R I M 📌📌📌 // ps_ptr text3; // text3.assign(" Hello, World! "); // text3.trim(); // → "Hello, World!" template requires std::is_same_v void trim() { if (!mem) return; char* str = static_cast(mem.get()); std::size_t len = std::strlen(str); if (len == 0) return; // trim on the left char* start = str; while (*start && isspace(*start)) ++start; // trim on the right char* end = str + len - 1; while (end >= start && isspace(*end)) --end; *(end + 1) = '\0'; // If not at the beginning, copy everything forward if (start != str) { std::size_t new_len = end - start + 1; std::memmove(str, start, new_len + 1); } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 T R U N C A T E _ A T 📌📌📌 // ps_ptrt; // t.assign("Hello, World"); // t.truncate_at(','); --> "Hello" // ps_ptrt; // t.assign("Hello, World"); // t.truncate_at('5'); --> "Hello" void truncate_at(char ch) { if (!valid()) return; char* str = get(); char* pos = strchr(str, ch); if (pos) *pos = '\0'; } void truncate_at(std::size_t pos) { if (!valid()) return; if (pos >= strlen()) return; char* str = get(); std::size_t len = std::strlen(str); if (pos < len) str[pos] = '\0'; else log_e("truncate pos %i out of length %i", pos, len); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E M O V E _ P R E F I X 📌📌📌 // ps_ptr t; // t.assign("../../2093120-b/RISMI/stream01/streamPlaylist.m3u8"); // t.remove_prefix("../../"); // Result: "2093120-b/RISMI/stream01/streamPlaylist.m3u8" void remove_prefix(const char* prefix) { if (!valid() || !prefix) return; std::size_t prefix_len = std::strlen(prefix); if (std::strncmp(get(), prefix, prefix_len) == 0) { char* str = get(); std::size_t len = std::strlen(str); std::memmove(str, str + prefix_len, len - prefix_len + 1); // inkl. '\0' } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E M O V E _ B E F O R E 📌📌📌 // t.assign("../../2093120-b/RISMI/stream01/streamPlaylist.m3u8"); // t.remove_before('/'); // removed until the first '/' // Result: "../2093120-b/RISMI/stream01/streamPlaylist.m3u8" void remove_before(char ch, bool includeChar = true) { if (!valid()) return; char* str = get(); char* pos = strchr(str, ch); if (pos) { if (!includeChar) ++pos; // wenn nicht inklusive: das Zeichen behalten std::size_t remaining = std::strlen(pos); std::memmove(str, pos, remaining + 1); // inkl. '\0' } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E M O V E _ B E F O R E ( B Y I N D E X ) 📌📌📌 // t.assign("HelloWorld"); // t.remove_before(5); // entfernt "Hello" // t.remove_before(5, false); // entfernt nur "Hell", das Zeichen an idx bleibt void remove_before(int idx, bool includeIdx = true) { if (!valid()) return; char* str = get(); std::size_t len = std::strlen(str); if (idx < 0 || static_cast(idx) > len) return; char* pos = str + idx; if (!includeIdx && idx < len) ++pos; // wenn das Zeichen nicht entfernt werden soll std::size_t remaining = std::strlen(pos); std::memmove(str, pos, remaining + 1); // inkl. '\0' } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S H I F T _ L E F T 📌📌📌 // Show the contents of the buffer around n bytes to the left and fill the rest with zeros // Consider UTF-16 data and the size of the allocated memory void shift_left(int n) { if (!mem || allocated_size == 0) { printf("Error: No allocated memory or invalid buffer\n"); return; } if (n < 0 || static_cast(n) > allocated_size) { printf("Error: Invalid shift amount %d, allocated_size=%zu\n", n, allocated_size); return; } // if (n % 2 != 0) { // printf("Warning: Shift amount %d is not even, adjusting to %d for UTF-16 alignment\n", n, n + 1); // n += 1; // make sure n is even for UTF-16 // } char* str = mem.get(); if (n == 0) return; // show the buffer around n bytes to the left std::size_t remaining = allocated_size - n; std::memmove(str, str + n, remaining); // fill the rest of the memory with zeros std::memset(str + remaining, 0, n); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C O N T A I N S 📌📌📌 // if (t.contains("xyz")) { // "xyz" occurs in t // } bool contains(const char* substr) const { return substr && this->valid() && std::strstr(this->get(), substr); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C O N T A I N S _ W I T H _ I C A S E 📌📌📌 bool contains_with_icase(const char* substr) const { if (!substr || !this->valid()) { return false; } const char* haystack = this->get(); const char* needle = substr; // Wir kopieren den Text in temporäre lowercase-Strings std::string haystack_lower(haystack); std::string needle_lower(needle); std::transform(haystack_lower.begin(), haystack_lower.end(), haystack_lower.begin(), [](unsigned char c) { return std::tolower(c); }); std::transform(needle_lower.begin(), needle_lower.end(), needle_lower.begin(), [](unsigned char c) { return std::tolower(c); }); return haystack_lower.find(needle_lower) != std::string::npos; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C L E A R 📌📌📌 void clear() { if (mem && allocated_size > 0) { std::memset(mem.get(), 0, allocated_size); length_ = 0; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S I Z E 📌📌📌 size_t size() const { return allocated_size; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 E M P T Y 📌📌📌 bool empty() const noexcept { return allocated_size == 0; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 V A L I D 📌📌📌 bool valid() const { return mem != nullptr; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E S E T 📌📌📌 void reset() { mem.reset(); allocated_size = 0; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 H E X _ D U M P 📌📌📌 // ps_ptrbuff; // buff.calloc(10); // buff.assign("eng\n"); // buff.hex_dump(6); // // 0x65 0x6E 0x67 0x0A 0x00 0x00 // e n g LF NUL NUL void hex_dump(uint16_t n = UINT16_MAX) { if (!valid()) { printf("hex_dump: invalid buffer\n"); return; } if (allocated_size < n) n = allocated_size; if (n == 0) { printf("hex_dump: no data\n"); return; } uint8_t items_per_line = 30; static const char* sym[32] = {"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS ", "TAB", "LF ", "VT ", "FF ", "CR ", "SO ", "SI ", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM ", "SUB", "ESC", "FS ", "GS ", "RS ", "US "}; const uint8_t* buff = reinterpret_cast(get()); if (!name) printf("dumping %u bytes:\n", n); else printf("%s dumping %u bytes:\n", name, n); for (uint16_t i = 0; i < n; i += items_per_line) { uint16_t m = std::min(n, i + items_per_line); uint8_t s = 0; // Hex view for (uint16_t j = i; j < m; j++) { printf("0x%02X ", buff[j]); if (++s % 10 == 0) printf(" "); } printf("\n"); // ASCII / symbolic view s = 0; for (uint16_t j = i; j < m; j++) { uint8_t c = buff[j]; if (c >= 32) printf("%c ", c); else printf("%s ", sym[c]); if (++s % 10 == 0) printf(" "); } printf("\n"); } printf("\n"); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 O P E R A T O R 📌📌📌 (within class) // —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // Copy-Assignment (only for char sensible) ps_ptr& operator=(const ps_ptr& other) { if (this != &other) { if constexpr (std::is_same_v) { assign(other.get()); } else { static_assert(!std::is_same_v, "Copy assignment disabled for this type"); } } return *this; } ps_ptr& operator=(const T* raw_ptr) { // e.g. ps_ptr h; h = "123"; if constexpr (std::is_same_v) { assign(raw_ptr); } else { static_assert(!std::is_same_v, "Assignment from const pointer disabled for this type"); } return *this; } ps_ptr& operator=(T* raw_ptr) { if (mem.get() != raw_ptr) { mem.reset(raw_ptr); allocated_size = 0; // (raw_ptr != nullptr) ? /* Calculate size here */ : 0; } return *this; } // Move-Assignment-Operator ps_ptr& operator=(ps_ptr&& other) noexcept { if (this != &other) { if (name) { free(name); name = nullptr; } mem = std::move(other.mem); allocated_size = other.allocated_size; name = other.name; other.allocated_size = 0; other.name = nullptr; } return *this; } T* operator->() const { return get(); } T& operator*() const { return *get(); } // Safe operator[] with logging T& operator[](std::size_t index) noexcept { if (index >= allocated_size) { log_e("[%s:%i] ps_ptr[]: Index %zu out of bounds (size = %zu, name = %s)", __FILE__, __LINE__, index, allocated_size, name ? name : "unnamed"); return dummy; // Access allowed, but ineffective } return mem[index]; } const T& operator[](std::size_t index) const noexcept { if (index >= allocated_size) { log_e("[%s:%i] ps_ptr[]: Index %zu out of bounds (size = %zu, name = %s)", __FILE__, __LINE__, index, allocated_size, name ? name : "unnamed"); return dummy; } return mem[index]; } // C++20: Spaceship-Operator — Automatically creates all comparison operators auto operator<=>(const ps_ptr& other) const noexcept { if constexpr (std::is_same_v) { const char* s1 = get(); const char* s2 = other.get(); int result = std::strcmp(s1 ? s1 : "", s2 ? s2 : ""); return (result < 0) ? std::strong_ordering::less : (result > 0) ? std::strong_ordering::greater : std::strong_ordering::equal; } else { // for non-string types: Compare the pointer address return mem.get() <=> other.mem.get(); } } // ps_ptr a = "Hallo"; a += " Welt"; template requires std::is_same_v ps_ptr& operator+=(const char* rhs) { append(rhs); return *this; } // ps_ptr a = "Hallo", b = " Welt"; a += b; template requires std::is_same_v ps_ptr& operator+=(const ps_ptr& rhs) { append(rhs); return *this; } // Iterator compatible accesses (make ps_ptr STL compatible) T* begin() noexcept { return mem.get(); } const T* begin() const noexcept { return mem.get(); } T* end() noexcept { return mem.get() + allocated_size; } const T* end() const noexcept { return mem.get() + allocated_size; } const T* cbegin() const noexcept { return mem.get(); } const T* cend() const noexcept { return mem.get() + allocated_size; } // 🔹 Optional: pointer arithmetic (syntactic only) T* operator+(std::size_t offset) noexcept { return mem.get() + offset; } const T* operator+(std::size_t offset) const noexcept { return mem.get() + offset; } void fill(const T& value) { std::fill(begin(), end(), value); } // Access as std::span – secure view of the data std::span span() noexcept { return std::span(mem.get(), allocated_size); } std::span span() const noexcept { return std::span(mem.get(), allocated_size); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 F O R M A T 📌📌📌 (fmt lib within class) // —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— template std::string to_string_any(const V& v) { using Raw = std::remove_cv_t>; using D = std::decay_t; // ps_ptr if constexpr (std::is_same_v>) { const char* str = v.c_get(); return str ? std::string(str) : ""; } // Treat character arrays as text in the same way as string literals. else if constexpr (std::is_array_v && std::is_same_v, char>) { return std::string(v); } // BOOL else if constexpr (std::is_same_v) { return v ? "true" : "false"; } // FLOAT else if constexpr (std::is_same_v || std::is_same_v) { char buf[32]; snprintf(buf, sizeof(buf), "%g", v); return buf; } // INTEGER / ENUM else if constexpr (std::is_integral_v || std::is_enum_v) { return std::to_string(static_cast(v)); } // POINTER else if constexpr (std::is_pointer_v) { // char* if constexpr (std::is_same_v || std::is_same_v) { return v ? std::string(v) : ""; } // unsigned char* else if constexpr (std::is_same_v || std::is_same_v) { return v ? std::string(reinterpret_cast(v)) : ""; } // generic pointer else { char buf[32]; snprintf(buf, sizeof(buf), "%p", static_cast(v)); return buf; } } // fallback else { return v; } } void format_append(std::string& out, const char* fmt) { out += fmt; } template void format_append(std::string& out, const char* fmt, First&& first, Rest&&... rest) { while (*fmt) { // Start eines Formatfeldes? if (*fmt == '{') { // Escape {{ if (fmt[1] == '{') { out += '{'; fmt += 2; continue; } // Ende suchen const char* end = std::strchr(fmt, '}'); // Fehlerfall: keine schließende Klammer if (!end) { out += *fmt++; continue; } // Inhalt zwischen { ... } std::string spec_str(fmt + 1, end - fmt - 1); format_spec fs; // Nur parsen wenn Inhalt existiert // also {:02} usw. if (!spec_str.empty()) { fs = parse_format(spec_str.c_str()); } // Wert formatieren out += format_value(std::forward(first), fs); // Rekursiv weitermachen format_append(out, end + 1, std::forward(rest)...); return; } // Escape }} if (fmt[0] == '}' && fmt[1] == '}') { out += '}'; fmt += 2; continue; } out += *fmt++; } } struct format_spec { int width = 0; int precision = -1; char fill = ' '; char type = 0; bool upper = false; bool align_right = false; }; inline format_spec parse_format(const char* fmt) { format_spec fs; // {:02} if (*fmt == ':') { ++fmt; // Right-aligned? if (*fmt == '-') { fs.align_right = true; ++fmt; } // leading zero if (*fmt == '0') { fs.fill = '0'; ++fmt; } // width while (isdigit(*fmt)) { fs.width = fs.width * 10 + (*fmt - '0'); ++fmt; } // precision if (*fmt == '.') { ++fmt; fs.precision = 0; while (isdigit(*fmt)) { fs.precision = fs.precision * 10 + (*fmt - '0'); ++fmt; } } // type if (*fmt) { fs.type = *fmt; if (*fmt == 'X') fs.upper = true; } } return fs; } template std::string format_value(const V& value, const format_spec& fs) { char buf[64]; // bool ist in C++ ein Integraltyp, soll hier aber als Text ausgegeben werden. if constexpr (std::is_same_v>, bool>) { return value ? "true" : "false"; } // ps_ptr if constexpr (std::is_same_v>, ps_ptr>) { const char* str = value.c_get(); return str ? std::string(str) : std::string(""); } // CHAR - separate Behandlung vor is_integral_v if constexpr (std::is_same_v>, char>) { if (fs.type == 'c' || fs.type == 0) { // 'c' oder kein Specifier buf[0] = value; buf[1] = '\0'; return buf; } } if constexpr (std::is_integral_v) { // HEX if (fs.type == 'X') { if (fs.width > 0) { const int safe_width = (fs.width > 30) ? 30 : fs.width; snprintf(buf, sizeof(buf), "%0*llX", safe_width, (unsigned long long)value); } else { snprintf(buf, sizeof(buf), "%llX", (unsigned long long)value); } std::string s = buf; apply_width(s, fs, true); return s; } // HEX if (fs.type == 'x') { if (fs.width > 0) { const int safe_width = (fs.width > 30) ? 30 : fs.width; snprintf(buf, sizeof(buf), "%0*llx", safe_width, (unsigned long long)value); } else { snprintf(buf, sizeof(buf), "%llx", (unsigned long long)value); } std::string s = buf; apply_width(s, fs, true); return s; } // Integer mit Padding if (fs.width > 0) { const int safe_width = (fs.width > 30) ? 30 : fs.width; if (fs.fill == '0') { snprintf(buf, sizeof(buf), "%0*lld", safe_width, (long long)value); } else { snprintf(buf, sizeof(buf), "%*lld", safe_width, (long long)value); } std::string s = buf; apply_width(s, fs, true); return s; } std::string s = to_string_any(value); apply_width(s, fs, true); return s; } // FLOAT if constexpr (std::is_floating_point_v) { if (fs.precision >= 0) { snprintf(buf, sizeof(buf), "%.*f", fs.precision, value); std::string s = buf; apply_width(s, fs, true); return s; } snprintf(buf, sizeof(buf), "%g", value); std::string s = buf; apply_width(s, fs, true); return s; } std::string s = to_string_any(value); apply_width(s, fs, false); return s; } inline size_t count_placeholders(const char* fmt) { size_t count = 0; while (*fmt) { // escaped {{ if (fmt[0] == '{' && fmt[1] == '{') { fmt += 2; continue; } // escaped }} if (fmt[0] == '}' && fmt[1] == '}') { fmt += 2; continue; } // echtes { if (*fmt == '{') { const char* end = std::strchr(fmt, '}'); if (end) { ++count; fmt = end + 1; continue; } } ++fmt; } return count; } inline void apply_width(std::string& s, const format_spec& fs, bool numeric = false) { if (fs.width <= 0) return; if ((int)s.size() >= fs.width) return; size_t missing = fs.width - s.size(); // Always fill in the numbers at the top if (numeric) { s.insert(s.begin(), missing, fs.fill); return; } // Strings if (fs.align_right) { s.insert(s.begin(), missing, fs.fill); } else { s.append(missing, fs.fill); } } }; // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 O P E R A T O R 📌📌📌 (witout class) // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // ps_ptr a = "Hello "; ps_ptr b = "World"; ps_ptr c = a + b; // results in new string template requires std::is_same_v ps_ptr operator+(const ps_ptr& lhs, const ps_ptr& rhs) { ps_ptr result(lhs); result.append(rhs); return result; } template requires std::is_same_v ps_ptr operator+(const ps_ptr& lhs, const char* rhs) { ps_ptr result(lhs); result.append(rhs); return result; } template requires std::is_same_v ps_ptr operator+(const char* lhs, const ps_ptr& rhs) { ps_ptr result(lhs); result.append(rhs); return result; } template requires std::is_same_v bool operator==(const ps_ptr& lhs, const ps_ptr& rhs) { const char* a = lhs.get(); const char* b = rhs.get(); if (!a || !b) return a == b; // beide nullptr → true return std::strcmp(a, b) == 0; } template requires std::is_same_v bool operator==(const ps_ptr& lhs, const char* rhs) { const char* a = lhs.get(); if (!a || !rhs) return a == rhs; return std::strcmp(a, rhs) == 0; } template requires std::is_same_v bool operator==(const char* lhs, const ps_ptr& rhs) { return rhs == lhs; } template requires std::is_same_v bool operator!=(const ps_ptr& lhs, const ps_ptr& rhs) { return !(lhs == rhs); } template requires std::is_same_v bool operator!=(const ps_ptr& lhs, const char* rhs) { return !(lhs == rhs); } template requires std::is_same_v bool operator!=(const char* lhs, const ps_ptr& rhs) { return !(lhs == rhs); } template requires std::is_same_v bool operator<(const ps_ptr& lhs, const ps_ptr& rhs) { const char* a = lhs.get(); const char* b = rhs.get(); if (!a || !b) return a < b; // nullptr kleiner als nicht-null return std::strcmp(a, b) < 0; } template requires std::is_same_v bool operator<(const ps_ptr& lhs, const char* rhs) { const char* a = lhs.get(); if (!a || !rhs) return a < rhs; return std::strcmp(a, rhs) < 0; } template requires std::is_same_v bool operator<(const char* lhs, const ps_ptr& rhs) { const char* b = rhs.get(); if (!lhs || !b) return lhs < b; return std::strcmp(lhs, b) < 0; } template requires std::is_same_v bool operator>(const ps_ptr& lhs, const ps_ptr& rhs) { return rhs < lhs; } template requires std::is_same_v bool operator>(const ps_ptr& lhs, const char* rhs) { return rhs && (std::strcmp(lhs.get(), rhs) > 0); } template requires std::is_same_v bool operator>(const char* lhs, const ps_ptr& rhs) { return rhs < lhs; } template requires std::is_same_v bool operator<=(const ps_ptr& lhs, const ps_ptr& rhs) { return !(rhs < lhs); } template requires std::is_same_v bool operator<=(const ps_ptr& lhs, const char* rhs) { return !(lhs > rhs); } template requires std::is_same_v bool operator<=(const char* lhs, const ps_ptr& rhs) { return !(rhs < lhs); } template requires std::is_same_v bool operator>=(const ps_ptr& lhs, const ps_ptr& rhs) { return !(lhs < rhs); } template requires std::is_same_v bool operator>=(const ps_ptr& lhs, const char* rhs) { return !(lhs < rhs); } template requires std::is_same_v bool operator>=(const char* lhs, const ps_ptr& rhs) { return !(lhs < rhs); } template requires std::is_same_v std::ostream& operator<<(std::ostream& os, const ps_ptr& str) { const char* s = str.get(); if (s) os << s; return os; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 S T R U C T U R E S 📌📌📌 // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // typedef struct _hwoe{ // bool ssl; // ps_ptr hwoe; // host without extension // uint16_t port; // ps_ptrextension[100]; // ps_ptr query_string; // } hwoe_t; // PS_STRUCT_FREE_MEMBERS(hwoe_t, // ptr->hwoe.reset(); // for(int i = 0; i< 100; i++)ptr->extension[i].reset(); // ptr->query_string.reset(); // ) // ps_struct_ptr result; // result.alloc(); // result->extension.copy_from(path // ..... template class ps_struct_ptr { private: std::unique_ptr mem; char* name = nullptr; // name member // Auxiliary function for setting the name void set_name(const char* new_name) { if (name) { free(name); name = nullptr; } if (new_name) { std::size_t len = std::strlen(new_name) + 1; if (psramFound()) { name = static_cast(ps_malloc(len)); } else { name = static_cast(malloc(len)); } if (name) { std::memcpy(name, new_name, len); } else { printf("OOM: failed to allocate %zu bytes for name %s\n", len, new_name); } } } public: ps_struct_ptr() = default; // default constructor explicit ps_struct_ptr(const char* name_str) { // named constructor set_name(name_str); } ~ps_struct_ptr() { // destructor if (mem) { log_w("Destructor called for %s: Freeing %zu bytes at %p", name ? name : "unnamed", sizeof(T), mem.get()); } else { log_w("Destructor called for %s: No memory to free.", name ? name : "unnamed"); } if (name) { free(name); name = nullptr; } } ps_struct_ptr(ps_struct_ptr&& other) noexcept { // Move constructor mem = std::move(other.mem); name = other.name; other.name = nullptr; } ps_struct_ptr& operator=(ps_struct_ptr&& other) noexcept { // Move Assignment operator if (this != &other) { if (name) { free(name); name = nullptr; } mem = std::move(other.mem); name = other.name; other.name = nullptr; } return *this; } ps_struct_ptr(const ps_struct_ptr&) = delete; ps_struct_ptr& operator=(const ps_struct_ptr&) = delete; // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A L L O C 📌📌📌 void alloc(const char* name_str = nullptr) { reset(); if (name_str) { set_name(name_str); } void* raw_mem = nullptr; if (psramFound()) { raw_mem = ps_malloc(sizeof(T)); } else { raw_mem = malloc(sizeof(T)); } if (raw_mem) { mem.reset(new (raw_mem) T()); } else { printf("OOM: failed to allocate %zu bytes for %s\n", sizeof(T), name ? name : (name_str ? name_str : "unnamed")); } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C A L L O C 📌📌📌 void calloc(const char* name_str = nullptr) { reset(); if (name_str) { set_name(name_str); } void* raw_mem = nullptr; if (psramFound()) { raw_mem = ps_calloc(1, sizeof(T)); } else { raw_mem = calloc(1, sizeof(T)); } if (raw_mem) { mem.reset(static_cast(raw_mem)); } else { printf("OOM: failed to allocate %zu bytes for %s\n", sizeof(T), name ? name : (name_str ? name_str : "unnamed")); } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E S E T 📌📌📌 void reset() { mem.reset(); // Name is not released, remains up to the destructor } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 O P E R A T O R -> 📌📌📌 T* operator->() { return mem.get(); } const T* operator->() const { return mem.get(); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 O P E R A T O R * 📌📌📌 T& operator*() { return *mem; } const T& operator*() const { return *mem; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 G E T 📌📌📌 T* get() noexcept { return mem.get(); } const T* get() const noexcept { return mem.get(); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— inline void free_all_ptr_members(); // prototypes inline void free_field(char*& field); void set_ptr_field(char** field, const char* text) { if (!mem || !field) return; if (*field) { free(*field); *field = nullptr; } if (text) { std::size_t len = strlen(text) + 1; char* p = nullptr; if (psramFound()) { // Check at the runtime whether PSRAM is available p = static_cast(ps_malloc(len)); } else { p = static_cast(malloc(len)); } if (p) { memcpy(p, text, len); *field = p; } } } bool valid() const { return mem.get() != nullptr; } std::size_t size() const { return sizeof(T); } void clear() { reset(); } }; // —————————————————————————————————————————————————————————————— // Macro for the declaration of releasing fields for a structure #define PS_STRUCT_FREE_MEMBERS(TYPE, ...) \ template <> inline void ps_struct_ptr::free_all_ptr_members() { \ auto ptr = this->get(); /* 'ptr' ist hier ein Pointer auf die Struktur TYPE */ \ if (ptr) { /* Sicherheitsprüfung, um sicherzustellen, dass ptr gültig ist */ \ __VA_ARGS__ \ } \ } inline void free_field(char*& field) { if (field) { free(field); field = nullptr; } } // Variadic Auxiliary function template inline void free_fields(Args&... fields) { (free_field(fields), ...); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 2 D A R R A Y 📌📌📌 // // 2D Array in PSRAM with Bounds-Check, log_e and reset() // // ps_array2d s_samples; // standard constructor // s_samples.alloc(2, 1152); // mem alloc for [2][1152] // // int ch = 0; // exanple: channel 0 // // declaration of PCM1 as a pointer on the first element of the line (similar to samples [CH]) // int32_t* pcm1; // // pcm1 = s_samples.get_raw_row_ptr(ch); // // use 'pcm1' now, as if it were an int32_t array of size 1152 // pcm1[0] = 42; // pcm1[1151] = 99; // // s_samples.reset(); // template class ps_array2d { private: std::unique_ptr mem; size_t rows = 0; size_t cols = 0; char* name = nullptr; // Neues Mitglied für den Objektnamen // Hilfsfunktion zum Setzen des Namens void set_name(const char* new_name) { if (name) { free(name); name = nullptr; } if (new_name) { std::size_t len = std::strlen(new_name) + 1; if (psramFound()) { name = static_cast(ps_malloc(len)); } else { name = static_cast(malloc(len)); } if (name) { std::memcpy(name, new_name, len); } else { printf("OOM: failed to allocate %zu bytes for name %s\n", len, new_name); } } } public: // Standardkonstruktor ps_array2d() = default; // Konstruktor mit Namen explicit ps_array2d(const char* name_str) { set_name(name_str); } // Destruktor ~ps_array2d() { if (mem) { log_w("Destructor called for %s: Freeing %zu bytes at %p", name ? name : "unnamed", rows * cols * sizeof(T), mem.get()); } else { log_w("Destructor called for %s: No memory to free.", name ? name : "unnamed"); } if (name) { free(name); name = nullptr; } } // Move-Constructor ps_array2d(ps_array2d&& other) noexcept { mem = std::move(other.mem); rows = other.rows; cols = other.cols; name = other.name; other.rows = 0; other.cols = 0; other.name = nullptr; } // Move-Assignment-Operator ps_array2d& operator=(ps_array2d&& other) noexcept { if (this != &other) { if (name) { free(name); name = nullptr; } mem = std::move(other.mem); rows = other.rows; cols = other.cols; name = other.name; other.rows = 0; other.cols = 0; other.name = nullptr; } return *this; } ps_array2d(const ps_array2d&) = delete; ps_array2d& operator=(const ps_array2d&) = delete; // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A L L O C 📌📌📌 void alloc(size_t r, size_t c, const char* alloc_name = nullptr, bool usePSRAM = true) { reset(); if (alloc_name) { set_name(alloc_name); } rows = r; cols = c; size_t total_size = rows * cols * sizeof(T); total_size = (total_size + 15) & ~15; // Align to 16 bytes if (psramFound() && usePSRAM) { mem.reset(static_cast(ps_malloc(total_size))); } else { mem.reset(static_cast(malloc(total_size))); } if (!mem) { printf("OOM: failed to allocate %zu bytes for %s\n", total_size, name ? name : (alloc_name ? alloc_name : "unnamed")); rows = 0; cols = 0; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C A L L O C 📌📌📌 void calloc(size_t r, size_t c, const char* alloc_name = nullptr, bool usePSRAM = true) { reset(); if (alloc_name) { set_name(alloc_name); } rows = r; cols = c; size_t total_size = rows * cols * sizeof(T); total_size = (total_size + 15) & ~15; // Align to 16 bytes void* raw_mem = nullptr; if (psramFound() && usePSRAM) { raw_mem = ps_calloc(1, total_size); } else { raw_mem = calloc(1, total_size); } if (raw_mem) { mem.reset(static_cast(raw_mem)); } else { printf("OOM: failed to allocate %zu bytes for %s\n", total_size, name ? name : (alloc_name ? alloc_name : "unnamed")); rows = 0; cols = 0; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E S E T 📌📌📌 void reset() { mem.reset(); rows = 0; cols = 0; // Name is not released, remains up to the destructor } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 O P E R A T O R [] 📌📌📌 T* operator[](size_t row) { return mem.get() + row * cols; } const T* operator[](size_t row) const { return mem.get() + row * cols; } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 G E T 📌📌📌 T* get() { return mem.get(); } const T* get() const { return mem.get(); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 V A L I D / S I Z E 📌📌📌 bool valid() const { return mem.get() != nullptr; } size_t get_rows() const { return rows; } size_t get_cols() const { return cols; } }; // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // psram_unique_ptr.hpp (Auszug mit ps_array3d) // // 📌📌📌 3 D A R R A Y 📌📌📌 // // 3D Array in PSRAM with Bounds-Check, log_e and reset() // // ps_array3d s_sbsample; // s_sbsample.alloc(2, 36, 32); // // int ch = 0; // example of the first dimension // int gr = 0; // example of the group index // // C- pointer to a 1D-Array [32] // int32_t (*sample)[32]; // // // allocation using the new method get_raw_row_ptr and the appropriate reinterpret_cast // sample = reinterpret_cast(s_sbsample.get_raw_row_ptr(ch, 18 * gr)); // // // use 'sample' now: // (*sample)[0] = 10; // // s_sbsample.reset(); // template class ps_array3d { private: std::unique_ptr mem; size_t dim1 = 0; size_t dim2 = 0; size_t dim3 = 0; char* name = nullptr; // Neues Mitglied für den Objektnamen // Hilfsfunktion zum Setzen des Namens void set_name(const char* new_name) { if (name) { free(name); name = nullptr; } if (new_name) { std::size_t len = std::strlen(new_name) + 1; if (psramFound()) { name = static_cast(ps_malloc(len)); } else { name = static_cast(malloc(len)); } if (name) { std::memcpy(name, new_name, len); } else { printf("OOM: failed to allocate %zu bytes for name %s\n", len, new_name); } } } public: // Standardkonstruktor ps_array3d() = default; // Konstruktor mit Namen explicit ps_array3d(const char* name_str) { set_name(name_str); } // Destruktor ~ps_array3d() { if (mem) { log_w("Destructor called for %s: Freeing %zu bytes at %p", name ? name : "unnamed", dim1 * dim2 * dim3 * sizeof(T), mem.get()); } else { log_w("Destructor called for %s: No memory to free.", name ? name : "unnamed"); } if (name) { free(name); name = nullptr; } } // Move-Konstruktor ps_array3d(ps_array3d&& other) noexcept { mem = std::move(other.mem); dim1 = other.dim1; dim2 = other.dim2; dim3 = other.dim3; name = other.name; other.dim1 = 0; other.dim2 = 0; other.dim3 = 0; other.name = nullptr; } // Move-Assignment-Operator ps_array3d& operator=(ps_array3d&& other) noexcept { if (this != &other) { if (name) { free(name); name = nullptr; } mem = std::move(other.mem); dim1 = other.dim1; dim2 = other.dim2; dim3 = other.dim3; name = other.name; other.dim1 = 0; other.dim2 = 0; other.dim3 = 0; other.name = nullptr; } return *this; } ps_array3d(const ps_array3d&) = delete; ps_array3d& operator=(const ps_array3d&) = delete; // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 A L L O C 📌📌📌 void alloc(size_t d1, size_t d2, size_t d3, const char* alloc_name = nullptr, bool usePSRAM = true) { reset(); if (alloc_name) { set_name(alloc_name); } dim1 = d1; dim2 = d2; dim3 = d3; size_t total_size = dim1 * dim2 * dim3 * sizeof(T); total_size = (total_size + 15) & ~15; // Align to 16 bytes if (psramFound() && usePSRAM) { mem.reset(static_cast(ps_malloc(total_size))); } else { mem.reset(static_cast(malloc(total_size))); } if (!mem) { printf("OOM: failed to allocate %zu bytes for %s\n", total_size, name ? name : (alloc_name ? alloc_name : "unnamed")); dim1 = 0; dim2 = 0; dim3 = 0; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 C A L L O C 📌📌📌 void calloc(size_t d1, size_t d2, size_t d3, const char* alloc_name = nullptr, bool usePSRAM = true) { reset(); if (alloc_name) { set_name(alloc_name); } dim1 = d1; dim2 = d2; dim3 = d3; size_t total_size = dim1 * dim2 * dim3 * sizeof(T); total_size = (total_size + 15) & ~15; // Align to 16 bytes void* raw_mem = nullptr; if (psramFound() && usePSRAM) { raw_mem = ps_calloc(1, total_size); } else { raw_mem = calloc(1, total_size); } if (raw_mem) { mem.reset(static_cast(raw_mem)); } else { printf("OOM: failed to allocate %zu bytes for %s\n", total_size, name ? name : (alloc_name ? alloc_name : "unnamed")); dim1 = 0; dim2 = 0; dim3 = 0; } } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 R E S E T 📌📌📌 void reset() { mem.reset(); dim1 = 0; dim2 = 0; dim3 = 0; // Name wird nicht freigegeben, bleibt bis zum Destruktor erhalten } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 O P E R A T O R [] 📌📌📌 class Proxy { T* ptr; size_t cols, depth; public: Proxy(T* p, size_t c, size_t d) : ptr(p), cols(c), depth(d) {} T* operator[](size_t col) { return ptr + col * depth; } const T* operator[](size_t col) const { return ptr + col * depth; } }; Proxy operator[](size_t d1) { return Proxy(mem.get() + d1 * dim2 * dim3, dim2, dim3); } const Proxy operator[](size_t d1) const { return Proxy(mem.get() + d1 * dim2 * dim3, dim2, dim3); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 G E T 📌📌📌 T* get() { return mem.get(); } const T* get() const { return mem.get(); } // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— // 📌📌📌 V A L I D / S I Z E 📌📌📌 bool valid() const { return mem.get() != nullptr; } size_t get_dim1() const { return dim1; } size_t get_dim2() const { return dim2; } size_t get_dim3() const { return dim3; } }; #endif