Обновить
4
0

Пользователь

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

А как понять? Не тратить время на мысли о социальности пока не станешь в профессиональном смысле выше окружающих. До тех пор пока ты не можешь точно сказать где именно у критика ошибка, и желательно можешь доказательно это продемонстрировать кодом — до тех пор учись. И даже тогда.

Ругался я тут помнится с одним серверным программистом паршиво реализующим команды привязанные ко времени, ругался так сильно, что трижды собирался консилиум из двух тимлидов и техдира. Так даже у него я научился новому алгоритму — плоскому хранилищу разнотипной информации в виде Dictionary циклопического размера. И когда он объяснил почему так, я послушно пошёл реализовывать так как он сказал, и стал умнее. При том, что по другим вопросам мы могли ругаться в дымину.
Вы не поверите. :)))))))
Внимательно слушать критику и вдумчиво учиться, не? :))))
Или криворукий даун хочет «удовлетворение от работы, реализовывать интересные и полезные проекты за адекватное вознаграждение» нихрена не умея? Так у меня для него есть очень плохая новость.

И что характерно, куда бы он не пришёл вокруг него будет обнаруживаться вдруг чрезвычайно токсичная ситуация.
И помните! Если на вас все орут, ругаются, и заявляют, что вы в ПТУ учились, это не потому что вы криворукий даун, просравший все полимеры, а только потому что коллеги у вас токсичные, и вообще перфекционисты гадские.
Митя из Glu, это ты что-ли? )))

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

1) На самом деле писанины очень не много. Это шаблонные две строчки кода, в которых меняется по сути только название переменной. Они делаются из соседней такой же модели простым копированием. Более того я просто добавлял готовый шаблон в настройки редактора, чтобы он по трём буквам добавлялся. Всё остальное спрятано под капотом движка и на написание конкретного функционала игры никак не влияет.
Самый просто способ проверки на сколько много или мало лишней писанины я выработал для себя давным давно. Берётся движок, и на нём делается модель в которой есть одно простое поле, показывающаяся в одном текстовом поле в одном новом вьюве, а рядом кнопка, изменяющая это поле. И засекаем сколько времени в минутах это займёт у человека делающего это хотя бы второй-третий раз. За счёт того, что вся машинерия спрятана под капотом и программисту её писать не надо на моём движке это занимает не больше 10 минут вместе с отправкой команды на сервер и отработкой серверного ответа, про опыте ещё и меньше. Все виденные мной альтернативы требовали в разы большего времени.
2) DTO плюс правила — тоже хороший вариант, но он требует дополнительных телодвижений чтобы сопоставлять DТО с правилами. Учитывая что сериализация всё равно кастомная, потому что хочется иметь в данных не только простые типы, но и ссылки на модели, варианты получаются фактически эквивалентными.
а) Откзываться от ссылок на модели значит требовать от программиста каждый раз руками по параметрам искать объект в дереве. Лишняя писанина, которая может быть спрятана под капотом команды.
б) Ну или иметь сквозные PersistentId для объектов и всегда писать их. Но это во-первых, очень плохо влияет на читаемость лога команды и, если таковой есть, лога произведённых в модели изменений. А кроме того ты всё равно не обойдёшься одним PersistentId, потому что нужно иметь один способ для объектов унимкальных в рамках сервера, один для объектов уникальных для игрока, и ещё один уникальный для игрока, но не затрагивающий игровую логику и его NextFreeId, чтобы детерминированность операций не нарушать.
в) Если ты вызываешь функции потребуются дополнительные телодвижения чтобы вызов функции превратить в отправку команды на сервер. Соответственно усложнение кода функции. При моём подходе он чище, понятнее, и лишён каких либо лишних телодвижений и места для ошибки.
3) Разделение модели для бизнеслогики и модели для View, что-то типа MVVM в одно движение приводит к тому, что эти две модели должны обслуживаться одним и тем же общим предком Model и иметь общую точку доступа к ним GameState. Сериализация-десериализация GameState как одной сущности проще сделать если он, в свою очередь, тоже наследник Model-а. И вот мы получаем, что по сути это единое целое, только одна веточка поддерева для буковки M а другая VM. А дальше возникает ситуация, что если уж всё равно это одно целое, а у тебя половина полей объекта нужны только для отображения, то вместо того чтобы для одной сущности заводить две модели, одна для бизнес-логики, а другая для View мы этот очень простой, на самом деле функционал, распространяем не только на первое деление на два дерева, но и на любое поле.
А танцы с бубном чтобы красиво и доходчиво показать ошибку это делается только один раз при создании движка, а для программистов фигачащих логику это всё просто движок, дающий понятные сообщения об ощибках и просто он это делает или сложно и им и приложению в проде когда ошибок нет — совершенно пофигу.

