diff --git a/lua/api/core/ble.lua b/lua/api/core/ble.lua index d7771b7..dc3a424 100644 --- a/lua/api/core/ble.lua +++ b/lua/api/core/ble.lua @@ -19,6 +19,10 @@ function ble.init(name) end ---Stops the BLE stack and releases its memory. function ble.deinit() end +---Whether the BLE stack is running. +---@return boolean +function ble.isInitialized() end + ---Scans for advertising devices. ---@param durationMs? integer Defaults to 3000. ---@return BleDevice[]? devices diff --git a/native/include/lua/layout.h b/native/include/lua/layout.h index 9a6c5c2..ceb4eea 100644 --- a/native/include/lua/layout.h +++ b/native/include/lua/layout.h @@ -131,10 +131,17 @@ public: const char* error = nullptr; uint16_t focus = NONE; + // Growth doubling needs the old and new buffers live at once, which is the + // allocation that fails first on a tight heap. One reservation covers a + // typical screen so a build reallocates only if it genuinely gets large. + static constexpr size_t RESERVE = 64; + void reset() { focus = NONE; nodes.clear(); specs.clear(); + nodes.reserve(RESERVE); + specs.reserve(RESERVE); labelAt.clear(); labels.clear(); styles.clear(); diff --git a/native/include/lua/providers.h b/native/include/lua/providers.h index a11344b..72cbadc 100644 --- a/native/include/lua/providers.h +++ b/native/include/lua/providers.h @@ -231,6 +231,7 @@ public: virtual ~BleProvider() = default; virtual Status init(const std::string* name) = 0; virtual void deinit() = 0; + virtual bool isInitialized() const = 0; virtual Status scan(int32_t durationMs, std::vector& devices) = 0; virtual Status connect(const std::string& address) = 0; virtual void disconnect() = 0; diff --git a/native/src/bindings/core/ble.cpp b/native/src/bindings/core/ble.cpp index ad85055..ed53af5 100644 --- a/native/src/bindings/core/ble.cpp +++ b/native/src/bindings/core/ble.cpp @@ -13,6 +13,9 @@ namespace { int init(lua_State* state) { std::string name; const bool hasName = optionalString(state, 1, name); + // The controller needs a large aggregate allocation; reclaim app garbage + // first so a live screen's churn does not fail the init. + lua_gc(state, LUA_GCCOLLECT, 0); return pushStatus( state, Runtime::from(state)->ble().init(hasName ? &name : nullptr)); } @@ -22,6 +25,11 @@ int deinit(lua_State* state) { return 0; } +int isInitialized(lua_State* state) { + lua_pushboolean(state, Runtime::from(state)->ble().isInitialized()); + return 1; +} + int scan(lua_State* state) { const int32_t durationMs = optionalInt(state, 1, 3000); luaL_argcheck(state, durationMs > 0, 1, "must be positive"); @@ -97,6 +105,9 @@ const luaL_Reg FUNCTIONS[] = { {"init", init}, // --- Stops the BLE stack and releases its memory. {"deinit", deinit}, + // --- Whether the BLE stack is running. + // @return boolean + {"isInitialized", isInitialized}, // --- Scans for advertising devices. // @param durationMs integer|nil Defaults to 3000. // @return BleDevice[]|nil devices diff --git a/native/src/bindings/core/wifi.cpp b/native/src/bindings/core/wifi.cpp index e26ebc9..9c911ae 100644 --- a/native/src/bindings/core/wifi.cpp +++ b/native/src/bindings/core/wifi.cpp @@ -41,6 +41,9 @@ int connect(lua_State* state) { std::string password; const bool hasSsid = optionalString(state, 1, ssid); const bool hasPassword = optionalString(state, 2, password); + // The driver needs ~28KB in aggregate and a live app can be sitting on that + // much garbage, which is why a failed connect often succeeds on retry. + lua_gc(state, LUA_GCCOLLECT, 0); const Status status = Runtime::from(state)->wifi().connect( hasSsid ? &ssid : nullptr, hasPassword ? &password : nullptr); return pushStatus(state, status); diff --git a/native/test/fake_providers.h b/native/test/fake_providers.h index 08f2811..aaee6be 100644 --- a/native/test/fake_providers.h +++ b/native/test/fake_providers.h @@ -275,9 +275,14 @@ struct Wifi : WifiProvider { struct Ble : BleProvider { int32_t duration = 0; std::string value; + bool initialized = false; - Status init(const std::string*) override { return Status::success(); } - void deinit() override {} + Status init(const std::string*) override { + initialized = true; + return Status::success(); + } + void deinit() override { initialized = false; } + bool isInitialized() const override { return initialized; } Status scan(int32_t durationMs, std::vector& devices) override { duration = durationMs; const BleDevice device = {"tag", "aa:bb", -60}; diff --git a/native/test/runtime_test.cpp b/native/test/runtime_test.cpp index 21594cd..1a329ff 100644 --- a/native/test/runtime_test.cpp +++ b/native/test/runtime_test.cpp @@ -96,7 +96,10 @@ int main() { "assert(wifi.connect())"); assert(bench.wifi.savedReconnect); - run(state, "assert(ble.scan()[1].address == 'aa:bb')\n" + run(state, "assert(not ble.isInitialized())\n" + "assert(ble.init())\n" + "assert(ble.isInitialized())\n" + "assert(ble.scan()[1].address == 'aa:bb')\n" "assert(#ble.read('svc', 'chr') == 3)\n" "assert(ble.write('svc', 'chr', 'x\\0y'))"); assert(bench.ble.duration == 3000);