Этой статьёй мы начинаем трилогию об игровом движке Nau Engine. В первой части мы сосредоточимся на его функциональности, уделяя особое внимание трём ключевым блокам ошибок: проблемам с памятью, копипасте и логическим ошибкам.

О Nau Engine

Nau Engine — это игровой движок, разработанный для упрощения процесса создания и поддержки игр. Он основан на трёх принципах: универсальность, доступность и кроссплатформенность.

1. Универсальность: Nau Engine предлагает инструменты для всех этапов создания игры — от разработки до пострелизной поддержки. Это включает системы для аналитики, обработки ошибок и обновления контента.

2. Доступность: движок нацелен на разработчиков с разным уровнем опыта. Он предоставляет интуитивно понятные инструменты и шаблоны, что помогает новичкам легко начать, а более опытным пользователям — углубляться в детали.

3. Кроссплатформенность: Nau Engine позволяет адаптировать игры для различных платформ, таких как ПК, мобильные устройства и веб, что делает разработку более гибкой и удобной.

Особое внимание стоит уделить адаптированной ECS (Entity Component System) библиотеке от движка Dagor, которая интегрирована в Nau Engine. Эта библиотека не только обеспечивает высокую производительность, но и навевает приятные воспоминания о таких культовых играх, как:

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

Следует отметить, что для анализа использовалось состояние репозитория на момент коммита 020cbfe.

Результаты проверки

Копипаста

Фрагмент N1

NAU_ASSERT(irRequest.usage.access == req.usage.access 
        && irRequest.usage.type == irRequest.usage.type);

Предупреждение PVS-Studio: V501_ There are identical sub-expressions 'irRequest.usage.type' to the left and to the right of the '==' operator. irGraphBuilder.cpp 719_

В этом фрагменте нас интересует макрос NAU_ASSERT, который служит для проверки условий во время выполнения кода. Если быть точнее, то интересно нам второе сравнение внутри этого макроса, а именно: irRequest.usage.type == irRequest.usage.type. При отдельном написании сразу становится видно, что в левой и правой части сравнения используется одно и то же выражение, а следовательно, условие всегда будет истинным.

Вероятно, автор кода хотел сравнить irRequest.usage.type с req.usage.type, чтобы проверить, совпадают ли типы в двух разных объектах:

NAU_ASSERT(irRequest.usage.access == req.usage.access 
        && irRequest.usage.type   == req.usage.type);

Фрагмент N2

bool VFXModFXInstance::deserialize(const nau::DataBlock* blk)
{
  ....
  m_life.part_life_min = blk->getReal("lifeMin", 5.0f);
  m_life.part_life_max = blk->getReal("lifeMin", 5.0f);
  ....
}

Предупреждение PVS-Studio: V656_ Variables 'm_life.part_life_min', 'm_life.part_life_max' are initialized through the call to the same function. It's probably an error or un-optimized code. Consider inspecting the 'blk->getReal("lifeMin", 5.0f)' expression. Check lines: 94, 95. vfx_mod_fx_instance.cpp 95_

В функции VFXModFXInstance::deserialize наблюдается проблема, связанная с инициализацией переменных m_life.part_life_min и m_life.part_life_max. Из-за копипасты обе переменные получают свои значения через вызов одной и той же функции с использованием одного и того же ключа "lifeMin". Очевидно, что для минимального и максимального времени жизни частиц должны использоваться разные значения. Поэтому можем предположить, что во втором случае вместо "lifeMin" должен использоваться ключ "lifeMax":

m_life.part_life_min = blk->getReal("lifeMin", 5.0f);
m_life.part_life_max = blk->getReal("lifeMax", 5.0f);

Фрагмент N3

Material* Material::clone() const
{
  auto material = new (std::nothrow) Material();
  if (material)
  {
    ....
    material->_textureSlots = material->_textureSlots;
    material->_textureSlotIndex = material->_textureSlotIndex;
    ....
  }
....
}

