SlideShare une entreprise Scribd logo
1  sur  46
Адаптация Promise/A+ для
взаимодействия C++ и
JavaScript
Сергей Шамбир
Ведущий программист
iSpring Solutions
Дилемма метапрограммирования
• Плюсы:
• Вносит в язык новые возможности
• Делает язык выразительнее
• Минусы:
• Имеет намного больший (чем ООП) порог вхождения
• Отнимает очень много (сколько угодно) времени
• Плохо подходит для решения повседневных задач на C++
• Хорошо подходит, чтобы заложить фундамент новых проектов
Суть нашей проблемы
CEF3 render processCEF3 browser process
V8 (Javascript)
Blink (HTML/CSS)
libcef.dll libcef.dll
JSON-подобные сообщения
(protobuf)
Прикладной протокол
(События, запуск фоновых
задач, управление жизненным
циклом UI)
???Прикладной
движок на C++
Прикладной UI
на HTML5/CSS3
+ Javascript
Диспетчеризация сообщений
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>
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
Запрос операции похож на вызов функции
• Операции могут завершиться успешно, с ошибкой либо быть
отменены
• Операция может выполниться немедленно или отложенно
Прикладной 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
Сложности работы с потоками
• Блокировать UI-потоки нельзя – это заденет пользователя
• Первый UI поток – browser-процессе (с прикладным C++-кодом)
• Второй UI поток – в render-процессе (с прикладным Javascript-кодом)
UI-поток в browser процессе
execute
task
handle
event
handle
event
handle
event
post
task
UI-поток в render процессе
IPC IPC
Тонкости маршалинга вызовов
• Можно ли проверить типы аргументов лучше, чем через assert?
• Повторять проверки в прикладном коде нелепо
• Информация о типах уже есть в сигнатуре функции-колбека
• Как сериализовать исключение?
• Тип или код ошибки могут подсказать стратегию обработки исключения
Мы решили писать шаблоны и велосипеды
https://github.com/sergey-shambir/cpp-promise-demo/
Преимущества Promise в Javascript
• Есть проверенная в деле спецификация: promisesaplus.com
• “An open standard for sound, interoperable JavaScript promises—by
implementers, for implementers.”
• Есть then/catch, т.е. можно повесить callback или продолжение
• Callback вызывается с чистым стеком на определённом потоке
(т.е. как новый task)
Pending
Fulfilled
Rejected
Задача запущена Выполнено
Подход «конвейер подзадач» с 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
Построение
карты уровня
Подход «у меня есть план B» с Promise
function loadUserPhotos(userId) {
let netPromise = netClient.loadPhotoCollectionAsync(userId);
let photosPromise = netPromise.catch(() => {
return localClient.loadCachedPhotoCollection(userId);
});
return photosPromise;
}
Запуск Запрос к сети
Запрос к
оффлайн-кешу
Fulfilled Rejected
Подход «подождать любого» с Promise
function loadUserPhotos(userId) {
let netPromise = netClient.loadPhotoCollection(userId);
let localPromise = localClient.loadCachedPhotoCollection(userId);
return Promise.race([netPromise, localPromise]);
}
Запуск
Запрос к сети
Запрос к
оффлайн-кешу
Fulfilled Rejected
Подход «подождать всех» с Promise
function loadUserPhotos(userId) {
let netPromise = netClient.loadPhotoCollection(userId);
let localPromise = localClient.loadCachedPhotoCollection(userId);
return Promise.all([netPromise, localPromise]);
}
Запуск
Запрос к сети
Запрос к
оффлайн-кешу
Fulfilled RejectedЖдём 2-х
Недостатки Promise в Javascript
• Легко нарушить контракт «Promise в конце операции переходит в
состояние Fulfilled или Rejected»
• Достаточно потерять колбеки в конструкторе Promise
• Легко нарушить контракт «Promise при успешном завершении
возвращает значимый результат»
• Просто сделайте обработчик catch такой же, как в примерах:
https://goo.gl/dEvi8V
var p1 = new Promise(function (resolve, reject) {
// .. давайте потеряем resolve/reject
});
Основной цикл и пул потоков
• В STL до сих пор нет каркаса событийного цикла
• Предполагаю, что комитет не пришёл к универсальной реализации
• В Boost.Asio и в каждой ОС есть свой основной цикл
• В UI-библиотеках циклы свои и в них надо встраиваться
• Цикл из Boost.Asio годен для серверов, а не для UI
UI-поток
execute
task
handle
event
handle
event
handle
event
post
task
Пул потоков на 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
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
Ответ на «Use the Boost, Luke!»
• Boost предоставляет then, он он имеет подводные камни
• Добиться работы «как в Javascript» можно, но сложно
• Даже над Boost лучше написать упрощённую и ограниченную
обёртку-велосипед
• И не забудьте взять с собой макросы:
#define BOOST_THREAD_PROVIDES_EXECUTORS
#define BOOST_THREAD_VERSION 4
#include <boost/thread.hpp>
Чемпионат по отстрелу ног с Boost, раунд 1
• На каком потоке по умолчанию будет вызван callback?
• Ответ: на новом потоке, т.к. Launch Policy – launch::none
// .. создаём boost::promise и получаем от него future
cerr << "called then on " << this_thread::get_id() << endl;
future.then([&](future<string> oldFuture) {
cerr << "then callback on " << this_thread::get_id() << endl;
dispatch.QuitMainLoop();
});
Чемпионат по отстрелу ног с Boost, раунд 2
• Будет ли вызван callback?
• Ответ: если задача ещё не завершилась, то не будет, т.к.
возвращённый от then объект future разрушается сразу после
выполнения инструкции
• Уточнение: если future получен от async, всё сложно.
// .. создаём boost::promise и получаем от него future
cerr << "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();
});
Чемпионат по отстрелу ног с Boost, раунд 3
• Будет ли вызван callback, если просто заменить Launch Policy?
• Ответ: если у future не указан executor, будет assert
• Assertion failed: this->future_->get_executor(), file
c:...boostthreadfuture.hpp, line 4761
// .. создаём boost::promise и получаем от него future
cerr << "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();
});
Чемпионат по отстрелу ног с 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();
Безопасный 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)
Идиома “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);
}
});
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);
}
Портируем JS Promise в C++
• Добавили метод Cancel и состояние Cancelled
• Для синхронизации использовали mutex
• Возможно, есть способ сделать lock-free, но в наших условиях нет
ежесекундного создания тысяч объектов Promise
Pending
Fulfilled
Rejected
Задача запущена Выполнено
Cancelled
Отменено
Портируем 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;
};
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-значения
Состояние храним в variant
• Promise содержит либо ошибку, либо исключение, либо ничего
• Можно использовать variant для экономии памяти на хранение
• Чтобы хранить состояние целиком, добавим два теговых типа CancelState
и PendingState
struct CanceledTag {};
struct PendingState {};
using StorageType = boost::variant<
PendingState,
CanceledTag,
ValueType,
std::exception_ptr
>;
Switch по типам для variant
void 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;
}
}
Получение 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;
}
Постановка задачи
// args пришёл из Javascript и выглядит так:
auto args = { Value(42.2), Value("add") };
ApplyVariantArguments([](const double &value, const string &operation) {
// выполняем действие над аргументами
}, args);
•Есть рекурсивный вариантный тип, который по набору типов
похож на JSON
•Есть callable, имеющий точно указанную сигнатуру
•Нужно применить аргументы к функции
// Для функторов, имеющих 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
•Могут быть ошибки компиляции с перегруженными функциями
Шаг 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);
}
Шаг 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();
}
Шаг 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));
}
Итог: интерфейс IJavascriptBinder
class 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;
};
Итог: интерфейс на стороне 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];
},
}
Самое время спросить о чём-нибудь...
Тестирование с Boost.Test
•Вдохновлялись Javascript-библиотекой sinon.js
• Сделали proxy-объекты
•Тесты в отдельном потоке, чтобы не блокировать Event Loop
• Поток тестов ждал значение через std::future
•Проверили передачу всех типов данных и исключений
• Были проблемы с передачей Object и временем жизни
• Нельзя передавать тип Function
• Нельзя передать три значения типа double: NaN, +INF и -INF
Тестирование с Boost.Test
Ожидание std::future
Поток, запустивший unit_test_main
execute
task
handle
event
handle
event
handle
event
UI-поток в browser process
Вызов Javascript через Proxy
execute
task
Proxy получил значение
Идиома “monitor”
// Нюанс: не соблюдается rule of five,
// что влечёт неверное копирование monitor
struct 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
}
};
}
};
Сторонние библиотеки для Promise/A+
• tored/qml-promise – однопоточные Promise для C++/QML
• rhashimoto/poolqueue – запускает Promise поверх пула потоков
или на базе таймера
• grantila/q – крупная библиотека со своими 🚲 Promise, thread pool,
timers и т.п.
• 0of/Promise2 – содержит заготовку Promise, интегрировать запуск
задач Promise в свой EventLoop/ThreadPool придётся
самостоятельно
“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
Вредные советы документации
Иногда документация содержит вредные советы.
• Примеры для JS Promise в сети содержат неправильную
обработку исключений (нет перевыброса): https://goo.gl/dEvi8V
Иногда документация неоднозначна (пример из STL от Microsoft):
void pop()
{
// erase element at end
c.pop_front();
}

