Так, а зачем вообще тащить эти костыли в движок? Все анимации лучше всего хранить в префабах (небольшие скриптовые файлы содержащие в себе всю логику взаимодействия анимаций, эвенеты, нарезка длинных анимаций на короткие, смешивание, скорость воспроизведения, цикличность воспроизведения, ИК кости и прочие констрейнты с их настройками, и т.д.), а из кода уже просто обращаться к этому компоненту как к отдельному классу и его Стейтам.
__________________
У котёнка мокрый нос и гладенькая шерсть, у него забавный хвост и быстрых лапок шесть. Две задних, две средних и две передних лапы, такая многоножка получилася у папы.
Он ученый — папа мой — зверушек изучает, гуляет по помойкам, ловит крыс и чаек. Две крысы белокрылые и чайки две унылые покрытые пупырчатою кожей лягушат без пёрышек тоскуют и ускакать спешат.
А ещё есть муравей большой размером с гуся он пугает всех зверей, и я его боюся, когда он ковыляет на лапках на своих.
И в двери ударяет, и начинает стих: Я — муравей, воды налей! Не меньше ведра, напиться мне пора!
Небольшая демонстрация того, на каких тоненьких ниточках в С++ держится детектирование тех или иных конструкций. В чистом Си, напомню, таких ситуаций не могло возникнуть в принципе. Напомню также, что компилятор в первую очередь ориентируется по синтаксису и только во вторую - по типу, причём синтаксис имеет определяющее значение.
Конструкция, помеченная стрелочкой, до вчерашнего дня ошибочно трактовалась компилятором как объявление новой функции. А поскольку функция внутри функции невозможна, то естественно выпадала ошибка. Почему так происходило? Первые два токена в этом плане не могут служить чётким критерием, при условии, что первый токен - реально существующий тип, а второй - имя. Дальше у нас есть ветвление, либо это переменная (неважно в сущности, локальная или глобальная), либо действительно объявление новой функции. В чистом Си, наличие открывающей круглой скобки уже явным образом говорило - перед нами объявление новой функции. В С++ это не так, ведь это может быть вызов кастомного конструктора. То есть, даже прочитав скобку, мы всё еще не можем определить что перед нами. Надо читать то что внутри скобок. Я не знаю как это устроено в самом С++, но я лично делаю лукап на несколько токенов вперёд (обычно на один, не больше), чтобы оценить обстановку, а потом возвращаюсь на текущее место. Так вот, в моменте ветвления, когда токен ( уже прочитан, а следующий находится в памяти, на первый взгляд мы можем просто посмотреть что это за токен и сделать выводы. Если токен это целое число, дробное число, строка или же пунктуация, то это 100% вызов конструктора. Следующая проверка - на наличие модификаторов или declspec. Если они есть - значит перед нами декларация новой функции. Разумеется они быть не обязаны. Следующая проверка на то, что следующий токен не является известным компилятору типом. И тут всё и падает с ошибкой. Потому что g_vecZero выглядит вот так:
C++ Source Code:
#define g_vecZero vec3( 0.0f )
Таким образом компилятор читает слово vec3, находит такой тип и думает - это объявление функции. Ну и дальше идёт ругань про невозможность объявления функции внутри функции. И невозможность распарсить её аргументы. А потому что кроме вызова пользовательских конструкторов в С++ есть ещё r-value объекты, которые добавляют ещё хаоса. И теперь к типу дополнительно надо проверять так же наличие очередной открывающей круглой скобки, чтобы убедиться что перед нами.
А ведь в теории конструктор может быть шаблонной функцией...
Дядя Миша писал: Конструкция, помеченная стрелочкой, до вчерашнего дня ошибочно трактовалась компилятором как объявление новой функции
Но это и есть объявление новой функции. Согласно принципу MVP в плюсах, "всё, что выглядит как объявление функции, есть объявление функции".
Чтобы избежать этого, в С++11 имеется инициализация фигурными скобками. Ну или по классике:
C++ Source Code:
auto transform = xform(...);
Что абсолютно эквивалетно приведённому выше коду, но не выглядит как объявление функции.
XaeroX писал: "всё, что выглядит как объявление функции, есть объявление функции".
Осталось только определиться с критериями. Потому что единственным способом отдуплить объявление функции от вызова конструктора является анализ того что между скобками.
Я пока не знаю так хорошо синтаксис, но что тут нелегального? Вызов функции, и задаёшь ей аргументы. Только мне не понятно что есть xform и зачем он тут? Тогда уж как то так myvar = xform.transform( g_vecZero, vec3( 0.0f, m_absangles.y, 0.0f ));
__________________
У котёнка мокрый нос и гладенькая шерсть, у него забавный хвост и быстрых лапок шесть. Две задних, две средних и две передних лапы, такая многоножка получилася у папы.
Он ученый — папа мой — зверушек изучает, гуляет по помойкам, ловит крыс и чаек. Две крысы белокрылые и чайки две унылые покрытые пупырчатою кожей лягушат без пёрышек тоскуют и ускакать спешат.
А ещё есть муравей большой размером с гуся он пугает всех зверей, и я его боюся, когда он ковыляет на лапках на своих.
И в двери ударяет, и начинает стих: Я — муравей, воды налей! Не меньше ведра, напиться мне пора!
Возникла потребность сделать перегрузку макросов.
Насколько я понял ни одна студия так и не обзавелась этой полезной возможностью в явном виде. Везде какие-то костыли с variadic.
Интересно почему? Может я что-то упускаю из виду?
Но мне кажется сделать перегрузку просто по числу аргументов вполне себе реально. Будем попробовать.
Дядя Миша писал: Насколько я понял ни одна студия так и не обзавелась этой полезной возможностью в явном виде. Везде какие-то костыли с variadic.
В блокнотике и такого нет
__________________
У котёнка мокрый нос и гладенькая шерсть, у него забавный хвост и быстрых лапок шесть. Две задних, две средних и две передних лапы, такая многоножка получилася у папы.
Он ученый — папа мой — зверушек изучает, гуляет по помойкам, ловит крыс и чаек. Две крысы белокрылые и чайки две унылые покрытые пупырчатою кожей лягушат без пёрышек тоскуют и ускакать спешат.
А ещё есть муравей большой размером с гуся он пугает всех зверей, и я его боюся, когда он ковыляет на лапках на своих.
И в двери ударяет, и начинает стих: Я — муравей, воды налей! Не меньше ведра, напиться мне пора!
Ну кстати вот первый спорный момент. #undef будет удалять всю группу перегруженных макросов. В принципе #undef используется редко, так что я думаю это не должно привести к каким-либо проблемам.
В текущей кодобазе на шоте он у меня вообще так ни разу и не понадобился. Но можно впоследствии сделать расширенный ундеф, который будет считать кол-во аргументов, например.
Чтож, наличие перегрузки макросов автоматически делает препроцессор более строгим к ошибкам, хотя по факту это всё соблюдалось и ранее.
Я вывел следующее правило наличия аргументов:
1. Если это макрос вида #define a или #define a b, то кол-во аргументов равно -1
2. Если это макрос вида #define a() то кол-во аргументов равно 0
3. Если это макрос вида #define foo( a, b, c ) то кол-во аргументов равно их кол-ву указанному пользователем. В данном случае три.
Здесь понятно и очевидно. Мы создаём лист наших макросов где под одинаковыми именами могут прятаться макросы с разным числом аргументов.
Иными словами сейчас можно объявить:
C++ Source Code:
#define foo
#define foo()
#define foo( a )
И это будет нашей перегрузкой. Если мы просто вызываем макрос для раскрытия в коде, то всё хорошо. Проблемы начинаются когда мы начинаем обращаться к нашему макросу средствами самого препроцессора. Для этого у нас есть следующие команды:
C++ Source Code:
1
#if
2
#ifdef
3
#ifndef
4
#elif
5
#undef
Тут уже намечается некоторое несоответствие причём в оригинальном препроцессоре. К примеру конструкцию #define foo( a ) бессмысленно проверять на #if. На #if проверяются вещи, типа #define foo 1 которые заведомо не имеют аргументов. В данном случае поиск макроса для условия #if будет вообще игнорировать те, что имеют аргументы. Тут логика не нарушется, всё в порядке. С остальными директивами некоторая неоднозначность присутствует. Как видно из примера выше мы объявили три перегруженных foo. Какой из них будет реагировать на команду #ifdef foo? Только #define foo!
Если мы хотим проверить наличие и остальных аргументов, то нам следует написать #ifdef foo( a ) к примеру. Но тут-то как раз и кроется засада.
Дело в том, что вот это вот a - оно там просто от балды. Парсеру всё равно что там будет, ему лишь надо посчитать кол-во аргументов.
Аналогичная ситуация и с undef. Т.е. некоторая неоднозначность будет присутствовать. Опять же команды препроцессора живут молча - если им что-то не удалось, не выводится никаких ошибок или предупреждений.
Добавлено 20-07-2024 в 19:23:
Надо заметить что это крайне непростое дело - перегрузка макросов.
Тут и совместимость мешает и сам факт что препроцессор нестрогий.
То есть условия #ifdef теперь ужесточаются но при этом сохраняют обратную совместимость. Пример:
C++ Source Code:
1
#define FOO( a )\
2
printf( "macro1: %d\n", a );
3
4
#define FOO( a, b )\
5
printf( "macro2: %d %d\n", a, b );
6
7
#define FOO( a, b, c )\
8
printf( "macro3: %d %d %d\n", a, b, c );
9
10
void main( void )
11
{
12
#ifdef FOO
13
FOO( 1 );
14
#endif
15
16
#ifdef FOO
17
FOO( 1, 2 );
18
#endif
19
20
#ifdef FOO
21
FOO( 1, 2, 3 );
22
#endif
23
}
Ни одно из условий #ifdef выполнено не будет. Потому что препроцессор теперь считает что мы ищем объявление #define FOO. Т.е. здесь всегда осуществляется проверка на строгое соответствие и разумеется, меняется синтаксис.
C++ Source Code:
1
#define FOO( a )\
2
printf( "macro1: %d\n", a );
3
4
#define FOO( a, b )\
5
printf( "macro2: %d %d\n", a, b );
6
7
#define FOO( a, b, c )\
8
printf( "macro3: %d %d %d\n", a, b, c );
9
10
void main( void )
11
{
12
#ifdef FOO( 1 )
13
FOO( 1 );
14
#endif
15
16
#ifdef FOO( 1, 2 )
17
FOO( 1, 2 );
18
#endif
19
20
#ifdef FOO( 1, 2, 3 )
21
FOO( 1, 2, 3 );
22
#endif
23
}
Этот код уже не прожуется ни Си ни С++ препроцессором - он просто не ожидает что после имени макроса ещё и аргументы. Причём, прошу заметить - их может и не быть. Тогда ищется декларация
#define FOO или #define FOO BAR. Но ведь никто никогда и не проверяет на #ifdef макросы с аргументами! Они служат вообще для другого.
Технически эта возможность есть и в старом препроцессоре, просто так никто не делает - это операция заведомо лишена смысла.
Пришло время добавить в язык ещё одну очень важную вещь - Direct Member Initializers. Как вы знаете, можно инициализировать переменные, объявленные в глобальном пространстве и переменные, объявленные внутри функции, а вот переменные-члены класса нельзя проинициализировать прямо там же внутри описания класса. Конечно есть member initializer list, но вы представьте на что будет похожа запись, если в вашем классе десятки членов? И как не запутаться что и чем вы там проинициализировали.
Лично мне эта штука нужна для более удобной декларации абстрактных переменных, я не знаю как это правильно назвать. Ну вот помните в халфе была такая структурка как movevars_t ?
Там ещё эти переменные можно было сохранять в конфиг, они по сети пересылались и их можно было менять из консоли. Вот, при помощи Шота я хочу сделать такой абстрактный и универсальный механизм для объявления таких кваров. У меня в сущности уже есть все компоненты для этого, но нет объявления по дефолту и это весьма неудобно. То есть можно и обойтись, но будет некрасиво.
в С11 насколько я понимаю ввели вот такую уродливую конструкцию
C++ Source Code:
1
class CFoo
2
{
3
int n = 7;
4
std::string s{'a', 'b', 'c'};
5
};
Как вы видите атомарные переменные инициализируются через присвоение, а классы - через фигурные скобки. Собственно, оно и так и эдак плохо. Потому что никакой реальной инициализации в этом месте всё равно не происходит. Плюс тут ещё встаёт вопрос, как задавать bitfield при таком раскладе, но ладно. Мой поинт в том, чтобы инициализировать такие переменные как конструкторы. Ну то есть
C++ Source Code:
1
class CFoo
2
{
3
int n( 7 );
4
std::string s( 'a', 'b', 'c );
5
};
Вы уже догадались в чём тут сложность? Да, это очередной случай MVX.
Но ведь подобное же объявление возможно в глобальном пространстве или внутри функции. То есть это уже вполне решаемая проблема. Да и выглядит оно так аккуратнее, т.к. конструктор не ассоциируется с явным присвоением, да собственно и не должен. Единственный негативный момент - вы не сможете это проинициализировать с скобками без аргументов, т.е. (); Потому что компилятор тогда решит что вы объявили функцию. Но ведь и с объявлением переменных на стеке и в куче обстоит ровно так же! Мы просто не пишем эти скобки и вызывается конструктор по умолчанию. Так что я планирую придерживаться именно этого способа.
Единственный минус - так нельзя проинициализировать массив, но ведь и вне объявления класса его невозможно так проинициализировать. Следовательно нововведение не нарушает целостность языка, что лично для меня самое главное. Чтож, попробуем реализовать вышеописанное.
Добавлено 06-08-2024 в 19:30:
Любопытный момент отмечу. Как выяснилось, мне совсем не пришлось вводить в компилятор какие-то дополнительные проверки для подобной формы записи, всё необходимое уже существовало, достаточно было лишь добавить новое условие и всё корректно распарсилось. Но! В процессе имплементации мне пришла в голову ещё одна весьма важная мысль.
Как все мы знаем при объявлении функции можно писать так:
C++ Source Code:
void foo( void )
А можно вот так
C++ Source Code:
void foo()
Причём в С++ этот как бы не регламентируется, мол хотите так пишете, хотите эдак, разницы нет. А между прочим это весьма и весьма важный момент. Ведь нельзя же вызвать конструктор вот так:
C++ Source Code:
int foo( void );
Т.е. это у нас однозначное определение функции. А в С++ присутствует неоднозначность в этом плане. Правда бедные программисты, которые очень бояться перетрудится и написать несколько лишних букв в массе своей стараются не писать это слово void, даже несмотря на регулярные заявления о том, что 90% времени программист не пишет код, а читает его, ну а я знаю, что только тех кто любит труд - октрябятами зовут!
Поэтому я всегда прописывал слово void в объявлении функций без аргументов. Нет, если серъезно, мне кажется что так красивее выглядит, поэтому и писал. Ну а в С++ этот момент сломать видимо уже не могли.
Правда я хочу отметить что и мне его вовсе не надо ломать для правильного объявления этих конструкторов по умолчанию. Оно и так будет прекрасно работать, это просто наметка на будущее, чтобы избежать неоднозначности. Потому что в языке, парсер которого смотрит лишь на один токен вперёд имеет значение абсолютно всё. Это вам не Раст, который анализирует исходник целиком.
Добавлено 06-08-2024 в 20:25:
Новая фича готова. Пример кода:
C++ Source Code:
1
class vec3
2
{
3
float x, y, z;
4
public:
5
vec3( float in_x, float in_y, float in_z )
6
{
7
x = in_x;
8
y = in_y;
9
z = in_z;
10
Msg( "%s( %g %g %g )\n", nameof( this ), x, y, z );
11
}
12
13
vec3()
14
{
15
Msg( "%s()\n", nameof( this ));
16
}
17
18
virtual ~vec3() {}
19
};
20
21
class CObject
22
{
23
vec3 origin( 10.0f, 20.0f, 30.0f ); // direct member initialization
24
vec3 angles( 30.0f, 20.0f, 10.0f ); // direct member initialization