Mobx, как кешировать вычисленные значения? - PullRequest
1 голос
/ 09 апреля 2020

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

import {observable, observe, computed, autorun} from 'mobx';

class Entity {
  @observable position = [0,0,0]
  @observable rotation = [0,0,0]

  @computed get modelMat(){
    return position * rotation;
  }
}

Я использую эту сущность как:

var ent = new Entity();
entity.position = [0,10,0];
if(entity.modelMat == 6){
  // do something
}

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

Это кажется мне не слишком понятным, поскольку вы определяете вычисленные значения с помощью помощника get, а затем не должны использовать это как добытчик? Параметр отладки computedRequiresReaction доступен для предотвращения этого шаблона прямого вычисляемого чтения.

configure({
  computedRequiresReaction: true
});

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

class Entity {
  @observable position = [0,0,0]
  @observable rotation = [0,0,0]

  modelMat = []

  constructor(){
    autorun(() => {
      this.modelMat = this.computedModelMat()
    })
  }

  @computed get computedModelMat(){
    return position * rotation;
  }
}

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

Ответы [ 3 ]

2 голосов
/ 14 апреля 2020

В основном происходит то, что вы выходите из mobx world, а mobx не касается того, что происходит вне его. В mobx системе ничто не является наблюдением вычисленного значения, поэтому нет причин сохранять его в кэше (в памяти).

Нет хорошего способа обойти эту проблему .

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

В следующем примере обратите внимание на cacheComputed() функция. Он принимает экземпляр и свойство для кэширования в виде string и просто оборачивает autorun вокруг него. Мы используем его в constructor класса. Кроме того, убедитесь, что dispose из autorun, если вы избавляетесь от самого экземпляра. Для этого у меня обычно есть метод dispose() для экземпляра, который удаляет все реакции внутри него.

Вы должны всегда останавливать все реакции, когда закончите с ними.

import { computed, autorun, observable, decorate } from "mobx";

function cacheComputed(instance, prop) {
  return autorun(reaction => {
    return instance[prop];
    //todo - maybe throw if 'prop' does not exist
  });
}

class A {
  constructor() {
    this.firstName = "Bob";
    this.lastName = "Marley";

    this.disposeFullName = cacheComputed(this, "fullName");
  }

  get fullName() {
    console.log("computed");
    return `${this.firstName} ${this.lastName}`;
  }

  dispose() {
    this.disposeFullName();
  }
}

decorate(A, {
  firstName: observable,
  lastName: observable,
  fullName: computed
});

const a = new A();

console.log(a.fullName); //cached
console.log(a.fullName); //cached
console.log(a.fullName); //cached

//--- force recompute
console.log("---- recalculate computed");

a.lastName = "Dylan";
console.log(a.fullName); //recomputed
console.log(a.fullName); //cached
a.dispose(); // after this fullName will be recomputed always

Проверьте это на CodeSandbox

2 голосов
/ 15 апреля 2020

Обратите внимание, что computed поддерживает опцию keepAlive, которая заставит mobx кешировать значение, даже если наблюдателей нет. И это на самом деле более эффективно, чем использование автозапуска для наблюдения, поскольку к этому флагу применены некоторые внутренние оптимизации.

Хотя существует небольшой риск утечек памяти: если что-либо, на что ссылается вычисляемый объект, все еще живо, вычисленное не будет очищено. Однако, если вы имеете в виду только локальные вещи класса, это следует сохранить.

2 голосов
/ 14 апреля 2020

Да, вы на самом деле используете рекомендуемый подход: https://github.com/mobxjs/mobx/issues/356

, пока значение computed не используется reaction, это не запоминается, и поэтому это как обычная функция оценки. Если вы используете [getter] в autorun, это поведение изменится, и вы не увидите ненужных вычислений.

...

причина MobX работает следующим образом: если значение computed не используется некоторыми reaction, его можно просто игнорировать. MobX не пересчитывает все это, и вычисления не поддерживают другие вычисления.

Но остерегайтесь утечек памяти . Код в вопросе не течет, но я не уверен насчет всего вашего кода:

const VAT = observable(1.2)
class OrderLine {
   @observable price = 10
   @observable amount = 1
   constructor() {
       // this autorun will be GC-ed together with the current orderline instance
       this.handler = autorun(() => {
           doSomethingWith(this.price * this.amount)
       })
       // this autorun won't be GC-ed together with the current orderline instance
       // since VAT keeps a reference to notify this autorun,
       // which in turn keeps 'this' in scope
       this.handler = autorun(() => {
           doSomethingWith(this.price * this.amount * VAT.get())
       })
       // So, to avoid subtle memory issues, always call..
       this.handler()
       // When the reaction is no longer needed!
   }
}
...