Всем привет. В 3д помимо моделек - статических, существуют анимации - анимированные модели, которые имеют набор своих данных, эти данные нужны для отображения модельки и её анимирования.

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

Постановка задачи

**Скелетная анимация - есть моделька,в которой есть сетка - mesh(состоит из полигонов) и скелетная составляющая.

выглядит она например так

Скрытый текст
тестовая прогонка меша и скелетной составляющей не в блендере
тестовая прогонка меша и скелетной составляющей не в блендере

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

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

И окончательное требование - соглашение на 1 сущность 1 тип данных.

Пользуюсь компилятором gcc 14.2 в среде Linux, assimp из репозитория

Задача поставлена, решил её так:

Скрытый текст
//язык С
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma pack(1)

//кастомная тестовая структура
struct EpicLEgendaryNodes_t
{
    char *name;
    int Num;
    float weights;
};
typedef struct EpicLEgendaryNodes_t EpicLEgendaryNodes;

//определение типов
enum typeElements 
{
    INT,
    CHAR,
    DOUBLE,
    FLOAT,
    CHARLINE,
    EPICLEGENDARYNODES
};

//структура списка
typedef struct NodeL_t NodeL;
struct NodeL_t 
{
    enum typeElements T;
    void* data;
    NodeL* Next;
};


//структура хендлера
typedef struct VNodeL_t VNodeL;
struct VNodeL_t
{
  int NodeSz;
  NodeL *head;
};


//определение необходимого минимума функций, которые покроют поставленную задачу
NodeL* getNode(VNodeL *vNodeL,enum typeElements T,void* d);

NodeL* addNodeL(void *d,enum typeElements T,VNodeL *vNodeL);

NodeL* FREENodeL(VNodeL *vNodeL);

NodeL* getByIndex(VNodeL *vNodeL,int Index);

NodeL* setByAfterIndex(VNodeL *vNodeL,int Index,void *d,enum typeElements T);

void printList(VNodeL *vNodeL);


//метод получения ноды - создание
NodeL* getNode(VNodeL *vNodeL,enum typeElements T,void* d) 
{
    char* dd=NULL;
    NodeL* tmp=NULL;
    tmp=malloc(sizeof(NodeL));
    switch(T)
    {
        case INT:
            tmp->data=malloc(sizeof(int));
            memcpy(tmp->data, d, sizeof(int)); //! использую memcpy
            vNodeL->NodeSz++;
            break;
        case CHAR:
            tmp->data=malloc(sizeof(char));
            memcpy(tmp->data, d, sizeof(char));
            vNodeL->NodeSz++;
            break;
        case DOUBLE:
            tmp->data=malloc(sizeof(double));
            memcpy(tmp->data, d, sizeof(double));
            vNodeL->NodeSz++;
            break;
        case FLOAT:
            tmp->data=malloc(sizeof(float));
            memcpy(tmp->data, d, sizeof(float));
            vNodeL->NodeSz++;
            break;
        case CHARLINE:
            dd=d;
            size_t szS=strlen(dd)+1;
            tmp->data=malloc(sizeof(char)*szS);
            ///dd[szS]='\0';
            memcpy(tmp->data, d, sizeof(char)*szS);
            vNodeL->NodeSz++;
            break;
        case EPICLEGENDARYNODES:
            tmp->data=malloc(sizeof(EpicLEgendaryNodes));
            memcpy(tmp->data, d, sizeof(EpicLEgendaryNodes));
            vNodeL->NodeSz++;
            break;
    }
    tmp->Next=NULL;
    return tmp;
}


//добавление в конец ноды
NodeL* addNodeL(void *d,enum typeElements T,VNodeL *vNodeL) 
{
    NodeL* tmp=getNode(vNodeL,T,d);
    if( vNodeL->head == NULL )
    {
        tmp->T=T;
        return tmp;
    }

    NodeL* cur = vNodeL->head;
    while( cur->Next != NULL )cur = cur->Next;

    cur->Next = tmp;

    return vNodeL->head;
}