Contenu connexe

Tendances

Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
victor-yastrebov
 
Основы и применение статического анализа кода при разработке лекция 1
Основы и применение статического анализа кода при разработке лекция 1Основы и применение статического анализа кода при разработке лекция 1
Основы и применение статического анализа кода при разработке лекция 1
m2rus
 

Tendances (20)

Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMДмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
 
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
 
Современный статический анализ кода: что умеет он, чего не умели линтеры
Современный статический анализ кода: что умеет он, чего не умели линтерыСовременный статический анализ кода: что умеет он, чего не умели линтеры
Современный статический анализ кода: что умеет он, чего не умели линтеры
 
Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++Борис Сазонов, RAII потоки и CancellationToken в C++
Борис Сазонов, RAII потоки и CancellationToken в C++
 
Андрей Карпов, Приватные байки от разработчиков анализатора кода
Андрей Карпов, Приватные байки от разработчиков анализатора кодаАндрей Карпов, Приватные байки от разработчиков анализатора кода
Андрей Карпов, Приватные байки от разработчиков анализатора кода
 
SWIG — cоздание мультиязыковых интерфейсов для C/C++ библиотек
SWIG — cоздание мультиязыковых интерфейсов для C/C++ библиотекSWIG — cоздание мультиязыковых интерфейсов для C/C++ библиотек
SWIG — cоздание мультиязыковых интерфейсов для C/C++ библиотек
 
