gpt4 book ai didi

system.reactive - 如何用 observables 定义循环

转载 作者:行者123 更新时间:2023-12-01 02:16:54 26 4
gpt4 key购买 nike

我正在尝试设置一个简单游戏的更新循环,并考虑到 observables。顶层组件是一个模型,它接受输入命令并产生更新;和一个 View ,它显示接收到的更新,并产生输入。单独来看,两者都可以正常工作,有问题的部分是将两者放在一起,因为两者都依赖于另一个。

将组件简化为以下内容:

var view = function (updates) {
return Rx.Observable.fromArray([1,2,3]);
};
var model = function (inputs) {
return inputs.map(function (i) { return i * 10; });
};

我把事情联系在一起的方式是这样的:

var inputBuffer = new Rx.Subject();
var updates = model(inputBuffer);
var inputs = view(updates);
updates.subscribe(
function (i) { console.log(i); },
function (e) { console.log("Error: " + e); },
function () { console.log("Completed"); }
);
inputs.subscribe(inputBuffer);

也就是说,我添加一个主题作为输入流的占位符,并将模型附加到该主题。然后,在构建 View 之后,我将实际输入传递给占位符主题,从而关闭循环。

然而,我不禁觉得这不是正确的做事方式。为此使用主题似乎是矫枉过正。有没有办法用 publish() 或 defer() 或类似的东西做同样的事情?

更新:这是一个不太抽象的例子来说明我遇到的问题。下面是一个简单“游戏”的代码,玩家需要点击一个目标才能击中它。目标可以出现在左侧或右侧,每当它被击中时,它就会切换到另一侧。看起来很简单,但我仍然觉得我错过了一些东西......
//-- Helper methods and whatnot
// Variables to easily represent the two states of the target
var left = 'left';
var right = 'right';
// Transition from one side to the other
var flip = function (side) {
if (side === left) {
return right;
} else {
return left;
}
};
// Creates a predicate used for hit testing in the view
var nearby = function (target, radius) {
return function (position) {
var min = target - radius;
var max = target + radius;
return position >= min && position <= max;
};
};
// Same as Observable.prototype.scan, but it also yields the initial value immediately.
var initScan = function (values, init, updater) {
var initValue = Rx.Observable.return(init);
var restValues = values.scan(init, updater);
return initValue.concat(restValues);
};

//-- Part 1: From input to state --
var process = function (inputs) {
// Determine new state based on current state and input
var update = function(current, input) {
// Input value ignored here because there's only one possible state transition
return flip(current);
};
return initScan(inputs, left, update);
};
//-- Part 2: From display to inputs --
var display = function (states) {
// Simulate clicks from the user at various positions (only one dimension, for simplicity)
var clicks = Rx.Observable.interval(800)
.map(function (v) {return (v * 5) % 30; })
.do(function (v) { console.log("Shooting at: " + v)})
.publish();
clicks.connect();

// Display position of target depending on the model
var targetPos = states.map(function (state) {
return state === left ? 5 : 25;
});
// Determine which clicks are hits based on displayed position
return targetPos.flatMapLatest(function (target) {
return clicks
.filter(nearby(target, 10))
.map(function (pos) { return "HIT! (@ "+ pos +")"; })
.do(console.log);
});
};

//-- Part 3: Putting the loop together
/**
* Creates the following feedback loop:
* - Commands are passed to the process function to generate updates.
* - Updates are passed to the display function to generates further commands.
* - (this closes the loop)
*/
var feedback = function (process, display) {
var inputBuffer = new Rx.Subject(),
updates = process(inputBuffer),
inputs = display(updates);
inputs.subscribe(inputBuffer);
};
feedback(process, display);

最佳答案

