Рубрика «c++» - 192

Данная статья — перевод моего туториала, который я изначально писал на английском. Однако этот перевод содержит дополнения и улучшения по сравнению с оригиналом.
Туториал не требует знания Lua, а вот C++ нужно знать на уровне чуть выше базового, но сложного кода здесь нет.

Когда-то я написал статью про использование Lua с C++ с помощью Lua C API. В то время, как написать простой враппер для Lua, поддерживающий простые переменные и функции, не составляет особого труда, написать враппер, который будет поддерживать более сложные вещи (функции, классы, исключения, пространства имён), уже затруднительно.
Врапперов для использования Lua и C++ написано довольно много. С многими из них можно ознакомиться здесь.
Я протестировал многие из них, и больше всего мне понравился LuaBridge. В LuaBridge есть многое: удобный интерфейс, exceptions, namespaces и ещё много всего.
Но начнём по порядку, зачем вообще использовать Lua c С++?
Читать полностью »

Проверяем Oracle VM VirtualBox. Часть 1
Виртуальные машины используются для самых разных нужд. Сам я уже не один год использую VirtualBox для тестирования ПО и просто изучения различных дистрибутивов Linux. Собственно, после длительного использования, периодически сталкиваясь с неопределённым поведением, я решил воспользоваться своим опытом в проверке open-source проектов и проанализировать исходный код Oracle VM Virtual Box.

VirtualBox является кроссплатформенным приложением виртуализации. Что это значит? Во-первых, он работает на компьютерах с процессорами Intel или AMD под управлением операционных систем Windows, Mac, Linux и других. Во-вторых, он расширяет возможности вашего компьютера тем, что позволяет работать множеству операционных систем одновременно (внутри виртуальных машин).

Проект оказался настолько богат проблемными местами, что описывая только те места, в которых ошибка более-менее очевидна, мне придётся разбить материал на две статьи.

В комментариях к статьям часто спрашивают: к чему приводит ошибка в runtime'е? В подавляющем большинстве мы не используем проверяемые проекты и, тем более, не отлаживаем их. На написание этой статьи меня сподвигли проблемы при регулярном использовании VirutalBox. Я решил, что буду оставлять оригинальный, но немного сокращённый комментарий, а если его не было, то добавлю комментарий из шапки файла. Пусть каждый попробует узнать свой глюк.
Читать полностью »

В сегодняшней краткой заметке я опишу тонкий момент связанный с перегрузкой и специализацией функций. Не так давно встретилось на практике и появился повод проапдейтить запись в личной БД на эту тему. Этой информацией и поделюсь.
Читать полностью »

PVS-Studio. Давай поиграем в игру.
Авторы анализатора PVS-Studio предлагают вам проверить свою внимательность.

Анализаторы кода работают без устали и умеют находить множество ошибок, которые сложно заметить. Мы отобрали некоторые фрагменты кода, в которых мы выявили ошибки с помощью PVS-Studio. Все фрагменты взяты из известных Open-Source проектов.

Предлагаем вам посоревноваться с анализаторами в прозорливости и попробовать самостоятельно найти ошибки. Вам будет предложено 15 случайно выбранных заданий. За верный ответ насчитывается одно очко, если он дан в течение 1 минуты. Фрагменты кода короткие, и 1 минута это честное ограничение.
Читать полностью »

Здравствуйте!

Статья предназначена для тех, кто ведет разработку на Torque3D и умеет компилировать движок в VisualStudio, а не только для тех, кто пользуется WorldEditor и пишет/дописывает torqueScript *.cs скрипты.

В Torque3D 3.5 есть небольшой баг с нормалями на кромках террейна (Terrain). Именно он мешает создавать множество террейнов «без швов» (и не только он). Если запустить какую нибудь демку, например, Empty, открыть в ней WorldEditor, создать 2 террейна, подогнать их друг к другу и попробовать «нарисовать кисточкой высоту» в области «склейки» этих террейнов, то мы увидим неприятный баг. Похожий баг можно увидеть, если мы создадим всего лишь 1 террейн и попробуем отредактировать высоту в областях кромок этого террейна.

Что есть террейн?

Террейн — это регулярная сетка ячеек с высотами ( имеющая размеры 256, 512, 1024 — кратная степени двойки), которая легко преобразуется в поверхность (mesh).
У меша есть не только полигоны и вершины, но также и нормали к вершинам, которые как раз дают нужную освещаемость конкретной вершины.

Дело в том, что в движке Torque3D нормаль в точке x,y рассчитывается исходя из разниц высот:
normal( height(x+1, y) — height (x — 1, y), height(x, y+1) — height(x, y-1), 'какая то постоянная высота' ),
где float height( int x, int y ) — функция взятия высоты по целочисленным координатам регулярной сетки ячеек в террейне.

