Files
tactility/Modules/app-module/source/event.cpp
Ken Van Hoeylandt 92ca046681 app-module events & callstack config, crash diagnostics (#630)
- Apps can specify task stack depth and preferred memory placement in their manifests.
- App identifiers are validated against length and character requirements.
- Crash diagnostics now show the crash cause, reason, fault address, call stack, and program-counter details, with logs saved for review.
- App closing is more consistent across built-in screens. There's now a dedicated function, and the old _emit() function is made private.
- Crash diagnostics no longer display a QR code without a call stack.
- Improved memory allocation for unrestricted requests.
2026-08-27 21:50:12 +02:00

129 lines
4.1 KiB
C++

// SPDX-License-Identifier: Apache-2.0
#include <app/event.h>
#include <app/scheduler.h>
#include <tactility/concurrent/mutex.h>
#include <tactility/time.h>
/**
* Intrusive singly-linked list of subscriptions, keyed by app_instance_id.
* Guarded by a single coarse-grained mutex, notifying a subscriber here never invokes caller code
* (just a struct copy and a task_event_group_signal), so there is no reentrancy concern requiring a snapshot-then-unlock dance.
*/
static AppEventSubscription* subscriptions = nullptr;
struct AppEventMutex {
Mutex handle {};
AppEventMutex() { mutex_construct(&handle); }
~AppEventMutex() { mutex_destruct(&handle); }
};
static AppEventMutex subscriptions_mutex;
extern "C" {
error_t app_event_subscribe_with_app_id(AppEventSubscription* sub, TaskEventGroup* event_group, AppInstanceId app_instance_id) {
uint32_t bit;
error_t claim_result = task_event_group_claim_bit(event_group, &bit);
if (claim_result != ERROR_NONE) {
return claim_result;
}
mutex_lock(&subscriptions_mutex.handle);
// Avoid cyclic subscription list that would loop forever
if (subscriptions == sub) {
mutex_unlock(&subscriptions_mutex.handle);
task_event_group_release_bit(event_group, bit);
return ERROR_INVALID_STATE;
}
sub->bit = bit;
sub->internal.app_instance_id = app_instance_id;
sub->internal.event_group = event_group;
sub->internal.head = 0;
sub->internal.count = 0;
sub->internal.next = subscriptions;
subscriptions = sub;
mutex_unlock(&subscriptions_mutex.handle);
return ERROR_NONE;
}
error_t app_event_subscribe(AppEventSubscription* sub, TaskEventGroup* event_group) {
return app_event_subscribe_with_app_id(sub, event_group, app_scheduler_current_app_id());
}
error_t app_event_unsubscribe(AppEventSubscription* sub) {
error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle);
for (AppEventSubscription** link = &subscriptions; *link != nullptr; link = &(*link)->internal.next) {
if (*link == sub) {
*link = sub->internal.next;
result = ERROR_NONE;
break;
}
}
mutex_unlock(&subscriptions_mutex.handle);
if (result == ERROR_NONE) {
task_event_group_release_bit(sub->internal.event_group, sub->bit);
}
return result;
}
error_t app_event_emit(AppInstanceId app_instance_id, const AppEvent* event) {
AppEvent stamped_event = *event;
stamped_event.timestamp = get_micros_since_boot();
error_t result = ERROR_NOT_FOUND;
mutex_lock(&subscriptions_mutex.handle);
for (AppEventSubscription* sub = subscriptions; sub != nullptr; sub = sub->internal.next) {
if (sub->internal.app_instance_id != app_instance_id) {
continue;
}
if (sub->internal.count >= APP_EVENT_QUEUE_CAPACITY) {
result = ERROR_RESOURCE;
continue;
}
uint8_t tail = (sub->internal.head + sub->internal.count) % APP_EVENT_QUEUE_CAPACITY;
sub->internal.queue[tail] = stamped_event;
sub->internal.count++;
if (result != ERROR_RESOURCE) {
result = ERROR_NONE;
}
task_event_group_signal(sub->internal.event_group, sub->bit);
}
mutex_unlock(&subscriptions_mutex.handle);
return result;
}
static bool try_pop(AppEventSubscription* sub, AppEvent* out_event) {
mutex_lock(&subscriptions_mutex.handle);
bool has_event = sub->internal.count > 0;
if (has_event) {
*out_event = sub->internal.queue[sub->internal.head];
sub->internal.head = (sub->internal.head + 1) % APP_EVENT_QUEUE_CAPACITY;
sub->internal.count--;
}
mutex_unlock(&subscriptions_mutex.handle);
return has_event;
}
error_t app_event_poll(AppEventSubscription* sub, AppEvent* out_event) {
return try_pop(sub, out_event) ? ERROR_NONE : ERROR_TIMEOUT;
}
error_t app_event_emit_close(AppInstanceId instance_id) {
AppEvent event { .type = APP_EVENT_CLOSE, .timestamp = 0, .result = {} };
return app_event_emit(instance_id, &event);
}
} // extern "C"