Сергей Шамбир, Адаптация Promise/A+ для взаимодействия...
-
Upload
sergey-platonov -
Category
Software
-
view
1.717 -
download
3
Transcript of Сергей Шамбир, Адаптация Promise/A+ для взаимодействия...
![Page 1: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/1.jpg)
Адаптация Promise/A+ для взаимодействия
C++ и JavaScriptСергей Шамбир
Ведущий программистiSpring Solutions
![Page 2: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/2.jpg)
Дилемма метапрограммирования• Плюсы:• Вносит в язык новые возможности• Делает язык выразительнее
• Минусы:• Имеет намного больший (чем ООП) порог вхождения• Отнимает очень много (сколько угодно) времени
• Плохо подходит для решения повседневных задач на C++• Хорошо подходит, чтобы заложить фундамент новых проектов
![Page 3: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/3.jpg)
Суть нашей проблемы
CEF3 render processCEF3 browser process
V8 (Javascript)
Blink (HTML/CSS)
libcef.dll libcef.dll
JSON-подобные сообщения(protobuf)
Прикладной протокол (События, запуск фоновых задач, управление жизненным циклом UI)
???Прикладной движок на C++
Прикладной UIна HTML5/CSS3
+ Javascript
![Page 4: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/4.jpg)
Диспетчеризация сообщений
string message_name = request;if (message_name == kFileOpenMessageName) { dialog_state_->mode_ = FILE_DIALOG_OPEN; title = "My Open Dialog";} else if (message_name == kFileOpenMultipleMessageName) { dialog_state_->mode_ = FILE_DIALOG_OPEN_MULTIPLE; title = "My Open Multiple Dialog";} else if (message_name == kFileOpenFolderMessageName) { dialog_state_->mode_ = FILE_DIALOG_OPEN_FOLDER; title = "My Open Folder Dialog";} /* ... */
• Наивный подход из разряда «Попробуй Смержить»• Начиная с C++11 легко заменяется на map<string, function>
![Page 5: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/5.jpg)
class CClientPaymentApi{public:
// Запуск операций: возвращает обещание результатаfuture<bool> StartPayment(int itemId);future<bool> CompletePayment(int itemId);future<bool> CancelPayment(int itemId);
// Сигналы-слоты: соединение "один ко многим"
Connection DoOnConnectionFailed(const Slot<void()> &handler);};
Доменная модель API в приложении• С такой моделью мы предпочли бы работать вместо switch/case
![Page 6: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/6.jpg)
Запрос операции похож на вызов функции• Операции могут завершиться успешно, с ошибкой либо быть
отменены• Операция может выполниться немедленно или отложенно
Прикладной UIна HTML5/CSS3
+ Javascript
Прикладной движок на C++
OpenDocument("cbook.doc")
returns true
Прикладной движок на C++
Прикладной UIна HTML5/CSS3
+ Javascript
OpenDocument(42)
throws TypeError
Прикладной движок на C++
Прикладной UIна HTML5/CSS3
+ Javascript
OpenDocument("1GB.doc")
cancel that
![Page 7: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/7.jpg)
Сложности работы с потоками
• Блокировать UI-потоки нельзя – это заденет пользователя• Первый UI поток – browser-процессе (с прикладным C++-кодом)• Второй UI поток – в render-процессе (с прикладным Javascript-кодом)
UI-поток в browser процессе
execute task
handle event
handle event
handle event
post task
UI-поток в render процессе
IPC IPC
![Page 8: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/8.jpg)
Тонкости маршалинга вызовов• Можно ли проверить типы аргументов лучше, чем через assert?• Повторять проверки в прикладном коде нелепо• Информация о типах уже есть в сигнатуре функции-колбека
• Как сериализовать исключение?• Тип или код ошибки могут подсказать стратегию обработки исключения
![Page 9: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/9.jpg)
Мы решили писать шаблоны и велосипеды
template <class TValue>
class IPromise
class
CAbstr
actJav
ascrip
tBinde
r
: publ
ic IJa
vascri
ptBind
er
, publ
ic
std::e
nable_
shared
_from_
this<C
Abstra
ctJava
s
criptB
inder>
templa
te <cl
ass Re
turnTy
pe, cl
ass Cl
assTyp
e, boo
l
AddCon
st, cla
ss...
Args>
struct
WeakIn
vokerhttps://github.com/sergey-shambir/cpp-promise-demo/
![Page 10: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/10.jpg)
Преимущества Promise в Javascript• Есть проверенная в деле спецификация: promisesaplus.com• “An open standard for sound, interoperable JavaScript promises—by
implementers, for implementers.”
• Есть then/catch, т.е. можно повесить callback или продолжение• Callback вызывается с чистым стеком на определённом потоке
(т.е. как новый task)
PendingFulfilled
Rejected
Задача запущена Выполнено
![Page 11: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/11.jpg)
Подход «конвейер подзадач» с Promise
function loadGameMap() { let contentPromise = utils.loadUrlAsStringAsync("/res/level1.tmx"); let xmlPromise = contentPromise.then((content) => { return utils.parseXmlString(content) }); let mapPromise = xmlPromise.then((xmlDocument) => { return utils.buildGameMap(xmlDocument) }); return mapPromise;};
Запуск
FulfilledRejected
Чтение файла Разбор XML Построение карты уровня
![Page 12: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/12.jpg)
Подход «у меня есть план B» с Promise
function loadUserPhotos(userId) { let netPromise = netClient.loadPhotoCollectionAsync(userId); let photosPromise = netPromise.catch(() => { return localClient.loadCachedPhotoCollection(userId); }); return photosPromise;}
Запуск Запрос к сети Запрос к оффлайн-кешу
Fulfilled Rejected
![Page 13: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/13.jpg)
Подход «подождать любого» с Promise
function loadUserPhotos(userId) { let netPromise = netClient.loadPhotoCollection(userId); let localPromise = localClient.loadCachedPhotoCollection(userId); return Promise.race([netPromise, localPromise]);}
Запуск
Запрос к сети Запрос к оффлайн-кешу
Fulfilled Rejected
![Page 14: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/14.jpg)
Подход «подождать всех» с Promise
function loadUserPhotos(userId) { let netPromise = netClient.loadPhotoCollection(userId); let localPromise = localClient.loadCachedPhotoCollection(userId); return Promise.all([netPromise, localPromise]);}
Запуск
Запрос к сети Запрос к оффлайн-кешу
Fulfilled RejectedЖдём 2-х
![Page 15: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/15.jpg)
Недостатки Promise в Javascript• Легко нарушить контракт «Promise в конце операции переходит в
состояние Fulfilled или Rejected»• Достаточно потерять колбеки в конструкторе Promise
• Легко нарушить контракт «Promise при успешном завершении возвращает значимый результат»• Просто сделайте обработчик catch такой же, как в примерах: https://
goo.gl/dEvi8V
var p1 = new Promise(function (resolve, reject) { // .. давайте потеряем resolve/reject});
![Page 16: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/16.jpg)
Основной цикл и пул потоков• В STL до сих пор нет каркаса событийного цикла• Предполагаю, что комитет не пришёл к универсальной реализации
• В Boost.Asio и в каждой ОС есть свой основной цикл• В UI-библиотеках циклы свои и в них надо встраиваться• Цикл из Boost.Asio годен для серверов, а не для UI
UI-поток
execute task
handle event
handle event
handle event
post task
![Page 17: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/17.jpg)
Пул потоков на Boost.Asio в 35 строк
AsioThreadPool: https://goo.gl/NiYTUY
boost::asio::io_service
boost::asio::io_service::work
std::thread { io.run(); }
std::thread { io.run(); }
std::thread { io.run(); }
std::thread { io.run(); }
• Конструктор вызывает на каждом потоке io.run• Деструктор вызывает io.stop() и затем join потоков• Для добавления задачи вызываем io.post
![Page 18: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/18.jpg)
future в C++ и Promise в Javascript• В C++14 и C++17 future не расчитан на модель «исполнители и
задачи»• В std::future нет then• Если future получен от async, в деструкторе будет ожидание завершения
задачи
• В Concurrency TS future всё так же не расчитан на модель «исполнители и задачи»• К std::future добавляется then(callback), но нет стратегии вызова callback• Нельзя выполнить callback в предсказуемом потоке и окружении
“Why is there no std::future::then in C++17?” stackoverflow.com/questions/41310197
![Page 19: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/19.jpg)
Ответ на «Use the Boost, Luke!»• Boost предоставляет then, он он имеет подводные камни• Добиться работы «как в Javascript» можно, но сложно• Даже над Boost лучше написать упрощённую и ограниченную
обёртку-велосипед• И не забудьте взять с собой макросы:
#define BOOST_THREAD_PROVIDES_EXECUTORS#define BOOST_THREAD_VERSION 4#include <boost/thread.hpp>
![Page 20: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/20.jpg)
Чемпионат по отстрелу ног с Boost, раунд 1• На каком потоке по умолчанию будет вызван callback?• Ответ: на новом потоке, т.к. Launch Policy – launch::none
// .. создаём boost::promise и получаем от него futurecerr << "called then on " << this_thread::get_id() << endl;future.then([&](future<string> oldFuture) { cerr << "then callback on " << this_thread::get_id() << endl; dispatch.QuitMainLoop();});
![Page 21: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/21.jpg)
Чемпионат по отстрелу ног с Boost, раунд 2• Будет ли вызван callback?• Ответ: если задача ещё не завершилась, то не будет, т.к.
возвращённый от then объект future разрушается сразу после выполнения инструкции• Уточнение: если future получен от async, всё сложно.
// .. создаём boost::promise и получаем от него futurecerr << "called then on " << this_thread::get_id() << endl;future.then(launch::deferred, [&](future<string> oldFuture) { cerr << "then callback on " << this_thread::get_id() << endl; dispatch.QuitMainLoop();});
![Page 22: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/22.jpg)
Чемпионат по отстрелу ног с Boost, раунд 3• Будет ли вызван callback, если просто заменить Launch Policy?• Ответ: если у future не указан executor, будет assert• Assertion failed: this->future_->get_executor(), file c:\...\boost\
thread\future.hpp, line 4761
// .. создаём boost::promise и получаем от него futurecerr << "called then on " << this_thread::get_id() << endl;future.then(launch::executor, [&](future<string> oldFuture) { cerr << "then callback on " << this_thread::get_id() << endl; dispatch.QuitMainLoop();});
![Page 23: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/23.jpg)
Чемпионат по отстрелу ног с Boost, раунд 4• Будет ли вызван callback, если установить executor, который
постит задачу в UI thread, и then вызывается из UI thread?• Ответ: нет, wait() заблокирует обработку событий в UI thread
cerr << "called then on " << this_thread::get_id() << endl;auto f2 = future.then(launch::executor, [&](future<string> oldFuture) { cerr << "then callback on " << this_thread::get_id() << endl; dispatch.QuitMainLoop();});f2.wait();
![Page 24: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/24.jpg)
Безопасный callback, связанный с объектом
void WelcomeController::OnLogin(){ auto callback = std::bind(&WelcomeController::SaveLoginData, this, _1); m_api.Login(m_view.GetEmail(), m_view.GetPassword(), callback);}
• В момент вызова callback объект уже может быть уничтожен• Из документации Boost.Signals: вызов слота может происходить после
disconnect, если disconnect был сделан в другом потоке
• Решения есть• Weak this (аналог weak self в Objective-C)• Monitor (альтернатива weak this)
![Page 25: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/25.jpg)
Идиома “weak this”
BindWeakPtr: goo.gl/xlRM3E
• Нужно наследовать класс от enable_shared_from_this• Нельзя вызывать shared_from_this в конструкторе и деструкторе• Не работает с std::bind
std::weak_ptr<WelcomeController> weakThis = shared_from_this();m_api.Login(m_view.GetEmail(), m_view.GetPassword(), [weakThis](const auto &data) { if (auto strongThis = weakThis.lock()) { strongThis->SaveLoginData(data); }});
![Page 26: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/26.jpg)
BindWeakPtr – адаптер std::bind
BindWeakPtr: goo.gl/xlRM3E
• Функция BindWeakPtr перегружена для const и не-const методов• Внутри создаёт WeakInvoker и вызывает bind с его копией• Объект WeakInvoker хранит weak_ptr и реализует operator()
void WelcomeController::OnLogin(){
auto callback = BindWeakPtr(&WelcomeController::SaveLoginData, shared_from_this(), _1);
m_restClient->Login(m_view->GetEmail(), m_view->GetPassword(), callback);}
![Page 27: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/27.jpg)
Портируем JS Promise в C++• Добавили метод Cancel и состояние Cancelled• Для синхронизации использовали mutex• Возможно, есть способ сделать lock-free, но в наших условиях нет
ежесекундного создания тысяч объектов Promise
Pending
Fulfilled
Rejected
Задача запущена Выполнено
Cancelled
Отменено
![Page 28: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/28.jpg)
Портируем JS Promise в C++template <class ValueType>class IPromise{public: using ThenFunction = function<void(ValueType)>; using CatchFunction = function<void(exception_ptr const&)>; virtual ~IPromise() = default; virtual void Then(const ThenFunction &onFulfilled) = 0; virtual void Catch(const CatchFunction &onRejected) = 0; virtual void Cancel() = 0;};
![Page 29: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/29.jpg)
let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve(42); });});
promise.then((value) => alert("Succeed 1st: " + value));promise.then((value) => alert("Succeed 2nd: " + value));promise.then((value) => alert("Succeed 3rd: " + value));
• В Javascript один объект Promise позволяет вызвать then несколько раз• В C++ это невозможно для Movable-only значений• Мы решили поддерживать Movable-only, нарушив стандарт Promise/A+
Выбор: совместимость или movable-значения
![Page 30: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/30.jpg)
Состояние храним в variant• Promise содержит либо ошибку, либо исключение, либо ничего• Можно использовать variant для экономии памяти на хранение• Чтобы хранить состояние целиком, добавим два теговых типа CancelState
и PendingState
struct CanceledTag {};struct PendingState {};using StorageType = boost::variant< PendingState, CanceledTag, ValueType, std::exception_ptr>;
![Page 31: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/31.jpg)
Switch по типам для variantvoid Then(const ThenFunction &onFulfilled) override{ lock_guard lock(m_mutex); if (m_then) throw std::logic_error("Cannot call Then twice"); switch (m_storage.which()) { case detail::VariantIndex<StorageType, PendingState>: m_then = onFulfilled; break; case detail::VariantIndex<StorageType, ValueType>: m_then = onFulfilled; InvokeThen(); break; }}
![Page 32: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/32.jpg)
Получение which index для типа в variant
namespace detail{template <class VariantType, class VariantCase>using WhichIndex = typename boost::mpl::find< typename boost::mpl::copy< typename VariantType::types, boost::mpl::back_inserter<boost::mpl::vector<>> >::type, VariantCase >::type::pos;
template <class VariantType, class VariantCase>constexpr int VariantIndex = WhichIndex<VariantType, VariantCase>::value;}
![Page 33: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/33.jpg)
Постановка задачи
// args пришёл из Javascript и выглядит так:auto args = { Value(42.2), Value("add") }; ApplyVariantArguments([](const double &value, const string &operation) {
// выполняем действие над аргументами}, args);
•Есть рекурсивный вариантный тип, который по набору типов похож на JSON•Есть callable, имеющий точно указанную сигнатуру•Нужно применить аргументы к функции
![Page 34: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/34.jpg)
// Для функторов, имеющих operator()template <typename T>struct function_traits : public function_traits<decltype(&T::operator())> {};// Для указателей на функцииtemplate <typename ReturnType, typename... Args>struct function_traits<ReturnType(*)(Args...)> {
typedef std::function<ReturnType(Args...)> f_type;};// ... для методов (константных и неконстантных) ...// функция для вывода типа из параметраtemplate <typename Callable>typename function_traits<Callable>::f_type make_function(Callable callable) {
return (typename function_traits<Callable>::f_type)(callable);}
Шаг 1: function_traits•Задаёт синонимы типов параметров и результата•Принимает лямбды, указатели на свободные функции и методы•Не принимает ни std::bind, ни generic lambda•Могут быть ошибки компиляции с перегруженными функциями
![Page 35: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/35.jpg)
Шаг 2: формируем tuple и вызываем apply
// Remove `const&` and other dangerous qualifiers.template <typename ...Args>using arguments_tuple = std::tuple<typename std::decay_t<Args>...>;
template <typename R, typename ...Args>R ApplyCefArgumentsImpl(const std::function<R(Args...)> &function, const CefRefPtr<CefListValue> & args){
detail::CJavascriptArgumentsAdapter adapter(args);detail::arguments_tuple<Args...> typedArgs;
adapter.CheckArgumentsCount(std::tuple_size<decltype(typedArgs)>::value);detail::for_each_in_tuple(typedArgs, adapter);
return detail::apply_tuple<R>(typedArgs, function);
}
![Page 36: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/36.jpg)
Шаг 3: класс JavascriptArgumentsAdapter
template <class T>using CanConvertType = is_any_of<T, bool, double, std::string, std::wstring,
nlohmann::json, CefRefPtr<CefListValue>, CefRefPtr<CefDictionaryValue>, CefRefPtr<CefBinaryValue>>;
template<class T>void operator()(T & destination, size_t index)const{
static_assert(CanConvertType<std::decay_t<T>>{},"argument conversion is not implemented for type T,"" please use another type");
Convert(destination, index);}
void Convert(bool &destination, size_t index)const;void Convert(int &destination, size_t index)const;void Convert(double &destination, size_t index)const;void Convert(std::string &destination, size_t index)const;void Convert(std::wstring &destination, size_t index)const;void Convert(nlohmann::json &destination, size_t index)const;void Convert(CefRefPtr<CefListValue> &destination, size_t index)const;void Convert(CefRefPtr<CefDictionaryValue> &destination, size_t index)const;void Convert(CefRefPtr<CefBinaryValue> &destination, size_t index)const;
void CJavascriptArgumentsAdapter::Convert(std::string & destination, size_t index) const
{assert(int(index) <= int(INT_MAX));CheckArgument(index, VTYPE_STRING);destination = m_arguments->GetString(int(index)).ToString();
}
![Page 37: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/37.jpg)
Шаг 4: заворачиваем в std::function
using RemoteCallback = std::function<CefRefPtr<CefValue>(const CefRefPtr<CefListValue> &list)>;
template <typename R, typename ...Args>CefCallback BindCefCallImpl(const std::function<R(Args...)> &function){
return [function](const CefRefPtr<CefListValue> &list) {return ConvertReturnValue(ApplyCefArgumentsImpl(function,
list));};
}
template <typename Callable>CefCallback BindCefCall(Callable && callable){
return BindRemoteCallImpl(detail::make_function(callable));}
![Page 38: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/38.jpg)
Итог: интерфейс IJavascriptBinderclass IJavascriptBinder{public:
void BindOperation(const string &name, const NativeFn &fn) = 0;void BindAsyncOperation(const string &name, const PromiseFn &fn) =
0;
template<class ...TArgs>ICefValuePromisePtr CallJavascript(const string &fn, TArgs&&...
args){
const string code = detail::FormatJsArgs(forward<TArgs>(args)...);
return CallJsImpl(functionName, jsCode);}
protected:ICefValuePromisePtr CallJsImpl(const string &fn, const string
&code) = 0;};
![Page 39: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/39.jpg)
Итог: интерфейс на стороне Javascript
cef.CefClient = goog.defineClass(null, { call: function(name, ...args) { var promise = new CancelablePromise(); var requestId = this._connector.sendRequest( name, args, promise.resolveFunc(), promise.rejectFunc()); return promise; }, addHandler: function(name, handler, object) { this._handlers[name] = handler.bind(object); }, removeHandler: function(name) { delete this._handlers[name]; },}
![Page 40: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/40.jpg)
Самое время спросить о чём-нибудь...
![Page 41: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/41.jpg)
Тестирование с Boost.Test•Вдохновлялись Javascript-библиотекой sinon.js• Сделали proxy-объекты
•Тесты в отдельном потоке, чтобы не блокировать Event Loop• Поток тестов ждал значение через std::future
•Проверили передачу всех типов данных и исключений• Были проблемы с передачей Object и временем жизни• Нельзя передавать тип Function• Нельзя передать три значения типа double: NaN, +INF и -INF
![Page 42: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/42.jpg)
Тестирование с Boost.Test
Ожидание std::future
Поток, запустивший unit_test_main
execute task
handle event
handle event
handle event
UI-поток в browser process
Вызов Javascript через Proxy
execute task
Proxy получил значение
![Page 43: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/43.jpg)
Идиома “monitor”// Нюанс: не соблюдается rule of five,// что влечёт неверное копирование monitorstruct Student {
std::shared_ptr<void> monitor;std::string name;
Student() : monitor(this, ignore) {}decltype(auto) GetNamePrinter() {
std::weak_ptr<void> monitor = this->monitor;return [=]() {
if (!monitor.expired()) {// working with this
}};
}};
![Page 44: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/44.jpg)
Сторонние библиотеки для Promise/A+• tored/qml-promise – однопоточные Promise для C++/QML• rhashimoto/poolqueue – запускает Promise поверх пула потоков
или на базе таймера• grantila/q – крупная библиотека со своими 🚲 Promise, thread
pool, timers и т.п.• 0of/Promise2 – содержит заготовку Promise, интегрировать запуск
задач Promise в свой EventLoop/ThreadPool придётся самостоятельно
![Page 45: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/45.jpg)
“libdispatch” от AppleБиблиотека содержит примитивы для событийной многозадачности: очереди задач, исполнители, пул потоков и основной поток• Версия libdispatch от Apple: https://
github.com/apple/swift-corelibs-libdispatch• Версия с улучшением поддержки Linux (встраивание в event loop):
https://github.com/nickhutchinson/libdispatch• Версия с поддержкой Win32: https://github.com/DrPizza/libdispatch
![Page 46: Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript](https://reader030.fdocuments.net/reader030/viewer/2022013120/58b8806b1a28ab44078b5d0d/html5/thumbnails/46.jpg)
Вредные советы документацииИногда документация содержит вредные советы.• Примеры для JS Promise в сети содержат неправильную
обработку исключений (нет перевыброса): https://goo.gl/dEvi8V
Иногда документация неоднозначна (пример из STL от Microsoft):
void pop(){
// erase element at endc.pop_front();
}