Цена ошибки
Цена ошибкиЦена ошибки
Цена ошибки
 
Асинхронность и сопрограммы
Асинхронность и сопрограммыАсинхронность и сопрограммы
Асинхронность и сопрограммы
 
Антон Полухин, Немного о Boost
Антон Полухин, Немного о BoostАнтон Полухин, Немного о Boost
Антон Полухин, Немного о Boost
 
Использование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработкиИспользование юнит-тестов для повышения качества разработки
Использование юнит-тестов для повышения качества разработки
 
Павел Беликов, Опыт мигрирования крупного проекта с Windows-only на Linux
Павел Беликов, Опыт мигрирования крупного проекта с Windows-only на LinuxПавел Беликов, Опыт мигрирования крупного проекта с Windows-only на Linux
Павел Беликов, Опыт мигрирования крупного проекта с Windows-only на Linux
 
C++ refelection and cats
C++ refelection and catsC++ refelection and cats
C++ refelection and cats
 
Шишки, набитые за 15 лет использования акторов в C++
Шишки, набитые за 15 лет использования акторов в C++Шишки, набитые за 15 лет использования акторов в C++
Шишки, набитые за 15 лет использования акторов в C++
 
Parallel STL
Parallel STLParallel STL
Parallel STL
 
Никита Глушков, К вопросу о реализации кроссплатформенных фреймворков
Никита Глушков, К вопросу о реализации кроссплатформенных фреймворковНикита Глушков, К вопросу о реализации кроссплатформенных фреймворков
Никита Глушков, К вопросу о реализации кроссплатформенных фреймворков
 
Python и Cython
Python и CythonPython и Cython
Python и Cython
 
Принципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-StudioПринципы работы статического анализатора кода PVS-Studio
Принципы работы статического анализатора кода PVS-Studio
 
Основы и применение статического анализа кода при разработке лекция 1
Основы и применение статического анализа кода при разработке лекция 1Основы и применение статического анализа кода при разработке лекция 1
Основы и применение статического анализа кода при разработке лекция 1
 
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программированияПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
ПВТ - весна 2015 - Лекция 4. Шаблоны многопоточного программирования
 
Intel IPP Samples for Windows - работа над ошибками
Intel IPP Samples for Windows - работа над ошибкамиIntel IPP Samples for Windows - работа над ошибками
Intel IPP Samples for Windows - работа над ошибками
 

Similaire à Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript

Система обработки бизнес-логики server-side приложения на Groovy
Система обработки бизнес-логики server-side приложения на GroovyСистема обработки бизнес-логики server-side приложения на Groovy
Система обработки бизнес-логики server-side приложения на Groovy
Regn
 
PHP 5.4: Что нового?
PHP 5.4: Что нового?PHP 5.4: Что нового?
PHP 5.4: Что нового?
phpdevby
 

Similaire à Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript (20)

2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
2014-10-04 02 Владислав Безверхий. Mocha - покрой frontend по полной
 
Семь тысяч Rps, один go
Семь тысяч Rps, один goСемь тысяч Rps, один go
Семь тысяч Rps, один go
 
Node.js введение в технологию, КПИ #ITmeetingKPI
Node.js введение в технологию, КПИ  #ITmeetingKPINode.js введение в технологию, КПИ  #ITmeetingKPI
Node.js введение в технологию, КПИ #ITmeetingKPI
 
C++ STL & Qt. Занятие 11.
C++ STL & Qt. Занятие 11.C++ STL & Qt. Занятие 11.
C++ STL & Qt. Занятие 11.
 
