Skip to content

Commit

Permalink
Merge pull request #251 from AntoinePrv/echo_update
Browse files Browse the repository at this point in the history
Update to protocol 2.1.0 (echo_update)
  • Loading branch information
SylvainCorlay authored Feb 7, 2023
2 parents 1cdc9f3 + cc06068 commit a49e38b
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 10 deletions.
36 changes: 30 additions & 6 deletions include/xwidgets/xcommon.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
#include <utility>
#include <vector>

#include <xeus/xcomm.hpp>
#include <xtl/xoptional.hpp>

#include "xbinary.hpp"
#include "xeus/xcomm.hpp"
#include "xwidgets_config.hpp"

namespace xw
Expand Down Expand Up @@ -81,10 +83,17 @@ namespace xw
template <class T>
void notify(const std::string& name, const T& value) const;
void send(nl::json&&, xeus::buffer_sequence&&) const;
void send_patch(nl::json&&, xeus::buffer_sequence&&) const;
void send_patch(nl::json&&, xeus::buffer_sequence&&, const char* method = "update") const;

private:

/**
* Indicate whether a global setting activates or deactivates ``echo_update`` messages.
*
* If the optional is empty, then no setting is set explicitly.
*/
static xtl::xoptional<bool> global_echo_update();

bool
same_patch(const std::string&, const nl::json&, const xeus::buffer_sequence&, const nl::json&, const xeus::buffer_sequence&)
const;
Expand Down Expand Up @@ -117,23 +126,38 @@ namespace xw
nl::json state;
xeus::buffer_sequence buffers;
xwidgets_serialize(value, state[name], buffers);
const char* method = "update";

// A current message is set, which implies that this change is triggered due to
// a change from the frontend.
if (m_hold != nullptr)
{
const auto& hold_state = m_hold->content()["data"]["state"];
const auto& hold_buffers = m_hold->buffers();

auto it = hold_state.find(name);
if (it != hold_state.end())
// The change that triggered this function is the same as the change coming from
// the frontend so we need not send back an update but only an "echo_update" since
// protocol 2.1.0
auto const it = hold_state.find(name);
if ((it != hold_state.end()) && same_patch(name, *it, hold_buffers, state[name], buffers))
{
if (same_patch(name, *it, hold_buffers, state[name], buffers))
// If the "echo_update" is explicitly deactivated we do not send the message
auto const echo_update = global_echo_update();
if (echo_update.has_value() && !echo_update.value())
{
return;
}
// If the "echo_update" is explicitly activated or unspecified, we continue and
// send message
method = "echo_update";
}
// On the contrary, the update could differ from the change in the frontend.
// For instance, if a validator adjusted the value of a property from the one
// recieved from the frontend.
// In this case, we need to send a regular "update" back to the frontend.
}

send_patch(std::move(state), std::move(buffers));
send_patch(std::move(state), std::move(buffers), method);
}
}

Expand Down
6 changes: 5 additions & 1 deletion include/xwidgets/xtransport.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,16 @@ namespace xw
const nl::json& state = data["state"];
const auto& buffers = message.buffers();
const nl::json& buffer_paths = data["buffer_paths"];
// Set the current message in ``xcommom::hold()``
// As a result ``x::commom`` will know that the change is coming from the frontend
this->hold() = std::addressof(message);
;
insert_buffer_paths(const_cast<nl::json&>(state), buffer_paths);
// This will potentially change the properties of the widget.
// As a result, ``xp::observed`` will call ``xcommon::notify`
/*D*/
this->derived_cast().apply_patch(state, buffers);
/*D*/
// Clear current message.
this->hold() = nullptr;
}
else if (method == "request_state")
Expand Down
2 changes: 1 addition & 1 deletion include/xwidgets/xwidgets_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

// Protocol version
#define XWIDGETS_PROTOCOL_VERSION_MAJOR 2
#define XWIDGETS_PROTOCOL_VERSION_MINOR 0
#define XWIDGETS_PROTOCOL_VERSION_MINOR 1
#define XWIDGETS_PROTOCOL_VERSION_PATCH 0

// Semver requirement for @jupyter-widgets/base
Expand Down
78 changes: 76 additions & 2 deletions src/xcommon.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
/***************************************************************************
* Copyright (c) 2022, QuantStack and XWidgets contributors *
* *
* Distributed under the terms of the BSD 3-Clause License. *
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/

#include "xwidgets/xcommon.hpp"

#include <algorithm>
#include <cstdlib>
#include <string>
#include <utility>
#include <vector>

#include <xtl/xoptional.hpp>

#include "xeus/xinterpreter.hpp"
#include "xtarget.hpp"

Expand Down Expand Up @@ -140,7 +151,7 @@ namespace xw
return m_buffer_paths;
}

void xcommon::send_patch(nl::json&& patch, xeus::buffer_sequence&& buffers) const
void xcommon::send_patch(nl::json&& patch, xeus::buffer_sequence&& buffers, const char* method) const
{
// extract buffer paths
auto paths = nl::json::array();
Expand All @@ -152,7 +163,7 @@ namespace xw

// data
nl::json data;
data["method"] = "update";
data["method"] = method;
data["state"] = std::move(patch);
data["buffer_paths"] = std::move(paths);

Expand Down Expand Up @@ -186,6 +197,69 @@ namespace xw
m_comm.close(nl::json::object(), nl::json::object(), xeus::buffer_sequence());
}

namespace
{
std::string tolower(std::string s)
{
auto safe_tolower = [](unsigned char c)
{
return std::tolower(c);
};
std::transform(s.begin(), s.end(), s.begin(), safe_tolower);
return s;
}

std::string ltrim(std::string s)
{
auto const safe_isnotspace = [](unsigned char ch)
{
return !std::isspace(ch);
};
s.erase(s.begin(), std::find_if(s.begin(), s.end(), safe_isnotspace));
return s;
}

std::string rtrim(std::string s)
{
auto const safe_isnotspace = [](unsigned char ch)
{
return !std::isspace(ch);
};
s.erase(std::find_if(s.rbegin(), s.rend(), safe_isnotspace).base(), s.end());
return s;
}

std::string trim(std::string s)
{
rtrim(s);
ltrim(s);
return s;
}

bool is_true_string(const char* str)
{
const std::string s = tolower(trim(str));
static const auto trues = {"true", "on", "yes", "1"};
return std::find(trues.begin(), trues.end(), s) < trues.end();
}

xtl::xoptional<bool> get_tristate_env(const char* name)
{
const char* const val = std::getenv(name);
if (val == nullptr)
{
return {};
}
return is_true_string(val);
}
}

xtl::xoptional<bool> xcommon::global_echo_update()
{
static const auto out = get_tristate_env("JUPYTER_WIDGETS_ECHO");
return out;
}

bool
xcommon::same_patch(const std::string& name, const nl::json& j1, const xeus::buffer_sequence&, const nl::json& j2, const xeus::buffer_sequence&)
const
Expand Down

0 comments on commit a49e38b

Please sign in to comment.