Предупреждения PVS-Studio:

V570_ The 'material->_textureSlots' variable is assigned to itself. CCMaterial.cpp 527_

V570_ The 'material->_textureSlotIndex' variable is assigned to itself. CCMaterial.cpp 528_

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

material->_textureSlots = _textureSlots;
material->_textureSlotIndex = _textureSlotIndex;

Логические ошибки

Фрагмент N4

template <typename T>
requires(std::is_enum_v<T>)
class TypedFlag
{
public:
  using EnumType = T;
  using ValueType = std::underlying_type_t<T>;
  ....
private:
  ValueType m_value = 0;
  ....
  friend TypedFlag<T> operator|(TypedFlag<T> value, TypedFlag<T> flags)
  {
  }
  ....
};

Предупреждение PVS-Studio: V591_ Non-void function should return a value. typed_flag.h 145_

В перегрузке оператора | для шаблона класса TypedFlag отсутствует возвращаемое значение, что может привести к ошибкам в логике работы программы. Особенно интересна эта ситуация тем, что для оператора |= логика реализована:

friend TypedFlag<T>& operator|=(TypedFlag<T>& value, T flag)
{
  return value.set(flag);
}

Есть несколько способов исправить эту проблему.

Способ N1. Реализовать функциональность операторов. Они должны возвращать результат, который соответствует логике объединения флагов. Например:

return TypedFlag<T> { value }.set(flags);

Способ N2. Удалить оператор. Если функционал оператора | не нужен, его можно удалить, чтобы избежать путаницы или, например, можно объявить оператор как удалённый:

friend TypedFlag<T> operator|(TypedFlag<T> value, TypedFlag<T> flags) = delete;

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

И вот ещё случаи:

Фрагмент N5

EntityId EntityManager::createEntitySync(....)
{
  ....
  if (EASTL_UNLIKELY(result != RequestResources::Loaded))
  {
#if NAU_DEBUG
    if (result == RequestResources::Loaded)
    {
      ....
    }
#endif
    if (result == RequestResources::Error)
    {
      ....
    }
  }
  ....
}

Предупреждение PVS-Studio: V637_ Two opposite conditions were encountered. The second condition is always false. Check lines: 711, 714. entityManager2.cpp 711_

В коде присутствует логическая ошибка, которая может ввести в заблуждение. Рассмотрим детали:

  • Условие if (EASTL_UNLIKELY(result != RequestResources::Loaded)): оно проверяет, что результат не равен RequestResources::Loaded. Если это условие истинно, значит, ресурсы не загружены.

  • Блок #if NAU_DEBUG: внутри него есть проверка if (result == RequestResources::Loaded), которая никогда не будет истинной, так как мы уже проверили, что result не равен RequestResources::Loaded. Это создаёт противоречие и может ввести в заблуждение.

Фрагмент N6

IGenSave* create_async_writer(....)
{
  AsyncWriterCB* ret = new AsyncWriterCB(buf_size);
  if (!ret->open(fname, mode))
  {
    if (ret)
    {
      delete ret;
      ret = nullptr;
    }
  }
  return ret;
}

Предупреждение PVS-Studio: V668_ There is no sense in testing the 'ret' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. asyncWrite.cpp 363_

В этом коде происходит проверка указателя ret на nullptr после вызова new. Это может быть источником ошибок, поскольку указатель ret всегда будет указывать на выделенную память, если выделение прошло успешно. В случае если память не удастся выделить, будет выброшено исключение, и код ниже не будет выполнен.

Кроме того, указатель ret разыменовывается до проверки, что делает саму проверку if (ret) ещё более бессмысленной. Если бы ret действительно был равен nullptr, программа бы уже аварийно завершилась при попытке разыменования.

