@infinite-list/data-model
Advanced tools
Comparing version 0.2.29-stillness.0 to 0.2.29-stillness.1
@@ -123,2 +123,19 @@ import Batchinator from '@x-oasis/batchinator'; | ||
}) => number; | ||
resolveSafeRange(props: { | ||
visibleStartIndex: number; | ||
visibleEndIndex: number; | ||
}): { | ||
startIndex: number; | ||
endIndex: number; | ||
}; | ||
updateIndices(targetIndices: Array<number>, props: { | ||
safeRange: { | ||
startIndex: number; | ||
endIndex: number; | ||
}; | ||
startIndex: number; | ||
maxCount: number; | ||
step: number; | ||
}): number; | ||
resolveRecycleRecycleState(state: ListState<ItemT>): any[]; | ||
resolveRecycleState(state: ListState<ItemT>): { | ||
@@ -125,0 +142,0 @@ recycleState: any[]; |
@@ -6,3 +6,5 @@ export declare type OnEndReachedHelperProps = { | ||
onEndReachedTimeoutThreshold?: number; | ||
distanceFromEndThresholdValue?: number; | ||
onEndReachedHandlerTimeoutThreshold?: number; | ||
maxCountOfHandleOnEndReachedAfterStillness?: number; | ||
}; | ||
@@ -14,1 +16,7 @@ export declare type OnEndReached = (props: { | ||
}) => void; | ||
export declare type SendOnEndReachedDistanceFromBottomStack = Array<{ | ||
distancesFromEnd: Array<number>; | ||
ts: Array<number>; | ||
hit: number; | ||
resetCount: number; | ||
}>; |
import Batchinator from '@x-oasis/batchinator'; | ||
import { OnEndReached, OnEndReachedHelperProps, ScrollMetrics } from '../types'; | ||
import { OnEndReached, OnEndReachedHelperProps, ScrollMetrics, SendOnEndReachedDistanceFromBottomStack } from '../types'; | ||
declare class OnEndReachedHelper { | ||
@@ -9,6 +9,16 @@ readonly id: string; | ||
readonly onEndReachedHandlerBatchinator: Batchinator; | ||
readonly sendOnEndReachedDistanceFromEndStack: SendOnEndReachedDistanceFromBottomStack; | ||
private onEndReached; | ||
private _maxCountOfHandleOnEndReachedAfterStillness; | ||
private _distanceFromEndThresholdValue; | ||
private _waitingForDataChangedSinceEndReached; | ||
private _onEndReachedTimeoutHandler; | ||
readonly _consecutiveDistanceTimeoutThresholdValue = 800; | ||
constructor(props: OnEndReachedHelperProps); | ||
static createStackToken(distanceFromEnd: number): { | ||
ts: number[]; | ||
hit: number; | ||
distancesFromEnd: number[]; | ||
resetCount: number; | ||
}; | ||
clear(): void; | ||
@@ -22,2 +32,13 @@ setHandler(onEndReached: OnEndReached): void; | ||
releaseHandlerMutex(): void; | ||
timeoutReleaseHandlerMutex(): void; | ||
get lastStack(): { | ||
distancesFromEnd: number[]; | ||
ts: number[]; | ||
hit: number; | ||
resetCount: number; | ||
}; | ||
getStack(): SendOnEndReachedDistanceFromBottomStack; | ||
reachCountLimitation(): boolean; | ||
shouldResetCountLimitation(distanceFromEnd: number): boolean; | ||
isConsecutiveDistance(distanceFromEnd: number): boolean; | ||
performEndReached(info: { | ||
@@ -27,2 +48,3 @@ isEndReached: boolean; | ||
}): void; | ||
updateStack(distanceFromEnd: number): void; | ||
onEndReachedHandler(opts: { | ||
@@ -29,0 +51,0 @@ distanceFromEnd: number; |
{ | ||
"name": "@infinite-list/data-model", | ||
"version": "0.2.29-stillness.0", | ||
"version": "0.2.29-stillness.1", | ||
"files": [ | ||
@@ -5,0 +5,0 @@ "dist", |
@@ -137,6 +137,9 @@ import noop from '@x-oasis/noop'; | ||
persistanceIndices, | ||
dispatchMetricsThreshold = DISPATCH_METRICS_THRESHOLD, | ||
stillnessThreshold, | ||
onEndReachedTimeoutThreshold, | ||
distanceFromEndThresholdValue, | ||
onEndReachedHandlerTimeoutThreshold, | ||
dispatchMetricsThreshold = DISPATCH_METRICS_THRESHOLD, | ||
maxCountOfHandleOnEndReachedAfterStillness, | ||
} = props; | ||
@@ -158,3 +161,5 @@ this._keyExtractor = keyExtractor; | ||
onEndReachedTimeoutThreshold, | ||
distanceFromEndThresholdValue, | ||
onEndReachedHandlerTimeoutThreshold, | ||
maxCountOfHandleOnEndReachedAfterStillness, | ||
}); | ||
@@ -1101,153 +1106,162 @@ this._onBatchLayoutFinished = onBatchLayoutFinished; | ||
resolveRecycleState(state: ListState<ItemT>) { | ||
const { | ||
visibleEndIndex, | ||
bufferedEndIndex, | ||
visibleStartIndex, | ||
bufferedStartIndex, | ||
data, | ||
} = state; | ||
resolveSafeRange(props: { | ||
visibleStartIndex: number; | ||
visibleEndIndex: number; | ||
}) { | ||
const { visibleStartIndex, visibleEndIndex } = props; | ||
const velocity = this._scrollMetrics?.velocity || 0; | ||
const _targetIndices = this._bufferSet.indices.map((i) => parseInt(i)); | ||
const targetIndices = new Array(_targetIndices.length).fill(null); | ||
if (velocity < 0) { | ||
return { | ||
startIndex: Math.max( | ||
visibleStartIndex - this.recycleBufferedCount * 2, | ||
0 | ||
), | ||
endIndex: visibleEndIndex, | ||
}; | ||
} else if (velocity > 0) { | ||
return { | ||
startIndex: visibleStartIndex, | ||
endIndex: Math.min( | ||
visibleEndIndex + this.recycleBufferedCount * 2, | ||
this._data.length | ||
), | ||
}; | ||
} | ||
const recycleEnabled = this._recycleEnabled(); | ||
const recycleStateResult = []; | ||
let spaceStateResult = []; | ||
return { | ||
startIndex: Math.max(visibleStartIndex - this.recycleBufferedCount, 0), | ||
endIndex: Math.min( | ||
visibleEndIndex + this.recycleBufferedCount, | ||
this._data.length | ||
), | ||
}; | ||
} | ||
// 只有当recycleEnabled为true的时候,才进行位置替换 | ||
if (recycleEnabled) { | ||
if (visibleEndIndex >= 0) { | ||
for (let index = visibleStartIndex; index <= visibleEndIndex; index++) { | ||
const position = this.getPosition( | ||
index, | ||
visibleStartIndex - this.recycleBufferedCount, | ||
visibleEndIndex + this.recycleBufferedCount | ||
); | ||
if (position !== null) targetIndices[position] = index; | ||
} | ||
} | ||
updateIndices( | ||
targetIndices: Array<number>, | ||
props: { | ||
safeRange: { | ||
startIndex: number; | ||
endIndex: number; | ||
}; | ||
startIndex: number; | ||
maxCount: number; | ||
step: number; | ||
} | ||
) { | ||
const { startIndex, safeRange, step, maxCount } = props; | ||
let finalIndex = startIndex; | ||
let count = 0; | ||
for ( | ||
let index = startIndex; | ||
step > 0 ? index <= this._data.length - 1 : index >= 0; | ||
index += step | ||
) { | ||
const item = this._data[index]; | ||
if (!item) continue; | ||
const itemMeta = this.getItemMeta(item, index); | ||
const itemLayout = itemMeta?.getLayout(); | ||
const visibleSize = Math.max(visibleEndIndex - visibleStartIndex + 1, 0); | ||
const beforeSize = Math.floor((this.recycleThreshold - visibleSize) / 2); | ||
const afterSize = this.recycleThreshold - visibleSize - beforeSize; | ||
if (count < maxCount || !itemLayout) { | ||
const position = this.getPosition( | ||
index, | ||
safeRange.startIndex, | ||
safeRange.endIndex | ||
); | ||
let _beforeCount = 0; | ||
let topStartIndex = visibleStartIndex; | ||
finalIndex = index; | ||
if (position !== null) targetIndices[position] = index; | ||
} else { | ||
break; | ||
} | ||
for ( | ||
let index = visibleStartIndex, size = beforeSize; | ||
size > 0 && index >= 0 && index >= bufferedStartIndex; | ||
size--, index-- | ||
) { | ||
const item = data[index]; | ||
if (!item) continue; | ||
const itemMeta = this.getItemMeta(item, index); | ||
const itemLayout = itemMeta?.getLayout(); | ||
if (_beforeCount < this.recycleBufferedCount || !itemLayout) { | ||
const position = this.getPosition( | ||
index, | ||
visibleStartIndex - this.recycleBufferedCount, | ||
visibleEndIndex + this.recycleBufferedCount | ||
); | ||
topStartIndex = index; | ||
if (position !== null) targetIndices[position] = index; | ||
} else { | ||
break; | ||
} | ||
if (index >= this.initialNumToRender) { | ||
_beforeCount++; | ||
} | ||
if (index >= this.initialNumToRender) { | ||
count++; | ||
} | ||
} | ||
return finalIndex; | ||
} | ||
let _afterCount = 0; | ||
let bottomStartIndex = visibleEndIndex + 1; | ||
resolveRecycleRecycleState(state: ListState<ItemT>) { | ||
const { visibleEndIndex, visibleStartIndex: _visibleStartIndex } = state; | ||
const _targetIndices = this._bufferSet.indices.map((i) => parseInt(i)); | ||
const targetIndices = new Array(_targetIndices.length).fill(null); | ||
const recycleStateResult = []; | ||
const velocity = this._scrollMetrics?.velocity || 0; | ||
for ( | ||
let index = visibleEndIndex + 1, size = afterSize; | ||
size > 0 && index <= bufferedEndIndex; | ||
size--, index++ | ||
) { | ||
const item = data[index]; | ||
if (!item) continue; | ||
const itemMeta = this.getItemMeta(item, index); | ||
const itemLayout = itemMeta?.getLayout(); | ||
const visibleStartIndex = Math.max( | ||
_visibleStartIndex, | ||
this.initialNumToRender | ||
); | ||
if (_afterCount < this.recycleBufferedCount || !itemLayout) { | ||
const position = this.getPosition( | ||
index, | ||
visibleStartIndex - this.recycleBufferedCount, | ||
visibleEndIndex + this.recycleBufferedCount | ||
); | ||
bottomStartIndex = index; | ||
if (position !== null) targetIndices[position] = index; | ||
} else { | ||
break; | ||
} | ||
const safeRange = this.resolveSafeRange({ | ||
visibleStartIndex, | ||
visibleEndIndex, | ||
}); | ||
if (index >= this.initialNumToRender) { | ||
_afterCount++; | ||
} | ||
if (visibleEndIndex >= 0) { | ||
for (let index = visibleStartIndex; index <= visibleEndIndex; index++) { | ||
const position = this.getPosition( | ||
index, | ||
safeRange.startIndex, | ||
safeRange.endIndex | ||
); | ||
if (position !== null) targetIndices[position] = index; | ||
} | ||
} | ||
const minValue = this._bufferSet.getMinValue(); | ||
const maxValue = this._bufferSet.getMaxValue(); | ||
const indexToOffsetMap = this.getIndexRangeOffsetMap( | ||
minValue, | ||
maxValue, | ||
true | ||
); | ||
let topStartIndex = visibleStartIndex; | ||
let bottomStartIndex = visibleEndIndex + 1; | ||
const getOffset = this.getRecycleReuseOffsetBuilder({ | ||
topStartOffset: indexToOffsetMap[Math.max(topStartIndex, 0)] || 0, | ||
bottomStartOffset: indexToOffsetMap[Math.max(bottomStartIndex, 0)] || 0, | ||
minIndex: 0, | ||
topStartIndex, | ||
bottomStartIndex, | ||
if (velocity > 0) { | ||
bottomStartIndex = this.updateIndices(targetIndices, { | ||
safeRange, | ||
startIndex: visibleEndIndex + 1, | ||
maxCount: this.recycleBufferedCount * 2, | ||
step: 1, | ||
}); | ||
} else if (velocity < 0) { | ||
topStartIndex = this.updateIndices(targetIndices, { | ||
safeRange, | ||
startIndex: visibleStartIndex, | ||
maxCount: this.recycleBufferedCount * 2, | ||
step: -1, | ||
}); | ||
} else { | ||
topStartIndex = this.updateIndices(targetIndices, { | ||
safeRange, | ||
startIndex: visibleStartIndex, | ||
maxCount: this.recycleBufferedCount, | ||
step: -1, | ||
}); | ||
bottomStartIndex = this.updateIndices(targetIndices, { | ||
safeRange, | ||
startIndex: visibleEndIndex + 1, | ||
maxCount: this.recycleBufferedCount, | ||
step: 1, | ||
}); | ||
} | ||
targetIndices.forEach((targetIndex, index) => { | ||
if (targetIndex == null) { | ||
targetIndex = _targetIndices[index]; | ||
const item = data[targetIndex]; | ||
if (!item) return; | ||
const itemKey = this.getItemKey(item, targetIndex); | ||
const itemMeta = this.getItemMeta(item, targetIndex); | ||
const itemLayout = itemMeta?.getLayout(); | ||
const itemLength = | ||
(itemLayout?.height || 0) + (itemMeta?.getSeparatorLength() || 0); | ||
const minValue = this._bufferSet.getMinValue(); | ||
const maxValue = this._bufferSet.getMaxValue(); | ||
const indexToOffsetMap = this.getIndexRangeOffsetMap( | ||
minValue, | ||
maxValue, | ||
true | ||
); | ||
let offset = 0; | ||
const getOffset = this.getRecycleReuseOffsetBuilder({ | ||
topStartOffset: indexToOffsetMap[Math.max(topStartIndex, 0)] || 0, | ||
bottomStartOffset: indexToOffsetMap[Math.max(bottomStartIndex, 0)] || 0, | ||
minIndex: this.initialNumToRender, | ||
topStartIndex, | ||
bottomStartIndex, | ||
}); | ||
if (this._scrollMetrics && itemLayout) { | ||
const velocity = this._scrollMetrics.velocity; | ||
offset = getOffset({ | ||
currentIndex: targetIndex, | ||
length: itemLength, | ||
velocity, | ||
}); | ||
} | ||
recycleStateResult.push({ | ||
key: `recycle_${index}`, | ||
targetKey: itemKey, | ||
targetIndex, | ||
length: itemLength, | ||
isSpace: false, | ||
isSticky: false, | ||
item, | ||
itemMeta, | ||
viewable: itemMeta.getState().viewable, | ||
// 如果没有offset,说明item是新增的,那么它渲染就在最开始位置好了 | ||
offset: itemLayout ? offset : 0, | ||
position: 'buffered', | ||
}); | ||
return; | ||
} | ||
const item = data[targetIndex]; | ||
targetIndices.forEach((targetIndex, index) => { | ||
if (targetIndex == null) { | ||
targetIndex = _targetIndices[index]; | ||
const item = this._data[targetIndex]; | ||
if (!item) return; | ||
const itemKey = this.getItemKey(item, targetIndex); | ||
@@ -1259,16 +1273,12 @@ const itemMeta = this.getItemMeta(item, targetIndex); | ||
const itemMetaState = | ||
!this._scrollMetrics || !itemMeta?.getLayout() | ||
? itemMeta | ||
? itemMeta.getState() | ||
: {} | ||
: this._configTuple.resolveItemMetaState( | ||
itemMeta, | ||
this._scrollMetrics, | ||
// should add container offset, because indexToOffsetMap containerOffset is | ||
// exclusive. | ||
() => indexToOffsetMap[targetIndex] + this.getContainerOffset() | ||
); | ||
let offset = 0; | ||
itemMeta?.setItemMetaState(itemMetaState); | ||
if (this._scrollMetrics && itemLayout) { | ||
const velocity = this._scrollMetrics.velocity; | ||
offset = getOffset({ | ||
currentIndex: targetIndex, | ||
length: itemLength, | ||
velocity, | ||
}); | ||
} | ||
@@ -1286,9 +1296,56 @@ recycleStateResult.push({ | ||
// 如果没有offset,说明item是新增的,那么它渲染就在最开始位置好了 | ||
offset: itemLength ? indexToOffsetMap[targetIndex] : 0, | ||
offset: itemLayout ? offset : 0, | ||
position: 'buffered', | ||
}); | ||
return; | ||
} | ||
const item = this._data[targetIndex]; | ||
if (!item) return; | ||
const itemKey = this.getItemKey(item, targetIndex); | ||
const itemMeta = this.getItemMeta(item, targetIndex); | ||
const itemLayout = itemMeta?.getLayout(); | ||
const itemLength = | ||
(itemLayout?.height || 0) + (itemMeta?.getSeparatorLength() || 0); | ||
const itemMetaState = | ||
!this._scrollMetrics || !itemMeta?.getLayout() | ||
? itemMeta | ||
? itemMeta.getState() | ||
: {} | ||
: this._configTuple.resolveItemMetaState( | ||
itemMeta, | ||
this._scrollMetrics, | ||
// should add container offset, because indexToOffsetMap containerOffset is | ||
// exclusive. | ||
() => indexToOffsetMap[targetIndex] + this.getContainerOffset() | ||
); | ||
itemMeta?.setItemMetaState(itemMetaState); | ||
recycleStateResult.push({ | ||
key: `recycle_${index}`, | ||
targetKey: itemKey, | ||
targetIndex, | ||
length: itemLength, | ||
isSpace: false, | ||
isSticky: false, | ||
item, | ||
itemMeta, | ||
viewable: itemMeta.getState().viewable, | ||
// 如果没有offset,说明item是新增的,那么它渲染就在最开始位置好了 | ||
offset: itemLength ? indexToOffsetMap[targetIndex] : 0, | ||
position: 'buffered', | ||
}); | ||
} | ||
}); | ||
return recycleStateResult; | ||
} | ||
spaceStateResult = this.resolveRecycleSpaceState(state); | ||
resolveRecycleState(state: ListState<ItemT>) { | ||
const recycleEnabled = this._recycleEnabled(); | ||
// 只有当recycleEnabled为true的时候,才进行位置替换 | ||
const recycleStateResult = recycleEnabled | ||
? this.resolveRecycleRecycleState(state) | ||
: []; | ||
const spaceStateResult = this.resolveRecycleSpaceState(state); | ||
@@ -1310,11 +1367,10 @@ const stateResult = { | ||
if (startIndex >= endIndex) return []; | ||
const tokens = [ | ||
{ | ||
startIndex, | ||
endIndex: startIndex + 1, | ||
isSticky: false, | ||
isReserved: false, | ||
isSpace: true, | ||
}, | ||
]; | ||
const createToken = (startIndex: number) => ({ | ||
startIndex, | ||
endIndex: startIndex + 1, | ||
isSticky: false, | ||
isReserved: false, | ||
isSpace: true, | ||
}); | ||
const tokens = [createToken(startIndex)]; | ||
@@ -1329,11 +1385,3 @@ this.reservedIndices.forEach((index) => { | ||
lastToken.isReserved = isReserved; | ||
if (index + 1 !== endIndex) { | ||
tokens.push({ | ||
startIndex: index + 1, | ||
endIndex: index + 2, | ||
isSticky: false, | ||
isReserved: false, | ||
isSpace: true, | ||
}); | ||
} | ||
if (index + 1 !== endIndex) tokens.push(createToken(index + 1)); | ||
} else { | ||
@@ -1348,11 +1396,3 @@ lastToken.endIndex = index; | ||
}); | ||
if (index + 1 !== endIndex) { | ||
tokens.push({ | ||
startIndex: index + 1, | ||
endIndex: index + 2, | ||
isSticky: false, | ||
isReserved: false, | ||
isSpace: true, | ||
}); | ||
} | ||
if (index + 1 !== endIndex) tokens.push(createToken(index + 1)); | ||
} | ||
@@ -1411,3 +1451,3 @@ } | ||
if (isSticky || isReserved) { | ||
const item = data[startIndex]; | ||
const item = this._data[startIndex]; | ||
spaceState.push({ | ||
@@ -1479,3 +1519,3 @@ item, | ||
if (isSticky || isReserved) { | ||
const item = data[startIndex]; | ||
const item = this._data[startIndex]; | ||
spaceState.push({ | ||
@@ -1482,0 +1522,0 @@ item, |
@@ -118,3 +118,2 @@ import Batchinator from '@x-oasis/batchinator'; | ||
this.onEndReachedHelper = new OnEndReachedHelper({ | ||
id, | ||
onEndReached, | ||
@@ -121,0 +120,0 @@ onEndReachedThreshold, |
@@ -6,3 +6,5 @@ export type OnEndReachedHelperProps = { | ||
onEndReachedTimeoutThreshold?: number; | ||
distanceFromEndThresholdValue?: number; | ||
onEndReachedHandlerTimeoutThreshold?: number; | ||
maxCountOfHandleOnEndReachedAfterStillness?: number; | ||
}; | ||
@@ -15,1 +17,8 @@ | ||
}) => void; | ||
export type SendOnEndReachedDistanceFromBottomStack = Array<{ | ||
distancesFromEnd: Array<number>; | ||
ts: Array<number>; | ||
hit: number; | ||
resetCount: number; | ||
}>; |
@@ -7,3 +7,9 @@ import Batchinator from '@x-oasis/batchinator'; | ||
} from '../common'; | ||
import { OnEndReached, OnEndReachedHelperProps, ScrollMetrics } from '../types'; | ||
import { | ||
OnEndReached, | ||
OnEndReachedHelperProps, | ||
ScrollMetrics, | ||
SendOnEndReachedDistanceFromBottomStack, | ||
} from '../types'; | ||
import isClamped from '@x-oasis/is-clamped'; | ||
@@ -16,7 +22,12 @@ class OnEndReachedHelper { | ||
readonly onEndReachedHandlerBatchinator: Batchinator; | ||
readonly sendOnEndReachedDistanceFromEndStack: SendOnEndReachedDistanceFromBottomStack; | ||
private onEndReached: OnEndReached; | ||
private _maxCountOfHandleOnEndReachedAfterStillness: number; | ||
private _distanceFromEndThresholdValue: number; | ||
private _waitingForDataChangedSinceEndReached = false; | ||
private _onEndReachedTimeoutHandler: NodeJS.Timeout; | ||
readonly _consecutiveDistanceTimeoutThresholdValue = 800; | ||
constructor(props: OnEndReachedHelperProps) { | ||
@@ -26,2 +37,4 @@ const { | ||
onEndReached, | ||
distanceFromEndThresholdValue = 100, | ||
maxCountOfHandleOnEndReachedAfterStillness = 3, | ||
onEndReachedThreshold = ON_END_REACHED_THRESHOLD, | ||
@@ -38,2 +51,6 @@ onEndReachedTimeoutThreshold = ON_END_REACHED_TIMEOUT_THRESHOLD, | ||
onEndReachedHandlerTimeoutThreshold; | ||
this.sendOnEndReachedDistanceFromEndStack = []; | ||
this._distanceFromEndThresholdValue = distanceFromEndThresholdValue; | ||
this._maxCountOfHandleOnEndReachedAfterStillness = | ||
maxCountOfHandleOnEndReachedAfterStillness; | ||
@@ -47,2 +64,11 @@ this.releaseHandlerMutex = this.releaseHandlerMutex.bind(this); | ||
static createStackToken(distanceFromEnd: number) { | ||
return { | ||
ts: [Date.now()], | ||
hit: 1, | ||
distancesFromEnd: [distanceFromEnd], | ||
resetCount: 0, | ||
}; | ||
} | ||
clear() { | ||
@@ -96,2 +122,67 @@ this.releaseHandlerMutex(); | ||
timeoutReleaseHandlerMutex() { | ||
console.warn( | ||
'OnEndReachedHelper ', | ||
this.lastStack, | ||
"' mutex is released due to timeout" | ||
); | ||
this.releaseHandlerMutex(); | ||
} | ||
get lastStack() { | ||
return this.sendOnEndReachedDistanceFromEndStack[ | ||
this.sendOnEndReachedDistanceFromEndStack.length - 1 | ||
]; | ||
} | ||
getStack() { | ||
return this.sendOnEndReachedDistanceFromEndStack; | ||
} | ||
reachCountLimitation() { | ||
if (!this.lastStack) return false; | ||
if (this.lastStack.hit >= this._maxCountOfHandleOnEndReachedAfterStillness) | ||
return true; | ||
return false; | ||
} | ||
shouldResetCountLimitation(distanceFromEnd: number) { | ||
const { distancesFromEnd } = this.lastStack; | ||
const distance = distancesFromEnd[distancesFromEnd.length - 1]; | ||
if (distanceFromEnd <= 0) return false; | ||
if (distance !== distanceFromEnd) { | ||
this.lastStack.resetCount += 1; | ||
this.lastStack.hit = 1; | ||
return true; | ||
} | ||
return false; | ||
} | ||
isConsecutiveDistance(distanceFromEnd: number) { | ||
const lastStack = | ||
this.sendOnEndReachedDistanceFromEndStack[ | ||
this.sendOnEndReachedDistanceFromEndStack.length - 1 | ||
]; | ||
if (lastStack) { | ||
const { distancesFromEnd, ts } = lastStack; | ||
const base = distancesFromEnd[0]; | ||
const _ts = ts[ts.length - 1]; | ||
const now = Date.now(); | ||
if ( | ||
isClamped( | ||
base - this._distanceFromEndThresholdValue, | ||
distanceFromEnd, | ||
base + this._distanceFromEndThresholdValue | ||
) && | ||
now - _ts < this._consecutiveDistanceTimeoutThresholdValue | ||
) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
performEndReached(info: { isEndReached: boolean; distanceFromEnd: number }) { | ||
@@ -102,9 +193,43 @@ if (this._waitingForDataChangedSinceEndReached) return; | ||
if (typeof this.onEndReached !== 'function') return; | ||
if (isEndReached) { | ||
this.onEndReachedHandlerBatchinator.schedule({ | ||
distanceFromEnd, | ||
}); | ||
if (isEndReached && !this.isConsecutiveDistance(distanceFromEnd)) { | ||
if ( | ||
!this.reachCountLimitation() || | ||
this.shouldResetCountLimitation(distanceFromEnd) | ||
) { | ||
this.onEndReachedHandlerBatchinator.schedule({ | ||
distanceFromEnd, | ||
}); | ||
} | ||
} | ||
} | ||
updateStack(distanceFromEnd: number) { | ||
const lastStack = | ||
this.sendOnEndReachedDistanceFromEndStack[ | ||
this.sendOnEndReachedDistanceFromEndStack.length - 1 | ||
]; | ||
if (lastStack) { | ||
const { distancesFromEnd } = lastStack; | ||
const stackDistanceFromEnd = distancesFromEnd[0]; | ||
if ( | ||
isClamped( | ||
stackDistanceFromEnd - this._distanceFromEndThresholdValue, | ||
distanceFromEnd, | ||
stackDistanceFromEnd + this._distanceFromEndThresholdValue | ||
) | ||
) { | ||
lastStack.distancesFromEnd.push(distanceFromEnd); | ||
lastStack.hit += 1; | ||
lastStack.ts.push(Date.now()); | ||
return; | ||
} | ||
} | ||
this.sendOnEndReachedDistanceFromEndStack.push( | ||
OnEndReachedHelper.createStackToken(distanceFromEnd) | ||
); | ||
} | ||
onEndReachedHandler(opts: { distanceFromEnd: number }) { | ||
@@ -117,5 +242,7 @@ if (typeof this.onEndReached !== 'function') return; | ||
this._onEndReachedTimeoutHandler = setTimeout(() => { | ||
this._waitingForDataChangedSinceEndReached = false; | ||
this.timeoutReleaseHandlerMutex(); | ||
}, this.onEndReachedHandlerTimeoutThreshold); | ||
this.updateStack(distanceFromEnd); | ||
this.onEndReached({ | ||
@@ -122,0 +249,0 @@ distanceFromEnd, |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
1768358
20936