diff options
author | Campbell Barton <ideasman42@gmail.com> | 2017-11-13 11:43:34 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2017-12-04 12:42:34 +0300 |
commit | 7a8ac1b09b1cf321f259b8eb9b832424d2c7bf5b (patch) | |
tree | 5f9a861e234799149fc99f25e5df7bd96fb46cae /source/blender/windowmanager/message_bus | |
parent | 5b6cfa705cb180d042f6f27e45331c12972be7ae (diff) |
WM: message bus replacement for property notifiers
Use dynamically generated message publish/subscribe
so buttons and manipulators update properly.
This resolves common glitches where manipulators weren't updating
as well as the UI when add-ons exposed properties which
hard coded listeners weren't checking for.
Python can also publish/scribe changes via `bpy.msgbus`.
See D2917
Diffstat (limited to 'source/blender/windowmanager/message_bus')
5 files changed, 995 insertions, 0 deletions
diff --git a/source/blender/windowmanager/message_bus/intern/wm_message_bus.c b/source/blender/windowmanager/message_bus/intern/wm_message_bus.c new file mode 100644 index 00000000000..0e7486fa293 --- /dev/null +++ b/source/blender/windowmanager/message_bus/intern/wm_message_bus.c @@ -0,0 +1,245 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/message_bus/intern/wm_message_bus.c + * \ingroup wm + */ + +#include <string.h> + +#include "BLI_utildefines.h" +#include "BLI_listbase.h" + +#include "BLI_ghash.h" + +#include "WM_types.h" + +#include "MEM_guardedalloc.h" + +#include "message_bus/wm_message_bus.h" +#include "message_bus/intern/wm_message_bus_intern.h" + +/* -------------------------------------------------------------------------- */ +/** \name Public API + * \{ */ + +static wmMsgTypeInfo wm_msg_types[WM_MSG_TYPE_NUM] = {NULL}; + +typedef void (*wmMsgTypeInitFn)(wmMsgTypeInfo *); + +static wmMsgTypeInitFn wm_msg_init_fn[WM_MSG_TYPE_NUM] = { + WM_msgtypeinfo_init_rna, + WM_msgtypeinfo_init_static, +}; + +void WM_msgbus_types_init(void) +{ + for (uint i = 0; i < WM_MSG_TYPE_NUM; i++) { + wm_msg_init_fn[i](&wm_msg_types[i]); + } +} + +struct wmMsgBus *WM_msgbus_create(void) +{ + struct wmMsgBus *mbus = MEM_callocN(sizeof(*mbus), __func__); + const uint gset_reserve = 512; + for (uint i = 0; i < WM_MSG_TYPE_NUM; i++) { + wmMsgTypeInfo *info = &wm_msg_types[i]; + mbus->messages_gset[i] = BLI_gset_new_ex(info->gset.hash_fn, info->gset.cmp_fn, __func__, gset_reserve); + } + return mbus; +} + +void WM_msgbus_destroy(struct wmMsgBus *mbus) +{ + for (uint i = 0; i < WM_MSG_TYPE_NUM; i++) { + wmMsgTypeInfo *info = &wm_msg_types[i]; + BLI_gset_free(mbus->messages_gset[i], info->gset.key_free_fn); + } + MEM_freeN(mbus); +} + +void WM_msgbus_clear_by_owner(struct wmMsgBus *mbus, void *owner) +{ + wmMsgSubscribeKey *msg_key, *msg_key_next; + for (msg_key = mbus->messages.first; msg_key; msg_key = msg_key_next) { + msg_key_next = msg_key->next; + + wmMsgSubscribeValueLink *msg_lnk_next; + for (wmMsgSubscribeValueLink *msg_lnk = msg_key->values.first; msg_lnk; msg_lnk = msg_lnk_next) { + msg_lnk_next = msg_lnk->next; + if (msg_lnk->params.owner == owner) { + if (msg_lnk->params.free_data) { + msg_lnk->params.free_data(msg_key, &msg_lnk->params); + } + BLI_remlink(&msg_key->values, msg_lnk); + MEM_freeN(msg_lnk); + } + } + + if (BLI_listbase_is_empty(&msg_key->values)) { + wmMsgTypeInfo *info = &wm_msg_types[msg_key->msg->type]; + BLI_remlink(&mbus->messages, msg_key); + bool ok = BLI_gset_remove(mbus->messages_gset[msg_key->msg->type], msg_key, info->gset.key_free_fn); + BLI_assert(ok); + UNUSED_VARS_NDEBUG(ok); + } + } +} + +void WM_msg_dump(struct wmMsgBus *mbus, const char *info_str) +{ + printf(">>>> %s\n", info_str); + for (wmMsgSubscribeKey *key = mbus->messages.first; key; key = key->next) { + const wmMsgTypeInfo *info = &wm_msg_types[key->msg->type]; + info->repr(stdout, key); + } + printf("<<<< %s\n", info_str); +} + +void WM_msgbus_handle(struct wmMsgBus *mbus, struct bContext *C) +{ + if (mbus->messages_tag_count == 0) { + // printf("msgbus: skipping\n"); + return; + } + + if (false) { + WM_msg_dump(mbus, __func__); + } + + // uint a = 0, b = 0; + for (wmMsgSubscribeKey *key = mbus->messages.first; key; key = key->next) { + for (wmMsgSubscribeValueLink *msg_lnk = key->values.first; msg_lnk; msg_lnk = msg_lnk->next) { + if (msg_lnk->params.tag) { + msg_lnk->params.notify(C, key, &msg_lnk->params); + msg_lnk->params.tag = false; + mbus->messages_tag_count -= 1; + } + // b++; + } + // a++; + } + BLI_assert(mbus->messages_tag_count == 0); + mbus->messages_tag_count = 0; + // printf("msgbus: keys=%u values=%u\n", a, b); +} + +/** + * \param msg_key_test: Needs following #wmMsgSubscribeKey fields filled in: + * - msg.params + * - msg.head.type + * - msg.head.id + * .. other values should be zeroed. + * + * \return The key for this subscription. + * note that this is only needed in rare cases when the key needs further manipulation. + */ +wmMsgSubscribeKey *WM_msg_subscribe_with_key( + struct wmMsgBus *mbus, + const wmMsgSubscribeKey *msg_key_test, + const wmMsgSubscribeValue *msg_val_params) +{ + const uint type = msg_key_test->msg->type; + const wmMsgTypeInfo *info = &wm_msg_types[type]; + wmMsgSubscribeKey *key; + + BLI_assert(msg_key_test->msg->id != NULL); + + void **r_key; + if (!BLI_gset_ensure_p_ex(mbus->messages_gset[type], msg_key_test, &r_key)) { + key = *r_key = MEM_mallocN(info->msg_key_size, __func__); + memcpy(key, msg_key_test, info->msg_key_size); + BLI_addtail(&mbus->messages, key); + } + else { + key = *r_key; + for (wmMsgSubscribeValueLink *msg_lnk = key->values.first; msg_lnk; msg_lnk = msg_lnk->next) { + if ((msg_lnk->params.notify == msg_val_params->notify) && + (msg_lnk->params.owner == msg_val_params->owner) && + (msg_lnk->params.user_data == msg_val_params->user_data)) + { + return key; + } + } + } + + wmMsgSubscribeValueLink *msg_lnk = MEM_mallocN(sizeof(wmMsgSubscribeValueLink), __func__); + msg_lnk->params = *msg_val_params; + BLI_addtail(&key->values, msg_lnk); + return key; +} + +void WM_msg_publish_with_key(struct wmMsgBus *mbus, wmMsgSubscribeKey *msg_key) +{ + for (wmMsgSubscribeValueLink *msg_lnk = msg_key->values.first; msg_lnk; msg_lnk = msg_lnk->next) { + if (false) { /* make an option? */ + msg_lnk->params.notify(NULL, msg_key, &msg_lnk->params); + } + else { + if (msg_lnk->params.tag == false) { + msg_lnk->params.tag = true; + mbus->messages_tag_count += 1; + } + } + } +} + +void WM_msg_id_update( + struct wmMsgBus *mbus, + struct ID *id_src, struct ID *id_dst) +{ + for (uint i = 0; i < WM_MSG_TYPE_NUM; i++) { + wmMsgTypeInfo *info = &wm_msg_types[i]; + if (info->update_by_id != NULL) { + info->update_by_id(mbus, id_src, id_dst); + } + } +} + +void WM_msg_id_remove(struct wmMsgBus *mbus, const struct ID *id) +{ + for (uint i = 0; i < WM_MSG_TYPE_NUM; i++) { + wmMsgTypeInfo *info = &wm_msg_types[i]; + if (info->remove_by_id != NULL) { + info->remove_by_id(mbus, id); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------------- */ +/** \name Internal API + * + * \note While we could have a separate type for ID's, use RNA since there is enough overlap. + * \{ */ + +void wm_msg_subscribe_value_free( + wmMsgSubscribeKey *msg_key, wmMsgSubscribeValueLink *msg_lnk) +{ + if (msg_lnk->params.free_data) { + msg_lnk->params.free_data(msg_key, &msg_lnk->params); + } + BLI_remlink(&msg_key->values, msg_lnk); + MEM_freeN(msg_lnk); +} + +/** \} */ diff --git a/source/blender/windowmanager/message_bus/intern/wm_message_bus_intern.h b/source/blender/windowmanager/message_bus/intern/wm_message_bus_intern.h new file mode 100644 index 00000000000..8d7b9d4ced2 --- /dev/null +++ b/source/blender/windowmanager/message_bus/intern/wm_message_bus_intern.h @@ -0,0 +1,41 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/message_bus/intern/wm_message_bus_intern.h + * \ingroup wm + */ + +#ifndef __WM_MESSAGE_BUS_INTERN_H__ +#define __WM_MESSAGE_BUS_INTERN_H__ + +/* wm_message_bus.h must be included first */ + +struct wmMsgBus { + struct GSet *messages_gset[WM_MSG_TYPE_NUM]; + /** Messages in order of being added. */ + ListBase messages; + /** Avoid checking messages when no tags exist. */ + uint messages_tag_count; +}; + +void wm_msg_subscribe_value_free( + struct wmMsgSubscribeKey *msg_key, struct wmMsgSubscribeValueLink *msg_lnk); + +#endif /* __WM_MESSAGE_BUS_INTERN_H__ */ diff --git a/source/blender/windowmanager/message_bus/intern/wm_message_bus_rna.c b/source/blender/windowmanager/message_bus/intern/wm_message_bus_rna.c new file mode 100644 index 00000000000..f9d8d968b84 --- /dev/null +++ b/source/blender/windowmanager/message_bus/intern/wm_message_bus_rna.c @@ -0,0 +1,316 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/intern/wm_message_bus_rna.c + * \ingroup wm + */ + +#include <stdio.h> +#include "DNA_ID.h" + +#include "BLI_utildefines.h" +#include "BLI_ghash.h" +#include "BLI_listbase.h" + +#include "WM_types.h" +#include "WM_message.h" +#include "message_bus/intern/wm_message_bus_intern.h" + +#include "RNA_access.h" + +#include "MEM_guardedalloc.h" + +/* -------------------------------------------------------------------------- */ + +BLI_INLINE uint void_hash_uint(const void *key) +{ + size_t y = (size_t)key >> (sizeof(void *)); + return (unsigned int)y; +} + +static uint wm_msg_rna_gset_hash(const void *key_p) +{ + const wmMsgSubscribeKey_RNA *key = key_p; + const wmMsgParams_RNA *params = &key->msg.params; +// printf("%s\n", RNA_struct_identifier(params->ptr.type)); + uint k = void_hash_uint(params->ptr.type); + k ^= void_hash_uint(params->ptr.data); + k ^= void_hash_uint(params->ptr.id.data); + k ^= void_hash_uint(params->prop); + return k; +} +static bool wm_msg_rna_gset_cmp(const void *key_a_p, const void *key_b_p) +{ + const wmMsgParams_RNA *params_a = &((const wmMsgSubscribeKey_RNA *)key_a_p)->msg.params; + const wmMsgParams_RNA *params_b = &((const wmMsgSubscribeKey_RNA *)key_b_p)->msg.params; + return !( + (params_a->ptr.type == + params_b->ptr.type) && + (params_a->ptr.id.data == + params_b->ptr.id.data) && + (params_a->ptr.data == + params_b->ptr.data) && + (params_a->prop == + params_b->prop) + ); +} +static void wm_msg_rna_gset_key_free(void *key_p) +{ + wmMsgSubscribeKey_RNA *key = key_p; + wmMsgSubscribeValueLink *msg_lnk_next; + for (wmMsgSubscribeValueLink *msg_lnk = key->head.values.first; msg_lnk; msg_lnk = msg_lnk_next) { + msg_lnk_next = msg_lnk->next; + wm_msg_subscribe_value_free(&key->head, msg_lnk); + } + if (key->msg.params.data_path != NULL) { + MEM_freeN(key->msg.params.data_path); + } + MEM_freeN(key); +} + +static void wm_msg_rna_repr(FILE *stream, const wmMsgSubscribeKey *msg_key) +{ + const wmMsgSubscribeKey_RNA *m = (wmMsgSubscribeKey_RNA *)msg_key; + const char *none = "<none>"; + fprintf(stream, + "<wmMsg_RNA %p, " + "id='%s', " + "%s.%s values_len=%d\n", + m, m->msg.head.id, + m->msg.params.ptr.type ? RNA_struct_identifier(m->msg.params.ptr.type) : none, + m->msg.params.prop ? RNA_property_identifier((PropertyRNA *)m->msg.params.prop) : none, + BLI_listbase_count(&m->head.values)); +} + +static void wm_msg_rna_update_by_id( + struct wmMsgBus *mbus, + ID *id_src, ID *id_dst) +{ + GSet *gs = mbus->messages_gset[WM_MSG_TYPE_RNA]; + GSetIterator gs_iter; + BLI_gsetIterator_init(&gs_iter, gs); + while (BLI_gsetIterator_done(&gs_iter) == false) { + wmMsgSubscribeKey_RNA *key = BLI_gsetIterator_getKey(&gs_iter); + BLI_gsetIterator_step(&gs_iter); + if (key->msg.params.ptr.id.data == id_src) { + + /* GSet always needs updating since the key changes. */ + BLI_gset_remove(gs, key, NULL); + + /* Remove any non-persistent values, so a single persistent + * value doesn't modify behavior for the rest. */ + wmMsgSubscribeValueLink *msg_lnk_next; + for (wmMsgSubscribeValueLink *msg_lnk = key->head.values.first; msg_lnk; msg_lnk = msg_lnk_next) { + msg_lnk_next = msg_lnk->next; + if (msg_lnk->params.is_persistent == false) { + wm_msg_subscribe_value_free(&key->head, msg_lnk); + } + } + + bool remove = true; + + if (BLI_listbase_is_empty(&key->head.values)) { + /* Remove, no reason to keep. */ + } + else if (key->msg.params.ptr.data == key->msg.params.ptr.id.data) { + /* Simple, just update the ID. */ + key->msg.params.ptr.data = id_dst; + key->msg.params.ptr.id.data = id_dst; + remove = false; + } + else { + /* we need to resolve this from the */ + PointerRNA idptr; + RNA_id_pointer_create(id_dst, &idptr); + PointerRNA ptr; + PropertyRNA *prop; + if (!RNA_path_resolve(&idptr, key->msg.params.data_path, &ptr, &prop)) { + key->msg.params.ptr = ptr; + key->msg.params.prop = prop; + remove = false; + } + } + + printf("AAA ~ %d\n", remove); + if (remove) { + /* Failed to persist, remove the key. */ + BLI_remlink(&mbus->messages, key); + wm_msg_rna_gset_key_free(key); + } + else { + /* note that it's not impossible this key exists, however it is very unlikely + * since a subscriber would need to register in the middle of an undo for eg. so assert for now. */ + BLI_assert(!BLI_gset_haskey(gs, key)); + BLI_gset_add(gs, key); + } + } + } +} + +static void wm_msg_rna_remove_by_id(struct wmMsgBus *mbus, const ID *id) +{ + GSet *gs = mbus->messages_gset[WM_MSG_TYPE_RNA]; + GSetIterator gs_iter; + BLI_gsetIterator_init(&gs_iter, gs); + while (BLI_gsetIterator_done(&gs_iter) == false) { + wmMsgSubscribeKey_RNA *key = BLI_gsetIterator_getKey(&gs_iter); + BLI_gsetIterator_step(&gs_iter); + if (key->msg.params.ptr.id.data == id) { + BLI_remlink(&mbus->messages, key); + BLI_gset_remove(gs, key, NULL); + wm_msg_rna_gset_key_free(key); + } + } +} + +void WM_msgtypeinfo_init_rna(wmMsgTypeInfo *msgtype_info) +{ + msgtype_info->gset.hash_fn = wm_msg_rna_gset_hash; + msgtype_info->gset.cmp_fn = wm_msg_rna_gset_cmp; + msgtype_info->gset.key_free_fn = wm_msg_rna_gset_key_free; + + msgtype_info->repr = wm_msg_rna_repr; + msgtype_info->update_by_id = wm_msg_rna_update_by_id; + msgtype_info->remove_by_id = wm_msg_rna_remove_by_id; + + msgtype_info->msg_key_size = sizeof(wmMsgSubscribeKey_RNA); +} + +/* -------------------------------------------------------------------------- */ + + +wmMsgSubscribeKey_RNA *WM_msg_lookup_rna(struct wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params) +{ + wmMsgSubscribeKey_RNA key_test; + key_test.msg.params = *msg_key_params; + return BLI_gset_lookup(mbus->messages_gset[WM_MSG_TYPE_RNA], &key_test); +} + +void WM_msg_publish_rna_params(struct wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params) +{ + wmMsgSubscribeKey_RNA *key; + + if ((key = WM_msg_lookup_rna(mbus, msg_key_params))) { + WM_msg_publish_with_key(mbus, &key->head); + } + + /* Support anonymous subscribers, this may be some extra overhead + * but we want to be able to be more ambiguous. */ + if (msg_key_params->ptr.id.data || msg_key_params->ptr.data) { + wmMsgParams_RNA msg_key_params_anon = *msg_key_params; + + /* We might want to enable this later? */ + if (msg_key_params_anon.prop != NULL) { + /* All properties for this type. */ + msg_key_params_anon.prop = NULL; + if ((key = WM_msg_lookup_rna(mbus, &msg_key_params_anon))) { + WM_msg_publish_with_key(mbus, &key->head); + } + msg_key_params_anon.prop = msg_key_params->prop; + } + + msg_key_params_anon.ptr.id.data = NULL; + msg_key_params_anon.ptr.data = NULL; + if ((key = WM_msg_lookup_rna(mbus, &msg_key_params_anon))) { + WM_msg_publish_with_key(mbus, &key->head); + } + + /* Support subscribers to a type. */ + if (msg_key_params->prop) { + msg_key_params_anon.prop = NULL; + if ((key = WM_msg_lookup_rna(mbus, &msg_key_params_anon))) { + WM_msg_publish_with_key(mbus, &key->head); + } + } + } +} + +void WM_msg_publish_rna(struct wmMsgBus *mbus, PointerRNA *ptr, PropertyRNA *prop) +{ + WM_msg_publish_rna_params(mbus, &(wmMsgParams_RNA){ .ptr = *ptr, .prop = prop, }); +} + +void WM_msg_subscribe_rna_params( + struct wmMsgBus *mbus, + const wmMsgParams_RNA *msg_key_params, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr) +{ + wmMsgSubscribeKey_RNA msg_key_test = {{NULL}}; + + /* use when added */ + msg_key_test.msg.head.id = id_repr; + msg_key_test.msg.head.type = WM_MSG_TYPE_RNA; + /* for lookup */ + msg_key_test.msg.params = *msg_key_params; + + wmMsgSubscribeKey_RNA *msg_key = (wmMsgSubscribeKey_RNA *)WM_msg_subscribe_with_key( + mbus, &msg_key_test.head, msg_val_params); + + if (msg_val_params->is_persistent) { + if (msg_key->msg.params.data_path == NULL) { + if (msg_key->msg.params.ptr.data != msg_key->msg.params.ptr.id.data) { + /* We assume prop type can't change. */ + msg_key->msg.params.data_path = RNA_path_from_ID_to_struct(&msg_key->msg.params.ptr); + } + } + } +} + +void WM_msg_subscribe_rna( + struct wmMsgBus *mbus, + PointerRNA *ptr, const PropertyRNA *prop, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr) +{ + WM_msg_subscribe_rna_params( + mbus, + &(const wmMsgParams_RNA){ + .ptr = *ptr, + .prop = prop, + }, + msg_val_params, id_repr); +} + +/** \} */ + +/* -------------------------------------------------------------------------- */ +/** \name ID variants of RNA API + * + * \note While we could have a separate type for ID's, use RNA since there is enough overlap. + * \{ */ + +void WM_msg_subscribe_ID( + struct wmMsgBus *mbus, ID *id, const wmMsgSubscribeValue *msg_val_params, + const char *id_repr) +{ + wmMsgParams_RNA msg_key_params = {NULL}; + RNA_id_pointer_create(id, &msg_key_params.ptr); + WM_msg_subscribe_rna_params(mbus, &msg_key_params, msg_val_params, id_repr); +} + +void WM_msg_publish_ID(struct wmMsgBus *mbus, ID *id) +{ + wmMsgParams_RNA msg_key_params = {NULL}; + RNA_id_pointer_create(id, &msg_key_params.ptr); + WM_msg_publish_rna_params(mbus, &msg_key_params); +} + +/** \} */ diff --git a/source/blender/windowmanager/message_bus/intern/wm_message_bus_static.c b/source/blender/windowmanager/message_bus/intern/wm_message_bus_static.c new file mode 100644 index 00000000000..59bebad7f7b --- /dev/null +++ b/source/blender/windowmanager/message_bus/intern/wm_message_bus_static.c @@ -0,0 +1,136 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/intern/wm_message_bus_static.c + * \ingroup wm + */ + +#include <stdio.h> + +#include "BLI_utildefines.h" +#include "BLI_ghash.h" +#include "BLI_listbase.h" + +#include "WM_types.h" +#include "WM_message.h" +#include "message_bus/intern/wm_message_bus_intern.h" + +#include "MEM_guardedalloc.h" + +/* -------------------------------------------------------------------------- */ + +static uint wm_msg_static_gset_hash(const void *key_p) +{ + const wmMsgSubscribeKey_Static *key = key_p; + const wmMsgParams_Static *params = &key->msg.params; + uint k = params->event; + return k; +} +static bool wm_msg_static_gset_cmp(const void *key_a_p, const void *key_b_p) +{ + const wmMsgParams_Static *params_a = &((const wmMsgSubscribeKey_Static *)key_a_p)->msg.params; + const wmMsgParams_Static *params_b = &((const wmMsgSubscribeKey_Static *)key_b_p)->msg.params; + return !( + (params_a->event == + params_b->event) + ); +} +static void wm_msg_static_gset_key_free(void *key_p) +{ + wmMsgSubscribeKey *key = key_p; + wmMsgSubscribeValueLink *msg_lnk_next; + for (wmMsgSubscribeValueLink *msg_lnk = key->values.first; msg_lnk; msg_lnk = msg_lnk_next) { + msg_lnk_next = msg_lnk->next; + BLI_remlink(&key->values, msg_lnk); + MEM_freeN(msg_lnk); + } + MEM_freeN(key); +} + +static void wm_msg_static_repr(FILE *stream, const wmMsgSubscribeKey *msg_key) +{ + const wmMsgSubscribeKey_Static *m = (wmMsgSubscribeKey_Static *)msg_key; + fprintf(stream, + "<wmMsg_Static %p, " + "id='%s', " + "values_len=%d\n", + m, m->msg.head.id, + BLI_listbase_count(&m->head.values)); +} + + +void WM_msgtypeinfo_init_static(wmMsgTypeInfo *msgtype_info) +{ + msgtype_info->gset.hash_fn = wm_msg_static_gset_hash; + msgtype_info->gset.cmp_fn = wm_msg_static_gset_cmp; + msgtype_info->gset.key_free_fn = wm_msg_static_gset_key_free; + msgtype_info->repr = wm_msg_static_repr; + + msgtype_info->msg_key_size = sizeof(wmMsgSubscribeKey_Static); +} + +/* -------------------------------------------------------------------------- */ + + +wmMsgSubscribeKey_Static *WM_msg_lookup_static(struct wmMsgBus *mbus, const wmMsgParams_Static *msg_key_params) +{ + wmMsgSubscribeKey_Static key_test; + key_test.msg.params = *msg_key_params; + return BLI_gset_lookup(mbus->messages_gset[WM_MSG_TYPE_STATIC], &key_test); +} + +void WM_msg_publish_static_params(struct wmMsgBus *mbus, const wmMsgParams_Static *msg_key_params) +{ + wmMsgSubscribeKey_Static *key = WM_msg_lookup_static(mbus, msg_key_params); + if (key) { + WM_msg_publish_with_key(mbus, &key->head); + } +} + +void WM_msg_publish_static(struct wmMsgBus *mbus, int event) +{ + WM_msg_publish_static_params(mbus, &(wmMsgParams_Static){ .event = event, }); +} + +void WM_msg_subscribe_static_params( + struct wmMsgBus *mbus, + const wmMsgParams_Static *msg_key_params, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr) +{ + wmMsgSubscribeKey_Static msg_key_test = {{NULL}}; + + /* use when added */ + msg_key_test.msg.head.id = id_repr; + msg_key_test.msg.head.type = WM_MSG_TYPE_STATIC; + /* for lookup */ + msg_key_test.msg.params = *msg_key_params; + + WM_msg_subscribe_with_key(mbus, &msg_key_test.head, msg_val_params); +} + +void WM_msg_subscribe_static( + struct wmMsgBus *mbus, + int event, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr) +{ + WM_msg_subscribe_static_params(mbus, &(const wmMsgParams_Static){ .event = event, }, msg_val_params, id_repr); +} diff --git a/source/blender/windowmanager/message_bus/wm_message_bus.h b/source/blender/windowmanager/message_bus/wm_message_bus.h new file mode 100644 index 00000000000..fd158e2cd7f --- /dev/null +++ b/source/blender/windowmanager/message_bus/wm_message_bus.h @@ -0,0 +1,257 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +/** \file blender/windowmanager/wm_message_bus.h + * \ingroup wm + */ + +#ifndef __WM_MESSAGE_BUS_H__ +#define __WM_MESSAGE_BUS_H__ + +struct GSet; +struct ID; +struct bContext; +struct wmMsg; + +/* opaque (don't expose outside wm_message_bus.c) */ +struct wmMsgBus; +struct wmMsgSubscribeKey; +struct wmMsgSubscribeValue; +struct wmMsgSubscribeValueLink; + +typedef void (*wmMsgNotifyFn)( + struct bContext *C, struct wmMsgSubscribeKey *msg_key, struct wmMsgSubscribeValue *msg_val); +typedef void (*wmMsgSubscribeValueFreeDataFn)( + struct wmMsgSubscribeKey *msg_key, struct wmMsgSubscribeValue *msg_val); + +/* Exactly what arguments here is not obvious. */ +typedef void (*wmMsgSubscribeValueUpdateIdFn)( + struct bContext *C, + struct wmMsgBus *mbus, + struct ID *id_src, struct ID *id_dst, + struct wmMsgSubscribeValue *msg_val); +enum { + WM_MSG_TYPE_RNA = 0, + WM_MSG_TYPE_STATIC = 1, +}; +#define WM_MSG_TYPE_NUM 2 + +typedef struct wmMsgTypeInfo { + struct { + unsigned int (*hash_fn)(const void *msg); + bool (*cmp_fn)(const void *a, const void *b); + void (*key_free_fn)(void *key); + } gset; + + void (*update_by_id)(struct wmMsgBus *mbus, struct ID *id_src, struct ID *id_dst); + void (*remove_by_id)(struct wmMsgBus *mbus, const struct ID *id); + void (*repr)(FILE *stream, const struct wmMsgSubscribeKey *msg_key); + + /* sizeof(wmMsgSubscribeKey_*) */ + uint msg_key_size; +} wmMsgTypeInfo; + +typedef struct wmMsg { + unsigned int type; +// #ifdef DEBUG + /* For debugging: '__func__:__LINE__'. */ + const char *id; +// #endif +} wmMsg; + +typedef struct wmMsgSubscribeKey { + /** Linked list for predicable ordering, otherwise we would depend on ghash bucketing. */ + struct wmMsgSubscribeKey *next, *prev; + ListBase values; + + /* over-alloc, eg: wmMsgSubscribeKey_RNA */ + wmMsg msg[0]; +} wmMsgSubscribeKey; + +/** One of many in #wmMsgSubscribeKey.values */ +typedef struct wmMsgSubscribeValue { + struct wmMsgSubscribe *next, *prev; + + /** Handle, used to iterate and clear. */ + void *owner; + /** User data, can be whatever we like, free using the 'free_data' callback if it's owned. */ + void *user_data; + + /** Callbacks */ + wmMsgNotifyFn notify; + wmMsgSubscribeValueUpdateIdFn update_id; + wmMsgSubscribeValueFreeDataFn free_data; + + /** Keep this subscriber if possible. */ + uint is_persistent : 1; + /* tag to run when handling events, + * we may want option for immediate execution. */ + uint tag : 1; +} wmMsgSubscribeValue; + +/** One of many in #wmMsgSubscribeKey.values */ +typedef struct wmMsgSubscribeValueLink { + struct wmMsgSubscribeValueLink *next, *prev; + wmMsgSubscribeValue params; +} wmMsgSubscribeValueLink; + +void WM_msgbus_types_init(void); + +struct wmMsgBus *WM_msgbus_create(void); +void WM_msgbus_destroy(struct wmMsgBus *mbus); + +void WM_msgbus_clear_by_owner(struct wmMsgBus *mbus, void *owner); + +void WM_msg_dump(struct wmMsgBus *mbus, const char *info); +void WM_msgbus_handle(struct wmMsgBus *mbus, struct bContext *C); + +void WM_msg_publish_with_key(struct wmMsgBus *mbus, wmMsgSubscribeKey *msg_key); +wmMsgSubscribeKey *WM_msg_subscribe_with_key( + struct wmMsgBus *mbus, + const wmMsgSubscribeKey *msg_key_test, + const wmMsgSubscribeValue *msg_val_params); + +void WM_msg_id_update( + struct wmMsgBus *mbus, + struct ID *id_src, struct ID *id_dst); +void WM_msg_id_remove(struct wmMsgBus *mbus, const struct ID *id); + +/* -------------------------------------------------------------------------- */ +/* wm_message_bus_static.c */ + +enum { + /* generic window redraw */ + WM_MSG_STATICTYPE_WINDOW_DRAW = 0, + WM_MSG_STATICTYPE_SCREEN_EDIT = 1, + WM_MSG_STATICTYPE_FILE_READ = 2, +}; + +typedef struct wmMsgParams_Static { + int event; +} wmMsgParams_Static; + +typedef struct wmMsg_Static { + wmMsg head; /* keep first */ + wmMsgParams_Static params; +} wmMsg_Static; + +typedef struct wmMsgSubscribeKey_Static { + wmMsgSubscribeKey head; + wmMsg_Static msg; +} wmMsgSubscribeKey_Static; + +void WM_msgtypeinfo_init_static(wmMsgTypeInfo *msg_type); + +wmMsgSubscribeKey_Static *WM_msg_lookup_static( + struct wmMsgBus *mbus, const wmMsgParams_Static *msg_key_params); +void WM_msg_publish_static_params( + struct wmMsgBus *mbus, + const wmMsgParams_Static *msg_key_params); +void WM_msg_publish_static( + struct wmMsgBus *mbus, + /* wmMsgParams_Static (expanded) */ + int event); +void WM_msg_subscribe_static_params( + struct wmMsgBus *mbus, + const wmMsgParams_Static *msg_key_params, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr); +void WM_msg_subscribe_static( + struct wmMsgBus *mbus, + int event, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr); + +/* -------------------------------------------------------------------------- */ +/* wm_message_bus_rna.c */ + +typedef struct wmMsgParams_RNA { + /** when #PointerRNA.data & id.data are NULL. match against all. */ + PointerRNA ptr; + /** when NULL, match against any property. */ + const PropertyRNA *prop; + + /** + * Optional RNA data path for persistent RNA properties, ignore if NULL. + * otherwise it's allocated. + */ + char *data_path; +} wmMsgParams_RNA; + +typedef struct wmMsg_RNA { + wmMsg head; /* keep first */ + wmMsgParams_RNA params; +} wmMsg_RNA; + +typedef struct wmMsgSubscribeKey_RNA { + wmMsgSubscribeKey head; + wmMsg_RNA msg; +} wmMsgSubscribeKey_RNA; + +void WM_msgtypeinfo_init_rna(wmMsgTypeInfo *msg_type); + +wmMsgSubscribeKey_RNA *WM_msg_lookup_rna( + struct wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params); +void WM_msg_publish_rna_params( + struct wmMsgBus *mbus, const wmMsgParams_RNA *msg_key_params); +void WM_msg_publish_rna( + struct wmMsgBus *mbus, + /* wmMsgParams_RNA (expanded) */ + PointerRNA *ptr, PropertyRNA *prop); +void WM_msg_subscribe_rna_params( + struct wmMsgBus *mbus, + const wmMsgParams_RNA *msg_key_params, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr); +void WM_msg_subscribe_rna( + struct wmMsgBus *mbus, + PointerRNA *ptr, const PropertyRNA *prop, + const wmMsgSubscribeValue *msg_val_params, + const char *id_repr); + +/* ID variants */ +void WM_msg_subscribe_ID( + struct wmMsgBus *mbus, struct ID *id, const wmMsgSubscribeValue *msg_val_params, + const char *id_repr); +void WM_msg_publish_ID( + struct wmMsgBus *mbus, struct ID *id); + +/* Anonymous variants (for convenience) */ +#define WM_msg_subscribe_rna_anon_type(mbus, type_, value) { \ + WM_msg_subscribe_rna_params( \ + mbus, \ + &(const wmMsgParams_RNA){ \ + .ptr = (PointerRNA){.type = &RNA_##type_}, \ + .prop = NULL, \ + }, \ + value, __func__); \ +} ((void)0) +#define WM_msg_subscribe_rna_anon_prop(mbus, type_, prop_, value) { \ + extern PropertyRNA rna_##type_##_##prop_; \ + WM_msg_subscribe_rna_params( \ + mbus, \ + &(const wmMsgParams_RNA){ \ + .ptr = (PointerRNA){.type = &RNA_##type_}, \ + .prop = &rna_##type_##_##prop_, \ + }, \ + value, __func__); \ +} ((void)0) + +#endif /* __WM_MESSAGE_BUS_H__ */ |