-
Notifications
You must be signed in to change notification settings - Fork 873
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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an interface to ServerValues that lets us read synctrees just in time #2499
Changes from 1 commit
ece6c5c
63e4f82
4a3b18b
7addf9c
9cff77b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
…time
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,53 @@ import { ChildrenNode } from '../snap/ChildrenNode'; | |
import { SyncTree } from '../SyncTree'; | ||
import { Indexable } from './misc'; | ||
|
||
/* It's critical for performance that we not calculate actual values from a SyncTree | ||
* unless and until the value is needed. Because we expose both a SyncTree and Node | ||
* version of deferred value resolution, we ned a wrapper class that will let us share | ||
* code. | ||
* | ||
* @see https://github.com/firebase/firebase-js-sdk/issues/2487 | ||
*/ | ||
interface DeferredExistingValue { | ||
getImmediateChild(childName: string): DeferredExistingValue; | ||
node(): Node; | ||
} | ||
|
||
class ExistingSnapshotValue implements DeferredExistingValue { | ||
private node_: Node; | ||
constructor(node: Node) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: You can drop the suffix for members of classes that are not part of the public API, which will allow you to use constructor property assignments ( Renaming to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I do that I fail to implement the protocol because |
||
this.node_ = node; | ||
} | ||
|
||
getImmediateChild(childName: string): DeferredExistingValue { | ||
const child = this.node_.getImmediateChild(childName); | ||
return new ExistingSnapshotValue(child); | ||
} | ||
|
||
node(): Node { | ||
return this.node_; | ||
} | ||
} | ||
|
||
class DeferredSyncTreeValue implements DeferredExistingValue { | ||
private syncTree_: SyncTree; | ||
private path_: Path; | ||
|
||
constructor(syncTree: SyncTree, path: Path) { | ||
this.syncTree_ = syncTree; | ||
this.path_ = path; | ||
} | ||
|
||
getImmediateChild(childName: string): DeferredExistingValue { | ||
const childPath = this.path_.child(childName); | ||
return new DeferredSyncTreeValue(this.syncTree_, childPath); | ||
} | ||
|
||
node(): Node { | ||
return this.syncTree_.calcCompleteEventCache(this.path_); | ||
} | ||
} | ||
|
||
/** | ||
* Generate placeholders for deferred values. | ||
* @param {?Object} values | ||
|
@@ -48,9 +95,9 @@ export const generateWithValues = function( | |
* @param {!Object} serverValues | ||
* @return {!(string|number|boolean)} | ||
*/ | ||
export const resolveDeferredValue = function( | ||
export const resolveDeferredLeafValue = function( | ||
value: { [k: string]: unknown } | string | number | boolean, | ||
existing: Node, | ||
existingVal: DeferredExistingValue, | ||
serverValues: { [k: string]: unknown } | ||
): string | number | boolean { | ||
if (!value || typeof value !== 'object') { | ||
|
@@ -59,17 +106,17 @@ export const resolveDeferredValue = function( | |
assert('.sv' in value, 'Unexpected leaf node or priority contents'); | ||
|
||
if (typeof value['.sv'] === 'string') { | ||
return resolveScalarDeferredValue(value['.sv'], existing, serverValues); | ||
return resolveScalarDeferredValue(value['.sv'], existingVal, serverValues); | ||
} else if (typeof value['.sv'] === 'object') { | ||
return resolveComplexDeferredValue(value['.sv'], existing, serverValues); | ||
return resolveComplexDeferredValue(value['.sv'], existingVal, serverValues); | ||
} else { | ||
assert(false, 'Unexpected server value: ' + JSON.stringify(value, null, 2)); | ||
} | ||
}; | ||
|
||
const resolveScalarDeferredValue = function( | ||
op: string, | ||
existing: Node, | ||
existing: DeferredExistingValue, | ||
serverValues: { [k: string]: unknown } | ||
): string | number | boolean { | ||
switch (op) { | ||
|
@@ -82,7 +129,7 @@ const resolveScalarDeferredValue = function( | |
|
||
const resolveComplexDeferredValue = function( | ||
op: object, | ||
existing: Node, | ||
existing: DeferredExistingValue, | ||
unused: { [k: string]: unknown } | ||
): string | number | boolean { | ||
if (!op.hasOwnProperty('increment')) { | ||
|
@@ -93,12 +140,18 @@ const resolveComplexDeferredValue = function( | |
assert(false, 'Unexpected increment value: ' + delta); | ||
} | ||
|
||
const existingNode = existing.node(); | ||
assert( | ||
existingNode !== null && typeof existingNode !== 'undefined', | ||
'Expected ChildrenNode.EMPTY_NODE for nulls' | ||
); | ||
|
||
// Incrementing a non-number sets the value to the incremented amount | ||
if (!existing.isLeafNode()) { | ||
if (!existingNode.isLeafNode()) { | ||
return delta; | ||
} | ||
|
||
const leaf = existing as LeafNode; | ||
const leaf = existingNode as LeafNode; | ||
const existingVal = leaf.getValue(); | ||
if (typeof existingVal !== 'number') { | ||
return delta; | ||
|
@@ -122,14 +175,10 @@ export const resolveDeferredValueTree = function( | |
): SparseSnapshotTree { | ||
const resolvedTree = new SparseSnapshotTree(); | ||
tree.forEachTree(new Path(''), (path, node) => { | ||
const existing = syncTree.calcCompleteEventCache(path); | ||
assert( | ||
existing !== null && typeof existing !== 'undefined', | ||
'Expected ChildrenNode.EMPTY_NODE for nulls' | ||
); | ||
const deferredExisting = new DeferredSyncTreeValue(syncTree, path); | ||
resolvedTree.remember( | ||
path, | ||
resolveDeferredValueSnapshot(node, existing, serverValues) | ||
resolveDeferredValue(node, deferredExisting, serverValues) | ||
); | ||
}); | ||
return resolvedTree; | ||
|
@@ -147,25 +196,37 @@ export const resolveDeferredValueSnapshot = function( | |
node: Node, | ||
existing: Node, | ||
serverValues: Indexable | ||
): Node { | ||
return resolveDeferredValue( | ||
node, | ||
new ExistingSnapshotValue(existing), | ||
serverValues | ||
); | ||
}; | ||
|
||
function resolveDeferredValue( | ||
node: Node, | ||
existingVal: DeferredExistingValue, | ||
serverValues: Indexable | ||
): Node { | ||
const rawPri = node.getPriority().val() as | ||
| Indexable | ||
| boolean | ||
| null | ||
| number | ||
| string; | ||
const priority = resolveDeferredValue( | ||
const priority = resolveDeferredLeafValue( | ||
rawPri, | ||
existing.getPriority(), | ||
existingVal.getImmediateChild('.priority'), | ||
serverValues | ||
); | ||
let newNode: Node; | ||
|
||
if (node.isLeafNode()) { | ||
const leafNode = node as LeafNode; | ||
const value = resolveDeferredValue( | ||
const value = resolveDeferredLeafValue( | ||
leafNode.getValue(), | ||
existing, | ||
existingVal, | ||
serverValues | ||
); | ||
if ( | ||
|
@@ -183,9 +244,9 @@ export const resolveDeferredValueSnapshot = function( | |
newNode = newNode.updatePriority(new LeafNode(priority)); | ||
} | ||
childrenNode.forEachChild(PRIORITY_INDEX, (childName, childNode) => { | ||
const newChildNode = resolveDeferredValueSnapshot( | ||
const newChildNode = resolveDeferredValue( | ||
childNode, | ||
existing.getImmediateChild(childName), | ||
existingVal.getImmediateChild(childName), | ||
serverValues | ||
); | ||
if (newChildNode !== childNode) { | ||
|
@@ -194,4 +255,4 @@ export const resolveDeferredValueSnapshot = function( | |
}); | ||
return newNode; | ||
} | ||
}; | ||
} |
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.
s/not/do not/