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:
parent
cd79e269dc
commit
144dc30661
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue