Skip to content

Commit

Permalink
feat: allow dynamic add observer
Browse files Browse the repository at this point in the history
  • Loading branch information
Tidyzq committed Jun 20, 2024
1 parent 07110ad commit 844bab4
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 50 deletions.
120 changes: 80 additions & 40 deletions glass-easel/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
type DataGroupObserverTree,
type DataValue,
type DeepCopyStrategy,
type DataObserver,
} from './data_proxy'
import { simpleDeepCopy } from './data_utils'
import {
Expand Down Expand Up @@ -229,7 +230,6 @@ type ComponentDefinitionDetail<
> = {
proto: ComponentInstProto<TData, TProperty, TMethod>
template: Template
dataGroupObserverTree: DataGroupObserverTree
dataDeepCopy: DeepCopyStrategy
propertyPassingDeepCopy: DeepCopyStrategy
relationDefinitionGroup: RelationDefinitionGroup | null
Expand Down Expand Up @@ -372,7 +372,7 @@ export class ComponentDefinition<
proto._$methodMap = behavior._$methodMap

// init other helpers
const dataGroupObserverTree = behavior._$generateObserverTree()
proto._$dataGroupObserverTree = behavior._$generateObserverTree()
proto._$lifetimeFuncs = behavior._$getAllLifetimeFuncs()
proto._$pageLifetimeFuncs = behavior._$getAllPageLifetimeFuncs()
const relationDefinitionGroup = generateRelationDefinitionGroup(behavior._$relationMap)
Expand All @@ -385,7 +385,6 @@ export class ComponentDefinition<
this._$detail = {
proto,
template,
dataGroupObserverTree,
dataDeepCopy,
propertyPassingDeepCopy,
relationDefinitionGroup,
Expand Down Expand Up @@ -425,6 +424,8 @@ export class Component<
/** @internal */
_$definition: ComponentDefinition<TData, TProperty, TMethod>
/** @internal */
_$dataGroupObserverTree: DataGroupObserverTree
/** @internal */
_$lifetimeFuncs: LifetimeFuncs
/** @internal */
_$pageLifetimeFuncs: PageLifetimeFuncs
Expand Down Expand Up @@ -527,7 +528,6 @@ export class Component<
}
const { proto, template, dataDeepCopy, propertyPassingDeepCopy, relationDefinitionGroup } =
def._$detail!
let dataGroupObserverTree = def._$detail!.dataGroupObserverTree
const options = def._$options
const behavior = def.behavior
const nodeTreeContext: GeneralBackendContext | null = owner
Expand Down Expand Up @@ -748,9 +748,6 @@ export class Component<
return { list, listAsTrait } as any
}
if (behavior._$init.length > 0) {
let cowObserver = true
let cowLifetime = true
let cowPageLifetime = true
let cowMethodMap = true
const methodCaller = comp.getMethodCaller()
const builderContext: BuilderContext<any, any, any> = {
Expand All @@ -777,16 +774,7 @@ export class Component<
'[implement]',
behavior.is,
)
if (cowObserver) {
cowObserver = false
dataGroupObserverTree = dataGroupObserverTree.cloneSub()
}
try {
dataGroupObserverTree.addObserver(func, parseMultiPaths(dataPaths))
} catch (e) {
// parse multi paths may throw errors
dispatchError(e, `observer`, behavior.is)
}
comp.dynamicAddObserver(func, dataPaths)
},
lifetime: <T extends keyof Lifetimes>(name: T, func: Lifetimes[T]): void => {
if (initDone)
Expand All @@ -795,17 +783,7 @@ export class Component<
'[implement]',
behavior.is,
)
if (cowLifetime) {
cowLifetime = false
comp._$lifetimeFuncs = behavior._$getAllLifetimeFuncs()
}
const fag = comp._$lifetimeFuncs
if (fag[name]) {
fag[name]!.add(func as GeneralFuncType)
} else {
const fa = (fag[name] = new FuncArr('lifetime') as LifetimeFuncs[T])
fa!.add(func as GeneralFuncType)
}
comp.addLifetimeListener(name, func)
},
pageLifetime: (name: string, func: (...args: unknown[]) => void): void => {
if (initDone)
Expand All @@ -814,17 +792,7 @@ export class Component<
'[implement]',
behavior.is,
)
if (cowPageLifetime) {
cowPageLifetime = false
comp._$pageLifetimeFuncs = behavior._$getAllPageLifetimeFuncs()
}
const fag = comp._$pageLifetimeFuncs
if (fag[name]) {
fag[name]!.add(func)
} else {
const fa = (fag[name] = new FuncArr('pageLifetime'))
fa.add(func)
}
comp.addPageLifetimeListener(name, func)
},
method: <Fn extends ComponentMethod>(func: Fn) => Component._$tagMethod(func),
listener: <T>(func: EventListener<T>) => Component._$tagMethod(func),
Expand Down Expand Up @@ -872,7 +840,7 @@ export class Component<
dataDeepCopy,
propertyPassingDeepCopy,
options.reflectToAttributes,
dataGroupObserverTree,
comp._$dataGroupObserverTree,
)
comp._$dataGroup = dataGroup