Zero Downtime PHP Deployment with Envoyer And Forge
Zero Downtime PHP Deployment with Envoyer And ForgeZero Downtime PHP Deployment with Envoyer And Forge
Zero Downtime PHP Deployment with Envoyer And Forge
 
Система обработки бизнес-логики server-side приложения на Groovy
Система обработки бизнес-логики server-side приложения на GroovyСистема обработки бизнес-логики server-side приложения на Groovy
Система обработки бизнес-логики server-side приложения на Groovy
 
JavaDay'14
JavaDay'14JavaDay'14
JavaDay'14
 
Сергей Константинов — Что интересного готовит нам W3C
Сергей Константинов — Что интересного готовит нам W3CСергей Константинов — Что интересного готовит нам W3C
Сергей Константинов — Что интересного готовит нам W3C
 
Михаил Рахманов — Promises, или почему обещания надо выполнять
Михаил Рахманов — Promises, или почему обещания надо выполнятьМихаил Рахманов — Promises, или почему обещания надо выполнять
Михаил Рахманов — Promises, или почему обещания надо выполнять
 
RDSDataSource: Promises
RDSDataSource: PromisesRDSDataSource: Promises
RDSDataSource: Promises
 
Григорий Демченко — Асинхронное программирование и сопрограммы
Григорий Демченко — Асинхронное программирование и сопрограммыГригорий Демченко — Асинхронное программирование и сопрограммы
Григорий Демченко — Асинхронное программирование и сопрограммы
 
Java 9: what is there beyond modularization
Java 9: what is there beyond modularizationJava 9: what is there beyond modularization
Java 9: what is there beyond modularization
 
Тестируй это / Виктор Русакович (GP Solutions)
Тестируй это / Виктор Русакович (GP Solutions)Тестируй это / Виктор Русакович (GP Solutions)
Тестируй это / Виктор Русакович (GP Solutions)
 
FrontTalks: Алексей Андросов (Яндекс), «Ошибки, которые мы любим»
FrontTalks: Алексей Андросов (Яндекс), «Ошибки, которые мы любим»FrontTalks: Алексей Андросов (Яндекс), «Ошибки, которые мы любим»
FrontTalks: Алексей Андросов (Яндекс), «Ошибки, которые мы любим»
 
Суперсилы Chrome DevTools — Роман Сальников, 2ГИС
Суперсилы Chrome DevTools — Роман Сальников, 2ГИССуперсилы Chrome DevTools — Роман Сальников, 2ГИС
Суперсилы Chrome DevTools — Роман Сальников, 2ГИС
 
Иван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программированиеИван Пузыревский — Введение в асинхронное программирование
Иван Пузыревский — Введение в асинхронное программирование
 
Jbreak 2016: Твой личный Spring Boot Starter
Jbreak 2016: Твой личный Spring Boot StarterJbreak 2016: Твой личный Spring Boot Starter
Jbreak 2016: Твой личный Spring Boot Starter
 
PHP 5.4: Что нового?
PHP 5.4: Что нового?PHP 5.4: Что нового?
PHP 5.4: Что нового?
 
Jiramania презентации @augspb
Jiramania презентации   @augspbJiramania презентации   @augspb
Jiramania презентации @augspb
 
Страх и ненависть в Event Bus
Страх и ненависть в Event BusСтрах и ненависть в Event Bus
Страх и ненависть в Event Bus
 

Plus de Sergey Platonov

Plus de Sergey Platonov (20)

Григорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптерГригорий Демченко, Универсальный адаптер
Григорий Демченко, Универсальный адаптер
 
Антон Бикинеев, Reflection in C++Next
Антон Бикинеев,  Reflection in C++NextАнтон Бикинеев,  Reflection in C++Next
Антон Бикинеев, Reflection in C++Next
 
Evgeniy Muralev, Mark Vince, Working with the compiler, not against it
Evgeniy Muralev, Mark Vince, Working with the compiler, not against itEvgeniy Muralev, Mark Vince, Working with the compiler, not against it
Evgeniy Muralev, Mark Vince, Working with the compiler, not against it
 
Василий Сорокин, Простой REST сервер на Qt с рефлексией
Василий Сорокин, Простой REST сервер на Qt с рефлексиейВасилий Сорокин, Простой REST сервер на Qt с рефлексией
Василий Сорокин, Простой REST сервер на Qt с рефлексией
 
Лев Казаркин, Удивительные приключения регистров SSE или в поисках одного бага
Лев Казаркин, Удивительные приключения регистров SSE или в поисках одного багаЛев Казаркин, Удивительные приключения регистров SSE или в поисках одного бага
Лев Казаркин, Удивительные приключения регистров SSE или в поисках одного бага
 
Антон Бикинеев, Writing good std::future&lt; C++ >
Антон Бикинеев, Writing good std::future&lt; C++ >Антон Бикинеев, Writing good std::future&lt; C++ >
Антон Бикинеев, Writing good std::future&lt; C++ >
 
Павел Филонов, Разделяй и управляй вместе с Conan.io
Павел Филонов, Разделяй и управляй вместе с Conan.ioПавел Филонов, Разделяй и управляй вместе с Conan.io
Павел Филонов, Разделяй и управляй вместе с Conan.io
 
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизацияГригорий Демченко, Асинхронность и неблокирующая синхронизация
Григорий Демченко, Асинхронность и неблокирующая синхронизация
 
Антон Полухин. C++17
Антон Полухин. C++17Антон Полухин. C++17
Антон Полухин. C++17
 
Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++Павел Беликов, Как избежать ошибок, используя современный C++
Павел Беликов, Как избежать ошибок, используя современный C++
 
Денис Кандров, Пушкова Евгения, QSpec: тестирование графических приложений на Qt
Денис Кандров, Пушкова Евгения, QSpec: тестирование графических приложений на QtДенис Кандров, Пушкова Евгения, QSpec: тестирование графических приложений на Qt
Денис Кандров, Пушкова Евгения, QSpec: тестирование графических приложений на Qt
 
Алексей Кутумов, Coroutines everywhere
Алексей Кутумов, Coroutines everywhereАлексей Кутумов, Coroutines everywhere
Алексей Кутумов, Coroutines everywhere
 
Дмитрий Нестерук, Паттерны проектирования в XXI веке
Дмитрий Нестерук, Паттерны проектирования в XXI векеДмитрий Нестерук, Паттерны проектирования в XXI веке
Дмитрий Нестерук, Паттерны проектирования в XXI веке
 
Dori Exterman, Considerations for choosing the parallel computing strategy th...
Dori Exterman, Considerations for choosing the parallel computing strategy th...Dori Exterman, Considerations for choosing the parallel computing strategy th...
Dori Exterman, Considerations for choosing the parallel computing strategy th...
 
Александр Гранин, Функциональная 'Жизнь': параллельные клеточные автоматы и к...
Александр Гранин, Функциональная 'Жизнь': параллельные клеточные автоматы и к...Александр Гранин, Функциональная 'Жизнь': параллельные клеточные автоматы и к...
Александр Гранин, Функциональная 'Жизнь': параллельные клеточные автоматы и к...
 
Антон Нонко, Классические строки в C++
Антон Нонко, Классические строки в C++Антон Нонко, Классические строки в C++
Антон Нонко, Классические строки в C++
 
Михаил Матросов, Повседневный С++: boost и STL
Михаил Матросов, Повседневный С++: boost и STLМихаил Матросов, Повседневный С++: boost и STL
Михаил Матросов, Повседневный С++: boost и STL
 
Алексей Кутумов, Вектор с нуля
Алексей Кутумов, Вектор с нуляАлексей Кутумов, Вектор с нуля
Алексей Кутумов, Вектор с нуля
 
Kirk Shoop, Reactive programming in C++
Kirk Shoop, Reactive programming in C++Kirk Shoop, Reactive programming in C++
Kirk Shoop, Reactive programming in C++
 
Дмитрий Демчук. Кроссплатформенный краш-репорт
Дмитрий Демчук. Кроссплатформенный краш-репортДмитрий Демчук. Кроссплатформенный краш-репорт
Дмитрий Демчук. Кроссплатформенный краш-репорт
 