Да, в Torque3D координата по Z — это высота.

Возникает вопрос: а если x+1 или x-1 или y+1 или y-1 выходят за границу террейна?
Тогда берется точка на противоположенной стороне террейна и возникает артефакт в виде необоснованной повышенной/пониженной освещенности такой «пограничной» вершины. Т.е. если, например, x=0, то x-1 = -1, который превращается в size — 1 (размер террейна минус единичка, например 256 — 1 = 255).

Почему так превращается? Потому что вот функция, возвращающая высоту в точке x, y:

inline U16 TerrainFile::getHeight( U32 x, U32 y ) const
{
x %= mSize;
y %= mSize;
return mHeightMap[ x + ( y * mSize ) ];
}

где mSize — это размер террейна, например 256, а оператор % берет остаток от деления. Тип U32 — это беззнаковый тип, а вызывается эта функция из функции TerrainBlock::getSmoothNormal(...) вот здесь:

F32 h1 = fixedToFloat( mFile->getHeight( x+1, y ) );
F32 h2 = fixedToFloat( mFile->getHeight( x, y+1 ) );
F32 h3 = fixedToFloat( mFile->getHeight( x-1, y ) );
F32 h4 = fixedToFloat( mFile->getHeight( x, y-1 ) );

а x и y они типа S32, что означает знаковый тип. Если приходит -1 на вход этой функции, то значение автоматически преобразуется к беззнаковому, причем к предельному (максимальному) для 32 битных чисел. Это будет 2^32 — 1 = 4 294 967 295 и остаток от деления на 256 будет 255.
4 294 967 295 % 256 = 255.

Я подумал, что нам такого не надо и решил немного подкорректировать исходный код Torque, попутно запилив некий функционал.

Функционал заключается в следующем:
если x-1 < 0 или x+1 > mSize — 1 или y-1<0 или y+1> mSize — 1, то значение высоты в данной точке брать не с текущего террейна, а с тех террейнов, которые мы указали соответствующими соседями.

Для этого поменялся инспектор в редакторе мира для террейна, теперь там есть вкладка «Attached terrains» и поля «ForwardId», «BackwardId», «LeftId» и «RightId», куда пользователь ручками вбивает id'шники соседних террейнов (которые он сочтет нужными, хаха).

Для того, чтобы поменять инспектор, был вставлен код в функцию void TerrainBlock::initPersistFields():

addGroup( "Media" );

addProtectedField( "terrainFile", TypeStringFilename, Offset( mTerrFileName, TerrainBlock ), &TerrainBlock::_setTerrainFile, &defaultProtectedGetFn,"The source terrain data file." );

endGroup( "Media" );

// Вкладка в инспекторе WorldEditor'а для "соседей" террейна
addGroup( "Attached terrains" );
addField( "ForwardId", TypeS32, Offset( m_Forward, TerrainBlock ), "Id of forward attached terrain" );
addField( "BackwardId", TypeS32, Offset( m_Backward, TerrainBlock ), "Id of backward attached terrain" );
addField( "LeftId", TypeS32, Offset( m_Left, TerrainBlock ), "Id of left attached terrain" );
addField( "RightId", TypeS32, Offset( m_Right, TerrainBlock ), "Id of right attached terrain" );
endGroup( "Attached terrains" );
// end of Вкладка

addGroup( "Misc" );
...

В прототип класса class TerrainBlock : public SceneObject были вставлены поля:
S32 m_Forward;
S32 m_Backward;
S32 m_Left;
S32 m_Right;

То есть у меня выглядит вот так:
...
///
FileName mTerrFileName;

/// Attached terrains Соседи террейна
S32 m_Forward;
S32 m_Backward;
S32 m_Left;
S32 m_Right;

/// The maximum detail distance found in the material list.
F32 mMaxDet

Для того, чтобы эти поля сериализовались, надо вставить код в функции TerrainBlock::packUpdate и TerrainBlock::unpackUpdate.

В TerrainBlock::packUpdate вставляется:

// для соседей террейна
if( stream->writeFlag( mask & NextFreeMask ) )
{
stream->write( m_Forward );
stream->write( m_Backward );
stream->write( m_Left );
stream->write( m_Right );
}
//
После строчек:
if ( stream->writeFlag( mask & FileMask ) )
{
stream->write( mTerrFileName );
stream->write( mCRC );
}

В TerrainBlock::unpackUpdate нужно вставить:

