/* PIP - Platform Independent Primitives Object, base class of some PIP classes, provide EVENT -> EVENT_HANDLER mechanism Ivan Pelipenko peri4ko@yandex.ru This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . */ #include "piobject.h" #include "pisysteminfo.h" #ifndef FREERTOS # include "pifile.h" #endif /** \class PIObject * \brief This is base class for any classes which use events -> handlers mechanism. * \details * \section PIObject_sec0 Events and Event handlers * %PIObject provide notification mechanism similar Qt but implemented * on language capabilities without any special preprocessors or compilers. * Any class inherits PIObject should use macro \a PIOBJECT() immediate * after declaration to proper compile. * * Event is a some abstract event that can be raised at any time. * Event is a function but declared with special macro \a EVENT(). * To raise event simply execute event function. * * Event handler is a function but declared with special macro * \a EVENT_HANDLER(). You can use event handlers as ordinary functions. * * Main goal of this mechanism is perform abstract connections between * various objects. This functionality provide macro \a CONNECT() which * connect some event of first object to some event handler or event of * second object. Each event can be connected any times to any event handlers. * * \image html events_handlers.png * * Example: \snippet piobject.cpp main * Result: \code{.cpp} handler B: 2 , 0.5 handler A: event to handler handler A: event to event \endcode */ PIString PIObject::__MetaFunc::arguments() const { return types.join(","); } PIString PIObject::__MetaFunc::fullFormat() const { PIString ret = type_ret + " " + scope + "::" + func_name +"("; for (int i = 0; i < types.size_s(); ++i) { if (i > 0) ret += ", "; ret += types[i] + " " + names[i]; } ret += ")"; return ret; } PIObject::PIObject(const PIString & name): _signature_(__PIOBJECT_SIGNATURE__), emitter_(0), thread_safe_(false), proc_event_queue(false) { //__PIVariantInitBuiltin__(); setName(name); setDebug(true); mutexObjects().lock(); objects() << this; mutexObjects().unlock(); //piCout << "new" << this; } PIObject::~PIObject() { //piCout << "delete" << this; mutexObjects().lock(); objects().removeAll(this); mutexObjects().unlock(); piDisconnect(this); } PIMap PIObject::properties() const { PIMap ret; piForeachC (PropertyHash p, properties_) ret[p.second.first] = p.second.second; return ret; } bool PIObject::execute(const PIString & method, const PIVector & vl) { if (method.isEmpty()) return false; if (!isPIObject()) { piCout << "Error: \"execute(" << method << ")\":" << (void*)this << "is not PIObject!"; return false; } int ac = 0; __MetaFunc func; bool ok = findSuitableMethodV(method, vl.size_s(), ac, func); if (!ok) return false; callAddrV(func.addrV, toThis(), ac, vl); return true; } bool PIObject::executeQueued(PIObject * performer, const PIString & method, const PIVector & vl) { if (!isPIObject()) { piCout << "Error: \"executeQueued(" << method << ")\": this(" << (void*)this << ") is not PIObject!"; return false; } if (!performer->isPIObject()) { piCout << "Error: \"executeQueued(" << method << ")\": performer(" << (void*)performer << ") is not PIObject!"; return false; } int ac = 0; __MetaFunc func; bool ok = findSuitableMethodV(method, vl.size_s(), ac, func); if (!ok) return false; performer->postQueuedEvent(__QueuedEvent(func.addrV, toThis(), this, performer, vl)); performer->proc_event_queue = true; return true; } void PIObject::piConnect(const PIString & src, const PIString & sig, void * dest, void * ev_h) { PIObject * o = findByName(src); if (o == 0) { piCout << "[PIObject] Can`t find object with name \"" << src << "\"!"; return; } PIMutexLocker _ml(o->mutex_connect); PIMutexLocker _mld(((PIObject*)dest)->mutex_connect, ((PIObject*)dest) != o); o->connections << __Connection(ev_h, 0, sig, (PIObject*)dest, dest); ((PIObject*)dest)->connectors << o; } void PIObject::piConnect(PIObject * src, const PIString & sig, const PIString & dest, void * ev_h) { PIObject * o = findByName(dest); if (o == 0) { piCout << "[PIObject] Can`t find object with name \"" << dest << "\"!"; return; } PIMutexLocker _ml(src->mutex_connect); PIMutexLocker _mld(o->mutex_connect, src != o); src->connections << __Connection(ev_h, 0, sig, o, o); ((PIObject*)o)->connectors << src; } void PIObject::piConnect(const PIString & src, const PIString & sig, const PIString & dest, void * ev_h) { PIObject * s = findByName(src); if (s == 0) { piCout << "[PIObject] Can`t find object with name \"" << src << "\"!"; return; } PIObject * d = findByName(dest); if (d == 0) { piCout << "[PIObject] Can`t find object with name \"" << dest << "\"!"; return; } PIMutexLocker _ml(s->mutex_connect); PIMutexLocker _mld(d->mutex_connect, s != d); s->connections << __Connection(ev_h, 0, sig, d, d); d->connectors << s; } /* PIStringList PIObject::events() { PIStringList l; for (PIMap::const_iterator i = signals_.begin(); i != signals_.end(); i++) l << (*i).first; return l; } */ PIStringList PIObject::scopeList() const { PIMutexLocker ml(__meta_mutex()); return __meta_data()[classNameID()].scope_list; } PIStringList PIObject::methodsEH() const { PIMutexLocker ml(__meta_mutex()); PIStringList ret; __MetaData & ehd(__meta_data()[classNameID()]); piForeachC (__EHPair & eh, ehd.eh_func) ret << eh.second.fullFormat(); return ret; } bool PIObject::isMethodEHContains(const PIString & name) const { PIMutexLocker ml(__meta_mutex()); __MetaData & ehd(__meta_data()[classNameID()]); piForeachC (__EHPair & eh, ehd.eh_func) if (eh.second.func_name == name) return true; return false; } PIString PIObject::methodEHArguments(const PIString & name) const { PIMutexLocker ml(__meta_mutex()); __MetaData & ehd(__meta_data()[classNameID()]); piForeachC (__EHPair & eh, ehd.eh_func) if (eh.second.func_name == name) return eh.second.arguments(); return PIString(); } PIString PIObject::methodEHFullFormat(const PIString & name) const { PIMutexLocker ml(__meta_mutex()); __MetaData & ehd(__meta_data()[classNameID()]); piForeachC (__EHPair & eh, ehd.eh_func) if (eh.second.func_name == name) return eh.second.fullFormat(); return PIString(); } PIString PIObject::methodEHFromAddr(const void * addr) const { return methodEH(addr).func_name; } PIVector PIObject::findEH(const PIString & name) const { PIVector<__MetaFunc> ret; __MetaData & ehd(__meta_data()[classNameID()]); piForeachC (__EHPair & eh, ehd.eh_func) if (eh.second.func_name == name) ret << eh.second; return ret; } PIObject::__MetaFunc PIObject::methodEH(const void * addr) const { PIMutexLocker ml(__meta_mutex()); return __meta_data()[classNameID()].eh_func.value(addr); } void PIObject::piConnect(PIObject * src, const PIString & sig, PIObject * dest_o, void * dest, void * ev_h, void * e_h, int args, const char * loc) { //piCout << "piConnect ..."; //piCout << "piConnect" << src << (void*)(dest) << sig; //piCout << "piConnect" << src->className() << "->" << ((PIObject*)dest)->className(); PIMutexLocker _ml(src->mutex_connect); PIMutexLocker _mld(dest_o->mutex_connect, src != dest_o); src->connections << __Connection(ev_h, e_h, sig, dest_o, dest, args); //piCout << "piConnect" << ((PIObject*)dest) << sig << ((PIObject*)dest)->connectors.size_s() << "..."; //piCout << "addConnector" << dest_o << src; dest_o->connectors << src; //piCout << "piConnect" << ((PIObject*)dest) << sig << ((PIObject*)dest)->connectors.size_s(); //piCout << "piConnect ok"; } bool PIObject::piConnectU(PIObject * src, const PIString & sig, PIObject * dest_o, void * dest, const PIString & hname, const char * loc, PIObject * performer) { if (src == 0 || dest_o == 0 || dest == 0) return false; if (!src->isPIObject()) { piCout << "[piConnectU] \"" << sig << "\" -> \"" << hname << "\" error: source object is not PIObject! (" << loc << ")"; return false; } if (!dest_o->isPIObject()) { piCout << "[piConnectU] \"" << sig << "\" -> \"" << hname << "\" error: destination object is not PIObject! (" << loc << ")"; return false; } PIMutexLocker ml(__meta_mutex()); PIMutexLocker mls(src->mutex_connect); PIMutexLocker mld(dest_o->mutex_connect, src != dest_o); PIVector<__MetaFunc> m_src = src->findEH(sig), m_dest = dest_o->findEH(hname); if (m_src.isEmpty()) { piCout << "[piConnectU] Error: can`t find event \"" << sig << "\" in class \"" << src->className() << "\"! (" << loc << ")"; return false; } if (m_dest.isEmpty()) { piCout << "[piConnectU] Error: can`t find handler \"" << hname << "\" in class \"" << dest_o->className() << "\"! (" << loc << ")"; return false; } void * addr_src(0), * addr_dest(0); int args(0); bool que = (performer != 0); piForeachC (__MetaFunc & fs, m_src) { if (addr_src != 0) break; piForeachC (__MetaFunc & fd, m_dest) { if (addr_src != 0) break; if (fs.arguments().startsWith(fd.arguments()) || fd.arguments().isEmpty()) { addr_src = fs.addr; addr_dest = que ? fd.addrV : fd.addr; args = fd.names.size_s(); } } } if (addr_src == 0) { piCout << "[piConnectU] Error: can`t find suitable pair of event \"" << sig << "\" in class \"" << src->className() << "\" and handler \"" << hname << "\" in class \"" << dest_o->className() << "\"! (" << loc << ")"; return false; } src->connections << PIObject::__Connection(addr_dest, addr_src, sig, dest_o, dest, args, performer); if (que) performer->proc_event_queue = true; dest_o->connectors << src; //piCout << cc << cq << _ol.size();//"connect" << src << "->" << dest_o << ", dest.connectors.size() =" << dest_o->connectors.size(); return true; } #ifdef PIP_CXX11_SUPPORT bool PIObject::piConnectLS(PIObject * src, const PIString & sig, std::function * f, const char * loc) { if (src == 0) { delete f; return false; } if (!src->isPIObject()) { piCout << "[piConnectLS] \"" << sig << "\" -> [lambda] error: source object is not PIObject! (" << loc << ")"; delete f; return false; } PIMutexLocker ml(__meta_mutex()); PIMutexLocker mls(src->mutex_connect); //piCout << "locked"; PIVector<__MetaFunc> m_src = src->findEH(sig); if (m_src.isEmpty()) { piCout << "[piConnectLS] Error: can`t find event \"" << sig << "\" in class \"" << src->className() << "\"! (" << loc << ")"; delete f; return false; } if (m_src.size() != 1) { piCout << "[piConnectLS] Error: can`t connect overloaded event \"" << sig << "\" in class \"" << src->className() << "\"! (" << loc << ")"; delete f; return false; } PIObject::__Connection conn(0, m_src[0].addr, sig); //piCout << "found"; conn.functor = f; src->connections << conn; //piCout << "finished"; return true; } #endif void PIObject::piDisconnect(PIObject * src, const PIString & sig, PIObject * dest, void * ev_h) { PIMutexLocker _ml(src->mutex_connect); PIMutexLocker _mld(dest->mutex_connect, src != dest); for (int i = 0; i < src->connections.size_s(); ++i) { __Connection & cc(src->connections[i]); if (cc.event == sig && cc.dest_o == dest && cc.slot == ev_h) { src->connections[i].destroy(); src->connections.remove(i); i--; } } dest->updateConnectors(); } void PIObject::piDisconnect(PIObject * src, const PIString & sig, PIObject * dest) { PIMutexLocker _ml(src->mutex_connect); PIMutexLocker _mld(dest->mutex_connect, src != dest); for (int i = 0; i < src->connections.size_s(); ++i) { __Connection & cc(src->connections[i]); if (cc.event == sig && cc.dest_o == dest) { src->connections[i].destroy(); src->connections.remove(i); i--; } } dest->updateConnectors(); } void PIObject::piDisconnect(PIObject * src, const PIString & sig) { PIMutexLocker _ml(src->mutex_connect); for (int i = 0; i < src->connections.size_s(); ++i) { __Connection & cc(src->connections[i]); if (cc.event == sig) { PIObject * dest = cc.dest_o; if (!dest) { src->connections[i].destroy(); src->connections.remove(i); i--; #if !defined(ANDROID) && !defined(MAC_OS) && !defined(FREERTOS) PIMutexLocker _mld(dest->mutex_connect, src != dest); #endif dest->updateConnectors(); } } } } void PIObject::piDisconnect(PIObject * src) { src->deleted(); PIMutexLocker _ml(src->mutex_connect); PIVector cv = src->connectors.toVector(); piForeach (PIObject * o, cv) { //piCout << "disconnect"<< src->className()<< o->className(); if (!o || (o == src)) continue; if (!o->isPIObject()) continue; #if !defined(ANDROID) && !defined(MAC_OS) && !defined(FREERTOS) PIMutexLocker _mld(o->mutex_connect, src != o); #endif PIVector<__Connection> & oc(o->connections); for (int i = 0; i < oc.size_s(); ++i) { if (oc[i].functor) continue; //piCout << " check" << (void*)(oc[i].dest_o) << "==" << (void*)(src); if (oc[i].dest_o == src) { oc[i].destroy(); oc.remove(i); --i; } } } piForeachC (PIObject::__Connection & c, src->connections) { if (c.functor) continue; if (!c.dest_o) continue; if (!c.dest_o->isPIObject()) continue; c.dest_o->connectors.remove(src); } for (int i = 0; i < src->connections.size_s(); ++i) src->connections[i].destroy(); src->connections.clear(); } void PIObject::updateConnectors() { //piCout << "*** updateConnectors" << this; connectors.clear(); PIMutexLocker _ml(mutexObjects()); piForeach (PIObject * o, objects()) { if (o == this) continue; PIVector<__Connection> & oc(o->connections); piForeach (__Connection & c, oc) if (c.dest == this) connectors << o; } } void PIObject::postQueuedEvent(const PIObject::__QueuedEvent & e) { mutex_queue.lock(); events_queue << e; mutex_queue.unlock(); } void * PIObject::toThis() const { //piCout << ptrOffset() << (void*)this << (void*)((char*)this - ptrOffset()); return (void*)((char*)this - ptrOffset()); } PIMutex & PIObject::__meta_mutex() { static PIMutex ret; return ret; } PIMap & PIObject::__meta_data() { static PIMap ret; return ret; } void PIObject::callQueuedEvents() { mutex_queue.lock(); PIVector<__QueuedEvent> qe = events_queue; events_queue.clear(); mutex_queue.unlock(); piForeachC (__QueuedEvent & e, qe) { if (e.dest_o->thread_safe_) e.dest_o->mutex_.lock(); e.dest_o->emitter_ = e.src; callAddrV(e.slot, e.dest, e.values.size_s(), e.values); e.dest_o->emitter_ = 0; if (e.dest_o->thread_safe_) e.dest_o->mutex_.unlock(); } } bool PIObject::findSuitableMethodV(const PIString & method, int args, int & ret_args, PIObject::__MetaFunc & ret) { PIVector<__MetaFunc> ml = findEH(method); if (ml.isEmpty()) { piCoutObj << "Error: no such method \"" << method << "\"!"; return false; } int mfi = -1, ac = -1, mac = -1; for (int i = 0; i < ml.size_s(); ++i) { __MetaFunc & m(ml[i]); int j = m.names.size_s(); if (mac < 0 || mac > j) mac = j; if ((j <= args) && (ac < j)) { ac = j; mfi = i; } } if (mfi < 0) { piCoutObj << "Error: no such suitable method \"" << method << "\", need at least" << mac << "arguments!"; return false; } ret_args = ac; ret = ml[mfi]; return true; } PIVector & PIObject::objects() { static PIVector * ret = new PIVector(); return *ret; } PIMutex & PIObject::mutexObjects() { static PIMutex ret; return ret; } void PIObject::callAddrV(void * slot, void * obj, int args, const PIVector & vl) { args = piMini(args, vl.size_s()); switch (args) { case 0: ((void(*)(void *))slot)(obj); break; case 1: ((void(*)(void * , const PIVariant & ))slot)(obj, vl[0]); break; case 2: ((void(*)(void * , const PIVariant & , const PIVariant & ))slot)(obj, vl[0], vl[1]); break; case 3: ((void(*)(void * , const PIVariant & , const PIVariant & , const PIVariant & ))slot)(obj, vl[0], vl[1], vl[2]); break; case 4: ((void(*)(void * , const PIVariant & , const PIVariant & , const PIVariant & , const PIVariant & ))slot)(obj, vl[0], vl[1], vl[2], vl[3]); break; default: break; } } PIString PIObject::simplifyType(const char * a) { PIString ret = PIStringAscii(a).trim(); int white = -1; for (int i = 0; i < ret.size_s(); ++i) { bool iw = ret[i] == ' ' || ret[i] == '\t' || ret[i] == '\r' || ret[i] == '\n'; //piCout << i << iw << white; if (white < 0) { if (iw) { white = i; continue; } } else { if (!iw) { ret.replace(white, i - white, " "); i = white; white = -1; //piCout << i; } } } ret.replaceAll(" &", "&"); ret.replaceAll(" *", "*"); if (ret.startsWith("const ") && ret.endsWith("&")) ret.cutLeft(6).cutRight(1).trim(); return ret; } bool PIObject::isPIObject(const PIObject * o) { if (!o) return false; return o->_signature_ == __PIOBJECT_SIGNATURE__; } void PIObject::dump(const PIString & line_prefix) const { //printf("dump %s \"%s\"\n", className(), name().data()); PICout(PICoutManipulators::AddNewLine) << line_prefix << "class " << className() << " (" << (const void*)this << ", \"" << name() << "\") {"; PICout(PICoutManipulators::AddNewLine) << line_prefix << " scope: " << scopeList().join(" -> "); PICout(PICoutManipulators::AddNewLine) << line_prefix << " properties {"; PICout(PICoutManipulators::AddNewLine) << line_prefix << " count: " << properties_.size_s(); //printf("dump %d properties\n", properties_.size()); piForeachC (PropertyHash p, properties_) if (p.first != PIString("name").hash()) PICout(PICoutManipulators::AddNewLine) << line_prefix << " " << p.second.first << ": " << p.second.second; //printf("dump %d properties ok\n", properties_.size()); PICout(PICoutManipulators::AddNewLine) << line_prefix << " }"; PICout(PICoutManipulators::AddNewLine) << line_prefix << " methods {"; __MetaData & ehd(__meta_data()[classNameID()]); PICout(PICoutManipulators::AddNewLine) << line_prefix << " count: " << ehd.eh_func.size_s(); //printf("dump %d methods\n", ehd.eh_func.size()); piForeachC (__EHPair & eh, ehd.eh_func) { PICout(PICoutManipulators::AddNewLine) << line_prefix << " " << eh.second.fullFormat(); } //printf("dump %d methods ok\n", ehd.eh_func.size()); PICout(PICoutManipulators::AddNewLine) << line_prefix << " }"; PICout(PICoutManipulators::AddNewLine) << line_prefix << " connections {"; PICout(PICoutManipulators::AddNewLine) << line_prefix << " count: " << connections.size_s(); //printf("dump %d connections\n",connections.size()); piForeachC (__Connection & c, connections) { PIObject * dst = c.dest_o; __MetaFunc ef = methodEH(c.signal); PIString src(c.event); if (!ef.func_name.isEmpty()) src = ef.func_name + "(" + ef.arguments() + ")"; if (dst) { __MetaFunc hf = dst->methodEH(c.slot); if (hf.func_name.isEmpty()) hf.func_name = "[BROKEN]"; else hf.func_name += "(" + hf.arguments() + ")"; PICout(PICoutManipulators::AddNewLine) << line_prefix << " " << src << " -> " << dst->className() << " (" << c.dest << ", \"" << dst->name() << "\")::" << hf.func_name; } else { PICout(PICoutManipulators::AddNewLine) << line_prefix << " " << src << " -> " << "[lambda]"; } } //printf("dump %d connections ok\n",connections.size()); PICout(PICoutManipulators::AddNewLine) << line_prefix << " }"; PICout(PICoutManipulators::AddNewLine) << line_prefix << "}"; } void dumpApplication() { PIMutexLocker _ml(PIObject::mutexObjects()); //printf("dump application ...\n"); PIDateTime cd = PIDateTime::current(); PISystemInfo * pi = PISystemInfo::instance(); PICout(PICoutManipulators::AddNewLine) << "application {"; PICout(PICoutManipulators::AddNewLine) << " PIP version: " << PIPVersion(); PICout(PICoutManipulators::AddNewLine) << " OS name: \"" << pi->OS_name << "\""; PICout(PICoutManipulators::AddNewLine) << " OS version: \"" << pi->OS_version << "\""; PICout(PICoutManipulators::AddNewLine) << " processors: " << pi->processorsCount; PICout(PICoutManipulators::AddNewLine) << " architecture: \"" << pi->architecture << "\""; PICout(PICoutManipulators::AddNewLine) << " hostname: \"" << pi->hostname << "\""; PICout(PICoutManipulators::AddNewLine) << " username: \"" << pi->user << "\""; PICout(PICoutManipulators::AddNewLine) << " exec command: \"" << pi->execCommand << "\""; PICout(PICoutManipulators::AddNewLine) << " started: " << pi->execDateTime.toString(); PICout(PICoutManipulators::AddNewLine) << " uptime: " << PITime::fromSystemTime(cd.toSystemTime() - pi->execDateTime.toSystemTime()).toString(); PICout(PICoutManipulators::AddNewLine) << " PIObjects {"; PICout(PICoutManipulators::AddNewLine) << " count: " << PIObject::objects().size_s(); piForeachC (PIObject * o, PIObject::objects()) o->dump(" "); PICout(PICoutManipulators::AddNewLine) << " }"; PICout(PICoutManipulators::AddNewLine) << "}"; //printf("dump application done\n"); } #ifndef FREERTOS bool dumpApplicationToFile(const PIString & path) { PIFile f(path + "_tmp"); f.setName("__S__DumpFile"); f.clear(); if (!f.open(PIIODevice::WriteOnly)) return false; bool ba = PICout::isBufferActive(); PICout::setBufferActive(true, true); dumpApplication(); f << PICout::buffer(); f.close(); PICout::setBufferActive(ba, true); PIFile::rename(path + "_tmp", path); return true; } #endif void PIObject::__MetaData::addScope(const PIString & s) { if (!scope_list.contains(s)) { scope_list << s; scope_id << s.hash(); } } void PIObject::__Connection::destroy() { #ifdef PIP_CXX11_SUPPORT if (functor) delete functor; functor = nullptr; #endif }