Сергей Шамбир, Адаптация Promise/A+ для взаимодействия между C++ и Javascript

  • 1. Адаптация Promise/A+ для взаимодействия C++ и JavaScript Сергей Шамбир Ведущий программист iSpring Solutions
  • 2. Дилемма метапрограммирования • Плюсы: • Вносит в язык новые возможности • Делает язык выразительнее • Минусы: • Имеет намного больший (чем ООП) порог вхождения • Отнимает очень много (сколько угодно) времени • Плохо подходит для решения повседневных задач на C++ • Хорошо подходит, чтобы заложить фундамент новых проектов
  • 3. Суть нашей проблемы CEF3 render processCEF3 browser process V8 (Javascript) Blink (HTML/CSS) libcef.dll libcef.dll JSON-подобные сообщения (protobuf) Прикладной протокол (События, запуск фоновых задач, управление жизненным циклом UI) ???Прикладной движок на C++ Прикладной UI на HTML5/CSS3 + Javascript
  • 4. Диспетчеризация сообщений 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>
  • 5. 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
  • 6. Запрос операции похож на вызов функции • Операции могут завершиться успешно, с ошибкой либо быть отменены • Операция может выполниться немедленно или отложенно Прикладной 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
  • 7. Сложности работы с потоками • Блокировать UI-потоки нельзя – это заденет пользователя • Первый UI поток – browser-процессе (с прикладным C++-кодом) • Второй UI поток – в render-процессе (с прикладным Javascript-кодом) UI-поток в browser процессе execute task handle event handle event handle event post task UI-поток в render процессе IPC IPC
  • 8. Тонкости маршалинга вызовов • Можно ли проверить типы аргументов лучше, чем через assert? • Повторять проверки в прикладном коде нелепо • Информация о типах уже есть в сигнатуре функции-колбека • Как сериализовать исключение? • Тип или код ошибки могут подсказать стратегию обработки исключения
  • 9. Мы решили писать шаблоны и велосипеды https://github.com/sergey-shambir/cpp-promise-demo/
  • 10. Преимущества Promise в Javascript • Есть проверенная в деле спецификация: promisesaplus.com • “An open standard for sound, interoperable JavaScript promises—by implementers, for implementers.” • Есть then/catch, т.е. можно повесить callback или продолжение • Callback вызывается с чистым стеком на определённом потоке (т.е. как новый task) Pending Fulfilled Rejected Задача запущена Выполнено
  • 11. Подход «конвейер подзадач» с 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 Построение карты уровня
  • 12. Подход «у меня есть план B» с Promise function loadUserPhotos(userId) { let netPromise = netClient.loadPhotoCollectionAsync(userId); let photosPromise = netPromise.catch(() => { return localClient.loadCachedPhotoCollection(userId); }); return photosPromise; } Запуск Запрос к сети Запрос к оффлайн-кешу Fulfilled Rejected
  • 13. Подход «подождать любого» с Promise function loadUserPhotos(userId) { let netPromise = netClient.loadPhotoCollection(userId); let localPromise = localClient.loadCachedPhotoCollection(userId); return Promise.race([netPromise, localPromise]); } Запуск Запрос к сети Запрос к оффлайн-кешу Fulfilled Rejected
  • 14. Подход «подождать всех» с Promise function loadUserPhotos(userId) { let netPromise = netClient.loadPhotoCollection(userId); let localPromise = localClient.loadCachedPhotoCollection(userId); return Promise.all([netPromise, localPromise]); } Запуск Запрос к сети Запрос к оффлайн-кешу Fulfilled RejectedЖдём 2-х
  • 15. Недостатки Promise в Javascript • Легко нарушить контракт «Promise в конце операции переходит в состояние Fulfilled или Rejected» • Достаточно потерять колбеки в конструкторе Promise • Легко нарушить контракт «Promise при успешном завершении возвращает значимый результат» • Просто сделайте обработчик catch такой же, как в примерах: https://goo.gl/dEvi8V var p1 = new Promise(function (resolve, reject) { // .. давайте потеряем resolve/reject });
  • 16. Основной цикл и пул потоков • В STL до сих пор нет каркаса событийного цикла • Предполагаю, что комитет не пришёл к универсальной реализации • В Boost.Asio и в каждой ОС есть свой основной цикл • В UI-библиотеках циклы свои и в них надо встраиваться • Цикл из Boost.Asio годен для серверов, а не для UI UI-поток execute task handle event handle event handle event post task
  • 17. Пул потоков на 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
  • 18. 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
  • 19. Ответ на «Use the Boost, Luke!» • Boost предоставляет then, он он имеет подводные камни • Добиться работы «как в Javascript» можно, но сложно • Даже над Boost лучше написать упрощённую и ограниченную обёртку-велосипед • И не забудьте взять с собой макросы: #define BOOST_THREAD_PROVIDES_EXECUTORS #define BOOST_THREAD_VERSION 4 #include <boost/thread.hpp>
  • 20. Чемпионат по отстрелу ног с Boost, раунд 1 • На каком потоке по умолчанию будет вызван callback? • Ответ: на новом потоке, т.к. Launch Policy – launch::none // .. создаём boost::promise и получаем от него future cerr << "called then on " << this_thread::get_id() << endl; future.then([&](future<string> oldFuture) { cerr << "then callback on " << this_thread::get_id() << endl; dispatch.QuitMainLoop(); });
  • 21. Чемпионат по отстрелу ног с Boost, раунд 2 • Будет ли вызван callback? • Ответ: если задача ещё не завершилась, то не будет, т.к. возвращённый от then объект future разрушается сразу после выполнения инструкции • Уточнение: если future получен от async, всё сложно. // .. создаём boost::promise и получаем от него future cerr << "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(); });
  • 22. Чемпионат по отстрелу ног с Boost, раунд 3 • Будет ли вызван callback, если просто заменить Launch Policy? • Ответ: если у future не указан executor, будет assert • Assertion failed: this->future_->get_executor(), file c:...boostthreadfuture.hpp, line 4761 // .. создаём boost::promise и получаем от него future cerr << "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(); });
  • 23. Чемпионат по отстрелу ног с 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();
  • 24. Безопасный 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)
  • 25. Идиома “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); } });
  • 26. 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); }
  • 27. Портируем JS Promise в C++ • Добавили метод Cancel и состояние Cancelled • Для синхронизации использовали mutex • Возможно, есть способ сделать lock-free, но в наших условиях нет ежесекундного создания тысяч объектов Promise Pending Fulfilled Rejected Задача запущена Выполнено Cancelled Отменено
  • 28. Портируем 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; };
  • 29. 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-значения
  • 30. Состояние храним в variant • Promise содержит либо ошибку, либо исключение, либо ничего • Можно использовать variant для экономии памяти на хранение • Чтобы хранить состояние целиком, добавим два теговых типа CancelState и PendingState struct CanceledTag {}; struct PendingState {}; using StorageType = boost::variant< PendingState, CanceledTag, ValueType, std::exception_ptr >;
  • 31. Switch по типам для variant void 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; } }
  • 32. Получение 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; }
  • 33. Постановка задачи // args пришёл из Javascript и выглядит так: auto args = { Value(42.2), Value("add") }; ApplyVariantArguments([](const double &value, const string &operation) { // выполняем действие над аргументами }, args); •Есть рекурсивный вариантный тип, который по набору типов похож на JSON •Есть callable, имеющий точно указанную сигнатуру •Нужно применить аргументы к функции
  • 34. // Для функторов, имеющих 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 •Могут быть ошибки компиляции с перегруженными функциями
  • 35. Шаг 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); }
  • 36. Шаг 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(); }
  • 37. Шаг 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)); }
  • 38. Итог: интерфейс IJavascriptBinder class 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; };
  • 39. Итог: интерфейс на стороне 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]; }, }
  • 40. Самое время спросить о чём-нибудь...
  • 41. Тестирование с Boost.Test •Вдохновлялись Javascript-библиотекой sinon.js • Сделали proxy-объекты •Тесты в отдельном потоке, чтобы не блокировать Event Loop • Поток тестов ждал значение через std::future •Проверили передачу всех типов данных и исключений • Были проблемы с передачей Object и временем жизни • Нельзя передавать тип Function • Нельзя передать три значения типа double: NaN, +INF и -INF
  • 42. Тестирование с Boost.Test Ожидание std::future Поток, запустивший unit_test_main execute task handle event handle event handle event UI-поток в browser process Вызов Javascript через Proxy execute task Proxy получил значение
  • 43. Идиома “monitor” // Нюанс: не соблюдается rule of five, // что влечёт неверное копирование monitor struct 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 } }; } };
  • 44. Сторонние библиотеки для Promise/A+ • tored/qml-promise – однопоточные Promise для C++/QML • rhashimoto/poolqueue – запускает Promise поверх пула потоков или на базе таймера • grantila/q – крупная библиотека со своими 🚲 Promise, thread pool, timers и т.п. • 0of/Promise2 – содержит заготовку Promise, интегрировать запуск задач Promise в свой EventLoop/ThreadPool придётся самостоятельно
  • 45. “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
  • 46. Вредные советы документации Иногда документация содержит вредные советы. • Примеры для JS Promise в сети содержат неправильную обработку исключений (нет перевыброса): https://goo.gl/dEvi8V Иногда документация неоднозначна (пример из STL от Microsoft): void pop() { // erase element at end c.pop_front(); }

