From 445c5d5076925beca1827bb8df8b2ee5c7d6fc27 Mon Sep 17 00:00:00 2001 From: kevin Date: Tue, 13 Dec 2022 16:53:10 -0500 Subject: [PATCH] cwUi.cpp: Received 'value' messages are now automatically reflected to other Websock sessions. The idle message is now sent to the client only when no other message has been send for 'idle_delay_ms'. Added createFromRsrc(). The resource object passed via create() is now duplicated and maintained for the life of the UI object. --- cwUi.cpp | 243 ++++++++++++++++++++++++++++++++++++++++++------------- cwUi.h | 53 ++++++------ 2 files changed, 215 insertions(+), 81 deletions(-) diff --git a/cwUi.cpp b/cwUi.cpp index da8df6d..6ea3851 100644 --- a/cwUi.cpp +++ b/cwUi.cpp @@ -2,6 +2,7 @@ #include "cwLog.h" #include "cwCommonImpl.h" #include "cwMem.h" +#include "cwTime.h" #include "cwFileSys.h" #include "cwThread.h" #include "cwObject.h" @@ -90,11 +91,12 @@ namespace cw unsigned recvBufN; unsigned recvBufIdx; unsigned recvShiftN; - + object_t* uiRsrc; + unsigned* sessA; // sessA[ sessN ] array of wsSessId's unsigned sessN; unsigned sessAllocN; - + } ui_t; ui_t* _handleToPtr( handle_t h ) @@ -141,8 +143,9 @@ namespace cw rc_t rc = kOkRC; // free each element - for(unsigned i=0; ieleN; ++i) - _destroy_element( p->eleA[i] ); + if( p->eleA != nullptr ) + for(unsigned i=0; ieleN; ++i) + _destroy_element( p->eleA[i] ); appIdMapRecd_t* m = p->appIdMap; @@ -158,6 +161,9 @@ namespace cw mem::release(p->eleA); mem::release(p->buf); mem::release(p->recvBuf); + + if( p->uiRsrc != nullptr ) + p->uiRsrc->free(); mem::release(p); @@ -323,11 +329,11 @@ namespace cw if( wsSessId != kInvalidId ) rc = p->sendCbFunc( p->sendCbArg, wsSessId, msg, msgByteN ); else - { - for(unsigned i=0; isessN; ++i) - rc = p->sendCbFunc( p->sendCbArg, p->sessA[i], msg, msgByteN ); - - } + { + for(unsigned i=0; isessN; ++i) + rc = p->sendCbFunc( p->sendCbArg, p->sessA[i], msg, msgByteN ); + + } } return rc; @@ -434,7 +440,7 @@ namespace cw rc_t _transmitTree( ui_t* p, unsigned wsSessId, ele_t* ele ) { rc_t rc; - + // transmit the parent (unelss 'ele' is the root if(ele->uuId == kRootUuId || (rc = _transmitOneEle(p,wsSessId,ele)) == kOkRC ) { @@ -605,7 +611,7 @@ namespace cw rc = kOkRC; else { - rc = cwLogError(rc,"The UI element name could not be read."); + rc = cwLogError(rc,"The UI element 'name' could not be read."); goto errLabel; } @@ -978,13 +984,59 @@ namespace cw if( snprintf(mbuf,mbufN,mFmt,opStr,uuId,vbuf) >= mbufN-1 ) return cwLogError(kBufTooSmallRC,"The msg buffer is too small."); - //p->sendCbFunc(p->sendCbArg,wsSessId,mbuf,strlen(mbuf)); _websockSend(p,wsSessId,mbuf); } return rc; } + rc_t _sendValue( ui_t* p, unsigned wsSessId, unsigned uuId, const value_t& value ) + { + rc_t rc = kOkRC; + switch( value.tid ) + { + case kBoolTId: + rc = _sendValue(p,wsSessId,uuId,"%i",value.u.b?1:0); + break; + + case kIntTId: + rc = _sendValue(p,wsSessId,uuId,"%i",value.u.i); + break; + + case kUIntTId: + rc = _sendValue(p,wsSessId,uuId,"%i",value.u.u); + break; + + case kFloatTId: + rc = _sendValue(p,wsSessId,uuId,"%f",value.u.f); + break; + + case kDoubleTId: + rc = _sendValue(p,wsSessId,uuId,"%f",value.u.d); + break; + + case kStringTId: + rc = _sendValue(p,kInvalidId,uuId,"\"%s\"",value.u.s,"value",strlen(value.u.s)+10); + break; + + default: + assert(0); + } + + return rc; + } + + + void _reflect_value_msg( ui_t* p, unsigned wsSessId, ele_t* ele, const value_t& value ) + { + // send a 'value' message to all sessions except 'wsSessId'. + for(unsigned i=0; isessN; ++i) + if( p->sessA[i] != wsSessId ) + _sendValue(p,p->sessA[i],ele->uuId, value ); + } + + + rc_t _onNewRemoteUi( ui_t* p, unsigned wsSessId ) { rc_t rc = kOkRC; @@ -1060,7 +1112,7 @@ namespace cw memcpy(p->recvBuf + p->recvBufIdx, msg, msgByteN ); p->recvBufIdx += msgByteN; - + return kOkRC; } @@ -1138,6 +1190,7 @@ cw::rc_t cw::ui::create( p->recvBufN = fmtBufByteN; p->recvBufIdx = 0; p->recvShiftN = 0; + p->uiRsrc = uiRsrc->duplicate(); // create the root element if((ele = _createBaseEle(p, nullptr, kRootAppId, kInvalidId, "uiDivId" )) == nullptr || ele->uuId != kRootUuId ) @@ -1151,9 +1204,13 @@ cw::rc_t cw::ui::create( goto errLabel; if( uiRsrc != nullptr ) - if((rc = _createFromObj( p, uiRsrc, kInvalidId, kRootUuId, kInvalidId )) != kOkRC ) - rc = cwLogError(rc,"Create from UI resource failed."); + { + const object_t* main_obj = nullptr; + if((main_obj = uiRsrc->find( "main")) != nullptr ) + if((rc = _createFromObj( p, main_obj, kInvalidId, kRootUuId, kInvalidId )) != kOkRC ) + rc = cwLogError(rc,"Create from UI resource failed."); + } h.set(p); @@ -1222,7 +1279,7 @@ cw::rc_t cw::ui::onDisconnect( handle_t h, unsigned wsSessId ) ui_t* p = _handleToPtr(h); p->uiCbFunc( p->uiCbArg, wsSessId, kDisconnectOpId, kInvalidId, kInvalidId, kInvalidId, kInvalidId, nullptr ); - + // erase the disconnected session id by shrinking the array for(unsigned i=0; isessN; ++i) if( p->sessA[i] == wsSessId ) @@ -1238,6 +1295,7 @@ cw::rc_t cw::ui::onDisconnect( handle_t h, unsigned wsSessId ) return kOkRC; } + cw::rc_t cw::ui::onReceive( handle_t h, unsigned wsSessId, const void* void_msg, unsigned msgByteN ) { rc_t rc = kOkRC; @@ -1296,7 +1354,6 @@ cw::rc_t cw::ui::onReceive( handle_t h, unsigned wsSessId, const void* void_msg, // parse the 'opId' from the message opId = _labelToOpId(msg); - switch( opId ) { @@ -1314,7 +1371,10 @@ cw::rc_t cw::ui::onReceive( handle_t h, unsigned wsSessId, const void* void_msg, else { p->uiCbFunc( p->uiCbArg, wsSessId, opId, ele->logical_parent->appId, ele->uuId, ele->appId, ele->chanId, &value ); - + + // reflect the UI element value state to other wsSessions + _reflect_value_msg( p, wsSessId, ele, value ); + } break; @@ -1367,6 +1427,8 @@ cw::rc_t cw::ui::onReceive( handle_t h, unsigned wsSessId, const void* void_msg, } // switch opId } + + errLabel: return rc; } @@ -1519,6 +1581,22 @@ cw::rc_t cw::ui::createFromText( handle_t h, const char* text, unsigned parentU return rc; } + +cw::rc_t cw::ui::createFromRsrc( handle_t h, const char* label, unsigned parentUuId, unsigned chanId) +{ + + ui_t* p = _handleToPtr(h); + rc_t rc = kOkRC; + + if( p->uiRsrc == nullptr ) + rc = cwLogError(kInvalidStateRC,"The UI resource '%s' was not found because the UI sub-system was not given a resource file.",cwStringNullGuard(label)); + else + rc = createFromObject( h, p->uiRsrc, parentUuId, chanId, label ); + + return rc; + +} + cw::rc_t cw::ui::createDiv( handle_t h, unsigned& uuIdRef, unsigned parentUuId, const char* eleName, unsigned appId, unsigned chanId, const char* clas, const char* title ) { return _createOneEle( _handleToPtr(h), uuIdRef, "div", kInvalidId, parentUuId, eleName, appId, chanId, clas, title ); } @@ -1851,39 +1929,64 @@ cw::rc_t cw::ui::registerAppIdMap( handle_t h, const appIdMap_t* map, unsigned cw::rc_t cw::ui::sendValueBool( handle_t h, unsigned uuId, bool value ) { ui_t* p = _handleToPtr(h); - return _sendValue(p,kInvalidId,uuId,"%i",value?1:0); + value_t v = { .tid=kBoolTId }; + v.u.b = value; + return _sendValue(p,kInvalidId,uuId,v); + + //return _sendValue(p,kInvalidId,uuId,"%i",value?1:0); } cw::rc_t cw::ui::sendValueInt( handle_t h, unsigned uuId, int value ) { ui_t* p = _handleToPtr(h); - return _sendValue(p,kInvalidId,uuId,"%i",value); + value_t v = { .tid=kIntTId }; + v.u.i = value; + return _sendValue(p,kInvalidId,uuId,v); + + //return _sendValue(p,kInvalidId,uuId,"%i",value); } cw::rc_t cw::ui::sendValueUInt( handle_t h, unsigned uuId, unsigned value ) { ui_t* p = _handleToPtr(h); - return _sendValue(p,kInvalidId,uuId,"%i",value); + + value_t v = { .tid=kUIntTId }; + v.u.u = value; + return _sendValue(p,kInvalidId,uuId,v); + + //return _sendValue(p,kInvalidId,uuId,"%i",value); } cw::rc_t cw::ui::sendValueFloat( handle_t h, unsigned uuId, float value ) { ui_t* p = _handleToPtr(h); - return _sendValue(p,kInvalidId,uuId,"%f",value); + value_t v = { .tid=kFloatTId }; + v.u.f = value; + return _sendValue(p,kInvalidId,uuId,v); + + //return _sendValue(p,kInvalidId,uuId,"%f",value); } cw::rc_t cw::ui::sendValueDouble( handle_t h, unsigned uuId, double value ) { ui_t* p = _handleToPtr(h); - return _sendValue(p,kInvalidId,uuId,"%f",value); + value_t v = { .tid=kDoubleTId }; + v.u.d = value; + return _sendValue(p,kInvalidId,uuId,v); + + //return _sendValue(p,kInvalidId,uuId,"%f",value); } cw::rc_t cw::ui::sendValueString( handle_t h, unsigned uuId, const char* value ) { ui_t* p = _handleToPtr(h); + value_t v = { .tid=kStringTId }; + v.u.s = value; + return _sendValue(p,kInvalidId,uuId,v); + // +10 allows for extra value buffer space for double quotes and slashed - return _sendValue(p,kInvalidId,uuId,"\"%s\"",value,"value",strlen(value)+10); + //return _sendValue(p,kInvalidId,uuId,"\"%s\"",value,"value",strlen(value)+10); } void cw::ui::report( handle_t h ) @@ -1928,6 +2031,8 @@ namespace cw uiCallback_t uiCbFunc; websock::cbFunc_t wsCbFunc; unsigned wsTimeOutMs; + unsigned idleMsgPeriodMs; + time::spec_t lastRecvMsgTimeStamp; } ui_ws_t; ui_ws_t* _handleToPtr( handle_t h ) @@ -1969,12 +2074,15 @@ namespace cw default: cwLogError(kInvalidIdRC,"An invalid websock msgTypeId (%i) was encountered",msg_type); } + + time::get(p->lastRecvMsgTimeStamp); + } rc_t _webSockSend( void* cbArg, unsigned wsSessId, const void* msg, unsigned msgByteN ) { ui_ws_t* p = static_cast(cbArg); - return websock::send( p->wsH, kUiProtocolId, kInvalidId, msg, msgByteN ); + return websock::send( p->wsH, kUiProtocolId, wsSessId, msg, msgByteN ); } } @@ -1998,8 +2106,15 @@ cw::rc_t cw::ui::ws::parseArgs( const object_t& o, args_t& args, const char* ob return cwLogError(kLabelNotFoundRC,"The ui configuration label '%s' was not found.", cwStringNullGuard(object_label)); if((rc = op->getv( - "physRootDir", args.physRootDir, "dfltPageFn", args.dfltPageFn, "port", args.port, "rcvBufByteN", args.rcvBufByteN, - "xmtBufByteN", args.xmtBufByteN, "fmtBufByteN", args.fmtBufByteN, "websockTimeOutMs", args.wsTimeOutMs, "uiCfgFn", uiCfgFn )) != kOkRC ) + "physRootDir", args.physRootDir, + "dfltPageFn", args.dfltPageFn, + "port", args.port, + "rcvBufByteN", args.rcvBufByteN, + "xmtBufByteN", args.xmtBufByteN, + "fmtBufByteN", args.fmtBufByteN, + "websockTimeOutMs", args.wsTimeOutMs, + "idleMsgPeriodMs", args.idleMsgPeriodMs, + "uiCfgFn", uiCfgFn )) != kOkRC ) { rc = cwLogError(rc,"'ui' cfg. parse failed."); } @@ -2045,36 +2160,38 @@ cw::rc_t cw::ui::ws::create( handle_t& h, websock::cbFunc_t wsCbFunc ) { return create(h, - args.port, - args.physRootDir, - cbArg, - uiCbFunc, - uiRsrc, - appIdMapA, - appIdMapN, - wsCbFunc, - args.dfltPageFn, - args.wsTimeOutMs, - args.rcvBufByteN, - args.xmtBufByteN, - args.fmtBufByteN ); + args.port, + args.physRootDir, + cbArg, + uiCbFunc, + uiRsrc, + appIdMapA, + appIdMapN, + wsCbFunc, + args.dfltPageFn, + args.wsTimeOutMs, + args.idleMsgPeriodMs, + args.rcvBufByteN, + args.xmtBufByteN, + args.fmtBufByteN ); } cw::rc_t cw::ui::ws::create( handle_t& h, - unsigned port, - const char* physRootDir, - void* cbArg, - uiCallback_t uiCbFunc, - const object_t* uiRsrc, - const appIdMap_t* appIdMapA, - unsigned appIdMapN, - websock::cbFunc_t wsCbFunc, - const char* dfltPageFn, - unsigned websockTimeOutMs, - unsigned rcvBufByteN, - unsigned xmtBufByteN, - unsigned fmtBufByteN ) + unsigned port, + const char* physRootDir, + void* cbArg, + uiCallback_t uiCbFunc, + const object_t* uiRsrc, + const appIdMap_t* appIdMapA, + unsigned appIdMapN, + websock::cbFunc_t wsCbFunc, + const char* dfltPageFn, + unsigned websockTimeOutMs, + unsigned idleMsgPeriodMs, + unsigned rcvBufByteN, + unsigned xmtBufByteN, + unsigned fmtBufByteN ) { rc_t rc = kOkRC; @@ -2111,7 +2228,10 @@ cw::rc_t cw::ui::ws::create( handle_t& h, p->uiCbFunc = uiCbFunc; p->wsCbFunc = wsCbFunc; p->wsTimeOutMs = websockTimeOutMs; - + p->idleMsgPeriodMs = idleMsgPeriodMs; + // initialize the last received msg + time::get(p->lastRecvMsgTimeStamp); + h.set(p); errLabel: @@ -2144,13 +2264,20 @@ cw::rc_t cw::ui::ws::exec( handle_t h ) { rc_t rc = kOkRC; ui_ws_t* p = _handleToPtr(h); - + time::spec_t t; + if((rc = websock::exec( p->wsH, p->wsTimeOutMs )) != kOkRC) cwLogError(rc,"The UI websock execution failed."); // make the idle callback - ui::onReceive( p->uiH, kInvalidId, "idle", strlen("idle")+1 ); - + time::get(t); + + if( time::elapsedMs(p->lastRecvMsgTimeStamp,t) > p->idleMsgPeriodMs ) + { + ui::onReceive( p->uiH, kInvalidId, "idle", strlen("idle")+1 ); + p->lastRecvMsgTimeStamp = t; + } + return rc; } @@ -2158,6 +2285,8 @@ cw::rc_t cw::ui::ws::onReceive( handle_t h, unsigned protocolId, unsigned sessio { ui_ws_t* p = _handleToPtr(h); _webSockCb( p, protocolId, sessionId, msg_type, msg, byteN ); + + return kOkRC; } diff --git a/cwUi.h b/cwUi.h index f049374..5424fcb 100644 --- a/cwUi.h +++ b/cwUi.h @@ -66,11 +66,13 @@ namespace cw // Create multiple UI elements from an object_t representation. // The channel id of all elements created from the object will be assigned 'chanId'. - // The 'fieldName' string identifies - rc_t createFromObject( handle_t h, const object_t* o, unsigned parentUuId=kInvalidId, unsigned chanId=kInvalidId, const char* cfgFieldName=nullptr); + // The 'label' string identifies a child wihtin the outer object (e.g. o,fn,text, create( ... uiRsrc, ...)) which should be used to create the element. + rc_t createFromObject( handle_t h, const object_t* o, unsigned parentUuId=kInvalidId, unsigned chanId=kInvalidId, const char* label=nullptr); rc_t createFromFile( handle_t h, const char* fn, unsigned parentUuId=kInvalidId, unsigned chanId=kInvalidId); rc_t createFromText( handle_t h, const char* text, unsigned parentUuId=kInvalidId, unsigned chanId=kInvalidId); - + // This function uses 'label' to locate the child of the internal uiRsrc (passed to 'create()') as the element configuration resource. + rc_t createFromRsrc( handle_t h, const char* label, unsigned parentUuId=kInvalidId, unsigned chanId=kInvalidId); + // // Create Sincgle UI elements on the UI instance identified by wsSessId. // @@ -182,35 +184,38 @@ namespace cw unsigned rcvBufByteN; unsigned xmtBufByteN; unsigned fmtBufByteN; - unsigned wsTimeOutMs; + unsigned wsTimeOutMs; + unsigned idleMsgPeriodMs; // min period without messages before an idle message is generated } args_t; rc_t parseArgs( const object_t& o, args_t& args, const char* object_label="ui" ); rc_t releaseArgs( args_t& args ); rc_t create( handle_t& h, - const args_t& args, - void* cbArg, - uiCallback_t uiCbFunc, - const object_t* uiRsrc = nullptr, - const appIdMap_t* appIdMapA = nullptr, - unsigned appIdMapN = 0, - websock::cbFunc_t wsCbFunc = nullptr ); + const args_t& args, + void* cbArg, + uiCallback_t uiCbFunc, + const object_t* uiRsrc = nullptr, + const appIdMap_t* appIdMapA = nullptr, + unsigned appIdMapN = 0, + websock::cbFunc_t wsCbFunc = nullptr ); rc_t create( handle_t& h, - unsigned port, - const char* physRootDir, - void* cbArg, - uiCallback_t uiCbFunc, - const object_t* uiRsrc = nullptr, - const appIdMap_t* appIdMapA = nullptr, - unsigned appIdMapN = 0, - websock::cbFunc_t wsCbFunc = nullptr, - const char* dfltPageFn = "index.html", - unsigned websockTimeOutMs = 50, - unsigned rcvBufByteN = 1024, - unsigned xmtBufByteN = 1024, - unsigned fmtBufByteN = 4096 ); + unsigned port, + const char* physRootDir, + void* cbArg, + uiCallback_t uiCbFunc, + const object_t* uiRsrc = nullptr, + const appIdMap_t* appIdMapA = nullptr, + unsigned appIdMapN = 0, + websock::cbFunc_t wsCbFunc = nullptr, + const char* dfltPageFn = "index.html", + unsigned websockTimeOutMs = 50, + unsigned idleMsgPeriodMs = 50, + unsigned rcvBufByteN = 1024, + unsigned xmtBufByteN = 1024, + unsigned fmtBufByteN = 4096 + ); rc_t destroy( handle_t& h );