| #ifdef DWC_NOTIFYLIB |
| |
| #include "dwc_notifier.h" |
| #include "dwc_list.h" |
| |
| typedef struct dwc_observer { |
| void *observer; |
| dwc_notifier_callback_t callback; |
| void *data; |
| char *notification; |
| DWC_CIRCLEQ_ENTRY(dwc_observer) list_entry; |
| } observer_t; |
| |
| DWC_CIRCLEQ_HEAD(observer_queue, dwc_observer); |
| |
| typedef struct dwc_notifier { |
| void *mem_ctx; |
| void *object; |
| struct observer_queue observers; |
| DWC_CIRCLEQ_ENTRY(dwc_notifier) list_entry; |
| } notifier_t; |
| |
| DWC_CIRCLEQ_HEAD(notifier_queue, dwc_notifier); |
| |
| typedef struct manager { |
| void *mem_ctx; |
| void *wkq_ctx; |
| dwc_workq_t *wq; |
| // dwc_mutex_t *mutex; |
| struct notifier_queue notifiers; |
| } manager_t; |
| |
| static manager_t *manager = NULL; |
| |
| static int create_manager(void *mem_ctx, void *wkq_ctx) |
| { |
| manager = dwc_alloc(mem_ctx, sizeof(manager_t)); |
| if (!manager) { |
| return -DWC_E_NO_MEMORY; |
| } |
| |
| DWC_CIRCLEQ_INIT(&manager->notifiers); |
| |
| manager->wq = dwc_workq_alloc(wkq_ctx, "DWC Notification WorkQ"); |
| if (!manager->wq) { |
| return -DWC_E_NO_MEMORY; |
| } |
| |
| return 0; |
| } |
| |
| static void free_manager(void) |
| { |
| dwc_workq_free(manager->wq); |
| |
| /* All notifiers must have unregistered themselves before this module |
| * can be removed. Hitting this assertion indicates a programmer |
| * error. */ |
| DWC_ASSERT(DWC_CIRCLEQ_EMPTY(&manager->notifiers), |
| "Notification manager being freed before all notifiers have been removed"); |
| dwc_free(manager->mem_ctx, manager); |
| } |
| |
| #ifdef DEBUG |
| static void dump_manager(void) |
| { |
| notifier_t *n; |
| observer_t *o; |
| |
| DWC_ASSERT(manager, "Notification manager not found"); |
| |
| DWC_DEBUG("List of all notifiers and observers:\n"); |
| DWC_CIRCLEQ_FOREACH(n, &manager->notifiers, list_entry) { |
| DWC_DEBUG("Notifier %p has observers:\n", n->object); |
| DWC_CIRCLEQ_FOREACH(o, &n->observers, list_entry) { |
| DWC_DEBUG(" %p watching %s\n", o->observer, o->notification); |
| } |
| } |
| } |
| #else |
| #define dump_manager(...) |
| #endif |
| |
| static observer_t *alloc_observer(void *mem_ctx, void *observer, char *notification, |
| dwc_notifier_callback_t callback, void *data) |
| { |
| observer_t *new_observer = dwc_alloc(mem_ctx, sizeof(observer_t)); |
| |
| if (!new_observer) { |
| return NULL; |
| } |
| |
| DWC_CIRCLEQ_INIT_ENTRY(new_observer, list_entry); |
| new_observer->observer = observer; |
| new_observer->notification = notification; |
| new_observer->callback = callback; |
| new_observer->data = data; |
| return new_observer; |
| } |
| |
| static void free_observer(void *mem_ctx, observer_t *observer) |
| { |
| dwc_free(mem_ctx, observer); |
| } |
| |
| static notifier_t *alloc_notifier(void *mem_ctx, void *object) |
| { |
| notifier_t *notifier; |
| |
| if (!object) { |
| return NULL; |
| } |
| |
| notifier = dwc_alloc(mem_ctx, sizeof(notifier_t)); |
| if (!notifier) { |
| return NULL; |
| } |
| |
| DWC_CIRCLEQ_INIT(¬ifier->observers); |
| DWC_CIRCLEQ_INIT_ENTRY(notifier, list_entry); |
| |
| notifier->mem_ctx = mem_ctx; |
| notifier->object = object; |
| return notifier; |
| } |
| |
| static void free_notifier(notifier_t *notifier) |
| { |
| observer_t *observer; |
| |
| DWC_CIRCLEQ_FOREACH(observer, ¬ifier->observers, list_entry) { |
| free_observer(notifier->mem_ctx, observer); |
| } |
| |
| dwc_free(notifier->mem_ctx, notifier); |
| } |
| |
| static notifier_t *find_notifier(void *object) |
| { |
| notifier_t *notifier; |
| |
| DWC_ASSERT(manager, "Notification manager not found"); |
| |
| if (!object) { |
| return NULL; |
| } |
| |
| DWC_CIRCLEQ_FOREACH(notifier, &manager->notifiers, list_entry) { |
| if (notifier->object == object) { |
| return notifier; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| int dwc_alloc_notification_manager(void *mem_ctx, void *wkq_ctx) |
| { |
| return create_manager(mem_ctx, wkq_ctx); |
| } |
| |
| void dwc_free_notification_manager(void) |
| { |
| free_manager(); |
| } |
| |
| dwc_notifier_t *dwc_register_notifier(void *mem_ctx, void *object) |
| { |
| notifier_t *notifier; |
| |
| DWC_ASSERT(manager, "Notification manager not found"); |
| |
| notifier = find_notifier(object); |
| if (notifier) { |
| DWC_ERROR("Notifier %p is already registered\n", object); |
| return NULL; |
| } |
| |
| notifier = alloc_notifier(mem_ctx, object); |
| if (!notifier) { |
| return NULL; |
| } |
| |
| DWC_CIRCLEQ_INSERT_TAIL(&manager->notifiers, notifier, list_entry); |
| |
| DWC_INFO("Notifier %p registered", object); |
| dump_manager(); |
| |
| return notifier; |
| } |
| |
| void dwc_unregister_notifier(dwc_notifier_t *notifier) |
| { |
| DWC_ASSERT(manager, "Notification manager not found"); |
| |
| if (!DWC_CIRCLEQ_EMPTY(¬ifier->observers)) { |
| observer_t *o; |
| |
| DWC_ERROR("Notifier %p has active observers when removing\n", notifier->object); |
| DWC_CIRCLEQ_FOREACH(o, ¬ifier->observers, list_entry) { |
| DWC_DEBUG(" %p watching %s\n", o->observer, o->notification); |
| } |
| |
| DWC_ASSERT(DWC_CIRCLEQ_EMPTY(¬ifier->observers), |
| "Notifier %p has active observers when removing", notifier); |
| } |
| |
| DWC_CIRCLEQ_REMOVE_INIT(&manager->notifiers, notifier, list_entry); |
| free_notifier(notifier); |
| |
| DWC_INFO("Notifier unregistered"); |
| dump_manager(); |
| } |
| |
| /* Add an observer to observe the notifier for a particular state, event, or notification. */ |
| int dwc_add_observer(void *observer, void *object, char *notification, |
| dwc_notifier_callback_t callback, void *data) |
| { |
| notifier_t *notifier = find_notifier(object); |
| observer_t *new_observer; |
| |
| if (!notifier) { |
| DWC_ERROR("Notifier %p is not found when adding observer\n", object); |
| return -DWC_E_INVALID; |
| } |
| |
| new_observer = alloc_observer(notifier->mem_ctx, observer, notification, callback, data); |
| if (!new_observer) { |
| return -DWC_E_NO_MEMORY; |
| } |
| |
| DWC_CIRCLEQ_INSERT_TAIL(¬ifier->observers, new_observer, list_entry); |
| |
| DWC_INFO("Added observer %p to notifier %p observing notification %s, callback=%p, data=%p", |
| observer, object, notification, callback, data); |
| |
| dump_manager(); |
| return 0; |
| } |
| |
| int dwc_remove_observer(void *observer) |
| { |
| notifier_t *n; |
| |
| DWC_ASSERT(manager, "Notification manager not found"); |
| |
| DWC_CIRCLEQ_FOREACH(n, &manager->notifiers, list_entry) { |
| observer_t *o; |
| observer_t *o2; |
| |
| DWC_CIRCLEQ_FOREACH_SAFE(o, o2, &n->observers, list_entry) { |
| if (o->observer == observer) { |
| DWC_CIRCLEQ_REMOVE_INIT(&n->observers, o, list_entry); |
| DWC_INFO("Removing observer %p from notifier %p watching notification %s:", |
| o->observer, n->object, o->notification); |
| free_observer(n->mem_ctx, o); |
| } |
| } |
| } |
| |
| dump_manager(); |
| return 0; |
| } |
| |
| typedef struct callback_data { |
| void *mem_ctx; |
| dwc_notifier_callback_t cb; |
| void *observer; |
| void *data; |
| void *object; |
| char *notification; |
| void *notification_data; |
| } cb_data_t; |
| |
| static void cb_task(void *data) |
| { |
| cb_data_t *cb = (cb_data_t *)data; |
| |
| cb->cb(cb->object, cb->notification, cb->observer, cb->notification_data, cb->data); |
| dwc_free(cb->mem_ctx, cb); |
| } |
| |
| void dwc_notify(dwc_notifier_t *notifier, char *notification, void *notification_data) |
| { |
| observer_t *o; |
| |
| DWC_ASSERT(manager, "Notification manager not found"); |
| |
| DWC_CIRCLEQ_FOREACH(o, ¬ifier->observers, list_entry) { |
| int len = DWC_STRLEN(notification); |
| |
| if (DWC_STRLEN(o->notification) != len) { |
| continue; |
| } |
| |
| if (DWC_STRNCMP(o->notification, notification, len) == 0) { |
| cb_data_t *cb_data = dwc_alloc(notifier->mem_ctx, sizeof(cb_data_t)); |
| |
| if (!cb_data) { |
| DWC_ERROR("Failed to allocate callback data\n"); |
| return; |
| } |
| |
| cb_data->mem_ctx = notifier->mem_ctx; |
| cb_data->cb = o->callback; |
| cb_data->observer = o->observer; |
| cb_data->data = o->data; |
| cb_data->object = notifier->object; |
| cb_data->notification = notification; |
| cb_data->notification_data = notification_data; |
| DWC_DEBUG("Observer found %p for notification %s\n", o->observer, notification); |
| DWC_WORKQ_SCHEDULE(manager->wq, cb_task, cb_data, |
| "Notify callback from %p for Notification %s, to observer %p", |
| cb_data->object, notification, cb_data->observer); |
| } |
| } |
| } |
| |
| #endif /* DWC_NOTIFYLIB */ |