Notes de l'éditeur

  1. О себе - я в iSpring в команде Desktop приложений для тренеров персонала в больших компаниях (ПО для быстрой разработки курсов) - так получилось, что попутно стал наставником для новичков и помогаю преподавать в ВУЗе - недавно команда стала использовать CEF3 для парочки проектов
  2. Началась разработка серии гибридных приложений на CEF3 UI на JavaScript+HTML5 Вычислительное ядро на C++ Они находятся в разных процессах Требуется интенсивное взаимодействие между С++ и JS: Реакция на события Запуск фоновых задач Управление состоянием UI CEF3 предполагает лишь асинхронную модель взаимодействия Требуется маршалинг аргументов и результата вызова В С++ типы и количество аргументов надо строго проверять Также надо передавать исключения Исключения могут нести код или тип ошибки для выбора стратегии обработки CEF3 позволяет отправить сообщение в любую сторону, содержащее JSON-подобные данные
  3. Если нужно извлекать аргументы вызова, код становится ещё запутаннее
  4. Операции могут завершиться успешно, с ошибкой либо быть отменены Операция может выполниться немедленно или отложенно В С++ принято распределять задачи на фоновые потоки и на основной поток В Javascript принято использовать один поток Есть Web Workers, но в приложении из заменяет наш C++ бекенд Нужна простая, понятная модель с приемлемым порогом вхождения При обычном вызове действует контракт: обычная функция может завершиться двумя путями: return и throw можно его нарушить: бесконечный цикл longjmp или иное переключение контекста без возврата останов процесса/потока все способы нарушения контракта ненормальные
  5. В render-процессе браузера есть поток, в котором исполняется Javascript код и происходит обработка DOM
  6. Ниже показано, почему наши проблемы не решаются стандартными средствами Либо решаются, но так, что решение позволит легко «отстрелить себе ногу»
  7. Наиболее важное отличие Promise в Javascript от C++: вызов callback происходит как таск в предсказуемом и безопасном для исключений окружении
  8. Приятная особенность: методы parseXmlString и loadTiledMap могут вернуть и Promise, и значение, и в любом случае произойдёт корректный переход в Fulfilled. Если возвращать Promise, конвейер будет асинхронным, нагрузка может обрабатываться во вторичном потоке.
  9. Приятная особенность: методы parseXmlString и loadTiledMap могут вернуть и Promise, и значение, и в любом случае произойдёт корректный переход в Fulfilled. Если возвращать Promise, конвейер будет асинхронным, нагрузка может обрабатываться во вторичном потоке.
  10. Одна из наибольших проблем для программистов – неправильное понимание работы языка. Полное незнание причиняет меньше проблем. В основном Javascript-разработчики читают примеры, а не спецификацию, и совершают ошибки в обработке ошибок. Такие ошибки остаются незаметными, т.к. сами по себе исключения редко возникают В примере: «давайте потеряем resolve/reject» – это предложение навечно оставить Promise в состоянии Pending. По ссылке https://goo.gl/dEvi8V показано, как легко забыть перебросить исключение в новый Promise
  11. в Gamedev и в серверной разработке event loop всегда Data-Oriented раздельные шаги (event process, update, render и т.п.) много похожих данных на одном шаге в Desktop и Mobile приложениях event loop всегда Object-Oriented много разных событий, разных реакций, разных объектов относительно мало массивов данных Вывод: в Desktop и Mobile простота event loop важнее низкого overhead на запуск одной задачи Вывод: в Desktop и Mobile хватает простого, но удобного объектно-ориентированного event loop, абстракции не страшны на серверах и в играх нужен быстрый event loop, с поддержкой сотен тысяч задач в секунду Мы сделал цикл на основе Win32 API Мы также использовали Message Only Window для встраивания в существующий цикл Win32-событий, запущенный кем угодно (даже если наш код выполняется из DLL как плагин)
  12. Не стоит использовать future, полученный от std::async: его деструктор будет блокировать поток до получения результат В Visual Studio 2010, 2012, 2013 деструктор не блокирует поток, и это баг, который исправлен в VS2015: https://connect.microsoft.com/VisualStudio/feedback/details/810623
  13. На момент, когда мы начинали работу, не было полного представления, как это должно выглядеть и способен ли Boost на такое Позже оказалось, что самые свежие версии Boost способны Boost Executors были реализованы в рамках Google Summer of Code 2014 В стандарт они войдут очень, очень нескоро
  14. Объект WeakInvoker хранит weak_ptr и реализует operator() Надо сделать две перегрузки BindWeakPtr: для const и не-const
  15. Наиболее важное отличие Promise в Javascript от C++: вызов callback происходит как таск в предсказуемом и безопасном для исключений окружении
  16. Наиболее важное отличие Promise в Javascript от C++: вызов callback происходит как таск в предсказуемом и безопасном для исключений окружении
  17. Использовали template variables и boost::mpl для определения индекса в диапазоне типов во время компиляции
  18. Есть рекурсивный вариантный тип, который по набору типов похож на JSON Есть callable, имеющий точно указанную сигнатуру Нужно без дополнительного указания типов применить аргументы к функции
  19. decay_t очень полезен, если надо сформировать tuple со значениями
  20. Перегружать функции для разных типов опасно тем, что появляются неоднозначные приведения типов Можно добавить строгости и требовать от программиста использовать float вместо double, int вместо unsigned/short/long
  21. Заворачивание в std::function позволяет вызвать callback отложенно, и даже добавить его в словарь, отображающий строковое имя сообщения на обработчик
  22. Позволяет привязать обработчик к имени метода, экспортируемого в Javascript
  23. Позволяет привязать обработчик к имени метода, экспортируемого в Javascript
  24. Позволяет привязать обработчик к имени метода, экспортируемого в Javascript
  25. Был создан отдельный поток для запуска Boost.Test Был создан Proxy Binder, который отслеживал аргументы и факт вызова (на манер sinon.js)
  26. Не требует enable_shared_from_this Работает в конструкторе/деструкторе, но недоступен извне Нельзя передавать monitor наружу Проблемы примера: Не соблюдает правило пяти, в итоге неправильно копируется/перемещается