-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FIX] stores: bye bye reactivity 馃憢 #4104
Conversation
bbaf339
to
472403b
Compare
f8b0ca3
to
f4b34aa
Compare
// @ts-ignore | ||
rendererManager.drawLayer(renderingContext, layer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drawLayer
is a method used during rendering. It's not a mutator.
src/store_engine/store_hooks.ts
Outdated
@@ -15,8 +15,8 @@ export function useStoreProvider() { | |||
__spreadsheet_stores__: container, | |||
getStore: <T extends StoreConstructor>(Store: T) => { | |||
const store = container.get(Store); | |||
const mutators: readonly string[] = store.mutators; | |||
return proxifyToRender(env, store, mutators); | |||
// TODO check this, it shouldn't be/use a hook here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
because useStoreRenderProxy
uses useCompent
which is only available in the setup
of components. (right click on a cell, Insert link => boom)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah right :'(
src/stores/grid_renderer_store.ts
Outdated
@@ -57,20 +56,14 @@ import { RendererStore } from "./renderer_store"; | |||
export const CELL_BACKGROUND_GRIDLINE_STROKE_STYLE = "#111"; | |||
|
|||
export class GridRenderer { | |||
mutators = [] as const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it mandatory to declare for stores? the typing seems to miss this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a store makes sense without mutators ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a store makes sense without mutators ?
currently yes because other stores need to register themselves to this store
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice 馃コ Bye bye toRaw
and thanks for nothing
@@ -84,9 +93,9 @@ export class ComposerStore extends SpreadsheetStore { | |||
|
|||
constructor(get: Get) { | |||
super(get); | |||
this.highlightStore.register(toRaw(this)); | |||
this.highlightStore.register(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
馃コ
src/stores/grid_renderer_store.ts
Outdated
@@ -57,20 +56,14 @@ import { RendererStore } from "./renderer_store"; | |||
export const CELL_BACKGROUND_GRIDLINE_STROKE_STYLE = "#111"; | |||
|
|||
export class GridRenderer { | |||
mutators = [] as const; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does a store makes sense without mutators ?
082434b
to
80dac25
Compare
5a8d43e
to
88bbda7
Compare
Could be the occasion to get rid of "RENDER_CANVAS" command then since we have a new bus to work with? diff --git a/src/components/helpers/highlight_hook.ts b/src/components/helpers/highlight_hook.ts
index 7c0edb7e0..4fb2116f5 100644
--- a/src/components/helpers/highlight_hook.ts
+++ b/src/components/helpers/highlight_hook.ts
@@ -1,12 +1,12 @@
-import { onMounted, useEffect, useEnv } from "@odoo/owl";
-import { useLocalStore } from "../../store_engine";
+import { onMounted, useEffect } from "@odoo/owl";
+import { useLocalStore, useStoreProvider } from "../../store_engine";
import { HighlightProvider, HighlightStore } from "../../stores/highlight_store";
-import { Ref, SpreadsheetChildEnv } from "../../types";
+import { Ref } from "../../types";
import { useHoveredElement } from "./listener_hook";
export function useHighlightsOnHover(ref: Ref<HTMLElement>, highlightProvider: HighlightProvider) {
const hoverState = useHoveredElement(ref);
- const env = useEnv() as SpreadsheetChildEnv;
+ const stores = useStoreProvider();
useHighlights({
get highlights() {
@@ -15,7 +15,7 @@ export function useHighlightsOnHover(ref: Ref<HTMLElement>, highlightProvider: H
});
useEffect(
() => {
- env.model.dispatch("RENDER_CANVAS");
+ stores.trigger("store-updated");
},
() => [hoverState.hovered]
);
``` @LucasLefevre WDYT? |
@rrahir true. I pushed a commit :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok for me :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
馃憤
/** | ||
* Creates a batched version of a callback so that all calls to it in the same | ||
* microtick will only call the original callback once. | ||
* | ||
* @param callback the callback to batch | ||
* @returns a batched version of the original callback | ||
* | ||
* Copied from odoo/owl repo. | ||
*/ | ||
export function batched(callback: () => void): () => void { | ||
let scheduled = false; | ||
return async (...args) => { | ||
if (!scheduled) { | ||
scheduled = true; | ||
await Promise.resolve(); | ||
scheduled = false; | ||
callback(...args); | ||
} | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't batched be linked to the nextanimationframe instead of the microtick ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed a commit but it's 馃あ just because we don't have a direct access to owl's Scheduler
(same trick used in the test).
I'll go and talk to the framework-js team tomorrow to check if they can export Scheduler
from owl (or an updated version of their batched
using Scheduler.requestAnimationFrame
)
I think it is a fantastic idea. good job ! |
src/helpers/misc.ts
Outdated
* Creates a batched version of a callback so that all calls to it in the same | ||
* microtick will only call the original callback once. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
THIS IS A LIE :)
what would be less error prone: class MyStore extends Store {
constructor() {...}
static mutators = ["myMytator","myOtherMutator"]
myMutator(value) { ... }
myOtherMutator(value) { ... }
myGetter() { return value; }
} or class MyStore extends Store {
constructor() {...}
mutators : {
myMutator = function (value) { ... },
myOtherMutator = function (value) { ... }
}
myGetter() { return value; } // if I write this.mutators.myMutator here, it's obviously a bug
} |
Reactivity is very very (very) slow, with potential overhead exceeding 90%. This commit completely revisit the way store updates trigger app rendering. Previously, this process relied entirely on Owl's reactivity system. With a reactive store, every `this.something` call traverses the reactive proxy, incurring significant cost, regardless of whether `something` was a method, a property, called publicly or privately. Instead, it introduces a new approach where store updates are triggered by explicitly declared public methods (mutators) Since stores follow the Command-Query Separation (CQS) principle, all public methods strictly mutate state in a store, they cannot have any other effect since they cannot return anything. The new approach also relies on proxies to track mutators calls, but the proxy is only on the surface. A mutator is usually only called once in the component. A property is also usually accessed only once for rendering. The internal working of the store is not affected and can run at full speed without any overhead. Note the loss of fined-grained rendering. However, as observed, bypassing reactivity might result in faster overall app rendering compared to relying on reactivity for fine-grained rendering, even if it means rendering the entire app rather than a few components. Reactivity had another unexpected error-prone issue: since everything was proxied, we could never compare objects references without using `toRaw` to retrieve the underlying un-proxied object. It was very error prone and added noise in the code. On the large number data set, open the find & replace panel and search for "1" It currently takes 1080ms (search + rendering) With this commit, that time is reduced to just 111ms, nearly a tenfold improvement! Task: 3829125
edd3c0d
to
5f4bcfc
Compare
@robodoo r+ |
Reactivity is very very (very) slow, with potential overhead exceeding 90%. This commit completely revisit the way store updates trigger app rendering. Previously, this process relied entirely on Owl's reactivity system. With a reactive store, every `this.something` call traverses the reactive proxy, incurring significant cost, regardless of whether `something` was a method, a property, called publicly or privately. Instead, it introduces a new approach where store updates are triggered by explicitly declared public methods (mutators) Since stores follow the Command-Query Separation (CQS) principle, all public methods strictly mutate state in a store, they cannot have any other effect since they cannot return anything. The new approach also relies on proxies to track mutators calls, but the proxy is only on the surface. A mutator is usually only called once in the component. A property is also usually accessed only once for rendering. The internal working of the store is not affected and can run at full speed without any overhead. Note the loss of fined-grained rendering. However, as observed, bypassing reactivity might result in faster overall app rendering compared to relying on reactivity for fine-grained rendering, even if it means rendering the entire app rather than a few components. Reactivity had another unexpected error-prone issue: since everything was proxied, we could never compare objects references without using `toRaw` to retrieve the underlying un-proxied object. It was very error prone and added noise in the code. On the large number data set, open the find & replace panel and search for "1" It currently takes 1080ms (search + rendering) With this commit, that time is reduced to just 111ms, nearly a tenfold improvement! closes #4104 Task: 3829125 Signed-off-by: R茅mi Rahir (rar) <rar@odoo.com>
Description:
Reactivity is very very (very) slow, with potential overhead exceeding 90%.
With a reactive store, every
this.something
call traverses the reactiveproxy, incurring significant cost.
This commit completely revisit the way store updates trigger app rendering.
Previously, this process relied entirely on Owl's reactivity system.
Instead, it introduces a new approach where store updates are triggered by
explicitly declared public methods (mutators)
Since stores follow the CQS principle all public methods actually
mutate state in a store, they cannot have any other effect since they
can't return anything.
The new approach also relies on proxies to track mutators calls, but
the proxy is only on the surface. A mutator is usually only called once
in the component. A properties is also usually accessed only once for
rendering. The internal working of the store is not affected and can
run at full speed without any overhead.
Note the loss of fined-grained rendering. However, as observed, bypassing
reactivity might result in faster overall app rendering compared to
relying on reactivity for fine-grained rendering, even if it means rendering
the entire app rather than a few components.
Reactivity had another unexpected error-prone issue: since everything
was proxied, we could never compare objects references without using
toRaw
to retrieve the underlying un-proxied object. It was veryerror prone and added noise in the code.
On the large number data set, open the find & replace panel and search for "1"
It currently takes 1080ms (search + rendering)
With this commit, that time is reduced to just 111ms, nearly a tenfold
improvement!
Task: : 3829125
review checklist