Skip to content

Commit

Permalink
Revert [RN][iOS] better event emitting
Browse files Browse the repository at this point in the history
Reviewed By: sahrens

Differential Revision: D3128586

fb-gh-sync-id: 5c3c79fa983f6c1c43319a6c14049f99e0dfee8a
fbshipit-source-id: 5c3c79fa983f6c1c43319a6c14049f99e0dfee8a
  • Loading branch information
frantic authored and Facebook Github Bot 5 committed Apr 1, 2016
1 parent cd79e26 commit 144dc30
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 110 deletions.
120 changes: 30 additions & 90 deletions Examples/UIExplorer/UIExplorerUnitTests/RCTEventDispatcherTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -83,8 +82,7 @@ - (void)setUp
{
[super setUp];

_bridge = [OCMockObject mockForClass:[RCTBatchedBridge class]];

_bridge = [OCMockObject mockForClass:[RCTBridge class]];
_eventDispatcher = [RCTEventDispatcher new];
[_eventDispatcher setValue:_bridge forKey:@"bridge"];

Expand All @@ -108,79 +106,61 @@ - (void)testLegacyEventsAreImmediatelyDispatched
[_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];
}
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[_testEvent arguments]];

[(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];

- (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
- (void)testNonCoalescingEventForcesColescedEventsToBeImmediatelyDispatched
{
__block dispatch_block_t eventsEmittingBlock;
[[_bridge expect] dispatchBlock:[OCMArg checkWithBlock:^(dispatch_block_t block) {
eventsEmittingBlock = block;
return YES;
}] queue:RCTJSThread];
RCTTestEvent *nonCoalescingEvent = [[RCTTestEvent alloc] initWithViewTag:nil
eventName:_eventName
body:@{}
coalescingKey:0];
nonCoalescingEvent.canCoalesce = NO;
[_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"
[[_bridge expect] enqueueJSCall:[[_testEvent class] moduleDotMethod]
args:[_testEvent arguments]];
eventsEmittingBlock();
[_bridge verify];

[[_bridge expect] enqueueJSCall:[[nonCoalescingEvent class] moduleDotMethod]
args:[nonCoalescingEvent arguments]];

[[_bridge expect] dispatchBlock:OCMOCK_ANY queue:RCTJSThread];
[_eventDispatcher sendEvent:_testEvent];
[_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];
}
Expand All @@ -189,60 +169,20 @@ - (void)testDifferentEventTypesDontCoalesce
{
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]];


[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:secondEvent];
[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:secondEvent];
[_eventDispatcher sendEvent:secondEvent];
[_eventDispatcher sendEvent:firstEvent];
[_eventDispatcher sendEvent:firstEvent];
[[_bridge expect] enqueueJSCall:@"RCTDeviceEventEmitter.emit"
args:[_testEvent arguments]];

eventsEmittingBlock();
[(id<RCTFrameUpdateObserver>)_eventDispatcher didUpdateFrame:nil];

[_bridge verify];
}
Expand Down
9 changes: 7 additions & 2 deletions React/Base/RCTEventDispatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
51 changes: 33 additions & 18 deletions React/Base/RCTEventDispatcher.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@

#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTBridge+Private.h"
#import "RCTUtils.h"
#import "RCTProfile.h"


const NSInteger RCTTextUpdateLagWarningThreshold = 3;

Expand All @@ -38,24 +35,38 @@
);
}

@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
Expand Down Expand Up @@ -128,23 +139,23 @@ - (void)sendTextEventWithType:(RCTTextEventType)type

- (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];
}
Expand All @@ -159,13 +170,17 @@ - (dispatch_queue_t)methodQueue
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) {
Expand Down

0 comments on commit 144dc30

Please sign in to comment.