Паттерн Команда

Паттерн Команда относится к поведенческим паттернам проектирования.

Это паттерн с которым я сижу дольше остальных, он настолько прост, что очень сложен. Но лично я нахожу прелесть самообучения в том что у тебя есть все время мира чтобы исследовать определенный вопрос под всеми углами.

Итак, в GoF применимость описывается достаточно лаконично и понятно:
Инкапсулирует запрос как объект, позволяя настраивать (parameterize) клиентов с разными запросами, использовать очереди, логировать запросы и осуществлять операции отмены.

Теперь реализуем простой вариант команды из описания:

 
string fakeTrumpsRequest = “SELECT * from Users where name beginsWith DonaldTrump”
 

Мы инкапсулировали запрос в объект класса строки, ей можно настраивать клиентов, добавлять команды в очередь, логировать, осуществлять отмену (с использованием паттерна “Снимок”)

Мне кажется этого вполне достаточно для осуществления запросов SQL и подобных, однако дальше начинаются детали реализации, разные варианты применения, также очень сильно разнится кодовая база паттерна, роли клиентов, добавляются вспомогательные классы.

Матчасть

Паттерн команда начинается с протокола Команды, который содержит единственный метод execute(). Дальше идет Конкретная Команда и Ресивер, КК реализует операцию над Ресивером, описывает связь между Ресивером и действием. Ничего непонятно? Мне тоже, но поехали дальше. Клиент создает экземпляр Конкретной Команды, связывает ее с Ресивером. Инвокер – объект который осуществляет процесс запуска Команды.

Теперь попробуем разобраться на примере, допустим мы хотим обновить myOS на телефоне myPhone, для этого мы запускаем приложение myOS_Update!, в нем нажимаем кнопку Update Now!, через 10 секунд система сообщит об успешном обновлении.

Клиентом в примере выше выступает приложение myOS_Update!, Инвокер это кнопка “Update Now!”, он запускает Конкретную Команду обновления системы с помощью метода execute(), которая обращается к Ресиверу – демону обновления операционной системы.

Пример использования

Допустим UI приложения myOS_Update! настолько хорош, что его решили продавать как отдельный продукт для предоставления интерфейса обновления других операционных систем. В таком случае мы реализуем приложение с поддержкой расширения через библиотеки, в библиотеках будут реализации Конкретных Команд, Ресиверов, оставим статичные/неизменяемые Инвокер, Клиент, протокол Команды.

Таким образом отпадает необходимость в осуществлении поддержки изменяемого кода, так как наш код останется неизменным, проблемы могут возникнут лишь при реализации на стороне клиентов, из-за ошибок в коде их Конкретных Команд и Ресиверов. Также в такой реализации отсутствует необходимость передавать исходный код основного приложения, то есть мы осуществили инкапсуляцию команд и взаимодействия UI с помощью паттерна Команда.

Источники

https://refactoring.guru/ru/design-patterns/command
https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612

Сборка macOS приложений под Ubuntu OSXCross CMake

В этой заметке я опишу сборку кросплатформенных C++ приложений для macOS на сборочной машине Ubuntu с использованием CMake и osxcross.
Для начала устанавливаем тулчейн osxcross:
https://github.com/tpoechtrager/osxcross
Установка происходит в 3 этапа, загрузка зависимостей:

 
cd tools
./get_dependencies.sh
 

Загрузка XCode.xip с официального сайта Apple, далее выгрузка SDK из XCode:

 
./gen_sdk_package_pbzx.sh /media/demensdeum/2CE62A79E62A4404/LinuxSupportStorage/xcode111.xip
 

Надеюсь вы прочитали лицензионное соглашение XCode на прошлом шаге? Далее сборка тулчейна с нужным префиксом:

 
INSTALLPREFIX=/home/demensdeum/Apps/osxcross ./build.sh 
 

Теперь можно пользоваться osxcross из директории-префикса прошлого шага. Добавим новый макрос сборки для CMake, пропишем все необходимо:

 
if (OSXCROSS)
SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER o64-clang)
SET(CMAKE_CXX_COMPILER o64-clang++)
SET(CMAKE_C_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_CXX_COMPILER_AR x86_64-apple-darwin19-ar)
SET(CMAKE_LINKER x86_64-apple-darwin19-ld)
SET(ENV{OSXCROSS_MP_INC} 1)
endif()
 

Динамическая линковка мне не удалась, поэтому экспортируем библиотеки статически:

 
if (OSXCROSS)
add_library(FlameSteelCore STATIC ${SOURCE_FILES})
else()
 

Далее вы можете столкнуться с фактом того что у вас нет необходимых библиотек для osxcross, с этим я встретился при использовании SDL2. osxcross поддерживает готовые пакеты библиотек – macports. Для примера установка SDL2-mixer:


osxcross-macports -v install libsdl2_mixer

После этого можно приступать к сборке библиотек/приложений как обычно в связке cmake-make, не забудьте прописать статическую линковку библиотек если необходимо.

Ручная сборка библиотек

На текущий момент я встретился с проблемой некорректной архивации библиотек при статической линковке, при сборке итогового приложения получаю ошибку:


file was built for archive which is not the architecture being linked (x86_64)

Очень похоже на этот тикет, удалось реализовать workaround в результате чего сборка завершается корректно. Разархивируем статическую библиотеку и соберем ее по новой с помощью архиватора osxcross:

 
ar x ../libFlameSteelCore.a
rm ../libFlameSteelCore.a
x86_64-apple-darwin19-ar rcs ../libFlameSteelCore.a *.o
 

Также одной из проблем лично я считаю отсутствие возможности запуска приложений macOS сразу на убунту (хотябы с частью функционала), конечно есть проект darling, но поддержка оставляет пока желать лучшего.

Источники

https://github.com/tpoechtrager/osxcross

Сборка для Windows под Ubuntu MinGW CMake

В данной заметке я опишу процесс сборки библиотек и приложений для Windows с помощью тулчейна MinGW32 на Ubuntu.
Установим wine, mingw:


sudo apt-get install wine mingw-w64

После этого уже можно собирать C/C++ приложения под Windows:


# C
i686-w64-mingw32-gcc helloWorld.c -o helloWorld32.exe      # 32-bit
x86_64-w64-mingw32-gcc helloWorld.c -o helloWorld64.exe    # 64-bit
 
# C++
i686-w64-mingw32-g++ helloWorld.cc -o helloWorld32.exe     # 32-bit
x86_64-w64-mingw32-g++ helloWorld.cc -o helloWorld64.exe   # 64-bit

Собранные exe можно проверить с помощью wine.

Далее рассмотрим изменения сборки CMake, файла CMakeLists.txt, добавляем MinGW специфичные вещи к файлу сборки:


if (MINGW32)
set(CMAKE_SYSTEM_NAME Windows)
SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
set(CMAKE_RANLIB i686-w64-mingw32-ranlib)
endif()

// для сборки shared dll
elseif (MINGW32)
add_library(FlameSteelEngineGameToolkit.dll SHARED ${SOURCE_FILES})
else()

// обязательно линкуем со всеми зависимостями
if (MINGW32)
target_link_libraries(
                        FlameSteelEngineGameToolkit.dll 
                        -static-libgcc
                        -static-libstdc++
                        SDL2 
                        SDL2_mixer 
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/FlameSteelCore.dll
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/FlameSteelBattleHorn.dll
                        /home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/FlameSteelCommonTraits.dll)

set_target_properties(FlameSteelEngineGameToolkit.dll PROPERTIES
        PREFIX ""
        SUFFIX ""
        LINK_FLAGS "-Wl,--add-stdcall-alias"
        POSITION_INDEPENDENT_CODE 0 # this is to avoid MinGW warning; 
        # MinGW generates position-independent-code for DLL by default
)
else()

Собираем:


cmake -DMINGW32=1 .
make

На выходе будет dll или exe, смотря что вы собираете. За рабочим примером можете смотреть в репозиторий нового проекта Cube-Art-Project и его библиотеки:
https://gitlab.com/demensdeum/cube-art-project
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/cube-art-project-bootstrap

Источники
https://arrayfire.com/cross-compile-to-windows-from-linux/

Простой Emscripten автотест для ChromeDriver

В данной заметке я опишу реализацию запуска автотеста для ChromeDriver браузера Chrome, который запускает транслированный из C++ автотест модуля с помощью Emscripten, считывает вывод консоли и возвращает результат проверки.
Для начала нужно установить selenium, для питона3-убунту это делается так:


pip3 install selenium

Далее скачиваем ChromeDriver с официального сайта, кладем chromedriver например в /usr/local/bin, после этого можно приступать к реализации автотеста.
Ниже я приведу код автотеста, который запускает браузер Chrome с открытой страницей автотеста на Emscripten, проверяет наличие текста “Window test succeded”:


import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

capabilities = DesiredCapabilities.CHROME
capabilities['goog:loggingPrefs'] = { 'browser':'ALL' }
driver = webdriver.Chrome()
driver.get("http://localhost/windowInitializeTest/indexFullscreen.html")

time.sleep(2)

exitCode = 1

for entry in driver.get_log('browser'):
    if entry["source"] == "console-api":
        message = entry["message"]
        if "Window test succeded" in message:
            print("Test succeded")
            exitCode = 0

driver.close()
exit(exitCode)

Сохраняем тест как main.py и запускаем python3 main.py

