diff --git a/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx b/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx index 31a0fe9cf91c2fe94ae1e4e0fc2be52f4ffa13a5..5be2df35eb3672d6415b515756a571fe15ef85a8 100644 --- a/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx +++ b/graf2d/gpad/v7/inc/ROOT/TCanvas.hxx @@ -136,7 +136,7 @@ public: bool IsModified() const; /// update drawing - void Update(bool async = false); + void Update(bool async = false, CanvasCallback_t callback = nullptr); /// Save canvas in image file void SaveAs(const std::string &filename, bool async = false, CanvasCallback_t callback = nullptr); diff --git a/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx b/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx index c9960f985600bd4c08328c9517a8297c26b0fad0..fdc4f5047ed62c9d12a5b9625e1c4674a6e3de97 100644 --- a/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx +++ b/graf2d/gpad/v7/inc/ROOT/TVirtualCanvasPainter.hxx @@ -57,7 +57,7 @@ public: virtual void AddDisplayItem(TDisplayItem *item) = 0; /// indicate that canvas changed, provides current version of the canvas - virtual void CanvasUpdated(uint64_t, bool) = 0; + virtual void CanvasUpdated(uint64_t, bool, CanvasCallback_t) = 0; /// return true if canvas modified since last painting virtual bool IsCanvasModified(uint64_t) const = 0; diff --git a/graf2d/gpad/v7/src/TCanvas.cxx b/graf2d/gpad/v7/src/TCanvas.cxx index 07287e18fa6d859b0a60a904ed0045a5f2e565e4..fd00696d70bc709ad532f8da376e8823aa354eac 100644 --- a/graf2d/gpad/v7/src/TCanvas.cxx +++ b/graf2d/gpad/v7/src/TCanvas.cxx @@ -48,10 +48,10 @@ bool ROOT::Experimental::TCanvas::IsModified() const return fPainter ? fPainter->IsCanvasModified(fModified) : fModified; } -void ROOT::Experimental::TCanvas::Update(bool async) +void ROOT::Experimental::TCanvas::Update(bool async, CanvasCallback_t callback) { if (fPainter) - fPainter->CanvasUpdated(fModified, async); + fPainter->CanvasUpdated(fModified, async, callback); // SnapshotList_t lst; // for (auto&& drw: fPrimitives) { @@ -97,7 +97,7 @@ void ROOT::Experimental::TCanvas::Show(const std::string &where) fPainter = Internal::TVirtualCanvasPainter::Create(*this, batch_mode); if (fPainter) { fPainter->NewDisplay(where); - fPainter->CanvasUpdated(fModified, true); // trigger async display + fPainter->CanvasUpdated(fModified, true, nullptr); // trigger async display } } diff --git a/gui/canvaspainter/v7/src/TCanvasPainter.cxx b/gui/canvaspainter/v7/src/TCanvasPainter.cxx index c1ad9e4ea201bc3e73994bb8b50d89e493b3182c..e175879e575721ad8e88667226afaeb1005b1520 100644 --- a/gui/canvaspainter/v7/src/TCanvasPainter.cxx +++ b/gui/canvaspainter/v7/src/TCanvasPainter.cxx @@ -173,18 +173,26 @@ private: }; struct WebCommand { - std::string fId; ///<! command identifier - std::string fName; ///<! command name - std::string fArg; ///<! command arg - bool fRunning; ///<! true when command submitted + std::string fId; ///<! command identifier + std::string fName; ///<! command name + std::string fArg; ///<! command arg + bool fRunning; ///<! true when command submitted ROOT::Experimental::CanvasCallback_t fCallback; ///<! callback function associated with command WebCommand() : fId(), fName(), fArg(), fRunning(false), fCallback() {} }; + struct WebUpdate { + uint64_t fVersion; ///<! canvas version + ROOT::Experimental::CanvasCallback_t fCallback; ///<! callback function associated with command + WebUpdate() : fVersion(0), fCallback() {} + }; + typedef std::list<WebConn> WebConnList; typedef std::list<WebCommand> WebCommandsList; + typedef std::list<WebUpdate> WebUpdatesList; + typedef std::vector<ROOT::Experimental::Detail::TMenuItem> MenuItemsVector; /// The canvas we are painting. It might go out of existence while painting. @@ -201,6 +209,7 @@ private: uint64_t fSnapshotVersion; ///!< version of snapshot std::string fSnapshot; ///!< last produced snapshot uint64_t fSnapshotDelivered; ///!< minimal version delivered to all connections + WebUpdatesList fUpdatesLst; ///!< list of callbacks for canvas update static std::string fAddr; ///<! real http address (when assigned) static THttpServer *gServer; ///<! server @@ -234,7 +243,7 @@ public: /// The painter observes it; it needs to know should the TCanvas be deleted. TCanvasPainter(const std::string &name, const ROOT::Experimental::TCanvas &canv, bool batch_mode) : THttpWSHandler(name.c_str(), "title"), fCanvas(canv), fBatchMode(batch_mode), fWebConn(), fDisplayList(), - fCmds(), fCmdsCnt(0), fWaitingCmdId(), fSnapshotVersion(0), fSnapshot(), fSnapshotDelivered(0) + fCmds(), fCmdsCnt(0), fWaitingCmdId(), fSnapshotVersion(0), fSnapshot(), fSnapshotDelivered(0), fUpdatesLst() { CreateHttpServer(); gServer->Register("/web7gui", this); @@ -244,12 +253,13 @@ public: virtual void AddDisplayItem(ROOT::Experimental::TDisplayItem *item) final; - virtual void CanvasUpdated(uint64_t, bool) override; + virtual void CanvasUpdated(uint64_t, bool, ROOT::Experimental::CanvasCallback_t) override; virtual bool IsCanvasModified(uint64_t) const override; /// perform special action when drawing is ready - virtual void DoWhenReady(const std::string &cmd, const std::string &arg, bool async, ROOT::Experimental::CanvasCallback_t callback) final; + virtual void DoWhenReady(const std::string &cmd, const std::string &arg, bool async, + ROOT::Experimental::CanvasCallback_t callback) final; // open new display for the canvas virtual void NewDisplay(const std::string &where) override; @@ -380,8 +390,8 @@ void TCanvasPainter::NewDisplay(const std::string &where) TString exec; - if (!is_native && !ic_cef && !is_qt5 && (where!="browser")) { - if (where.find("$url")!=std::string::npos) { + if (!is_native && !ic_cef && !is_qt5 && (where != "browser")) { + if (where.find("$url") != std::string::npos) { exec = where.c_str(); exec.ReplaceAll("$url", addr); } else { @@ -397,13 +407,26 @@ void TCanvasPainter::NewDisplay(const std::string &where) gSystem->Exec(exec); } -void TCanvasPainter::CanvasUpdated(uint64_t ver, bool async) +void TCanvasPainter::CanvasUpdated(uint64_t ver, bool async, ROOT::Experimental::CanvasCallback_t callback) { + if (ver && fSnapshotDelivered && (ver <= fSnapshotDelivered)) { + // if given canvas version was already delivered to clients, can return immediately + if (callback) callback(true); + return; + } + fSnapshotVersion = ver; fSnapshot = CreateSnapshot(fCanvas); CheckDataToSend(); + if (callback) { + WebUpdate item; + item.fVersion = ver; + item.fCallback = callback; + fUpdatesLst.push_back(item); + } + if (!async) WaitWhenCanvasPainted(ver); } @@ -431,7 +454,8 @@ bool TCanvasPainter::WaitWhenCanvasPainted(uint64_t ver) return false; } -void TCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg, bool async, ROOT::Experimental::CanvasCallback_t callback) +void TCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg, bool async, + ROOT::Experimental::CanvasCallback_t callback) { if (!async && !fWaitingCmdId.empty()) { Error("DoWhenReady", "Fail to submit sync command when previous is still awaited - use async"); @@ -446,11 +470,13 @@ void TCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg cmd.fCallback = callback; fCmds.push_back(cmd); - if (!async) fWaitingCmdId = cmd.fId; + if (!async) + fWaitingCmdId = cmd.fId; CheckDataToSend(); - if (async) return; + if (async) + return; uint64_t cnt = 0; bool had_connection = false; @@ -467,7 +493,6 @@ void TCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg gSystem->ProcessEvents(); gSystem->Sleep((++cnt < 500) ? 1 : 100); // increase sleep interval when do very often } - } ///////////////////////////////////////////////////////////////////////////////////////////// @@ -476,12 +501,15 @@ void TCanvasPainter::DoWhenReady(const std::string &name, const std::string &arg void TCanvasPainter::PopFrontCommand(bool result) { - if (fCmds.size() == 0) return; + if (fCmds.size() == 0) + return; // simple condition, which will be checked in waiting loop - if (!fWaitingCmdId.empty() && (fWaitingCmdId == fCmds.front().fId)) fWaitingCmdId.clear(); + if (!fWaitingCmdId.empty() && (fWaitingCmdId == fCmds.front().fId)) + fWaitingCmdId.clear(); - if (fCmds.front().fCallback) fCmds.front().fCallback(result); + if (fCmds.front().fCallback) + fCmds.front().fCallback(result); fCmds.pop_front(); } @@ -619,7 +647,8 @@ Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) const char *sid = cdata + 6; const char *separ = strchr(sid, ':'); std::string id; - if (separ) id.append(sid, separ - sid); + if (separ) + id.append(sid, separ - sid); if (fCmds.size() == 0) { Error("ProcessWS", "Get REPLY without command"); } else if (!fCmds.front().fRunning) { @@ -627,7 +656,7 @@ Bool_t TCanvasPainter::ProcessWS(THttpCallArg *arg) } else if (fCmds.front().fId != id) { Error("ProcessWS", "Mismatch with front command and ID in REPLY"); } else { - bool res = FrontCommandReplied(separ+1); + bool res = FrontCommandReplied(separ + 1); PopFrontCommand(res); } conn->fReady = kTRUE; @@ -666,7 +695,7 @@ void TCanvasPainter::CheckDataToSend() TString buf; - if (conn.fDrawReady && (fCmds.size()>0) && !fCmds.front().fRunning) { + if (conn.fDrawReady && (fCmds.size() > 0) && !fCmds.front().fRunning) { WebCommand &cmd = fCmds.front(); cmd.fRunning = true; buf = "CMD:"; @@ -710,6 +739,16 @@ void TCanvasPainter::CheckDataToSend() if (fSnapshotDelivered != min_delivered) { fSnapshotDelivered = min_delivered; + + auto iter = fUpdatesLst.begin(); + while (iter != fUpdatesLst.end()) { + auto curr = iter; iter++; + if (curr->fVersion <= fSnapshotDelivered) { + curr->fCallback(true); + fUpdatesLst.erase(curr); + } + } + // one could call-back canvas methods here } } diff --git a/tutorials/v7/draw_v6.cxx b/tutorials/v7/draw_v6.cxx index 945560e35ffded48983720f689dec2876097ef96..bfd6ec45dbf2ce25fb98b64bbe328deed65e8920 100644 --- a/tutorials/v7/draw_v6.cxx +++ b/tutorials/v7/draw_v6.cxx @@ -40,7 +40,11 @@ void draw_v6() // canvas->Show("/usr/bin/opera"); // one could specify program name which should show canvas // canvas->Show("firefox"); // it could be firefox, opera, chromium; canvas can be shown several times - canvas->Update(); // synchronous, wait until painting is finished + // synchronous, wait until painting is finished + canvas->Update(false, [](bool res) { std::cout << "First Update done = " << (res ? "true" : "false") << std::endl; }); + + // call again, should return immediately + canvas->Update(false, [](bool res) { std::cout << "Second Update done = " << (res ? "true" : "false") << std::endl; }); // request to create PNG file in asynchronous mode and specify lambda function as callback // when request processed by the client, callback called with result value