Оптимизация алгоритма множественной отправки уведомлений в C #? - PullRequest
3 голосов
/ 03 марта 2010

Извините за название, я не мог придумать лучшего способа описать проблему. По сути, я пытаюсь внедрить систему столкновений в игру. Я хочу иметь возможность зарегистрировать «обработчик столкновений», который обрабатывает любое столкновение двух объектов (заданных в любом порядке), которое может быть приведено к определенным типам. Поэтому, если зарегистрированы Player : Ship : Entity и Laser : Particle : Entity и обработчики для (Ship, Particle) и (Laser, Entity), чем для коллизии (Laser, Player), оба обработчика должны быть уведомлены с аргументами в правильном порядке и коллизией (Laser, Laser) должен уведомить только второй обработчик.

Фрагмент кода говорит тысячу слов, поэтому вот что я делаю сейчас (наивный метод):

    public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
        where T1 : Entity
        where T2 : Entity
    {
        Type t1 = typeof(T1);
        Type t2 = typeof(T2);
        Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
        _onCollisionInternal += delegate(Entity obj1, Entity obj2)
        {
            if (t1.IsAssignableFrom(obj1.GetType()) && t2.IsAssignableFrom(obj2.GetType()))
                obs.OnNext(new Collision<T1, T2>((T1) obj1, (T2) obj2));
            else if (t1.IsAssignableFrom(obj2.GetType()) && t2.IsAssignableFrom(obj1.GetType()))
                obs.OnNext(new Collision<T1, T2>((T1) obj2, (T2) obj1));
        };
        return obs;
    }

Однако этот метод довольно медленный (измеримый; я потерял ~ 2 FPS после его реализации), поэтому я ищу способ сократить пару циклов / выделение из этого.

Я подумал (т. Е. Потратил час на реализацию, а затем ударил меня головой о стену за то, что я был таким идиотом) о методе, который приводит типы в порядок на основе их хэш-кода, а затем помещает их в словарь, каждая запись представляет собой связанный список обработчиков для пар этого типа с логическим указанием, хотел ли обработчик изменить порядок аргументов. К сожалению, это не работает для производных типов, поскольку, если производный тип передается, он не уведомляет подписчика о базовом типе. Может ли кто-нибудь придумать способ лучше, чем проверять каждую пару типов ( дважды ), чтобы убедиться, что она совпадает?

Спасибо, Роберт

Ответы [ 3 ]

3 голосов
/ 03 марта 2010

Все это отражение не будет быстрым. Обратите внимание, как отражение происходит при каждом столкновении. В этом проблема.

Некоторые мысли.

Мысль номер один: шаблон посетителя.

Стандартный способ реализовать достаточно быструю двойную виртуальную диспетчеризацию на языках ОО - это создать реализацию шаблона посетителя.

Можете ли вы реализовать шаблон посетителя, согласно которому каждый получатель в посетителе ведет список «действий, которые необходимо выполнить в этой ситуации»? Тогда ваш метод сумматора состоит из определения правильного места для написания новой «вещи, которую нужно сделать», и затем добавления делегата к этой вещи. Когда происходит столкновение, вы начинаете посетителя выполнять двойную диспетчеризацию, находите делегат действий и вызываете его.

Вам нужно больше, чем двойная отправка? Эффективная многократная виртуальная отправка выполнима, но это не совсем просто.

Мысль номер два: динамическая отправка

C # 4 имеет динамическую отправку. Как это работает, мы используем отражение при первом обнаружении сайта вызова для анализа сайта вызова. Затем мы динамически генерируем новый новый IL для выполнения вызова и кешируем IL. Во втором вызове мы размышляем над аргументами, чтобы увидеть, являются ли они точно такими же типами, как и раньше. Если они есть, мы повторно используем существующий IL и просто вызываем его. Если нет, мы делаем анализ снова. Если аргументы обычно бывают только нескольких типов, то кэш очень быстро начинает только попадать, а не пропускает, и производительность на самом деле довольно хорошая, учитывая все обстоятельства. Конечно, быстрее, чем много размышлений каждый раз. Единственное отражение, которое мы делаем каждый раз, - это анализ типа аргументов во время выполнения.

Мысль номер три: реализовать собственную динамическую диспетчеризацию

В том, что делает DLR, нет ничего волшебного. Один раз он анализирует, выплевывает немного IL и кеширует результат. Я подозреваю, что ваша боль случается, потому что вы каждый раз проводите повторный анализ.

1 голос
/ 03 марта 2010

Если бы у каждого Entity было свойство, которое идентифицировало его тип сущности, и вы просто получили значение этого свойства, разве это не было бы быстрее?

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

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

public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
    where T1 : Entity
    where T2 : Entity
{
    EntityTypeEnum t1 = T1.EntityType;
    EntityTypeEnum t2 = T2.EntityType;

    Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
    _onCollisionInternal += delegate(Entity obj1, Entity obj2)
    {
       if (t1 < t2)
           obs.OnNext(new Collision<T1, T2>((T1) obj1, (T2) obj2));
       else
           obs.OnNext(new Collision<T1, T2>((T1) obj2, (T2) obj1));
    };
    return obs;
}
0 голосов
/ 03 марта 2010

Я предполагаю, что есть 2 серии столкновений, которые вам нужны: лазер / плеер и лазер / лазер. Если вы захотите, чтобы IObservable > просто совпадал с этими двумя случаями, то вы могли бы сократить делегата до одной проверки и иметь все строгие данные для сравнения.

_onCollisionInternal += delegate(T1 obj1, T2 obj2) {
  obs.OnNext(new Collision<T1, T2>( obj1, obj2));
};


public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
        where T1 : Entity
        where T2 : Entity
    {
        Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
        _onCollisionInternal += delegate(T1 obj1, T2 obj2) {
           obs.OnNext(new Collision<T1, T2>( obj1, obj2));
        };
        return obs;
    }

Observable<Collision<Laser, Player>> lp = onCollisionsOf<Laser, Player>();
Observable<Collision<Laser, Laser>> ll = onCollisionsOf<Laser, Laser>();
...