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());
  }
}
posted @   zjh6  阅读(29)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示