Refactor app loading and window management (#609)
This commit is contained in:
committed by
GitHub
parent
dc3f6104b8
commit
37c507544b
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
// Minimal filesystem helpers shared by app-module internals that need to look at on-disk app
|
||||
// directories (app_install.cpp, manager.cpp's install-path scan) - app-module may not depend
|
||||
// upward on Tactility::file, so this is a small local re-implementation (see
|
||||
// app_metadata_parsing.cpp for the same constraint applied to properties-file loading).
|
||||
|
||||
#include <tactility/filesystem/file_mutex.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <dirent.h>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
#include <vector>
|
||||
|
||||
inline bool app_fs_is_directory(const std::string& path) {
|
||||
struct stat result {};
|
||||
FileMutex file_mutex;
|
||||
file_mutex_get(&file_mutex, path.c_str());
|
||||
file_mutex_lock(&file_mutex);
|
||||
auto is_dir = stat(path.c_str(), &result) == 0 && S_ISDIR(result.st_mode);
|
||||
file_mutex_unlock(&file_mutex);
|
||||
return is_dir;
|
||||
}
|
||||
|
||||
inline bool app_fs_is_file(const std::string& path) {
|
||||
FileMutex file_mutex;
|
||||
file_mutex_get(&file_mutex, path.c_str());
|
||||
file_mutex_lock(&file_mutex);
|
||||
struct stat result {};
|
||||
auto retval = stat(path.c_str(), &result) == 0 && S_ISREG(result.st_mode);
|
||||
file_mutex_unlock(&file_mutex);
|
||||
return retval;
|
||||
}
|
||||
|
||||
// Appends the full path of every direct subdirectory of @a path to @a out.
|
||||
// No-op (not an error) if @a path can't be opened.
|
||||
inline void app_fs_list_direct_subdirectories(const std::string& path, std::vector<std::string>& out) {
|
||||
// Collect child names while the directory lock is held, then release it before classifying
|
||||
// each one with app_fs_is_directory() - that function looks up and locks a FileMutex too,
|
||||
// and file_mutex_get() resolves a child path to the same registered mutex as its parent
|
||||
// mount. Calling it while still holding the directory's own lock would be a nested
|
||||
// acquisition of that same (possibly non-recursive) mutex, and could self-deadlock.
|
||||
std::vector<std::string> children;
|
||||
|
||||
FileMutex file_mutex;
|
||||
file_mutex_get(&file_mutex, path.c_str());
|
||||
file_mutex_lock(&file_mutex);
|
||||
DIR* dir = opendir(path.c_str());
|
||||
if (dir == nullptr) {
|
||||
file_mutex_unlock(&file_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != nullptr) {
|
||||
if (std::strcmp(entry->d_name, ".") == 0 || std::strcmp(entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
children.push_back(path + "/" + entry->d_name);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
file_mutex_unlock(&file_mutex);
|
||||
|
||||
for (const auto& child_path : children) {
|
||||
if (app_fs_is_directory(child_path)) {
|
||||
out.push_back(child_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/instance.h>
|
||||
#include <app/manifest.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/freertos/freertos.h>
|
||||
#include <tactility/freertos/semphr.h>
|
||||
#include <tactility/freertos/task.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* A dedicated completion signal(1) for one app instance's task, given as the
|
||||
* literal last action app_task_main() takes before vTaskDelete().
|
||||
* Heap-allocated with its own refcount (protected by app_ledger().mutex, not atomic)
|
||||
* rather than owned by the ledger entry, since app_task_main() always erases that entry -
|
||||
* and may run its exit path entirely - before app_scheduler_stop() ever looks for it:
|
||||
* Whichever side(2) finishes with it last is the one that deletes `semaphore` and frees this struct.
|
||||
*
|
||||
* (1) Not the task's shared default FreeRTOS notification, which app_event.cpp's
|
||||
* AppEventSubscription also uses - an unrelated event delivered to the same task could
|
||||
* otherwise unblock a waiter early.
|
||||
* (2) The exiting task, or a concurrent app_scheduler_stop() that found the entry in time and is waiting on `semaphore`
|
||||
*/
|
||||
struct AppCompletionSignal {
|
||||
SemaphoreHandle_t semaphore;
|
||||
/** Starts at 1, owned by app_task_main() until its own exit. app_scheduler_stop() takes an
|
||||
* additional reference for as long as it's waiting on `semaphore`, if it finds the instance
|
||||
* still running. Reaching 0 means deletion. */
|
||||
int refcount = 1;
|
||||
};
|
||||
|
||||
/** A registered/running app instance, as tracked internally by app-module. */
|
||||
struct AppInstanceRecord {
|
||||
uint32_t id;
|
||||
const AppManifest* manifest;
|
||||
AppInstanceState state;
|
||||
/** The FreeRTOS task currently executing AppLoaderApi::run() for this instance; NULL when not running. */
|
||||
TaskHandle_t task;
|
||||
|
||||
/** 0 for a top-level launch (app_manager_start()). Non-zero for a modal child launched via
|
||||
* app_manager_start_for_result() - the instance that receives this child's APP_EVENT_RESULT. */
|
||||
uint32_t parent_id = 0;
|
||||
|
||||
/** This instance's completion signal - see AppCompletionSignal. Set once by
|
||||
* app_scheduler_start(), never reassigned. */
|
||||
AppCompletionSignal* completion = nullptr;
|
||||
};
|
||||
|
||||
struct AppLedger {
|
||||
std::unordered_map<std::string, const AppManifest*> manifests;
|
||||
std::unordered_map<uint32_t, AppInstanceRecord> instances;
|
||||
uint32_t next_instance_id = 1;
|
||||
Mutex mutex {};
|
||||
|
||||
AppLedger() { mutex_construct(&mutex); }
|
||||
~AppLedger() { mutex_destruct(&mutex); }
|
||||
};
|
||||
|
||||
inline AppLedger& app_ledger() {
|
||||
static AppLedger ledger;
|
||||
return ledger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees a deep-copied argv previously built by app_manager_start_with_parameters()/app_manager_start_for_result():
|
||||
* each individually heap-allocated string, then the array itself. Safe to call with count == 0 values == nullptr (no-op).
|
||||
*/
|
||||
inline void app_ledger_free_arguments(int count, char** values) {
|
||||
if (values == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
delete[] values[i];
|
||||
}
|
||||
delete[] values;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/metadata.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
/** Shared helpers + per-format parsers for app_metadata_parse() (source/app_metadata_parsing.cpp)
|
||||
* - split out like the old tt::app manifest parser (AppManifestParsing/V1/V2.cpp) that this is
|
||||
* modelled on, one file per format plus a shared dispatcher. */
|
||||
|
||||
bool app_metadata_get_value(const std::map<std::string, std::string>& properties, const std::string& key, std::string& out_value);
|
||||
|
||||
bool app_metadata_is_valid_format_version(const std::string& version);
|
||||
bool app_metadata_is_valid_id(const std::string& id);
|
||||
bool app_metadata_is_valid_name(const std::string& name);
|
||||
bool app_metadata_is_valid_version_name(const std::string& version);
|
||||
bool app_metadata_is_valid_version_code(const std::string& version);
|
||||
|
||||
/** Copies @a value into @a dest (a fixed-size buffer of @a dest_size bytes, including the NULL
|
||||
* terminator) if it fits.
|
||||
* @retval false @a value doesn't fit in @a dest_size bytes - @a dest is left untouched */
|
||||
bool app_metadata_copy_bounded(char* dest, size_t dest_size, const std::string& value);
|
||||
|
||||
/** Parses a V1 (sectioned INI, e.g. "[app]versionName=...") manifest map into @a out_metadata. */
|
||||
bool app_metadata_parse_v1(const std::map<std::string, std::string>& properties, struct AppMetadata& out_metadata);
|
||||
|
||||
/** Parses a V2 (flat dot-notation, e.g. "app.version.name=...") manifest map into @a out_metadata. */
|
||||
bool app_metadata_parse_v2(const std::map<std::string, std::string>& properties, struct AppMetadata& out_metadata);
|
||||
@@ -0,0 +1,41 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/manifest.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
/**
|
||||
* Owns per-app task lifecycle on behalf of app_manager_*(). AppLoaderApi implementations
|
||||
* stay task-agnostic; all of xTaskCreate()/vTaskDelete() happens here, as a plain FreeRTOS task
|
||||
* (not TactilityKernel's Thread wrapper). Every app instance gets its own dedicated task for its
|
||||
* entire lifetime - no task is ever reused for a different instance.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Loads and starts an app instance: spawns a dedicated task that calls
|
||||
* AppLoaderApi::load()/run(), marking the instance ACTIVE for the duration of run().
|
||||
* @param[in] app_instance_id id already allocated in the ledger for this instance
|
||||
* @param[in] location the location of the app
|
||||
* @param[in] argc the amount of arguments to pass to the app's main function
|
||||
* @param[in] argv the array of arguments to pass to the app's main function - ownership is
|
||||
* taken by the scheduler regardless of outcome (freed once the spawned task's run() returns, or
|
||||
* immediately on a failure to start it)
|
||||
*/
|
||||
error_t app_scheduler_start(AppInstanceId app_instance_id, struct AppLocation location, int argc, char* argv[]);
|
||||
|
||||
/**
|
||||
* Permanently stops an app instance (APP_EVENT_CLOSE if it was running), bound-waits for its
|
||||
* task to exit, and removes it from the ledger.
|
||||
*/
|
||||
error_t app_scheduler_stop(AppInstanceId app_instance_id, TickType_t join_timeout);
|
||||
|
||||
// app_scheduler_current_app_id() is public - see app/scheduler.h.
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user