• Как правильно передать в метод массив и получить обратно массив другого размера?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тебе не нужны new и сырые указатели. Тебе нужен линейный контейнер с хранением элементов в непрерывном блоке памяти. Это будет или std::vector, или std::array.

    Для начала можно остановиться на векторе. Еще одним важным типом будет std::span[?] или gsl::span[?][S] если ты не можешь пользоваться C++20.

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

    Вот объвление твоей функции: gsl::span<float> Метод( gsl::span<float> values ).
    span - это не владеющий памятью тип, обозначающий участок непрерывной памяти с данными определенного типа. span очень легок и является value type - т.е. создан чтобы его передача по значению не приводила к ощутимым нагрузкам. span конструируется из std::vector, std::array, плоских массивов, std::unique_ptr<[]> и сырых блоков памяти.

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

    Псевдокод
    std::span<float> Метод( std::span<float> values )
    {
    	size_t processed_count = 0;
    	for( float& element : values )
    	{
    		// ... обработка значений
    		// ... изменение processed_count
    		// ... условия обрыва цикла
    	}
    	
    	return values.subspan( 0, processed_count );
    }
    
    int main()
    {
    	std::vector<float> values{ 7.83f, 14.1f, 20.3f };
    	std::span<float> processed_values = Метод( values );
    	
    	for( const float& value : processed_values )
    	{
    		std::cout << value << ' ';
    	}
    	
    	return 0;
    }
    Ответ написан
    2 комментария
  • Как влияет перекрыетие объектов на скорость отрисовки?

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

    Здесь стоит припомнить некоторые базовые этапы графического конвейера.

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

    Вторым важным сейчас этапом является обработка фрагментных данных. Растеризация, если проще.
    Для растеризации каждый примитив бьется на фрагменты, обработка которых происходит в индивидуальном порядке. На этапе растеризации от обработки фрагмента уже можно отказаться прямо в шейдере. Это может немного ускорить презентацию кадра.
    Обработка фрагментов разной геометрии никак не регламентирована и может происходить в произвольном порядке.
    При этом, если один фрагмент имеет отношение к нескольким участкам геометрии, он будет вычислен из каждого такого участка. Это называется Overdraw (Перекрытие).
    Перекрытие не только ведет к обработке ненужных, часто не видимых, фрагментов, но и приводит к графическим артефактам, вроде z-fighting. Разработчики стараются не допускать перекрытия геометрии через более агрессивное отсечение полигонов в модели и сортировку геометрии, а не моделей, в пространстве. Так же есть ряд эффективных техник работы с буфером глубины (например), которые позволяют практически полностью справиться перекрытием.

    Поэтому, при правильной настройке контекста, при выборе только важной для кадра геометрии, при работе с буфером глубины, при использовании Adaptive Shading и вовремя отбрасывая фрагменты из стадии растеризации скорость презентации будет выше чем без всех этих настроек.
    Если ты сделаешь у себя z-prepass, то сортировка геометрии по расстоянию до камеры для тебя станет избыточной.
    Ответ написан
    Комментировать
  • Какие требования предъявляются разработчику на с++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    $1.5k/mth - это довольно серьезная оплата для специалиста. Такую з/п начислят линейному разработчику (Middle Engineer). В области работы с C++ все очень индивидуально и требования сильно пляшут даже не между компаниями, а между командами и проектами внутри компаний. Однако, средний набор требований выделить все-таки можно.

    За эти деньги от тебя потребуется глубокое знание стандарта C++ и способность быстро восполнить любой пробел в своей компетентности. Тебя допустят к ревью младших сотрудников, где от тебя будет требоваться, в первую очередь, отлов глубоких нарушений стандарта. Например, таких как нарушение алиасинга, нарушение передачи ресурса, нарушение времени жизни, потенциальные утечки ресурсов и памяти, потенциальное или явное неопределенное поведение. Также от тебя потребуется детальное понимание различий между версиями стандарта.
    От твоей экспертизы в стандарте будет зависеть скорость реализации функционала. Не только от одного тебя тебя, но и от тебя тоже.
    Это - твой Hard skill requirement. Алгоритмическая база и экспертиза в математике тоже имеют свой вес в профессиональных навыках. Но без глубокого знания стандарта, будь у тебя хоть Нобелевка по математике, толку на роли m.e. с тебя будет мало.

    И это - всего одна треть требований к тебе на такой позиции.

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

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

    Не стоит сегодня надеяться на то, что лишь за способность писать код тебе будут платить больше $800/mth. Хотя, конечно же, на нашем рынке труда существуют и на такое готовые предприятия.
    Ответ написан
    Комментировать
  • Не могу запустить код в visual studio почему?

    @MarkusD
    все время мелю чепуху :)
    long long matr[1001][1001] - это будет 8016008 байт, 7.645МБ.
    Стандартный размер стека в MS Visual Studio задан в 1МБ. Естественно, при объявлении настолько большого массива ты сразу получишь Stack Overflow.

    Выходов из данной ситуации несколько.
    Выход первый - подойти к вопросу рассудительно. Тебе точно нужен именно статический массив в 8МБ именно на стеке? Я думаю что нет. Я думаю что тебе нужен std::vector, в котором ты сможешь легко разместить все 1002001 элементов. На самом деле и двумерность массива тебе тоже не очень нужна, т.к. на самом деле она тебя сейчас только запутывает. Через простую функцию от двух аргументов можно легко перейти от двух индексов к индексу в линейном массиве.

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

    Выход третий, которым я советую не пользоваться без однозначного понимания своих действий.
    Можно изменить размер стека через настройки линкера.
    В свойствах проекта: Configuration Properties -> Linker -> System:
    Stack Reserve Size - значение в байтах, это максимальный размер стека для потока. Его можно изменить хоть до 32МБ и больше.

    Подвох с этим значением в том, что потоков у твоего приложения не один даже если само твое приложение является однопоточным. Вместе с твоим главным потоком работает еще несколько служебных. Их стек тоже будет расширен. Это все приводит к увеличению потребления памяти.
    Обычно размер стека по умолчанию не трогают или сжимают до 64КБ, т.к. большинству потоков этого более чем достаточно. А вот для требовательных потоков, обычно, отдельно расширяют стек до требуемых размеров в момент создания потока.
    Таким образом достигается контроль памяти. Даже сегодня бывают случаи, когда ее бывает мало.
    Ответ написан
    Комментировать
  • Что не так с работой unordered_map?

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

    Если в шаблоне алокатора не описано правило смены аллоцируемого типа (A::template rebind<U>::other), по умолчанию при смене аллоцируемого типа будет заменен первый шаблонный параметр. Был у нас Alloc<Foo, ...>, станет Alloc<Bar, ...>.
    Это означает что для правильной передачи состояния аллокатора нужно предусмотреть конструктор преобразования из аллокатора от твоего шаблона, но с другим первым аргументом.

    Еще можно использовать Polymorphic Allocator, но для этого потребуется сменить стандарт на C++17.
    Эту стратегию не так просто описать, поэтому я прибегну к ссылкам на доклады по этой теме.

    CppCon 2017: Bob Steagall “How to Write a Custom A...
    051. Modern C++ Allocators – Руслан Арутюнян (Intel)
    Taming dynamic memory - An introduction to custom ...
    C++Now 2018: David Sankel “C++17's std::pmr Comes ...
    Это, конечно, далеко не всё на данную тему. Но цели выписывать все у меня и нет. Я привел ссылки, которым доверяю в плане чистоты информации.
    Советую просто пройтись по хабру и ytube поиском докладов и статей.

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

    Но если ты работаешь со стандартом до C++11, то у тебя аллокатор вообще не может иметь состояние.
    All custom allocators also must be stateless. (until C++11)
    Ответ написан
    Комментировать
  • Большие шаблонные классы( реализуя CRTP). Все пихать в один .h файл?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Сегодня с помощью шаблонов делается очень много предварительных вычислений на этапе компиляции, чтобы не делать эти вычисления вручную и не проводить их на этапе исполнения. Не всегда такие шаблоны могут похвастаться компактностью кода.
    Прежде всего, шаблоны служат для того чтобы автоматизировать генерацию кода. Не всегда требующий генерации код будет именно компактным или простым. Любой умный указатель или стандартный контейнер легко выбьется за пределы обозначенных 400 строк лишь только своим телом. А если учесть еще и команду поддержки такого стандартного шаблона, то там счет пойдет уже на тысячи строк.

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

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

    При разумном ограничении сложности инстанцирования на факт инстанцирования для каждого модуля трансляции можно даже не смотреть.
    А еще, между делом, это означает что код лучше генерировать не макросами, а шаблонами по мере возможности.

    По поводу организации кода.
    Код шаблонов точно так же можно организовать в наборы файлов. Для шаблонов точно так же разрешена и дружественность, и предварительное объявление, и определение не по месту объявления.
    Реализацию шаблонов можно развести между заголовками (.h-файлами) и файлами встраиваемых реализаций (.inl-файлами).
    При этом важным остается правило доступности шаблона из места его инстанцирования. Т.е. вся группа файлов с реализацией шаблона должна быть как-либо связана с заголовком объявления шаблона, который далее планируется предоставлять пользователю шаблона.
    Ответ написан
    2 комментария
  • Как ускорить этот код(клеточный автомат)?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В общем смысле, как я вижу по твоему коду, ты вляпался в True Sharing, попутно обмазавшись Cache Misses и окончательно убив свою производительность с помощью неоправданно огромного размера клеток.

    8Б на клетку, состояние которой может поместиться в 1Б, это действительно огромный размер.
    enum CellState : uint8_t уменьшит размер состояния с 4Б до 1Б. А еще этот тип стоит переименовать, т.к. это не CellState, а что-то относящееся к поведению клетки. А вот CellState будет выглядеть так:
    // Renamed from `CellState`.
    enum CellBehavior : uint8_t
    {
        Empty,
        Alive,
    };
    
    struct CellState final
    {
    	CellBehavior	current_behavior : 4;
    	CellBehavior	next_behavior : 4;
    };

    Это позволяет уменьшить размер клетки до 1 байта.

    Данные оперативной памяти процессор подтягивает к себе во внутренний кэш. Кэшей у процессора много и все они связаны. Кэш процессора поделен на линии, работа с которыми синхронизируется между ядрами процессора. Вот именно тут появляется два термина: False cacheline sharing и True cacheline sharing. Если "False", то обрабатываемые разными ядрами данные разделены в разные кэш-линии. Когда "True" - требуемые разным ядрам данные находятся в одной кэш-линии и привет синхронизация. А это ой как медленно.

    В каждом процессоре сегодня сидит гадалка, которая предсказывает какие тебе надо подтянуть данные из RAM в CPU Cache. Выборка из RAM - это довольно долгая процедура, поэтому нужна гадалка чтобы предсказать что судьбой твоего алгоритма предначертано выбрать на следующем этапе. Бывает что гадалка ошибается и тогда твой лагоритм встает в синхронизацию до завершения нужной выборки из памяти. А это - еще медленнее чем синхронизация по кэш-линиям. Это называется промахом по кэшу - cache miss.
    К счастью, это не гадалка виновата в своей ошибке, а ты просто неправильно написал лагоритм. Вот чтобы из лагоритма сделать алгоритм, следует озаботиться чтобы он был более лоялен к гадалке и кэшу процессора.

    Докину еще немного полезной информации.
    Сходи к Адаму Мартину и к Unity, посмотри на парадигму ES/ESP/ECS. Изучи DOD. Попробуй реорганизацию из твоего текущего потока сущностей с полями в потоки полей сущностей. Переделай батчинг обработки клеток так, чтобы данные не синхронизировались между ядрами процессора.
    Возможно тебе еще поможет понимание подхода Out of line, т.к. там хорошо объясняется почему очень большие объекты при их поточной обработке - это не очень дружественно кэшу процессора.
    Еще сюда можно добавить информацию о автоматической векторизации. Это позволит задействовать SIMD инструкции для твоего кода. DOD очень элегантно ложится для обработки твоих клеток SIMD командами.

    Я тут крайне сумбурно накидал, только чтобы дать тебе направления. Кое-чего я даже не написал, но ты обязательно зацепишь все неописанное когда будешь изучать то, что я описал. Думаю, ты уже видишь, в какой объем выльется весь этот материал, если писать его в удобном понятном формате и раскрывая каждую тему.
    Ответ написан
    Комментировать
  • Какой используется тип возвращаемого значения в операторе преобразования?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Это называется оператором преобразования (conversion operator).
    Именем такого оператора должен быть явно определенный полный тип, определяющий тип возвращаемого значения оператора.
    Соответственно, тип возвращаемого значения опускается (его нельзя указать) т.к. избыточен и будет дублировать имя оператора.
    Ответ написан
    Комментировать
  • Повреждение стека вокруг переменной - как побороть?

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

    В результате подмены сигнатуры с BOOL (__stdcall *)(HANDLE, PBOOL) на bool (__stdcall *)(void*, bool*) компилятор думает об одном размере стека, а код функции - о других.

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

    Однако, конкретно тут проблема у нас не в несовпадении типов, потому что.
    __stdcall свой результат передает через регистр. регистр используется целочисленный или вещественный. Для целочисленного регистра используется правило продвижения типа. Это означает, что функция, записав значение типа BOOL (размер 4Б) ничего не испортит пользовательскому коду, который прочитает из регистра все 4Б с учетом правила продвижения.

    Реальная проблема кроется в том, что указатель на однобайтовое целое (bool*) передается в использование как указатель на четырехбайтовое целое (BOOL* или PBOOL). Вызываемая функция ведь имеет сигнатуру BOOL (__stdcall *)(HANDLE, PBOOL) и со вторым параметром работает как с 4Б целым по указателю.
    Именно это и приводит к порче стека и тебе, автор, сильно повезло что ты запускаешься в отладке, где каждое значение на стеке обрамлено заборчиком, за сохранностью которого всегда приглядывает специальный сервисный код между обращениями к подпрограммам.

    Именно такой заборчик рядом с isWow64 и был поломан в результате вызова IsWow64Process с параметром неподходящей длины. Измени тип isWow64 на BOOL и все станет нормально, даже хендл "kernel32.dll" потом сможешь нормально освободить.
    Ответ написан
    5 комментариев
  • Как возможно задать параметры, а именно текстуру отдельно для каждого треугольника?

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

    Развернуто:

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

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

    Под материалом понимается комплекс из программы и ресурсов, которые отображаются на геометрии.
    Материал отображает на геометрию заданный своими свойствами набор текстур и параметров. С точки зрения геометрии, материал - это DIP (Draw Indexed Primitive - твой glDrawElements). С точки зрения шейдера, материал - это комплекс из программы шейдера и набора входных/выходных параметров этой программы.
    Один материал одной геометрии - это один DIP с установкой набора текстур и параметров материала, а так же - с установкой нужного буфера индексов, вершин и инстансов.

    Ты хочешь чтобы Один набор треугольников твоей плоскости имел одну текстуру, а другой - другую. Вот и организуй два материала, у которых одна шейдерная программа, но две разные текстуры. Далее тебе надо ввести больше вершин, т.к. с вводом материала и текстурных координат, с учетом твоих требований, твои 4 вершины больше не могут обеспечить свою уникальность. В тех позициях плоскости, где два смежных треугольника отображают разные текстуры, тебе требуется ввести по две вершины вместо одной.
    Далее - организуй индексный буфер так, чтобы примитивы в нем описывались непрерывной последовательностью для каждого материала отдельно. Т.е. сперва все треугольники с одним материалом, потом все треугольники - с другим.
    Под конец определи инструкции для своей геометрии, сколько DIP-ов согласно твоим требованиям должно быть выполнено, для каких материалов и на какой геометрии.

    Таким образом ты реализуешь очень простую систему поддержки материалов и выполнишь свою задачу.
    Ответ написан
    Комментировать
  • Как правильно использовать anonymous namespace?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Если хочется полностью исключить возможность использования приватных выражений, то использовать стоит не пространства имен, а класс с вложенными типами под соответствующими секциями.
    class a final
    {
    private:
    	class ex
    	{
    		int f = 0;
    	};
    	
    	static void foo() { std::cout << 'f' << std::endl; };
    	
    public:
    	class F
    	{
    	public:
    		F() { foo(); };
    		
    	private:
    		ex m_exes[4];
    	};
    };
    Ответ написан
    3 комментария
  • Несовместимые типы операндов?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    state ? (cout << b / a << endl, exit(0)) : state = (a == 0) && (b == 0);

    Не стоит так писать. Мало того что это выражение не укладывается в синтаксис, так еще оно из себя представляет слепую зону, код в которой невидим. Никому в голову не придет читать exit(0) или конструкцию присвоения внутри тернарного оператора.

    По синтаксису: тернарный оператор предполагает гарантированную выдачу одного из результатов. При этом, на выражения между : накладываются некоторые ограничения. Если выражения не проходят по ограничениям тернарного оператора, выражение тернарного оператора считается плохо сформированным, что и приводит к ошибке синтаксиса.
    Ответ написан
    Комментировать
  • Как нарисовать такую звезду openGL?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Фигура состоит из 10 вершин, соединенных 10 ребрами.
    На первом шаге тебе надо равномерно распределить по единичной окружности 10 вершин. Сделать это нужно в специально отведенном массиве вершин, т.к. индексированные примитивы в этом старом режиме вывести нельзя.
    На втором этапе переключаем контекст на вывод замкнутой линии (GL_LINE_LOOP) и выводим все 10 точек из массива вершин, но со смещением на 3 точки и по модулю 10.

    Псевдокод:
    void display()
    {
       glClear( GL_COLOR_BUFFER_BIT );
       glBegin( GL_LINE_LOOP );
    
       for( size_t index = 0; index < vertices.size(); ++index )
       {
          const Vertex& vertex = vertices[ ( index * 3 ) % vertices.size() ];
          glVertex2i( vertex.x, vertex.y );
       }
    
       glEnd();
       glFlush(); 
    }
    Ответ написан
    1 комментарий
  • Как сделать "переопределение" функции в c++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Твой вопрос можно решить таким способом.
    #include <iostream>
    #include <functional>
    
    class Enemy final
    {
    public:
    	std::function<void()> update = []() {};
    };
    
    int main( int argc, char* argv[] )
    {
    	Enemy enemy;
    	enemy.update = []() { std::cout << "Hello"; };
    
    	enemy.update();
    	return 0;
    }
    Ответ написан
    Комментировать
  • OpenGL и C++. Как лучше отсортировать полигоны?

    @MarkusD
    все время мелю чепуху :)
    Честно говоря, странно видеть в заголовке и тегах вопрос об OpenGL, а в теле вопроса - про коллизии и, возможно, физику.

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

    Тебе надо иметь два набора вершин. Один - набор вершин для визуализации объекта. Второй - набор вершин для симуляции его физики, не только коллизий. И таких наборов может быть много. Для симуляции повреждений и деформаций, для скиннинга, для хитбоксов, для постройки маршрутов перемещений, для звуков...
    BSP подходят больше для отсечения видимой геометрии. В наши дни этот подход не оправдывает своих затрат, его не используют. Для эффективной симуляции коллизий лучше подходят QTree, OTree или KD-списки.

    Почитать начать можно отсюда: Study path for game programmer. Твой раздел: 9. Game Physics and Animation.
    Далее, тебе сюда: https://gamedev.ru/code/articles/?physics
    Да, геймдев все еще жив, хоть уже и не принадлежит Сереге Ваткину.
    Следом лучше пойти на хабр: [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11].
    У Фабьена Сангларда есть набор статей о внутреннем устройстве DOOM 3 и Quake 3, там есть материал и про столкновения.

    На этом этапе у тебя уже должно сформироваться представление о предметной области симуляции коллизий.
    Ответ написан
    Комментировать
  • Как std::initializer_list определяет количество элементов в {списке}?

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

    Foo bar = {...};
    Foo bar{...};


    В обоих случаях выполняется List Initialization, в первом - copy list-initialization, во втором - direct list-initialiaztion.

    Тут важно отметить что принятие решения о использовании std::initializer_list выполняются только на этапе трансляции. В обоих случаях сперва транслятор попробует придумать std::initializer_list. Если у аргументов типы разные (а приведение типов при такой записи не делается), то попробовать создать std::initializer_list у транслятора не получится. Но если получилось, то транслятор уже итак знает число аргументов, переданных в конструктор.

    Образно выражаясь, транслятор прямо перед вызовом конструктора объекта оформляет короткую область видимости, в рамках которой оформляется локальный массив неизменной длины. В этот локальный массив по своему значению складываются аргументы конструктора, далее этот массив обрамляется в std::initializer_list, с которым конструктор и вызывается.
    Сразу по завершении конструктора локальная область видимости закрывается и память массива аргументов конструктора освобождается. Поэтому std::initializer_list нельзя копировать, перемещать, сохранять в состоянии конструируемого объекта. std::initializer_list не владеет отображаемой памятью, он только дает к ней доступ.
    Ответ написан
    Комментировать
  • Game-dev путь. Что мне делать?

    @MarkusD
    все время мелю чепуху :)
    Вот держи: Game developer roadmap и Study path for game programmer.
    Этих двух ссылок тебе лет на 15 усерднейшего запоя хватит. :)
    Подробнее, чем там, больше негде.
    Ответ написан
    Комментировать
  • Проектирование и архитектура приложений?

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

    Поэтому, from classes import MyTelegramm - думаю, не стоит. Мне там и try-catch тоже не нравится. Пусть пользователь блоки try-catch пишет только для себя и только тогда, когда ему это надо. Когда он точно может словить исключение и выкрутиться из ситуации. Про такие исключения тебе, по-хорошему, и знать-то не стоит, т.к. они лишь меняют ветвь исполнения, но не обрывают его.
    Клиентский код стоит выполнять в контролируемой среде, внутри специального защитного блока try-catch, в котором ты уже будешь ловить все доступные тебе исключения и производить их диспетчеризацию.

    Скажем, doit все действия задачи выполняет внутри своего защитного контура. При этом, таких защитных контуров у doit несколько, включая и самый базовый, отвечающий за оповещение о критических сбоях работы. Что мне в doit малость не нравится, так это что обработка исключения делается по месту вместо того, чтобы организовать некоторый диспетчер, в котором это исключение уже и обрабатывать.

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

    Диспетчер начал бы свою работу с того, что завернул бы исключение в конверт (Envelope, [2], [3]). Конверт просто легче читать, чем исключение непредсказуемого типа. Заворачивание я бы сделал с помощью абстрактной фабрики (Abstract factory) конвертов, в которой исключение каждого типа и от каждого источника было бы завернуто в правильный конверт согласно конфигурации фабрики.

    Получив конверт, диспетчер посетил(Visitor) бы с этим конвертом почтовый ящик каждого адресата. Почтовый ящик адресата можно снабдить стратегией (Strategy) приема конвертов, согласно которой почтовый ящик или перепишет конверт к себе, или оставит без внимания, в зависимости от написанных на конверте данных. Управлять почтовыми ящиками может локальный реестр (Registry) диспетчера, в котором так же может быть реализована и функция обхода (Visit) для посещения почтовых ящиков.

    Почтовый ящик обслуживает адресата. Адресат может подписаться (Observer) на поступление письма, а может приходить к ящику за новыми письмами по расписанию и доставать только первые несколько писем, а не все. Как выгребать почтовый ящик - вопрос конфигурации адресата.
    Когда адресат забирает письма, он раздает их в конечные точки, в роли которых уже и будут выступать все эти MyTelegramm или MyLog, но строго сконфигурированные и принимающие уже только копию письма.

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

    Ну и, конечно же, любую часть всего этого безобразия можно отбросить и написать проще. :)
    На начальных этапах можно заглушить стратегии и свести конфигураторы к виду линейной функции.
    MVP тут, на мой взгляд, будет выглядеть как цельная система с гарантированной доставкой конверта с любым исключением во все созданные конечные точки, проводя их от защитного контура, через диспетчер и до адресата через почтовый ящик.
    Ответ написан
  • Что выбрать для освоения DirectX - UWP или Win32 API?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Microsoft UWP является надстройкой для создания универсальных приложений, которые, в теории, должны легко переноситься между различными аппаратными слоями платформы Windows. UWP поддерживается только в Win10, реализована на базе расширения C++/CX стандарта C++17 и выполна преимущественно в объектном стиле.

    Windows API является низкоуровневым слоем взаимодействия между приложением и операционной системой. Win32 API поддерживается всеми версиями Windows, начиная с Windows 95. Однако, разные версии Windows поддерживают разные наборы функций. Поэтому уровень поддержки той или иной функции из набора API всегда следует уточнять в документации. Реализован Win32 API на языке C стандарта C99 и выполнен в процедурном стиле.

    Данная информация создает ряд допущений и ограничений для использования как UWP, так и WinAPI. Исходя из требований проекта и опираясь как на допущения, так и на ограничения, можно вывести однозначное решение о выборе только одной из этих двух технологий.

    C++/CX является расширением стандарта и не поддерживается другими платформами, а так же всеми компиляторами кроме компилятора Microsoft. При этом, существуют такие условия, когда отказаться от использования C++/CX и UWP невозможно. В иных ситуациях следует принимать к сведению особый синтаксис расширения и объектную организацию. Скажем, я бы не стал полностью весь проект делать на базе C++/CX.

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

    В любом случае, выбрав UWP или WinAPI, дальше тебе все равно лучше работать исключительно со стандартным C++ и стандартной реализацией DirctX для C++. От C++/CX стоит избавляться на как можно ранних слоях абстракции палатформозависимого кода. От типов и абстракций WInAPI, равно как и от наследия C99, тоже лучше избавляться как можно раньше и переходить на работу исключительно со стандартным C++.
    Ответ написан
    1 комментарий
  • Как лучше реализовать локализацию?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    s = loc::get("key");    // ambiguous overload for op=

    неоднозначность у тебя появляется потому что оба конструктора std::wstring (конструктор копирования и конструктор преобразования из const wchar_t*) являются неявными.
    Операторы преобразования TaggedCWstr так же являются неявными.

    Эту неоднозначность нужно исключить. Замени, например, оба оператора преобразования на оператор преобразования в std::wstring_view.

    Я бы не стал делать неявный оператор преобразования в std::wstring и этим позволять бесконтрольно обращаться к динамической памяти без явного понимания этого процесса. Лучше для получения std::wstring сделать operator *, а еще лучше - вообще не ломать семантику операторов и сделать метод с говорящим именем.
    Ответ написан
    1 комментарий