Детали разработки
кроссплатформенного
фреймворка.
SPB TV, руководитель отдела перспективных
разработок
Никита Глушков
Чем мы занимаемся
Условия разработки
• Историческое наследие
• Кросс-платформенность
• Продолжительные проекты
Технический долг
• Для продолжительных проектов поддержка
кода много дороже его разработки.
• Стоимость поддержки может расти со
временем.
• Плохой код – не синоним быстрого
результата.
Технический дефолт*
• Разработчики время тратят, но новых фич
больше не появляется.
• Всё время уходит на:
– исправление проекта
– попытки в нём просто разобраться
– регулярное переписывание значительной части
функционала
* Попрощайтесь с отличным бизнесом
Архитектура
• Совокупность способов организации
системы, которые позволяют после
реализации очередного мейлстоуна,
релиза или обновления сохранить
относительно дешёвую возможность
развивать продукты далее*
* После короткого рефакторинга
Структура нашего проекта
Представление
Переносимые графические примитивы
SceneGraph Animators
Шаблоны SceneGraph + DataBinding
💗
Модель данных
Источник данных
Методов
Событий
Машина состояний
Кстати, о С++
Указатели
• Аналог intrusive_ptr
• Аналог weak_ptr
• std::shared_ptr – антипаттерн.
class Foo : public Referenced {...}
auto obj = make_shared<Foo>();
shared_ptr<Foo> obj2 = this;
class IMyInterface {
public:
WEAK_REFERABLE( IMyInterface );
Указатели
Label
• Field 1
• Field 2
• Field 3
Shape
Group
Animator
weak_ptr
void*
Descriptors
• Структуры хранящие строковый буфер +
длину.
• Позволяют работать с подстроками без
выделения памяти.
TPtrC8 ptr("Test TPtrC");
ptr.Length() == 10;
ptr.Mid(1, 3) == "est";
std::string str( "string" );
TPtrC8 ptr2( str.c_str(), 3 );
ptr2 == "str";
Descriptors
• Комбинируют в одном int длину и флаги.
• Полностью заменяют все записи вида char*,
const wchar_t* и т.п.
const char* const TDesC8&
• Можно распарсить xml или json без
аллокации памяти под строки.
Strings
• Утино-типизированы с ATL/WTL::CString
• Прозрачно работают с дескрипторами
• Используют счётчик ссылок
• std::string - антипаттерн
void Test( const TDesC8& desc );
CStringA str = "Test";
str.Append( "2" );
Test( str );
Strings
• Выполняется правило:
– CStringA отображает строки необходимые для
работы системы
– CStringW отображает строки, которые может
увидеть пользователь
• Универсальные строковые преобразования:
size_t CastFromString( const TDesC8& str, SomeType& t );
CStringA CastToString( SomeType );
RTTI
• Позволяет идентифицировать классы в
рантайме, содержит их базовое описание.
class variant_type
{
const char* name;
size_t size;
size_t aligned_size;
func_constructor constructor;
func_destructor destructor;
func_copy_constructor copy_constructor;
RTTI
• template<class T>
static variant_type const* type_id()
{
static variant_type static_type;
return static_type.m_true_type;
}
• Проблема – при работе с несколькими
исполняемыми модулями может
получаться несколько идентифицирующих
указателей для одного типа.
Variant
• class variant: контейнер, в котором можно
безопасно хранить объект (почти) любого
типа
variant v1 = 1;
variant v2 = "Test";
variant v3 = myMegaObject;
variant v4 = Calculate( "1 + 2 * ( 3 +
GetInt('Settings.someValue') )", some_ctx );
int i = variant_cast<CString>( v4 ); // Oops!
Variant
• Хранит указатель на структуру, содержащую
счётчик, указатель на идентификатор RTTI и
место под хранимый объект.
• При сохранении объекта используется
только одна аллокация.
struct variant_info
{
variant_type const* m_type;
int m_ref;
};
variant_info* m_pInfo;
Variant
• Один из ключевых элементов системы,
поэтому ради оптимизации за милым
интерфейсом прячутся страшные вещи:
template<class T>
void Store(T const& val)
{
variant_type const* type = variant_type::type_id<T>();
size_t size = type->align_size + sizeof(variant_info);
m_pInfo = (variant_info*)SmallAlloc( size, SA_VARIANT );
m_pInfo->m_ref = 1;
m_pInfo->m_type = type;
new (GetValue()) T( val );
}
Variant
template<class T>
T& variant_cast(variant& var)
{
LCCHECKM( var.GetType() == variant_type::type_id<T>(),
"Variant cannot be casted from: " <<
(var.IsEmpty() ? "'empty'" : var.GetType()->name ) <<
", to: " << variant_type::type_id<T>()->name );
return *static_cast<T*>(var.GetValue());
}
class IVariantSerializator
{
virtual CStringA Save( const variant&, IReferencedToId& )=0;
virtual variant Load( const TDesC8& str, IIdToReferenced& )=0;
};
Variant
• Не хватает возможности автоматического
приведения типов внутри контейнера.
TexturePtr texture;
if ( it->m_Value.GetType() == rtti::type_id<Texture2DPtr>() )
...
else if ( it->m_Value.GetType() == rtti::type_id<TexturePtr>() )
...
else if ( it->m_Value.GetType() ==
rtti::type_id<IUniformSourceTexture2DPtr>() )
...
else
...
Serialization
• Маршалинг:
– Из и в xml, json, binary
– Конвертация в нативный формат
– Оптимизация сцен
• Рефлексия:
– Поиск объектов и их мемберов в иерархии
– Модификация сцен аниматорами или
скриптами
Serialization
• Форма бралась из boost:
class AnimaControl : public Referenced
{
public:
...
template<class TArchive>
void Serialize(TArchive& ar)
{
ar & SERIALIZE_ITEM_EXT( m_CurrentTime, "Time", SER_ATTRIBUTE );
ar & SERIALIZE_ITEM_EXT( m_PlayRate, "PlayRate", SER_ATTRIBUTE );
ar & SERIALIZE_ITEM_EXT( m_Enabled, "Enabled", SER_ATTRIBUTE );
ar & SERIALIZE_ITEM_EXT( m_Weight, "Weight", SER_ATTRIBUTE );
...
Serialization
<Modify Element="MainView:Animator"
Variable="AnimaControl:PlayRate" Value="-1"/>
<AnimaControl TimeEnd="0.5" PlayRate="0" ClampTime="1"/>1.
2.
3. <Material>
<Platform Name="OpenGL">
<UniformValues>
<Texture Name="sampler"/>
<Real Name="alpha"
Value="MainView:Animator:AnimaControl:Time"/>
</UniformValues>
...
Функторы
class FunctorImpl : public Referenced
{
public:
virtual void operator()() = 0;
};
class Functor : public shared_ptr<FunctorImpl>
{
typedef shared_ptr<FunctorImpl> baseClass;
public:
template<class T, class TPtr>
Functor( T ptr, TPtr func )
...
Система запросов
• Запросы сетевые, к системе, внутренним
процессам, пользователям.
RequestHandle RequestSomeInfo(
CStringA someParam1,
Function<void, Foo> on_success,
Function<void, ErrorReason> on_fail );
class ITVService : public lc::ILcPlugin
{
public:
virtual lc::RequestHandle TryToConnect(
lc::Functor onSuccess,
lc::Function<void, lc::ErrorReason> ) = 0;
Декларативные элементы
{
action1;
scope_exit { cleanup1 };
scope_fail { rollback1 }; // nope
action2;
scope_exit { cleanup2 };
scope_fail { rollback2 }; // nope
next2;
}
www.github.com/panaseleus/stack_unwinding
Процесс разработки
• Разработчики вольны выбирать наиболее
удобную ОС для работы.
• UI в основном разрабатывается под
Windows
• Модификация интерфейса и основных
переходов происходит преимущественно
без перезагрузки системы
Оформление кода
Комментарии в коде
• Хочешь понять код? Читай его.
• Тянет написать комментарий? Потрать
время на рефакторинг.
• Комментарии = дублирование кода = $$$.
• Полезного комментария: // Google “3DDDA”
• Осмысленные логи – совсем другое дело!
• А так же – многочисленные юнит-тесты,
примеры и обзорные статьи
Вопросы?

Никита Глушков, К вопросу о реализации кроссплатформенных фреймворков

  • 1.
    Детали разработки кроссплатформенного фреймворка. SPB TV,руководитель отдела перспективных разработок Никита Глушков
  • 2.
  • 3.
    Условия разработки • Историческоенаследие • Кросс-платформенность • Продолжительные проекты
  • 4.
    Технический долг • Дляпродолжительных проектов поддержка кода много дороже его разработки. • Стоимость поддержки может расти со временем. • Плохой код – не синоним быстрого результата.
  • 5.
    Технический дефолт* • Разработчикивремя тратят, но новых фич больше не появляется. • Всё время уходит на: – исправление проекта – попытки в нём просто разобраться – регулярное переписывание значительной части функционала * Попрощайтесь с отличным бизнесом
  • 6.
    Архитектура • Совокупность способоворганизации системы, которые позволяют после реализации очередного мейлстоуна, релиза или обновления сохранить относительно дешёвую возможность развивать продукты далее* * После короткого рефакторинга
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
    Указатели • Аналог intrusive_ptr •Аналог weak_ptr • std::shared_ptr – антипаттерн. class Foo : public Referenced {...} auto obj = make_shared<Foo>(); shared_ptr<Foo> obj2 = this; class IMyInterface { public: WEAK_REFERABLE( IMyInterface );
  • 12.
    Указатели Label • Field 1 •Field 2 • Field 3 Shape Group Animator weak_ptr void*
  • 13.
    Descriptors • Структуры хранящиестроковый буфер + длину. • Позволяют работать с подстроками без выделения памяти. TPtrC8 ptr("Test TPtrC"); ptr.Length() == 10; ptr.Mid(1, 3) == "est"; std::string str( "string" ); TPtrC8 ptr2( str.c_str(), 3 ); ptr2 == "str";
  • 14.
    Descriptors • Комбинируют водном int длину и флаги. • Полностью заменяют все записи вида char*, const wchar_t* и т.п. const char* const TDesC8& • Можно распарсить xml или json без аллокации памяти под строки.
  • 15.
    Strings • Утино-типизированы сATL/WTL::CString • Прозрачно работают с дескрипторами • Используют счётчик ссылок • std::string - антипаттерн void Test( const TDesC8& desc ); CStringA str = "Test"; str.Append( "2" ); Test( str );
  • 16.
    Strings • Выполняется правило: –CStringA отображает строки необходимые для работы системы – CStringW отображает строки, которые может увидеть пользователь • Универсальные строковые преобразования: size_t CastFromString( const TDesC8& str, SomeType& t ); CStringA CastToString( SomeType );
  • 17.
    RTTI • Позволяет идентифицироватьклассы в рантайме, содержит их базовое описание. class variant_type { const char* name; size_t size; size_t aligned_size; func_constructor constructor; func_destructor destructor; func_copy_constructor copy_constructor;
  • 18.
    RTTI • template<class T> staticvariant_type const* type_id() { static variant_type static_type; return static_type.m_true_type; } • Проблема – при работе с несколькими исполняемыми модулями может получаться несколько идентифицирующих указателей для одного типа.
  • 19.
    Variant • class variant:контейнер, в котором можно безопасно хранить объект (почти) любого типа variant v1 = 1; variant v2 = "Test"; variant v3 = myMegaObject; variant v4 = Calculate( "1 + 2 * ( 3 + GetInt('Settings.someValue') )", some_ctx ); int i = variant_cast<CString>( v4 ); // Oops!
  • 20.
    Variant • Хранит указательна структуру, содержащую счётчик, указатель на идентификатор RTTI и место под хранимый объект. • При сохранении объекта используется только одна аллокация. struct variant_info { variant_type const* m_type; int m_ref; }; variant_info* m_pInfo;
  • 21.
    Variant • Один изключевых элементов системы, поэтому ради оптимизации за милым интерфейсом прячутся страшные вещи: template<class T> void Store(T const& val) { variant_type const* type = variant_type::type_id<T>(); size_t size = type->align_size + sizeof(variant_info); m_pInfo = (variant_info*)SmallAlloc( size, SA_VARIANT ); m_pInfo->m_ref = 1; m_pInfo->m_type = type; new (GetValue()) T( val ); }
  • 22.
    Variant template<class T> T& variant_cast(variant&var) { LCCHECKM( var.GetType() == variant_type::type_id<T>(), "Variant cannot be casted from: " << (var.IsEmpty() ? "'empty'" : var.GetType()->name ) << ", to: " << variant_type::type_id<T>()->name ); return *static_cast<T*>(var.GetValue()); } class IVariantSerializator { virtual CStringA Save( const variant&, IReferencedToId& )=0; virtual variant Load( const TDesC8& str, IIdToReferenced& )=0; };
  • 23.
    Variant • Не хватаетвозможности автоматического приведения типов внутри контейнера. TexturePtr texture; if ( it->m_Value.GetType() == rtti::type_id<Texture2DPtr>() ) ... else if ( it->m_Value.GetType() == rtti::type_id<TexturePtr>() ) ... else if ( it->m_Value.GetType() == rtti::type_id<IUniformSourceTexture2DPtr>() ) ... else ...
  • 24.
    Serialization • Маршалинг: – Изи в xml, json, binary – Конвертация в нативный формат – Оптимизация сцен • Рефлексия: – Поиск объектов и их мемберов в иерархии – Модификация сцен аниматорами или скриптами
  • 25.
    Serialization • Форма браласьиз boost: class AnimaControl : public Referenced { public: ... template<class TArchive> void Serialize(TArchive& ar) { ar & SERIALIZE_ITEM_EXT( m_CurrentTime, "Time", SER_ATTRIBUTE ); ar & SERIALIZE_ITEM_EXT( m_PlayRate, "PlayRate", SER_ATTRIBUTE ); ar & SERIALIZE_ITEM_EXT( m_Enabled, "Enabled", SER_ATTRIBUTE ); ar & SERIALIZE_ITEM_EXT( m_Weight, "Weight", SER_ATTRIBUTE ); ...
  • 26.
    Serialization <Modify Element="MainView:Animator" Variable="AnimaControl:PlayRate" Value="-1"/> <AnimaControlTimeEnd="0.5" PlayRate="0" ClampTime="1"/>1. 2. 3. <Material> <Platform Name="OpenGL"> <UniformValues> <Texture Name="sampler"/> <Real Name="alpha" Value="MainView:Animator:AnimaControl:Time"/> </UniformValues> ...
  • 27.
    Функторы class FunctorImpl :public Referenced { public: virtual void operator()() = 0; }; class Functor : public shared_ptr<FunctorImpl> { typedef shared_ptr<FunctorImpl> baseClass; public: template<class T, class TPtr> Functor( T ptr, TPtr func ) ...
  • 28.
    Система запросов • Запросысетевые, к системе, внутренним процессам, пользователям. RequestHandle RequestSomeInfo( CStringA someParam1, Function<void, Foo> on_success, Function<void, ErrorReason> on_fail ); class ITVService : public lc::ILcPlugin { public: virtual lc::RequestHandle TryToConnect( lc::Functor onSuccess, lc::Function<void, lc::ErrorReason> ) = 0;
  • 29.
    Декларативные элементы { action1; scope_exit {cleanup1 }; scope_fail { rollback1 }; // nope action2; scope_exit { cleanup2 }; scope_fail { rollback2 }; // nope next2; } www.github.com/panaseleus/stack_unwinding
  • 30.
    Процесс разработки • Разработчикивольны выбирать наиболее удобную ОС для работы. • UI в основном разрабатывается под Windows • Модификация интерфейса и основных переходов происходит преимущественно без перезагрузки системы
  • 31.
  • 32.
    Комментарии в коде •Хочешь понять код? Читай его. • Тянет написать комментарий? Потрать время на рефакторинг. • Комментарии = дублирование кода = $$$. • Полезного комментария: // Google “3DDDA” • Осмысленные логи – совсем другое дело! • А так же – многочисленные юнит-тесты, примеры и обзорные статьи
  • 33.