Публикации

Igor
О, интересно. Некоторые идеи мне в голову не приходили.

Мне кажется, разместить два объекта для пружины подряд не получится, потому что к объекту может быть присоединено много разных пружин. Но в целом идея хорошая, может для чего-то ещё подойдёт. Я точно знаю, что так можно сделать иерархию объектов и положить всех детей после родителя, но у меня сейчас нет иерархии в принципе :)
Кстати, для итерации конкретно по пружинам я их в линейный массив складываю и по нему бегу, а не по HashMap. Так действительно быстрее.

Про дублирование данных я почему-то вообще не думал.

Но в общем и целом JVM не даёт возможности класть объекты в памяти как захочется и ECS радикальных бонусов производительности не даёт. Но мне оно просто понравилось как способ огранизации кода и масштабировая программы дописыванием новых компонент вместо выстраивания сложной иерархии.

У меня ещё сильный упор на то, что код должно быть легко писать и менять, чтобы я в одиночку быстро его писал и не сильно страдал от багов, с JVM в этом плане попроще.
Igor
Ещё вариант — есть sshfs, который под капотом работает как scp и от сервера требует только установленный ssh.

И дальше из примонтированной по sshfs папки можно выборочно докопировать файлы, если нужны не все. Ещё вроде можно указать опции для реконнекта.

rsync круто копирует, но в нём туева куча параметров и настроек и они все на что-то влияют. Например, rsync может по-разному определять, надо ли копировать файл — может по таймстампу создания или редактирования, может использовать rolling hash, может ещё как. Ещё вопрос, что rsync будет делать с симлинками. Мне обычно лень во всём этом разбираться, я один и тот же скрипт копирую и какие-то опции по необходимости добавляю/убираю.
Комментарий отредактирован: 10 февраля 2025, 19:27
Igor
Я до этого прочитал цикл статей ECS back and forth и пробовал сделать примерно как там.

И я пытался код более абстрактным и эффективным сделать, получилась какая-то неудобная фигня.

У тебя многое по-другому сделано, при чтении была куча wtf-моментов, но интересно получилось. Например, у тебя просто массив фильтров и кеш объектов.

Я вместо этого хранил хеш-таблички entity->T, а когда вызывался фильтр, например, для двух компонент, я просто брал две хеш-таблички, выбирал ту что меньше, итерировался по её объектам и проверял наличие вторых.

Как итог у меня было почти бесплатное добавление-удаление компонентов (просто поменять одно значение в табличке), но не такая эффективная итерация по объектам. А у тебя наоборот — итерация вроде эффективная, но добавление компонента или фильтра требует обновлять кеш.
Комментарий отредактирован: 12 января 2025, 15:46
Igor
А, я кажется понял. Просто в моём подходе компоненты были тупые, а у тебя каждый компонент ещё знает его родительскую entity. Поэтому часть логики иначе сделана.
Igor
Хм, а годот при удалении ноды Target автоматически занулит все ссылки на неё?

Что если, допустим, где-то осталась ссылка на компонент противника, а противник уже убит и даже убран с карты?

Ещё вопрос.
github.com/nakoff/Core-Godot/blob/main/Framework/Components/DamageComponent.cs
Получается, за один игровой цикл нельзя получить урон два раза?

Кстати по коду — я видел идею (и мне самому так понравилось) по папкам раскладывать по смыслу — типа система + компоненты которые она использует, а не так что в одной папке только системы и в другой только компоненты.
Igor
А как сделано удаление и ссылки из компонентов других Entity на удаляемую Entity? Особенно с учётом того, что у тебя компоненты могут ссылаться и на другие ноды (которые могут быть ещё и компонентами).

Просто я видел разные подходы и они все были со своими недостатками.

Ещё вопрос про компоненты. У тебя можно прицепить несколько компонент одного типа к одному объекту? Выглядит так что можно (раз в коде есть компонент типа DamageReceiveComponent).

Я пытался свой ECS сделать, и кажется так и не нашёл баланса между удобством использования ECS и жёсткостью ограничений (которые позволили бы делать всякие крутые оптимизации).
Я пробовал ограничить, что ссылки из компонентов могут быть только на Entity и чтобы компонент какого-то типа мог быть только один. Для случаев когда входящего урона, например, может быть несколько, я делал компонент со списком элементов.
И даже в таком случае решение задачи типа «хотим сохранить снапшот мира со всеми ссылками» уже выглядело довольно костыльно.
Комментарий отредактирован: 11 января 2025, 20:56
Igor
https://github.com/Kright/mySmallProjects/tree/master/2024/wellcome2valhalla
Вот мой код с уже готовым проектом если кто захочет по-быстрому попробовать.
Igor
Для истории — я сделал четыре класса.
изменяемый, с final полями, record и Value класс. Rysen 3700x, VM version: JDK 23.0.1, OpenJDK 64-Bit Server VM, 23.0.1+11-39

Benchmark                           Mode  Cnt      Score     Error  Units
BenchValhalla.finalPoint            avgt    5   3399.923 ±  88.193  us/op
BenchValhalla.plainOldMutablePoint  avgt    5  52134.455 ± 942.619  us/op
BenchValhalla.recordPoint           avgt    5   3354.371 ±  56.565  us/op
BenchValhalla.valhallaPoint         avgt    5   3385.298 ±  39.188  us/op
Igor
Я попробовал на очень простом примере. Как ни странно, на моём экспериментальном и очень простом примере value класс в два раза медленнее.