//очищение структуры списка
NodeL* FREENodeL(VNodeL *vNodeL)
{
    NodeL* cur=vNodeL->head;
    while( cur != NULL )
    {
        NodeL* temp=cur;
        cur = temp->Next;
        free(temp);
    }
  
    return vNodeL->head;
}


//получение значения по индексу
NodeL* getByIndex(VNodeL *vNodeL,int Index)
{
    if(Index>vNodeL->NodeSz-1)
    {
      printf("\n\ngetByIndex\nSearchIndex > SizeNodeLists\n");
      return 0;
    }
  
    int tInd=0;
    NodeL *cur=vNodeL->head;
  
    while(cur!=NULL)
    {
        if(tInd==Index)break;
        tInd++;
        cur=cur->Next;
    }
    return cur;
}


//вставка в место следующее за заданным индексом
NodeL* setByAfterIndex(VNodeL *vNodeL,int Index,void *d,enum typeElements T)
{
    NodeL *setting=getNode(vNodeL,T,d);
    if(Index>vNodeL->NodeSz-1)
    {
      printf("SearchIndex > SizeNodeLists\n");
      return 0;
    }
  
    int tInd=0;
    NodeL *cur=vNodeL->head;
    while(cur!=NULL)
    {
        if(tInd==Index)break;
        tInd++;
        cur=cur->Next;
    }
  
    NodeL *tmp1=cur->Next;
    cur->Next=setting;
    setting->Next=tmp1;
    return vNodeL->head;
}


//вывод списка
void printList(VNodeL *vNodeL)
{
    if(vNodeL->head==NULL){printf("LIST IS EMPTY\n");return ;}
    if(vNodeL==NULL){printf("LIST IS EMPTY\n");return ;}
    NodeL* cur=vNodeL->head;
    enum typeElements Type=vNodeL->head->T;
    
    while(cur!=NULL)
    {
        switch(Type)
        {
            case INT:
                printf("%d\n",*(int*)cur->data);
                break;
            case CHAR:
                printf("%c\n",*(char*)cur->data);
                break;
            case DOUBLE:
                printf("%f\n",*(double*)cur->data);
                break;
            case FLOAT:
                printf("%f\n",*(float*)cur->data);
                break;
            case CHARLINE:
                printf("%s\n",(char*)cur->data);
                break;
            case EPICLEGENDARYNODES:
              {
                EpicLEgendaryNodes *temp=(EpicLEgendaryNodes*)cur->data;
                printf("%s\n",temp->name);
                printf("%d\n",temp->Num);
                printf("%f\n",temp->weights);
                break;
              }
        }
        cur=cur->Next;
    }
}


//типовая прогонка списка 1 тест базовых типов 2 тест кастомной структурки
int main()
{
  //1
    VNodeL *tempBuf=malloc(sizeof(VNodeL));
    tempBuf->head=NULL;
    tempBuf->NodeSz=0;
    int t=4;
    float qq=0.1f;
    double qw=0.1f;
    char* strline=(char*)"CHARLINE";
    tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;
    strline="CHARLINE1";
    tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;
    strline="CHARLINE2";
    tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;
    strline="CHARLINE3";
    tempBuf->head=addNodeL(strline,CHARLINE,tempBuf);t++;qq++;qw++;

    printList(tempBuf);

    NodeL* search=getByIndex(tempBuf,4);
    printf("%d\n",tempBuf->NodeSz);

    tempBuf->head=setByAfterIndex(tempBuf,2,"CHARLINE10",CHARLINE);

    search=getByIndex(tempBuf,4);
    printf("\nSearched element\n%s\n\n",(char*)search->data);//см в printList
    tempBuf->head=FREENodeL(tempBuf);
    free(tempBuf);
    //tempBuf->head=NULL;
    //printList(tempBuf);
    
  //2
    VNodeL *tempBuf1=malloc(sizeof(VNodeL));;
    tempBuf1->head=NULL;
    tempBuf1->NodeSz=0;
    EpicLEgendaryNodes temp;
    temp.name="CHARLINE111111";
    temp.Num=t;
    temp.weights=qq;
    tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);t++;qq++;qw++;
    temp.name="CHARLINE22221";
    temp.Num=t;
    temp.weights=qq;
    tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);t++;qq++;qw++;
    temp.name="CHARLINE23333";
    temp.Num=t;
    temp.weights=qq;
    tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);t++;qq++;qw++;
    temp.name="CHARLINE34444";
    temp.Num=t;
    temp.weights=qq;
    tempBuf1->head=addNodeL(&temp,EPICLEGENDARYNODES,tempBuf1);
    printList(tempBuf1);
    tempBuf1->head=FREENodeL(tempBuf1);
    free(tempBuf1);
    //tempBuf1->head=NULL;
    //printList(tempBuf1);
    return 0;
}