А вот правильная идеология или неправильная — это вопрос религиозного свойства, особого внимания на прагматическим аргументам тут уделять не нужно. :))
Вышла третья часть статьи: habr.com/post/436060
Вышла третья часть статьи: habr.com/post/436060
И вы чертовски правы. На самом деле у меня часто бывает так, что я сначала делаю какую-то конструкцию составным контролом до тех пор пока её можно сделать легко, грубо говоря, пока я могу собрать её из стандартных контролов, которые я могу написать «с листа» без отладки и не ошибиться. А если конструкция усложняется на столько, что у меня возникают такие сомнения, как у вас сейчас я переношу все эти вычисления в модель и сложных трудно отлаживающихся контролов становится меньше, а моделей — больше, но они отслеживаются как раз очень просто. Так часто бывает, потому что геймдизайнер часто хочет усложнить в самых неожиданных местах, никогда не угадаешь в каких. Пример такой ситуации есть во второй статье habr.com/post/435704 в виде: match.CalculateAssignments(); Когда-то количество бабла для закупок определялось просто — количество бабла на одну сторону, делённое на количество игроков на этой стороне. Очень просто сделать контрол в три строчки и не париться. Но потом выяснилось, что закупка не считается успешной если закупленно меньше, чем на половину суммы, а с другой стороны можно потратить больше своей доли, на 10% если остальные игроки свои доли пока не израсходовали. В таком варианте отлаживать это на многоуровневых реактивных конструкциях было уже явно не подарок, и это превратилось в правила над моделью, а количество доступного игроку бабла стало не вычисляемым значением, а полем в модели, которое пересчитывалось этой вот функцией.
Хитрость тут в том, что на самом деле мы можем узнать какие поля потребуются для команды. По целым двум причинам. У нас есть экранированный Геттер, ничего не мешает в нём спрятать фиксацию информации о том, что к нему обратились. Клиент сможет сообщить какие ему модели понадобились когда он команду выполнял. Второй вариант ещё интереснее: На сервере непосредственно выполнение команды обычно занимает в десятки раз меньше времени, чем подгрузка данных из реляционной базы. Так что мы сначала грузим большую часть стейта из большого блоба, после чего можем запускать выполнение команды, если натыкаемся на отсутствие данных в каком-нибудь коллекшене, хранящемся не в блобе, а в отдельной таблице, команда стопорится эксепшеном, состояние дерева откатывается, и мы грузим из базы этот коллекшен, после чего повторно пытаемся выполнить команду. Идея чисто теоретическая, надо будет её, при случае, попробовать.
Почему именно игры яснее видно во второй части про команды: habr.com/post/435704
В отличии от бизнес-приложений типичная игра почти полностью детерминирована клиентской информацией, поэтому предиктит почти все действия сервера, кроме того возникают типичные для игр, но не характерные для бизнес-приложений проблемы, что много анимаций, которые надо асинхронно контролировать. Наконец именно для игр характерна ситуация отладки когда цена ошибки невелика, ДАУ очень велико, но при этом информация, известная программисту обычно явно недостаточна, и логов мало, и приложение делает очень много что не сообщая об этом серваку.