Как я сделал — скачал экспериментальную сборку JDK, распаковал. Скачал отсюда: jdk.java.net/valhalla/. Прям в папочку bin положил класс с файлом и вызвал его как /java --enable-preview --source 23 ValuePoint.java


public value class ValuePoint {
    public final double x;
    public final double y;

    public ValuePoint(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public ValuePoint add(ValuePoint p) {
        return new ValuePoint(x + p.x, y + p.y);
    }

    public ValuePoint sub(ValuePoint p) {
        return new ValuePoint(x - p.x, y - p.y);
    }

    public String toString() {
        return "ValuePoint2(" + x + "," + y + ")";
    }

    public static void main(String[] args) {
        ValuePoint[] arr = new ValuePoint[1000];

        for (int i = 0; i < arr.length; i++) {
            arr[i] = new ValuePoint(i, i);
        }

        for (int k = 0; k < 100; k++) {
            long start = System.nanoTime();
            ValuePoint sum = new ValuePoint(0, 0);
            for (int i = 0; i < arr.length; ++i) {
                for (int j = 0; j < arr.length; ++j) {
                    sum.add(arr[j].sub(arr[i]));
                }
            }
            long end = System.nanoTime();
            System.out.println("sum = " + sum + " time = " + (end - start) + "ns");
        }
    }
}

Без слова value шаг вычисления внутри цикла завершается за 0.7 мс, с ним за 1.3мс.
Комментарий отредактирован: 28 декабря 2024, 17:15
Igor
В принципе можно, но по закону Ньютона сила будет действовать на оба мотоцилка :) В реальности мотоциклисты стараются не контактировать ни с чем, очень высокая вероятность улететь. Ещё скажу, что в реальности пилот очень сильно влияет на мотоцикл тем как сидит и держится за руль, например если вцепляться в руль — можно поймать вобблинг и начнутся автоколебания руля влево-вправо или наклониться влево-вправо и повлиять на баланс мотоцикла. Вот этих нюансов к сожалению почти никак не передать.

Вообще тема мотоциклов мне интересна, но без вестибулярного аппарата и на гоночном круглом руле управлемость как у пьяного самокатчика. Это совсем не то, что я чувствовал на реальном мотоциле.

Может быть, что-то такое можно сделать, если как в игральных автоматах поставить модель мотоцикла, сделать мощный и точный большой руль с force feedback и ещё какие-нибудь приводы для наклона мотоцикла влево-вправо (тоже с force feedback, чтобы чувствовать баланс веса игрока). Потому что мотоцикл рулится контррулением и это очень требовательно к точности и скорости реакции руля.
Igor
Интересно, я так глубоко не забирался.
Кстати насчёт кодогенерации и прочих ухищрений — в скале есть макросы и inline функции, можно с их помощью что-то навертеть.
Igor
Конкретно в 3д графике обычно в тип vector3 обычно пихают подряд и координаты и смещения между точками, хотя по аналогии с таймспаном вектора и точки можно назвать чуть разными типами.

Эта разница вылазит в проективной геометрии, когда точку записывают (x, y, z, 1), а вектор как (x, y, z, 0), тогда матрица вращения крутит и то и то, а матрица трансляции двигает только точку.
Igor
Опять же, смотри какая фишка — есть тип-дата и есть тип-timespan, который описывает расстояние между двумя датами.
Это два разных типа с разными свойствами. Например, timeSpan можно умножить на число, а дату — бессмысленно. Или например сумма timeSpan + date это date, а сумма date + date это некий объект, который датой не является.

Т.е., чуваки догадались до полезности разных типов, но пишут их всё ещё руками — отдельно Date и отдельно TimeSpan.
Igor
Ну, а как ты потом запишешь, допустим, скорость? Будешь ещё один тип руками писать и делать конструктор из расстояния и времени? Там дофига типов будет кроме скорости, я в комменте выше перечислил
Комментарий отредактирован: 21 ноября 2024, 23:50
Igor
Да. Я про то, что единиц измерения много, есть их различные сочетания. Не хочется вручную писать классы для времени, расстояния, скорости, ускорения, силы, момента силы, мощности, энергии, иммульса с опять же вручую придуманными правилами преобразования. Потому что кто-нибудь потом напишет E = 0.5 m * v^2 и окажется, что я в списке типов забыл скорость в квадрате или ещё что-то.

Правила умножения-деления величин с размерностями в абстрактном виде одни и те же. В java я могу на дженериках сделать числа Пеано типа `Value<Zero, Next<Next>, Negate<Next>`, но это будет извращение и вместо этого начинаются извращения с венгерской нотацией типа long timeMs; double speedKmh; и т.п.
Igor
Ну смотри, по высоте твоё сообщение сильно выше чем аватар. Так что по вертикали оно ничего не съест.
По горизонтали съест, но по горизонтали вроде нет дефицита места, монитор сильно шире чем сайт
Igor
Ну хз, мне очень сильно хабр напоминает.
Igor
Кажется, плюс к сообщению даёт +0.1