Вывод:

CHARLINE
CHARLINE1
CHARLINE2
CHARLINE3


getByIndex
SearchIndex > SizeNodeLists
4

Searched element
CHARLINE3

CHARLINE111111
8
4.100000
CHARLINE22221
9
5.100000
CHARLINE23333
10
6.100000
CHARLINE34444
11
7.100000

gcc -Ofast -ffast-math -flto -msse4.1 main.c -lm

clang -Ofast -ffast-math -flto -msse4.1 main.c -lm

/nologo /MD /O2 /Ob2 /DNDEBUG - x64! msvc v19.40 VS17.10

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

При такой организации, конечно придётся хранить структуры рядом с перечислением.

Дополнительная информация:

*https://ru.wikipedia.org/wiki/Скелетная_анимация

**https://en.wikipedia.org/wiki/Linked_list

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


  1. Sazonov
    15.02.2025 23:12

    Чем это лучше существующих решений? В чём смысл статьи? Где код работы со скелетной анимацией, почему не используется GPU, и тп.

    Ваш текст выглядит как отчёт по лабораторной работе на 1 курсе колледжа.


    1. Jijiki Автор
      15.02.2025 23:12

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

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


      1. Sazonov
        15.02.2025 23:12

        Вы забыли ответить на главный вопрос: в чём смысл вашей статьи на Хабре?

        Зачем вы упомянули скелетную анимацию, если ни строчки про неё не написали?

        Зачем отладка через вывод в консоль, когда уже более четверти века есть нормальные отладчики?


        1. Jijiki Автор
          15.02.2025 23:12

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


          1. Sazonov
            15.02.2025 23:12

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


            1. Jijiki Автор
              15.02.2025 23:12

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


        1. Jijiki Автор
          15.02.2025 23:12

          в статье написано

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

          сомневаюсь что через дебагер сразу скопом с участием ГПУ вы разберетесь и с математикой и с листами и со всеми нюансами

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

          в статье указан пример анимации, указано что это такое, и то почему не массивы


          1. Sazonov
            15.02.2025 23:12

            Ну то есть написать printf, подсунуть туда нужные переменные - это по вашему проще чем 1 раз кликнуть мышкой (или хоткеем) для установки breakpoint?

            И всё таки - у вас ни слова про анимации. Какие будете использовать? Скелетные? Велосипед изобретёте? Функции работы с матрицами свои напишете? GPU будете использовать для матриц? Обосновать написание велосипедов, вместо использования готового сможете? Если нет, то мы возвращаемся к тому, что ваша статья - это лаба по спискам на си, которую вы почему-то решили оформить на Хабре.

            Это вот всё - очень большие темы, по ним люди книжки пишут. А у вас даже банальных примеров нет.


            1. Jijiki Автор
              15.02.2025 23:12

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

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

              Как пример: 10 часов видео гайда конвертируется в реальное обучение к примеру и это реальность осваивания/обучения и придётся еще обучаться смежным вопросам

              тоесть 10 часов гайда видео придётся еще понять, и попробовать разные моменты, чтобы придти к реализации комплексной, то что вы спрашиваете надо иметь реализацию, тоесть, как сделать модельку вас не волнует, вас волнует только анимация. то что на контент потратиться время вас тоже не волнует, ну а анимация, на С++ попроще может быть все сущности известны, пара гайдов есть, в том числе начиная с консоли тоже, тут как кому удобно изучать. А и потом разве есть единственное решение анимаций? есть тема в 3д что можно анимировать, как не крути это придётся изучать, и даже те люди кто пишет книги или гайды учаться.


              1. Sazonov
                15.02.2025 23:12

                Интересное у вас «вскользь», аж 2/3 текста «статьи».

                Кем и какая поставлена задача?

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

                Повторюсь - пока что весь ваш код и статья выглядят как лаба 1 курса, да и то средненькая. Впрочем, оценки за статью как бы не это намекают.


                1. Jijiki Автор
                  15.02.2025 23:12

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


              1. Jijiki Автор
                15.02.2025 23:12

                прошу прощения если не угодил в статье.


  1. DmitryKoterov
    15.02.2025 23:12

    Чо-то нет консистентности: ни PascalCase, ни camelCase, ни snake_case… что-то все время промежуточное и смешанное.


  1. Serpentine
    15.02.2025 23:12

    typedef struct NodeL_t NodeL;
    struct NodeL_t //структура списка
    {
        enum typeElements T;
        void* data;
        NodeL* Next;
    };
    typedef struct VNodeL_t VNodeL;
    
    struct VNodeL_t//структура хендлера
    {
      long NodeSz;
      NodeL *head;
    };

    Не знаю, на какой архитектуре сидите, но если на x64 у вас вероятно будет паддинг в 4 байта на экземпляр каждой из этих структур. Кроме того, в Win64 long обычно 32-битный.

    Если не знали про выравнивание структур, то можете почитать, например, перевод статьи Эрика Реймонда "The Lost Art of C Structure Packing".


    1. Jijiki Автор
      15.02.2025 23:12

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

      проверил да паддинг есть (NodeL 4 байта), инта хватит конечно )

      structure-member-alignment-padding-and-data-packing/

      Скрытый текст
      struct Meshes_t
      {
        vec3 *vertices;
        vec3 *colors;
        vec3 *normals;
        vec4 *boneIDs;
        vec4 *weights;
        unsigned int *indices;
      };
      typedef struct Meshes_t Meshes;
      
      struct Model_t
      {
        Meshes *meshes;
        VNodeL *vNodel;
      };
      typedef struct Model_t Model;

      чтото около рабочего всё равно будет что-то наподобии такого

      mat4 весит 64 - да кстати а их например с варешкой и скрепленным мешем - цельным 50 в сумме , спасибо что про паддинги напомнили


      1. Jijiki Автор
        15.02.2025 23:12

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

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


        1. VoodooCat
          15.02.2025 23:12

          Вы случайно не аудиофил? Код работает "мягче" - это характеристика - "мне нравится". И если аудиофилы страдают невозможностью объективных тестов за счет психоакустики - то в данной области - инструменты есть.


  1. Serpentine
    15.02.2025 23:12

     case CHARLINE:
                char* dd=d;
                tmp->data=malloc(sizeof(char)*strlen(dd));
                memcpy(tmp->data, dd, sizeof(char)*strlen(dd));
                vNodeL->NodeSz++;
                break;
    
    /*...*/
    case EPICLEGENDARYNODES:
                    EpicLEgendaryNodes *temp=(EpicLEgendaryNodes*)cur->data;
                    printf("%s\n",temp->name);
                    printf("%d\n",temp->Num);
                    printf("%f\n",temp->weights);
                    break;

    Это C23 что ли? В C за метками должны следовать операторы, тогда как объявления переменных не являются операторами, и в православных стандартах это вообще компилироваться недолжно. В С++ тоже - там в пределах одной области видимости запрещен переход минуя инициализацию, т.е. блок {} так и так нужен.


    1. Jijiki Автор
      15.02.2025 23:12

      вы про фигурные скобочки?

      Скрытый текст
      .L3:
              mov     edi, 16
              call    malloc
              movdqu  xmm0, XMMWORD PTR [r13+0]
              add     QWORD PTR [r12], 1
              mov     QWORD PTR [rbp+4], rax
              movups  XMMWORD PTR [rax], xmm0
              jmp     .L2

      там 2 свитча, в свитчах кейсы, первая часть что вы показали из стадии получения ноды, вторая часть после троеточия в стадии вывода ноды в консоль

      у меня не компилируется на кланге 19 версии, выше O1 - проблема в установке ноды, что-то урезается при этом компилируется ассемблерная часть, по тому что я заметил

      я клангом не пользуюсь, кланг стоит только для clangd, а создавал на gcc 14.2

      а извиняюсь. обнуление индекса при инициализации хендлера фиксит-тут пофиксил, просто в тесте не было. На обоих компилируется (clang 19/gcc 14.2)


      1. Jijiki Автор
        15.02.2025 23:12

        Скрытый текст
        case EPICLEGENDARYNODES:
        {
          EpicLEgendaryNodes *temp=(EpicLEgendaryNodes*)cur->data;
          printf("%s\n",temp->name);
          printf("%d\n",temp->Num);
          printf("%f\n",temp->weights);
          break;
        }

        вас понял да скобочки, спасибо пофикшу


        1. Serpentine
          15.02.2025 23:12

          Лучше перед публикацией скомпилировать код, который приводите в статье, с флагами -Wall -pedantic и исправить его, чтобы не было таких эксцессов.

          У вас еще, например, из кучи этих переменных:

          int t=4,i=1,q=2,w=3,e=5,r=6,y=7,u=8;

          часть вообще не используется.

          В setByAfterIndex() NodeL *tmp тоже не используется, строкой ниже вы пишете NodeL *tmp1=cur->Next;.

          Про неправильные спецификаторы типа в print() тоже покажет.

          Инициализацию NodeSz нулем вы исправили, тоже ее заметил. При компиляции MSVC программа просто упадет.

          Кстати, вот что он выводит, когда я ошибки с размером long`а, метками и NodeSz немного поправил:

          Скрытый текст
          CHARLINE¤¤¤¤
          CHARLINE1¤¤¤¤
          CHARLINE2¤¤¤¤
          CHARLINE3¤¤¤¤▌▌▌EУ░ж
          
          
          getByIndex
          SearchIndex > SizeNodeLists
          4
          
          Searched element
          CHARLINE3¤¤¤¤▌▌▌OТ╣↓Еж
          
          CHARLINE111111
          3
          1.100000
          CHARLINE22221
          4
          2.100000
          CHARLINE23333
          5
          3.100000
          CHARLINE34444
          6
          4.100000

          Думаю, перед использованием вам его еще много придется дебажить.


          1. Jijiki Автор
            15.02.2025 23:12

            тоесть вы скопировали то что в статье и у вас вывелось так да?, платформа Linux извините.


            1. Serpentine
              15.02.2025 23:12

              Да. Вот что выдает при версии, что вы исправили сейчас:

              Скрытый текст

              CHARLINE¤¤¤¤
              CHARLINE1¤¤¤¤
              CHARLINE2¤¤¤¤
              CHARLINE3¤¤¤¤

              getByIndex
              SearchIndex > SizeNodeLists
              4

              Searched element
              CHARLINE3¤¤¤¤

              CHARLINE111111
              3
              1.100000
              CHARLINE22221
              4
              2.100000
              CHARLINE23333
              5
              3.100000
              CHARLINE34444
              6
              4.100000


              1. Jijiki Автор
                15.02.2025 23:12

                а где вы запускаете?

                Скрытый текст

                а что в принт не так простите?


                1. Serpentine
                  15.02.2025 23:12

                  Win10 x64. Visual Studio 2019.


                1. Serpentine
                  15.02.2025 23:12

                  Windows 10 x64. Visual Studio 2019. Стандарт С17.

                  Сборка с Release тоже выдает всякий мусор

                  Скрытый текст
                  CHARLINEP☺Л     М☺
                  CHARLINE1
                  CHARLINE2
                  CHARLINE3
                  
                  
                  getByIndex
                  SearchIndex > SizeNodeLists
                  4
                  
                  Searched element
                  CHARLINE3
                  
                  CHARLINE111111
                  3
                  1.100000
                  CHARLINE22221
                  4
                  2.100000
                  CHARLINE23333
                  5
                  3.100000
                  CHARLINE34444
                  6
                  4.100000

                  На первую строчку взгляните. Читаем что-то из неинициализированной памяти.


                  1. Jijiki Автор
                    15.02.2025 23:12

                    понял, спасибо вижу. Простите


                    1. Serpentine
                      15.02.2025 23:12

                      Небольшая подсказка. Взгляните на getNode()

                              case CHARLINE:
                                  //char* dd=d;
                                  //tmp->data=malloc(sizeof(char)*strlen(dd));
                                  //memcpy(tmp->data, dd, sizeof(char)*strlen(dd));
                                  //vNodeL->NodeSz++;
                                  tmp->data=malloc(sizeof(char)*strlen((char*)d));
                                  memcpy(tmp->data, d, sizeof(char)*strlen((char*)d));
                                  vNodeL->NodeSz++;
                                  break;

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


                      1. Jijiki Автор
                        15.02.2025 23:12

                        спасибо исправил.

                        обновил информацию как я проверял


                      1. Serpentine
                        15.02.2025 23:12

                                case CHARLINE:
                                    char* dd=d;
                                    size_t szS=(size_t)strlen(dd)+1;
                                    tmp->data=malloc(sizeof(char)*szS));
                                    memcpy(tmp->data, d, sizeof(char)*szS);
                                    dd[szS]='\0'; /* WTF??? */
                                    vNodeL->NodeSz++;
                                    break;

                        Вы куда терминальный ноль записали, а главное для чего?


                      1. alef13
                        15.02.2025 23:12

                        предлагаю заменить на strdup


                      1. Jijiki Автор
                        15.02.2025 23:12

                        в Windows https://learn.microsoft.com/ru-ru/cpp/c-runtime-library/reference/strdup-wcsdup?view=msvc-170 и она кидает предупреждение, которое можно отключить (уровень 3 :C4996), поэтому даже не знаю как лучше


  1. SpiderEkb
    15.02.2025 23:12

    Какой у вас размер списка (сколько элементов в типовых сценариях)?

    Я про поиск по индексу перебором - не будет ли это слишком долго?

    Может стоит присмотреться к алгоритму SkipList

    https://15721.courses.cs.cmu.edu/spring2018/papers/08-oltpindexes1/pugh-skiplists-cacm1990.pdf

    и его расширениям

    https://webdiis.unizar.es/asignaturas/APD/skip_list_cookbook.pdf


    1. Jijiki Автор
      15.02.2025 23:12

      спасибо большое посмотрю, первичная проборка 50(на меш 25 костей - упрощенный скелет без пальцев ) - этот этап пока не проверял, далее надо будет загнать веса и посмотреть, толи 4*вершины, толи 1 в 1 если учесть что вектор-веса xyzw, в гифке чтото около грубого расчета по вершинам, очень грубого в большую сторону 2к

      Скрытый текст
      1310 Vertices
      1310 Colors
      1310 Normals
      1314 Indices
      1310 BoneIDS
      1310 Weights
      
      Num Bones 25#list
      
      2128 Vertices
      2128 Colors
      2128 Normals
      2216 Indices
      2128 BoneIDS
      2128 Weights
      
      Num Bones 25#list
      LIST NUMBER ELEMENTS: 50
      mixamorig:Hips
      100.000000 0.000001 -0.000000 0.000000
      -0.000000 -0.000004 -100.000000 0.000000
      0.000001 100.000015 -0.000004 0.000000
      0.000002 -413.847046 13.272202 1.000000
      mixamorig:Spine
      100.000000 0.000001 0.000000 0.000000
      -0.000000 3.754293 -99.929504 0.000000
      0.000001 99.929504 3.754293 0.000000
      0.000002 -458.326569 -2.274207 1.000000
      mixamorig:Spine1
      ...


      1. SpiderEkb
        15.02.2025 23:12

        Ну это немного... Я привык работать со списками в сотни тысяч элементов.

        В вашем случае, возможно, перебором будет достаточно.

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

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

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


        1. Jijiki Автор
          15.02.2025 23:12

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


        1. alef13
          15.02.2025 23:12

          а ещё лучше дерево :)


          1. SpiderEkb
            15.02.2025 23:12

            Не всегда. Дерево хорошо для поиска. И то при условии что оно сбалансировано (что при постоянном добавлении-удалении элементов будет требовать дополнительных затрат).

            Но вот при обходе "от первого до последнего элемента" дерево будет хуже списка.


            1. unreal_undead2
              15.02.2025 23:12

              Кстати попробовал - обход обычного std::map тормознее std::list раз в пять (в одних конкретно взятых условиях). Не думал, что разница настолько большая.


              1. SpiderEkb
                15.02.2025 23:12

                Ну я за свою специфику могу говорить - большие объемы данных + HiLoad (все что делаем проходит через нагрузочное тестирование).

                В моей специфике деревья как-то не прижились. Два основных сценария

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

                2. Справочники в памяти. Тут справочник один раз заполняется при старте, потом многократно используется для поиска нужных данных. Здесь лучше всего работает отсортированный массив с двоичным поиском по нему (самый быстрый поиск). Заполнили, один раз отсортировали и потом пользуемся. Тут, в принципе, деревья тоже неплохи, но они сложнее в реализации и накладных расходов там немного больше.


              1. Jijiki Автор
                15.02.2025 23:12

                теперь сравните vector и list)

                1000 елементов 100 000 и 1 000 000 елементов


                1. Jijiki Автор
                  15.02.2025 23:12

                  на кланге посмотрел код, тоже компилируется


                1. unreal_undead2
                  15.02.2025 23:12

                  Тут даже смысла особого нет, и так всё понятно ) Но если смотреть list vs map - и там и там pointer chasing, в мапе всё посложнее, но казалось бы можно сделать похоже.


                  1. SpiderEkb
                    15.02.2025 23:12

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

                    А список - это именно упорядоченная последовательность элементов. Там каждый элемент содержит указатель на следующий. Но там сложнее реализовать поиск элемента по значению (кроме перебора). Потому и придумали SkipList список с возможностью быстрого не такого быстрого как по дереву, но быстрее чем перебором) поиска. Именно для тех ситуаций когда нужно потом с ним работать как со списком и "гулять" по нему вперед-назад.

                    Затем и нужно понимание "что под капотом" таких сущностей как list, vector, map... чтобы под конкретный сценарий использования выбирать наиболее эффективный подход.


                    1. Jijiki Автор
                      15.02.2025 23:12

                      на фрибсд классно работает я тест С++ глянул(но нагрузку не посмотрел на CPU linux от фряхи отличаются некоторыми функциями- на линуксе были всплески по частоте на анимации, которая на гифке тут. а на бсд вроде не было всплесков, потом посмотрю внимательнее ), и фрибсд стал дружелюбнее прям не верится, возможно не в тему, ставится быстрее чем раньше, все доки поправили до текущих, он настраивается прям из хендбука минут за 5, подвезли блендер ассимп вулкан, на гноме красота


      1. Jijiki Автор
        15.02.2025 23:12

        по итогу 3 списка, но будет около 10, вообщем до 100 чтоли как я понял будет. я эти моменты еще посмотрю, ну грубо говоря 10 списков по 100 елементов это по максимуму - фантазирую, но на самом деле грубо прикинул, потомучто 2 комплекта бонов, и трансформации - это только начало, там еще сами анимации - так сказать в анимациях, то на чем посмотрел быстро вроде работает, ключевая нагрузка всё равно будет в цикле при обновлении, там будет видно


  1. slonopotamus
    15.02.2025 23:12

    Вся статья сводится к

    struct LinkedList
    {
        void* data;
        struct LinkedList* next;
    };
    

    Всё, я сделал односвязный список на C.


  1. kekoz
    15.02.2025 23:12

    Осталось научиться использовать man :)

    apropos slist