Expand Down Expand Up @@ -1227,6 +1195,32 @@ export class Component<
return this._$methodCaller
}

/**
* Add a lifetime event listener on the component
*/
addLifetimeListener<N extends keyof Lifetimes>(name: N, func: Lifetimes[N]) {
if (!Object.prototype.hasOwnProperty.call(this, '_$lifetimeFuncs')) {
// copy on write
this._$lifetimeFuncs = this._$behavior._$getAllLifetimeFuncs()
}
const fag = this._$lifetimeFuncs
const fa = (fag[name] = fag[name] || (new FuncArr('lifetime') as LifetimeFuncs[N]))
fa!.add(func as GeneralFuncType)
}

/**
* remove a lifetime event listener on the component
*/
removeLifetimeListener<N extends keyof Lifetimes>(name: N, func: Lifetimes[N]) {
if (!Object.prototype.hasOwnProperty.call(this, '_$lifetimeFuncs')) {
// copy on write
this._$lifetimeFuncs = this._$behavior._$getAllLifetimeFuncs()
}
const fag = this._$lifetimeFuncs
const fa = fag[name]
fa?.remove(func as GeneralFuncType)
}

/**
* Triggers a life-time callback on an element
*
Expand All @@ -1246,6 +1240,32 @@ export class Component<
return this.triggerLifetime(name, args)
}

/**
* Add a page lifetime event listener on the component
*/
addPageLifetimeListener(name: string, func: (...args: unknown[]) => void) {
if (!Object.prototype.hasOwnProperty.call(this, '_$pageLifetimeFuncs')) {
// copy on write
this._$pageLifetimeFuncs = this._$behavior._$getAllPageLifetimeFuncs()
}
const fag = this._$pageLifetimeFuncs
const fa = (fag[name] = fag[name] || new FuncArr('pageLifetime'))
fa.add(func)
}

/**
* remove a page lifetime event listener on the component
*/
removePageLifetimeListener(name: string, func: (...args: unknown[]) => void) {
if (!Object.prototype.hasOwnProperty.call(this, '_$pageLifetimeFuncs')) {
// copy on write
this._$pageLifetimeFuncs = this._$behavior._$getAllPageLifetimeFuncs()
}
const fag = this._$pageLifetimeFuncs
const fa = fag[name]
fa?.remove(func)
}

/**
* Triggers a page-life-time callback on an element
*/
Expand Down Expand Up @@ -1277,6 +1297,25 @@ export class Component<
return this.triggerPageLifetime(name, args)
}

/**
* Add an observer on the runtime
* @note This method is for debug or inspect use only, do not use it in production.
*/
dynamicAddObserver(func: DataObserver, dataPaths: string | readonly string[]) {
if (!Object.prototype.hasOwnProperty.call(this, '_$dataGroupObserverTree')) {
// copy on write
this._$dataGroupObserverTree = this._$dataGroupObserverTree.cloneSub()
if (this._$dataGroup) this._$dataGroup._$observerTree = this._$dataGroupObserverTree
}
const dataGroupObserverTree = this._$dataGroupObserverTree
try {
dataGroupObserverTree.addObserver(func, parseMultiPaths(dataPaths))
} catch (e) {
// parse multi paths may throw errors
dispatchError(e, `observer`, this.is)
}
}

/**
* Get the target elements of a relation
*/
Expand Down Expand Up @@ -1550,6 +1589,7 @@ type ComponentInstProto<
> = Component<TData, TProperty, TMethod> & {
_$behavior: Behavior<TData, TProperty, TMethod, any>
_$definition: ComponentDefinition<TData, TProperty, TMethod>
_$dataGroupObserverTree: DataGroupObserverTree
_$lifetimeFuncs: LifetimeFuncs
_$pageLifetimeFuncs: PageLifetimeFuncs
_$methodMap: MethodList
Expand Down
24 changes: 14 additions & 10 deletions glass-easel/src/data_proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ type ObserverNode = {

export class DataGroupObserverTree {
propFields: { [name: string]: PropertyDefinition }
observerTree: ObserverNode = { sub: {} }
observerRoot: ObserverNode = { sub: {} }
observers: DataObserverWithPath[] = []

constructor(propFields: { [name: string]: PropertyDefinition }) {
Expand All @@ -348,7 +348,7 @@ export class DataGroupObserverTree {
cloneSub(): DataGroupObserverTree {
const ret = new DataGroupObserverTree(this.propFields)
if (this.observers.length > 0) {
ret.observerTree = simpleDeepCopy(this.observerTree)
ret.observerRoot = simpleDeepCopy(this.observerRoot)
ret.observers = this.observers.slice()
}
return ret
Expand All @@ -362,7 +362,7 @@ export class DataGroupObserverTree {
})
for (let i = 0; i < dataPath.length; i += 1) {
const singlePath = dataPath[i]!
let cur = this.observerTree
let cur = this.observerRoot
let wildcard = false
for (let j = 0; j < singlePath.length; j += 1) {
const pathSlice = singlePath[j]!
Expand Down Expand Up @@ -506,8 +506,8 @@ export class DataGroup<
private _$propertyPassingDeepCopy: DeepCopyStrategy
private _$reflectToAttributes: boolean
private _$propFields: { [name: string]: PropertyDefinition }
private _$observerTree: ObserverNode
private _$observers: DataObserverWithPath[]
/** @internal */
_$observerTree: DataGroupObserverTree
private _$observerStatus: boolean[]
private _$modelBindingListener: { [name: string]: ModelBindingListener } | null = null
private _$updateListener?: DataUpdateCallback
Expand Down Expand Up @@ -557,8 +557,7 @@ export class DataGroup<
this._$propertyPassingDeepCopy = propertyPassingDeepCopy
this._$reflectToAttributes = reflectToAttributes && !!associatedComponent
this._$propFields = observerTree.propFields
this._$observerTree = observerTree.observerTree
this._$observers = observerTree.observers
this._$observerTree = observerTree
this._$observerStatus = new Array(observerTree.observers.length) as boolean[]
this.innerData = this._$generateInnerData(data)
}
Expand Down Expand Up @@ -670,7 +669,7 @@ export class DataGroup<
const isChainedUpdates = !!this._$doingUpdates
let combinedChanges: DataChange[]
let propChanges: PropertyChange[]
if (this._$observers.length > 0) {
if (this._$observerTree.observers.length > 0) {
if (this._$doingUpdates) {
combinedChanges = this._$doingUpdates.combined
propChanges = this._$doingUpdates.prop
Expand Down Expand Up @@ -969,7 +968,7 @@ export class DataGroup<
})
}
}
markTriggerBitOnPath(this._$observerTree, this._$observerStatus, path)
markTriggerBitOnPath(this._$observerTree.observerRoot, this._$observerStatus, path)
if (!excluded && changed) {
combinedChanges.push(change)
}
Expand All @@ -984,7 +983,12 @@ export class DataGroup<
let changesCount: number
do {
changesCount = this._$doingUpdates.count
triggerAndCleanTriggerBit(this._$observers, this._$observerStatus, comp, this.data)
triggerAndCleanTriggerBit(
this._$observerTree.observers,
this._$observerStatus,
comp,
this.data,
)
} while (changesCount !== this._$doingUpdates.count)
this._$doingUpdates = null
}
Expand Down

0 comments on commit 844bab4

Please sign in to comment.