#ifndef _EVENT_EMITTER_ #define _EVENT_EMITTER_ #include #include #include #include // -> std::cout << "..." << std::endl; #define THIS_EMITTER \ EventEmitter *emitter = ObjectWrap::Unwrap(info.This()); class EventEmitter : public Nan::ObjectWrap { typedef Nan::CopyablePersistentTraits::CopyablePersistent FN_TYPE; typedef std::deque VEC_TYPE; typedef std::map MAP_TYPE; typedef std::map FNMAP_TYPE; typedef VEC_TYPE::iterator IT_TYPE; typedef MAP_TYPE::iterator MAP_IT_TYPE; typedef FNMAP_TYPE::iterator FNMAP_IT_TYPE; protected: EventEmitter () { _maxListeners = 0; _freeId = 0; } virtual ~EventEmitter () {} static void extendPrototype(v8::Local &proto) { Nan::SetPrototypeMethod(proto, "listenerCount", jsListenerCount); Nan::SetPrototypeMethod(proto, "addListener", jsAddListener); Nan::SetPrototypeMethod(proto, "emit", jsEmit); Nan::SetPrototypeMethod(proto, "eventNames", jsEventNames); Nan::SetPrototypeMethod(proto, "getMaxListeners", jsGetMaxListeners); Nan::SetPrototypeMethod(proto, "listeners", jsListeners); Nan::SetPrototypeMethod(proto, "on", jsOn); Nan::SetPrototypeMethod(proto, "once", jsOnce); Nan::SetPrototypeMethod(proto, "prependListener", jsPrependListener); Nan::SetPrototypeMethod(proto, "prependOnceListener", jsPrependOnceListener); Nan::SetPrototypeMethod(proto, "removeAllListeners", jsRemoveAllListeners); Nan::SetPrototypeMethod(proto, "removeListener", jsRemoveListener); Nan::SetPrototypeMethod(proto, "setMaxListeners", jsSetMaxListeners); Nan::SetPrototypeMethod(proto, "rawListeners", jsRawListeners); } static void extendConstructor(v8::Local &ctorFn) { v8::Local ctor = v8::Local::Cast(ctorFn); Nan::SetMethod(ctor, "listenerCount", jsStaticListenerCount); } public: // C++ side emit() method void emit(const std::string &name, int argc = 0, v8::Local *argv = NULL) { // Important! As actual get map[key] produces a new (empty) map entry if ( _listeners.find(name) == _listeners.end() ) { return; } // A copy is intended, because handlers can call removeListener (and they DO) VEC_TYPE list = _listeners[name]; if (list.empty()) { return; } for (IT_TYPE it = list.begin(); it != list.end(); ++it) { Nan::Callback callback(Nan::New(*it)); if ( ! callback.IsEmpty() ) { callback.Call(argc, argv); } } } // C++ side on() method void on(const std::string &name, v8::Local that, const std::string &method) { v8::Local code = JS_STR( "((emitter, name, that, method) => emitter.on(name, that[method]))" ); v8::Local connector = v8::Local::Cast(v8::Script::Compile(code)->Run()); Nan::Callback connectorCb(connector); v8::Local emitter = Nan::New(); this->Wrap(emitter); v8::Local argv[] = { emitter, JS_STR(name.c_str()), that, JS_STR(method.c_str()) }; connectorCb.Call(4, argv); } protected: // Deprecated static method static NAN_METHOD(jsStaticListenerCount) { REQ_OBJ_ARG(0, obj); EventEmitter *emitter = ObjectWrap::Unwrap(obj); REQ_UTF8_ARG(1, name); const VEC_TYPE &list = emitter->_listeners[*name]; RET_VALUE(JS_INT(static_cast(list.size()))); } static NAN_METHOD(jsAddListener) { _wrapListener(info); } static NAN_METHOD(jsEmit) { THIS_EMITTER; REQ_UTF8_ARG(0, name); int length = info.Length(); std::vector< v8::Local > args; for (int i = 1; i < length; i++) { args.push_back(info[i]); } emitter->emit(*name, length - 1, &args[0]); } static NAN_METHOD(jsEventNames) { THIS_EMITTER; v8::Local jsNames = Nan::New(emitter->_raw.size()); if (emitter->_raw.empty()) { RET_VALUE(jsNames); return; } int i = 0; for (MAP_IT_TYPE it = emitter->_raw.begin(); it != emitter->_raw.end(); ++it, i++) { jsNames->Set(JS_INT(i), JS_STR(it->first)); } RET_VALUE(jsNames); } static NAN_METHOD(jsGetMaxListeners) { THIS_EMITTER; RET_VALUE(JS_INT(emitter->_maxListeners)); } static NAN_METHOD(jsListenerCount) { THIS_EMITTER; REQ_UTF8_ARG(0, name); const VEC_TYPE &list = emitter->_listeners[*name]; RET_VALUE(JS_INT(static_cast(list.size()))); } static NAN_METHOD(jsListeners) { THIS_EMITTER; REQ_UTF8_ARG(0, name); VEC_TYPE &list = emitter->_listeners[*name]; v8::Local jsListeners = Nan::New(list.size()); if (list.empty()) { RET_VALUE(jsListeners); return; } int i = 0; for (IT_TYPE it = list.begin(); it != list.end(); ++it, i++) { jsListeners->Set(JS_INT(i), Nan::New(*it)); } RET_VALUE(jsListeners); } static inline void _addListener( const Nan::FunctionCallbackInfo &info, const std::string &name, Nan::Persistent &cb, bool isFront ) { THIS_EMITTER; v8::Local args[] = { info[0], info[1] }; emitter->emit("newListener", 2, args); if (isFront) { emitter->_listeners[name].push_front(cb); emitter->_raw[name].push_front(cb); } else { emitter->_listeners[name].push_back(cb); emitter->_raw[name].push_back(cb); } int count = emitter->_raw[name].size(); if (emitter->_maxListeners > 0 && count > emitter->_maxListeners) { std::cout << "EventEmitter Warning: too many listeners ("; std::cout << count << " > " << emitter->_maxListeners << ") on '"; std::cout << name << "' event, possible memory leak." << std::endl; // Some JS magic to retrieve the call stack v8::Local code = JS_STR( "(new Error()).stack.split('\\n').slice(1).join('\\n')" ); v8::Local stack = v8::Local::Cast( v8::Script::Compile(code)->Run() ); Nan::Utf8String stackStr(stack); std::cout << *stackStr << std::endl; } } static inline void _wrapListener( const Nan::FunctionCallbackInfo &info, bool isFront = false ) { REQ_UTF8_ARG(0, name); REQ_FUN_ARG(1, cb); Nan::Persistent persistentCb; persistentCb.Reset(cb); _addListener(info, *name, persistentCb, isFront); } static inline void _addOnceListener( const Nan::FunctionCallbackInfo &info, const std::string &name, Nan::Persistent &raw, Nan::Persistent &cb, bool isFront ) { THIS_EMITTER; v8::Local args[] = { info[0], info[1] }; emitter->emit("newListener", 2, args); if (isFront) { emitter->_listeners[name].push_front(cb); emitter->_raw[name].push_front(raw); } else { emitter->_listeners[name].push_back(cb); emitter->_raw[name].push_back(raw); } int nextId = emitter->_freeId++; emitter->_wrappedIds[nextId] = cb; emitter->_rawIds[nextId] = raw; int count = emitter->_raw[name].size(); if (emitter->_maxListeners > 0 && count > emitter->_maxListeners) { std::cout << "EventEmitter Warning: too many listeners ("; std::cout << count << " > " << emitter->_maxListeners << ") on '"; std::cout << name << "' event, possible memory leak." << std::endl; // Some JS magic to retrieve the call stack v8::Local code = JS_STR( "(new Error()).stack.split('\\n').slice(1).join('\\n')" ); v8::Local stack = v8::Local::Cast( v8::Script::Compile(code)->Run() ); Nan::Utf8String stackStr(stack); std::cout << *stackStr << std::endl; } } static inline void _wrapOnceListener( const Nan::FunctionCallbackInfo &info, bool isFront = false ) { REQ_UTF8_ARG(0, name); REQ_FUN_ARG(1, raw); v8::Local code = JS_STR( "((emitter, name, cb) => (...args) => {\n\ cb(...args);\n\ emitter.removeListener(name, cb);\n\ })" ); v8::Local decor = v8::Local::Cast(v8::Script::Compile(code)->Run()); Nan::Callback decorCb(decor); v8::Local argv[] = { info.This(), info[0], raw }; v8::Local wrap = v8::Local::Cast(decorCb.Call(3, argv)); Nan::Persistent persistentWrap; persistentWrap.Reset(wrap); Nan::Persistent persistentRaw; persistentRaw.Reset(raw); _addOnceListener(info, *name, persistentRaw, persistentWrap, isFront); } static NAN_METHOD(jsOn) { _wrapListener(info); } static NAN_METHOD(jsOnce) { _wrapOnceListener(info); } static NAN_METHOD(jsPrependListener) { _wrapListener(info, true); } static NAN_METHOD(jsPrependOnceListener) { _wrapOnceListener(info, true); } static NAN_METHOD(jsRemoveAllListeners) { THIS_EMITTER; if (info.Length() > 0 && info[0]->IsString()) { MAP_TYPE tmpMap = emitter->_raw; emitter->_listeners.clear(); emitter->_raw.clear(); emitter->_wrappedIds.clear(); emitter->_rawIds.clear(); for (MAP_IT_TYPE itMap = tmpMap.begin(); itMap != tmpMap.end(); ++itMap) { const std::string ¤t = itMap->first; VEC_TYPE &list = itMap->second; for (IT_TYPE it = list.begin(); it != list.end(); ++it) { v8::Local args[] = { JS_STR(current.c_str()), Nan::New(*it) }; emitter->emit("removeListener", 2, args); } } return; } REQ_UTF8_ARG(0, n); std::string name = std::string(*n); VEC_TYPE &list = emitter->_raw[name]; if (list.empty()) { return; } if (emitter->_rawIds.size()) { std::vector removes; for (IT_TYPE it = list.begin(); it != list.end(); ++it) { FN_TYPE fn = *it; for (FNMAP_IT_TYPE itRaw = emitter->_rawIds.begin(); itRaw != emitter->_rawIds.end(); ++itRaw) { if (fn == itRaw->second) { removes.push_back(itRaw->first); } } } if (removes.size()) { for (std::vector::const_iterator it = removes.begin(); it != removes.end(); ++it) { emitter->_wrappedIds.erase(*it); emitter->_rawIds.erase(*it); } } } VEC_TYPE tmpVec = emitter->_raw[name]; emitter->_listeners[name].clear(); emitter->_raw[name].clear(); for (IT_TYPE it = tmpVec.begin(); it != tmpVec.end(); ++it) { v8::Local args[] = { JS_STR(name.c_str()), Nan::New(*it) }; emitter->emit("removeListener", 2, args); } } static NAN_METHOD(jsRemoveListener) { THIS_EMITTER; REQ_UTF8_ARG(0, n); REQ_FUN_ARG(1, raw); Nan::Persistent persistentRaw; persistentRaw.Reset(raw); std::string name = std::string(*n); VEC_TYPE &rawList = emitter->_raw[name]; if (rawList.empty()) { return; } v8::Local args[] = { info[0], info[1] }; for (IT_TYPE it = rawList.begin(); it != rawList.end(); ++it) { if (*it == persistentRaw) { rawList.erase(it); if (rawList.empty()) { emitter->_raw.erase(name); } break; } } VEC_TYPE &wrapList = emitter->_listeners[name]; if (emitter->_wrappedIds.size() == 0) { for (IT_TYPE it = wrapList.begin(); it != wrapList.end(); ++it) { if (*it == persistentRaw) { wrapList.erase(it); if (wrapList.empty()) { emitter->_listeners.erase(name); } break; } } emitter->emit("removeListener", 2, args); return; } for (FNMAP_IT_TYPE itRaw = emitter->_rawIds.begin(); itRaw != emitter->_rawIds.end(); ++itRaw) { if (persistentRaw == itRaw->second) { FN_TYPE fn = emitter->_wrappedIds[itRaw->first]; for (IT_TYPE it = wrapList.begin(); it != wrapList.end(); ++it) { if (*it == fn) { wrapList.erase(it); if (wrapList.empty()) { emitter->_listeners.erase(name); } break; } } emitter->_wrappedIds.erase(itRaw->first); emitter->_rawIds.erase(itRaw->first); break; } } emitter->emit("removeListener", 2, args); } static NAN_METHOD(jsSetMaxListeners) { THIS_EMITTER; REQ_INT32_ARG(0, value); emitter->_maxListeners = value; } static NAN_METHOD(jsRawListeners) { THIS_EMITTER; REQ_UTF8_ARG(0, name); VEC_TYPE &list = emitter->_raw[*name]; v8::Local jsListeners = Nan::New(list.size()); if (list.empty()) { RET_VALUE(jsListeners); return; } int i = 0; for (IT_TYPE it = list.begin(); it != list.end(); ++it, i++) { jsListeners->Set(JS_INT(i), Nan::New(*it)); } RET_VALUE(jsListeners); } private: int _maxListeners; MAP_TYPE _listeners; MAP_TYPE _raw; int _freeId; FNMAP_TYPE _wrappedIds; FNMAP_TYPE _rawIds; }; #endif // _EVENT_EMITTER_