Как превратить систему опроса в Rx.Net IObservable?

У меня есть игра (на основе MonoGame/XNA) с таким методом обновления:

public void Update(GameTime gameTime)
{
    component.Update(gameTime);
}

Я хотел бы преобразовать это в реактивный шаблон. Мое текущее решение:

public void Initialize()
{
    updateSubject = new Subject<GameTime>();

    component = new Component();

    updateSubject.Subscribe((gameTime) => component.Update(gameTime));
}

public void Update(GameTime gameTime)
{
    updateSubject.OnNext(gameTime);
}

Я новичок в Rx, поэтому я все еще изучаю лучший способ делать что-то. Я читал, что следует избегать Subject и вместо него следует использовать Observable.Create.

Подходит ли здесь Subject?

Как я мог использовать Observable.Create в этом случае?


person sdgfsdh    schedule 03.07.2016    source источник


Ответы (2)


Ключевая проблема, с которой вы сталкиваетесь здесь, заключается в том, что вам нужен источник для вашего наблюдаемого. В общем, вы можете создавать наблюдаемые объекты из различных источников, таких как события, делегаты, задачи, многие наблюдаемые расширения (например, .Interval или .Generate) и темы.

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

Если вы используете тему, то ваш код в порядке. Единственным недостатком является то, что вы можете вызвать updateSubject.OnCompleted и закончить наблюдаемое.

Если вы хотите использовать делегата, ваш код может выглядеть так:

private Action<GameTime> updateGameTime = null;

public void Initialize()
{
    component = new Component();

    Observable
        .FromEvent<GameTime>(a => updateGameTime += a, a => updateGameTime -= a)
        .Subscribe((gameTime) => component.Update(gameTime));
}

public void Update(GameTime gameTime)
{
    updateGameTime(gameTime);
}

Таким образом, единственное, что вы можете сделать с updateGameTime, это передать новый GameTime - вы не можете "случайно" закончить последовательность.

Теперь вся проблема с использованием субъектов по сравнению с Observable.Create связана с состоянием. В вашем коде вам нужно состояние, поэтому тема в порядке. Однако в целом, по возможности, желательно инкапсулировать состояние - и это то, что Observable.Create делает для вас.

Возьмите этот пример:

var i = -1;
var query =
    Observable
        .Range(0, 10).Select(x =>
        {
            i = -i * 2;
            return x * i;
        });

Если я подпишусь на эту наблюдаемую дважды, я получу эти две последовательности:

(1)

0 
-4 
16 
-48 
128 
-320 
768 
-1792 
4096 
-9216 

(2)

0 
-4096 
16384 
-49152 
131072 
-327680 
786432 
-1835008 
4194304 
-9437184 

Последовательность меняется, потому что я использовал состояние (т.е. var i = -1;).

Если бы я написал код с Observable.Create, я мог бы избежать этого состояния:

var query =
    Observable
        .Create<int>(o =>
        {
            var i = -1;
            return
                Observable
                .Range(0, 10).Select(x =>
                {
                    i = -i * 2;
                    return x * i;
                })
                .Subscribe(o);
        });

Это все тот же запрос, но состояние инкапсулировано, поэтому, если я подпишусь дважды, я получу:

(1)

0 
-4 
16 
-48 
128 
-320 
768 
-1792 
4096 
-9216 

(2)

0 
-4 
16 
-48 
128 
-320 
768 
-1792 
4096 
-9216 

Бывают случаи, когда при написании сложных запросов вы можете подумать, что использование темы значительно облегчит задачу, и в целом именно здесь случаются ошибки. Вы всегда должны пытаться найти чисто операторный подход, прежде чем использовать субъекты в этом случае. Если вы не можете инкапсулировать использование субъекта в Observable.Create.

В такие времена, как ваше, использование темы — это нормально, потому что вам нужно это внешнее состояние.

person Enigmativity    schedule 04.07.2016

Просто указав, что ваш код без необходимости использует Rx.

public void Initialize()
{
    //updateSubject = new Subject<GameTime>();

    component = new Component();

    //updateSubject.Subscribe((gameTime) => component.Update(gameTime));
}

public void Update(GameTime gameTime)
{
    //updateSubject.OnNext(gameTime);
    component.Update(gameTime)
}

Здесь я удалил Subject и просто вызываю напрямую метод components Update, чтобы проиллюстрировать это.

Возможно, вы ищете частный метод опроса? В этом случае Observable.Interval может быть хорошим местом для начала.

person Lee Campbell    schedule 04.07.2016
comment
Я привел минимальный пример для SO. Настоящий код сложнее этого. - person sdgfsdh; 04.07.2016
comment
Это приятно слышать. Но если это полный минимальный проверяемый пример, то Субъект (и, следовательно, Rx) является избыточным. Я не верю, что вы предоставляете достаточно информации, чтобы дать разумный ответ. - person Lee Campbell; 04.07.2016
comment
Примером использования моего IObservable<GameTime> является обработка ввода. Если у меня также есть IObservable<KeyboardState>, я могу открыть его, используя поток GameTime. - person sdgfsdh; 04.07.2016