// Соседи террейна
if ( stream->readFlag() )
{
stream->read( &m_Forward );
stream->read( &m_Backward );
stream->read( &m_Left );
stream->read( &m_Right );
}
// end of Соседи террейна

После строчек:

if ( stream->readFlag() ) // FileMask
{
FileName terrFile;
stream->read( &terrFile );
stream->read( &mCRC );

if ( isProperlyAdded() )
setFile( terrFile );
else
mTerrFileName = terrFile;
}

Внимание! Код в этих двух функциях должен быть в определенном порядке, а не абы где. Именно там, где я указал, идет работа со stream'ом (наподобии STLвского стрима).

Теперь дошла очередь и до самой функции TerrainBlock::getSmoothNormal(...).

Изменения в terrData.cpp.

Сначала перед самой функцией TerrainBlock::getSmoothNormal(...) надо объявить:

namespace Sim
{
// Defined in simManager.cpp
extern SimIdDictionary *gIdDictionary;
}

Это объявит неймспейс локально прямо в этом файле реализации и продекларирует, что есть такая переменная SimIdDictionary *gIdDictionary.
Прошу заметить, это не создание новой глобальной переменной Sim::gIdDictionary, а лишь указание компилятору (впоследствии линковщику), что такая переменная уже есть в какой то другой единице трансляции — в другом *.cpp файле (а следовательно — в другом *.obj файле, после того как компилятор скушает *.cpp)

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

В функции TerrainBlock::getSmoothNormal(...) после строчек

const TerrainSquare *sq = mFile->findSquare( 0, x, y );
if ( skipEmpty && sq->flags & TerrainSquare::Empty )
return false;

Вставляю следующий код:

Resource File1, File2, File3, File4;
File1 = File2 = File3 = File4 = mFile;

S32 x1 = x + 1;
S32 x2 = x — 1;
S32 y1 = y + 1;
S32 y2 = y — 1;

if( x1 > mFile->mSize — 1 )
{
x1 = mFile->mSize — 1;
TerrainBlock *rBlk = dynamic_cast<TerrainBlock*>( Sim::gIdDictionary->find( m_Right ) );
if( rBlk )
{
x1 = 1;
File1 = rBlk->mFile;
}
}

if( y1 > mFile->mSize — 1 )
{
y1 = mFile->mSize — 1;
TerrainBlock *fBlk = dynamic_cast<TerrainBlock*>( Sim::gIdDictionary->find( m_Forward ) );
if( fBlk )
{
y1 = 1;
File2 = fBlk->mFile;
}
}

if( x2 < 0 )
{
x2 = 0;
TerrainBlock *lBlk = dynamic_cast<TerrainBlock*>( Sim::gIdDictionary->find( m_Left ) );
if( lBlk )
{
x2 = lBlk->mFile->mSize — 2;
File3 = lBlk->mFile;
}
}

if( y2 < 0 )
{
y2 = 0;
TerrainBlock *bBlk = dynamic_cast<TerrainBlock*>( Sim::gIdDictionary->find( m_Backward ) );
if( bBlk )
{
y2 = bBlk->mFile->mSize - 2;
File4 = bBlk->mFile;
}
}

А строчки:

F32 h1 = fixedToFloat( mFile->getHeight( x+1, y ) );
F32 h2 = fixedToFloat( mFile->getHeight( x, y+1 ) );
F32 h3 = fixedToFloat( mFile->getHeight( x-1, y ) );
F32 h4 = fixedToFloat( mFile->getHeight( x, y-1 ) );

Заменить строчками:

F32 h1 = fixedToFloat( File1->getHeight( x1, y ) );
F32 h2 = fixedToFloat( File2->getHeight( x, y1 ) );
F32 h3 = fixedToFloat( File3->getHeight( x2, y ) );
F32 h4 = fixedToFloat( File4->getHeight( x, y2 ) );

Вроде бы все! Перекомпилировать — и ПРОФИТ! Ура!

Кстати, можно оптимизировать по скорости, то есть указатель на соседний террейн не искать в функции TerrainBlock::getSmoothNormal(...), как здесь: например:

TerrainBlock *rBlk = dynamic_cast<TerrainBlock*>( Sim::gIdDictionary->find( m_Right ) );

Ибо функция сглаживания нормали вызывается очень много раз( mSize*mSize раз ) для каждой вершины террейна, если террейн поднимается/опускается/сглаживается.

Тогда нужно создать 4 соответствующих указателя как поля класса и, скорее всего, инициализировать их там, где происходит «сериализация» (скорее всего ф-ции TerrainBlock::packUpdate или TerrainBlock::unpackUpdate).

