Как стать автором
Обновить

Комментарии 18

Спасибо, полезная статья.

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

В байт влезает номер поля?

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

Тут можно посмотреть внутреннюю реализацию чтения дельты на примере float.

Спасибо, так понятно.

Номера нет, но есть маркер изменения(бит)

Поэтому если читаешь по порядку, можно понять, какой изменен

Тоже самое сделать получателю - сравнить с предыдущими полученными данными.

Нет, вы видимо не поняли вопрос.

Понятно, что получателю, чтобы получить новые данные нужны предыдущие и дельта.

Вопрос в том, как отличить состояние "поле не изменилось" от состояния "поле изменилось на х"

И выше ответили, на самом деле там есть бит изменения.

Если значение не изменилось - то ридер прочитаете 0 и пропустит поле (использует предыдущее значение)

Если там 1 - то он прочитает следующие х бит как квантизованную дельту поля

Дельта это хорошо, пока потери пакетов не произойдет.

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

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

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

Интересно, где реализовано такое:

  • Что-то предсказуемо движется - сервер подтверждает это каждый квант, затрачивая на пересылку один бит в структуре.

  • Предсказание не удалось - сервер отправляет полные координаты или дельту с последней синхронизации.

Готовой реализации нет. Но можно написать вот такой метод расширения:

Пример
public static class Extentions
{
    public static void WriteValue(this BitWriter writer, float baseline, float updated, float expectedDiff)
    {
        var defaultFloatPrecision = 0.000001f;
        var actualDiff = updated - baseline;
        
        if (Math.Abs(expectedDiff - actualDiff) < defaultFloatPrecision)
        {
            writer.Write(false);
        }
        else
        {
            writer.Write(true);
            writer.Write(updated);
        }
    }

    public static float ReadFloat(this BitReader reader, float baseline, float expectedDiff)
    {
        var isChanged = reader.ReadBool();
        if (isChanged)
        {
            return reader.ReadFloat();
        }

        return baseline + expectedDiff;
    }
}

В недетерминированном мире постоянно будет происходить ошибка предсказания банально из за floating-point error.

В детерминированном мире достаточно передачи инпута. В случае десинка можно накатить стейт.

Из готовых реализаций знаком с Quantum, довольно неплохая реализация.

В теории на одинаковых архитектурах можно построить синхронный floating point.

На практике просто используют числа с фикс знаками после запятой

Интересна применимость этой билиотеки и подхода вообще в HFT на биржах

HFT строится близко к инфраструктуре биржи. Проблема интересна, т.к. помимо тиковых значений сделок передаëтся ещë биржевой стакан (массив заявок (цена и объëм) на покупку/продажу). float-типы скорее всего не используются, а используется что-то типа Decimal, но с меньшим диапазоном, либо вообще передаются целочисленные значения, т.к. для каждого инструмента (почти) известен первоначальный номинал и шаг цены (откуда тек.цена = номинал + шаг цены * целочисленное знач, считается уже на клиенте).

Да, любопытно. Подобные способы "сжатия" актуальны не только в HFT, но и в обычных пользовательских терминалах (QUIK и проч.).

Хорошая статья на почитать вечерком! И было бы супер, если бы описание работы кода предшествовало самому коду. Ибо смотреть реализацию после объяснения - благое дело, а вот читать объяснение, после того как ты уже понял код - увы...

Крутяк, полезная статья, за ссылки на Github большое спасибо и за серию статей отдельное cпасибо - вот это прямо кладезь для тех, кто заинтересовался в сетевом программировании.

А что заставляет разработчика кода, который должен быть быстрым, использовать float вместо int, если точно известно, что значения всегда находятся в диапазоне 0f - 100f с шагом 0.1f?

Замечу, что типа int тут тоже много, достаточно short'а.

Приведу пару преимуществ использования float'а перед short'ом.

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

  2. Никто не отменял сценарий, когда нам понадобится изменить ограничения в большую сторону и, в связи с этим, мы перестаним влазить в short. Менять тип с одного на другой - не самое приятное удовольствие.

Забыл добавить. Библиотека поддерживает работу и с целочисленными типами.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории