Сборка проекта с зависимостями для 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

0

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

0

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

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

0

Потерянные исключения 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.

0

Паттерн Строитель

Паттерн Строитель относится к группе паттернов существование которых мне не особо понятно, отмечаю явную избыточность сего. Относится к группе порождающих паттернов проектирования. Используется для реализации простого интерфейса создания комплексных объектов.

Применимость

Упрощение интерфейса. Он может облегчить создание объекта в конструкторах с большим числом аргументов, объективно улучшить читаемость кода.

Пример на C++ без строителя:

auto monster = new Monster();
auto weapon = new Weapon(“Claws”);
monster->weapon = weapon;
auto health = new MonsterHealth(100);
monster->health = health;

Пример со строителем на C++:

auto monster = new MonsterBuilder()
                  .addWeapon(“Claws”)
                  .addHealth(100)
                  .build();

Однако в языках поддерживающих именованные аргументы (named arguments), необходимость использовать именно для этого случая отпадает.

Пример на Swift с использованием named arguments:


let monster = Monster(weapon: “Claws”, health: 100)

Иммутабельность. Используя строитель можно обеспечить инкапсуляцию создаваемого объекта, до окончательного этапа сборки. Здесь надо хорошо подумать, спасет ли использование паттерна от высокой динамики окружения в котором вы работаете, возможно применение паттерна ничего не даст, из-за простого отсутствия культуры использования инкапсуляции у команды разработки.

Взаимодействие с компонентами на разных этапах создания объекта. Также используя паттерн есть возможность обеспечить пошаговое создание объекта, при взаимодействии с другими компонентами системы. Скорее всего это очень полезно (?)

Критика

Конечно нужно *хорошенько* подумать стоит ли налаживать повсеместное использование паттерна в своем проекте. Языки с современным синтаксисом и продвинутым IDE нивелируют необходимость в использовании Строителя, в плане улучшения читаемости кода (см, пункт про именованные аргументы)
Нужно ли было использовать данный паттерн в 1994 году, на момент выпуска книги GoF? Скорее всего да, однако, судя по Open source кодовой базе тех лет, мало кто им пользовался.

Источники

https://refactoring.guru/ru/design-patterns/builder

0

Паттерн Композит

Паттерн Композит относится к структурным паттернам проектирования, в отечественных источниках он известен как “Компоновщик”.
Допустим мы разрабатываем приложение – фотоальбом. Пользователь может создавать папки, добавлять туда фото, производить прочие манипуляции. Обязательно нужна возможность показывать количество файлов в папках, общее количество всех файлов и папок.
Очевидно что нужно использовать дерево, но как реализовать архитектуру древа, с простым и удобным интерфейсом? На помощь приходит паттерн Композит.

Sheila in Moonducks

Далее в Directory реализуем метод dataCount() – путем прохода по всем элементам лежащим в массиве компонентов, сложив все их dataCount’s.
Все готово!
Ниже пример на Go:

package main

import "fmt"

type component interface {

dataCount() int

}

type file struct {

}

type directory struct {

c []component

}

func (f file) dataCount() int {

return 1

}

func (d directory) dataCount() int {

var outputDataCount int = 0

for _, v := range d.c {
outputDataCount += v.dataCount()
}

return outputDataCount

}

func (d *directory) addComponent(c component) {

d.c = append(d.c, c)

}

func main() {

var f file
var rd directory
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)
rd.addComponent(f)

fmt.Println(rd.dataCount())

var sd directory
sd.addComponent(f)

rd.addComponent(sd)
rd.addComponent(sd)
rd.addComponent(sd)

fmt.Println(sd.dataCount())
fmt.Println(rd.dataCount())

}

Источники

https://refactoring.guru/ru/design-patterns/composite

0

Паттерн Адаптер

Benjamín Núñez González

Паттерн Адаптер относится к структурным паттернам проектирования.

Адаптер обеспечивает конвертацию данных/интерфейсов между двумя классами/интерфейсами.

Допустим мы разрабатываем систему определения цели покупателя в магазине на основе нейросетей. Система получает видеопоток с камеры магазина, определяет покупателей по их поведению, классифицирует по группам. Виды групп – пришел купить (потенциальный покупатель), просто посмотреть (зевака), пришел что-то украсть (вор), пришел сдать товар (недовольный покупатель), пришел пьяный/под кайфом (потенциальный дебошир).

Как все опытные разработчики, мы находим готовую нейросеть которая умеет классифицировать виды обезьян в клетке по видеопотоку, которую любезно выложил в свободный доступ зоологический институт берлинского зоопарка, переучиваем ее на видеопотоке из магазина и получаем рабочую state-of-the-art систему.