Если бы была возможность вставить сюда измененные файлы исходников — с удовольствием вставил бы!

ТоркEnginesourceterrainterrData.cpp
и
ТоркEnginesourceterrainterrData.h

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

Пока фиксит только нормали. Для сглаживания текстур — отдельная история.

Кстати, если соседей не назначить, то нормали будут пересчитываться все равно более адекватно — высоты с противоположенных сторон браться не будут — не будет ужасных непонятных артефактов, будет браться высота текущей точки, для которой и рассчитывается нормаль.

Выглядеть будет офигенчиком как здесь, использована монотонная текстура.
imageЧитать полностью »

Я помню как играл первый раз в Цезарь 3, это удивительно умная и сбалансированная игра, создает чувство, что город живет своей жизнью и после завершения миссии. Можно провести часы, наблюдая за городом и не вмешиваться в его жизнь: плебеи будут бегать по городу в поисках работы, а патриции жаловаться на неважные условия жизни, торговцы, школьники, лодки, жрецы — этот мир замирает лишь в минуты пауз, давая игроку возможность продумать следующий шаг. Исследуя внутренние алгоритмы игры, я не перестаю удивляться с какой точностью авторы сложили кусочки мозаики, под названием «баланс». За время, проведенное над восстановлением кода оригинальной игры скопилось достаточно материала по макромеханике игры, которым я хочу поделиться с читателим.


Читать полностью »

Введение (лирика)

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

Но, в то же время, меня всегда привлекало низкоуровневое программирование. Ещё во время учёбы мне очень понравился язык программирования C++. Только негде было его применять, кроме как для своего развлечения. Дальше я опустился пониже — изучил Assembler. Понял, как работает процессор (хотя слишком поверхностно) и как выполняются программы на самом деле.

Со всем этим набором знаний и опыта я попал в веб-разработку. Всё поначалу казалось весьма и весьма хорошо, оказалось гораздо проще, чем я думал. А со временем приелось, стало слишком просто, неинтересно, нет простора для оптимизаций и интересных решений. Генерируешь веб-странички, пишешь и подключаешь js-скрипты, оформляешь страницы с помощью css. Чувствовал, что больше не развиваюсь как программист.
Читать полностью »

В статье «String enum — строковые enum» я писал о том как связывать текстовые представления с enum class — метод хороший но только если все элементы заранее известны, но зачастую бывает что строки являются некими идентификаторами и конечно же заранее не известны, а зачастую будут добавляться позднее и причем без пересборки программы.

Требования к библиотеке все теже:

  • Кроссплатформенность;
  • Минимум зависимостей;
  • Скорость чтения;
  • Простой синтаксис;

Пример конфига

{
    "objects":
    [
        {
            "id": "object1",
            "events":
            {
                "event1":{
                    "give": {"object2": 4}
                },
            }
        },
        {
            "id": "object2",
            "events":
            {
                "event2":{
                    "give": {"object1": 3}
                },
            },
            {
            "id": "object3",
            "events":
            {
                "event3":{
                    "give": {"object3": 4}
                },
            }
        },

Первая и самая простая идея которая напрашивается это:

    std::map<std::string,script> events;

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

Вторая идея парсить этот конфиг в 2 прохода тогда на 2-м проходе object1, object2, object3 будут уже известны и можно будет записать на них прямо указатели или ссылки. Но если зависимости еще более сложные то такой подход может и не сработать.

Я предлагаю способ позволяющий существенно сократить runtime издержки подобных конструкций

Читать полностью »

В последнее время я увлёкся темой зависимости от C Runtime в проектах, написанных на Visual C++. Вернее, темой избавления от зависимости от Visual C++ Redistributable, ведь если проект представляет собой небольшую библиотеку интеграции или простейшую утилиту, таскать за собой целый распространяемый пакет не очень удобно.

На Хабре уже была статья на данную тему, однако я в процессе своих экспериментов столкнулся с некоторыми проблемами. Об этих проблемах и о способе их решения и пойдёт речь.

Сразу оговорюсь, что я изначально ожидаю, в целом, справедливой критики на счёт правильности подхода к проблемам и вообще в целом линковки с msvcrt.dll — да, это не поддерживаемое Microsoft решение, да, это решение больше подходит для новых проектов, и да, возможно, придётся отказаться от многих плюшек, но ведь это используют, да и кто не рискует… В общем, все, кому интересна эта тема, как и мне, — прошу под кат.

Заранее прошу прощения за заголовок: я старался перевести фразу «Linking to msvcrt.dll in Visual C++». Статья моя, это не перевод, но название всё-таки проще сформулировать на английском.
Читать полностью »

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js