Revert [RN][iOS] better event emitting

Reviewed By: sahrens

Differential Revision: D3128586

fb-gh-sync-id: 5c3c79fa983f6c1c43319a6c14049f99e0dfee8a
fbshipit-source-id: 5c3c79fa983f6c1c43319a6c14049f99e0dfee8a
This commit is contained in:
Alex Kotliarskyi 2016-04-01 14:25:51 -07:00 committed by Facebook Github Bot 5
parent cd79e269dc
commit 144dc30661
3 changed files with 74 additions and 114 deletions

View File

@ -17,7 +17,6 @@
#import <OCMock/OCMock.h>
#import "RCTEventDispatcher.h"
#import "RCTBridge+Private.h"
@interface RCTTestEvent : NSObject <RCTEvent>
@property (atomic, assign, readwrite) BOOL canCoalesce;
@ -83,8 +82,7 @@
{
[super setUp];
_bridge = [OCMockObject mockForClass:[RCTBatchedBridge class]];
_bridge = [OCMockObject mockForClass:[RCTBridge class]];
_eventDispatcher = [RCTEventDispatcher new];
[_eventDispatcher setValue:_bridge forKey:@"bridge"];
@ -108,79 +106,61 @@
[_bridge verify];
}
- (void)testNonCoalescingEventIsImmediatelyDispatched
- (void)testNonCoalescingEventsAreImmediatelyDispatched
{
_testEvent.canCoalesce = NO;
[[_bridge expect] dispatchBlock:OCMOCK_ANY queue:RCTJSThread];
[[_bridge expect] enqueueJSCall:_JSMethod
args:[_testEvent arguments]];
[_eventDispatcher sendEvent:_testEvent];
[_bridge verify];
}
- (void)testCoalescingEventIsImmediatelyDispatched
- (void)testCoalescedEventShouldBeDispatchedOnFrameUpdate
{
_testEvent.canCoalesce = YES;
[[_bridge expect] dispatchBlock:OCMOCK_ANY queue:RCTJSThread];
[_eventDispatcher sendEvent:_testEvent];
[_bridge verify];
}
- (void)testMultipleEventsResultInOnlyOneDispatchAfterTheFirstOne
{
[[_bridge expect] dispatchBlock:OCMOCK_ANY queue:RCTJSThread];
[_eventDispatcher sendEvent:_testEvent];
[_eventDispatcher sendEvent:_testEvent];
[_eventDispatcher sendEvent:_testEvent];
[_eventDispatcher sendEvent:_testEvent];
[_eventDispatcher sendEvent:_testEvent];
[_bridge verify];
}
- (void)testRunningTheDispatchedBlockResultInANewOneBeingEnqueued
{
__block dispatch_block_t eventsEmittingBlock;
[[_bridge expect] dispatchBlock:[OCMArg checkWithBlock:^(dispatch_block_t block) {
eventsEmittingBlock = block;
return YES;
}] queue:RCTJSThread];
[_eventDispatcher sendEvent:_testEvent];
[_bridge verify];
// eventsEmittingBlock would be called when js is no longer busy, which will result in emitting events
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[_testEvent arguments]];
eventsEmittingBlock();
[(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];
[_bridge verify];
}
[[_bridge expect] dispatchBlock:OCMOCK_ANY queue:RCTJSThread];
- (void)testNonCoalescingEventForcesColescedEventsToBeImmediatelyDispatched
{
RCTTestEvent *nonCoalescingEvent = [[RCTTestEvent alloc] initWithViewTag:nil
eventName:_eventName
body:@{}
coalescingKey:0];
nonCoalescingEvent.canCoalesce = NO;
[_eventDispatcher sendEvent:_testEvent];
[[_bridge expect] enqueueJSCall:[[_testEvent class] moduleDotMethod]
args:[_testEvent arguments]];
[[_bridge expect] enqueueJSCall:[[nonCoalescingEvent class] moduleDotMethod]
args:[nonCoalescingEvent arguments]];
[_eventDispatcher sendEvent:nonCoalescingEvent];
[_bridge verify];
}
- (void)testBasicCoalescingReturnsLastEvent
{
__block dispatch_block_t eventsEmittingBlock;
[[_bridge expect] dispatchBlock:[OCMArg checkWithBlock:^(dispatch_block_t block) {
eventsEmittingBlock = block;
return YES;
}] queue:RCTJSThread];
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[_testEvent arguments]];
RCTTestEvent *ignoredEvent = [[RCTTestEvent alloc] initWithViewTag:nil
eventName:_eventName
body:@{ @"other": @"body" }
coalescingKey:0];
[_eventDispatcher sendEvent:ignoredEvent];
[_eventDispatcher sendEvent:_testEvent];
eventsEmittingBlock();
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[_testEvent arguments]];
[(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];
[_bridge verify];
}
@ -189,60 +169,20 @@
{
NSString *firstEventName = RCTNormalizeInputEventName(@"firstEvent");
RCTTestEvent *firstEvent = [[RCTTestEvent alloc] initWithViewTag:nil
eventName:firstEventName
body:_body
eventName:firstEventName
body:_body
coalescingKey:0];
__block dispatch_block_t eventsEmittingBlock;
[[_bridge expect] dispatchBlock:[OCMArg checkWithBlock:^(dispatch_block_t block) {
eventsEmittingBlock = block;
return YES;
}] queue:RCTJSThread];
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[firstEvent arguments]];
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[_testEvent arguments]];
[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:_testEvent];
eventsEmittingBlock();
[_bridge verify];
}
- (void)testSameEventTypesWithDifferentCoalesceKeysDontCoalesce
{
NSString *eventName = RCTNormalizeInputEventName(@"firstEvent");
RCTTestEvent *firstEvent = [[RCTTestEvent alloc] initWithViewTag:nil
eventName:eventName
body:_body
coalescingKey:0];
RCTTestEvent *secondEvent = [[RCTTestEvent alloc] initWithViewTag:nil
eventName:eventName
body:_body
coalescingKey:1];
__block dispatch_block_t eventsEmittingBlock;
[[_bridge expect] dispatchBlock:[OCMArg checkWithBlock:^(dispatch_block_t block) {
eventsEmittingBlock = block;
return YES;
}] queue:RCTJSThread];
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[firstEvent arguments]];
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[secondEvent arguments]];
args:[_testEvent arguments]];
[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:secondEvent];
[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:secondEvent];
[_eventDispatcher sendEvent:secondEvent];
[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:firstEvent];
eventsEmittingBlock();
[(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];
[_bridge verify];
}

View File

@ -97,8 +97,13 @@ RCT_EXTERN NSString *RCTNormalizeInputEventName(NSString *eventName);
/**
* Send a pre-prepared event object.
*
* Events are sent to JS as soon as the thread is free to process them.
* If an event can be coalesced and there is another compatible event waiting, the coalescing will happen immediately.
* If the event can be coalesced it is added to a pool of events that are sent at the beginning of the next js frame.
* Otherwise if the event cannot be coalesced we first flush the pool of coalesced events and the new event after that.
*
* Why it works this way?
* Making sure js gets events in the right order is crucial for correctly interpreting gestures.
* Unfortunately we cannot emit all events as they come. If we would do that we would have to emit scroll and touch moved event on every frame,
* which is too much data to transfer and process on older devices. This is especially bad when js starts lagging behind main thread.
*/
- (void)sendEvent:(id<RCTEvent>)event;

View File

@ -11,10 +11,7 @@
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTUtils.h"
#import "RCTProfile.h"
const NSInteger RCTTextUpdateLagWarningThreshold = 3;
@ -38,24 +35,38 @@ static NSNumber *RCTGetEventID(id<RCTEvent> event)
);
}
@interface RCTEventDispatcher() <RCTFrameUpdateObserver>
@end
@implementation RCTEventDispatcher
{
// We need this lock to protect access to _eventQueue and __eventsDispatchScheduled. It's filled in on main thread and consumed on js thread.
NSLock *_eventQueueLock;
NSMutableDictionary *_eventQueue;
BOOL _eventsDispatchScheduled;
NSLock *_eventQueueLock;
}
@synthesize bridge = _bridge;
@synthesize paused = _paused;
@synthesize pauseCallback = _pauseCallback;
RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
_bridge = bridge;
_paused = YES;
_eventQueue = [NSMutableDictionary new];
_eventQueueLock = [NSLock new];
_eventsDispatchScheduled = NO;
}
- (void)setPaused:(BOOL)paused
{
if (_paused != paused) {
_paused = paused;
if (_pauseCallback) {
_pauseCallback();
}
}
}
- (void)sendAppEventWithName:(NSString *)name body:(id)body
@ -128,23 +139,23 @@ RCT_EXPORT_MODULE()
- (void)sendEvent:(id<RCTEvent>)event
{
if (!event.canCoalesce) {
[self flushEventsQueue];
[self dispatchEvent:event];
return;
}
[_eventQueueLock lock];
NSNumber *eventID = RCTGetEventID(event);
id<RCTEvent> previousEvent = _eventQueue[eventID];
if (previousEvent) {
RCTAssert([event canCoalesce], @"Got event %@ which cannot be coalesced, but has the same eventID %@ as the previous event %@", event, eventID, previousEvent);
event = [previousEvent coalesceWithEvent:event];
}
_eventQueue[eventID] = event;
if (!_eventsDispatchScheduled) {
_eventsDispatchScheduled = YES;
[_bridge dispatchBlock:^{
[self flushEventsQueue];
} queue:RCTJSThread];
}
_eventQueue[eventID] = event;
self.paused = NO;
[_eventQueueLock unlock];
}
@ -159,13 +170,17 @@ RCT_EXPORT_MODULE()
return RCTJSThread;
}
// js thread only
- (void)didUpdateFrame:(__unused RCTFrameUpdate *)update
{
[self flushEventsQueue];
}
- (void)flushEventsQueue
{
[_eventQueueLock lock];
NSDictionary *eventQueue = _eventQueue;
_eventQueue = [NSMutableDictionary new];
_eventsDispatchScheduled = NO;
self.paused = YES;
[_eventQueueLock unlock];
for (id<RCTEvent> event in eventQueue.allValues) {