组件:c语言版本事件集EventSet

本文介绍了事件回调机制的基本概念和在界面编程中的应用,详细阐述了一个简化版的事件系统设计,包括静态函数和成员函数的支持、事件处理过程中的解注册安全性、单线程环境的考虑以及字符串事件名的使用。通过链表存储注册函数并使用智能指针确保内存安全。在实际案例中展示了事件的订阅、解订阅和派发流程,同时提供了关键的数据结构和处理函数的实现细节。
摘要由CSDN通过智能技术生成

背景介绍

相信做过界面编程的人都会对事件回调机制不陌生。事件的概念抽象了对象间的通讯机制,解耦了不同类型互相强引用的情况,将对象的关系用一种类似连接电话线的方式简化。而实现一套事件系统却不简单。通常我们需要满足的案例情况:

a.静态函数和成员函数(当然c不需要考虑成员函数)
b.在事件被派发过程中如果产生了解注册,系统不能崩溃
c.是否考虑多线程
d.事件系统通常需要一个事件被多个函数注册的情况

基本思路

我们设计时仅考虑简化了的情况,我们将以链表保存注册函数。需要考虑派发过程中的解注册。
以智能指针管理链表节点的内存,就可以达到安全延后释放的目标。

// Simplified EventSet for c.
//
// 1. Only support the one static function signature.
//    int(*func)(void*, struct mmEventArgs*);
//
// 2. Supports nested event. During the process of handling an event, a listener
//    can safely dispatch event and Subscribe/UnSubscribe other listeners.
//
// 3. Subscribe always insert tail.
//
// 4. Not thread safety.
//
// 5. Only support string for event name.
//
// 6. Not support delete the running EventSet at event callback.

1.最终使用案例

https://bitbucket.org/mm_longcheng/mm-core/src/dev/mm/src/core/mmEventSet.h

int HandlerFunc0(void* obj, struct mmEventArgs* evt)
{
    printf("handle 0\n");
    return MM_FALSE;
}
int HandlerFunc1(void* obj, struct mmEventArgs* evt)
{
    struct mmEventSet* p = (struct mmEventSet*)(obj);
    mmEventSet_UnSubscribeEvent(p, "Name", &HandlerFunc0, p);
    printf("handle 1\n");
    return MM_FALSE;
}
int HandlerFunc2(void* obj, struct mmEventArgs* evt)
{
    printf("handle 2\n");
    return MM_FALSE;
}
static void EventSetFunc(void)
{
    struct mmEventArgs args;
    struct mmEventSet s;
    mmEventSet_Init(&s);
    mmEventArgs_Reset(&args);
    mmEventSet_SubscribeEvent(&s, "Name", &HandlerFunc0, &s);
    
    // handle 0
    mmEventSet_FireEvent(&s, "Name", &args);
    
    mmEventSet_SubscribeEvent(&s, "Name", &HandlerFunc1, &s);
    mmEventSet_SubscribeEvent(&s, "Name", &HandlerFunc2, &s);
    
    // handle 0
    // handle 1 <handle 0 将在这里被安全解注册掉>
    // handle 2 后面的事件能正确处理
    mmEventSet_FireEvent(&s, "Name", &args);
    
    // handle 1
    // handle 2
    mmEventSet_FireEvent(&s, "Name", &args);
    
    mmEventSet_UnSubscribeEvent(&s, "Name", &HandlerFunc1, &s);
    
    // handle 2
    mmEventSet_FireEvent(&s, "Name", &args);
    
    mmEventSet_Destroy(&s);
}

2.前置数据结构组件

#include "core/mmString.h"
#include "container/mmRbtreeString.h"

// mmString 是一个字符串组件.
// 参考这篇文章 https://joellaity.com/2020/01/31/string.html
//
// mmRbtreeString 需要一个<字符串, void*>表
// 参考 http://en.wikipedia.org/wiki/Rbtree
// 以及linux rbtree.

3.主要数据结构

// 我们简化了事件参数,并且仅能传递继承于基础事件的参数
struct mmEventArgs
{
    // handle event number.
    long handled;
};

struct mmEventListHead;

// 事件系统特制的智能指针
struct mmEventSharedPtr
{
    // reference count.
    long* ref;
    // pointer.
    struct mmEventListHead* ptr;
};

// 事件系统特制的双向链表结构,注意头尾为智能指针
struct mmEventListHead
{
    struct mmEventSharedPtr next;
    struct mmEventSharedPtr prev;
};

// 存储回调函数的链表节点
struct mmEventHandler
{
    // function
    int(*func)(void*, struct mmEventArgs*);
    // object
    void* obj;
    // status for UnSubscribe.
    // 1 is Subscribe.
    // 0 is UnSubscribe.
    int status;
    // list head n.
    struct mmEventListHead n;
};

// 单一名称的事件链表
struct mmEvent
{
    // event name.
    struct mmString name;
    // head -> l.next
    // tail -> l.prev
    struct mmEventListHead l;
    // listener size.
    size_t size;
};

