feat(ble): expose isInitialized and reclaim memory before radios start

The controller and the WiFi driver each need a large aggregate allocation,
which a live app sitting on garbage can deny - that is why a failed connect
often succeeded on retry. Both bindings now collect before initializing, and
the tree reserves its node and spec capacity so a build does not reallocate
into a tight heap.
This commit is contained in:
2026-08-04 13:10:18 -04:00
parent 3416f54c83
commit bf286eefa6
7 changed files with 37 additions and 3 deletions
+4
View File
@@ -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
+7
View File
@@ -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();
+1
View File
@@ -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<BleDevice>& devices) = 0;
virtual Status connect(const std::string& address) = 0;
virtual void disconnect() = 0;
+11
View File
@@ -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
+3
View File
@@ -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);
+7 -2
View File
@@ -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<BleDevice>& devices) override {
duration = durationMs;
const BleDevice device = {"tag", "aa:bb", -60};
+4 -1
View File
@@ -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);