Но так то да, комплекс проблем характерен не только для игр.
Собственно подробнее про команды в следующей статье, которую я опубликовал сегодня: habr.com/post/435704
Так я в комментариях как раз и привёл не вырожденный случай подписки в функции InventoryView.ConnectModel(), он там аж 11 строк занимает, и агрегирует информацию из массива сложных моделей и отдельно лежащего поля чтобы показать результирующую.
По ходу нет, вы не правильно поняли.
У меня моделью называются обе части модели, и та, которая нужна только для бизнес-логики, и та, которая нужна только для UI. Более того, у меня может быть объект, у которого одно поле серверу видно, а другое рядом с ним интересует только view-ы. То есть на самом деле это одно большое дерево, только сервер его не всё видит.

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

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

Смысл этого как раз в том, чтобы не хранить внутри View никакой скрытой информации, которой не было бы видно на отладочном геймстейте. В том числе и в виде запущенных корутин.
Эпик фейл!!! Только что обнаружил, что из-за ошибки в форматировании, (неэкранированной треугольной скобки) половина статьи не показывалась! Афигеть вообще…
Вообще комманды и сложные конструкции во View-ах будут описываться во второй и третьей части статьи, которые я планирую завтра и послезавтра. Так что тут отвечу кратко. Случай на столько архитипичный, что я пожалуй включу туда в качестве отличного жизненного примера.

Да, подписывание в модели, конечно, только для примера, на самом деле такие строчки находятся, конечно, в View.ConnectModel(), но про вьювы я тут ещё не говорил, поэтому фейковый пример кинул сюда.

У нас в практике тоже был такой случай, когда открываются мистери боксы, и конечно деньги должны поменяться только когда анимация доиграется до конца. Один из наших разработчиков решил упростить себе жизнь, и по команде значение не менять, но серверу говорить, что изменил, а по концу анимации запускать callback, который поправит значения в модели на нужные. Молодец, короче. Две недели он эти мистерибоксы делал, а потом у нас ещё трижды всплывали очень трудно уловимые ошибки, ставшие результатом его деятельности и на их ловлю пришлось истратить ещё три недели по итогу, при том, что времени на «переписать по нормальному» уже, конечно, никто выделить не мог. Из чего ярко следует, как мне кажется, вывод что лучше нужно было с самого начала сделать всё с нормальной реактивностью.

Так вот, моё решение примерно такое. Конечно деньги лежат не в отдельном поле, а являются одним из объектов в словаре inventory, но это сейчас не так важно. У модели есть одна часть, которая проверяется сервером, и на основе которой работает бизнес-логика, и другая, которая существует только на клиенте. Деньги в основной модели начисляются сразу по факту принятия решения, а во второй части в списке «отложенное показывание» создаётся элемент на ту же сумму, который фактом своего появления запускает анимацию, а по окончании анимации запускается команда, которая этот элемент удаляет.
И в реальном поле показывается не просто значение поля, а значение поля за минусом всех клиентских откладываний. В коде это будет примерно так:
public class OpenMisterBox : Command {
    public BoxItemModel item;
    public int slot;
    // Эта часть команды выполняется и на сервере тоже, и проверяется.
    public override void Applay(GameState state) {
        state.inventory[item.revardKey] += item.revardCount;
    }
    // Эта часть команды выполняется только на клиенте.
    public override void Applay(GameState state) {
        var cause = state.NewPersistent<WaitForCommand>();
        cause.key = item.key;
        cause.value = item.value;
        state.ui.delayedInventoryVisualization.Add(cause);
        state.ui.mysteryBoxScreen.animations.Add(new Animation() {cause = item, slot = slot}));
    }
}
public class MysteryBoxView : View {
    /* ... */
    public override void ConnectModel(MysteryBoxScreenModel model, List<Control> c) {
        model.Get(c, MysteryBoxScreenModel.ANIMATIONS)
            .Control(c, 
                onAdd = item => animationFactory(item, OnComleteOrAbort => {
                    AsincQueue(new RemoveAnimation() {cause = item.cause, animation = item}) }),
                onRemove = null
            )
    }
}

public class InventoryView : View<InventoryItem> {
    public Text text;
    public override void ConnectModel(InventoryItem model, List<Control> c) {
        model.GameState.ui.Get(c, UIModel.DELAYED_INVENTORY_VISUALIZATION).
            .Where(c, item => item.key == model.key)
            .Expression(c, onChange = (IList<InventoryItem> list) => {
                int sum = 0;
                for (int i = 0; i < list.Count; i++)
                    sum += list[i].value;
                return sum;
            }, onAdd = null, onRemove = null ) // Чисто ради показать сигнатуру метода
            .Join (c, model.GameState.Get(GameState.INVENTORY).ItemByKey(model.key))
            .Expression(c, (delay, count) => count - delay)
            .SetText(c, text);
        // Здесь я написал полный код, но в реальности это операция типовая, поэтому для неё, конечно же, существует функция обёртка, которая дёргается в проекте во всех случаях, выглядит её вызов вот так:
        model.inventory.CreateVisibleInventoryItemCount(c, model.key).SetText(c, text);
    }
}
public class RemoveDelayedInventoryVisualization : Command {
    public DelayCauseModel cause;
    public override void Applay(GameState state) {
        state.ui.delayedInventoryVisualization.Remove(cause);
        state.DestroyPersistent(cause);
    }
}
public class RemoveAnimation : RemoveDelayedInventoryVisualization {
    public Animation animation
    public override void Applay(GameState state) {
        base.Applay(state);
        state.ui.mysteryBoxScreen.animations.Remove(animation);
    }
}

Что мы имеем в итоге? Есть два View, в одном из них играется некая анимация, окончания которой ждёт отображение денег в совсем другом вьюве, понятия не имеющем кто и зачем хочет чтобы показывалось другое значение. Всё реактивненько. В любой момент можно загрузить в игру полное состояние GameState и оно начнёт играться ровно с того места, на котором мы остановились, в том числе и анимация запустится. Правда запустится с начала, потому что мы этап анимации не сторим, но если очень нужно, сторить можем даже его.

Любую сеть можно апроксимировать однослойкой, но в этой теореме ничего не сказано про размеры этой однослойки. Грубо говоря вы можете одного и того же результата можете добиться однослойкой на 10000 нейронов в слое и год обучения, или двухслойкой на 500 нейронов, или трёхслойкой на 30.

Всё зависит от задачи. Если задача — запомнить огромное случайное число — лучше всего подойдёт однослойка, если задача имеет возможности для вычленения обобщающего знания — ваш выбор — глубокое обучение и много слоёв. Чем больше уровней обобщения в целевой функции можно предположить, тем больше слоёв.
Много красивой математики. Безусловный плюс в карму, но вопрос, а не кажется ли вам, что отсутствие реализаций этого метода не случайно?
1) Вторую производную быстрее получить буквально. Посчитав первую производную, а потом сдвинув вдоль линии градиента и предположив, что всё малое отличие от первой производной относится ко второй.
2) Вторую производную вдоль одной линии, а не всю эту адскую матрица можно при желании посчитать аналитически. Я когда-то это проделал, вычислительного времени это занимает всего в три раза больше, чем вычисление первой производной. Однако занятие изрядно бессмысленное смотри пункт первый.
3) Метод имеет смысл только если предполагать функцию ошибки не только слабо наклонной, но ещё и не содержащей множества локальных минимумов. Потому что любой алгоритм со вторыми производными вязнет в локальных минимумах намертво. А гладкая функция ошибки, на сколько я успел понять, большая редкость.

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

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность