/* * Copyright 2017 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #import "FServerValues.h" #import "FChildrenNode.h" #import "FConstants.h" #import "FLeafNode.h" #import "FSnapshotUtilities.h" const NSString *kTimestamp = @"timestamp"; const NSString *kIncrement = @"increment"; BOOL canBeRepresentedAsLong(NSNumber *num) { switch (num.objCType[0]) { case 'f': // float; fallthrough case 'd': // double return NO; case 'L': // unsigned long; fallthrough case 'Q': // unsigned long long; fallthrough // Only use ulong(long) if there isn't an overflow. if (num.unsignedLongLongValue > LONG_MAX) { return NO; } } return YES; } // Running through CompoundWrites for all update paths has been shown to // be a 20% pessimization in microbenchmarks. This is because it slows // down by O(N) of the write queue length. To eliminate the performance // hit, we wrap around existing data of either snapshot or CompoundWrite // (allowing us to share code) and read from the CompoundWrite only when/where // we need to calculate an incremented value's prior state. @protocol ValueProvider - (id)getChild:(NSString *)pathSegment; - (id)value; @end @interface DeferredValueProvider : NSObject - (instancetype)initWithSyncTree:(FSyncTree *)tree atPath:(FPath *)path; - (id)getChild:(NSString *)pathSegment; - (id)value; @property FPath *path; @property FSyncTree *tree; @end @interface ExistingValueProvider : NSObject - (instancetype)initWithSnapshot:(id)snapshot; - (id)getChild:(NSString *)pathSegment; - (id)value; @property id snapshot; @end @implementation DeferredValueProvider - (instancetype)initWithSyncTree:(FSyncTree *)tree atPath:(FPath *)path { self.tree = tree; self.path = path; return self; } - (id)getChild:(NSString *)pathSegment { FPath *child = [self.path childFromString:pathSegment]; return [[DeferredValueProvider alloc] initWithSyncTree:self.tree atPath:child]; } - (id)value { return [self.tree calcCompleteEventCacheAtPath:self.path excludeWriteIds:@[]]; } @end @implementation ExistingValueProvider - (instancetype)initWithSnapshot:(id)snapshot { self.snapshot = snapshot; return self; } - (id)getChild:(NSString *)pathSegment { return [[ExistingValueProvider alloc] initWithSnapshot:[self.snapshot getImmediateChild:pathSegment]]; } - (id)value { return self.snapshot; } @end @interface FServerValues () + (id)resolveScalarServerOp:(NSString *)op withServerValues:(NSDictionary *)serverValues; + (id)resolveComplexServerOp:(NSDictionary *)op withValueProvider:(id)existing serverValues:(NSDictionary *)serverValues; + (id)resolveDeferredValueSnapshot:(id)node withValueProvider:(id)existing serverValues:(NSDictionary *)serverValues; @end @implementation FServerValues + (NSDictionary *)generateServerValues:(id)clock { long long millis = (long long)([clock currentTime] * 1000); return @{kTimestamp : [NSNumber numberWithLongLong:millis]}; } + (id)resolveDeferredValue:(id)val withExisting:(id)existing serverValues:(NSDictionary *)serverValues { if (![val isKindOfClass:[NSDictionary class]]) { return val; } NSDictionary *dict = val; id op = dict[kServerValueSubKey]; if (op == nil) { return val; } else if ([op isKindOfClass:NSString.class]) { return [FServerValues resolveScalarServerOp:op withServerValues:serverValues]; } else if ([op isKindOfClass:NSDictionary.class]) { return [FServerValues resolveComplexServerOp:op withValueProvider:existing serverValues:serverValues]; } return val; } + (id)resolveScalarServerOp:(NSString *)op withServerValues:(NSDictionary *)serverValues { return serverValues[op]; } + (id)resolveComplexServerOp:(NSDictionary *)op withValueProvider:(id)jitExisting serverValues:(NSDictionary *)serverValues { // Only increment is supported as of now if (op[kIncrement] == nil) { return nil; } // Incrementing a non-number sets the value to the incremented amount NSNumber *delta = op[kIncrement]; id existing = jitExisting.value; if (![existing isLeafNode]) { return delta; } FLeafNode *existingLeaf = existing; if (![existingLeaf.value isKindOfClass:NSNumber.class]) { return delta; } NSNumber *existingNum = existingLeaf.value; BOOL incrLong = canBeRepresentedAsLong(delta); BOOL baseLong = canBeRepresentedAsLong(existingNum); if (incrLong && baseLong) { long x = delta.longValue; long y = existingNum.longValue; long r = x + y; // See "Hacker's Delight" 2-12: Overflow if both arguments have the // opposite sign of the result if (((x ^ r) & (y ^ r)) >= 0) { return @(r); } } return @(delta.doubleValue + existingNum.doubleValue); } + (FCompoundWrite *)resolveDeferredValueCompoundWrite:(FCompoundWrite *)write withSyncTree:(FSyncTree *)tree atPath:(FPath *)path serverValues: (NSDictionary *)serverValues { __block FCompoundWrite *resolved = write; [write enumerateWrites:^(FPath *subPath, id node, BOOL *stop) { id existing = [[DeferredValueProvider alloc] initWithSyncTree:tree atPath:[path child:subPath]]; id resolvedNode = [FServerValues resolveDeferredValueSnapshot:node withValueProvider:existing serverValues:serverValues]; // Node actually changed, use pointer inequality here if (resolvedNode != node) { resolved = [resolved addWrite:resolvedNode atPath:subPath]; } }]; return resolved; } + (id)resolveDeferredValueSnapshot:(id)node withSyncTree:(FSyncTree *)tree atPath:(FPath *)path serverValues:(NSDictionary *)serverValues { id jitExisting = [[DeferredValueProvider alloc] initWithSyncTree:tree atPath:path]; return [FServerValues resolveDeferredValueSnapshot:node withValueProvider:jitExisting serverValues:serverValues]; } + (id)resolveDeferredValueSnapshot:(id)node withExisting:(id)existing serverValues:(NSDictionary *)serverValues { id jitExisting = [[ExistingValueProvider alloc] initWithSnapshot:existing]; return [FServerValues resolveDeferredValueSnapshot:node withValueProvider:jitExisting serverValues:serverValues]; } + (id)resolveDeferredValueSnapshot:(id)node withValueProvider:(id)existing serverValues:(NSDictionary *)serverValues { id priorityVal = [FServerValues resolveDeferredValue:[[node getPriority] val] withExisting:[existing getChild:@".priority"] serverValues:serverValues]; id priority = [FSnapshotUtilities nodeFrom:priorityVal]; if ([node isLeafNode]) { id value = [self resolveDeferredValue:[node val] withExisting:existing serverValues:serverValues]; if (![value isEqual:[node val]] || ![priority isEqual:[node getPriority]]) { return [[FLeafNode alloc] initWithValue:value withPriority:priority]; } else { return node; } } else { __block FChildrenNode *newNode = node; if (![priority isEqual:[node getPriority]]) { newNode = [newNode updatePriority:priority]; } [node enumerateChildrenUsingBlock:^(NSString *childKey, id childNode, BOOL *stop) { id newChildNode = [FServerValues resolveDeferredValueSnapshot:childNode withValueProvider:[existing getChild:childKey] serverValues:serverValues]; if (![newChildNode isEqual:childNode]) { newNode = [newNode updateImmediateChild:childKey withNewChild:newChildNode]; } }]; return newNode; } } @end