// 事件集 <名称, 事件>
struct mmEventSet
{
    // event tree.
    struct mmRbtreeStringVpt rbtree;
    // muted status.
    int muted;
};

4.主要处理函数

// 事件派发,注意事件的派发过程移除的处理
MM_EXPORT_DLL void 
mmEvent_FireEvent(struct mmEvent* p, struct mmEventArgs* args)
{
    struct mmEventSharedPtr next;
    struct mmEventSharedPtr curr;
    struct mmEventHandler* it = NULL;
    mmEventSharedPtr_Init(&next);
    mmEventSharedPtr_Init(&curr);
    mmEventSharedPtr_Assign(&next, &p->l.next);
    while (NULL != next.ptr)
    {
        mmEventSharedPtr_Assign(&curr, &next);
        mmEventSharedPtr_Assign(&next, &next.ptr->next);
        it = (struct mmEventHandler*)mmContainerOf(curr.ptr, struct mmEventHandler, n);
        if (0 != it->status)
        {
            if (mmEventHandler_FireEvent(it, args))
            {
                ++args->handled;
            }
        }
    }
    mmEventSharedPtr_Destroy(&curr);
    mmEventSharedPtr_Destroy(&next);
}

// 注册函数,注意链表节点的指向
MM_EXPORT_DLL struct mmEventHandler* 
mmEvent_Subscribe(struct mmEvent* p, int(*func)(void*, struct mmEventArgs*), void* obj)
{
    struct mmEventHandler* it = NULL;
    struct mmEventSharedPtr node;
    mmEventSharedPtr_Init(&node);
    it = mmEventSharedPtr_MakeReference(&node);
    it->status = 1;
    it->func = func;
    it->obj = obj;
    if(p->l.next.ptr)
    {
        // node->prev = tail;
        // tail->next = node;
        // tail = node;
        mmEventSharedPtr_Assign(&node.ptr->prev, &p->l.prev);
        mmEventSharedPtr_Assign(&p->l.prev.ptr->next, &node);
        mmEventSharedPtr_Assign(&p->l.prev, &node);
    }
    else
    {
        // head = node;
        // tail = node;
        mmEventSharedPtr_Assign(&p->l.prev, &node);
        mmEventSharedPtr_Assign(&p->l.next, &node);
    }
    mmEventSharedPtr_Destroy(&node);
    p->size++;
    return it;
}

// 解注册一个节点,注意节点移除
MM_EXPORT_DLL void 
mmEvent_UnSubscribeNode(struct mmEvent* p, struct mmEventSharedPtr* node)
{
    struct mmEventHandler* it = NULL;
    it = (struct mmEventHandler*)mmContainerOf(node->ptr, struct mmEventHandler, n);
    
    if(node->ptr->next.ptr)
    {
        // node->next->prev = node->prev;
        mmEventSharedPtr_Assign(&node->ptr->next.ptr->prev, &node->ptr->prev);
    }
    if(node->ptr->prev.ptr)
    {
        // node->prev->next = node->next;
        mmEventSharedPtr_Assign(&node->ptr->prev.ptr->next, &node->ptr->next);
    }

    // Mark it as deleted, this must be before the assignment of head and tail below,
    // because node can be a reference to head or tail, and after the assignment, node
    // can be null pointer.
    it->status = 0;

    if(p->l.next.ptr == node->ptr)
    {
        // head = node->next;
        mmEventSharedPtr_Assign(&p->l.next, &node->ptr->next);
    }
    if(p->l.prev.ptr == node->ptr)
    {
        // tail = node->prev;
        mmEventSharedPtr_Assign(&p->l.prev, &node->ptr->prev);
    }

    // don't modify node->previous or node->next
    // because node may be still used in a loop.
    
    p->size--;
}

// 解注册函数,注意智能指针的持有态流转,安全的延迟删除是怎么发生的
MM_EXPORT_DLL void 
mmEvent_UnSubscribe(struct mmEvent* p, int(*func)(void* obj, struct mmEventArgs*), void* obj)
{
    struct mmEventSharedPtr next;
    struct mmEventSharedPtr curr;
    struct mmEventHandler* it = NULL;
    mmEventSharedPtr_Init(&next);
    mmEventSharedPtr_Init(&curr);
    mmEventSharedPtr_Assign(&next, &p->l.next);
    while (NULL != next.ptr)
    {
        mmEventSharedPtr_Assign(&curr, &next);
        mmEventSharedPtr_Assign(&next, &next.ptr->next);
        it = (struct mmEventHandler*)mmContainerOf(curr.ptr, struct mmEventHandler, n);
        if (it->func == func && it->obj == obj)
        {
            mmEvent_UnSubscribeNode(p, &curr);
        }
    }
    mmEventSharedPtr_Destroy(&curr);
    mmEventSharedPtr_Destroy(&next);
}

5.原理

使用<名称, 事件>的机制即可,其中比较难以理解的是派发遍历时的解注册。

我们的链表节点指针是一个只能指针,所以即便遍历过程被移除,也仅仅只是将其断链而已,而在遍历结束后,减引用被连环触发,之前被断链的链表节点将被递次移除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值