d事件系统
原文
这是简单的"事件总线"
系统,其工作方式类似"信号
",但更易使用.
程序员
必须继承事件
对象,并在其中定义如下所示必要
属性:
class EventChat : Event {
string msg;
this (ChatServer asrv, string amsg) { source = asrv; msg = amsg; }
}
然后必须使用.post()
方法提交.可合并为一行:
(new EventChat(srv0, "message #0")).post;
要处理所有提交
(排队
)事件,必须调用processEvents()
函数.
现在最有趣:接收和处理
事件.要接收
事件,程序员必须注册事件
监听器:
addEventListener(null, (EventChat evt) {
import std.stdio;
writeln("server #", (cast(ChatServer)evt.source).id, ": ", evt.msg);
log ~= evt.msg;
});
注意其中的巧妙技巧
:指定要就在闭包
中抓事件类型
,且事件系统仅路由EventChat(及其子类)
到监听
器.
在运行时,可以通过cast(MyObj)obj
转换来检查
对象类型,如果不能强制转换obj
为给定类型
,则可返回null
.事件系统在公共结构
中存储处理器
:
struct EventListenerInfo {
TypeInfo_Class ti;
void delegate (/*Event*/void* e) dg;
//`e`是`Event`子类;
uint id;
}
在此,有类类型
,但没有类
自身.如果要cast(ti)obj
,编译器会抱怨.则如何模拟动态转换
呢?
好吧,编译器为此转换
调用d运行时
函数,且该函数接受TypeInfo_Class
!所以模拟
编译器动作:
//导入运行时强制转换函数
private extern(C) void* _d_dynamic_cast (Object o, ClassInfo c);
//并调用它!
auto cobj = _d_dynamic_cast(obj, ti);
//现在,如果`obj`类型不对,则`cobj`为`null`
当然,可在addEventListener()
中创建内部闭包
并在其中强制转换
,因为那里有确切
类型,但是这里不能展示该很酷
的强制转换技巧!;-)
源码还包括"弱引用"
实现,因此为某些对象
添加事件侦听器不会使其"始终活动状态
".
现在,"事件总线
"的完整源码,如:
module eventbus;
//示例用法
class ChatServer {
uint id;
this (uint aid) { id = aid; }
}
class EventChat : Event {
string msg;
this (ChatServer asrv, string amsg) { source = asrv; msg = amsg; }
}
class ChatTextPane {
string[] log;
uint lid;
this () {
lid = addEventListener(null, (EventChat evt) {
import std.stdio;
writeln("#服务", (cast(ChatServer)evt.source).id, ": ", evt.msg);
log ~= evt.msg;
});
}
~this () { removeEventListener(lid); }
}
void main () {
auto srv0 = new ChatServer(0);
auto srv1 = new ChatServer(1);
// 文本面板接收所有聊天事件
auto textlog = new ChatTextPane();
// 仅接收`server1`聊天事件
addEventListener(srv1, (EventChat evt) {
assert(evt.source is srv1);
import std.stdio;
writeln("#1服务器: ", evt.msg);
});
// 现在发送一些事件
(new EventChat(srv0, "#0消息")).post;
(new EventChat(srv1, "#1消息")).post;
// 处理排队事件
processEvents();
}
//----------
public class Event {
Object source; //可为null
// 传播标志
enum PFlags : ubyte {
Eaten = 1U<<0,//已处理事件,但未取消
Cancelled = 1U<<1,
//已取消事件(可能*既*已处理又已取消!)
Posted = 1U<<7,//已提交事件
}
private ubyte flags;
final void post () {
if (posted) throw new Exception("不能提交已提交事件");
flags |= PFlags.Posted;
events ~= this;
}
final pure nothrow @safe @nogc:
void eat () { flags |= PFlags.Eaten; }
void cancel () { flags |= PFlags.Cancelled; }
const @property:
bool eaten () { return ((flags&(PFlags.Eaten|PFlags.Cancelled)) == PFlags.Eaten); }
bool cancelled () { return ((flags&PFlags.Cancelled) != 0); }
bool processed () { return (eaten || cancelled); }
bool posted () { return ((flags&PFlags.Posted) != 0); }
}
//返回可在`removereventListener()`中用的事件侦听器`ID`或0
public uint addEventListener(E:Event) (Object srcobj, void delegate (E evt) dg) {
if (dg is null) return 0;
foreach (ref EventListenerInfo eli; llist) {
if (typeid(E) == eli.ti && eli.dg is cast(EventListenerInfo.DgType)dg) return eli.id;
}
if (lastid == lastid.max) lastid = 1; //包装
llist ~= EventListenerInfo(typeid(E), cast(EventListenerInfo.DgType)dg, srcobj);
return llist[$-1].id;
}
//如果成功删除监听器,则返回`true`
//这是`@nogc`,因此可在`dtors`中调用
public bool removeEventListener (uint id) @nogc {
if (id == 0) return false;
foreach (ref EventListenerInfo eli; llist) {
if (eli.id == id) {
needListenerCleanup = true;
eli.id = 0;
eli.dg = null;
return true;
}
}
return false;
}
//调用它来处理所有排队事件
//注意,如果事件处理器一直在加事件,则该函数永远不会返回.
public void processEvents () {
if (events.length == 0) return;
cleanupListeners();
while (events.length > 0) {
auto evt = events.ptr[0];
foreach (immutable c; 1..events.length) events.ptr[c-1] = events.ptr[c];
events[$-1] = null;
events.length -= 1;
events.assumeSafeAppend;
try {
callEventListeners(evt);
} catch (Exception e) {
import std.stdio : stderr;
stderr.writefln("错误处理事件: %s", e.msg);
}
}
cleanupListeners();
}
// 私
private:
Event[] events; // 排队事件
//取`d运行时`动态转换函数
private extern(C) void* _d_dynamic_cast (Object o, ClassInfo c);
struct EventListenerInfo {
alias DgType = void delegate (/*Event*/void* e); // 实际上,`e`是任何`Event`子类
TypeInfo_Class ti;
DgType dg;
uint id;
Weak!Object srcobj;
this (TypeInfo_Class ati, DgType adg, Object sobj) {
ti = ati;
id = ++lastid;
dg = adg;
if (sobj !is null) srcobj = new Weak!Object(sobj);
}
}
uint lastid;
EventListenerInfo[] llist;
bool needListenerCleanup = false;
void cleanupListeners () {
if (!needListenerCleanup) return;
needListenerCleanup = false;
size_t pos = 0;
while (pos < llist.length) {
if (llist.ptr[pos].srcobj !is null && llist.ptr[pos].srcobj.empty) { llist.ptr[pos].id = 0; }
if (llist.ptr[pos].id == 0) {
foreach (immutable c; pos+1..llist.length) llist.ptr[c-1] = llist.ptr[c];
llist[$-1] = EventListenerInfo.init;
llist.length -= 1;
llist.assumeSafeAppend;
} else {
++pos;
}
}
}
void callEventListeners (Event evt) {
if (evt is null || evt.processed) return;
foreach (ref EventListenerInfo eli; llist) {
if (eli.id == 0) continue;
if (eli.srcobj !is null) {
//如果源对象死亡,则标记监听器为删除
if (eli.srcobj.empty) { needListenerCleanup = true; eli.id = 0; eli.dg = null; continue; }
if (evt.source is null) continue;
if (evt.source !is eli.srcobj.object) continue;
}
// 以下行用`TypeInfo_Class`来`cast(ObjType)evt`
auto ecc = _d_dynamic_cast(evt, eli.ti);
if (ecc !is null) {
eli.dg(ecc);
if (evt.processed) break;
}
}
}
// 线安弱引用实现
// 基于http://forum.dlang.org/thread/jjote0$1cql$1@digitalmars.com.
import core.atomic, core.memory;
private alias void delegate (Object) DEvent;
private extern (C) void rt_attachDisposeEvent (Object h, DEvent e);
private extern (C) void rt_detachDisposeEvent (Object h, DEvent e);
final class Weak(T : Object) {
// 用保守垃集技巧.不适用`压缩/复制`.如`D`添加`压缩`,则用`内置`弱引用.
private size_t mObject;
private size_t mPtr;
private size_t mHash;
this (T obj=null) @trusted {
hook(obj);
}
@property T object () const @trusted nothrow {
auto obj = cast(T)cast(void*)(atomicLoad(*cast(shared)&mObject)^0xa5a5a5a5u);
// 移动对象,至垃集堆,因而,可查询是否活着.
//注意,即使`objlocal`的强制转换和赋值
//未把对象放入堆栈,此调用也会.
//所以,这是安全的
if (obj !is null && GC.addrOf(cast(void*)obj)) return obj;
return null;
}
@property void object (T obj) @trusted {
auto oobj = cast(T)cast(void*)(atomicLoad(*cast(shared)&mObject)^0xa5a5a5a5u);
if (oobj !is null && GC.addrOf(cast(void*)oobj)) unhook(oobj);
oobj = null;
hook(obj);
}
@property bool empty () const @trusted nothrow {
return (object is null);
}
void clear () @trusted { object = null; }
void opAssign (T obj) @trusted { object = obj; }
private void hook (Object obj) @trusted {
if (obj !is null) {
//auto ptr = cast(size_t)cast(void*)obj;
// Andrej Mitrovic修复
auto ptr = cast(size_t)*(cast(void**)&obj);
// 使用原子,因为并非所有架构都可保证这些值的原子存储和加载
atomicStore(*cast(shared)&mObject, ptr^0xa5a5a5a5u);
// 只赋值一次,所以没有原子
mPtr = ptr^0xa5a5a5a5u;
mHash = typeid(T).getHash(&obj);
rt_attachDisposeEvent(obj, &unhook);
GC.setAttr(cast(void*)this, GC.BlkAttr.NO_SCAN);
} else {
atomicStore(*cast(shared)&mObject, cast(size_t)0^0xa5a5a5a5u);
}
}
private void unhook (Object obj) @trusted {
rt_detachDisposeEvent(obj, &unhook);
//该赋值很重要.
//如果收集时,不置`mObject`为空,则`垃集`为新对象`重用`内存时,对象中的检查,可能返回`假阳`性.
atomicStore(*cast(shared)&mObject, cast(size_t)0^0xa5a5a5a5u);
}
override bool opEquals (Object o) @trusted nothrow {
if (this is o) return true;
if (auto weak = cast(Weak!T)o) return (mPtr == weak.mPtr);
return false;
}
override int opCmp (Object o) @trusted nothrow {
if (auto weak = cast(Weak!T)o) return (mPtr > weak.mPtr ? 1 : mPtr < weak.mPtr ? -1 : 0);
return 1;
}
override size_t toHash () @trusted nothrow {
auto obj = object;
return (obj ? typeid(T).getHash(&obj) : mHash);
}
override string toString () @trusted {
auto obj = object;
return (obj ? obj.toString() : toString());
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现