• Разделение системы на модули?

    @MarkusD
    все время мелю чепуху :)
    Ты все правильно думаешь. Базовое разделение на модули выглядит рационально.
    А дальше давай думать логически.

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

    В итоге, нам надо завести утилитарную подсистему, связанную как с фильмами, так и с рейтингами.
    Для утилитарной подсистемы нормально знать сразу о многих других подсистемах, даже о других утилитарных. Поэтому в одной утилитарной подсистеме можно завести связи между фильмами и актерами, фильмами и внешними рейтингами, и.т.д.
    Чувство меры в этом деле работает на уровне богатства интерфейса по связи между подсистемами. Если связь между некоторым набором подсистем порождает богатый интерфейс, то этот интерфейс имеет смысл выдавить в отдельную подсистему.

    Более того, я обычно рекомендую полностью разделять данные и логику в разные подсистемы. Всякие там поиски, фильтрации и прочие любые манипуляции над неизменными данными лучше держать в отдельной подсистеме. Тогда всегда будет понятно, структура данных поменялась или метода манипуляции с этими данными.
    Ответ написан
    2 комментария
  • Как проверить на утечки памяти?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Больше трех лет пользуюсь инструментом Visual Leak Detector. Это тихий и неприхотливый инструмент, требующий соблюдения всего двух правил: вписать один include в одном cpp и собрать отладочную конфигурацию проекта.

    После завершения отлаживаемого процесса, VLD пишет в (файл/консоль/debug output) все найденные утечки со стеками выделения утекшей памяти. К отчету об утечке, помимо стека, еще добавляется HEX dump самого участка памяти, чтобы можно было на глаз сориентироваться, что за память утекла.
    А если утечек нет, VLD утешительно говорит что все нормально.

    Проект является открытым, доступен на github, стабильно развивается, выкладывается для современных версий Visual Studio и доступен из Extensions Manager самой студии.

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

    Ну и, безусловно, всегда есть valgrind memcheck, который по своей сути является sandbox-ом памяти и с радостью покажет тебе не только утечку, но и любой heap corruption, любое неправомерное обращение к памяти, которое ты будешь просто не в состоянии отловить руками или глазами.
    Насколько я помню, за последние 2-3 года valgrind так и не стал кроссплатформенным.
    Ответ написан
    4 комментария
  • Что не даёт на C++ писать кроссплатформенные приложения?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Да, как бы, ничего не мешает писать один С++ код для множества платформ. Почти весь мой трудовой стаж связан именно с разработкой кроссплатформенных игр. Я работал с несколькими (самодельными и не очень) движками и имею свое собственное детище, прекрасно и однозначно собирающееся на 5 целевых платформ (Win, Mac, Linux, ios, Android), к которым без труда можно добавить и консоли, и новые платформы.

    Нет, вру, не без труда. Попотеть над слоем абстракции придется. Но попотеть придется только над ним, все остальное заведется само, т.к. изначально написано в стандарте C++, без расширений под конкретные компиляторы, и с применением ряда очень важных для кроссплатформенности подходов.

    Кроссплатформенность подразумевает решение ряда вопросов, которые и выливаются в слой абстракции над операционной системой. Эти вопросы, зачастую, решать никто не хочет. Несколько раз встречал такое сам и еще мне рассказывали о том, как тот или иной движок сперва был только под конкретную ###, а потом решили портировать на @@@. Оказалось, что компилятор, которым всегда и собирали движок, нашпигован расширениями языка, которые конечно же все пользовали на 100%, и при смене компилятора ни один файл исходников не остался без доброй сотни ошибок. Т.е. переписывать надо было ВСЁ.

    Mercury13 хорошо рассказал про Unicode пути к файлам. Drakonoved правильно подметил про разделители путей к файлам. Максим Гришин очень хорошо напомнил про порядок следования байт. Это все и есть часть этого ряда вопросов.
    У каждой платформы есть свой API, которого не будет на другой платформе. Но на другой платформе будет свой API, со своими именами и схожей функциональностью. И работу с API надо абстрагировать от универсального кода.
    Еще, на одной платформе у тебя может быть разомкнутый главный цикл обработки сообщений (Win), а на другой - замкнутый (Android). Надо подстраиваться. GUI везде разный, надо подстраиваться. Сама структура приложения на одной платформе может быть монолитной, а на другой - композиционной. Графические и звуковые API могут быть и кроссплатформенными, однако простоты использования это им не прибавляет. Инициализация все равно будет платформозависимой.
    На самом деле даже в рамках работы на одной платформе надо соблюдать ряд правил, чтобы иметь возможность из одного кода получать и 32-битное приложение, и 64-битное тоже. Об этом неплохо написано на сайте разработчиков PVS-Studio.

    И все это решается. От части - с помощью архитектурных приемов. Один из таких я уже показывал в другом своем ответе.
    И еще эти вопросы можно не решать.
    ДубльГИС, например, уже давно работает на базе Qt, что сильно упростило им кроссплатформенную жизнь. Qt решает ряд проблем кроссплатформенности.
    Ответ написан
    10 комментариев
  • Может ли код(определение) в заголовочных файлах быть вынесен в shared library?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    У C++ есть понятие Translation Unit. Как видишь, это 7я фаза трансляции, слияние всех .h файлов происходит на 4й фазе.
    .h файлы не являются единицами трансляции по своей семантике.

    В идеале, библиотека "fast-cpp-csv-parser" потому и выполнена в виде header-only, что она fast, используется по ее месту и не подразумевает за собой дополнительных единиц трансляции. Ее незачем выводить в динамическую библиотеку.

    Но если, скажем, тебе очень надо, то можно сделать так.
    Нужно объявить общий интерфейс взаимодействия между кодом процесса и кодом библиотеки (набор функций или интерфейс, по вкусу). В рамках единицы трансляции твоей динамической библиотеки, и вне этого общего интерфейса, надо подключить и задействовать "fast-cpp-csv-parser". Конкретная реализация общего интерфейса взаимодействия внутри твоей динамической библиотеки должна использовать код "fast-cpp-csv-parser".
    Ответ написан
    Комментировать
  • Какое время жизни у переменной?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    auto out = in;
    Тип переменной `out` будет `std::forward_list<T>`. [Пояснение 1], [Пояснение 2]

    Поэтому `out ` будет локальной переменной и будет иметь локальное время жизни.
    Исключением может быть только temporary lifetime extension. Тогда время жизни переменной продлится.
    The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.
    Ответ написан
    5 комментариев
  • Стоит ли заранее заботится о возможности поддержки предыдущих версий многопользовательской мобильной онлайн игры? И как?

    @MarkusD
    все время мелю чепуху :)
    В процессе сопровождения игры может измениться протокол клиент/серверного взаимодействия или еще что-то, после чего предыдущие версии игры перестанут поддерживаться.

    Честно говоря, изменение и обновление протокола между версиями продукта - это норма жизни. Только не путай с выпуском обновлений контента, это другой вопрос.

    Можно сказать так: сетевой протокол полезно периодически полностью менять. Это хорошая профилактика от разного рода паразитов твоего проекта. Этакая мера гигиены.

    В процессе обновления протокола мы обычно приходим к одному из двух вариантов:
    • Меняется именно протокол - т.е. набор и содержание пакетов.
    • Меняется сервисный код протокола - т.е. сервисный код для сериализации данных в пакеты.


    В первом случае проблем особых не возникает. Второй случай действительно приводит к нарушению совместимости.
    Чтобы в первом случае добиться совместимости между версиями клиентов и сервера, разработчик обычно использует библиотеки сериализации с поддержкой обратной (или/и прямой) совместимости.
    Примерами таких библиотек являются Google Protobuf, Google Flatbuffers и Cap'N Proto. (да, тега C++ нет, но делу это не мешает)
    Еще я сталкивался с самостоятельно разработанными решениями, которые в разной степени обладают достоинствами приведенных библиотек.

    Есть ли статистика по влиянию обязательных обновлений игры на заинтересованность пользователей?

    Статистика по влиянию обязаловки на желание пользователя играть есть - очень большое влияние. Буквально это выливается в последовательность: запустил - просят обновить клиент - удалил, т.к. играть хочу, а не обновлять.
    Так поступает очень много пользователей в странах, где мобильный интернет очень дорогой и одно обновление на 10Мб буквально означает дыру в кошельке.

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

    Если мы ведем проект для мобильных платформ, то там ситуация с обновлениями еще интереснее. Тебе никто никогда не скажет, когда в конкретном маркете обновится твоя версия клиента. Выкладка версии - это долгий процесс, синхронизировать его между разными маркетами невозможно. Если у тебя в охвате только GooglePlay и AppStore, то это еще половина проблемы. Обычно маркетов десятки, куда обязательно входит Я.Стор для Android, маркеты от Самсунга, сотовых операторов, мелких лавочников и тому подобного. А если мы заходим в Китай, то число маркетов начинает достигать сотен. И каждый из них твое приложение обновит только когда захочет.
    Это все говорит о том, что обновление сервера и мобильных клиентов - дело несинхронизируемое. Сервер сразу должен быть готов принять обновленные клиенты, поэтому его надо обновить одномоментно с выкладкой версий в маркеты. И в то же время этот новый сервер должен уметь впускать старые клиенты, потому что новые еще не прошли проверку и не выложились для скачивания пользоавтелям.

    Реально ли создать игру таким образом, что бы вносимые изменения не влияли на предыдущие версии и срок службы каждой версии оставался максимальным? И как это сделать?

    Да, реально, примеры таких игр уже лет 8 с лишним бороздят просторы Appstore и GP. Названий не дам, рекламой не занимаюсь, пеарить не привык.
    Я в своей работе использую Cap'N Proto и Flatbuffers. Эти две библиотеки покрывают задачи обратной совместимости для протокола и бинарных ресурсов. Использования этих библиотек хватает для свободы изменять данные не боясь потерять клиентов.
    В общем, для решения этой проблемы нужны инструменты обеспечения обратной и прямой совместимости.

    Важно понимать, что совместимость обеспечивать надо не только для протокола, но и для ресурсов клиента.
    Ресурсы могут быть залиты на локальный CDN провайдера пользователя и поставляться по каналу связи с льготным тарифом, но у пользователя может не быть возможности зайти в Appstore и обновить само приложение клиента.
    Поэтому, старый клиент должен иметь возможность запускаться с новыми ресурсами в режиме совместимости.
    Именно эту задачу и решает библиотека Flatbuffers. Она дает возможность обновлять формат бинарных данных вперед и сохранять очень эффективный доступ к ним через старую версию формата.

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

    Может ли задача сопровождения предыдущих версий сильно влиять на архитектуру ПО, скорость и процесс разработки?

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

    Важнее всего понимать, что обратная совместимость - крайне временная мера. Ее вводить очень надо, но только на время миграции клиентов между версиями. Самых отсталых клиентов лучше подгонять палкой-погонялкой, чтобы поскорее обновились.
    Поддерживать 10 прошлых версий - неразумно и создает плодороднейшую почву для паразитов.

    Если сказать в общем. Да, обеспечивать совместимость - это тяжело. Это ряд архитектурных решений, которые должны быть сделаны правильно. Но если навыки уже есть, если есть опыт, если есть видение проекта, то обеспечение совместимости - штука посильная.
    Ответ написан
    1 комментарий
  • Как стать художником в геймдеве?

    @MarkusD
    все время мелю чепуху :)
    Юра Березовский , читать сгодится все то, что полезно для укрепления знаний. Книги по архитектуре, машиностроению, анатомии животных и людей, бодибилдингу (т.к. там хорошо рассказывают про моторику и мышцы).

    Конкретнее... Вот, что глазом у жены с полок ухватил:
    * Джеймс Гани - Цвет и свет.
    * Готтфрид Баммес - Образ человека
    * Ф. Делавье - Анатомия силовых упражнений.

    И, как всегда, изучать первым делом надо всё. Но укрепляться желательно только в тех направлениях, которые тебя увлекают. Тогда твои навыки попрут в гору.

    Основные инструменты артиста имеют или вот такое название:
    Логотипчики
    148715.gif
    или вот такое:
    huion-logo-1421263547.jpg


    Wacom - дороже и считаются более крутыми. Huion - молодые, дерзкие, все время норовят куснуть у вакома долю.
    Бытует мнение, что даже если сейчас у тебя не получается работать с планшетом, все равно работе с ним стоит уделять минимум 2 часа в день. Привыкнешь.

    ArtStation хорошо мотивирует самообучаться.
    Роман Гура, вроде, иногда берет учебные группы за денежку. Навык Ромка поднимать умеет как у тех, у кого все кисло, так и у тех, кто уже чего-то добился.
    Лео Хао, вроде бы, тоже недавно занимался обучением начинающих.

    А вот на счет того, как развивать свои навыки, есть вот такой совет:
    i9Cbdc9K9XA.jpg

    Кстати, советую запиннить группу "Берешь и рисуешь", она полезная.

    Как-то так, в общем. Дерзай.
    Ответ написан
    Комментировать
  • Выбор типа переменной класса в зависимости от параметра шаблона (C++17, if constexpr)?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ты можешь добиться желаемого используя частную специализацию, например.

    Суть такова. Нужно превратить значения типа перечисления в тип. Сделать мы это можем используя возможность указывать в качестве параметра шаблона значения перечислимых типов.

    Например:
    enum class AllowedTypes : uint8_t
    {
    	Int8	= 0,
    	Uint8,
    	Int16,
    	Uint16,
    };
    
    template< AllowedTypes TYPE >
    struct AllowedType;


    А для того, чтобы конкретное значение перечисления правильно переводилось в тип, шаблон "AllowedType" нужно специализировать.
    spoiler
    template<>
    struct AllowedType<AllowedTypes::Int8>
    {
    	using Type = int8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint8>
    {
    	using Type = uint8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Int16>
    {
    	using Type = int16_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint16>
    {
    	using Type = uint16_t;
    };


    После этого достаточно использовать "typename AllowedType::Type" в нужном тебе месте.
    spoiler
    template< AllowedTypes TYPE >
    struct spi
    {
    	typename AllowedType<TYPE>::Type*	p_tx = nullptr;
    	
    	constexpr spi() {};
    };


    Вот пример работы:
    spoiler
    // Example program
    #include <typeinfo>
    #include <iostream>
    #include <string>
    #include <cstdint>
    
    enum class AllowedTypes : uint8_t
    {
    	Int8	= 0,
    	Uint8,
    	Int16,
    	Uint16,
    };
    
    template< AllowedTypes TYPE >
    struct AllowedType;
    
    template<>
    struct AllowedType<AllowedTypes::Int8>
    {
    	using Type = int8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint8>
    {
    	using Type = uint8_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Int16>
    {
    	using Type = int16_t;
    };
    
    template<>
    struct AllowedType<AllowedTypes::Uint16>
    {
    	using Type = uint16_t;
    };
    
    template< AllowedTypes TYPE >
    using TypeFor = typename AllowedType<TYPE>::Type;
    
    
    template< AllowedTypes TYPE >
    struct spi
    {
    	TypeFor<TYPE>*	p_tx = nullptr;
    	
    	constexpr spi() {};
    };
    
    int main()
    {
    	spi<AllowedTypes::Int8> spi1;
    	std::cout << typeid( *spi1.p_tx ).name() << " " << sizeof( *spi1.p_tx ) << std::endl;
    	
    	spi<AllowedTypes::Uint16> spi2;
    	std::cout << typeid( *spi2.p_tx ).name() << " " << sizeof( *spi2.p_tx ) << std::endl;
    	
    	return 0;
    }

    cpp.sh/85p6
    Ответ написан
    2 комментария
  • Как совместить 2 языка С++ и C#?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    2. Писать на С++/CLI. Только тут я не понимаю, как их совместить.
    Ну написал я код на С++/CLI, а как мне его в проект , написанный на С# вставить ?


    Этот вариант неплохо разжеван в этих двух вопросах.
    Можно ли совместить в одном проекте программы написанные на разных языках (C++, C#) в VS2012?
    Как динамически подключать библиотеки?

    Этот вариант на деле оказался самым удачным, потому что деление проекта на три части (C++; CLI; C#) так же строго делит и области ответственности. На стороне C# остается только интерфейс, на стороне CLI - только связь с низким уровнем, на стороне C++ - только низкий уровень.
    Целостность кода сохраняется, библиотеку низкого уровня можно с равной легкостью подключить как к библиотеке CLI, так и к другому низкоуровневому проекту.
    Ответ написан
  • Как компилировать C++, JAVA?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для примера можно посмотреть вот этот сайтик: https://gcc.godbolt.org/
    Он доступен в открытом исходном коде: https://github.com/mattgodbolt/compiler-explorer

    Еще можно посмотреть вот эту репку: https://github.com/shawon100/Online-Compiler
    На гитхабе таких штук очень много. Если пороешься, скорее всего найдешь уже готовое для себя.
    Ответ написан
    Комментировать
  • Как динамически подключать библиотеки?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    lightalex , да, описанное тобой поведение возможно.
    У меня есть очень старый проект одной утилиты. Там практически реализовано необходимое тебе.

    Вот самая основа, которая может тебя заинтересовать.
    https://github.com/FrankStain/tex-conv/blob/master...
    Это - функция обнаружения и загрузки всех плагинов в определенной директории.

    https://github.com/FrankStain/tex-conv/blob/master...
    Это - функция выгрузки плагинов.
    Ответ написан
    4 комментария
  • Как передать указатель на метод экземпляра класса, как параметр конструктора?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Павел Каптур , а правильно будет - использовать примитивы проектирования. В частности - декоратор или просто интерфейс.

    А вот по поводу std::function и std::bind смотри пример - cpp.sh/56bak . У тебя плейсхолдера нехватает.
    Ответ написан
    Комментировать
  • Какая разница между ClassName::m_Field и this->m_Field в нестатическом методе?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Денис , есть такой термин, как Квалификация идентификатора. Соответственно, идентификатор может быть как полностью квалифицированным (fully qualified), так и частично (partially qualified). Обычно большая часть квалификации может быть автоматически подставлена во время трансляции кода. Однако, в целом ряде случаев нужно руками расширять квалификацию.

    Возьмем такой пример:
    spoiler
    class Foo
    {
    public:
    	void baz1( int bar )
    	{
    		this->bar = bar;
    	}
    	
    	void baz2( int bar )
    	{
    		Foo::bar = bar;
    	}
    	
    	void baz3( int bar )
    	{
    		this->Foo::bar = bar;
    	}
    
    public:
    	int bar = 0;
    };


    Имя параметра функций соответствует имени поля класса. Это плохой стиль, но для примера - самое то. Частично квалифицированное имя "bar" в любой из функций будет ссылаться на как можно более локальные данные. В данном случае - именно на параметр, а не на поле класса. Теперь, чтобы сказать "поле класса bar" нам надо сделать квалификацию имени "bar" более полной. Самая полная квалификация используется в функции "baz3".

    Любое имя может быть заменено на его полную квалификацию, включая и операторы. Можно написать "foo.operator->()" вместо "foo->", если класс "foo" имеет перегруженный оператор "->". Я не представляю зачем это нужно, но так тоже можно написать.

    Можно обратиться к перекрытой или виртуальной функции родительского класса.

    К слову, полная квалификация очень редко когда действительно нужна. Например, вот в этом шаблоне без расширения частичной квалификации поля "m_data" использовать его в наследнике не получится. А вот в этом шаблоне без полной квалификации имени не получится взять указатель на статическую функцию шаблонного класса.
    Ответ написан
    Комментировать
  • Что значит шаблон friend-функции внутри структуры?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    По всем этим вопросам к прочтению предлагаю следующую книгу: Дэвид Вандевурд, Николаи М. Джосаттис: Шаблоны C++....

    Сходу можно сказать что объявление дружественности оператора лишнее - в заблуждение вводит, преимуществ не дает. Внешняя перегрузка конкретно этого оператора в дружественности не нуждается.
    Проверка тут: cpp.sh/73htm

    По поводу конструкций вида "template< typename >" у Вандервуда написано так: "Поскольку шаблонный параметр параметра шаблона далее не используется, его имя можно опустить".
    Это равнозначно объявлению функции вида "void foo( int32_t );". Неиспользуемые идентификаторы можно не писать, но это снижает культуру кода.

    template <class > friend std::ostream& operator<<(std::ostream& s, Vec2<t>& v);

    Параметр опущен потому что не используется. Вместо него используется шаблонный параметр "t" из "Vec2".
    Это нужно для того, чтобы для любого конкретного "t" у "Vec2" был всего один дружественный оператор - инстанцированный от все того же "t".
    Ответ написан
    Комментировать
  • Как реализовать кроссплатформенное ПО с библиотеками для Windows/Linux?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Я с 2011 года занимаюсь совершенствованием игрового фреймворка. Весь его код написан на C++. Целевых платформ раньше было 5 (Win, MacOs, iOs, Android, Bada), теперь 4 (Bada закрылась же) с приглядом за Tizen, WinMo и, когда-нибудь, консолями.
    В общем, уровень требований к кроссплатформенности должен быть понятен. И вот как я этого добился.

    Большая часть кода написана на платформонезависимом C++. Весь платформозависимый код расщеплен на три слоя:
    - Нижний слой, общий интерфейс для всех платформ, общие поля всех платформ.
    - Средний слой, platform-specific решения и поля. Наследуется от нижнего.
    - Верхний слой, ввод platform-spcific кода во фреймворк. Наследуется от среднего слоя.

    Условная компиляция применяется только для включения заголовка платформозависимого кода. Никаких макросов, никакой условной компиляции больше не допускается. В платформозависимом коде все пишется открытым кодом так, как будто пишется под одну платформу.

    На уровне файлов этот подход работает так.
    Есть в заголовках проекта папка "platform", где собраны нижние уровни расщепления, мастер-заголовок с условным подключением среднего уровня расщепления и все общие типы для платформ.
    Так же в проекте есть папки "platform.windows", "platform.macos", "platform.###", в которых реализован средний уровень расщепления и мастер-заголовки для условного подключения.
    Верхний уровень или реализуется в своей папке, если он представляет собой целую подсистему, или описывается во все той же папке "platform".
    Исходный код сгруппирован так же, но включает в себя только мастер-заголовок .

    Сценарии сборки на каждую из платформ включают в себя платформозависимый код только своей платформы.
    Все собирается в статические библиотеки и линкуется в один исполняемый файл. Хотя есть возможность вытеснения библиотек в динамические модули (сделано на случай передачи фреймворка аутсорсерам).

    Это все дает полную прозрачность исполнения кода для любой платформы. Так же этот подход делает очень легкой экспансию всего фреймворка на новую платформу.

    UPD:
    Пример с файлом очень хорошо подходит благодаря своей простоте, его я даже по памяти могу выписать из своего фреймворка, но я кое-что все таки упрощу, чтобы никого не смущать и не пугать.
    spoiler
    // PlatformSpecificFile.Windows.h
    class PlatformSpecificFile
    {
    // Platform-specific interface.
    public:
    	inline ::HANDLE GetHandle() const	{ return m_handle; };
    	
    // Platform-independent interface, but platform-dependent implementation.
    public:
    	// RAII.
    	PlatformSpecificFile() = delete;
    	PlatformSpecificFile(
    		const std::string& path,
    		const OpeningMode desired_mode,
    		const AccessOptions& desired_access,
    		const SharingOptions& desired_sharing
    	);
    	
    	virtual ~PlatformSpecificFile();
    	
    	void Close();
    	void Flush();
    	
    	const size64_t GetSize() const;
    	const bool Resize( const size64_t new_size );
    	
    	const size32_t Read( NotNull<uint8_t> buffer, const size32_t buffer_size ) const;
    	const size32_t Write( NotNull<const uint8_t> buffer, const size32_t buffer_size );
    	
    	const size64_t Seek( const size64_t offset, const SeekOrientation orientation );
    	
    	inline const bool IsValid() const	{ return IsHandleValid( m_handle ); };
    	
    private:
    	::HANDLE	m_handle = INVALID_HANDLE_VALUE;
    };
    
    // PlatformSpecificFile.Android.h
    class PlatformSpecificFile
    {
    // Platform-specific interface.
    public:
    	inline int GetHandle() const	{ return m_handle; };
    	
    // Platform-independent interface, but platform-dependent implementation.
    public:
    	// RAII.
    	PlatformSpecificFile() = delete;
    	PlatformSpecificFile(
    		const std::string& path,
    		const OpeningMode desired_mode,
    		const AccessOptions& desired_access,
    		const SharingOptions& desired_sharing
    	);
    	
    	virtual ~PlatformSpecificFile();
    	
    	void Close();
    	void Flush();
    	
    	const size64_t GetSize() const;
    	const bool Resize( const size64_t new_size );
    	
    	const size32_t Read( NotNull<uint8_t> buffer, const size32_t buffer_size ) const;
    	const size32_t Write( NotNull<const uint8_t> buffer, const size32_t buffer_size );
    	
    	const size64_t Seek( const size64_t offset, const SeekOrientation orientation );
    	
    	inline const bool IsValid() const	{ return m_handle >= 0; };
    	
    private:
    	int		m_handle = -1;
    };
    
    // File.h
    class File final : public PlatformSpecificFile
    {
    public:
    	using PlatformSpecificFile::PlatformSpecificFile;
    	
    	const size64_t GetPosition() const; // Seek( 0, SeekOrientation::FromPosition );
    	
    	const bool SetPosition( const size64_t position ); // Seek( position, SeekOrientation::FromBeginning );
    	
    	const bool IsFileEnded() const; // GetPosition() == getSize();
    };


    Мастер-заголовок платформенного кода "platform.h" в зависимости от сценария сборки включает в себя один из мастер-заголовков платформозависимого кода "platform.###.h". Платформозависимый код уже включает в себя соответствующий заголовок файла "PlatformSpecificFile.###.h"
    Ответ написан
  • Можно ли совместить в одном проекте программы написанные на разных языках (C++, C#) в VS2012?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    https://github.com/FrankStain/tex-conv
    Это пример такого проекта. Многозадачный конвертер текстур.

    На верхнем уровне лежит C# приложение с UI на WinForms, вся работа с пользователем выполнена именно тут.
    К верхнему уровню подключается C++/CLI ядро, представляющее собой библиотеку классов. Это ядро связывает верхний уровень с нижним, который представлен потенциально несчетным набором плагинов.

    Библиотека классов в C# используется как будто это рядовой модуль.

    Конкретно для твоего случая я порекомендую поступить так.
    • Нижний уровень - проект статической библиотеки, в которой будет весь твой C++ код.
    • Средний уровень - C++/CLI библиотека классов, в которую и линкуется нижний уровень, свяжет нижний уровень с верхним через свои классы.
    • Высокий уровень C# приложение, использующее классы среднего уровня.
    Ответ написан
    Комментировать
  • Что такое анимация и с чем ее кушать если OpenGL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Spine C runtime.
    https://github.com/EsotericSoftware/spine-runtimes...
    Это не серебряная пуля и не идеальные практики. Пожалуй, в плане кода это пример наоборот, как код писать точно не надо.
    Но вот в плане реализации анимаций эта репка тебе очень поможет.

    Суть такова. Есть модель, она статична. Есть отрезок времени (таймлайн), на этом отрезке есть точки - ключевые кадры. Ключевой кадр несет в себе информацию о том, какую часть модели и как сместить. Чем проще инструкция в ключевом кадре, тем удобнее. Масштаб, поворот и смещение многие любят разделять по разным таймлайнам.
    OGL в этом деле не нужен, до поры.

    С помощью SRT таймлайнов можно анимировать объекты в пространстве целиком, но если тебе захочется точно так же анимировать части меша модели, то впереди тебя будут ждать трудности.
    Анимацию меша лучше реализовать на основе скелета. Это дело в двух словах уже не описать, тут лучше читать статьи.
    www.gamedev.ru/code/terms/SkeletalAnim
    www.gamedev.ru/code/articles/skeletal_animation
    https://habrahabr.ru/post/219509/
    https://habrahabr.ru/post/304042/
    www.gamedev.ru/code/articles/?id=4182

    Самое зерно скелетной анимации в том, что модель остается моделью, анимируются только кости скелета. И именно анимация кости приводит к перемещению фрагмента меша.

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

    В общем, как то так анимация выглядит. Анимируем в цикле обновления, вершины трансформируем в момент формирования кадра. OGL нам нужен, собственно, чтобы нарисовать раскоряченый анимацией меш.
    OGL для непосредственного анимирования мешей тебе понадобится только тогда, когда ты достаточно глубоко нырнешь в анимации, в тот же момент ты уже полностью поймешь что от OGL понадобится для этого.
    Ответ написан
    2 комментария
  • Ошибка с кодировкой python, что не так?

    @MarkusD
    все время мелю чепуху :)
    В крайне укороченном виде твоя проблема выглядит так:
    >>> s = 'привет'
    >>> b = s.encode( 'utf-8' )
    >>> t = b.decode( 'ascii' )
    # Traceback (most recent call last):
    #   File "<stdin>", line 1, in <module>
    # UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 0: ordinal not in range(128)


    Дело в том, что консоль, в которой ты запускаешь скрипт, имеет кодировку ASCII.
    Смени кодировку консоли на UTF-8, это лучший из вариантов. Но еще можно уговорить бота не писать логи в консоль.
    Вариант номер три: использовать питон 3.6, если можно. :)
    Ответ написан
  • Как реализовать наследование статического поля/метода, если это возможно?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    CodeInside , в твоем вопросе тебе поможет такой примитив проектирования, как Абстрактная Фабрика.

    Простая реализация на шаблоне и C++11 выглядит примерно так.
    spoiler
    template< typename TInterface, typename... TArguments >
    class AbstractFactory final
    {
    public:
    	// Produce the implementation, but return the pointer to interface.
    	inline std::shared_ptr<TInterface> Produce( const std::string& implementation_name, TArguments... arguments )
    	{
    		auto found_function = m_factory_functions.find( implementation_name );
    		return ( found_function == m_factory_functions.end() )? std::shared_ptr<TInterface>{} : found_function->second( std::forward<TArguments>( arguments )... );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation()
    	{
    		return DefineImplementation<TImplementation>( TImplementation::ClassName() );
    	};
    	
    	// Define the implementation.
    	template< typename TImplementation >
    	inline const bool DefineImplementation( const std::string& implementation_name )
    	{
    		// Abort the incorrect registration.
    		static_assert( std::is_base_of<TInterface, TImplementation>::value, "Implementation may only be derived from interface of Factory." );
    		
    		auto found_function = m_factory_functions.find( implementation_name );
    		if( found_function == m_factory_functions.end() )
    		{
    			m_factory_functions[ implementation_name ] = &AbstractFactory<TInterface, TArguments...>::template ConstructImplementation<TImplementation>;
    			return true;
    		};
    		
    		return false;
    	};
    	
    	// Check the implementation name is already defined.
    	inline const bool IsImplementationDefined( const std::string& implementation_name ) const
    	{
    		return m_factory_functions.find( implementation_name ) != m_factory_functions.end();
    	};
    	
    private:
    	// The factory function just produce implementation.
    	template< typename TImplementation >
    	static std::shared_ptr<TInterface> ConstructImplementation( TArguments... arguments )
    	{
    		return std::static_pointer_cast<TInterface>(
    			std::make_shared<TImplementation>( std::forward<TArguments>( arguments )... )
    		);
    	};
    
    private:
    	// Factory function produces the implementations of TInterface.
    	using FactoryFunction	= std::shared_ptr<TInterface> (*)( TArguments... arguments );
    	
    	std::unordered_map<std::string, FactoryFunction>	m_factory_functions;
    };


    Работает она примерно так:
    cpp.sh/93obm
    Ответ написан
    Комментировать
  • Есть ли компилятор для Андроид в бинарный код?

    @MarkusD
    все время мелю чепуху :)
    Как я понял Java компилируются в байт-код, который потом кушает JVM.

    Немного не так. В основе процесса приложения лежит не JVM, а Dalvik. Ни на что не смотря, резкие отличия от JVM есть.
    Только Dalvik уже отжил свое, английская статья в вики повествует о так называемом Android Runtime (ART), который присутствует в Android начиная с версии 4.4, а с 5.0 и вовсе вытеснил Dalvik. Подробнее о принципах работы ART можно почитать снова в английской вики. Только не стоит думать что ELF может содержать исключительно и только инструкции для реальных процессоров. ELF - это просто контейнер исполняемого кода.

    Теперь обратимся к коду.

    https://android.googlesource.com/platform/art/+/ma...
    Это код утилиты dex2oat, которая и производит преобразование из DEX в ELF.

    https://android.googlesource.com/platform/art/+/ma...
    Вот тут есть некоторые пояснения самих данных и принципов их хранения в ELF формате.

    Это всё не говорит о том, что после обработки DEX в ELF будут храниться исключительно инструкции реального процессора. Это все говорит только о том, что в ELF формате хранится уже подготовленный и оптимизированный код, не требующий докомпиляции и оптимизации во время своего запуска.

    Так и зачем смущаться этого всего? Разницы мало, для виртуального там процессора инструкции или для реального. Все уже оптимально собрано.

    Идем дальше.

    https://android.googlesource.com/platform/art/+/ma...
    Это описание главной функции процесса приложения.

    https://android.googlesource.com/platform/art/+/ma...
    Вот тут Dalvik создается безусловно, т.е. от него никуда не деться. Вот точное место.

    Далее, касательно пресловутой Native activity.

    https://github.com/android/platform_frameworks_bas...
    Это исходный код NativeActivity, который зашит внутри стандартной библиотеки Android.

    https://github.com/android/platform_frameworks_bas...
    Чтобы NativeActivity смогла загрузить свой низкий уровень, в AndroidManifest.xml нужно описать мету вот с этим именем и именем .so файла в качестве значения.

    https://github.com/android/platform_frameworks_bas...
    Вот тут производится загрузка низкого уровня. Как видно, это делается из метода onCreate самой NativeActivity. То есть, это самое начало жизненного цикла Activity.

    spoiler
    У NativeActivity есть много очень неприятных ограничений и очень неудобный интерфейс. Из за этого я еще в 2011 году для используемого в производстве игр движка был вынужден разработать альтернативу NativeActivity и некоторую обертку над JNI для увеличения удобства разработки.

    Иными словами, непосредственно для Android нельзя создать пользовательское приложение без задействования кода высокого уровня.

    NDK что-то дает, но далеко не все, буквально мелочи. Как только тебе понадобится обратиться к телефонной книжке, узнать громкость звука уведомлений или состояние беззвучности, как-либо опросить окружение устройства, тебе придется писать код на Java.
    NDK не является инструментом разработки приложений. NDK - это средство реализации только критичных к производительности участков программы.

    Хочу начать разрабатывать моб. приложения, кроссплатформерность не интересует. Гонюсь за быстродействием.

    Не спеши гнаться за быстродействием, по всей видимости тебе и C++, как инструмент разработки, не нужен, т.к. вообще не ясно для чего и зачем ты решил его использовать.
    Инструменты выбирают исходя из целей. Цели пока не обозначены. Быстродействие не может быть целью, т.к. это только условие достижения лишь некоторых целей.
    Во многих случаях в мобильной разработке максимальное быстродействие дает именно использование основного предлагаемого языка программирования. Лично я предлагаю тебе сперва уделить 5-6 месяцев (но лучше год и более) ежедневному чтению документаций, книг и исходного кода как открытых проектов, так и репозиториев разработчиков мобильных платформ.

    В качестве эпилога.
    https://developer.android.com/ndk/guides/index.html
    The NDK may not be appropriate for most novice Android programmers who need to use only Java code and framework APIs to develop their apps.
    Ответ написан
    Комментировать