И вот ещё случаи:

  • V668 There is no sense in testing the 'ret' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. asyncWrite.cpp 377

  • V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCFontCharMap.cpp 58

  • V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCFontCharMap.cpp 77

  • V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCFontCharMap.cpp 89

  • V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCFontFNT.cpp 523

  • V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCFontFNT.cpp 545

  • V668 There is no sense in testing the 'tempFont' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCFontFNT.cpp 569

  • V668 There is no sense in testing the 'layerGradient' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCLayer.cpp 579

  • V668 There is no sense in testing the 'layerGradient' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCLayer.cpp 592

  • V668 There is no sense in testing the 'pwszBuffer' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCDevice-win32.cpp 90

  • V668 There is no sense in testing the 'pwszBuffer' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. CCDevice-win32.cpp 326

Фрагмент N7

void Properties::skipWhiteSpace()
{
  signed char c;
  do
  {
    c = readChar();
  } while (isspace(c) && c != EOF);
  ....
  if (c != EOF)
  {
    ....
  }
}

Предупреждения PVS-Studio:

V739_ EOF should not be compared with a value of the 'char' type. The 'c' should be of the 'int' type. CCProperties.cpp 464_

V739_ EOF should not be compared with a value of the 'char' type. The 'c' should be of the 'int' type. CCProperties.cpp 468_

Функция-член Properties::skipWhiteSpace использует переменную типа signed char для хранения символа, считанного с помощью функции Properties::readChar. Последняя, судя по комментарию, симулирует поведение std::getchar:

//
// Stream simulation
//
signed char Properties::readChar()
{
  if (eof())
    return EOF;
  return _data->_bytes[(*_dataIdx)++];
}

Код содержит две ошибки:

Ошибка N1. Функция std::getchar неспроста возвращает значения типа int. Функция возвращает символ (1 байт) или EOF в результате ошибки. EOF — константа, имеющая негативное значение, обычно -1. Поэтому перед тем как преобразовывать результат std::getchar к символу, нужно отсечь вариант с EOF:

if (int res = std::getchar(); res != EOF)
{
  char ch = res;
  // your logic with character
}

Что может произойти, если не обработать такую ситуацию? Пользователи, использующие Extended ASCII Codes, иногда сталкиваются с ошибкой, когда один из символов их алфавита некорректно обрабатывается программами. Например, последняя буква русского алфавита в кодировке Windows-1251 как раз имеет код 0xFF и воспринимается некоторыми программами как конец файла.

Корректная имплементация функции Properties::readChar должна выглядеть так:

//
// Stream simulation
//
int Properties::readChar()
{
  if (eof())
    return EOF;
  return _data->_bytes[(*_dataIdx)++];
}

Ошибка N2. Функция Properties::skipWhiteSpace после исправления Properties::readChar также должна работать с типом int до тех пор, пока не отсечёт вариант с EOF:

void Properties::skipWhiteSpace()
{
  int c;
  do
  {
    c = readChar();
  }
  while (c != EOF && isspace(c));
  ....
  if (c != EOF)
  {
    // Now we can cast 'c' to 'signed char'
    signed char ch = c;
    ....
  }
}

Фрагмент N8

void Sweep::EdgeEvent(....)
{
  ....
  if (o1 == COLLINEAR) {
    if( triangle->Contains(&eq, p1)) {
    ....
    } else {
      std::runtime_error("EdgeEvent - collinear points not supported");
      assert(0);
    }
    return;
  }
  ....
  if (o2 == COLLINEAR) {
    if (triangle->Contains(&eq, p2)){
      ....
    } else {
      std::runtime_error("EdgeEvent - collinear points not supported");
      assert(0);
    }
    return;
  }
  ....
}

Предупреждения PVS-Studio:

V596_ The object was created but it is not being used. The 'throw' keyword could be missing: throw runtime_error(FOO); sweep.cc 123_

V596_ The object was created but it is not being used. The 'throw' keyword could be missing: throw runtime_error(FOO); sweep.cc 140_

В функции-члене Sweep::EdgeEvent создаются объекты исключений std::runtime_error при обнаружении коллинеарных точек, но они не выбрасываются. Это приводит к тому, что программа может продолжать выполнение, игнорируя возникшую ошибку, что может вызвать некорректное поведение.

Исправленный код:

throw std::runtime_error("EdgeEvent - collinear points not supported");

Фрагмент N9

std::size_t UniformLocation::operator()(const UniformLocation &uniform) const
{
    return (((size_t) shaderStage) & 0xF)
           |((size_t)(location[0] << 4))
           |((size_t)(location[1] << 8));
}

Предупреждения PVS-Studio:

V1028_ Possible overflow. Consider casting operands of the 'location[0] << 4' operator to the 'size_t' type, not the result. Types.cpp 40_

V1028_ Possible overflow. Consider casting operands of the 'location[1] << 8' operator to the 'size_t' type, not the result. Types.cpp 40_

Оператор UniformLocation::operator() выполняет битовые операции над значениями, полученными из переменных shaderStage и location, чтобы вернуть уникальный идентификатор.

Проблема заключается в том, что операции сдвига (<<) могут привести к переполнению, если значения location[0] или location[1] слишком велики. Следует привести location[0] и location[1] к типу size_t перед выполнением операции сдвига:

std::size_t UniformLocation::operator()(const UniformLocation &uniform) const
{
    return (((size_t) shaderStage) & 0xF)
           |((size_t)(location[0]) << 4)
           |((size_t)(location[1]) << 8);
}

Проблемы с памятью

Фрагмент N10

char** m_memBlock = nullptr;

template<class Type>
class ThreadLocalValue final
{
  ....
private:
  void resizeLines(size_t sizeReq)
  {
    ....
    auto newSize = sizeReq + 1;
    ....
    m_memBlock = static_cast<char**>(realloc(m_memBlock, 
                                             sizeof(char*) * newSize));
    ....
  }
  ....
};

Предупреждение PVS-Studio: V701_ realloc() possible leak: when realloc() fails in allocating memory, original pointer 'm_memBlock' is lost. Consider assigning realloc() to a temporary pointer. thread_local_value.h 235_

В данном коде m_memBlock представляет собой массив указателей на char, а метод resizeLines изменяет его размер с помощью realloc.

Если realloc успешно выделяет память, он может вернуть либо тот же указатель, либо новый, переместив данные. Однако, если выделение памяти не удаётся, realloc возвращает nullptr, а прежнее значение m_memBlock теряется.

Чтобы избежать утечки, необходимо сначала присвоить результат realloc временной переменной, проверить его на nullptr и только затем обновлять m_memBlock.

И вот ещё случаи:

  • V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer '_buffer' is lost. Consider assigning realloc() to a temporary pointer. CCDrawNode.cpp 102

  • V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer '_bufferGLPoint' is lost. Consider assigning realloc() to a temporary pointer. CCDrawNode.cpp 116

  • V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer '_bufferGLLine' is lost. Consider assigning realloc() to a temporary pointer. CCDrawNode.cpp 130

  • V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer '_triBatchesToDraw' is lost. Consider assigning realloc() to a temporary pointer. CCRenderer.cpp 629

  • V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer '* out' is lost. Consider assigning realloc() to a temporary pointer. ZipUtils.cpp 185

  • V701 realloc() possible leak: when realloc() fails in allocating memory, original pointer 'arr->arr' is lost. Consider assigning realloc() to a temporary pointer. ccCArray.cpp 100

Фрагмент N11

IAssetContainerLoader* loader = nullptr;
const auto importSettingsProviders = ....;
RuntimeReadonlyDictionary::Ptr importSettings;
for (const auto& importSettingsProvider : importSettingsProviders)
{
  if (importSettings = 
      importSettingsProvider->getAssetImportSettings(containerPath, 
                                                     *loader); 
      importSettings)
  {
    break;
  }
}

Предупреждение PVS-Studio: V522_ Dereferencing of the null pointer 'loader' might take place. asset_file_content_provider.cpp 34_

В начале кода указатель loader инициализируется значением nullptr. Затем в цикле вызывается функция-член getAssetImportSettings, при этом аргументом передаётся разыменованный указатель loader. Странно, что между этими строками он никаким образом не модифицируется. Получаем гарантированное неопределённое поведение.

И вот ещё случай:

  • V522 Dereferencing of the null pointer 'object' might take place. The null pointer is passed into 'replace' function. Inspect the second argument. Check lines: 'CCVector.h:481', 'CCLayer.cpp:976'. CCVector.h 481

Фрагмент N12

struct DelayedEntityCreationChunk
{
  ....
  DelayedEntityCreationChunk(uint16_t cap) : capacity(cap)
  {
    queue = (DelayedEntityCreation*)
      malloc(capacity * sizeof(DelayedEntityCreation));
  }
  ....
};

Предупреждение PVS-Studio: V630_ The 'malloc' function is used to allocate memory for an array of objects which are classes containing constructors and destructors. entityManager.h 1391_

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

Тут у читателя может возникнуть вопрос: "А зачем тогда вообще включать этот фрагмент в статью?". Я собирался убрать его, но тут моё внимание привлекло ещё кое-что в этом классе:

struct DelayedEntityCreationChunk
{
  ....
  DelayedEntityCreation* queue = nullptr;
  uint16_t readFrom = 0, writeTo = 0, capacity;

  DelayedEntityCreationChunk(uint16_t cap) :
    capacity(cap)
  {
    queue = (DelayedEntityCreation*)
         malloc(capacity * sizeof(DelayedEntityCreation));
  }

  ~DelayedEntityCreationChunk()
  {
    for(auto i = begin(), e = end(); i != e; ++i)
      i->~DelayedEntityCreation();
    free(queue);
  }

  DelayedEntityCreationChunk(const DelayedEntityCreationChunk&) = delete;
  DelayedEntityCreationChunk&
    operator=(const DelayedEntityCreationChunk&) = delete;

  DelayedEntityCreationChunk(DelayedEntityCreationChunk&& a)
  {
    memcpy(this, &a, sizeof(DelayedEntityCreationChunk));
    memset(&a, 0, sizeof(DelayedEntityCreationChunk));
  }

  DelayedEntityCreationChunk& operator=(DelayedEntityCreationChunk&& a)
  {
    alignas(DelayedEntityCreationChunk) 
      char buf[sizeof(DelayedEntityCreationChunk)];

    memcpy(buf, this, sizeof(DelayedEntityCreationChunk));
    memcpy(this, &a, sizeof(DelayedEntityCreationChunk));
    memcpy(&a, buf, sizeof(DelayedEntityCreationChunk));
    return *this;
  }

  ....

  template <typename... Args>
  bool emplace_back(EntityId eid, Args &&...args)
  {
    DAECS_EXT_ASSERT(!full());
    new (queue + (writeTo++)) DelayedEntityCreation(eid,
                                             eastl::forward<Args>(args)...);
    return full();
  }
  ....
};

Какими свойствами обладает этот класс:

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

Рекомендую переделать код на примерно следующий:

struct DelayedEntityCreationChunk
{
  ....
  DelayedEntityCreation* queue = nullptr;
  uint16_t readFrom = 0, writeTo = 0, capacity;
  ....
public:
  DelayedEntityCreationChunk(DelayedEntityCreationChunk &&a) noexcept
    : queue { std::exchange(a.queue, {}) }
    , readFrom { std::exchange(a.readFrom, {}) }
    , writeTo { std::exchange(a.writeTo, {}) }
    , capacity { std::exchange(a.capacity, {}) }
  {
  }

  void swap(DelayedEntityCreationChunk &other) noexcept
  {
    auto lhs = std::tie(queue, readFrom, writeTo, capacity);
    auto rhs = std::tie(other.queue, other.readFrom,
                        other.writeTo, other.capacity);

    std::swap(lhs, rhs);
  }

  DelayedEntityCreationChunk&
    operator=(DelayedEntityCreationChunk &&a) noexcept
  {
    if (this == std::addressof(a)) return *this;

    auto tmp = std::move(a);
    swap(tmp);
    return *this;
  }
  ....
};

Заключение

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

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

Благодарю за внимание!

Комментарии (11)


  1. datacompboy
    13.02.2025 11:51

    Это приводит к тому, что программа может продолжать выполнение, игнорируя возникшую ошибку, что может вызвать некорректное поведение.

    Я, кажется, догадываюсь, как там assert(0) появился в следующей строчке :)


  1. Jijiki
    13.02.2025 11:51

    Фрагмент 7 (О1, О2) : почему нельзя сделать поляпропертис табличными, в файл кидать таблицы и читать потоком поблочно?, циклов не будет будут поточныеитераторы поидее

    тоесть перевести всё в блоки не в getchar

    если всё в строке или строках есть split

    без проверки(в годболте) по чуйке увидел, просто визуально показалось что можно улучшить

    а извиняюсь может гетчар и лучше

    Скрытый текст
        std::ifstream file(filename);
    // Find the bitmap file's size
        file.seekg(0, std::ios_base::end);
        std::istream::pos_type fileSize = file.tellg();
        file.seekg(0);
    
        std::vector<unsigned char> b;
        b.resize((fileSize));
        std::copy(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>(), b.begin());

    почему-то об этом подумал


    1. Jijiki
      13.02.2025 11:51

      просто по закону сериализации все длины должны быть известны (длинна наименования -какоето наименование блоков по назначению - число блоков [[блокN[[число] - [данные]]])

      как будто рекурсия но там можно в цикл превратить по известным значениям

      наименование где-то было присвоено (посчиталась длинна в этот момент), в блоке что-то написали (длинна посчиталась в этот момент)


  1. Serpentine
    13.02.2025 11:51

    Копипаста

    Фрагмент N1

    Предупреждение PVS-Studio: V501_ There are identical sub-expressions 'irRequest.usage.type' to the left and to the right of the '==' operator. irGraphBuilder.cpp 719_

    Это из Дагора перекочевало (было), но там исправили (стало).

    Фрагмент N3

    Предупреждения PVS-Studio:

    V570_ The 'material->_textureSlots' variable is assigned to itself. CCMaterial.cpp 527_

    V570_ The 'material->_textureSlotIndex' variable is assigned to itself. CCMaterial.cpp 528_

    Это код cocos2d-x, т.е. 3rdParty.

    Фрагмент N5

    Предупреждение PVS-Studio: V637_ Two opposite conditions were encountered. The second condition is always false. Check lines: 711, 714. entityManager2.cpp 711_

    В коде присутствует логическая ошибка, которая может ввести в заблуждение. Рассмотрим детали:

    • Условие if (EASTL_UNLIKELY(result != RequestResources::Loaded)): оно проверяет, что результат не равен RequestResources::Loaded. Если это условие истинно, значит, ресурсы не загружены.

    Реализация createEntitySync() в Дагоре, на первый взгляд, такая же:

    EntityId EntityManager::createEntitySync(template_t templId, ComponentsInitializer &&initializer, ComponentsMap &&map)
    {
      /*,,,*/ 
      if (DAGOR_UNLIKELY(result != RequestResources::AlreadyLoaded))
      {
    #if DAECS_EXTENSIVE_CHECKS
        if (result == RequestResources::Loaded)
        {
          /*,,,*/
        }
    #endif
        if (result == RequestResources::Error)
        {
         /*,,,*/
        }
      }
      /*,,,*/
    }

    Но есть нюанс, перечисление RequestResources помимо константы Loaded там содержит еще и AlreadyLoaded (с ним и происходит сравнение сначала). Тогда как в Nau в этом же перечислении AlreadyLoaded нет. Отсюда может быть и ошибка.

    Фрагмент N6

    Предупреждение PVS-Studio: V668_ There is no sense in testing the 'ret' pointer against null, as the memory was allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. asyncWrite.cpp 363_

    В этом коде происходит проверка указателя ret на nullptr после вызова new

    В Дагоре create_async_writer() реализована точно также, только проверка с delete и обнулением завернута в макрос del_it. В Nau этот макрос просто развернули.

    Фрагмент N7

    Фрагмент N8

    Фрагмент N9

    Это опять код cocos2d-x. Обратите внимание на названия файлов, начинающихся на "CC" (типа CCLayer.cpp) - это все исходники кокоса. Добрая часть предупреждений на кокос2д.


    1. Jijiki
      13.02.2025 11:51

      если большая часть из встройки блин база получается, я как-то писал себе утилку, которая сканит мои файлы проекта С++ и даёт статистику и тоже с этим столкнулся, приходилось ради анализа копировать папку


      1. Serpentine
        13.02.2025 11:51

        Насколько бОльшая для всего движка сказать не могу, в статье только часть, наверное, показали. Но все равно заметно, что с этим cocos2d-x и его зависимостями что-то не так.


  1. Serpentine
    13.02.2025 11:51

    На правах оффтопа и бесполезных фактов.

    Когда боролся (да и сейчас борюсь) с DX12, я в образовательных целях изучал исходники DagorEngine и NauEngine, чтобы боевой код посмотреть, ну и как серьезные люди с в этим api работают и код оформляют, а то вдруг мне учебные фреймворки и всякие туториалы дичь втирают.

    И почти сразу наткнулся на dag_comPtr.h с замечательным комментарием:

    // copy paste (with some small changes) from wrl/client.h
    // don't blame me for the stupid shit of this...

    в Nau он тоже перекочевал.


    1. Jijiki
      13.02.2025 11:51

      тоже база, https://learn.microsoft.com/ru-ru/cpp/cppcx/wrl/comptr-class?view=msvc-170

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

      этот птр так же используется в улучшенном варианте апи виндовс по загрузке текстур, ну и в исходниках майкрософт можно посмотреть по мини движку на 11 на 12 не знаю есть ли


      1. Serpentine
        13.02.2025 11:51

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

        Вникать в DX12 по видео, КМК, не очень благодарная затея. Видео на русском я видел, но сложилось впечатление, что автор сам недавно начал его изучать и повторял дословно за документацией и/или чуть модифицировал код HelloTriangle из DirectX-Graphics-Samples.

        Хотя нового-то особо и придумывать действительно не надо.

        Насчет Microsoft::WRL::ComPtr- он уже в книге Фрэнка Луны по DX12 фигурирует, а ей почти десять лет (вот из репозитория автора с исходниками из нее). После самописных макросов/шаблонов SafeRelease() - это наверное удобная вещь для работы с COM, если работа с ним вообще может считаться удобной.


        1. Jijiki
          13.02.2025 11:51

          понял вас, я делал по PardCode повторил работало, дальше как глянул на код пока не хочется с таким кодом взаимодействовать, я немного по другому пытаюсь, и ушел на OpenGL, потом на С изза математики ( отговорка странная, но пока нормально всё ), меня отпугивает усложненность апи на dx11/12, поэтому пока решил уверенно постич гл

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

          я и вулкан на С смотрел по видео, смотря как проще воспринимать, правда потом всё равно возврат к началу, в то с чем проще и где можно вникнуть в базовые вещи

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

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


          1. Jijiki
            13.02.2025 11:51

            ну тоесть риг я научился делать, но с ИК пока нет желания работать, пока нормально Т позу или idle пошевелить руками визуально в блендере, професионально конечно надо через ИК, ну тоесть сделать модельку и зариговать её, потомучто если дальнейшие планы по созданию контента демки, в риг-аддоне не все существа есть