Есть лишь небольшая проблема – видеопоток закодирован в формате mpeg2, а наша система поддерживает только OGG Theora. Исходного кода системы у нас нет, единственное что мы можем делать – менять датасет и обучать нейросеть. Что же делать? Написать класс-адаптер, который будет переводит поток из mpeg2 -> OGG Theora и отдавать в нейросеть.

По классической схеме в паттерне участвуют client, target, adaptee и adapter. Клиент в данном случае это нейросеть, получающая видеопоток в OGG Theora, таргет – интерфейс с которым она взаимодействует, adaptee – интерфейс отдающий видеопоток в mpeg2, адаптер – конвертирует mpeg2 в OGG Theora и отдает по интерфейсу target.

Вроде все просто?

Источники

https://ru.wikipedia.org/wiki/Адаптер_(шаблон_проектирования)
https://refactoring.guru/ru/design-patterns/adapter

1+

Паттерн Делегат

Паттерн делегат относится к основным паттернам проектирования.
Допустим мы разрабатываем приложение барбершопа. В приложении есть календарь для выбора дня для записи, по тапу на дате должен открываться список барберов с возможностью выбора.
Реализуем наивное связывание компонентов системы, объединим календарь и экран с помощью указателей друг на друга, для реализации вывода списка:



// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date) {
        screen.showBarbersList(date: date)
    }
}

Через несколько дней требования меняются, перед выводом списка нужно показывать предложения с выбором услуг (стрижка бороды и т.п.) но не всегда, во все дни кроме субботы.
Добавляем в календарь проверку суббота сегодня или нет, в зависимости от нее вызываем метод списка барберов или списка услуг, для наглядности продемонстрирую:



// псевдокод

class BarbershopScreen {
   let calendar: Calendar

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    let screen: BarbershopScreen

    func handleTap(on date: Date)  {
        if date.day != .saturday {
             screen.showOffersList()
        }
        else {
             screen.showBarbersList(date: date)
        }
    }
}

Через неделю нас просят добавить календарь на экран обратной связи, и в этот момент случается первое архитектурное ой!
Что же делать? Календарь ведь связан намертво с экраном записи на стрижку.
ух! уф! ой-ой
Если продолжить работать с такой бредовой архитектурой приложения, то следует сделать копию всего класса календаря и связать эту копию с экраном обратной связи.
Ок вроде хорошо, далее мы добавили еще несколько экранов и несколько копий календаря, и тут наступил момент икс. Нас попросили изменить дизайн календаря, тоесть теперь нужно найти все копии календаря и добавить одинаковые изменения во все. Такой “подход” очень сказывается на скорости разработки, увеличивает шанс допустить ошибку. В итоге такие проекты оказываются в состоянии разбитого корыта, когда уже даже автор оригинальной архитектуры не понимает как работают копии его классов, прочие хаки добавленные по пути разваливаются на лету.
Что же нужно было делать, а лучше что еще не поздно начать делать? Использовать паттерн делегирования!
Делегирование это способ передавать события класса через общий интерфейс. Далее пример делегата для календаря:


protocol CalendarDelegate {
   func calendar(_ calendar: Calendar, didSelect date: Date)
}

Теперь добавим код работу с делегатом в код примера:



// псевдокод

class BarbershopScreen: CalendarDelegate {
   let calendar: Calendar

   init() {
       calendar.delegate = self
   }

   func calendar(_ calendar: Calendar, didSelect date: Date) {
        if date.day != .saturday {
            showOffersList()
        }
        else {
             showBarbersList(date: date)
        }
   }

   func showBarbersList(date: Date) {
      showSelectionSheet(barbers(forDate: date))
   }

   func showOffersList() {
      showSelectionSheet(offers)
   }
}

class Calendar {
    weak var delegate: CalendarDelegate

    func handleTap(on date: Date)  {
        delegate?.calendar(self, didSelect: date)
    }
}

В итоге мы отвязали календарь от экрана совсем, при выборе даты из календаря он передает событие выбора даты – *делегирует* обработку события подписчику; подписчиком выступает экран.
Какие преимущества мы получаем в таком подходе? Теперь мы можем менять календарь и логику экранов независимо друг от друга, не дублируя классы, упрощая дальнейшую поддержку; таким образом реализуется “принцип единственной ответственности” реализации компонентов системы, соблюдается принцип DRY.
При использовании делегирования можно добавлять, менять логику вывода окошек, очередности чего угодно на экране и это совершенно не будет затрагивать календарь и прочие классы, которые объективно и не должны участвовать в несвязанных напрямую с ними процессами.
Альтернативно, не особо утруждающие себя программисты, используют отправку сообщений через общую шину, без написания отдельного протокола/интерфейса делегата, там где лучше было бы использовать делегирование. О недостатках такого подхода я написал в прошлой заметке – “Паттерн Наблюдатель”.

Источники

https://refactoring.guru/ru/replace-inheritance-with-delegation

1+