Pull to refresh
28
0
Николай Шалимов @nshalimov

Principal Developer

Send message

Думаю, что к этому мы ещё придём.
А для транзактных изменений у нас тоже есть инструменты, но используются они явно. Код в статье слишком упрощён, я не стал там показыать всё. Делается как-то так


using (viewModel.Lock())
{
    // тут мы меняем свойста
} // тут срабатывают все затронутые диспатчеры

1) Согласен. Есть такой план, даже расширенный – уметь ещё и десериализовать ViewModel. Очень пригодится для автотестов интерфйса. Автотест загружает префаб, пихает в нго условный джсон и смотрит, что получилось. Огромный плюс в том, что при реализации такого тестирования можно максимально абстрагироваться от кода самого проекта.
2) Да, возможно это зло. Но почти за два года использования связанных с этим проблем возникло исчезающе мало. Я подумываю, что тут можно сделать, но меня останавливает нежелание чинить то, что не сломано.

Строго говоря константы там не строковые, ключи имеют тип PropertyName обычно их значениня уносятся в константы. Хотя есть желание отказаться от этого в пользу обычных строк. Но я так понимаю, вы не можете «простить» именно «динамичность» вьюмодели? Если так, то дискуссия рискует перетечь в плоскость очердного обсуждения «статика vs динамика». Мы и сами туда часто скатывамся во внутрннних обсуждениях. Но тут был упор именно на то, что набор свойств без особых усилий сформировать может человек, не пишущий код.
Валидация – это хорошо, и мы добавляем проверки, особенно в критичных частях. Но это касается и первого куска кода, который с линковкой компонент в поля, так что тут описанный подход ничем особенным не выделяется.


Дорабатывать планируем. Будет расширение разнообразия типов (очень в этом поможет SerializeReference) и прочие плюшки. А валидация (и даже некоторая кодогенерация) уже частично реализованы.


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

Смотрели. Если кратко, то в UniRx нет динамических ViewModel, из-за чего авторинг блокируется программистами

Смотря что вы имеете ввиду под Dependency Injection. Если вы про DI-контейнер, то да, некоторые проекты используют Zenject, но не все (один, если быть точным). А если про более общее понятие "внедрение зависимостей", то тоже да, конечно же.
Что касается моей точки зрения, то я сторонник как можно более явного подхода к управлению зависимостями. Просто не очень люблю магию метапрограммирования в рантайме (это я про C# в частности). Я за так называемый Poor Man's DI.
Можно узнать, как вас подтолкнула тема статьи к этому вопросу?)

Я сейчас попробую ответить, возможно немного сумбурно получится.


Префаб обходится просто – берём все копоненты на всех GO внутри и вызываем валидацию на каждом. Со сценами сложнее, сцену нужно загрузить (EditorSceneManager.OpenScene), а потом пройтись по GetRootGameObjects так же, как по префабам. Ещё мы проверяем ассеты ScriptableObject.
Тут есть подводный камень – ассеты ScriptableObject могут быть вложены в любые другие ассеты.


Далее главное синхронизировать обход по SerializedObject и обход рефлексией вглубь по типу объекта.


Берём каждый компонент или скруптуемый объект и обходим его
var serializedObject = new SerializedObject(unityObject);
Enumerate(unityObject, serializedObject.GetIterator());

Вот так примерно выглядит сам обход
IEnumerable<ValueTuple<SerializedProperty, FieldInfo>> EnumerateProperties(object obj, SerializedProperty property)
{
    // и идём по property
    if (obj == null || !property.Next(true)) yield break; // это был пустой объект

    do
    {
        var fieldInfo = unityObject.GetType().GetFieldInfo(property.name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); // *

        if (fieldInfo == null) continue;

        yield return (property.Copy(), fieldInfo); // отдаём на проверку

        // если это референс на другой объект, то вглубь идти не нужно, пропускаем
        if (property.propertyType != SerializedPropertyType.Generic) continue;

        if (property.isArray)
        {
            var array = fieldInfo.GetValue(unityObject) as IEnumerable; // не забываем проверить на null, бывают такие случаи
            var index = 0;
            foreach (var element in array)
            {
                var item = property.GetArrayElementAtIndex(index++);
                yield return (item.Copy(), fieldInfo); // отдаём на проверку элемент массива

                if (item.propertyType != SerializedPropertyType.Generic) continue; // опять же, не проверяем ссылки

                foreach (var pair in Enumerate(element, item.Copy())) yield return pair;
            }

            continue; // с массивом покончили
        }

        // тут просто поле, и его нужно вглубь
        foreach (var pair in Enumerate(fieldInfo.GetValue(obj), property.Copy())) yield return pair;

    }
    while (property.Next(false));
}

Примечание к * в коде:
Тут на самом деле нужно хитрее, пройтись по всей иерархии типа наверх (BaseType), пока не упрёшься в типы, которые уже не нужно проверять. Это как MonoBehaviour, SerializedObject и прочие основные типы, так и thirdparty компоненты, в которые вы не можете вставлять аттрибуты валидации. Это один подход. Другой подход – это заранее подготовить список типов, в которых есть аттрибуты валидации и искать только в них.

Тут вам уже немного наотвечали. Я думаю, что стоит посвятить этому отдельный пост в будущем.

Это, конечно, элегантно) Только, во-первых, у вас опечатка


struct rnode { int data; rnode* prev; rnode* next; };

Во вторых, вам придётся писать по две реализации любой функции, которая принимает ваш список.
Но попытка хороша)

Не стал писать remove потому, что это не имело отнишения к делу. Специально для вас быстренько накидал. Без дырок. Можно лучше, но быстренько вот так:


    void remove(node n)
    {
        auto prev = prev_[n.index];
        auto next = next_[n.index];
        if (prev == INVALID_INDEX)
            first_ = next;
        else
            next_[prev] = next;

        if (next == INVALID_INDEX)
            last_ = prev;
        else
            prev_[next] = prev;

        if (--length_ == n.index) return;

        prev = prev_[length_];
        next = next_[length_];

        data_[n.index] = data_[length_];
        prev_[n.index] = prev;
        next_[n.index] = next;

        if (prev == INVALID_INDEX)
            first_ = n.index;
        else
            next_[prev] = n.index;

        if (next == INVALID_INDEX)
            last_ = n.index;
        else
            prev_[next] = n.index;
    }

Ответ на этот комментарий и на комментарий выше.


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

Именно связи. Связи != указатели. И указатели в принципе – это тоже индексы, смещение которых скрывает от вас контекст (рантайм). Здесь просто более явный контекст – конкрретный экземпляр DLL.


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

Да, для данного конкретного юзкейса DLL предложенный вариант не подходит. Но не все так работают со списком.


Например такой сценарий: вам нужно просто получить данные, скажем, 2762 элемента списка. Вы просто итерируете 2762 раз вперед от начала (не подгружая ненужные поля) и забираете данные по индексу из массива с данными.
Для такого сценария предложенный вариант будет эффективнее.


Структура это не просто интерфейс, важно ее устройство.

В том-то и дело, что устройство логически не поменялось, поменялся просто лейаут. Он из вертикального (AoS) превратился в горизонтальный (SoA). Мне очень часто приходится так делать, для разделения горячих и холодных данных, например.
Заметьте так же, что даже алгоритмы вставки в примере не так уж отличаются от примеров в той же википедии.


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

И тут я могу обойтись без дыр с помощью свопа из конца в удалённую позицию. Жертвуя валидностью итератора. Который, впрочем, я могу вернуть из метода удаления обновлённым и дейсвтительным.


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

Всё, кроме этого вывода в статье – детали. Спасибо, что обратили внимание на главное

Да, конечно. Если вы не переполните капасити. Но на этот случай, например, подойдёт предложенный выше вариант с чанками.
Вы правы, при вставке кортежа тут будет O(размер кортежа). Но для подобной задачи я бы что-нибудь иное придумал. Первое, что приходит в голову – делить эти массивы между списками, с которыми так придётся поступить (смержить).
Опять же, всё зависит от задачи, не всегда есть возможность написать эффективное общее решение.
Как вы быстро и безапелляционно отмели разговоры о кеш-линиях и не дали права называться этому двусвязным списком.
Если это снаружи выглядит для пользователя как список, ведёт себя как список – то это список.

Да, вариант с флажком тоже имеет право на существование, кто-то мне его предлагал, но это показалось мне слишком простым. Плюс интепрпретация указателей в зависимости от состояния этого флажка – ещё один уровень перенаправления (level of indirection). В виде бранча (return is_reversed? link1: link2) или в виде взятия по индексу (return links[is_reversed]). Не сказал бы, что это решение будет «чище».

Кто-то даже предлагал редактировать виртуальную таблицу (!) ноды, но это уж совсем экзотика
И меня это тоже смутило, я и сам не понимаю. Сама постановка задачи. Но я её встречал множество раз со времён университета. Меня смутило само решение, которое все пишут одинаково.
Ну и ещё, кстати, в стандарте есть такой метод: en.cppreference.com/w/cpp/container/list/reverse и он работает за O(N)
Приведённый код – это всго лишь proof of concept, а не полноценная реализация. Я упомянул это в тексте. Можно убрать этот лимит и увеличивать капасити массивов при переполнении. И да, в некоторых случаях это может повлиять на время добавления нового узла. Иногда нет. Всё опять же зависит от ваших данных, и от задачи. Иногда вы просто знаете капасити наперёд.

Information

Rating
Does not participate
Date of birth
Registered
Activity