Сборка проекта с зависимостями для Emscripten

В этой заметке я опишу сборку проекта состоящего из нескольких библиотек с помощью Emscripten.
На данный момент Emscripten не поддерживает сборку shared библиотек, поэтому первым делом переводим все библиотеки из Shared в Static. Emscripten работает со своими include файлами, поэтому нужно решить вопрос с видимостью заголовочных файлов, я решил это с помощью проброса симлинка из системной директории в тулчейн Emscripten:


ln -s /usr/local/include/FlameSteelFramework $EMSDK/fastcomp/emscripten/system/include/FlameSteelFramework

Если вы используете CMake, то нужно поменять SHARED->STATIC в CMakeLists.txt файле метода add_library. Собрать библиотеку/приложение для дальнейшей статической линковки можно с помощью команд:


emcmake cmake .
emmake make

Далее нужно будет собрать основное приложение с указанием *.a файлов библиотек на этапе линковки. Относительный путь указать мне не удалось, сборка завершилась корректно только после указания полных путей в файле CMakeLists.txt:


elseif(EMSCRIPTEN)
target_link_libraries(${FSEGT_PROJECT_NAME} GL GLEW 
/home/demensdeum/Sources/cube-art-project-bootstrap/cube-art-project/sharedLib/libCubeArtProject.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkitFSGL/libFlameSteelEngineGameToolkitFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelEngineGameToolkit/libFlameSteelEngineGameToolkit.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCore/libFlameSteelCore.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelBattleHorn/libFlameSteelBattleHorn.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FSGL/libFSGL.a 
/home/demensdeum/Sources/cube-art-project-bootstrap/FlameSteelFramework/FlameSteelCommonTraits/libFlameSteelCommonTraits.a)
else()

Источники

https://emscripten.org/docs/compiling/Building-Projects.html#using-libraries

Shared Library CMake C++

Недавно решил сделать все части FlameSteelFramework отдельными shared библиотеками, далее покажу пример CMakeLists.txt файла для FlameSteelCore:


cmake_minimum_required(VERSION 3.5)

project (FlameSteelCore)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)

file(GLOB_RECURSE SOURCE_FILES
    "src/FlameSteelCore/*.cpp"
)

add_library(FlameSteelCore SHARED ${SOURCE_FILES})

install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/FlameSteelCore"
        DESTINATION include/FlameSteelFramework
        FILES_MATCHING
        PATTERN "*.h"
)

install(TARGETS FlameSteelCore DESTINATION lib)

Команды которые выполняет CMake: собирает все файлы с расширением *.cpp из директории src/FlameSteelCore/ в shared library, копирует все хидеры с расширением *.h из src/FlameSteelCore в include/FlameSteelFramework (в моем случае это /usr/local/include/FlameSteelFramework), копирует shared lib в директорию lib (/usr/local/lib)
После установки возможно будет необходимо обновить кэш LD – sudo ldconfig.
Для сборки и установки на Ubuntu (при наличии корректного тулчейна сборки) достаточно выполнить команды:


cmake . && make && sudo make install

Для проверки процесса установки я передаю make prefix в локальную папку makeInstallTestPlayground:


cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/demensdeum/makeInstallTestPlayground . && make && make install

References

https://stackoverflow.com/questions/17511496/how-to-create-a-shared-library-with-cmake
https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install

Интерпретатор языка C++ – Cling

Не так давно наткнулся на один интересный проект под названием Cling, интерпретатор языка C++, который может работать в интерактивном режиме из консоли в том числе. Ознакомиться с проектом можно по ссылке: https://github.com/root-project/cling
Установка для Ubuntu простейшая – скачать архив для нужно версии, распаковать, зайти в папку bin и запустить cling в терминале.
Далее пример загрузки библиотеки FlameSteelCore, инициализация объекта, распечатка id:

Потерянные исключения Emscripten и проблемы regex

Потерянные exception

Интересная особенность Emscripten, при запуске игрового цикла через emscripten_set_main_loop, следует помнить о том что хэндлинг исключений должен быть заново добавлен через try catch прямо в методе цикла, т.к. рантайм теряет блок try catch извне.
Проще всего выводить текст ошибки силами браузера, используя javascript alert:


            catch (const std::exception &exc)
            {
                const char *errorText = exc.what();
                cout << "Exception: " << errorText << "; Stop execution" << endl;

                EM_ASM_(
                {
                    var errorText = UTF8ToString($0);
                    alert(errorText);

                }, errorText);

                abort();

Слишком сложный regexp

Релизация regex в std может кинуть исключение error_complexity, если посчитает регулярное выражение слишком сложным. Такое происходит в текущей реализации emscripten, так что предлагаю вам реализовать тесты для парсинга через регулярки, либо использовать сторонние реализации regex.