我想我理解你在这里想要达到的目标:

  • 我怎样才能得到一系列输入事件在一个方向上输入模型
  • 但是有一系列输出事件从模型馈送到 View
  • 的另一个方向。

    我相信这里的答案是你可能想要翻转你的设计。假设一个 MVVM 风格的设计,而不是让模型知道输入序列,它变得不可知。这意味着您现在拥有一个具有 InputRecieved/OnInput/ExecuteCommand 方法的模型, View 将使用输入值调用该方法。现在,您应该更容易处理“一个方向的命令”和“另一个方向的事件”模式。这里是对 CQRS 的一种提示。

    在过去的 4 年里,我们在 WPF/Silverlight/JS 中的 Views+Models 上广泛使用了这种风格。

    也许是这样的;
    var model = function()
    {
    var self = this;
    self.output = //Create observable sequence here

    self.filter = function(input) {
    //peform some command with input here
    };
    }

    var viewModel = function (model) {
    var self = this;
    self.filterText = ko.observable('');
    self.items = ko.observableArray();
    self.filterText.subscribe(function(newFilterText) {
    model.filter(newFilterText);
    });
    model.output.subscribe(item=>items.push(item));
    };

    更新

    感谢您发布完整的样本。这看起来不错的样子。我喜欢你的新 initScan运算符,似乎是 Rx 的明显遗漏。

    我按照我可能编写的方式对您的代码进行了重组。我希望它有所帮助。我做的主要事情是将逻辑封装到模型中(翻转、附近等),并让 View 将模型作为参数。然后我还必须向模型中添加一些成员,而不仅仅是一个可观察的序列。但是,这确实允许我从 View 中删除一些额外的逻辑并将其也放入模型中(命中逻辑)
    //-- Helper methods and whatnot

    // Same as Observable.prototype.scan, but it also yields the initial value immediately.
    var initScan = function (values, init, updater) {
    var initValue = Rx.Observable.return(init);
    var restValues = values.scan(init, updater);
    return initValue.concat(restValues);
    };

    //-- Part 1: From input to state --
    var process = function () {
    var self = this;
    var shots = new Rx.Subject();
    // Variables to easily represent the two states of the target
    var left = 'left';
    var right = 'right';
    // Transition from one side to the other
    var flip = function (side) {
    if (side === left) {
    return right;
    } else {
    return left;
    }
    };
    // Determine new state based on current state and input
    var update = function(current, input) {
    // Input value ignored here because there's only one possible state transition
    return flip(current);
    };
    // Creates a predicate used for hit testing in the view
    var isNearby = function (target, radius) {
    return function (position) {
    var min = target - radius;
    var max = target + radius;
    return position >= min && position <= max;
    };
    };

    self.shoot = function(input) {
    shots.onNext(input);
    };

    self.positions = initScan(shots, left, update).map(function (state) {
    return state === left ? 5 : 25;
    });

    self.hits = self.positions.flatMapLatest(function (target) {
    return shots.filter(isNearby(target, 10));
    });
    };
    //-- Part 2: From display to inputs --
    var display = function (model) {
    // Simulate clicks from the user at various positions (only one dimension, for simplicity)
    var clicks = Rx.Observable.interval(800)
    .map(function (v) {return (v * 5) % 30; })
    .do(function (v) { console.log("Shooting at: " + v)})
    .publish();
    clicks.connect();

    model.hits.subscribe(function(pos)=>{console.log("HIT! (@ "+ pos +")");});

    // Determine which clicks are hits based on displayed position
    model.positions(function (target) {
    return clicks
    .subscribe(pos=>{
    console.log("Shooting at " + pos + ")");
    model.shoot(pos)
    });
    });
    };

    //-- Part 3: Putting the loop together
    /**
    * Creates the following feedback loop:
    * - Commands are passed to the process function to generate updates.
    * - Updates are passed to the display function to generates further commands.
    * - (this closes the loop)
    */
    var feedback = function (process, display) {
    var model = process();
    var view = display(model);
    };
    feedback(process, display);

    关于system.reactive - 如何用 observables 定义循环,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24107599/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com