From c7d9db03cdc7e26452c705a4efd4933d1a1b571c Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Sun, 2 May 2021 20:33:55 +0200 Subject: [PATCH 01/25] added proxy core functionality, which only triggers rerender if used branch of object has changed (in theory not tested yet) --- packages/core/src/runtime/index.ts | 32 ++++++++++++++- packages/core/src/runtime/observer.ts | 2 + .../CallbackSubscriptionContainer.ts | 8 ++-- .../ComponentSubscriptionContainer.ts | 8 ++-- .../container/SubscriptionContainer.ts | 36 ++++++++++++++-- .../runtime/subscription/sub.controller.ts | 41 +++++++++++-------- packages/core/src/state/index.ts | 13 ++++++ packages/core/src/state/state.observer.ts | 4 +- 8 files changed, 113 insertions(+), 31 deletions(-) diff --git a/packages/core/src/runtime/index.ts b/packages/core/src/runtime/index.ts index 628b7b93..7cecd2f6 100644 --- a/packages/core/src/runtime/index.ts +++ b/packages/core/src/runtime/index.ts @@ -5,6 +5,7 @@ import { CallbackSubscriptionContainer, ComponentSubscriptionContainer, defineConfig, + notEqual, } from '../internal'; export class Runtime { @@ -140,8 +141,35 @@ export class Runtime { if (subscriptionContainer.isObjectBased) this.handleObjectBasedSubscription(subscriptionContainer, job); - subscriptionsToUpdate.add(subscriptionContainer); - job.subscriptionContainersToUpdate.delete(subscriptionContainer); + // Check if proxy property has changed + if (subscriptionContainer.proxyBased && job.observer._key) { + const paths = subscriptionContainer.proxyBased[job.observer._key]; + if (paths) { + for (const path of paths) { + let newValue = undefined; + for (const branch of path) { + newValue = job.observer.value[branch]; + } + + let previousValue = undefined; + for (const branch of path) { + previousValue = job.observer.previousValue[branch]; + } + + // Check if value has changed, if so add it to the rerender queue + if (notEqual(newValue, previousValue)) { + subscriptionsToUpdate.add(subscriptionContainer); + job.subscriptionContainersToUpdate.delete( + subscriptionContainer + ); + break; + } + } + } + } else { + subscriptionsToUpdate.add(subscriptionContainer); + job.subscriptionContainersToUpdate.delete(subscriptionContainer); + } }); }); diff --git a/packages/core/src/runtime/observer.ts b/packages/core/src/runtime/observer.ts index 8b55dd27..85262bc8 100644 --- a/packages/core/src/runtime/observer.ts +++ b/packages/core/src/runtime/observer.ts @@ -17,6 +17,7 @@ export class Observer { public dependents: Set = new Set(); // Observers that depend on this Observer public subs: Set = new Set(); // SubscriptionContainers (Components) that this Observer has subscribed public value?: ValueType; // Value of Observer + public previousValue?: ValueType; // Previous Value of Observer /** * @internal @@ -36,6 +37,7 @@ export class Observer { this.agileInstance = () => agileInstance; this._key = config.key; this.value = config.value; + this.previousValue = config.value; config.dependents?.forEach((observer) => this.depend(observer)); config.subs?.forEach((subscriptionContainer) => this.subscribe(subscriptionContainer) diff --git a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts index a2fc0ef0..e9771a0b 100644 --- a/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/CallbackSubscriptionContainer.ts @@ -1,7 +1,7 @@ import { Observer, SubscriptionContainer, - SubscriptionContainerKeyType, + SubscriptionContainerConfigInterface, } from '../../../internal'; export class CallbackSubscriptionContainer extends SubscriptionContainer { @@ -12,14 +12,14 @@ export class CallbackSubscriptionContainer extends SubscriptionContainer { * CallbackSubscriptionContainer - Subscription Container for Callback based Subscriptions * @param callback - Callback Function that causes rerender on Component that is subscribed by Agile * @param subs - Initial Subscriptions - * @param key - Key/Name of Callback Subscription Container + * @param config - Config */ constructor( callback: Function, subs: Array = [], - key?: SubscriptionContainerKeyType + config: SubscriptionContainerConfigInterface = {} ) { - super(subs, key); + super(subs, config); this.callback = callback; } } diff --git a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts index 65eca82c..d71c3e2f 100644 --- a/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/ComponentSubscriptionContainer.ts @@ -1,7 +1,7 @@ import { Observer, SubscriptionContainer, - SubscriptionContainerKeyType, + SubscriptionContainerConfigInterface, } from '../../../internal'; export class ComponentSubscriptionContainer extends SubscriptionContainer { @@ -12,14 +12,14 @@ export class ComponentSubscriptionContainer extends SubscriptionContainer { * ComponentSubscriptionContainer - SubscriptionContainer for Component based Subscription * @param component - Component that is subscribed by Agile * @param subs - Initial Subscriptions - * @param key - Key/Name of Component Subscription Container + * @param config - Config */ constructor( component: any, subs: Array = [], - key?: SubscriptionContainerKeyType + config: SubscriptionContainerConfigInterface = {} ) { - super(subs, key); + super(subs, config); this.component = component; } } diff --git a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts index 66752e06..42442381 100644 --- a/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts +++ b/packages/core/src/runtime/subscription/container/SubscriptionContainer.ts @@ -1,10 +1,19 @@ -import { generateId, Observer } from '../../../internal'; +import { + defineConfig, + generateId, + notEqual, + Observer, +} from '../../../internal'; export class SubscriptionContainer { public key?: SubscriptionContainerKeyType; public ready = false; public subs: Set; // Observers that are Subscribed to this SubscriptionContainer (Component) + // Represents the paths to the properties used in the Component of States + public proxyKeyMap: ProxyMapInterface; + public proxyBased = false; + // For Object based Subscription public isObjectBased = false; public observerKeysToUpdate: Array = []; // Holds temporary keys of Observers that got updated (Note: keys based on 'subsObject') @@ -15,12 +24,31 @@ export class SubscriptionContainer { * SubscriptionContainer - Represents Component/(Way to rerender Component) that is subscribed by Observer/s (Agile) * -> Used to cause rerender on Component * @param subs - Initial Subscriptions - * @param key - Key/Name of Subscription Container + * @param config - Config */ - constructor(subs: Array = [], key?: SubscriptionContainerKeyType) { + constructor( + subs: Array = [], + config: SubscriptionContainerConfigInterface = {} + ) { + const _config = defineConfig(config, { + proxyKeymap: {}, + key: generateId(), + }); + this.subs = new Set(subs); - this.key = key || generateId(); + this.key = _config.key; + this.proxyKeyMap = config.proxyKeyMap as any; + this.proxyBased = notEqual(this.proxyKeyMap, {}); } } export type SubscriptionContainerKeyType = string | number; + +export interface SubscriptionContainerConfigInterface { + proxyKeyMap?: ProxyMapInterface; + key?: SubscriptionContainerKeyType; +} + +export interface ProxyMapInterface { + [key: string]: { paths: string[][] }; +} diff --git a/packages/core/src/runtime/subscription/sub.controller.ts b/packages/core/src/runtime/subscription/sub.controller.ts index 1a404c47..ba3e0318 100644 --- a/packages/core/src/runtime/subscription/sub.controller.ts +++ b/packages/core/src/runtime/subscription/sub.controller.ts @@ -6,6 +6,7 @@ import { CallbackSubscriptionContainer, isFunction, SubscriptionContainerKeyType, + SubscriptionContainerConfigInterface, } from '../../internal'; export class SubController { @@ -33,12 +34,12 @@ export class SubController { * Subscribe with Object shaped Subscriptions * @param integrationInstance - Callback Function or Component * @param subs - Initial Subscription Object - * @param key - Key/Name of SubscriptionContainer + * @param config - Config */ public subscribeWithSubsObject( integrationInstance: any, subs: { [key: string]: Observer } = {}, - key?: SubscriptionContainerKeyType + config: SubscriptionContainerConfigInterface = {} ): { subscriptionContainer: SubscriptionContainer; props: { [key: string]: Observer['value'] }; @@ -53,7 +54,7 @@ export class SubController { const subscriptionContainer = this.registerSubscription( integrationInstance, subsArray, - key + config ); // Set SubscriptionContainer to Object based @@ -81,18 +82,18 @@ export class SubController { * Subscribe with Array shaped Subscriptions * @param integrationInstance - Callback Function or Component * @param subs - Initial Subscription Array - * @param key - Key/Name of SubscriptionContainer + * @param config - Config */ public subscribeWithSubsArray( integrationInstance: any, subs: Array = [], - key?: SubscriptionContainerKeyType + config: SubscriptionContainerConfigInterface = {} ): SubscriptionContainer { // Register Subscription -> decide weather subscriptionInstance is callback or component based const subscriptionContainer = this.registerSubscription( integrationInstance, subs, - key + config ); // Register subs @@ -206,16 +207,24 @@ export class SubController { * Registers SubscriptionContainer and decides weather integrationInstance is a callback or component based Subscription * @param integrationInstance - Callback Function or Component * @param subs - Initial Subscriptions - * @param key - Key/Name of SubscriptionContainer + * @param config - Config */ public registerSubscription( integrationInstance: any, subs: Array = [], - key?: SubscriptionContainerKeyType + config: SubscriptionContainerConfigInterface = {} ): SubscriptionContainer { if (isFunction(integrationInstance)) - return this.registerCallbackSubscription(integrationInstance, subs, key); - return this.registerComponentSubscription(integrationInstance, subs, key); + return this.registerCallbackSubscription( + integrationInstance, + subs, + config + ); + return this.registerComponentSubscription( + integrationInstance, + subs, + config + ); } //========================================================================================================= @@ -228,17 +237,17 @@ export class SubController { * otherwise it creates a new Instance called 'subscriptionContainer' which holds the new SubscriptionContainer * @param componentInstance - Component that got subscribed by Observer/s * @param subs - Initial Subscriptions - * @param key - Key/Name of SubscriptionContainer + * @param config - Config */ public registerComponentSubscription( componentInstance: any, subs: Array = [], - key?: SubscriptionContainerKeyType + config: SubscriptionContainerConfigInterface = {} ): ComponentSubscriptionContainer { const componentSubscriptionContainer = new ComponentSubscriptionContainer( componentInstance, subs, - key + config ); this.componentSubs.add(componentSubscriptionContainer); @@ -278,17 +287,17 @@ export class SubController { * Registers Callback based Subscription * @param callbackFunction - Callback Function that causes rerender on Component which got subscribed by Observer/s * @param subs - Initial Subscriptions - * @param key - Key/Name of SubscriptionContainer + * @param config - Config */ public registerCallbackSubscription( callbackFunction: () => void, subs: Array = [], - key?: SubscriptionContainerKeyType + config: SubscriptionContainerConfigInterface = {} ): CallbackSubscriptionContainer { const callbackSubscriptionContainer = new CallbackSubscriptionContainer( callbackFunction, subs, - key + config ); this.callbackSubs.add(callbackSubscriptionContainer); callbackSubscriptionContainer.ready = true; diff --git a/packages/core/src/state/index.ts b/packages/core/src/state/index.ts index ec8a10e4..32538e2b 100644 --- a/packages/core/src/state/index.ts +++ b/packages/core/src/state/index.ts @@ -96,6 +96,19 @@ export class State { return this._value; } + public get proxy(): ValueType { + if (isValidObject(this._value)) { + return new Proxy(this._value as any, { + get: (object, property) => { + console.log(object, property); + + return property in object ? object[property] : undefined; + }, + }) as any; + } + return this._value; + } + /** * @public * Set Key/Name of State diff --git a/packages/core/src/state/state.observer.ts b/packages/core/src/state/state.observer.ts index 52bb5301..dc71da87 100644 --- a/packages/core/src/state/state.observer.ts +++ b/packages/core/src/state/state.observer.ts @@ -118,6 +118,7 @@ export class StateObserver extends Observer { */ public perform(job: StateRuntimeJob) { const state = job.observer.state(); + const previousValue = copy(state._value); // Assign new State Values state.previousStateValue = copy(state._value); @@ -136,9 +137,10 @@ export class StateObserver extends Observer { this.sideEffects(job); // Assign Public Value to Observer after sideEffects like 'rebuildGroup', - // because sometimes (for instance in Group) the publicValue is not the value(nextStateValue) + // because sometimes (for instance in Group) the publicValue is not the value (nextStateValue) // and the observer value is at some point the publicValue because the end user uses it job.observer.value = copy(state.getPublicValue()); + job.observer.previousValue = previousValue; } //========================================================================================================= From 5cff0e4c34fbd826bb89b43072fb3702b7ee4949 Mon Sep 17 00:00:00 2001 From: Benno Kohrs Date: Mon, 3 May 2021 08:08:20 +0200 Subject: [PATCH 02/25] created dummy useProxy hook --- .../react/functional-component-ts/src/App.tsx | 24 ++- .../functional-component-ts/src/core/index.ts | 115 ++++++------ packages/core/src/runtime/index.ts | 27 ++- .../container/SubscriptionContainer.ts | 6 +- packages/event/src/hooks/useEvent.ts | 2 +- packages/react/src/DeepProxy.ts | 98 ++++++++++ packages/react/src/hooks/useAgile.ts | 2 +- packages/react/src/hooks/useProxy.ts | 171 ++++++++++++++++++ packages/react/src/index.ts | 1 + 9 files changed, 383 insertions(+), 63 deletions(-) create mode 100644 packages/react/src/DeepProxy.ts create mode 100644 packages/react/src/hooks/useProxy.ts diff --git a/examples/react/functional-component-ts/src/App.tsx b/examples/react/functional-component-ts/src/App.tsx index c69e67cc..f0972b2b 100644 --- a/examples/react/functional-component-ts/src/App.tsx +++ b/examples/react/functional-component-ts/src/App.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import './App.css'; -import { useAgile, useWatcher } from '@agile-ts/react'; +import { useAgile, useWatcher, useProxy } from '@agile-ts/react'; import { useEvent } from '@agile-ts/event'; import { COUNTUP, @@ -10,8 +10,9 @@ import { MY_STATE, MY_STATE_2, MY_STATE_3, + STATE_OBJECT, } from './core'; -import { globalBind } from '@agile-ts/core'; +import { generateId, globalBind } from '@agile-ts/core'; let rerenderCount = 0; let rerenderCountInCountupView = 0; @@ -41,6 +42,8 @@ const App = (props: any) => { ]); const [myGroup] = useAgile([MY_COLLECTION.getGroupWithReference('myGroup')]); + const stateObject = useProxy(STATE_OBJECT); + // const myCollection2 = useAgile(MY_COLLECTION); const mySelector = useAgile(MY_COLLECTION.getSelector('mySelector')); @@ -93,6 +96,23 @@ const App = (props: any) => {

{myComputed}

+
+

My State Object

+

Deep Name: {stateObject.friends.hans.name}

+ + +
+

My Event